summaryrefslogtreecommitdiffstats
path: root/content/browser/media
diff options
context:
space:
mode:
authordalecurtis <dalecurtis@chromium.org>2016-01-27 13:10:25 -0800
committerCommit bot <commit-bot@chromium.org>2016-01-27 21:11:23 +0000
commitbb3eaacc70007d44dc7788fcbbe106977f49b066 (patch)
tree74130bda24af1c328edb0fa4b5250a7213e74294 /content/browser/media
parent491c71da6d6b3dc6bfe529d11537b667a6085ebc (diff)
downloadchromium_src-bb3eaacc70007d44dc7788fcbbe106977f49b066.zip
chromium_src-bb3eaacc70007d44dc7788fcbbe106977f49b066.tar.gz
chromium_src-bb3eaacc70007d44dc7788fcbbe106977f49b066.tar.bz2
Implement MediaSession on top of the WebMediaPlayerDelegate.
Extracts the existing MediaSession usage from the browser media player manager and moves it into the WebMediaPlayerDelegate where it can be shared by both the desktop and android media players. Notes: - Removes RenderFrameObserver from all WebMediaPlayers since the delegate observer interface handles all of their needs. - The MediaStream WebMediaPlayer now has MediaSession support. - Currently the WMPI has no concept of requesting permission to play, the WMPA behavior has been preserved via a request to the MediaWebContentsObserverAndroid from BrowserMediaPlayerManager. - Fixes flakiness issues with the MediaSession tests which were not completely waiting for playback to start before moving on with test expectations. - Extracts misplaced delegate messages from frame_messages.h and puts them in their own media_player_delegate_messages.h file. - During the message move, cleans up the player_cookie (a int64_t pointer) in favor of a plain int. Renames messages for the better. - Removes all delegate calls from the cast adapter since they are always remote type which should be ignored anyways. - |has_audio| is sticky in the MediaSessionController since WMPA can't be relied upon to provide a true value and plumbing the value from every MediaPlayerAndroid is non-trivial. - Fixes MediaSession ContentShellTests which were passing on the bots by happy circumstance -- the bots do not appear able to play the .ogg files checked in -- instead instantly ending. Test were fixed by adding .mp3 variants. BUG=529887, 580626 TEST=new tests, existing MediaSession tests pass with and without the unified media pipeline flag, manual verification of background behavior. Review URL: https://codereview.chromium.org/1570043002 Cr-Commit-Position: refs/heads/master@{#371870}
Diffstat (limited to 'content/browser/media')
-rw-r--r--content/browser/media/android/browser_media_player_manager.cc59
-rw-r--r--content/browser/media/android/browser_media_player_manager.h17
-rw-r--r--content/browser/media/android/media_session_controller.cc109
-rw-r--r--content/browser/media/android/media_session_controller.h69
-rw-r--r--content/browser/media/android/media_session_controller_unittest.cc216
-rw-r--r--content/browser/media/android/media_web_contents_observer_android.cc95
-rw-r--r--content/browser/media/android/media_web_contents_observer_android.h31
-rw-r--r--content/browser/media/media_web_contents_observer.cc85
-rw-r--r--content/browser/media/media_web_contents_observer.h19
9 files changed, 594 insertions, 106 deletions
diff --git a/content/browser/media/android/browser_media_player_manager.cc b/content/browser/media/android/browser_media_player_manager.cc
index 8d60c6a..7484a9d 100644
--- a/content/browser/media/android/browser_media_player_manager.cc
+++ b/content/browser/media/android/browser_media_player_manager.cc
@@ -52,9 +52,6 @@ namespace content {
const int kMediaPlayerThreshold = 1;
const int kInvalidMediaPlayerId = -1;
-// Minimal duration of a media player in order to be considered as Content type.
-const int kMinimumDurationForContentInSeconds = 5;
-
static BrowserMediaPlayerManager::Factory g_factory = NULL;
static media::MediaUrlInterceptor* media_url_interceptor_ = NULL;
@@ -233,7 +230,6 @@ BrowserMediaPlayerManager::~BrowserMediaPlayerManager() {
for (MediaPlayerAndroid* player : players_)
player->DeleteOnCorrectThread();
- MediaSession::Get(web_contents())->RemovePlayers(this);
players_.weak_clear();
}
@@ -309,13 +305,11 @@ void BrowserMediaPlayerManager::OnMediaMetadataChanged(
void BrowserMediaPlayerManager::OnPlaybackComplete(int player_id) {
Send(new MediaPlayerMsg_MediaPlaybackCompleted(RoutingID(), player_id));
- MediaSession::Get(web_contents())->RemovePlayer(this, player_id);
}
void BrowserMediaPlayerManager::OnMediaInterrupted(int player_id) {
// Tell WebKit that the audio should be paused, then release all resources
Send(new MediaPlayerMsg_MediaPlayerReleased(RoutingID(), player_id));
- MediaSession::Get(web_contents())->RemovePlayer(this, player_id);
ReleaseResources(player_id);
}
@@ -406,45 +400,12 @@ MediaPlayerAndroid* BrowserMediaPlayerManager::GetPlayer(int player_id) {
bool BrowserMediaPlayerManager::RequestPlay(int player_id,
base::TimeDelta duration,
bool has_audio) {
- if (!has_audio)
- return true;
-
- MediaSession::Type media_session_type =
- duration == base::TimeDelta() ||
- duration.InSeconds() > kMinimumDurationForContentInSeconds
- ? MediaSession::Type::Content
- : MediaSession::Type::Transient;
-
- bool succeeded = MediaSession::Get(web_contents())->AddPlayer(
- this, player_id, media_session_type);
- if (!succeeded)
- Send(new MediaPlayerMsg_DidMediaPlayerPause(RoutingID(), player_id));
- return succeeded;
-}
-
-void BrowserMediaPlayerManager::OnSuspend(int player_id) {
- MediaPlayerAndroid* player = GetPlayer(player_id);
- DCHECK(player);
-
- player->Pause(true);
- Send(new MediaPlayerMsg_DidMediaPlayerPause(RoutingID(), player_id));
-}
-
-void BrowserMediaPlayerManager::OnResume(int player_id) {
- MediaPlayerAndroid* player = GetPlayer(player_id);
- DCHECK(player);
-
- player->Start();
- Send(new MediaPlayerMsg_DidMediaPlayerPlay(RoutingID(), player_id));
-}
-
-void BrowserMediaPlayerManager::OnSetVolumeMultiplier(
- int player_id, double volume_multiplier) {
- MediaPlayerAndroid* player = GetPlayer(player_id);
- if (!player)
- return;
-
- player->SetVolumeMultiplier(volume_multiplier);
+ DCHECK(player_id_to_delegate_id_map_.find(player_id) !=
+ player_id_to_delegate_id_map_.end());
+ return MediaWebContentsObserverAndroid::FromWebContents(web_contents_)
+ ->RequestPlay(render_frame_host_,
+ player_id_to_delegate_id_map_[player_id], has_audio,
+ IsPlayingRemotely(player_id), duration);
}
#if defined(VIDEO_HOLE)
@@ -589,6 +550,8 @@ void BrowserMediaPlayerManager::OnInitialize(
if (!player)
return;
+ player_id_to_delegate_id_map_[media_player_params.player_id] =
+ media_player_params.delegate_id;
AddPlayer(player);
}
@@ -621,9 +584,6 @@ void BrowserMediaPlayerManager::OnPause(
MediaPlayerAndroid* player = GetPlayer(player_id);
if (player)
player->Pause(is_media_related_action);
-
- if (is_media_related_action && !IsPlayingRemotely(player_id))
- MediaSession::Get(web_contents())->OnPlayerPaused(this, player_id);
}
void BrowserMediaPlayerManager::OnSetVolume(int player_id, double volume) {
@@ -637,7 +597,6 @@ void BrowserMediaPlayerManager::OnSetPoster(int player_id, const GURL& url) {
}
void BrowserMediaPlayerManager::OnSuspendAndReleaseResources(int player_id) {
- MediaSession::Get(web_contents())->RemovePlayer(this, player_id);
ReleaseResources(player_id);
}
@@ -674,11 +633,11 @@ void BrowserMediaPlayerManager::DestroyPlayer(int player_id) {
#endif
(*it)->DeleteOnCorrectThread();
players_.weak_erase(it);
- MediaSession::Get(web_contents())->RemovePlayer(this, player_id);
break;
}
}
active_players_.erase(player_id);
+ player_id_to_delegate_id_map_.erase(player_id);
}
void BrowserMediaPlayerManager::ReleaseResources(int player_id) {
diff --git a/content/browser/media/android/browser_media_player_manager.h b/content/browser/media/android/browser_media_player_manager.h
index 4530069..ccf28ff 100644
--- a/content/browser/media/android/browser_media_player_manager.h
+++ b/content/browser/media/android/browser_media_player_manager.h
@@ -13,7 +13,6 @@
#include "base/memory/scoped_vector.h"
#include "base/time/time.h"
#include "content/browser/android/content_video_view.h"
-#include "content/browser/media/android/media_session_observer.h"
#include "content/common/content_export.h"
#include "ipc/ipc_message.h"
#include "media/base/android/media_player_android.h"
@@ -45,7 +44,6 @@ class WebContents;
// process.
class CONTENT_EXPORT BrowserMediaPlayerManager
: public media::MediaPlayerManager,
- public MediaSessionObserver,
public ContentVideoView::Client {
public:
// Permits embedders to provide an extended version of the class.
@@ -113,11 +111,6 @@ class CONTENT_EXPORT BrowserMediaPlayerManager
void OnFrameInfoUpdated();
#endif // defined(VIDEO_HOLE)
- // MediaSessionObserver overrides.
- void OnSuspend(int player_id) override;
- void OnResume(int player_id) override;
- void OnSetVolumeMultiplier(int player_id, double volume_multiplier) override;
-
// Message handlers.
virtual void OnEnterFullscreen(int player_id);
virtual void OnInitialize(
@@ -145,9 +138,6 @@ class CONTENT_EXPORT BrowserMediaPlayerManager
WebContents* web_contents() const { return web_contents_; }
- // Adds a given player to the list.
- void AddPlayer(media::MediaPlayerAndroid* player);
-
// Removes the player with the specified id.
void DestroyPlayer(int player_id);
@@ -182,6 +172,9 @@ class CONTENT_EXPORT BrowserMediaPlayerManager
bool Send(IPC::Message* msg);
private:
+ // Adds a given player to the list.
+ void AddPlayer(media::MediaPlayerAndroid* player);
+
// Constructs a MediaPlayerAndroid object.
media::MediaPlayerAndroid* CreateMediaPlayer(
const MediaPlayerHostMsg_Initialize_Params& media_player_params,
@@ -234,6 +227,10 @@ class CONTENT_EXPORT BrowserMediaPlayerManager
// Object for retrieving resources media players.
scoped_ptr<media::MediaResourceGetter> media_resource_getter_;
+ // Map of player IDs to delegate IDs for use with
+ // MediaWebContentsObserverAndroid.
+ std::map<int, int> player_id_to_delegate_id_map_;
+
// NOTE: Weak pointers must be invalidated before all other member variables.
base::WeakPtrFactory<BrowserMediaPlayerManager> weak_ptr_factory_;
diff --git a/content/browser/media/android/media_session_controller.cc b/content/browser/media/android/media_session_controller.cc
new file mode 100644
index 0000000..f5ff8aa
--- /dev/null
+++ b/content/browser/media/android/media_session_controller.cc
@@ -0,0 +1,109 @@
+// Copyright 2016 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/android/media_session_controller.h"
+
+#include "content/browser/media/android/media_session.h"
+#include "content/browser/media/media_web_contents_observer.h"
+#include "content/common/media/media_player_delegate_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+
+namespace content {
+
+MediaSessionController::MediaSessionController(
+ const WebContentsObserver::MediaPlayerId& id,
+ MediaWebContentsObserver* media_web_contents_observer)
+ : id_(id),
+ media_web_contents_observer_(media_web_contents_observer),
+ media_session_(
+ MediaSession::Get(media_web_contents_observer_->web_contents())) {}
+
+MediaSessionController::~MediaSessionController() {
+ if (!has_session_)
+ return;
+ media_session_->RemovePlayer(this, player_id_);
+}
+
+bool MediaSessionController::Initialize(bool has_audio,
+ bool is_remote,
+ base::TimeDelta duration) {
+ // Don't generate a new id if one has already been set.
+ if (!has_session_) {
+ // These objects are only created on the UI thread, so this is safe.
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ static uint32_t player_id = 0;
+ player_id_ = static_cast<int>(player_id++);
+ } else {
+ // WebMediaPlayerAndroid does not have an accurate sense of audio presence,
+ // only the MediaPlayerManager does, so WMPA never reports audio unless it's
+ // sure (no video stream). This leads to issues when Initialize() is called
+ // by WMPA (reporting no audio and subsequently releasing the session) after
+ // the manager accurately reported audio.
+ //
+ // To workaround this, |has_audio| is sticky; I.e., once a session has been
+ // created with audio all future sessions will also have audio.
+ //
+ // TODO(dalecurtis): Delete sticky audio once we're no longer using WMPA and
+ // the BrowserMediaPlayerManagers. Tracked by http://crbug.com/580626
+ has_audio = true;
+ }
+
+ // Don't bother with a MediaSession for remote players or without audio. If
+ // we already have a session from a previous call, release it.
+ if (!has_audio || is_remote) {
+ if (has_session_) {
+ has_session_ = false;
+ media_session_->RemovePlayer(this, player_id_);
+ }
+ return true;
+ }
+
+ const MediaSession::Type media_session_type =
+ duration == base::TimeDelta() ||
+ duration >
+ base::TimeDelta::FromSeconds(kMinimumDurationForContentSecs)
+ ? MediaSession::Type::Content
+ : MediaSession::Type::Transient;
+
+ // If a session can't be created, force a pause immediately. Attempt to add a
+ // session even if we already have one. MediaSession expects AddPlayer() to
+ // be called after OnPlaybackPaused() to reactivate the session.
+ if (!media_session_->AddPlayer(this, player_id_, media_session_type)) {
+ OnSuspend(player_id_);
+ return false;
+ }
+
+ has_session_ = true;
+ return true;
+}
+
+void MediaSessionController::OnSuspend(int player_id) {
+ DCHECK_EQ(player_id_, player_id);
+ media_web_contents_observer_->Send(
+ new MediaPlayerDelegateMsg_Pause(id_.first->GetRoutingID(), id_.second));
+}
+
+void MediaSessionController::OnResume(int player_id) {
+ DCHECK_EQ(player_id_, player_id);
+ media_web_contents_observer_->Send(
+ new MediaPlayerDelegateMsg_Play(id_.first->GetRoutingID(), id_.second));
+}
+
+void MediaSessionController::OnSetVolumeMultiplier(int player_id,
+ double volume_multiplier) {
+ DCHECK_EQ(player_id_, player_id);
+ media_web_contents_observer_->Send(
+ new MediaPlayerDelegateMsg_UpdateVolumeMultiplier(
+ id_.first->GetRoutingID(), id_.second, volume_multiplier));
+}
+
+void MediaSessionController::OnPlaybackPaused() {
+ // We check for suspension here since the renderer may issue its own pause
+ // in response to or while a pause from the browser is in flight.
+ if (!media_session_->IsSuspended())
+ media_session_->OnPlayerPaused(this, player_id_);
+}
+
+} // namespace content
diff --git a/content/browser/media/android/media_session_controller.h b/content/browser/media/android/media_session_controller.h
new file mode 100644
index 0000000..33b7d4f
--- /dev/null
+++ b/content/browser/media/android/media_session_controller.h
@@ -0,0 +1,69 @@
+// Copyright 2016 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_ANDROID_MEDIA_SESSION_CONTROLLER_H_
+#define CONTENT_BROWSER_MEDIA_ANDROID_MEDIA_SESSION_CONTROLLER_H_
+
+#include "base/time/time.h"
+#include "content/browser/media/android/media_session_observer.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace content {
+
+class MediaSession;
+class MediaWebContentsObserver;
+
+// Helper class for controlling a single player's MediaSession instance. Sends
+// browser side MediaSession commands back to a player hosted in the renderer
+// process.
+class CONTENT_EXPORT MediaSessionController : public MediaSessionObserver {
+ public:
+ MediaSessionController(const WebContentsObserver::MediaPlayerId& id,
+ MediaWebContentsObserver* media_web_contents_observer);
+ ~MediaSessionController() override;
+
+ // Minimum duration of content for a MediaSession instance to be created.
+ enum { kMinimumDurationForContentSecs = 5 };
+
+ // Clients must call this after construction and destroy the controller if it
+ // returns false. May be called more than once; does nothing if none of the
+ // input parameters have changed since the last call.
+ //
+ // Note: Once a session has been initialized with |has_audio| as true, all
+ // future calls to Initialize() will retain this flag.
+ // TODO(dalecurtis): Delete sticky audio once we're no longer using WMPA and
+ // the BrowserMediaPlayerManagers. Tracked by http://crbug.com/580626
+ bool Initialize(bool has_audio, bool is_remote, base::TimeDelta duration);
+
+ // Must be called when a pause occurs on the renderer side media player; keeps
+ // the MediaSession instance in sync with renderer side behavior.
+ void OnPlaybackPaused();
+
+ // MediaSessionObserver implementation.
+ void OnSuspend(int player_id) override;
+ void OnResume(int player_id) override;
+ void OnSetVolumeMultiplier(int player_id, double volume_multiplier) override;
+
+ // Test helpers.
+ int get_player_id_for_testing() const { return player_id_; }
+
+ private:
+ const WebContentsObserver::MediaPlayerId id_;
+
+ // Non-owned pointer; |media_web_contents_observer_| is the owner of |this|.
+ MediaWebContentsObserver* const media_web_contents_observer_;
+
+ // Non-owned pointer; lifetime is the same as |media_web_contents_observer_|.
+ MediaSession* const media_session_;
+
+ int player_id_ = 0;
+ bool has_session_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaSessionController);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_ANDROID_MEDIA_SESSION_CONTROLLER_H_
diff --git a/content/browser/media/android/media_session_controller_unittest.cc b/content/browser/media/android/media_session_controller_unittest.cc
new file mode 100644
index 0000000..be56ccb
--- /dev/null
+++ b/content/browser/media/android/media_session_controller_unittest.cc
@@ -0,0 +1,216 @@
+// Copyright 2016 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 "base/tuple.h"
+#include "content/browser/media/android/media_session.h"
+#include "content/browser/media/android/media_session_controller.h"
+#include "content/browser/media/android/media_web_contents_observer_android.h"
+#include "content/common/media/media_player_delegate_messages.h"
+#include "content/test/test_render_view_host.h"
+#include "content/test/test_web_contents.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+class MediaSessionControllerTest : public RenderViewHostImplTestHarness {
+ public:
+ void SetUp() override {
+ RenderViewHostImplTestHarness::SetUp();
+ id_ = WebContentsObserver::MediaPlayerId(contents()->GetMainFrame(), 0);
+ observer_ = MediaWebContentsObserverAndroid::FromWebContents(contents());
+ controller_ = CreateController();
+ }
+
+ void TearDown() override {
+ // Destruct the controller prior to any other teardown to avoid out of order
+ // destruction relative to the MediaSession instance.
+ controller_.reset();
+ RenderViewHostImplTestHarness::TearDown();
+ }
+
+ protected:
+ scoped_ptr<MediaSessionController> CreateController() {
+ return scoped_ptr<MediaSessionController>(
+ new MediaSessionController(id_, observer_));
+ }
+
+ MediaSession* media_session() { return MediaSession::Get(contents()); }
+
+ IPC::TestSink& test_sink() { return main_test_rfh()->GetProcess()->sink(); }
+
+ void Suspend() {
+ controller_->OnSuspend(controller_->get_player_id_for_testing());
+ }
+
+ void Resume() {
+ controller_->OnResume(controller_->get_player_id_for_testing());
+ }
+
+ void SetVolumeMultiplier(double multiplier) {
+ controller_->OnSetVolumeMultiplier(controller_->get_player_id_for_testing(),
+ multiplier);
+ }
+
+ // Returns a duration long enough for a media session instance to be created.
+ base::TimeDelta DurationJustRight() {
+ return base::TimeDelta::FromSeconds(
+ MediaSessionController::kMinimumDurationForContentSecs + 1);
+ }
+
+ // Returns a duration too short for a media session instance.
+ base::TimeDelta DurationTooShort() {
+ return base::TimeDelta::FromSeconds(
+ MediaSessionController::kMinimumDurationForContentSecs);
+ }
+
+ template <typename T>
+ bool ReceivedMessagePlayPause() {
+ const IPC::Message* msg = test_sink().GetUniqueMessageMatching(T::ID);
+ if (!msg)
+ return false;
+
+ base::Tuple<int> result;
+ if (!T::Read(msg, &result))
+ return false;
+
+ EXPECT_EQ(id_.second, base::get<0>(result));
+ test_sink().ClearMessages();
+ return id_.second == base::get<0>(result);
+ }
+
+ template <typename T>
+ bool ReceivedMessageVolumeMultiplierUpdate(double expected_multiplier) {
+ const IPC::Message* msg = test_sink().GetUniqueMessageMatching(T::ID);
+ if (!msg)
+ return false;
+
+ base::Tuple<int, double> result;
+ if (!T::Read(msg, &result))
+ return false;
+
+ EXPECT_EQ(id_.second, base::get<0>(result));
+ if (id_.second != base::get<0>(result))
+ return false;
+
+ EXPECT_EQ(expected_multiplier, base::get<1>(result));
+ test_sink().ClearMessages();
+ return expected_multiplier == base::get<1>(result);
+ }
+
+ WebContentsObserver::MediaPlayerId id_;
+ MediaWebContentsObserverAndroid* observer_;
+ scoped_ptr<MediaSessionController> controller_;
+};
+
+TEST_F(MediaSessionControllerTest, NoAudioNoSession) {
+ ASSERT_TRUE(controller_->Initialize(false, false, DurationJustRight()));
+ EXPECT_TRUE(media_session()->IsSuspended());
+ EXPECT_FALSE(media_session()->IsControllable());
+}
+
+TEST_F(MediaSessionControllerTest, IsRemoteNoSession) {
+ ASSERT_TRUE(controller_->Initialize(true, true, DurationJustRight()));
+ EXPECT_TRUE(media_session()->IsSuspended());
+ EXPECT_FALSE(media_session()->IsControllable());
+}
+
+TEST_F(MediaSessionControllerTest, TooShortNoControllableSession) {
+ ASSERT_TRUE(controller_->Initialize(true, false, DurationTooShort()));
+ EXPECT_FALSE(media_session()->IsSuspended());
+ EXPECT_FALSE(media_session()->IsControllable());
+}
+
+TEST_F(MediaSessionControllerTest, BasicControls) {
+ ASSERT_TRUE(controller_->Initialize(true, false, DurationJustRight()));
+ EXPECT_FALSE(media_session()->IsSuspended());
+ EXPECT_TRUE(media_session()->IsControllable());
+
+ // Verify suspend notifies the renderer and maintains its session.
+ Suspend();
+ EXPECT_TRUE(ReceivedMessagePlayPause<MediaPlayerDelegateMsg_Pause>());
+
+ // Likewise verify the resume behavior.
+ Resume();
+ EXPECT_TRUE(ReceivedMessagePlayPause<MediaPlayerDelegateMsg_Play>());
+
+ // Verify destruction of the controller removes its session.
+ controller_.reset();
+ EXPECT_TRUE(media_session()->IsSuspended());
+ EXPECT_FALSE(media_session()->IsControllable());
+}
+
+TEST_F(MediaSessionControllerTest, VolumeMultiplier) {
+ ASSERT_TRUE(controller_->Initialize(true, false, DurationJustRight()));
+ EXPECT_FALSE(media_session()->IsSuspended());
+ EXPECT_TRUE(media_session()->IsControllable());
+
+ // Upon creation of the MediaSession the default multiplier will be sent.
+ EXPECT_TRUE(ReceivedMessageVolumeMultiplierUpdate<
+ MediaPlayerDelegateMsg_UpdateVolumeMultiplier>(1.0));
+
+ // Verify a different volume multiplier is sent.
+ const double kTestMultiplier = 0.5;
+ SetVolumeMultiplier(kTestMultiplier);
+ EXPECT_TRUE(ReceivedMessageVolumeMultiplierUpdate<
+ MediaPlayerDelegateMsg_UpdateVolumeMultiplier>(kTestMultiplier));
+}
+
+TEST_F(MediaSessionControllerTest, ControllerSidePause) {
+ ASSERT_TRUE(controller_->Initialize(true, false, DurationJustRight()));
+ EXPECT_FALSE(media_session()->IsSuspended());
+ EXPECT_TRUE(media_session()->IsControllable());
+
+ // Verify pause behavior.
+ controller_->OnPlaybackPaused();
+ EXPECT_TRUE(media_session()->IsSuspended());
+ EXPECT_TRUE(media_session()->IsControllable());
+
+ // Verify the next Initialize() call restores the session.
+ ASSERT_TRUE(controller_->Initialize(true, false, DurationJustRight()));
+ EXPECT_FALSE(media_session()->IsSuspended());
+ EXPECT_TRUE(media_session()->IsControllable());
+}
+
+TEST_F(MediaSessionControllerTest, Reinitialize) {
+ ASSERT_TRUE(controller_->Initialize(false, false, DurationJustRight()));
+ EXPECT_TRUE(media_session()->IsSuspended());
+ EXPECT_FALSE(media_session()->IsControllable());
+
+ // Create a transient type session.
+ ASSERT_TRUE(controller_->Initialize(true, false, DurationTooShort()));
+ EXPECT_FALSE(media_session()->IsSuspended());
+ EXPECT_FALSE(media_session()->IsControllable());
+ const int current_player_id = controller_->get_player_id_for_testing();
+
+ // Reinitialize the session as a content type.
+ ASSERT_TRUE(controller_->Initialize(true, false, DurationJustRight()));
+ EXPECT_FALSE(media_session()->IsSuspended());
+ EXPECT_TRUE(media_session()->IsControllable());
+ // Player id should not change when there's an active session.
+ EXPECT_EQ(current_player_id, controller_->get_player_id_for_testing());
+
+ // Verify suspend notifies the renderer and maintains its session.
+ Suspend();
+ EXPECT_TRUE(ReceivedMessagePlayPause<MediaPlayerDelegateMsg_Pause>());
+
+ // Likewise verify the resume behavior.
+ Resume();
+ EXPECT_TRUE(ReceivedMessagePlayPause<MediaPlayerDelegateMsg_Play>());
+
+ // Attempt to switch to no audio player, which should do nothing.
+ // TODO(dalecurtis): Delete this test once we're no longer using WMPA and
+ // the BrowserMediaPlayerManagers. Tracked by http://crbug.com/580626
+ ASSERT_TRUE(controller_->Initialize(false, false, DurationJustRight()));
+ EXPECT_FALSE(media_session()->IsSuspended());
+ EXPECT_TRUE(media_session()->IsControllable());
+ EXPECT_EQ(current_player_id, controller_->get_player_id_for_testing());
+
+ // Switch to a remote player, which should release the session.
+ ASSERT_TRUE(controller_->Initialize(true, true, DurationJustRight()));
+ EXPECT_TRUE(media_session()->IsSuspended());
+ EXPECT_FALSE(media_session()->IsControllable());
+ EXPECT_EQ(current_player_id, controller_->get_player_id_for_testing());
+}
+
+} // namespace content
diff --git a/content/browser/media/android/media_web_contents_observer_android.cc b/content/browser/media/android/media_web_contents_observer_android.cc
index 224a8f1..f5f53b3 100644
--- a/content/browser/media/android/media_web_contents_observer_android.cc
+++ b/content/browser/media/android/media_web_contents_observer_android.cc
@@ -6,9 +6,12 @@
#include "content/browser/media/android/browser_media_player_manager.h"
#include "content/browser/media/android/browser_media_session_manager.h"
+#include "content/browser/media/android/media_session.h"
+#include "content/browser/media/android/media_session_controller.h"
#include "content/browser/media/android/media_session_observer.h"
#include "content/browser/media/cdm/browser_cdm_manager.h"
#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/media/media_player_delegate_messages.h"
#include "content/common/media/media_player_messages_android.h"
#include "content/common/media/media_session_messages_android.h"
#include "content/public/browser/render_frame_host.h"
@@ -58,6 +61,19 @@ MediaWebContentsObserverAndroid::GetMediaSessionManager(
return manager;
}
+bool MediaWebContentsObserverAndroid::RequestPlay(
+ RenderFrameHost* render_frame_host,
+ int delegate_id,
+ bool has_audio,
+ bool is_remote,
+ base::TimeDelta duration) {
+ // |has_video| forced to true since the value doesn't matter at present.
+ OnMediaPlaying(render_frame_host, delegate_id, true, has_audio, is_remote,
+ duration);
+ return media_session_map_.find(MediaPlayerId(
+ render_frame_host, delegate_id)) != media_session_map_.end();
+}
+
#if defined(VIDEO_HOLE)
void MediaWebContentsObserverAndroid::OnFrameInfoUpdated() {
for (auto it = media_player_managers_.begin();
@@ -71,6 +87,13 @@ void MediaWebContentsObserverAndroid::RenderFrameDeleted(
RenderFrameHost* render_frame_host) {
MediaWebContentsObserver::RenderFrameDeleted(render_frame_host);
+ for (auto it = media_session_map_.begin(); it != media_session_map_.end();) {
+ if (it->first.first == render_frame_host)
+ it = media_session_map_.erase(it);
+ else
+ ++it;
+ }
+
// Always destroy the media players before CDMs because we do not support
// detaching CDMs from media players yet. See http://crbug.com/330324
media_player_managers_.erase(render_frame_host);
@@ -90,6 +113,10 @@ void MediaWebContentsObserverAndroid::RenderFrameDeleted(
bool MediaWebContentsObserverAndroid::OnMessageReceived(
const IPC::Message& msg,
RenderFrameHost* render_frame_host) {
+ // Receive play/pause/destroyed messages, but don't mark as processed so they
+ // are also handled by MediaWebContentsObserver.
+ OnMediaPlayerDelegateMessageReceived(msg, render_frame_host);
+
if (MediaWebContentsObserver::OnMessageReceived(msg, render_frame_host))
return true;
@@ -105,6 +132,19 @@ bool MediaWebContentsObserverAndroid::OnMessageReceived(
return false;
}
+void MediaWebContentsObserverAndroid::OnMediaPlayerDelegateMessageReceived(
+ const IPC::Message& msg,
+ RenderFrameHost* render_frame_host) {
+ IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(MediaWebContentsObserverAndroid, msg,
+ render_frame_host)
+ IPC_MESSAGE_HANDLER(MediaPlayerDelegateHostMsg_OnMediaDestroyed,
+ OnMediaDestroyed)
+ IPC_MESSAGE_HANDLER(MediaPlayerDelegateHostMsg_OnMediaPaused, OnMediaPaused)
+ IPC_MESSAGE_HANDLER(MediaPlayerDelegateHostMsg_OnMediaPlaying,
+ OnMediaPlaying)
+ IPC_END_MESSAGE_MAP()
+}
+
bool MediaWebContentsObserverAndroid::OnMediaPlayerMessageReceived(
const IPC::Message& msg,
RenderFrameHost* render_frame_host) {
@@ -218,4 +258,59 @@ void MediaWebContentsObserverAndroid::OnSetCdm(
media_player->SetCdm(cdm);
}
+void MediaWebContentsObserverAndroid::OnMediaDestroyed(
+ RenderFrameHost* render_frame_host,
+ int delegate_id) {
+ media_session_map_.erase(MediaPlayerId(render_frame_host, delegate_id));
+}
+
+void MediaWebContentsObserverAndroid::OnMediaPaused(
+ RenderFrameHost* render_frame_host,
+ int delegate_id,
+ bool reached_end_of_stream) {
+ // Drop the session if playback completes normally.
+ if (reached_end_of_stream) {
+ OnMediaDestroyed(render_frame_host, delegate_id);
+ return;
+ }
+
+ auto it =
+ media_session_map_.find(MediaPlayerId(render_frame_host, delegate_id));
+ if (it == media_session_map_.end())
+ return;
+
+ it->second->OnPlaybackPaused();
+}
+
+void MediaWebContentsObserverAndroid::OnMediaPlaying(
+ RenderFrameHost* render_frame_host,
+ int delegate_id,
+ bool has_video,
+ bool has_audio,
+ bool is_remote,
+ base::TimeDelta duration) {
+ const MediaPlayerId id(render_frame_host, delegate_id);
+
+ // Since we don't remove session instances on pause, there may be an existing
+ // instance for this playback attempt.
+ //
+ // In this case, try to reinitialize it with the new settings. If they are
+ // the same, this is a no-op. If the reinitialize fails, destroy the
+ // controller. A later playback attempt will create a new controller.
+ auto it = media_session_map_.find(id);
+ if (it != media_session_map_.end()) {
+ if (!it->second->Initialize(has_audio, is_remote, duration))
+ media_session_map_.erase(it);
+ return;
+ }
+
+ scoped_ptr<MediaSessionController> controller(
+ new MediaSessionController(id, this));
+
+ if (!controller->Initialize(has_audio, is_remote, duration))
+ return;
+
+ media_session_map_[id] = std::move(controller);
+}
+
} // namespace content
diff --git a/content/browser/media/android/media_web_contents_observer_android.h b/content/browser/media/android/media_web_contents_observer_android.h
index 5a6f481..e6024681 100644
--- a/content/browser/media/android/media_web_contents_observer_android.h
+++ b/content/browser/media/android/media_web_contents_observer_android.h
@@ -18,6 +18,7 @@ namespace content {
class BrowserCdmManager;
class BrowserMediaPlayerManager;
class BrowserMediaSessionManager;
+class MediaSessionController;
// This class adds Android specific extensions to the MediaWebContentsObserver.
class CONTENT_EXPORT MediaWebContentsObserverAndroid
@@ -38,6 +39,16 @@ class CONTENT_EXPORT MediaWebContentsObserverAndroid
BrowserMediaSessionManager* GetMediaSessionManager(
RenderFrameHost* render_frame_host);
+ // Initiates a synchronous MediaSession request for browser side players.
+ //
+ // TODO(dalecurtis): Delete this method once we're no longer using WMPA and
+ // the BrowserMediaPlayerManagers. Tracked by http://crbug.com/580626
+ bool RequestPlay(RenderFrameHost* render_frame_host,
+ int delegate_id,
+ bool has_audio,
+ bool is_remote,
+ base::TimeDelta duration);
+
#if defined(VIDEO_HOLE)
void OnFrameInfoUpdated();
#endif // defined(VIDEO_HOLE)
@@ -48,6 +59,21 @@ class CONTENT_EXPORT MediaWebContentsObserverAndroid
RenderFrameHost* render_frame_host) override;
private:
+ // Handles messages from the WebMediaPlayerDelegate; does not modify the
+ // handled state since the superclass needs to handle these as well.
+ void OnMediaPlayerDelegateMessageReceived(const IPC::Message& msg,
+ RenderFrameHost* render_frame_host);
+ void OnMediaDestroyed(RenderFrameHost* render_frame_host, int delegate_id);
+ void OnMediaPaused(RenderFrameHost* render_frame_host,
+ int delegate_id,
+ bool reached_end_of_stream);
+ void OnMediaPlaying(RenderFrameHost* render_frame_host,
+ int delegate_id,
+ bool has_video,
+ bool has_audio,
+ bool is_remote,
+ base::TimeDelta duration);
+
// Helper functions to handle media player IPC messages. Returns whether the
// |message| is handled in the function.
bool OnMediaPlayerMessageReceived(const IPC::Message& message,
@@ -73,6 +99,11 @@ class CONTENT_EXPORT MediaWebContentsObserverAndroid
scoped_ptr<BrowserMediaSessionManager>>;
MediaSessionManagerMap media_session_managers_;
+ // Map of renderer process media players to session controllers.
+ using MediaSessionMap =
+ std::map<MediaPlayerId, scoped_ptr<MediaSessionController>>;
+ MediaSessionMap media_session_map_;
+
DISALLOW_COPY_AND_ASSIGN(MediaWebContentsObserverAndroid);
};
diff --git a/content/browser/media/media_web_contents_observer.cc b/content/browser/media/media_web_contents_observer.cc
index 67ef036..17e8fc5 100644
--- a/content/browser/media/media_web_contents_observer.cc
+++ b/content/browser/media/media_web_contents_observer.cc
@@ -11,7 +11,7 @@
#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/common/media/media_player_delegate_messages.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "ipc/ipc_message_macros.h"
@@ -64,28 +64,64 @@ bool MediaWebContentsObserver::OnMessageReceived(
// 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_HANDLER(MediaPlayerDelegateHostMsg_OnMediaDestroyed,
+ OnMediaDestroyed)
+ IPC_MESSAGE_HANDLER(MediaPlayerDelegateHostMsg_OnMediaPaused, OnMediaPaused)
+ IPC_MESSAGE_HANDLER(MediaPlayerDelegateHostMsg_OnMediaPlaying,
+ OnMediaPlaying)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
-void MediaWebContentsObserver::OnMediaPlayingNotification(
+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::OnMediaDestroyed(
RenderFrameHost* render_frame_host,
- int64_t player_cookie,
+ 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 id(render_frame_host, delegate_id);
+ 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::OnMediaPlaying(
+ RenderFrameHost* render_frame_host,
+ int delegate_id,
bool has_video,
bool has_audio,
- bool is_remote) {
+ bool is_remote,
+ base::TimeDelta duration) {
// 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);
+ const MediaPlayerId id(render_frame_host, delegate_id);
if (has_audio) {
AddMediaPlayerEntry(id, &active_audio_players_);
@@ -112,20 +148,6 @@ void MediaWebContentsObserver::OnMediaPlayingNotification(
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;
@@ -161,19 +183,6 @@ void MediaWebContentsObserver::CreateVideoPowerSaveBlocker() {
#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
@@ -226,8 +235,8 @@ void MediaWebContentsObserver::RemoveAllMediaPlayerEntries(
if (it == player_map->end())
return;
- for (int64_t player_cookie : it->second)
- removed_players->insert(MediaPlayerId(render_frame_host, player_cookie));
+ for (int delegate_id : it->second)
+ removed_players->insert(MediaPlayerId(render_frame_host, delegate_id));
player_map->erase(it);
}
diff --git a/content/browser/media/media_web_contents_observer.h b/content/browser/media/media_web_contents_observer.h
index 4429d93..d902dc9 100644
--- a/content/browser/media/media_web_contents_observer.h
+++ b/content/browser/media/media_web_contents_observer.h
@@ -48,13 +48,16 @@ class CONTENT_EXPORT MediaWebContentsObserver : public WebContentsObserver {
}
private:
- void OnMediaPlayingNotification(RenderFrameHost* render_frame_host,
- int64_t player_cookie,
- bool has_video,
- bool has_audio,
- bool is_remote);
- void OnMediaPausedNotification(RenderFrameHost* render_frame_host,
- int64_t player_cookie);
+ void OnMediaDestroyed(RenderFrameHost* render_frame_host, int delegate_id);
+ void OnMediaPaused(RenderFrameHost* render_frame_host,
+ int delegate_id,
+ bool reached_end_of_stream);
+ void OnMediaPlaying(RenderFrameHost* render_frame_host,
+ int delegate_id,
+ bool has_video,
+ bool has_audio,
+ bool is_remote,
+ base::TimeDelta duration);
// Clear |render_frame_host|'s tracking entry for its power save blockers.
void ClearPowerSaveBlockers(RenderFrameHost* render_frame_host);
@@ -69,7 +72,7 @@ class CONTENT_EXPORT MediaWebContentsObserver : public WebContentsObserver {
void MaybeReleasePowerSaveBlockers();
// Helper methods for adding or removing player entries in |player_map|.
- using PlayerList = std::vector<int64_t>;
+ using PlayerList = std::vector<int>;
using ActiveMediaPlayerMap = std::map<RenderFrameHost*, PlayerList>;
void AddMediaPlayerEntry(const MediaPlayerId& id,
ActiveMediaPlayerMap* player_map);