Commit b0a785a1 authored by liberato@chromium.org's avatar liberato@chromium.org Committed by Commit Bot

Make MediaWebContentsObserver use MediaExperimentManager

This adds calls to MediaExperimentManager when players start / stop,
and receives callbacks when a player is the only one playing.  Right
now, these callbacks are ignored.

Adds a switch "MediaPowerExperiment" to enable tracking players, in
preparation for the power experiment.

This CL also simplifies MediaExperimentManager.

Change-Id: I02aa61971c7ca59093dcf2d7336709e25b8a70a7
Bug: 1017783
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1879751
Commit-Queue: Frank Liberato <liberato@chromium.org>
Reviewed-by: default avatarChrome Cunningham <chcunningham@chromium.org>
Cr-Commit-Position: refs/heads/master@{#710007}
parent 3c9e618c
......@@ -1183,8 +1183,6 @@ jumbo_source_set("browser") {
"media/media_devices_permission_checker.h",
"media/media_devices_util.cc",
"media/media_devices_util.h",
"media/media_experiment_manager.cc",
"media/media_experiment_manager.h",
"media/media_interface_factory_holder.cc",
"media/media_interface_factory_holder.h",
"media/media_interface_proxy.cc",
......@@ -1201,6 +1199,8 @@ jumbo_source_set("browser") {
"media/media_internals_ui.h",
"media/media_keys_listener_manager_impl.cc",
"media/media_keys_listener_manager_impl.h",
"media/media_power_experiment_manager.cc",
"media/media_power_experiment_manager.h",
"media/media_web_contents_observer.cc",
"media/media_web_contents_observer.h",
"media/midi_host.cc",
......
// Copyright 2019 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_experiment_manager.h"
#include <utility>
#include "base/bind_helpers.h"
namespace content {
MediaExperimentManager::ScopedPlayerState::ScopedPlayerState(
base::OnceClosure destruction_cb,
PlayerState* state)
: state_(state), destruction_cb_(std::move(destruction_cb)) {}
MediaExperimentManager::ScopedPlayerState::ScopedPlayerState(
ScopedPlayerState&& rhs) {
destruction_cb_ = std::move(rhs.destruction_cb_);
state_ = rhs.state_;
}
MediaExperimentManager::ScopedPlayerState::~ScopedPlayerState() {
if (destruction_cb_)
std::move(destruction_cb_).Run();
}
MediaExperimentManager::MediaExperimentManager() = default;
MediaExperimentManager::~MediaExperimentManager() = default;
// Keeps track of all media players across all pages, and notifies them when
// they enter or leave an active experiment.
void MediaExperimentManager::PlayerCreated(const MediaPlayerId& player_id,
Client* client) {
// TODO: check that we don't know about it already.
player_ids_by_client_[client].insert(player_id);
players_[player_id].client = client;
}
void MediaExperimentManager::PlayerDestroyed(const MediaPlayerId& player_id) {
ErasePlayersInternal({player_id});
}
MediaExperimentManager::ScopedPlayerState
MediaExperimentManager::GetPlayerState(const MediaPlayerId& player_id) {
auto iter = players_.find(player_id);
DCHECK(iter != players_.end());
// TODO(liberato): Replace this callback with something that checks the
// experiment state, to see if an experiment has started / stopped.
return ScopedPlayerState(base::DoNothing(), &iter->second);
}
void MediaExperimentManager::ClientDestroyed(Client* client) {
auto by_client = player_ids_by_client_.find(client);
// Make a copy, since ErasePlayers will modify the original.
ErasePlayersInternal(std::set<MediaPlayerId>(by_client->second));
}
void MediaExperimentManager::ErasePlayersInternal(
const std::set<MediaPlayerId>& player_ids) {
for (auto player_id : player_ids) {
auto player_iter = players_.find(player_id);
DCHECK(player_iter != players_.end());
// Erase this player from the client, and maybe the client if it's the
// only player owned by it.
auto by_client_iter =
player_ids_by_client_.find(player_iter->second.client);
DCHECK(by_client_iter != player_ids_by_client_.end());
by_client_iter->second.erase(player_id);
if (by_client_iter->second.size() == 0)
player_ids_by_client_.erase(by_client_iter);
players_.erase(player_iter);
}
}
size_t MediaExperimentManager::GetPlayerCountForTesting() const {
return players_.size();
}
// static
MediaExperimentManager* MediaExperimentManager::Instance() {
return nullptr;
}
} // namespace content
// Copyright 2019 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 CONTENT_BROWSER_MEDIA_MEDIA_EXPERIMENT_MANAGER_H_
#define CONTENT_BROWSER_MEDIA_MEDIA_EXPERIMENT_MANAGER_H_
#include <map>
#include <set>
#include <vector>
#include "base/callback.h"
#include "base/macros.h"
#include "base/optional.h"
#include "content/common/content_export.h"
#include "content/public/browser/media_player_id.h"
#include "media/base/video_codecs.h"
namespace content {
// Keeps track of all media players across all pages, and notifies them when
// they enter or leave an active experiment.
class CONTENT_EXPORT MediaExperimentManager {
public:
// The Client interface allows the manager to send messages back to the
// player about experiment state.
class CONTENT_EXPORT Client {
public:
Client() = default;
virtual ~Client() = default;
// Called when |player| becomes the focus of some experiment.
// TODO: Should include an ExperimentId, so the player knows what to do.
virtual void OnExperimentStarted(const MediaPlayerId& player) = 0;
// Called when |player| stops being the focus of some experiment. Not
// called when the player is destroyed, or the player's client is destroyed,
// even though it does quit being the focus at that point too.
// TODO: Should include an ExperimentId, so the player knows what to do.
virtual void OnExperimentStopped(const MediaPlayerId& player) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(Client);
};
// State for one media player.
struct PlayerState {
Client* client = nullptr;
bool is_playing = false;
bool is_fullscreen = false;
bool is_pip = false;
};
class CONTENT_EXPORT ScopedPlayerState {
public:
ScopedPlayerState(base::OnceClosure destruction_cb, PlayerState* state);
ScopedPlayerState(ScopedPlayerState&&);
~ScopedPlayerState();
PlayerState* operator->() { return state_; }
private:
PlayerState* state_;
base::OnceClosure destruction_cb_;
DISALLOW_COPY_AND_ASSIGN(ScopedPlayerState);
};
MediaExperimentManager();
virtual ~MediaExperimentManager();
static MediaExperimentManager* Instance();
// Notifies us that |player| has been created, and is being managed by
// |client|. |client| must exist until all its players have been destroyed
// via calls to DestroyPlayer, or the client calls ClientDestroyed().
virtual void PlayerCreated(const MediaPlayerId& player, Client* client);
// Called when the given player has been destroyed.
virtual void PlayerDestroyed(const MediaPlayerId& player);
// Notify us that |client| is being destroyed. All players that it created
// will be deleted. No further notifications will be sent to it. This is
// useful, for example, when a page is being destroyed so that we don't keep
// sending notifications while everything is being torn down. It's not an
// error if |client| has no active players.
virtual void ClientDestroyed(Client* client);
// Update the player state. When the returned ScopedMediaPlayerState is
// destroyed, we will process the changes. One may not create or destroy
// players while the ScopedMediaPlayerState exists.
virtual ScopedPlayerState GetPlayerState(const MediaPlayerId& player);
// Return the number of players total.
size_t GetPlayerCountForTesting() const;
private:
// Erase all players in |player_ids|. Does not send any notifications, nor
// does it FindRunningExperiments.
void ErasePlayersInternal(const std::set<MediaPlayerId>& player_ids);
// Set of all players that we know about.
std::map<MediaPlayerId, PlayerState> players_;
// [client] == set of all player ids that it owns.
std::map<Client*, std::set<MediaPlayerId>> player_ids_by_client_;
DISALLOW_COPY_AND_ASSIGN(MediaExperimentManager);
};
} // namespace content
#endif // CONTENT_BROWSER_MEDIA_MEDIA_EXPERIMENT_MANAGER_H_
// Copyright 2019 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_experiment_manager.h"
#include "base/bind_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/time/time.h"
#include "base/version.h"
#include "media/base/media_controller.h"
#include "media/base/mock_filters.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
using ScopedPlayerState = MediaExperimentManager::ScopedPlayerState;
class MockExperimentClient : public MediaExperimentManager::Client {
public:
MOCK_METHOD1(OnExperimentStarted, void(const MediaPlayerId& player));
MOCK_METHOD1(OnExperimentStopped, void(const MediaPlayerId& player));
};
class MediaExperimentManagerTest : public testing::Test {
public:
MediaExperimentManagerTest()
: manager_(std::make_unique<MediaExperimentManager>()),
player_id_1_(nullptr, 1),
player_id_2_(nullptr, 2) {}
protected:
std::unique_ptr<MediaExperimentManager> manager_;
// Unique player IDs. Note that we can't CreateMediaPlayerIdForTesting()
// since it doesn't return unique IDs.
MediaPlayerId player_id_1_;
MediaPlayerId player_id_2_;
};
TEST_F(MediaExperimentManagerTest, CreateAndDestroyPlayer) {
MockExperimentClient client;
EXPECT_EQ(manager_->GetPlayerCountForTesting(), 0u);
manager_->PlayerCreated(player_id_1_, &client);
EXPECT_EQ(manager_->GetPlayerCountForTesting(), 1u);
manager_->PlayerCreated(player_id_2_, &client);
EXPECT_EQ(manager_->GetPlayerCountForTesting(), 2u);
manager_->PlayerDestroyed(player_id_1_);
EXPECT_EQ(manager_->GetPlayerCountForTesting(), 1u);
manager_->PlayerDestroyed(player_id_2_);
EXPECT_EQ(manager_->GetPlayerCountForTesting(), 0u);
}
TEST_F(MediaExperimentManagerTest, CreatePlayerAndDestroyClient) {
// Create two players from one client, and make sure that destroying the
// client destroys both players.
MockExperimentClient client;
manager_->PlayerCreated(player_id_1_, &client);
manager_->PlayerCreated(player_id_2_, &client);
EXPECT_EQ(manager_->GetPlayerCountForTesting(), 2u);
manager_->ClientDestroyed(&client);
EXPECT_EQ(manager_->GetPlayerCountForTesting(), 0u);
}
TEST_F(MediaExperimentManagerTest, CreateTwoClientsAndDestroyOneClient) {
// Create one player each in two clients, and verify that destroying one
// client destroys only one player.
MockExperimentClient client_1;
MockExperimentClient client_2;
manager_->PlayerCreated(player_id_1_, &client_1);
manager_->PlayerCreated(player_id_2_, &client_2);
EXPECT_EQ(manager_->GetPlayerCountForTesting(), 2u);
manager_->ClientDestroyed(&client_1);
EXPECT_EQ(manager_->GetPlayerCountForTesting(), 1u);
}
TEST_F(MediaExperimentManagerTest, ScopedPlayerStateModifiesState) {
MockExperimentClient client_1;
MockExperimentClient client_2;
manager_->PlayerCreated(player_id_1_, &client_1);
manager_->PlayerCreated(player_id_2_, &client_2);
// Set the player state differently for each player. We set two things so
// that each player has a non-default value.
{
ScopedPlayerState state = manager_->GetPlayerState(player_id_1_);
state->is_fullscreen = true;
state->is_pip = false;
}
{
ScopedPlayerState state = manager_->GetPlayerState(player_id_2_);
state->is_fullscreen = false;
state->is_pip = true;
}
// Make sure that the player state matches what we set for it.
{
ScopedPlayerState state = manager_->GetPlayerState(player_id_1_);
EXPECT_TRUE(state->is_fullscreen);
EXPECT_FALSE(state->is_pip);
}
{
ScopedPlayerState state = manager_->GetPlayerState(player_id_2_);
EXPECT_FALSE(state->is_fullscreen);
EXPECT_TRUE(state->is_pip);
}
}
TEST_F(MediaExperimentManagerTest, ScopedPlayerStateCallsCallback) {
bool cb_called = false;
base::OnceClosure cb = base::BindOnce([](bool* flag) { *flag = true; },
base::Unretained(&cb_called));
MediaExperimentManager::PlayerState state;
state.is_fullscreen = false;
// Normally, these would not by dynamically allocated. However, this makes
// it much easier to control when they're destroyed. This is also why we
// reference the underying state as (*scoped_1)-> ; we want tp use the
// overloaded -> operator on ScopedPlayerState, not unique_ptr.
std::unique_ptr<ScopedPlayerState> scoped_1 =
std::make_unique<ScopedPlayerState>(std::move(cb), &state);
(*scoped_1)->is_fullscreen = true;
EXPECT_TRUE(state.is_fullscreen);
EXPECT_FALSE(cb_called);
// Moving |scoped_1| and deleting it should not call the callback.
std::unique_ptr<ScopedPlayerState> scoped_2 =
std::make_unique<ScopedPlayerState>(std::move(*scoped_1));
scoped_1.reset();
EXPECT_FALSE(cb_called);
// |scoped_2| should now modify |state|.
(*scoped_2)->is_fullscreen = false;
EXPECT_FALSE(state.is_fullscreen);
// Deleting |scoped_2| should call the callback.
scoped_2.reset();
EXPECT_TRUE(cb_called);
}
} // namespace content
// Copyright 2019 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_power_experiment_manager.h"
#include <memory>
#include <utility>
#include "base/bind_helpers.h"
#include "base/feature_list.h"
#include "base/no_destructor.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "media/base/media_switches.h"
namespace content {
MediaPowerExperimentManager::MediaPowerExperimentManager()
: task_runner_(base::SequencedTaskRunnerHandle::Get()) {}
MediaPowerExperimentManager::~MediaPowerExperimentManager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void MediaPowerExperimentManager::PlayerStarted(const MediaPlayerId& player_id,
ExperimentCB cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
players_[player_id] = std::move(cb);
CheckExperimentState();
}
void MediaPowerExperimentManager::PlayerStopped(
const MediaPlayerId& player_id,
NotificationMode notification_mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If we're supposed to skip notification, then clear the current player if
// it matches |player_id|, so that CheckExperimentState doesn't notify it.
if (notification_mode == NotificationMode::kSkip &&
current_experiment_player_ && *current_experiment_player_ == player_id) {
current_experiment_player_.reset();
current_experiment_cb_ = ExperimentCB();
}
players_.erase(player_id);
CheckExperimentState();
}
void MediaPowerExperimentManager::CheckExperimentState() {
// See if an experiment should be running.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::Optional<MediaPlayerId> new_experiment_player;
if (players_.size() == 1)
new_experiment_player = players_.begin()->first;
// If there's a current experiment running, and it's not the same as the
// incoming one (if any), then stop it and clear the current player.
if (current_experiment_player_ &&
(!new_experiment_player ||
*current_experiment_player_ != *new_experiment_player)) {
task_runner_->PostTask(FROM_HERE,
base::BindOnce(current_experiment_cb_, false));
current_experiment_player_.reset();
current_experiment_cb_ = ExperimentCB();
}
// If there's an incoming player that's not currently running, then notify it
// and remember the current player.
if (!current_experiment_player_ && new_experiment_player) {
current_experiment_player_ = std::move(new_experiment_player);
// We cache the callback so that, when this player is stopped, we can
// notify it. Otherwise, it's removed from the map by the time we're here.
current_experiment_cb_ = players_.find(*current_experiment_player_)->second;
task_runner_->PostTask(FROM_HERE,
base::BindOnce(current_experiment_cb_, true));
}
}
// static
MediaPowerExperimentManager* MediaPowerExperimentManager::Instance() {
// Return nullptr unless an experiment is enabled that needs us.
if (base::FeatureList::IsEnabled(media::kMediaPowerExperiment)) {
static base::NoDestructor<MediaPowerExperimentManager> s_manager;
return s_manager.get();
}
return nullptr;
}
} // namespace content
// Copyright 2019 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 CONTENT_BROWSER_MEDIA_MEDIA_POWER_EXPERIMENT_MANAGER_H_
#define CONTENT_BROWSER_MEDIA_MEDIA_POWER_EXPERIMENT_MANAGER_H_
#include <map>
#include <set>
#include <vector>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/optional.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "content/common/content_export.h"
#include "content/public/browser/media_player_id.h"
#include "media/base/video_codecs.h"
namespace content {
// Keeps track of all media players across all pages, and notifies them when
// they enter or leave an active experiment.
class CONTENT_EXPORT MediaPowerExperimentManager {
public:
// Callback to notify the client when an experiment starts / stops.
using ExperimentCB = base::RepeatingCallback<void(bool)>;
// Flags for handling notifications of stopped players.
enum class NotificationMode {
// If the stopped player is the current experiment, then notify it that it
// no longer is.
kNotify,
// If the stopped player is the current experiment, then skip notification.
// This is useful if the player is being destroyed.
kSkip
};
MediaPowerExperimentManager();
virtual ~MediaPowerExperimentManager();
// May return nullptr if experiments aren't enabled.
static MediaPowerExperimentManager* Instance();
// Called when the given player begins playing. |cb| will be called if it
// becomes / stops being the only playing player, though never re-entrantly.
virtual void PlayerStarted(const MediaPlayerId& player, ExperimentCB cb);
// Called when the given player has stopped playing. It is okay if it was
// never started via PlayerStarted; we'll just ignore it. If
// |notification_mode| is kSkip, then we won't notify |player| that it's
// stopping, if it's the current experiment.
virtual void PlayerStopped(
const MediaPlayerId& player,
NotificationMode notification_mode = NotificationMode::kNotify);
private:
// Send start / stop notifications and update |current_experiment_player_|
// based on whether an experiment should be running.
void CheckExperimentState();
// Set of all playing players that we know about.
std::map<MediaPlayerId, ExperimentCB> players_;
// If set, this is the player that has a running experiment.
base::Optional<MediaPlayerId> current_experiment_player_;
ExperimentCB current_experiment_cb_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(MediaPowerExperimentManager);
};
} // namespace content
#endif // CONTENT_BROWSER_MEDIA_MEDIA_POWER_EXPERIMENT_MANAGER_H_
// Copyright 2019 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_power_experiment_manager.h"
#include "base/bind_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "base/version.h"
#include "content/public/test/browser_task_environment.h"
#include "media/base/media_controller.h"
#include "media/base/media_switches.h"
#include "media/base/mock_filters.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
class MediaPowerExperimentManagerTest : public testing::Test {
public:
MediaPowerExperimentManagerTest()
: manager_(std::make_unique<MediaPowerExperimentManager>()),
player_id_1_(nullptr, 1),
player_id_2_(nullptr, 2),
cb_1_(base::BindRepeating([](bool* out, bool state) { *out = state; },
&experiment_state_1_)),
cb_2_(base::BindRepeating([](bool* out, bool state) { *out = state; },
&experiment_state_2_)) {}
protected:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<MediaPowerExperimentManager> manager_;
// Unique player IDs. Note that we can't CreateMediaPlayerIdForTesting()
// since it doesn't return unique IDs.
MediaPlayerId player_id_1_;
MediaPlayerId player_id_2_;
bool experiment_state_1_ = false;
bool experiment_state_2_ = false;
MediaPowerExperimentManager::ExperimentCB cb_1_;
MediaPowerExperimentManager::ExperimentCB cb_2_;
};
TEST_F(MediaPowerExperimentManagerTest, ExperimentStopsWhenPlayerStopped) {
// Create a player, and verify that the the experiment starts, then stops
// when it stops playing.
EXPECT_FALSE(experiment_state_1_);
manager_->PlayerStarted(player_id_1_, cb_1_);
// Should call back, but not re-entrantly.
EXPECT_FALSE(experiment_state_1_);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(experiment_state_1_);
manager_->PlayerStopped(player_id_1_);
EXPECT_TRUE(experiment_state_1_);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(experiment_state_1_);
}
TEST_F(MediaPowerExperimentManagerTest, ExperimentStopsWhenSecondPlayerStarts) {
// If we add a second playing player, then the experiment should stop.
manager_->PlayerStarted(player_id_1_, cb_1_);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(experiment_state_1_);
EXPECT_FALSE(experiment_state_2_);
manager_->PlayerStarted(player_id_2_, cb_2_);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(experiment_state_1_);
EXPECT_FALSE(experiment_state_2_);
manager_->PlayerStopped(player_id_1_);
manager_->PlayerStopped(player_id_2_);
}
TEST_F(MediaPowerExperimentManagerTest,
ExperimentRestartsWhenFirstPlayerRemoved) {
// If we add a second playing player, then removing the first should start it.
manager_->PlayerStarted(player_id_1_, cb_1_);
manager_->PlayerStarted(player_id_2_, cb_2_);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(experiment_state_1_);
EXPECT_FALSE(experiment_state_2_);
manager_->PlayerStopped(player_id_1_);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(experiment_state_1_);
EXPECT_TRUE(experiment_state_2_);
manager_->PlayerStopped(player_id_2_);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(experiment_state_2_);
}
TEST_F(MediaPowerExperimentManagerTest, NotificationsCanBeSkipped) {
manager_->PlayerStarted(player_id_1_, cb_1_);
manager_->PlayerStopped(player_id_1_,
MediaPowerExperimentManager::NotificationMode::kSkip);
base::RunLoop().RunUntilIdle();
// Since we skipped notification, the state should still be true.
EXPECT_TRUE(experiment_state_1_);
}
} // namespace content
......@@ -66,9 +66,14 @@ static void SuspendAllMediaPlayersInRenderFrame(
MediaWebContentsObserver::MediaWebContentsObserver(WebContents* web_contents)
: WebContentsObserver(web_contents),
audible_metrics_(GetAudibleMetrics()),
session_controllers_manager_(this) {}
session_controllers_manager_(this),
power_experiment_manager_(MediaPowerExperimentManager::Instance()),
weak_factory_(this) {}
MediaWebContentsObserver::~MediaWebContentsObserver() = default;
MediaWebContentsObserver::~MediaWebContentsObserver() {
// Remove all players so that the experiment manager is notified.
RemoveAllPlayers();
}
void MediaWebContentsObserver::WebContentsDestroyed() {
AudioStreamMonitor* audio_stream_monitor =
......@@ -77,6 +82,9 @@ void MediaWebContentsObserver::WebContentsDestroyed() {
audible_metrics_->WebContentsDestroyed(
web_contents(), audio_stream_monitor->WasRecentlyAudible() &&
!web_contents()->IsAudioMuted());
// Remove all players so that the experiment manager is notified.
RemoveAllPlayers();
}
void MediaWebContentsObserver::RenderFrameDeleted(
......@@ -179,6 +187,7 @@ bool MediaWebContentsObserver::IsPlayerActive(
void MediaWebContentsObserver::OnMediaDestroyed(
RenderFrameHost* render_frame_host,
int delegate_id) {
// TODO(liberato): Should we skip power manager notifications in this case?
OnMediaPaused(render_frame_host, delegate_id, true);
}
......@@ -351,11 +360,21 @@ void MediaWebContentsObserver::AddMediaPlayerEntry(
const MediaPlayerId& id,
ActiveMediaPlayerMap* player_map) {
(*player_map)[id.render_frame_host].insert(id.delegate_id);
if (power_experiment_manager_) {
power_experiment_manager_->PlayerStarted(
id,
base::BindRepeating(&MediaWebContentsObserver::OnExperimentStateChanged,
weak_factory_.GetWeakPtr(), id));
}
}
bool MediaWebContentsObserver::RemoveMediaPlayerEntry(
const MediaPlayerId& id,
ActiveMediaPlayerMap* player_map) {
// If the power experiment is running, then notify it.
if (power_experiment_manager_)
power_experiment_manager_->PlayerStopped(id);
auto it = player_map->find(id.render_frame_host);
if (it == player_map->end())
return false;
......@@ -380,8 +399,17 @@ void MediaWebContentsObserver::RemoveAllMediaPlayerEntries(
if (it == player_map->end())
return;
for (int delegate_id : it->second)
removed_players->insert(MediaPlayerId(render_frame_host, delegate_id));
for (int delegate_id : it->second) {
MediaPlayerId id(render_frame_host, delegate_id);
removed_players->insert(id);
// Since the player is being destroyed, don't bother to notify it if it's
// no longer the active experiment.
if (power_experiment_manager_) {
power_experiment_manager_->PlayerStopped(
id, MediaPowerExperimentManager::NotificationMode::kSkip);
}
}
player_map->erase(it);
}
......@@ -397,4 +425,29 @@ void MediaWebContentsObserver::SuspendAllMediaPlayers() {
}
#endif // defined(OS_ANDROID)
void MediaWebContentsObserver::OnExperimentStateChanged(MediaPlayerId id,
bool is_starting) {
// TODO(liberato): Notify the player.
}
void MediaWebContentsObserver::RemoveAllPlayers(
ActiveMediaPlayerMap* player_map) {
if (power_experiment_manager_) {
for (auto& iter : *player_map) {
for (auto delegate_id : iter.second) {
MediaPlayerId id(iter.first, delegate_id);
power_experiment_manager_->PlayerStopped(
id, MediaPowerExperimentManager::NotificationMode::kSkip);
}
}
}
player_map->clear();
}
void MediaWebContentsObserver::RemoveAllPlayers() {
RemoveAllPlayers(&active_audio_players_);
RemoveAllPlayers(&active_video_players_);
}
} // namespace content
......@@ -13,6 +13,7 @@
#include "base/macros.h"
#include "build/build_config.h"
#include "content/browser/media/media_power_experiment_manager.h"
#include "content/browser/media/session/media_session_controllers_manager.h"
#include "content/common/content_export.h"
#include "content/public/browser/media_player_id.h"
......@@ -161,6 +162,16 @@ class CONTENT_EXPORT MediaWebContentsObserver : public WebContentsObserver {
// Convenience method that casts web_contents() to a WebContentsImpl*.
WebContentsImpl* web_contents_impl() const;
// Notify |id| about |is_starting|. Note that |id| might no longer be in the
// active players list, which is fine.
void OnExperimentStateChanged(MediaPlayerId id, bool is_starting);
// Remove all players from |player_map|.
void RemoveAllPlayers(ActiveMediaPlayerMap* player_map);
// Remove all players.
void RemoveAllPlayers();
// Helper class for recording audible metrics.
AudibleMetrics* audible_metrics_;
......@@ -173,6 +184,9 @@ class CONTENT_EXPORT MediaWebContentsObserver : public WebContentsObserver {
bool has_audio_wake_lock_for_testing_ = false;
MediaSessionControllersManager session_controllers_manager_;
MediaPowerExperimentManager* power_experiment_manager_ = nullptr;
base::WeakPtrFactory<MediaWebContentsObserver> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(MediaWebContentsObserver);
};
......
......@@ -1660,8 +1660,8 @@ test("content_unittests") {
"../browser/media/flinging_renderer_unittest.cc",
"../browser/media/forwarding_audio_stream_factory_unittest.cc",
"../browser/media/media_devices_permission_checker_unittest.cc",
"../browser/media/media_experiment_manager_unittest.cc",
"../browser/media/media_internals_unittest.cc",
"../browser/media/media_power_experiment_manager_unittest.cc",
"../browser/media/midi_host_unittest.cc",
"../browser/media/session/media_session_controller_unittest.cc",
"../browser/media/session/media_session_controllers_manager_unittest.cc",
......
......@@ -578,6 +578,10 @@ const base::Feature kMediaLearningExperiment{"MediaLearningExperiment",
const base::Feature kMediaLearningFramework{"MediaLearningFramework",
base::FEATURE_DISABLED_BY_DEFAULT};
// Enable aggregate power measurement for media playback.
const base::Feature kMediaPowerExperiment{"MediaPowerExperiment",
base::FEATURE_DISABLED_BY_DEFAULT};
// Enables flash to be ducked by audio focus. This is enabled on Chrome OS which
// has audio focus enabled.
const base::Feature kAudioFocusDuckFlash {
......
......@@ -123,6 +123,7 @@ MEDIA_EXPORT extern const base::Feature kMediaEngagementHTTPSOnly;
MEDIA_EXPORT extern const base::Feature kMediaInspectorLogging;
MEDIA_EXPORT extern const base::Feature kMediaLearningExperiment;
MEDIA_EXPORT extern const base::Feature kMediaLearningFramework;
MEDIA_EXPORT extern const base::Feature kMediaPowerExperiment;
MEDIA_EXPORT extern const base::Feature kMemoryPressureBasedSourceBufferGC;
MEDIA_EXPORT extern const base::Feature kChromeosVideoDecoder;
MEDIA_EXPORT extern const base::Feature kNewEncodeCpuLoadEstimator;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment