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/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<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 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<std::string> 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<DesktopMediaList::Source> 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

View file

@ -27,9 +27,7 @@ class DesktopCapturer: public mate::EventEmitter,
public:
static mate::Handle<DesktopCapturer> 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;

View file

@ -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

View file

@ -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

View file

@ -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() {}

View file

@ -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_);
}
}

View file

@ -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 <index>'; 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`.