// 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 "base/lazy_instance.h" #include "base/memory/scoped_ptr.h" #include "build/build_config.h" #include "content/browser/media/audible_metrics.h" #include "content/browser/media/audio_stream_monitor.h" #include "content/browser/power_save_blocker_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/common/frame_messages.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "ipc/ipc_message_macros.h" namespace content { namespace { static base::LazyInstance::Leaky g_audible_metrics = LAZY_INSTANCE_INITIALIZER; } // anonymous namespace MediaWebContentsObserver::MediaWebContentsObserver(WebContents* web_contents) : WebContentsObserver(web_contents) {} MediaWebContentsObserver::~MediaWebContentsObserver() {} void MediaWebContentsObserver::WebContentsDestroyed() { g_audible_metrics.Get().UpdateAudibleWebContentsState(web_contents(), false); } void MediaWebContentsObserver::RenderFrameDeleted( RenderFrameHost* render_frame_host) { ClearPowerSaveBlockers(render_frame_host); } void MediaWebContentsObserver::MaybeUpdateAudibleState() { if (!AudioStreamMonitor::monitoring_available()) return; AudioStreamMonitor* audio_stream_monitor = static_cast(web_contents())->audio_stream_monitor(); if (audio_stream_monitor->WasRecentlyAudible()) { if (!audio_power_save_blocker_) CreateAudioPowerSaveBlocker(); } else { audio_power_save_blocker_.reset(); } g_audible_metrics.Get().UpdateAudibleWebContentsState( web_contents(), audio_stream_monitor->IsCurrentlyAudible()); } bool MediaWebContentsObserver::OnMessageReceived( const IPC::Message& msg, RenderFrameHost* render_frame_host) { bool handled = true; // TODO(dalecurtis): These should no longer be FrameHostMsg. IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(MediaWebContentsObserver, msg, render_frame_host) IPC_MESSAGE_HANDLER(FrameHostMsg_MediaPlayingNotification, OnMediaPlayingNotification) IPC_MESSAGE_HANDLER(FrameHostMsg_MediaPausedNotification, OnMediaPausedNotification) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } void MediaWebContentsObserver::OnMediaPlayingNotification( RenderFrameHost* render_frame_host, int64_t player_cookie, bool has_video, bool has_audio, bool is_remote) { // 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, player_cookie); if (has_audio) { AddMediaPlayerEntry(id, &active_audio_players_); // If we don't have audio stream monitoring, allocate the audio power save // blocker here instead of during NotifyNavigationStateChanged(). if (!audio_power_save_blocker_ && !AudioStreamMonitor::monitoring_available()) { CreateAudioPowerSaveBlocker(); } } 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(web_contents())->IsHidden()) { CreateVideoPowerSaveBlocker(); } } // Notify observers of the new player. DCHECK(has_audio || has_video); static_cast(web_contents())->MediaStartedPlaying(id); } void MediaWebContentsObserver::OnMediaPausedNotification( RenderFrameHost* render_frame_host, int64_t player_cookie) { const MediaPlayerId id(render_frame_host, player_cookie); const bool removed_audio = RemoveMediaPlayerEntry(id, &active_audio_players_); const bool removed_video = RemoveMediaPlayerEntry(id, &active_video_players_); MaybeReleasePowerSaveBlockers(); if (removed_audio || removed_video) { // Notify observers the player has been "paused". static_cast(web_contents())->MediaStoppedPlaying(id); } } void MediaWebContentsObserver::ClearPowerSaveBlockers( RenderFrameHost* render_frame_host) { std::set removed_players; RemoveAllMediaPlayerEntries(render_frame_host, &active_audio_players_, &removed_players); RemoveAllMediaPlayerEntries(render_frame_host, &active_video_players_, &removed_players); MaybeReleasePowerSaveBlockers(); // Notify all observers the player has been "paused". WebContentsImpl* wci = static_cast(web_contents()); for (const auto& id : removed_players) wci->MediaStoppedPlaying(id); } void MediaWebContentsObserver::CreateAudioPowerSaveBlocker() { DCHECK(!audio_power_save_blocker_); audio_power_save_blocker_ = PowerSaveBlocker::Create( PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, PowerSaveBlocker::kReasonAudioPlayback, "Playing audio"); } void MediaWebContentsObserver::CreateVideoPowerSaveBlocker() { DCHECK(!video_power_save_blocker_); DCHECK(!active_video_players_.empty()); video_power_save_blocker_ = PowerSaveBlocker::Create( PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep, PowerSaveBlocker::kReasonVideoPlayback, "Playing video"); // TODO(mfomitchev): Support PowerSaveBlocker on Aura - crbug.com/546718. #if defined(OS_ANDROID) && !defined(USE_AURA) static_cast(video_power_save_blocker_.get()) ->InitDisplaySleepBlocker(web_contents()); #endif } 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::MaybeReleasePowerSaveBlockers() { // If there are no more audio players and we don't have audio stream // monitoring, release the audio power save blocker here instead of during // NotifyNavigationStateChanged(). if (active_audio_players_.empty() && !AudioStreamMonitor::monitoring_available()) { audio_power_save_blocker_.reset(); } // 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) { DCHECK(std::find((*player_map)[id.first].begin(), (*player_map)[id.first].end(), id.second) == (*player_map)[id.first].end()); (*player_map)[id.first].push_back(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. auto player_for_removal = std::remove(it->second.begin(), it->second.end(), id.second); if (player_for_removal == it->second.end()) return false; it->second.erase(player_for_removal); // 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* removed_players) { auto it = player_map->find(render_frame_host); if (it == player_map->end()) return; for (int64_t player_cookie : it->second) removed_players->insert(MediaPlayerId(render_frame_host, player_cookie)); player_map->erase(it); } } // namespace content