blob: 67ef0368680e8d6be542d65dd5b9f43a857ffd01 [file] [log] [blame]
// 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<AudibleMetrics>::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<WebContentsImpl*>(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<WebContentsImpl*>(web_contents())->IsHidden()) {
CreateVideoPowerSaveBlocker();
}
}
// Notify observers of the new player.
DCHECK(has_audio || has_video);
static_cast<WebContentsImpl*>(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<WebContentsImpl*>(web_contents())->MediaStoppedPlaying(id);
}
}
void MediaWebContentsObserver::ClearPowerSaveBlockers(
RenderFrameHost* render_frame_host) {
std::set<MediaPlayerId> 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<WebContentsImpl*>(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<PowerSaveBlockerImpl*>(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<MediaPlayerId>* 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