Refine API design: desktopCapturer.getSources.

This commit is contained in:
Haojian Wu 2015-10-06 14:34:54 +08:00
parent 36c0ad7fda
commit dcb457e76e
7 changed files with 124 additions and 194 deletions

View file

@ -6,6 +6,7 @@
#include "atom/common/api/atom_api_native_image.h" #include "atom/common/api/atom_api_native_image.h"
#include "atom/common/node_includes.h" #include "atom/common/node_includes.h"
#include "atom/common/native_mate_converters/gfx_converter.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "chrome/browser/media/desktop_media_list.h" #include "chrome/browser/media/desktop_media_list.h"
#include "native_mate/dictionary.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/screen_capturer.h"
#include "third_party/webrtc/modules/desktop_capture/window_capturer.h" #include "third_party/webrtc/modules/desktop_capture/window_capturer.h"
namespace mate {
template<>
struct Converter<DesktopMediaList::Source> {
static v8::Local<v8::Value> 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 atom {
namespace api { namespace api {
namespace { namespace {
// Refresh every second.
const int kUpdatePeriod = 1000;
const int kThumbnailWidth = 150; const int kThumbnailWidth = 150;
const int kThumbnailHeight = 150; const int kThumbnailHeight = 150;
} // namespace } // namespace
@ -31,7 +49,7 @@ DesktopCapturer::DesktopCapturer() {
DesktopCapturer::~DesktopCapturer() { DesktopCapturer::~DesktopCapturer() {
} }
void DesktopCapturer::StartUpdating(const mate::Dictionary& args) { void DesktopCapturer::StartHandling(const mate::Dictionary& args) {
std::vector<std::string> sources; std::vector<std::string> sources;
if (!args.Get("types", &sources)) if (!args.Get("types", &sources))
return; return;
@ -68,64 +86,41 @@ void DesktopCapturer::StartUpdating(const mate::Dictionary& args) {
media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(),
window_capturer.Pass())); window_capturer.Pass()));
int update_period = kUpdatePeriod; gfx::Size thumbnail_size(kThumbnailWidth, kThumbnailHeight);
int thumbnail_width = kThumbnailWidth, thumbnail_height = kThumbnailHeight; args.Get("thumbnailSize", &thumbnail_size);
args.Get("updatePeriod", &update_period);
args.Get("thumbnailWidth", &thumbnail_width);
args.Get("thumbnailHeight", &thumbnail_height);
media_list_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds( media_list_->SetThumbnailSize(thumbnail_size);
update_period));
media_list_->SetThumbnailSize(gfx::Size(thumbnail_width, thumbnail_height));
media_list_->StartUpdating(this); media_list_->StartUpdating(this);
} }
void DesktopCapturer::StopUpdating() {
media_list_.reset();
}
void DesktopCapturer::OnSourceAdded(int index) { void DesktopCapturer::OnSourceAdded(int index) {
EmitDesktopCapturerEvent("source-added", index, false);
} }
void DesktopCapturer::OnSourceRemoved(int index) { void DesktopCapturer::OnSourceRemoved(int index) {
EmitDesktopCapturerEvent("source-removed", index, false);
} }
// Ignore this event.
void DesktopCapturer::OnSourceMoved(int old_index, int new_index) { void DesktopCapturer::OnSourceMoved(int old_index, int new_index) {
} }
void DesktopCapturer::OnSourceNameChanged(int index) { void DesktopCapturer::OnSourceNameChanged(int index) {
EmitDesktopCapturerEvent("source-removed", index, false);
} }
void DesktopCapturer::OnSourceThumbnailChanged(int index) { void DesktopCapturer::OnSourceThumbnailChanged(int index) {
EmitDesktopCapturerEvent("source-thumbnail-changed", index, true);
} }
void DesktopCapturer::OnRefreshFinished() { bool DesktopCapturer::OnRefreshFinished() {
Emit("refresh-finished"); std::vector<DesktopMediaList::Source> sources;
} for (int i = 0; i < media_list_->GetSourceCount(); ++i)
sources.push_back(media_list_->GetSource(i));
void DesktopCapturer::EmitDesktopCapturerEvent( media_list_.reset();
const std::string& event_name, int index, bool with_thumbnail) { Emit("refresh-finished", sources);
const DesktopMediaList::Source& source = media_list_->GetSource(index); return false;
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)));
}
} }
mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder(
v8::Isolate* isolate) { v8::Isolate* isolate) {
return mate::ObjectTemplateBuilder(isolate) return mate::ObjectTemplateBuilder(isolate)
.SetMethod("startUpdating", &DesktopCapturer::StartUpdating) .SetMethod("startHandling", &DesktopCapturer::StartHandling);
.SetMethod("stopUpdating", &DesktopCapturer::StopUpdating);
} }
// static // static

View file

@ -27,9 +27,7 @@ class DesktopCapturer: public mate::EventEmitter,
public: public:
static mate::Handle<DesktopCapturer> Create(v8::Isolate* isolate); static mate::Handle<DesktopCapturer> Create(v8::Isolate* isolate);
void StartUpdating(const mate::Dictionary& args); void StartHandling(const mate::Dictionary& args);
void StopUpdating();
protected: protected:
DesktopCapturer(); DesktopCapturer();
@ -41,11 +39,9 @@ class DesktopCapturer: public mate::EventEmitter,
void OnSourceMoved(int old_index, int new_index) override; void OnSourceMoved(int old_index, int new_index) override;
void OnSourceNameChanged(int index) override; void OnSourceNameChanged(int index) override;
void OnSourceThumbnailChanged(int index) override; void OnSourceThumbnailChanged(int index) override;
void OnRefreshFinished() override; bool OnRefreshFinished() override;
private: private:
void EmitDesktopCapturerEvent(
const std::string& event_name, int index, bool with_thumbnail);
// mate::Wrappable: // mate::Wrappable:
mate::ObjectTemplateBuilder GetObjectTemplateBuilder( mate::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) override; v8::Isolate* isolate) override;

View file

@ -1,38 +1,38 @@
ipc = require 'ipc' ipc = require 'ipc'
BrowserWindow = require 'browser-window'
# The browser module manages all desktop-capturer moduels in renderer process. # The browser module manages all desktop-capturer moduels in renderer process.
desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer
getWebContentsFromId = (id) -> isOptionsEqual = (opt1, opt2) ->
windows = BrowserWindow.getAllWindows() return JSON.stringify opt1 is JSON.stringify opt2
return window.webContents for window in windows when window.webContents?.getId() == id
# The set for tracking id of webContents. # A queue for holding all requests from renderer process.
webContentsIds = new Set requestsQueue = []
stopDesktopCapture = (id) -> ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, options) ->
webContentsIds.delete id request = { options: options, webContents: event.sender }
# Stop updating if no renderer process listens the desktop capturer. desktopCapturer.startHandling options if requestsQueue.length is 0
if webContentsIds.size is 0 requestsQueue.push request
desktopCapturer.stopUpdating() # 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. desktopCapturer.emit = (event_name, event, sources) ->
ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', (event, args) -> # Receiving sources result from main process, now send them back to renderer.
id = event.sender.getId() handledRequest = requestsQueue.shift 0
if not webContentsIds.has id result = ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() } for source in sources)
# Stop sending desktop capturer events to the destroyed webContents. handledRequest.webContents?.send 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', result
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
# Handle `desktopCapturer.stopUpdating` API. # Check the queue to see whether there is other same request. If has, handle
ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING', (event) -> # it for reducing redunplicated `desktopCaptuer.startHandling` calls.
stopDesktopCapture event.sender.getId() unhandledRequestsQueue = []
for request in requestsQueue
desktopCapturer.emit = (event_name, event, desktopId, name, thumbnail) -> if isOptionsEqual handledRequest.options, request.options
webContentsIds.forEach (id) -> request.webContents?.send 'ATOM_REDNERER_DESKTOP_CAPTURER_RESULT', result
getWebContentsFromId(id).send 'ATOM_RENDERER_DESKTOP_CAPTURER', event_name, desktopId, name, thumbnail?.toDataUrl() 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

View file

@ -1,19 +1,10 @@
ipc = require 'ipc' ipc = require 'ipc'
remote = require 'remote'
NativeImage = require 'native-image' NativeImage = require 'native-image'
EventEmitter = require('events').EventEmitter getSources = (options, callback) ->
desktopCapturer = new EventEmitter 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) -> module.exports =
ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', args getSources: getSources
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

View file

@ -14,7 +14,7 @@ class DesktopMediaListObserver {
virtual void OnSourceMoved(int old_index, int new_index) = 0; virtual void OnSourceMoved(int old_index, int new_index) = 0;
virtual void OnSourceNameChanged(int index) = 0; virtual void OnSourceNameChanged(int index) = 0;
virtual void OnSourceThumbnailChanged(int index) = 0; virtual void OnSourceThumbnailChanged(int index) = 0;
virtual void OnRefreshFinished() = 0; virtual bool OnRefreshFinished() = 0;
protected: protected:
virtual ~DesktopMediaListObserver() {} virtual ~DesktopMediaListObserver() {}

View file

@ -358,10 +358,13 @@ void NativeDesktopMediaList::OnSourceThumbnail(
} }
void NativeDesktopMediaList::OnRefreshFinished() { void NativeDesktopMediaList::OnRefreshFinished() {
observer_->OnRefreshFinished(); // Give a chance to the observer to stop the refresh work.
bool is_continue = observer_->OnRefreshFinished();
if (is_continue) {
BrowserThread::PostDelayedTask( BrowserThread::PostDelayedTask(
BrowserThread::UI, FROM_HERE, BrowserThread::UI, FROM_HERE,
base::Bind(&NativeDesktopMediaList::Refresh, base::Bind(&NativeDesktopMediaList::Refresh,
weak_factory_.GetWeakPtr()), weak_factory_.GetWeakPtr()),
update_period_); update_period_);
}
} }

View file

@ -7,23 +7,15 @@ screen and individual app windows.
// In the renderer process. // In the renderer process.
var desktopCapturer = require('desktop-capturer'); var desktopCapturer = require('desktop-capturer');
desktopCapturer.on('source-added', function(id, name) { desktopCapturer.getSources({types: ['window', 'screen']}, function(sources) {
console.log("source " + name + " is added."); for (var i = 0; i < sources.length; ++i) {
// navigator.webkitGetUserMedia is not ready for use now. if (sources[i].name == "Electron") {
});
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({ navigator.webkitGetUserMedia({
audio: false, audio: false,
video: { video: {
mandatory: { mandatory: {
chromeMediaSource: 'desktop', chromeMediaSource: 'desktop',
chromeMediaSourceId: id, chromeMediaSourceId: sources[i].id,
minWidth: 1280, minWidth: 1280,
maxWidth: 1280, maxWidth: 1280,
minHeight: 720, minHeight: 720,
@ -31,12 +23,11 @@ desktopCapturer.on('source-thumbnail-changed', function(id, name, thumbnail) {
} }
} }
}, gotStream, getUserMediaError); }, gotStream, getUserMediaError);
return;
}
} }
}); });
// Let's start updating after setting all event of `desktopCapturer`
desktopCapturer.startUpdating();
function gotStream(stream) { function gotStream(stream) {
document.querySelector('video').src = URL.createObjectURL(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 * `id` String - The id of the captured window or screen used in
`navigator.webkitGetUserMedia`. The format looks like 'window:XX' or `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 * `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 <index>'; if source is a screen, the name will be 'Entire Screen' or 'Screen <index>'; if
it is a window, the name will be the window's title. 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. * `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`.