From dcb457e76ecc7ec530f9ececa5af2974ab5d17a5 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Tue, 6 Oct 2015 14:34:54 +0800 Subject: [PATCH] Refine API design: desktopCapturer.getSources. --- atom/browser/api/atom_api_desktop_capturer.cc | 69 ++++----- atom/browser/api/atom_api_desktop_capturer.h | 8 +- atom/browser/lib/desktop-capturer.coffee | 58 +++---- atom/renderer/api/lib/desktop-capturer.coffee | 21 +-- .../media/desktop_media_list_observer.h | 2 +- .../media/native_desktop_media_list.cc | 15 +- docs/api/desktop-capturer.md | 145 ++++++------------ 7 files changed, 124 insertions(+), 194 deletions(-) diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index a6556f6a89fa..116b0f4debf0 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -6,6 +6,7 @@ #include "atom/common/api/atom_api_native_image.h" #include "atom/common/node_includes.h" +#include "atom/common/native_mate_converters/gfx_converter.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/media/desktop_media_list.h" #include "native_mate/dictionary.h" @@ -14,13 +15,30 @@ #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" #include "third_party/webrtc/modules/desktop_capture/window_capturer.h" +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const DesktopMediaList::Source& source) { + mate::Dictionary dict(isolate, v8::Object::New(isolate)); + content::DesktopMediaID id = source.id; + dict.Set("name", base::UTF16ToUTF8(source.name)); + dict.Set("id", id.ToString()); + dict.Set( + "thumbnail", + atom::api::NativeImage::Create(isolate, gfx::Image(source.thumbnail))); + return ConvertToV8(isolate, dict); + } +}; + +} // namespace mate + namespace atom { namespace api { namespace { -// Refresh every second. -const int kUpdatePeriod = 1000; const int kThumbnailWidth = 150; const int kThumbnailHeight = 150; } // namespace @@ -31,7 +49,7 @@ DesktopCapturer::DesktopCapturer() { DesktopCapturer::~DesktopCapturer() { } -void DesktopCapturer::StartUpdating(const mate::Dictionary& args) { +void DesktopCapturer::StartHandling(const mate::Dictionary& args) { std::vector sources; if (!args.Get("types", &sources)) return; @@ -68,64 +86,41 @@ void DesktopCapturer::StartUpdating(const mate::Dictionary& args) { media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), window_capturer.Pass())); - int update_period = kUpdatePeriod; - int thumbnail_width = kThumbnailWidth, thumbnail_height = kThumbnailHeight; - args.Get("updatePeriod", &update_period); - args.Get("thumbnailWidth", &thumbnail_width); - args.Get("thumbnailHeight", &thumbnail_height); + gfx::Size thumbnail_size(kThumbnailWidth, kThumbnailHeight); + args.Get("thumbnailSize", &thumbnail_size); - media_list_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds( - update_period)); - media_list_->SetThumbnailSize(gfx::Size(thumbnail_width, thumbnail_height)); + media_list_->SetThumbnailSize(thumbnail_size); media_list_->StartUpdating(this); } -void DesktopCapturer::StopUpdating() { - media_list_.reset(); -} - void DesktopCapturer::OnSourceAdded(int index) { - EmitDesktopCapturerEvent("source-added", index, false); } void DesktopCapturer::OnSourceRemoved(int index) { - EmitDesktopCapturerEvent("source-removed", index, false); } -// Ignore this event. void DesktopCapturer::OnSourceMoved(int old_index, int new_index) { } void DesktopCapturer::OnSourceNameChanged(int index) { - EmitDesktopCapturerEvent("source-removed", index, false); } void DesktopCapturer::OnSourceThumbnailChanged(int index) { - EmitDesktopCapturerEvent("source-thumbnail-changed", index, true); } -void DesktopCapturer::OnRefreshFinished() { - Emit("refresh-finished"); -} - -void DesktopCapturer::EmitDesktopCapturerEvent( - const std::string& event_name, int index, bool with_thumbnail) { - const DesktopMediaList::Source& source = media_list_->GetSource(index); - content::DesktopMediaID id = source.id; - if (!with_thumbnail) - Emit(event_name, id.ToString(), base::UTF16ToUTF8(source.name)); - else { - Emit(event_name, id.ToString(), base::UTF16ToUTF8(source.name), - atom::api::NativeImage::Create(isolate(), - gfx::Image(source.thumbnail))); - } +bool DesktopCapturer::OnRefreshFinished() { + std::vector sources; + for (int i = 0; i < media_list_->GetSourceCount(); ++i) + sources.push_back(media_list_->GetSource(i)); + media_list_.reset(); + Emit("refresh-finished", sources); + return false; } mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) - .SetMethod("startUpdating", &DesktopCapturer::StartUpdating) - .SetMethod("stopUpdating", &DesktopCapturer::StopUpdating); + .SetMethod("startHandling", &DesktopCapturer::StartHandling); } // static diff --git a/atom/browser/api/atom_api_desktop_capturer.h b/atom/browser/api/atom_api_desktop_capturer.h index f0620c8e9cf8..32687abf7367 100644 --- a/atom/browser/api/atom_api_desktop_capturer.h +++ b/atom/browser/api/atom_api_desktop_capturer.h @@ -27,9 +27,7 @@ class DesktopCapturer: public mate::EventEmitter, public: static mate::Handle Create(v8::Isolate* isolate); - void StartUpdating(const mate::Dictionary& args); - - void StopUpdating(); + void StartHandling(const mate::Dictionary& args); protected: DesktopCapturer(); @@ -41,11 +39,9 @@ class DesktopCapturer: public mate::EventEmitter, void OnSourceMoved(int old_index, int new_index) override; void OnSourceNameChanged(int index) override; void OnSourceThumbnailChanged(int index) override; - void OnRefreshFinished() override; + bool OnRefreshFinished() override; private: - void EmitDesktopCapturerEvent( - const std::string& event_name, int index, bool with_thumbnail); // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; diff --git a/atom/browser/lib/desktop-capturer.coffee b/atom/browser/lib/desktop-capturer.coffee index 0177d9721bb5..daef854cb74e 100644 --- a/atom/browser/lib/desktop-capturer.coffee +++ b/atom/browser/lib/desktop-capturer.coffee @@ -1,38 +1,38 @@ ipc = require 'ipc' -BrowserWindow = require 'browser-window' # The browser module manages all desktop-capturer moduels in renderer process. desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer -getWebContentsFromId = (id) -> - windows = BrowserWindow.getAllWindows() - return window.webContents for window in windows when window.webContents?.getId() == id +isOptionsEqual = (opt1, opt2) -> + return JSON.stringify opt1 is JSON.stringify opt2 -# The set for tracking id of webContents. -webContentsIds = new Set +# A queue for holding all requests from renderer process. +requestsQueue = [] -stopDesktopCapture = (id) -> - webContentsIds.delete id - # Stop updating if no renderer process listens the desktop capturer. - if webContentsIds.size is 0 - desktopCapturer.stopUpdating() +ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options) -> + request = { options: options, webContents: event.sender } + desktopCapturer.startHandling options if requestsQueue.length is 0 + requestsQueue.push request + # If the WebContents is destroyed before receiving result, just remove the + # reference from requestsQueue to make the module not send the result to it. + event.sender.once 'destroyed', () -> + request.webContents = null -# Handle `desktopCapturer.startUpdating` API. -ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', (event, args) -> - id = event.sender.getId() - if not webContentsIds.has id - # Stop sending desktop capturer events to the destroyed webContents. - event.sender.on 'destroyed', ()-> - stopDesktopCapture id - # Start updating the desktopCapturer if it doesn't. - if webContentsIds.size is 0 - desktopCapturer.startUpdating args - webContentsIds.add id +desktopCapturer.emit = (event_name, event, sources) -> + # Receiving sources result from main process, now send them back to renderer. + handledRequest = requestsQueue.shift 0 + result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources) + handledRequest.webContents?.send 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', result -# Handle `desktopCapturer.stopUpdating` API. -ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING', (event) -> - stopDesktopCapture event.sender.getId() - -desktopCapturer.emit = (event_name, event, desktopId, name, thumbnail) -> - webContentsIds.forEach (id) -> - getWebContentsFromId(id).send 'ATOM_RENDERER_DESKTOP_CAPTURER', event_name, desktopId, name, thumbnail?.toDataUrl() + # Check the queue to see whether there is other same request. If has, handle + # it for reducing redunplicated `desktopCaptuer.startHandling` calls. + unhandledRequestsQueue = [] + for request in requestsQueue + if isOptionsEqual handledRequest.options, request.options + request.webContents?.send 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', result + else + unhandledRequestsQueue.push request + requestsQueue = unhandledRequestsQueue + # If the requestsQueue is not empty, start a new request handling. + if requestsQueue.length > 0 + desktopCapturer.startHandling requestsQueue[0].options diff --git a/atom/renderer/api/lib/desktop-capturer.coffee b/atom/renderer/api/lib/desktop-capturer.coffee index ae5e054c2a75..7ac5a4024c9b 100644 --- a/atom/renderer/api/lib/desktop-capturer.coffee +++ b/atom/renderer/api/lib/desktop-capturer.coffee @@ -1,19 +1,10 @@ ipc = require 'ipc' -remote = require 'remote' NativeImage = require 'native-image' -EventEmitter = require('events').EventEmitter -desktopCapturer = new EventEmitter +getSources = (options, callback) -> + ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options + ipc.once 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', (sources) -> + callback ({id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.thumbnail} for source in sources) -desktopCapturer.startUpdating = (args) -> - ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', args - -desktopCapturer.stopUpdating = () -> - ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING' - -ipc.on 'ATOM_RENDERER_DESKTOP_CAPTURER', (event_name, id, name, thumbnail) -> - if not thumbnail - return desktopCapturer.emit event_name, id, name - desktopCapturer.emit event_name, id, name, NativeImage.createFromDataUrl thumbnail - -module.exports = desktopCapturer +module.exports = + getSources: getSources diff --git a/chromium_src/chrome/browser/media/desktop_media_list_observer.h b/chromium_src/chrome/browser/media/desktop_media_list_observer.h index eccaa407d2df..34cd626bf6bf 100644 --- a/chromium_src/chrome/browser/media/desktop_media_list_observer.h +++ b/chromium_src/chrome/browser/media/desktop_media_list_observer.h @@ -14,7 +14,7 @@ class DesktopMediaListObserver { virtual void OnSourceMoved(int old_index, int new_index) = 0; virtual void OnSourceNameChanged(int index) = 0; virtual void OnSourceThumbnailChanged(int index) = 0; - virtual void OnRefreshFinished() = 0; + virtual bool OnRefreshFinished() = 0; protected: virtual ~DesktopMediaListObserver() {} diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc index a326ded91b80..4d15b068b7c3 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -358,10 +358,13 @@ void NativeDesktopMediaList::OnSourceThumbnail( } void NativeDesktopMediaList::OnRefreshFinished() { - observer_->OnRefreshFinished(); - BrowserThread::PostDelayedTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&NativeDesktopMediaList::Refresh, - weak_factory_.GetWeakPtr()), - update_period_); + // Give a chance to the observer to stop the refresh work. + bool is_continue = observer_->OnRefreshFinished(); + if (is_continue) { + BrowserThread::PostDelayedTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&NativeDesktopMediaList::Refresh, + weak_factory_.GetWeakPtr()), + update_period_); + } } diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 6dc971cd2066..916538a4e9f8 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -7,36 +7,27 @@ screen and individual app windows. // In the renderer process. var desktopCapturer = require('desktop-capturer'); -desktopCapturer.on('source-added', function(id, name) { - console.log("source " + name + " is added."); - // navigator.webkitGetUserMedia is not ready for use now. -}); - -desktopCapturer.on('source-thumbnail-changed', function(id, name, thumbnail) { - if (name == "Electron") { - // stopUpdating since we have found the window that we want to capture. - desktopCapturer.stopUpdating(); - - // It's ready to use webkitGetUserMedia right now. - navigator.webkitGetUserMedia({ - audio: false, - video: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: id, - minWidth: 1280, - maxWidth: 1280, - minHeight: 720, - maxHeight: 720 +desktopCapturer.getSources({types: ['window', 'screen']}, function(sources) { + for (var i = 0; i < sources.length; ++i) { + if (sources[i].name == "Electron") { + navigator.webkitGetUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: sources[i].id, + minWidth: 1280, + maxWidth: 1280, + minHeight: 720, + maxHeight: 720 + } } - } - }, gotStream, getUserMediaError); + }, gotStream, getUserMediaError); + return; + } } }); -// Let's start updating after setting all event of `desktopCapturer` -desktopCapturer.startUpdating(); - function gotStream(stream) { document.querySelector('video').src = URL.createObjectURL(stream); } @@ -46,9 +37,35 @@ function getUserMediaError(e) { } ``` -## Events +## Methods -### Event: 'source-added' +The `desktopCapturer` module has the following methods: + +### `desktopCapturer.getSources(options, callback)` + +`options` Object, properties: +* `types` Array - An array of String that enums the types of desktop sources. + * `screen` String - Screen + * `window` String - Individual window +* `thumnailSize` Object (optional) - The suggested size that thumbnail should be + scaled. + * `width` Integer - The width of thumbnail. By default, it is 150px. + * `height` Integer - The height of thumbnail. By default, it is 150px. + +`callback` Function - `function(sources) {}` + +* `Sources` Array - An array of Source + +Gets all desktop sources. + +**Note:** There is no garuantee that the size of `source.thumbnail` is always +the same as the `thumnailSize` in `options`. It also depends on the scale of the +screen or window. + +## Source + +`Source` is an object represents a captured screen or individual window. It has +following properties: * `id` String - The id of the captured window or screen used in `navigator.webkitGetUserMedia`. The format looks like 'window:XX' or @@ -56,76 +73,4 @@ function getUserMediaError(e) { * `name` String - The descriped name of the capturing screen or window. If the source is a screen, the name will be 'Entire Screen' or 'Screen '; if it is a window, the name will be the window's title. - -Emits when there is a new source added, usually a new window is created or a new -screen is attached. - -**Note:** `navigator.webkitGetUserMedia` is not ready for use in this event. - -### Event: 'source-removed' - -* `id` String - The id of the captured window or screen used in - `navigator.webkitGetUserMedia`. -* `name` String - The descriped name of the capturing screen or window. - -Emits when there is a source removed. - -### Event: 'source-name-changed' - -* `id` String - The id of the captured window or screen used in - `navigator.webkitGetUserMedia`. -* `name` String - The descriped name of the capturing screen or window. - -Emits when the name of source is changed. - -### Event: 'source-thumbnail-changed' - -* `id` String - The id of the captured window or screen used in - `navigator.webkitGetUserMedia`. -* `name` String - The descriped name of the capturing screen or window. * `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image. - -Emits when the thumbnail of source is changed. `desktopCapturer` will refresh -all sources every second. - -### Event: 'refresh-finished' - -Emits when `desktopCapturer` finishes a refresh. - -## Methods - -The `desktopCapturer` module has the following methods: - -### `desktopCapturer.startUpdating(options)` - -`options` Object, properties: - -* `types` Array - An array of String that enums the types of desktop sources. - * `screen` String - Screen - * `window` String - Individual window -* `updatePeriod` Integer (optional) - The update period in milliseconds. By - default, `desktopCapturer` updates every second. -* `thumbnailWidth` Integer (optional) - The width of thumbnail. By default, it - is 150px. -* `thumbnailHeight` Integer (optional) - The height of thumbnail. By default, it - is 150px. - -Starts updating desktopCapturer. The events of `desktopCapturer` will only be -emitted after `startUpdating` API is invoked. - -**Note:** At beginning, the desktopCapturer is initially empty, so the -`source-added` event will be emitted for each existing source as it is -enumrated. -On Windows, you will see the screen ficker when desktopCapturer starts updating. -This is normal because the desktop effects(e.g. Aero) will be disabled when -desktop capturer is working. The ficker will disappear once -`desktopCapturer.stopUpdating()` is invoked. - -### `desktopCapturer.stopUpdating()` - -Stops updating desktopCapturer. The events of `desktopCapturer` will not be -emitted after the API is invoked. - -**Note:** It is a good practice to call `stopUpdating` when you do not need -getting any infomation of sources, usually after passing a id to -`navigator.webkitGetUserMedia`.