Browse Source

Refine: make desktop-capturer as a renderer module.

Haojian Wu 9 years ago
parent
commit
1e69ef79de

+ 32 - 35
atom/browser/api/atom_api_desktop_capturer.cc

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

+ 10 - 7
atom/browser/api/atom_api_desktop_capturer.h

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

+ 1 - 17
atom/browser/api/atom_api_screen.cc

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

+ 0 - 5
atom/browser/api/atom_api_screen.h

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

+ 5 - 9
atom/browser/api/lib/app.coffee

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

+ 42 - 0
atom/browser/lib/desktop-capturer.coffee

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

+ 3 - 0
atom/browser/lib/init.coffee

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

+ 22 - 0
atom/renderer/api/lib/desktop-capturer.coffee

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

+ 1 - 1
chromium_src/chrome/browser/media/native_desktop_media_list.cc

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

+ 1 - 0
docs/README.md

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

+ 118 - 0
docs/api/desktop-capturer.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.

+ 2 - 0
filenames.gypi

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