| // Copyright 2014 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 "content/browser/media/media_web_contents_observer.h" |
| |
| #include <memory> |
| |
| #include "build/build_config.h" |
| #include "content/browser/media/audible_metrics.h" |
| #include "content/browser/media/audio_stream_monitor.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/media/media_player_delegate_messages.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "device/power_save_blocker/power_save_blocker.h" |
| #include "ipc/ipc_message_macros.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| AudibleMetrics* GetAudibleMetrics() { |
| static AudibleMetrics* metrics = new AudibleMetrics(); |
| return metrics; |
| } |
| |
| } // anonymous namespace |
| |
| MediaWebContentsObserver::MediaWebContentsObserver(WebContents* web_contents) |
| : WebContentsObserver(web_contents), |
| session_controllers_manager_(this) {} |
| |
| MediaWebContentsObserver::~MediaWebContentsObserver() = default; |
| |
| void MediaWebContentsObserver::WebContentsDestroyed() { |
| GetAudibleMetrics()->UpdateAudibleWebContentsState(web_contents(), false); |
| } |
| |
| void MediaWebContentsObserver::RenderFrameDeleted( |
| RenderFrameHost* render_frame_host) { |
| ClearPowerSaveBlockers(render_frame_host); |
| session_controllers_manager_.RenderFrameDeleted(render_frame_host); |
| |
| if (fullscreen_player_ && fullscreen_player_->first == render_frame_host) |
| fullscreen_player_.reset(); |
| } |
| |
| void MediaWebContentsObserver::MaybeUpdateAudibleState() { |
| AudioStreamMonitor* audio_stream_monitor = |
| static_cast<WebContentsImpl*>(web_contents())->audio_stream_monitor(); |
| |
| if (audio_stream_monitor->WasRecentlyAudible()) { |
| if (!audio_power_save_blocker_) |
| CreateAudioPowerSaveBlocker(); |
| } else { |
| audio_power_save_blocker_.reset(); |
| } |
| |
| GetAudibleMetrics()->UpdateAudibleWebContentsState( |
| web_contents(), audio_stream_monitor->IsCurrentlyAudible()); |
| } |
| |
| bool MediaWebContentsObserver::HasActiveEffectivelyFullscreenVideo() const { |
| if (!web_contents()->IsFullscreen() || !fullscreen_player_) |
| return false; |
| |
| // Check that the player is active. |
| const auto& players = active_video_players_.find(fullscreen_player_->first); |
| if (players == active_video_players_.end()) |
| return false; |
| if (players->second.find(fullscreen_player_->second) == players->second.end()) |
| return false; |
| |
| return true; |
| } |
| |
| bool MediaWebContentsObserver::OnMessageReceived( |
| const IPC::Message& msg, |
| RenderFrameHost* render_frame_host) { |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(MediaWebContentsObserver, msg, |
| render_frame_host) |
| IPC_MESSAGE_HANDLER(MediaPlayerDelegateHostMsg_OnMediaDestroyed, |
| OnMediaDestroyed) |
| IPC_MESSAGE_HANDLER(MediaPlayerDelegateHostMsg_OnMediaPaused, OnMediaPaused) |
| IPC_MESSAGE_HANDLER(MediaPlayerDelegateHostMsg_OnMediaPlaying, |
| OnMediaPlaying) |
| IPC_MESSAGE_HANDLER( |
| MediaPlayerDelegateHostMsg_OnMediaEffectivelyFullscreenChange, |
| OnMediaEffectivelyFullscreenChange) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| return handled; |
| } |
| |
| void MediaWebContentsObserver::WasShown() { |
| // Restore power save blocker if there are active video players running. |
| if (!active_video_players_.empty() && !video_power_save_blocker_) |
| CreateVideoPowerSaveBlocker(); |
| } |
| |
| void MediaWebContentsObserver::WasHidden() { |
| // If there are entities capturing screenshots or video (e.g., mirroring), |
| // don't release the power save blocker. |
| if (!web_contents()->GetCapturerCount()) |
| video_power_save_blocker_.reset(); |
| } |
| |
| void MediaWebContentsObserver::RequestPersistentVideo(bool value) { |
| if (!fullscreen_player_) |
| return; |
| |
| // The message is sent to the renderer even though the video is already the |
| // fullscreen element itself. It will eventually be handled by Blink. |
| Send(new MediaPlayerDelegateMsg_BecamePersistentVideo( |
| fullscreen_player_->first->GetRoutingID(), fullscreen_player_->second, |
| value)); |
| } |
| |
| void MediaWebContentsObserver::OnMediaDestroyed( |
| RenderFrameHost* render_frame_host, |
| int delegate_id) { |
| OnMediaPaused(render_frame_host, delegate_id, true); |
| } |
| |
| void MediaWebContentsObserver::OnMediaPaused(RenderFrameHost* render_frame_host, |
| int delegate_id, |
| bool reached_end_of_stream) { |
| const MediaPlayerId player_id(render_frame_host, delegate_id); |
| const bool removed_audio = |
| RemoveMediaPlayerEntry(player_id, &active_audio_players_); |
| const bool removed_video = |
| RemoveMediaPlayerEntry(player_id, &active_video_players_); |
| MaybeReleasePowerSaveBlockers(); |
| |
| if (removed_audio || removed_video) { |
| // Notify observers the player has been "paused". |
| static_cast<WebContentsImpl*>(web_contents()) |
| ->MediaStoppedPlaying( |
| WebContentsObserver::MediaPlayerInfo(removed_video), player_id); |
| } |
| |
| if (reached_end_of_stream) |
| session_controllers_manager_.OnEnd(player_id); |
| else |
| session_controllers_manager_.OnPause(player_id); |
| } |
| |
| void MediaWebContentsObserver::OnMediaPlaying( |
| RenderFrameHost* render_frame_host, |
| int delegate_id, |
| bool has_video, |
| bool has_audio, |
| bool is_remote, |
| media::MediaContentType media_content_type) { |
| // Ignore the videos playing remotely and don't hold the wake lock for the |
| // screen. TODO(dalecurtis): Is this correct? It means observers will not |
| // receive play and pause messages. |
| if (is_remote) |
| return; |
| |
| const MediaPlayerId id(render_frame_host, delegate_id); |
| if (has_audio) |
| AddMediaPlayerEntry(id, &active_audio_players_); |
| |
| if (has_video) { |
| AddMediaPlayerEntry(id, &active_video_players_); |
| |
| // If we're not hidden and have just created a player, create a blocker. |
| if (!video_power_save_blocker_ && |
| !static_cast<WebContentsImpl*>(web_contents())->IsHidden()) { |
| CreateVideoPowerSaveBlocker(); |
| } |
| } |
| |
| if (!session_controllers_manager_.RequestPlay( |
| id, has_audio, is_remote, media_content_type)) { |
| return; |
| } |
| |
| // Notify observers of the new player. |
| DCHECK(has_audio || has_video); |
| static_cast<WebContentsImpl*>(web_contents()) |
| ->MediaStartedPlaying(WebContentsObserver::MediaPlayerInfo(has_video), |
| id); |
| } |
| |
| void MediaWebContentsObserver::OnMediaEffectivelyFullscreenChange( |
| RenderFrameHost* render_frame_host, |
| int delegate_id, |
| bool is_fullscreen) { |
| const MediaPlayerId id(render_frame_host, delegate_id); |
| |
| if (!is_fullscreen) { |
| if (fullscreen_player_ && *fullscreen_player_ == id) |
| fullscreen_player_.reset(); |
| return; |
| } |
| |
| fullscreen_player_ = id; |
| } |
| |
| void MediaWebContentsObserver::ClearPowerSaveBlockers( |
| RenderFrameHost* render_frame_host) { |
| std::set<MediaPlayerId> removed_players; |
| RemoveAllMediaPlayerEntries(render_frame_host, &active_video_players_, |
| &removed_players); |
| std::set<MediaPlayerId> video_players(removed_players); |
| RemoveAllMediaPlayerEntries(render_frame_host, &active_audio_players_, |
| &removed_players); |
| MaybeReleasePowerSaveBlockers(); |
| |
| // Notify all observers the player has been "paused". |
| WebContentsImpl* wci = static_cast<WebContentsImpl*>(web_contents()); |
| for (const auto& id : removed_players) { |
| auto it = video_players.find(id); |
| bool was_video = (it != video_players.end()); |
| wci->MediaStoppedPlaying(WebContentsObserver::MediaPlayerInfo(was_video), |
| id); |
| } |
| } |
| |
| void MediaWebContentsObserver::CreateAudioPowerSaveBlocker() { |
| DCHECK(!audio_power_save_blocker_); |
| audio_power_save_blocker_.reset(new device::PowerSaveBlocker( |
| device::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, |
| device::PowerSaveBlocker::kReasonAudioPlayback, "Playing audio", |
| BrowserThread::GetTaskRunnerForThread(BrowserThread::UI), |
| BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE))); |
| } |
| |
| void MediaWebContentsObserver::CreateVideoPowerSaveBlocker() { |
| DCHECK(!video_power_save_blocker_); |
| DCHECK(!active_video_players_.empty()); |
| video_power_save_blocker_.reset(new device::PowerSaveBlocker( |
| device::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep, |
| device::PowerSaveBlocker::kReasonVideoPlayback, "Playing video", |
| BrowserThread::GetTaskRunnerForThread(BrowserThread::UI), |
| BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE))); |
| #if defined(OS_ANDROID) |
| if (web_contents()->GetNativeView()) { |
| video_power_save_blocker_.get()->InitDisplaySleepBlocker( |
| web_contents()->GetNativeView()); |
| } |
| #endif |
| } |
| |
| void MediaWebContentsObserver::MaybeReleasePowerSaveBlockers() { |
| // If there are no more video players, clear the video power save blocker. |
| if (active_video_players_.empty()) |
| video_power_save_blocker_.reset(); |
| } |
| |
| void MediaWebContentsObserver::AddMediaPlayerEntry( |
| const MediaPlayerId& id, |
| ActiveMediaPlayerMap* player_map) { |
| (*player_map)[id.first].insert(id.second); |
| } |
| |
| bool MediaWebContentsObserver::RemoveMediaPlayerEntry( |
| const MediaPlayerId& id, |
| ActiveMediaPlayerMap* player_map) { |
| auto it = player_map->find(id.first); |
| if (it == player_map->end()) |
| return false; |
| |
| // Remove the player. |
| bool did_remove = it->second.erase(id.second) == 1; |
| if (!did_remove) |
| return false; |
| |
| // If there are no players left, remove the map entry. |
| if (it->second.empty()) |
| player_map->erase(it); |
| |
| return true; |
| } |
| |
| void MediaWebContentsObserver::RemoveAllMediaPlayerEntries( |
| RenderFrameHost* render_frame_host, |
| ActiveMediaPlayerMap* player_map, |
| std::set<MediaPlayerId>* removed_players) { |
| auto it = player_map->find(render_frame_host); |
| if (it == player_map->end()) |
| return; |
| |
| for (int delegate_id : it->second) |
| removed_players->insert(MediaPlayerId(render_frame_host, delegate_id)); |
| |
| player_map->erase(it); |
| } |
| |
| } // namespace content |