Merge pull request #3727 from atom/desktop-capture-api-continue

Merge the desktopCapturer API
This commit is contained in:
Cheng Zhao 2015-12-08 19:46:01 +08:00
commit 5e9aca4524
20 changed files with 894 additions and 4 deletions

View file

@ -256,6 +256,10 @@
'vendor/node/deps/cares/include', 'vendor/node/deps/cares/include',
# The `third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h` is using `platform/PlatformExport.h`. # The `third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h` is using `platform/PlatformExport.h`.
'<(libchromiumcontent_src_dir)/third_party/WebKit/Source', '<(libchromiumcontent_src_dir)/third_party/WebKit/Source',
# The 'third_party/libyuv/include/libyuv/scale_argb.h' is using 'libyuv/basic_types.h'.
'<(libchromiumcontent_src_dir)/third_party/libyuv/include',
# The 'third_party/webrtc/modules/desktop_capture/desktop_frame.h' is using 'webrtc/base/scoped_ptr.h'.
'<(libchromiumcontent_src_dir)/third_party/',
], ],
'direct_dependent_settings': { 'direct_dependent_settings': {
'include_dirs': [ 'include_dirs': [
@ -282,6 +286,7 @@
'-lcomctl32.lib', '-lcomctl32.lib',
'-lcomdlg32.lib', '-lcomdlg32.lib',
'-lwininet.lib', '-lwininet.lib',
'-lwinmm.lib',
], ],
}, },
'dependencies': [ 'dependencies': [

View file

@ -0,0 +1,120 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/api/atom_api_desktop_capturer.h"
#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"
#include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h"
#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 {
DesktopCapturer::DesktopCapturer() {
}
DesktopCapturer::~DesktopCapturer() {
}
void DesktopCapturer::StartHandling(bool capture_window,
bool capture_screen,
const gfx::Size& thumbnail_size) {
webrtc::DesktopCaptureOptions options =
webrtc::DesktopCaptureOptions::CreateDefault();
#if defined(OS_WIN)
// On windows, desktop effects (e.g. Aero) will be disabled when the Desktop
// capture API is active by default.
// We keep the desktop effects in most times. Howerver, the screen still
// fickers when the API is capturing the window due to limitation of current
// implemetation. This is a known and wontFix issue in webrtc (see:
// http://code.google.com/p/webrtc/issues/detail?id=3373)
options.set_disable_effects(false);
#endif
scoped_ptr<webrtc::ScreenCapturer> screen_capturer(
capture_screen ? webrtc::ScreenCapturer::Create(options) : nullptr);
scoped_ptr<webrtc::WindowCapturer> window_capturer(
capture_window ? webrtc::WindowCapturer::Create(options) : nullptr);
media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(),
window_capturer.Pass()));
media_list_->SetThumbnailSize(thumbnail_size);
media_list_->StartUpdating(this);
}
void DesktopCapturer::OnSourceAdded(int index) {
}
void DesktopCapturer::OnSourceRemoved(int index) {
}
void DesktopCapturer::OnSourceMoved(int old_index, int new_index) {
}
void DesktopCapturer::OnSourceNameChanged(int index) {
}
void DesktopCapturer::OnSourceThumbnailChanged(int index) {
}
bool DesktopCapturer::OnRefreshFinished() {
Emit("finished", media_list_->GetSources());
media_list_.reset();
return false;
}
mate::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
return mate::ObjectTemplateBuilder(isolate)
.SetMethod("startHandling", &DesktopCapturer::StartHandling);
}
// static
mate::Handle<DesktopCapturer> DesktopCapturer::Create(v8::Isolate* isolate) {
return mate::CreateHandle(isolate, new DesktopCapturer);
}
} // namespace api
} // namespace atom
namespace {
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.Set("desktopCapturer", atom::api::DesktopCapturer::Create(isolate));
}
} // namespace
NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_desktop_capturer, Initialize);

View file

@ -0,0 +1,52 @@
// Copyright (c) 2015 GitHub, Inc.
// 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_H_
#define ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_
#include "atom/browser/api/event_emitter.h"
#include "chrome/browser/media/desktop_media_list_observer.h"
#include "chrome/browser/media/native_desktop_media_list.h"
#include "native_mate/handle.h"
namespace atom {
namespace api {
class DesktopCapturer: public mate::EventEmitter,
public DesktopMediaListObserver {
public:
static mate::Handle<DesktopCapturer> Create(v8::Isolate* isolate);
void StartHandling(bool capture_window,
bool capture_screen,
const gfx::Size& thumbnail_size);
protected:
DesktopCapturer();
~DesktopCapturer();
// DesktopMediaListObserver overrides.
void OnSourceAdded(int index) override;
void OnSourceRemoved(int index) override;
void OnSourceMoved(int old_index, int new_index) override;
void OnSourceNameChanged(int index) override;
void OnSourceThumbnailChanged(int index) override;
bool OnRefreshFinished() override;
private:
// mate::Wrappable:
mate::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) override;
scoped_ptr<DesktopMediaList> media_list_;
DISALLOW_COPY_AND_ASSIGN(DesktopCapturer);
};
} // namespace api
} // namespace atom
#endif // ATOM_BROWSER_API_ATOM_API_DESKTOP_CAPTURER_H_

View file

@ -0,0 +1,37 @@
{ipcMain} = require 'electron'
{desktopCapturer} = process.atomBinding 'desktop_capturer'
deepEqual = (opt1, opt2) ->
return JSON.stringify(opt1) is JSON.stringify(opt2)
# A queue for holding all requests from renderer process.
requestsQueue = []
ipcMain.on 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', (event, captureWindow, captureScreen, thumbnailSize, id) ->
request = id: id, options: {captureWindow, captureScreen, thumbnailSize}, webContents: event.sender
requestsQueue.push request
desktopCapturer.startHandling captureWindow, captureScreen, thumbnailSize if requestsQueue.length is 1
# 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
desktopCapturer.emit = (event, name, 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_RENDERER_DESKTOP_CAPTURER_RESULT_#{handledRequest.id}", result
# 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 deepEqual handledRequest.options, request.options
request.webContents?.send "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{request.id}", errorMessage, result
else
unhandledRequestsQueue.push request
requestsQueue = unhandledRequestsQueue
# If the requestsQueue is not empty, start a new request handling.
if requestsQueue.length > 0
{captureWindow, captureScreen, thumbnailSize} = requestsQueue[0].options
desktopCapturer.startHandling captureWindow, captureScreen, thumbnailSize

View file

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

View file

@ -35,6 +35,7 @@ REFERENCE_MODULE(atom_browser_app);
REFERENCE_MODULE(atom_browser_auto_updater); REFERENCE_MODULE(atom_browser_auto_updater);
REFERENCE_MODULE(atom_browser_content_tracing); REFERENCE_MODULE(atom_browser_content_tracing);
REFERENCE_MODULE(atom_browser_dialog); REFERENCE_MODULE(atom_browser_dialog);
REFERENCE_MODULE(atom_browser_desktop_capturer);
REFERENCE_MODULE(atom_browser_download_item); REFERENCE_MODULE(atom_browser_download_item);
REFERENCE_MODULE(atom_browser_menu); REFERENCE_MODULE(atom_browser_menu);
REFERENCE_MODULE(atom_browser_power_monitor); REFERENCE_MODULE(atom_browser_power_monitor);

View file

@ -0,0 +1,20 @@
{ipcRenderer, nativeImage} = require 'electron'
nextId = 0
getNextId = -> ++nextId
# |options.type| can not be empty and has to include 'window' or 'screen'.
isValid = (options) ->
return options?.types? and Array.isArray options.types
exports.getSources = (options, callback) ->
return callback new Error('Invalid options') unless isValid options
captureWindow = 'window' in options.types
captureScreen = 'screen' in options.types
options.thumbnailSize ?= width: 150, height: 150
id = getNextId()
ipcRenderer.send 'ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id
ipcRenderer.once "ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_#{id}", (event, sources) ->
callback null, ({id: source.id, name: source.name, thumbnail: nativeImage.createFromDataURL source.thumbnail} for source in sources)

View file

@ -3,6 +3,9 @@ module.exports = require '../../../../common/api/lib/exports/electron'
Object.defineProperties module.exports, Object.defineProperties module.exports,
# Renderer side modules, please sort with alphabet order. # Renderer side modules, please sort with alphabet order.
desktopCapturer:
enumerable: true
get: -> require '../desktop-capturer'
ipcRenderer: ipcRenderer:
enumerable: true enumerable: true
get: -> require '../ipc-renderer' get: -> require '../ipc-renderer'

View file

@ -0,0 +1,62 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_
#define CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_
#include "base/basictypes.h"
#include "base/strings/string16.h"
#include "base/time/time.h"
#include "content/public/browser/desktop_media_id.h"
#include "ui/gfx/image/image_skia.h"
class DesktopMediaListObserver;
// DesktopMediaList provides the list of desktop media source (screens, windows,
// tabs), and their thumbnails, to the desktop media picker dialog. It
// transparently updates the list in the background, and notifies the desktop
// media picker when something changes.
class DesktopMediaList {
public:
// Struct used to represent each entry in the list.
struct Source {
// Id of the source.
content::DesktopMediaID id;
// Name of the source that should be shown to the user.
base::string16 name;
// The thumbnail for the source.
gfx::ImageSkia thumbnail;
};
virtual ~DesktopMediaList() {}
// Sets time interval between updates. By default list of sources and their
// thumbnail are updated once per second. If called after StartUpdating() then
// it will take effect only after the next update.
virtual void SetUpdatePeriod(base::TimeDelta period) = 0;
// Sets size to which the thumbnails should be scaled. If called after
// StartUpdating() then some thumbnails may be still scaled to the old size
// until they are updated.
virtual void SetThumbnailSize(const gfx::Size& thumbnail_size) = 0;
// Sets ID of the hosting desktop picker dialog. The window with this ID will
// be filtered out from the list of sources.
virtual void SetViewDialogWindowId(content::DesktopMediaID::Id dialog_id) = 0;
// Starts updating the model. The model is initially empty, so OnSourceAdded()
// notifications will be generated for each existing source as it is
// enumerated. After the initial enumeration the model will be refreshed based
// on the update period, and notifications generated only for changes in the
// model.
virtual void StartUpdating(DesktopMediaListObserver* observer) = 0;
virtual int GetSourceCount() const = 0;
virtual const Source& GetSource(int index) const = 0;
virtual std::vector<Source> GetSources() const = 0;
};
#endif // CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_

View file

@ -0,0 +1,23 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_OBSERVER_H_
#define CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_OBSERVER_H_
// Interface implemented by the desktop media picker dialog to receive
// notifications about changes in DesktopMediaList.
class DesktopMediaListObserver {
public:
virtual void OnSourceAdded(int index) = 0;
virtual void OnSourceRemoved(int index) = 0;
virtual void OnSourceMoved(int old_index, int new_index) = 0;
virtual void OnSourceNameChanged(int index) = 0;
virtual void OnSourceThumbnailChanged(int index) = 0;
virtual bool OnRefreshFinished() = 0;
protected:
virtual ~DesktopMediaListObserver() {}
};
#endif // CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_OBSERVER_H_

View file

@ -0,0 +1,375 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/media/native_desktop_media_list.h"
#include <map>
#include <set>
#include <sstream>
#include "base/hash.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/media/desktop_media_list_observer.h"
#include "content/public/browser/browser_thread.h"
#include "media/base/video_util.h"
#include "third_party/libyuv/include/libyuv/scale_argb.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "third_party/webrtc/modules/desktop_capture/screen_capturer.h"
#include "third_party/webrtc/modules/desktop_capture/window_capturer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/skia_util.h"
using content::BrowserThread;
using content::DesktopMediaID;
namespace {
// Update the list every second.
const int kDefaultUpdatePeriod = 1000;
// Returns a hash of a DesktopFrame content to detect when image for a desktop
// media source has changed.
uint32 GetFrameHash(webrtc::DesktopFrame* frame) {
int data_size = frame->stride() * frame->size().height();
return base::SuperFastHash(reinterpret_cast<char*>(frame->data()), data_size);
}
gfx::ImageSkia ScaleDesktopFrame(scoped_ptr<webrtc::DesktopFrame> frame,
gfx::Size size) {
gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
gfx::Rect(0, 0, size.width(), size.height()),
gfx::Size(frame->size().width(), frame->size().height()));
SkBitmap result;
result.allocN32Pixels(scaled_rect.width(), scaled_rect.height(), true);
result.lockPixels();
uint8* pixels_data = reinterpret_cast<uint8*>(result.getPixels());
libyuv::ARGBScale(frame->data(), frame->stride(),
frame->size().width(), frame->size().height(),
pixels_data, result.rowBytes(),
scaled_rect.width(), scaled_rect.height(),
libyuv::kFilterBilinear);
// Set alpha channel values to 255 for all pixels.
// TODO(sergeyu): Fix screen/window capturers to capture alpha channel and
// remove this code. Currently screen/window capturers (at least some
// implementations) only capture R, G and B channels and set Alpha to 0.
// crbug.com/264424
for (int y = 0; y < result.height(); ++y) {
for (int x = 0; x < result.width(); ++x) {
pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] =
0xff;
}
}
result.unlockPixels();
return gfx::ImageSkia::CreateFrom1xBitmap(result);
}
} // namespace
NativeDesktopMediaList::SourceDescription::SourceDescription(
DesktopMediaID id,
const base::string16& name)
: id(id),
name(name) {
}
class NativeDesktopMediaList::Worker
: public webrtc::DesktopCapturer::Callback {
public:
Worker(base::WeakPtr<NativeDesktopMediaList> media_list,
scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
scoped_ptr<webrtc::WindowCapturer> window_capturer);
~Worker() override;
void Refresh(const gfx::Size& thumbnail_size,
content::DesktopMediaID::Id view_dialog_id);
private:
typedef std::map<DesktopMediaID, uint32> ImageHashesMap;
// webrtc::DesktopCapturer::Callback interface.
webrtc::SharedMemory* CreateSharedMemory(size_t size) override;
void OnCaptureCompleted(webrtc::DesktopFrame* frame) override;
base::WeakPtr<NativeDesktopMediaList> media_list_;
scoped_ptr<webrtc::ScreenCapturer> screen_capturer_;
scoped_ptr<webrtc::WindowCapturer> window_capturer_;
scoped_ptr<webrtc::DesktopFrame> current_frame_;
ImageHashesMap image_hashes_;
DISALLOW_COPY_AND_ASSIGN(Worker);
};
NativeDesktopMediaList::Worker::Worker(
base::WeakPtr<NativeDesktopMediaList> media_list,
scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
scoped_ptr<webrtc::WindowCapturer> window_capturer)
: media_list_(media_list),
screen_capturer_(screen_capturer.Pass()),
window_capturer_(window_capturer.Pass()) {
if (screen_capturer_)
screen_capturer_->Start(this);
if (window_capturer_)
window_capturer_->Start(this);
}
NativeDesktopMediaList::Worker::~Worker() {}
void NativeDesktopMediaList::Worker::Refresh(
const gfx::Size& thumbnail_size,
content::DesktopMediaID::Id view_dialog_id) {
std::vector<SourceDescription> sources;
if (screen_capturer_) {
webrtc::ScreenCapturer::ScreenList screens;
if (screen_capturer_->GetScreenList(&screens)) {
bool mutiple_screens = screens.size() > 1;
base::string16 title;
for (size_t i = 0; i < screens.size(); ++i) {
if (mutiple_screens) {
title = base::UTF8ToUTF16("Screen " + base::IntToString(i+1));
} else {
title = base::UTF8ToUTF16("Entire screen");
}
sources.push_back(SourceDescription(DesktopMediaID(
DesktopMediaID::TYPE_SCREEN, screens[i].id), title));
}
}
}
if (window_capturer_) {
webrtc::WindowCapturer::WindowList windows;
if (window_capturer_->GetWindowList(&windows)) {
for (webrtc::WindowCapturer::WindowList::iterator it = windows.begin();
it != windows.end(); ++it) {
// Skip the picker dialog window.
if (it->id != view_dialog_id) {
sources.push_back(SourceDescription(
DesktopMediaID(DesktopMediaID::TYPE_WINDOW, it->id),
base::UTF8ToUTF16(it->title)));
}
}
}
}
// Update list of windows before updating thumbnails.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&NativeDesktopMediaList::OnSourcesList,
media_list_, sources));
ImageHashesMap new_image_hashes;
// Get a thumbnail for each source.
for (size_t i = 0; i < sources.size(); ++i) {
SourceDescription& source = sources[i];
switch (source.id.type) {
case DesktopMediaID::TYPE_SCREEN:
if (!screen_capturer_->SelectScreen(source.id.id))
continue;
screen_capturer_->Capture(webrtc::DesktopRegion());
break;
case DesktopMediaID::TYPE_WINDOW:
if (!window_capturer_->SelectWindow(source.id.id))
continue;
window_capturer_->Capture(webrtc::DesktopRegion());
break;
default:
NOTREACHED();
}
// Expect that DesktopCapturer to always captures frames synchronously.
// |current_frame_| may be NULL if capture failed (e.g. because window has
// been closed).
if (current_frame_) {
uint32 frame_hash = GetFrameHash(current_frame_.get());
new_image_hashes[source.id] = frame_hash;
// Scale the image only if it has changed.
ImageHashesMap::iterator it = image_hashes_.find(source.id);
if (it == image_hashes_.end() || it->second != frame_hash) {
gfx::ImageSkia thumbnail =
ScaleDesktopFrame(current_frame_.Pass(), thumbnail_size);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&NativeDesktopMediaList::OnSourceThumbnail,
media_list_, i, thumbnail));
}
}
}
image_hashes_.swap(new_image_hashes);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&NativeDesktopMediaList::OnRefreshFinished, media_list_));
}
webrtc::SharedMemory* NativeDesktopMediaList::Worker::CreateSharedMemory(
size_t size) {
return NULL;
}
void NativeDesktopMediaList::Worker::OnCaptureCompleted(
webrtc::DesktopFrame* frame) {
current_frame_.reset(frame);
}
NativeDesktopMediaList::NativeDesktopMediaList(
scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
scoped_ptr<webrtc::WindowCapturer> window_capturer)
: screen_capturer_(screen_capturer.Pass()),
window_capturer_(window_capturer.Pass()),
update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)),
thumbnail_size_(100, 100),
view_dialog_id_(-1),
observer_(NULL),
weak_factory_(this) {
base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool();
capture_task_runner_ = worker_pool->GetSequencedTaskRunner(
worker_pool->GetSequenceToken());
}
NativeDesktopMediaList::~NativeDesktopMediaList() {
capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release());
}
void NativeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period) {
DCHECK(!observer_);
update_period_ = period;
}
void NativeDesktopMediaList::SetThumbnailSize(
const gfx::Size& thumbnail_size) {
thumbnail_size_ = thumbnail_size;
}
void NativeDesktopMediaList::SetViewDialogWindowId(
content::DesktopMediaID::Id dialog_id) {
view_dialog_id_ = dialog_id;
}
void NativeDesktopMediaList::StartUpdating(DesktopMediaListObserver* observer) {
DCHECK(!observer_);
DCHECK(screen_capturer_ || window_capturer_);
observer_ = observer;
worker_.reset(new Worker(weak_factory_.GetWeakPtr(),
screen_capturer_.Pass(), window_capturer_.Pass()));
Refresh();
}
int NativeDesktopMediaList::GetSourceCount() const {
return sources_.size();
}
const DesktopMediaList::Source& NativeDesktopMediaList::GetSource(
int index) const {
return sources_[index];
}
std::vector<DesktopMediaList::Source> NativeDesktopMediaList::GetSources() const {
return sources_;
}
void NativeDesktopMediaList::Refresh() {
capture_task_runner_->PostTask(
FROM_HERE, base::Bind(&Worker::Refresh, base::Unretained(worker_.get()),
thumbnail_size_, view_dialog_id_));
}
void NativeDesktopMediaList::OnSourcesList(
const std::vector<SourceDescription>& new_sources) {
typedef std::set<content::DesktopMediaID> SourceSet;
SourceSet new_source_set;
for (size_t i = 0; i < new_sources.size(); ++i) {
new_source_set.insert(new_sources[i].id);
}
// 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()) {
observer_->OnSourceRemoved(i);
sources_.erase(sources_.begin() + i);
--i;
}
}
// Iterate through the new sources to find the added sources.
if (new_sources.size() > sources_.size()) {
SourceSet old_source_set;
for (size_t i = 0; i < sources_.size(); ++i) {
old_source_set.insert(sources_[i].id);
}
for (size_t i = 0; i < new_sources.size(); ++i) {
if (old_source_set.find(new_sources[i].id) == old_source_set.end()) {
sources_.insert(sources_.begin() + i, Source());
sources_[i].id = new_sources[i].id;
sources_[i].name = new_sources[i].name;
observer_->OnSourceAdded(i);
}
}
}
DCHECK_EQ(new_sources.size(), sources_.size());
// Find the moved/changed sources.
size_t pos = 0;
while (pos < sources_.size()) {
if (!(sources_[pos].id == new_sources[pos].id)) {
// Find the source that should be moved to |pos|, starting from |pos + 1|
// of |sources_|, because entries before |pos| should have been sorted.
size_t old_pos = pos + 1;
for (; old_pos < sources_.size(); ++old_pos) {
if (sources_[old_pos].id == new_sources[pos].id)
break;
}
DCHECK(sources_[old_pos].id == new_sources[pos].id);
// Move the source from |old_pos| to |pos|.
Source temp = sources_[old_pos];
sources_.erase(sources_.begin() + old_pos);
sources_.insert(sources_.begin() + pos, temp);
observer_->OnSourceMoved(old_pos, pos);
}
if (sources_[pos].name != new_sources[pos].name) {
sources_[pos].name = new_sources[pos].name;
observer_->OnSourceNameChanged(pos);
}
++pos;
}
}
void NativeDesktopMediaList::OnSourceThumbnail(
int index,
const gfx::ImageSkia& image) {
DCHECK_LT(index, static_cast<int>(sources_.size()));
sources_[index].thumbnail = image;
observer_->OnSourceThumbnailChanged(index);
}
void NativeDesktopMediaList::OnRefreshFinished() {
// 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

@ -0,0 +1,101 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_
#define CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequenced_task_runner.h"
#include "chrome/browser/media/desktop_media_list.h"
#include "content/public/browser/desktop_media_id.h"
#include "ui/gfx/image/image_skia.h"
namespace webrtc {
class ScreenCapturer;
class WindowCapturer;
}
// Implementation of DesktopMediaList that shows native screens and
// native windows.
class NativeDesktopMediaList : public DesktopMediaList {
public:
// Caller may pass NULL for either of the arguments in case when only some
// types of sources the model should be populated with (e.g. it will only
// contain windows, if |screen_capturer| is NULL).
NativeDesktopMediaList(
scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
scoped_ptr<webrtc::WindowCapturer> window_capturer);
~NativeDesktopMediaList() override;
// DesktopMediaList interface.
void SetUpdatePeriod(base::TimeDelta period) override;
void SetThumbnailSize(const gfx::Size& thumbnail_size) override;
void StartUpdating(DesktopMediaListObserver* observer) override;
int GetSourceCount() const override;
const Source& GetSource(int index) const override;
std::vector<Source> GetSources() const override;
void SetViewDialogWindowId(content::DesktopMediaID::Id dialog_id) override;
private:
class Worker;
friend class Worker;
// Struct used to represent sources list the model gets from the Worker.
struct SourceDescription {
SourceDescription(content::DesktopMediaID id, const base::string16& name);
content::DesktopMediaID id;
base::string16 name;
};
// Order comparator for sources. Used to sort list of sources.
static bool CompareSources(const SourceDescription& a,
const SourceDescription& b);
// Post a task for the |worker_| to update list of windows and get thumbnails.
void Refresh();
// Called by |worker_| to refresh the model. First it posts tasks for
// OnSourcesList() with the fresh list of sources, then follows with
// OnSourceThumbnail() for each changed thumbnail and then calls
// OnRefreshFinished() at the end.
void OnSourcesList(const std::vector<SourceDescription>& sources);
void OnSourceThumbnail(int index, const gfx::ImageSkia& thumbnail);
void OnRefreshFinished();
// Capturers specified in SetCapturers() and passed to the |worker_| later.
scoped_ptr<webrtc::ScreenCapturer> screen_capturer_;
scoped_ptr<webrtc::WindowCapturer> window_capturer_;
// Time interval between mode updates.
base::TimeDelta update_period_;
// Size of thumbnails generated by the model.
gfx::Size thumbnail_size_;
// ID of the hosting dialog.
content::DesktopMediaID::Id view_dialog_id_;
// The observer passed to StartUpdating().
DesktopMediaListObserver* observer_;
// Task runner used for the |worker_|.
scoped_refptr<base::SequencedTaskRunner> capture_task_runner_;
// An object that does all the work of getting list of sources on a background
// thread (see |capture_task_runner_|). Destroyed on |capture_task_runner_|
// after the model is destroyed.
scoped_ptr<Worker> worker_;
// Current list of sources.
std::vector<Source> sources_;
base::WeakPtrFactory<NativeDesktopMediaList> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(NativeDesktopMediaList);
};
#endif // CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_

View file

@ -58,6 +58,7 @@ select the tag that matches your version.
### Modules for the Renderer Process (Web Page): ### Modules for the Renderer Process (Web Page):
* [desktopCapturer](api/desktop-capturer.md)
* [ipcRenderer](api/ipc-renderer.md) * [ipcRenderer](api/ipc-renderer.md)
* [remote](api/remote.md) * [remote](api/remote.md)
* [webFrame](api/web-frame.md) * [webFrame](api/web-frame.md)

View file

@ -0,0 +1,69 @@
# desktopCapturer
The `desktopCapturer` module can be used to get available sources that can be
used to be captured with `getUserMedia`.
```javascript
// In the renderer process.
var desktopCapturer = require('electron').desktopCapturer;
desktopCapturer.getSources({types: ['window', 'screen']}, function(error, sources) {
if (error) throw error;
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);
return;
}
}
});
function gotStream(stream) {
document.querySelector('video').src = URL.createObjectURL(stream);
}
function getUserMediaError(e) {
console.log('getUserMediaError');
}
```
## Methods
The `desktopCapturer` module has the following methods:
### `desktopCapturer.getSources(options, callback)`
* `options` Object
* `types` Array - An array of String that lists the types of desktop sources
to be captured, available types are `screen` and `window`.
* `thumbnailSize` Object (optional) - The suggested size that thumbnail should
be scaled, it is `{width: 150, height: 150}` by default.
* `callback` Function
Starts a request to get all desktop sources, `callback` will be called with
`callback(error, sources)` when the request is completed.
The `sources` is an array of `Source` objects, each `Source` represents a
captured screen or individual window, and has following properties:
* `id` String - The id of the captured window or screen used in
`navigator.webkitGetUserMedia`. The format looks like `window:XX` or
`screen:XX` where `XX` is a random generated number.
* `name` String - The described 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.
* `thumbnail` [NativeImage](NativeImage.md) - A thumbnail image.
**Note:** There is no guarantee that the size of `source.thumbnail` is always
the same as the `thumnbailSize` in `options`. It also depends on the scale of
the screen or window.

View file

@ -31,6 +31,7 @@
'atom/browser/api/lib/tray.coffee', 'atom/browser/api/lib/tray.coffee',
'atom/browser/api/lib/web-contents.coffee', 'atom/browser/api/lib/web-contents.coffee',
'atom/browser/lib/chrome-extension.coffee', 'atom/browser/lib/chrome-extension.coffee',
'atom/browser/lib/desktop-capturer.coffee',
'atom/browser/lib/guest-view-manager.coffee', 'atom/browser/lib/guest-view-manager.coffee',
'atom/browser/lib/guest-window-manager.coffee', 'atom/browser/lib/guest-window-manager.coffee',
'atom/browser/lib/init.coffee', 'atom/browser/lib/init.coffee',
@ -53,6 +54,7 @@
'atom/renderer/lib/web-view/web-view.coffee', 'atom/renderer/lib/web-view/web-view.coffee',
'atom/renderer/lib/web-view/web-view-attributes.coffee', 'atom/renderer/lib/web-view/web-view-attributes.coffee',
'atom/renderer/lib/web-view/web-view-constants.coffee', 'atom/renderer/lib/web-view/web-view-constants.coffee',
'atom/renderer/api/lib/desktop-capturer.coffee',
'atom/renderer/api/lib/exports/electron.coffee', 'atom/renderer/api/lib/exports/electron.coffee',
'atom/renderer/api/lib/ipc.coffee', 'atom/renderer/api/lib/ipc.coffee',
'atom/renderer/api/lib/ipc-renderer.coffee', 'atom/renderer/api/lib/ipc-renderer.coffee',
@ -81,6 +83,8 @@
'atom/browser/api/atom_api_content_tracing.cc', 'atom/browser/api/atom_api_content_tracing.cc',
'atom/browser/api/atom_api_cookies.cc', 'atom/browser/api/atom_api_cookies.cc',
'atom/browser/api/atom_api_cookies.h', 'atom/browser/api/atom_api_cookies.h',
'atom/browser/api/atom_api_desktop_capturer.cc',
'atom/browser/api/atom_api_desktop_capturer.h',
'atom/browser/api/atom_api_download_item.cc', 'atom/browser/api/atom_api_download_item.cc',
'atom/browser/api/atom_api_download_item.h', 'atom/browser/api/atom_api_download_item.h',
'atom/browser/api/atom_api_dialog.cc', 'atom/browser/api/atom_api_dialog.cc',
@ -374,6 +378,10 @@
'chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_x11.h',
'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.cc', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.cc',
'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h', 'chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h',
'chromium_src/chrome/browser/media/desktop_media_list.h',
'chromium_src/chrome/browser/media/desktop_media_list_observer.h',
'chromium_src/chrome/browser/media/native_desktop_media_list.cc',
'chromium_src/chrome/browser/media/native_desktop_media_list.h',
'chromium_src/chrome/browser/printing/print_job.cc', 'chromium_src/chrome/browser/printing/print_job.cc',
'chromium_src/chrome/browser/printing/print_job.h', 'chromium_src/chrome/browser/printing/print_job.h',
'chromium_src/chrome/browser/printing/print_job_manager.cc', 'chromium_src/chrome/browser/printing/print_job_manager.cc',

View file

@ -17,6 +17,7 @@ LINUX_DEPS = [
'libgtk2.0-dev', 'libgtk2.0-dev',
'libnotify-dev', 'libnotify-dev',
'libnss3-dev', 'libnss3-dev',
'libxtst-dev',
'gcc-multilib', 'gcc-multilib',
'g++-multilib', 'g++-multilib',
] ]

View file

@ -7,8 +7,8 @@ import sys
BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \
'http://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' 'http://github-janky-artifacts.s3.amazonaws.com/libchromiumcontent'
LIBCHROMIUMCONTENT_COMMIT = '451ea93cc3090f7000f8f0daa4cb84e90ad6c842' LIBCHROMIUMCONTENT_COMMIT = 'cfbe8ec7e14af4cabd1474386f54e197db1f7ac1'
PLATFORM = { PLATFORM = {
'cygwin': 'win32', 'cygwin': 'win32',

View file

@ -8,7 +8,7 @@
# Do NOT CHANGE this if you don't know what you're doing -- see # Do NOT CHANGE this if you don't know what you're doing -- see
# https://code.google.com/p/chromium/wiki/UpdatingClang # https://code.google.com/p/chromium/wiki/UpdatingClang
# Reverting problematic clang rolls is safe, though. # Reverting problematic clang rolls is safe, though.
CLANG_REVISION=245965 CLANG_REVISION=247874
# This is incremented when pushing a new build of Clang at the same revision. # This is incremented when pushing a new build of Clang at the same revision.
CLANG_SUB_REVISION=1 CLANG_SUB_REVISION=1

View file

@ -0,0 +1,9 @@
assert = require 'assert'
{desktopCapturer} = require 'electron'
describe 'desktopCapturer', ->
it 'should returns something', (done) ->
desktopCapturer.getSources {types: ['window', 'screen']}, (error, sources) ->
assert.equal error, null
assert.notEqual sources.length, 0
done()

2
vendor/brightray vendored

@ -1 +1 @@
Subproject commit 9b4d052d2af716c340034ed7815d9d758cd7804d Subproject commit 814923b77d21261a9d1780bff0bd1beb55203843