Refine: make desktop-capturer as a renderer module.

This commit is contained in:
Haojian Wu 2015-10-04 09:35:00 +08:00
Родитель 48fbd47416
Коммит 1e69ef79de
12 изменённых файлов: 237 добавлений и 74 удалений

Просмотреть файл

@ -5,7 +5,6 @@
#include "atom/browser/api/atom_api_desktop_capturer.h"
#include "atom/common/api/atom_api_native_image.h"
#include "atom/common/native_mate_converters/callback.h"
#include "atom/common/node_includes.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/media/desktop_media_list.h"
@ -25,7 +24,8 @@ struct Converter<DesktopMediaList::Source> {
content::DesktopMediaID id = source.id;
dict.Set("name", base::UTF16ToUTF8(source.name));
dict.Set("id", id.ToString());
dict.Set("thumbnail",
dict.Set(
"thumbnail",
atom::api::NativeImage::Create(isolate, gfx::Image(source.thumbnail)));
return ConvertToV8(isolate, dict);
}
@ -38,15 +38,29 @@ namespace atom {
namespace api {
namespace {
// The wrapDesktopCapturer funtion which is implemented in JavaScript
using WrapDesktopCapturerCallback = base::Callback<void(v8::Local<v8::Value>)>;
WrapDesktopCapturerCallback g_wrap_desktop_capturer;
const int kThumbnailWidth = 150;
const int kThumbnailHeight = 150;
} // namespace
DesktopCapturer::DesktopCapturer(bool show_screens, bool show_windows) {
DesktopCapturer::DesktopCapturer() {
}
DesktopCapturer::~DesktopCapturer() {
}
void DesktopCapturer::StartUpdating(const std::vector<std::string>& sources) {
bool show_screens = false;
bool show_windows = false;
for (const auto& source_type : sources) {
if (source_type == "screen")
show_screens = true;
else if (source_type == "window")
show_windows = true;
}
if (!show_windows && !show_screens)
return;
webrtc::DesktopCaptureOptions options =
webrtc::DesktopCaptureOptions::CreateDefault();
@ -70,54 +84,39 @@ DesktopCapturer::DesktopCapturer(bool show_screens, bool show_windows) {
media_list_->StartUpdating(this);
}
DesktopCapturer::~DesktopCapturer() {
}
const DesktopMediaList::Source& DesktopCapturer::GetSource(int index) {
return media_list_->GetSource(index);
void DesktopCapturer::StopUpdating() {
media_list_.reset();
}
void DesktopCapturer::OnSourceAdded(int index) {
Emit("source-added", index);
Emit("source-added", media_list_->GetSource(index));
}
void DesktopCapturer::OnSourceRemoved(int index) {
Emit("source-removed", index);
Emit("source-removed", media_list_->GetSource(index));
}
void DesktopCapturer::OnSourceMoved(int old_index, int new_index) {
Emit("source-moved", old_index, new_index);
}
void DesktopCapturer::OnSourceNameChanged(int index) {
Emit("source-name-changed", index);
Emit("source-name-changed", media_list_->GetSource(index));
}
void DesktopCapturer::OnSourceThumbnailChanged(int index) {
Emit("source-thumbnail-changed", index);
Emit("source-thumbnail-changed", media_list_->GetSource(index));
}
mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
return mate::ObjectTemplateBuilder(isolate)
.SetMethod("getSource", &DesktopCapturer::GetSource);
}
void SetWrapDesktopCapturer(const WrapDesktopCapturerCallback& callback) {
g_wrap_desktop_capturer = callback;
}
void ClearWrapDesktopCapturer() {
g_wrap_desktop_capturer.Reset();
.SetMethod("startUpdating", &DesktopCapturer::StartUpdating)
.SetMethod("stopUpdating", &DesktopCapturer::StopUpdating);
}
// static
mate::Handle<DesktopCapturer> DesktopCapturer::Create(v8::Isolate* isolate,
bool show_screens, bool show_windows) {
auto handle = mate::CreateHandle(isolate,
new DesktopCapturer(show_screens, show_windows));
g_wrap_desktop_capturer.Run(handle.ToV8());
return handle;
mate::Handle<DesktopCapturer> DesktopCapturer::Create(v8::Isolate* isolate) {
return mate::CreateHandle(isolate, new DesktopCapturer);
}
} // namespace api
@ -130,9 +129,7 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
v8::Local<v8::Context> context, void* priv) {
v8::Isolate* isolate = context->GetIsolate();
mate::Dictionary dict(isolate, exports);
dict.SetMethod("_setWrapDesktopCapturer", &atom::api::SetWrapDesktopCapturer);
dict.SetMethod("_clearWrapDesktopCapturer",
&atom::api::ClearWrapDesktopCapturer);
dict.Set("desktopCapturer", atom::api::DesktopCapturer::Create(isolate));
}
} // namespace

Просмотреть файл

@ -2,8 +2,11 @@
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_
#define ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_
#ifndef ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_
#define ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_
#include <string>
#include <vector>
#include "base/memory/scoped_ptr.h"
#include "atom/browser/api/event_emitter.h"
@ -18,13 +21,13 @@ namespace api {
class DesktopCapturer: public mate::EventEmitter,
public DesktopMediaListObserver {
public:
static mate::Handle<DesktopCapturer> Create(v8::Isolate* isolate,
bool show_screens, bool show_windows);
static mate::Handle<DesktopCapturer> Create(v8::Isolate* isolate);
const DesktopMediaList::Source& GetSource(int index);
void StartUpdating(const std::vector<std::string>& sources);
void StopUpdating();
protected:
DesktopCapturer(bool show_screens, bool show_windows);
DesktopCapturer();
~DesktopCapturer();
// DesktopMediaListObserver overrides.
@ -48,4 +51,4 @@ class DesktopCapturer: public mate::EventEmitter,
} // namespace atom
#endif // ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_
#endif // ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_

Просмотреть файл

@ -56,21 +56,6 @@ Screen::~Screen() {
screen_->RemoveObserver(this);
}
mate::Handle<atom::api::DesktopCapturer> Screen::GetDesktopCapturer(
const std::vector<std::string>& sources) {
bool show_screens = false;
bool show_windows = false;
for (const auto& source_type : sources) {
if (source_type == "screen") {
show_screens = true;
} else if (source_type == "window") {
show_windows = true;
}
}
return DesktopCapturer::Create(isolate(), show_screens, show_windows);
}
gfx::Point Screen::GetCursorScreenPoint() {
return screen_->GetCursorScreenPoint();
}
@ -122,8 +107,7 @@ mate::ObjectTemplateBuilder Screen::GetObjectTemplateBuilder(
.SetMethod("getPrimaryDisplay", &Screen::GetPrimaryDisplay)
.SetMethod("getAllDisplays", &Screen::GetAllDisplays)
.SetMethod("getDisplayNearestPoint", &Screen::GetDisplayNearestPoint)
.SetMethod("getDisplayMatching", &Screen::GetDisplayMatching)
.SetMethod("getDesktopCapturer", &Screen::GetDesktopCapturer);
.SetMethod("getDisplayMatching", &Screen::GetDisplayMatching);
}
// static

Просмотреть файл

@ -6,9 +6,7 @@
#define ATOM_BROWSER_API_ATOM_API_SCREEN_H_
#include <vector>
#include <string>
#include "atom/browser/api/atom_api_desktop_capturer.h"
#include "atom/browser/api/event_emitter.h"
#include "native_mate/handle.h"
#include "ui/gfx/display_observer.h"
@ -38,9 +36,6 @@ class Screen : public mate::EventEmitter,
gfx::Display GetDisplayNearestPoint(const gfx::Point& point);
gfx::Display GetDisplayMatching(const gfx::Rect& match_rect);
mate::Handle<DesktopCapturer> GetDesktopCapturer(
const std::vector<std::string>& sources);
// gfx::DisplayObserver:
void OnDisplayAdded(const gfx::Display& new_display) override;
void OnDisplayRemoved(const gfx::Display& old_display) override;

Просмотреть файл

@ -3,18 +3,17 @@ EventEmitter = require('events').EventEmitter
bindings = process.atomBinding 'app'
sessionBindings = process.atomBinding 'session'
downloadItemBindings = process.atomBinding 'download_item'
desktopCapturerBindings = process.atomBinding 'desktop_capturer'
app = bindings.app
app.__proto__ = EventEmitter.prototype
wrapToEventListener = (item) ->
# item is an Event Emitter.
item.__proto__ = EventEmitter.prototype
wrapSession = (session) ->
# session is an Event Emitter.
session.__proto__ = EventEmitter.prototype
wrapDownloadItem = (download_item) ->
# download_item is an Event Emitter.
wrapToEventListener download_item
download_item.__proto__ = EventEmitter.prototype
# Be compatible with old APIs.
download_item.url = download_item.getUrl()
download_item.filename = download_item.getFilename()
@ -59,14 +58,11 @@ app.resolveProxy = -> @defaultSession.resolveProxy.apply @defaultSession, argume
app.on 'activate', (event, hasVisibleWindows) -> @emit 'activate-with-no-open-windows' if not hasVisibleWindows
# Session wrapper.
sessionBindings._setWrapSession wrapToEventListener
sessionBindings._setWrapSession wrapSession
process.once 'exit', sessionBindings._clearWrapSession
downloadItemBindings._setWrapDownloadItem wrapDownloadItem
process.once 'exit', downloadItemBindings._clearWrapDownloadItem
desktopCapturerBindings._setWrapDesktopCapturer wrapToEventListener
process.once 'exit', desktopCapturerBindings._clearWrapDesktopCapturer
# Only one App object pemitted.
module.exports = app

Просмотреть файл

@ -0,0 +1,42 @@
ipc = require 'ipc'
BrowserWindow = require 'browser-window'
EventEmitter = require('events').EventEmitter
# The browser module manages all desktop-capturer moduels in renderer process.
desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer
desktopCapturer.__proto__ = EventEmitter.prototype
getWebContentsFromId = (id) ->
windows = BrowserWindow.getAllWindows()
return window.webContents for window in windows when window.webContents?.getId() == id
# The set for tracking id of webContents.
webContentsIds = new Set
stopDesktopCapture = (id) ->
webContentsIds.delete id
if webContentsIds.size is 0
desktopCapturer.stopUpdating()
ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_REQUIRED', (event) ->
id = event.sender.getId()
# Remove the tracked webContents when it is destroyed.
getWebContentsFromId(id).on 'destroyed', ()->
stopDesktopCapture id
event.returnValue = 'done'
# Handle `desktopCapturer.startUpdating` API.
ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', (event, args) ->
id = event.sender.getId()
webContentsIds.add id
desktopCapturer.startUpdating args
# Handle `desktopCapturer.stopUpdating` API.
ipc.on 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING', (event) ->
stopDesktopCapture event.sender.getId()
for event_name in ['source-added', 'source-removed', 'source-name-changed', "source-thumbnail-changed"]
do (event_name) ->
desktopCapturer.on event_name, (event, source) ->
webContentsIds.forEach (id) ->
getWebContentsFromId(id).send event_name, { id: source.id, name: source.name, dataUrl: source.thumbnail.toDataUrl() }

Просмотреть файл

@ -92,6 +92,9 @@ app.setAppPath packagePath
# Load the chrome extension support.
require './chrome-extension'
# Load internal desktop-capturer module.
require './desktop-capturer'
# Set main startup script of the app.
mainStartupScript = packageJson.main or 'index.js'

Просмотреть файл

@ -0,0 +1,22 @@
ipc = require 'ipc'
remote = require 'remote'
NativeImage = require 'native-image'
EventEmitter = require('events').EventEmitter
desktopCapturer = new EventEmitter
# Tells main process the renderer is requiring 'desktop-capture' module.
ipc.sendSync 'ATOM_BROWSER_DESKTOP_CAPTURER_REQUIRED'
desktopCapturer.startUpdating = (args) ->
ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_START_UPDATING', args
desktopCapturer.stopUpdating = () ->
ipc.send 'ATOM_BROWSER_DESKTOP_CAPTURER_STOP_UPDATING'
for event_name in ['source-added', 'source-removed', 'source-name-changed', "source-thumbnail-changed"]
do (event_name) ->
ipc.on event_name, (source) ->
desktopCapturer.emit event_name, { id: source.id, name: source.name, thumbnail: NativeImage.createFromDataUrl source.dataUrl }
module.exports = desktopCapturer

Просмотреть файл

@ -297,8 +297,8 @@ void NativeDesktopMediaList::OnSourcesList(
// Iterate through the old sources to find the removed sources.
for (size_t i = 0; i < sources_.size(); ++i) {
if (new_source_set.find(sources_[i].id) == new_source_set.end()) {
sources_.erase(sources_.begin() + i);
observer_->OnSourceRemoved(i);
sources_.erase(sources_.begin() + i);
--i;
}
}

Просмотреть файл

@ -46,6 +46,7 @@
### Modules for the Renderer Process (Web Page):
* [desktop-capturer](api/desktop-capturer.md)
* [ipc (renderer)](api/ipc-renderer.md)
* [remote](api/remote.md)
* [web-frame](api/web-frame.md)

Просмотреть файл

@ -0,0 +1,118 @@
# desktop-capturer
The `desktop-capturer` is a renderer module used to capture the content of
screen and individual app windows.
```javascript
// In the renderer process.
var desktopCapturer = require('desktop-capturer');
desktopCapturer.on('source-added', function(source) {
console.log("source " + source.name + " is added.");
// source.thumbnail is not ready to use here and webkitGetUserMedia neither.
});
desktopCapturer.on('source-thumbnail-changed', function(source) {
if (source.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: source.id,
minWidth: 1280,
maxWidth: 1280,
minHeight: 720,
maxHeight: 720
}
}
}, gotStream, getUserMediaError);
}
});
// Let's start updating after setting all event of `desktopCapturer`
desktopCapturer.startUpdating();
function gotStream(stream) {
document.querySelector('video').src = URL.createObjectURL(stream);
}
function getUserMediaError(e) {
console.log('getUserMediaError');
}
```
## Events
### Event: 'source-added'
* `source` Source
Emits when there is a new source added, usually a new window is created,
a new screen is attached.
**Note:** The thumbnail of the source is not ready for use when 'source-added'
event is emitted, and `navigator.webkitGetUserMedia` neither.
### Event: 'source-removed'
* `source` Source
Emits when there is a source removed.
### Event: 'source-name-changed'
* `source` Source
Emits when the name of source is changed.
### Event: 'source-thumbnail-changed'
* `source` Source
Emits when the thumbnail of source is changed. `desktopCapturer` will refresh
all sources every second.
## Methods
The `desktopCapturer` module has the following methods:
### `desktopCapturer.startUpdating(options)`
* `options` Array - An array of String that enums the types of desktop sources.
* `screen` String - Screen
* `window` String - Individual window
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 of source to
`navigator.webkitGetUserMedia`.
## Source
`Source` is an object represents a captured screen or individual window. It has
following properties:
* `id` String - The id of the capturing 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.

Просмотреть файл

@ -26,6 +26,7 @@
'atom/browser/api/lib/tray.coffee',
'atom/browser/api/lib/web-contents.coffee',
'atom/browser/lib/chrome-extension.coffee',
'atom/browser/lib/desktop-capturer.coffee',
'atom/browser/lib/guest-view-manager.coffee',
'atom/browser/lib/guest-window-manager.coffee',
'atom/browser/lib/init.coffee',
@ -45,6 +46,7 @@
'atom/renderer/lib/web-view/web-view.coffee',
'atom/renderer/lib/web-view/web-view-attributes.coffee',
'atom/renderer/lib/web-view/web-view-constants.coffee',
'atom/renderer/api/lib/desktop-capturer.coffee',
'atom/renderer/api/lib/ipc.coffee',
'atom/renderer/api/lib/remote.coffee',
'atom/renderer/api/lib/screen.coffee',