diff options
4 files changed, 178 insertions, 72 deletions
diff --git a/chrome/browser/media/media_stream_capture_indicator.cc b/chrome/browser/media/media_stream_capture_indicator.cc index 8e18c65..710a9d8 100644 --- a/chrome/browser/media/media_stream_capture_indicator.cc +++ b/chrome/browser/media/media_stream_capture_indicator.cc @@ -17,6 +17,7 @@ #include "chrome/browser/status_icons/status_icon.h" #include "chrome/browser/status_icons/status_tray.h" #include "chrome/browser/tab_contents/tab_util.h" +#include "chrome/browser/ui/screen_capture_notification_ui.h" #include "chrome/common/extensions/api/icons/icons_handler.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/content_browser_client.h" @@ -118,17 +119,26 @@ class MediaStreamCaptureIndicator::WebContentsDeviceUsage bool IsCapturingAudio() const { return audio_ref_count_ > 0; } bool IsCapturingVideo() const { return video_ref_count_ > 0; } bool IsMirroring() const { return mirroring_ref_count_ > 0; } + bool IsCapturingScreen() const { return screen_capture_ref_count_ > 0; } + const base::Closure& StopScreenCaptureCallback() const { + return stop_screen_capture_callback_; + } - // Updates ref-counts up (|direction| is true) or down by one, based on the - // type of each device provided. In the increment-upward case, the return - // value is the message ID for the balloon body to show, or zero if the + // Increment ref-counts up based on the type of each device provided. The + // return value is the message ID for the balloon body to show, or zero if the // balloon should not be shown. - int TallyUsage(const content::MediaStreamDevices& devices, bool direction); + int AddDevices(const content::MediaStreamDevices& devices, + const base::Closure& close_callback); + + // Decrement ref-counts up based on the type of each device provided. + void RemoveDevices(const content::MediaStreamDevices& devices); private: int audio_ref_count_; int video_ref_count_; int mirroring_ref_count_; + int screen_capture_ref_count_; + base::Closure stop_screen_capture_callback_; DISALLOW_COPY_AND_ASSIGN(WebContentsDeviceUsage); }; @@ -136,34 +146,34 @@ class MediaStreamCaptureIndicator::WebContentsDeviceUsage MediaStreamCaptureIndicator::WebContentsDeviceUsage::WebContentsDeviceUsage( WebContents* web_contents) : content::WebContentsObserver(web_contents), - audio_ref_count_(0), video_ref_count_(0), mirroring_ref_count_(0) { + audio_ref_count_(0), + video_ref_count_(0), + mirroring_ref_count_(0), + screen_capture_ref_count_(0) { } -int MediaStreamCaptureIndicator::WebContentsDeviceUsage::TallyUsage( - const content::MediaStreamDevices& devices, bool direction) { - const int inc_amount = direction ? +1 : -1; +int MediaStreamCaptureIndicator::WebContentsDeviceUsage::AddDevices( + const content::MediaStreamDevices& devices, + const base::Closure& close_callback) { bool incremented_audio_count = false; bool incremented_video_count = false; for (content::MediaStreamDevices::const_iterator it = devices.begin(); it != devices.end(); ++it) { if (it->type == content::MEDIA_TAB_AUDIO_CAPTURE || it->type == content::MEDIA_TAB_VIDEO_CAPTURE) { - mirroring_ref_count_ += inc_amount; + ++mirroring_ref_count_; + } else if (it->type == content::MEDIA_SCREEN_VIDEO_CAPTURE) { + ++screen_capture_ref_count_; + stop_screen_capture_callback_ = close_callback; } else if (content::IsAudioMediaType(it->type)) { - audio_ref_count_ += inc_amount; - incremented_audio_count = (inc_amount > 0); + ++audio_ref_count_; } else if (content::IsVideoMediaType(it->type)) { - video_ref_count_ += inc_amount; - incremented_video_count = (inc_amount > 0); + ++video_ref_count_; } else { NOTIMPLEMENTED(); } } - DCHECK_LE(0, audio_ref_count_); - DCHECK_LE(0, video_ref_count_); - DCHECK_LE(0, mirroring_ref_count_); - if (incremented_audio_count && incremented_video_count) return IDS_MEDIA_STREAM_STATUS_TRAY_BALLOON_BODY_AUDIO_AND_VIDEO; else if (incremented_audio_count) @@ -174,6 +184,30 @@ int MediaStreamCaptureIndicator::WebContentsDeviceUsage::TallyUsage( return 0; } +void MediaStreamCaptureIndicator::WebContentsDeviceUsage::RemoveDevices( + const content::MediaStreamDevices& devices) { + for (content::MediaStreamDevices::const_iterator it = devices.begin(); + it != devices.end(); ++it) { + if (it->type == content::MEDIA_TAB_AUDIO_CAPTURE || + it->type == content::MEDIA_TAB_VIDEO_CAPTURE) { + --mirroring_ref_count_; + } else if (it->type == content::MEDIA_SCREEN_VIDEO_CAPTURE) { + --screen_capture_ref_count_; + } else if (content::IsAudioMediaType(it->type)) { + --audio_ref_count_; + } else if (content::IsVideoMediaType(it->type)) { + --video_ref_count_; + } else { + NOTIMPLEMENTED(); + } + } + + DCHECK_GE(audio_ref_count_, 0); + DCHECK_GE(video_ref_count_, 0); + DCHECK_GE(mirroring_ref_count_, 0); + DCHECK_GE(screen_capture_ref_count_, 0); +} + MediaStreamCaptureIndicator::MediaStreamCaptureIndicator() : status_icon_(NULL), mic_image_(NULL), @@ -258,6 +292,34 @@ void MediaStreamCaptureIndicator::CaptureDevicesClosed( this, render_process_id, render_view_id, devices)); } + +bool MediaStreamCaptureIndicator::IsCapturingUserMedia( + int render_process_id, int render_view_id) const { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + WebContents* const web_contents = + LookUpByKnownAlias(render_process_id, render_view_id); + if (!web_contents) + return false; + + UsageMap::const_iterator it = usage_map_.find(web_contents); + return (it != usage_map_.end() && + (it->second->IsCapturingAudio() || it->second->IsCapturingVideo())); +} + +bool MediaStreamCaptureIndicator::IsBeingMirrored( + int render_process_id, int render_view_id) const { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + WebContents* const web_contents = + LookUpByKnownAlias(render_process_id, render_view_id); + if (!web_contents) + return false; + + UsageMap::const_iterator it = usage_map_.find(web_contents); + return it != usage_map_.end() && it->second->IsMirroring(); +} + void MediaStreamCaptureIndicator::DoDevicesOpenedOnUIThread( int render_process_id, int render_view_id, @@ -374,7 +436,7 @@ void MediaStreamCaptureIndicator::MaybeDestroyStatusTrayIcon() { } } -void MediaStreamCaptureIndicator::UpdateStatusTrayIconContextMenu() { +void MediaStreamCaptureIndicator::UpdateNotificationUserInterface() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); scoped_ptr<ui::SimpleMenuModel> menu(new ui::SimpleMenuModel(this)); @@ -382,30 +444,57 @@ void MediaStreamCaptureIndicator::UpdateStatusTrayIconContextMenu() { bool video = false; int command_id = IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_FIRST; command_targets_.clear(); + + WebContents* screen_capturer = NULL; + base::Closure close_callback; + for (UsageMap::const_iterator iter = usage_map_.begin(); iter != usage_map_.end(); ++iter) { // Check if any audio and video devices have been used. const WebContentsDeviceUsage& usage = *iter->second; WebContents* const web_contents = iter->first; - if (usage.IsWebContentsDestroyed() || - !GetExtension(web_contents) || - (!usage.IsCapturingAudio() && !usage.IsCapturingVideo())) { + if (usage.IsWebContentsDestroyed()) { // We only show the tray icon for extensions that have not been // destroyed and are capturing audio or video. - // For regular tabs, we show an indicator in the tab icon. continue; } - audio = audio || usage.IsCapturingAudio(); - video = video || usage.IsCapturingVideo(); + if (usage.IsCapturingScreen()) { + DCHECK(!screen_capturer); + screen_capturer = web_contents; + close_callback = usage.StopScreenCaptureCallback(); + } + + // Audio/video icon is shown only for extensions. For regular tabs, we show + // an indicator in the tab icon. + if (GetExtension(web_contents) && + (usage.IsCapturingAudio() || usage.IsCapturingVideo())) { + audio = audio || usage.IsCapturingAudio(); + video = video || usage.IsCapturingVideo(); - command_targets_.push_back(web_contents); - menu->AddItem(command_id, GetTitle(web_contents)); + command_targets_.push_back(web_contents); + menu->AddItem(command_id, GetTitle(web_contents)); - // If reaching the maximum number, no more item will be added to the menu. - if (command_id == IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_LAST) - break; - ++command_id; + // If reaching the maximum number, no more item will be added to the menu. + if (command_id == IDC_MEDIA_CONTEXT_MEDIA_STREAM_CAPTURE_LIST_LAST) + break; + ++command_id; + } + } + + if (screen_capturer) { + if (!screen_capture_notification_) { + screen_capture_notification_ = ScreenCaptureNotificationUI::Create(); + if (!screen_capture_notification_->Show( + base::Bind(&MediaStreamCaptureIndicator::OnStopScreenCapture, + this, close_callback), + GetTitle(screen_capturer))) { + OnStopScreenCapture(close_callback); + screen_capture_notification_.reset(); + } + } + } else { + screen_capture_notification_.reset(); } if (command_targets_.empty()) { @@ -473,7 +562,8 @@ void MediaStreamCaptureIndicator::AddCaptureDevices( WebContentsDeviceUsage*& usage = usage_map_[web_contents]; if (!usage) usage = new WebContentsDeviceUsage(web_contents); - const int balloon_body_message_id = usage->TallyUsage(devices, true); + const int balloon_body_message_id = + usage->AddDevices(devices, close_callback); // Keep track of the IDs as a known alias to the WebContents instance. const AliasMap::iterator insert_it = aliases_.insert( @@ -484,7 +574,7 @@ void MediaStreamCaptureIndicator::AddCaptureDevices( web_contents->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB); - UpdateStatusTrayIconContextMenu(); + UpdateNotificationUserInterface(); if (balloon_body_message_id) ShowBalloon(web_contents, balloon_body_message_id); } @@ -507,14 +597,14 @@ void MediaStreamCaptureIndicator::RemoveCaptureDevices( return; } WebContentsDeviceUsage* const usage = it->second; - usage->TallyUsage(devices, false); + usage->RemoveDevices(devices); if (!usage->IsWebContentsDestroyed()) web_contents->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB); // Remove the usage and alias mappings if all the devices have been closed. if (!usage->IsCapturingAudio() && !usage->IsCapturingVideo() && - !usage->IsMirroring()) { + !usage->IsMirroring() && !usage->IsCapturingScreen()) { for (AliasMap::iterator alias_it = aliases_.begin(); alias_it != aliases_.end(); ) { if (alias_it->second == web_contents) @@ -526,32 +616,11 @@ void MediaStreamCaptureIndicator::RemoveCaptureDevices( usage_map_.erase(it); } - UpdateStatusTrayIconContextMenu(); -} - -bool MediaStreamCaptureIndicator::IsCapturingUserMedia( - int render_process_id, int render_view_id) const { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - WebContents* const web_contents = - LookUpByKnownAlias(render_process_id, render_view_id); - if (!web_contents) - return false; - - UsageMap::const_iterator it = usage_map_.find(web_contents); - return (it != usage_map_.end() && - (it->second->IsCapturingAudio() || it->second->IsCapturingVideo())); + UpdateNotificationUserInterface(); } -bool MediaStreamCaptureIndicator::IsBeingMirrored( - int render_process_id, int render_view_id) const { +void MediaStreamCaptureIndicator::OnStopScreenCapture( + const base::Closure& stop) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - WebContents* const web_contents = - LookUpByKnownAlias(render_process_id, render_view_id); - if (!web_contents) - return false; - - UsageMap::const_iterator it = usage_map_.find(web_contents); - return it != usage_map_.end() && it->second->IsMirroring(); + stop.Run(); } diff --git a/chrome/browser/media/media_stream_capture_indicator.h b/chrome/browser/media/media_stream_capture_indicator.h index c5f603b..9acf977 100644 --- a/chrome/browser/media/media_stream_capture_indicator.h +++ b/chrome/browser/media/media_stream_capture_indicator.h @@ -23,6 +23,7 @@ namespace gfx { class ImageSkia; } // namespace gfx +class ScreenCaptureNotificationUI; class StatusIcon; class StatusTray; @@ -61,9 +62,6 @@ class MediaStreamCaptureIndicator // media for remote broadcast). bool IsBeingMirrored(int render_process_id, int render_view_id) const; - // ImageLoader callback. - void OnImageLoaded(const string16& message, const gfx::Image& image); - private: class WebContentsDeviceUsage; @@ -112,19 +110,25 @@ class MediaStreamCaptureIndicator void ShowBalloon(content::WebContents* web_contents, int balloon_body_message_id); + // ImageLoader callback. + void OnImageLoaded(const string16& message, const gfx::Image& image); + // Removes the status tray icon from the desktop. This function is called by // RemoveCaptureDevices() when the device usage map becomes empty. void MaybeDestroyStatusTrayIcon(); - // Updates the status tray menu with the new device list. This call will be - // triggered by both AddCaptureDevices() and RemoveCaptureDevices(). - void UpdateStatusTrayIconContextMenu(); + // Updates the status tray menu and the screen capture notification. Called + // from AddCaptureDevices() and RemoveCaptureDevices(). + void UpdateNotificationUserInterface(); // Updates the status tray tooltip and image according to which kind of // devices are being used. This function is called by // UpdateStatusTrayIconContextMenu(). void UpdateStatusTrayIconDisplay(bool audio, bool video); + // Callback for ScreenCaptureNotificationUI. + void OnStopScreenCapture(const base::Closure& stop); + // Reference to our status icon - owned by the StatusTray. If null, // the platform doesn't support status icons. StatusIcon* status_icon_; @@ -152,6 +156,8 @@ class MediaStreamCaptureIndicator CommandTargets command_targets_; bool should_show_balloon_; + + scoped_ptr<ScreenCaptureNotificationUI> screen_capture_notification_; }; #endif // CHROME_BROWSER_MEDIA_MEDIA_STREAM_CAPTURE_INDICATOR_H_ diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc index 87dbfe2..a640dc5 100644 --- a/content/browser/renderer_host/media/media_stream_manager.cc +++ b/content/browser/renderer_host/media/media_stream_manager.cc @@ -151,7 +151,8 @@ MediaStreamManager::MediaStreamManager(media::AudioManager* audio_manager) ui_controller_(new MediaStreamUIController(this))), audio_manager_(audio_manager), monitoring_started_(false), - io_loop_(NULL) { + io_loop_(NULL), + screen_capture_active_(false) { DCHECK(audio_manager_); memset(active_enumeration_ref_count_, 0, sizeof(active_enumeration_ref_count_)); @@ -249,6 +250,23 @@ std::string MediaStreamManager::GenerateStream( } } + if (options.video_type == MEDIA_SCREEN_VIDEO_CAPTURE) { + if (options.audio_type != MEDIA_NO_SERVICE) { + // TODO(sergeyu): Surface error message to the calling JS code. + LOG(ERROR) << "Audio is not supported for screen capture streams."; + return std::string(); + } + + if (screen_capture_active_) { + // TODO(sergeyu): Implement support for more than one concurrent screen + // capture streams. + LOG(ERROR) << "Another screen capture stream is active."; + return std::string(); + } + + screen_capture_active_ = true; + } + // Create a new request based on options. DeviceRequest* request = new DeviceRequest(requester, options, @@ -274,7 +292,7 @@ void MediaStreamManager::CancelRequest(const std::string& label) { // TODO(xians): update the |state| to STATE_DONE to trigger a state // changed notification to UI before deleting the request? scoped_ptr<DeviceRequest> request(it->second); - requests_.erase(it); + RemoveRequest(it); for (int i = MEDIA_NO_SERVICE + 1; i < NUM_MEDIA_TYPES; ++i) { const MediaStreamType stream_type = static_cast<MediaStreamType>(i); MediaStreamProvider* device_manager = GetDeviceManager(stream_type); @@ -299,6 +317,7 @@ void MediaStreamManager::CancelRequest(const std::string& label) { void MediaStreamManager::StopGeneratedStream(const std::string& label) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + // Find the request and close all open devices for the request. DeviceRequests::iterator it = requests_.find(label); if (it != requests_.end()) { @@ -308,7 +327,7 @@ void MediaStreamManager::StopGeneratedStream(const std::string& label) { } scoped_ptr<DeviceRequest> request(it->second); - requests_.erase(it); + RemoveRequest(it); for (StreamDeviceInfoArray::const_iterator device_it = request->devices.begin(); device_it != request->devices.end(); ++device_it) { @@ -400,7 +419,7 @@ void MediaStreamManager::StopEnumerateDevices(const std::string& label) { DCHECK_EQ(it->second->type, MEDIA_ENUMERATE_DEVICES); // Delete the DeviceRequest. scoped_ptr<DeviceRequest> request(it->second); - requests_.erase(it); + RemoveRequest(it); } } @@ -539,6 +558,15 @@ std::string MediaStreamManager::AddRequest(DeviceRequest* request) { return unique_label; } +void MediaStreamManager::RemoveRequest(DeviceRequests::iterator it) { + if (it->second->options.video_type == MEDIA_SCREEN_VIDEO_CAPTURE) { + DCHECK(screen_capture_active_); + screen_capture_active_ = false; + } + + requests_.erase(it); +} + void MediaStreamManager::PostRequestToUI(const std::string& label) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DeviceRequest* request = requests_[label]; @@ -810,7 +838,7 @@ void MediaStreamManager::Error(MediaStreamType stream_type, if (request->requester) request->requester->StreamGenerationFailed(it->first); - requests_.erase(it); + RemoveRequest(it); } else { // 2. Not opened but other devices exists for this request -> remove // device from list, but don't signal an error. @@ -845,7 +873,7 @@ void MediaStreamManager::DevicesAccepted(const std::string& label, } // Delete the request since it is done. - requests_.erase(request_it); + RemoveRequest(request_it); return; } @@ -923,7 +951,7 @@ void MediaStreamManager::SettingsError(const std::string& label) { request->callback.Run(label, MediaStreamDevices()); } - requests_.erase(it); + RemoveRequest(it); } void MediaStreamManager::StopStreamFromUI(const std::string& label) { diff --git a/content/browser/renderer_host/media/media_stream_manager.h b/content/browser/renderer_host/media/media_stream_manager.h index 4558e0f..205ddb2 100644 --- a/content/browser/renderer_host/media/media_stream_manager.h +++ b/content/browser/renderer_host/media/media_stream_manager.h @@ -203,6 +203,7 @@ class CONTENT_EXPORT MediaStreamManager MediaStreamProvider* GetDeviceManager(MediaStreamType stream_type); void StartEnumeration(DeviceRequest* request); std::string AddRequest(DeviceRequest* request); + void RemoveRequest(DeviceRequests::iterator it); void ClearEnumerationCache(EnumerationCache* cache); void PostRequestToUI(const std::string& label); void HandleRequest(const std::string& label); @@ -250,6 +251,8 @@ class CONTENT_EXPORT MediaStreamManager // managers on the right thread. MessageLoop* io_loop_; + bool screen_capture_active_; + DISALLOW_COPY_AND_ASSIGN(MediaStreamManager); }; |