Commit e532c7c8 authored by Takumi Fujimoto's avatar Takumi Fujimoto Committed by Commit Bot

Implement CastMediaSessionController for Global Media Controls

CastMediaSessionController receives media action commands from
CastMediaNotificationItem and forwards them to a media controller in
Media Router, which then sends the command to the Cast device.

Bug: 987479
Change-Id: Ib7ce9b7be603d47f866e34e8c23154daf36bfeaf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1927377Reviewed-by: default avatarTommy Steimel <steimel@chromium.org>
Commit-Queue: Takumi Fujimoto <takumif@chromium.org>
Cr-Commit-Position: refs/heads/master@{#719617}
parent 0de0ee72
...@@ -933,6 +933,8 @@ jumbo_static_library("ui") { ...@@ -933,6 +933,8 @@ jumbo_static_library("ui") {
"global_media_controls/cast_media_notification_item.h", "global_media_controls/cast_media_notification_item.h",
"global_media_controls/cast_media_notification_provider.cc", "global_media_controls/cast_media_notification_provider.cc",
"global_media_controls/cast_media_notification_provider.h", "global_media_controls/cast_media_notification_provider.h",
"global_media_controls/cast_media_session_controller.cc",
"global_media_controls/cast_media_session_controller.h",
"global_media_controls/media_dialog_delegate.cc", "global_media_controls/media_dialog_delegate.cc",
"global_media_controls/media_dialog_delegate.h", "global_media_controls/media_dialog_delegate.h",
"global_media_controls/media_notification_container_impl.h", "global_media_controls/media_notification_container_impl.h",
......
...@@ -5,8 +5,10 @@ ...@@ -5,8 +5,10 @@
#include "chrome/browser/ui/global_media_controls/cast_media_notification_item.h" #include "chrome/browser/ui/global_media_controls/cast_media_notification_item.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/global_media_controls/cast_media_session_controller.h"
#include "components/media_message_center/media_notification_controller.h" #include "components/media_message_center/media_notification_controller.h"
#include "components/media_message_center/media_notification_view.h" #include "components/media_message_center/media_notification_view.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/media_session/public/mojom/media_session.mojom.h" #include "services/media_session/public/mojom/media_session.mojom.h"
namespace { namespace {
...@@ -73,8 +75,10 @@ media_session::mojom::MediaSessionInfo::SessionState ToSessionState( ...@@ -73,8 +75,10 @@ media_session::mojom::MediaSessionInfo::SessionState ToSessionState(
CastMediaNotificationItem::CastMediaNotificationItem( CastMediaNotificationItem::CastMediaNotificationItem(
const media_router::MediaRoute& route, const media_router::MediaRoute& route,
media_message_center::MediaNotificationController* notification_controller) media_message_center::MediaNotificationController* notification_controller,
std::unique_ptr<CastMediaSessionController> session_controller)
: notification_controller_(notification_controller), : notification_controller_(notification_controller),
session_controller_(std::move(session_controller)),
media_route_id_(route.media_route_id()), media_route_id_(route.media_route_id()),
session_info_(CreateSessionInfo()) { session_info_(CreateSessionInfo()) {
metadata_.artist = base::UTF8ToUTF16(route.description()); metadata_.artist = base::UTF8ToUTF16(route.description());
...@@ -93,7 +97,7 @@ void CastMediaNotificationItem::SetView( ...@@ -93,7 +97,7 @@ void CastMediaNotificationItem::SetView(
void CastMediaNotificationItem::OnMediaSessionActionButtonPressed( void CastMediaNotificationItem::OnMediaSessionActionButtonPressed(
media_session::mojom::MediaSessionAction action) { media_session::mojom::MediaSessionAction action) {
// TODO(crbug.com/987479): Forward the action to the Cast receiver. session_controller_->Send(action);
} }
void CastMediaNotificationItem::Dismiss() { void CastMediaNotificationItem::Dismiss() {
...@@ -109,6 +113,12 @@ void CastMediaNotificationItem::OnMediaStatusUpdated( ...@@ -109,6 +113,12 @@ void CastMediaNotificationItem::OnMediaStatusUpdated(
// TODO(crbug.com/987479): Fetch and set the background image. // TODO(crbug.com/987479): Fetch and set the background image.
UpdateView(); UpdateView();
session_controller_->OnMediaStatusUpdated(std::move(status));
}
mojo::PendingRemote<media_router::mojom::MediaStatusObserver>
CastMediaNotificationItem::GetObserverPendingRemote() {
return observer_receiver_.BindNewPipeAndPassRemote();
} }
void CastMediaNotificationItem::UpdateView() { void CastMediaNotificationItem::UpdateView() {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_CAST_MEDIA_NOTIFICATION_ITEM_H_ #define CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_CAST_MEDIA_NOTIFICATION_ITEM_H_
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "chrome/browser/ui/global_media_controls/cast_media_session_controller.h"
#include "chrome/common/media_router/media_route.h" #include "chrome/common/media_router/media_route.h"
#include "chrome/common/media_router/mojom/media_status.mojom.h" #include "chrome/common/media_router/mojom/media_status.mojom.h"
#include "components/media_message_center/media_notification_item.h" #include "components/media_message_center/media_notification_item.h"
...@@ -23,9 +24,11 @@ class CastMediaNotificationItem ...@@ -23,9 +24,11 @@ class CastMediaNotificationItem
: public media_message_center::MediaNotificationItem, : public media_message_center::MediaNotificationItem,
public media_router::mojom::MediaStatusObserver { public media_router::mojom::MediaStatusObserver {
public: public:
CastMediaNotificationItem(const media_router::MediaRoute& route, CastMediaNotificationItem(
media_message_center::MediaNotificationController* const media_router::MediaRoute& route,
notification_controller); media_message_center::MediaNotificationController*
notification_controller,
std::unique_ptr<CastMediaSessionController> session_controller);
CastMediaNotificationItem(const CastMediaNotificationItem&) = delete; CastMediaNotificationItem(const CastMediaNotificationItem&) = delete;
CastMediaNotificationItem& operator=(const CastMediaNotificationItem&) = CastMediaNotificationItem& operator=(const CastMediaNotificationItem&) =
delete; delete;
...@@ -41,6 +44,11 @@ class CastMediaNotificationItem ...@@ -41,6 +44,11 @@ class CastMediaNotificationItem
void OnMediaStatusUpdated( void OnMediaStatusUpdated(
media_router::mojom::MediaStatusPtr status) override; media_router::mojom::MediaStatusPtr status) override;
// Returns a pending remote bound to |this|. This should not be called more
// than once per instance.
mojo::PendingRemote<media_router::mojom::MediaStatusObserver>
GetObserverPendingRemote();
base::WeakPtr<CastMediaNotificationItem> GetWeakPtr() { base::WeakPtr<CastMediaNotificationItem> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr(); return weak_ptr_factory_.GetWeakPtr();
} }
...@@ -52,6 +60,7 @@ class CastMediaNotificationItem ...@@ -52,6 +60,7 @@ class CastMediaNotificationItem
notification_controller_; notification_controller_;
media_message_center::MediaNotificationView* view_ = nullptr; media_message_center::MediaNotificationView* view_ = nullptr;
std::unique_ptr<CastMediaSessionController> session_controller_;
const media_router::MediaRoute::Id media_route_id_; const media_router::MediaRoute::Id media_route_id_;
media_session::MediaMetadata metadata_; media_session::MediaMetadata metadata_;
std::vector<media_session::mojom::MediaSessionAction> actions_; std::vector<media_session::mojom::MediaSessionAction> actions_;
......
...@@ -53,14 +53,28 @@ class MockMediaNotificationView ...@@ -53,14 +53,28 @@ class MockMediaNotificationView
MOCK_METHOD1(UpdateWithMediaArtwork, void(const gfx::ImageSkia&)); MOCK_METHOD1(UpdateWithMediaArtwork, void(const gfx::ImageSkia&));
}; };
class MockSessionController : public CastMediaSessionController {
public:
MockSessionController(
mojo::Remote<media_router::mojom::MediaController> remote)
: CastMediaSessionController(std::move(remote)) {}
MOCK_METHOD1(Send, void(media_session::mojom::MediaSessionAction));
MOCK_METHOD1(OnMediaStatusUpdated, void(media_router::mojom::MediaStatusPtr));
};
} // namespace } // namespace
class CastMediaNotificationItemTest : public testing::Test { class CastMediaNotificationItemTest : public testing::Test {
public: public:
void SetUp() override { void SetUp() override {
EXPECT_CALL(notification_controller_, ShowNotification(kRouteId)); EXPECT_CALL(notification_controller_, ShowNotification(kRouteId));
auto session_controller = std::make_unique<MockSessionController>(
mojo::Remote<media_router::mojom::MediaController>());
session_controller_ = session_controller.get();
item_ = std::make_unique<CastMediaNotificationItem>( item_ = std::make_unique<CastMediaNotificationItem>(
CreateMediaRoute(), &notification_controller_); CreateMediaRoute(), &notification_controller_,
std::move(session_controller));
} }
void SetView() { void SetView() {
...@@ -87,6 +101,7 @@ class CastMediaNotificationItemTest : public testing::Test { ...@@ -87,6 +101,7 @@ class CastMediaNotificationItemTest : public testing::Test {
protected: protected:
MockMediaNotificationController notification_controller_; MockMediaNotificationController notification_controller_;
MockSessionController* session_controller_ = nullptr;
MockMediaNotificationView view_; MockMediaNotificationView view_;
std::unique_ptr<CastMediaNotificationItem> item_; std::unique_ptr<CastMediaNotificationItem> item_;
}; };
...@@ -177,3 +192,21 @@ TEST_F(CastMediaNotificationItemTest, HideNotificationOnDelete) { ...@@ -177,3 +192,21 @@ TEST_F(CastMediaNotificationItemTest, HideNotificationOnDelete) {
EXPECT_CALL(notification_controller_, HideNotification(kRouteId)); EXPECT_CALL(notification_controller_, HideNotification(kRouteId));
item_.reset(); item_.reset();
} }
TEST_F(CastMediaNotificationItemTest, SendMediaStatusToController) {
auto status = MediaStatus::New();
status->can_play_pause = true;
EXPECT_CALL(*session_controller_, OnMediaStatusUpdated(_))
.WillOnce([](const media_router::mojom::MediaStatusPtr& media_status) {
EXPECT_TRUE(media_status->can_play_pause);
});
item_->OnMediaStatusUpdated(std::move(status));
}
TEST_F(CastMediaNotificationItemTest, SendActionToController) {
auto status = MediaStatus::New();
item_->OnMediaStatusUpdated(std::move(status));
EXPECT_CALL(*session_controller_, Send(MediaSessionAction::kPlay));
item_->OnMediaSessionActionButtonPressed(MediaSessionAction::kPlay);
}
...@@ -22,6 +22,7 @@ CastMediaNotificationProvider::CastMediaNotificationProvider( ...@@ -22,6 +22,7 @@ CastMediaNotificationProvider::CastMediaNotificationProvider(
media_message_center::MediaNotificationController* notification_controller, media_message_center::MediaNotificationController* notification_controller,
base::RepeatingClosure items_changed_callback) base::RepeatingClosure items_changed_callback)
: media_router::MediaRoutesObserver(router), : media_router::MediaRoutesObserver(router),
router_(router),
notification_controller_(notification_controller), notification_controller_(notification_controller),
items_changed_callback_(std::move(items_changed_callback)) {} items_changed_callback_(std::move(items_changed_callback)) {}
...@@ -48,11 +49,18 @@ void CastMediaNotificationProvider::OnRoutesUpdated( ...@@ -48,11 +49,18 @@ void CastMediaNotificationProvider::OnRoutesUpdated(
if (std::find_if(items_.begin(), items_.end(), [&route](const auto& item) { if (std::find_if(items_.begin(), items_.end(), [&route](const auto& item) {
return item.first == route.media_route_id(); return item.first == route.media_route_id();
}) == items_.end()) { }) == items_.end()) {
items_.emplace(std::piecewise_construct, mojo::Remote<media_router::mojom::MediaController> controller_remote;
std::forward_as_tuple(route.media_route_id()), mojo::PendingReceiver<media_router::mojom::MediaController>
std::forward_as_tuple(route, notification_controller_)); controller_receiver = controller_remote.BindNewPipeAndPassReceiver();
// TODO(crbug.com/987479): Connect the CastMediaNotificationItem to a auto it_pair = items_.emplace(
// controller through MediaRouter::GetMediaController(). std::piecewise_construct,
std::forward_as_tuple(route.media_route_id()),
std::forward_as_tuple(route, notification_controller_,
std::make_unique<CastMediaSessionController>(
std::move(controller_remote))));
router_->GetMediaController(
route.media_route_id(), std::move(controller_receiver),
it_pair.first->second.GetObserverPendingRemote());
} }
} }
if (HasItems() != had_items) if (HasItems() != had_items)
......
...@@ -54,6 +54,7 @@ class CastMediaNotificationProvider : public media_router::MediaRoutesObserver { ...@@ -54,6 +54,7 @@ class CastMediaNotificationProvider : public media_router::MediaRoutesObserver {
virtual bool HasItems() const; virtual bool HasItems() const;
private: private:
media_router::MediaRouter* const router_;
media_message_center::MediaNotificationController* const media_message_center::MediaNotificationController* const
notification_controller_; notification_controller_;
......
// 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 "chrome/browser/ui/global_media_controls/cast_media_session_controller.h"
#include "base/time/time.h"
#include "chrome/common/media_router/mojom/media_status.mojom.h"
#include "services/media_session/public/mojom/constants.mojom.h"
namespace {
constexpr base::TimeDelta kDefaultSeekTimeSeconds =
base::TimeDelta::FromSeconds(media_session::mojom::kDefaultSeekTimeSeconds);
} // namespace
CastMediaSessionController::CastMediaSessionController(
mojo::Remote<media_router::mojom::MediaController> route_controller)
: route_controller_(std::move(route_controller)) {}
CastMediaSessionController::~CastMediaSessionController() {}
void CastMediaSessionController::Send(
media_session::mojom::MediaSessionAction action) {
if (!media_status_)
return;
switch (action) {
case media_session::mojom::MediaSessionAction::kPlay:
route_controller_->Play();
return;
case media_session::mojom::MediaSessionAction::kPause:
route_controller_->Pause();
return;
case media_session::mojom::MediaSessionAction::kPreviousTrack:
route_controller_->PreviousTrack();
return;
case media_session::mojom::MediaSessionAction::kNextTrack:
route_controller_->NextTrack();
return;
case media_session::mojom::MediaSessionAction::kSeekBackward:
route_controller_->Seek(PutWithinBounds(media_status_->current_time -
kDefaultSeekTimeSeconds));
return;
case media_session::mojom::MediaSessionAction::kSeekForward:
route_controller_->Seek(PutWithinBounds(media_status_->current_time +
kDefaultSeekTimeSeconds));
return;
case media_session::mojom::MediaSessionAction::kStop:
route_controller_->Pause();
return;
case media_session::mojom::MediaSessionAction::kSkipAd:
case media_session::mojom::MediaSessionAction::kSeekTo:
case media_session::mojom::MediaSessionAction::kScrubTo:
NOTREACHED();
return;
}
}
void CastMediaSessionController::OnMediaStatusUpdated(
media_router::mojom::MediaStatusPtr media_status) {
media_status_ = std::move(media_status);
}
void CastMediaSessionController::FlushForTesting() {
route_controller_.FlushForTesting();
}
base::TimeDelta CastMediaSessionController::PutWithinBounds(
const base::TimeDelta& time) {
if (time < base::TimeDelta() || !media_status_)
return base::TimeDelta();
if (time > media_status_->duration)
return media_status_->duration;
return time;
}
// 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 CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_CAST_MEDIA_SESSION_CONTROLLER_H_
#define CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_CAST_MEDIA_SESSION_CONTROLLER_H_
#include "base/macros.h"
#include "chrome/common/media_router/mojom/media_controller.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
namespace base {
class TimeDelta;
} // namespace base
// Forwards media action commands to media_router::mojom::MediaController.
class CastMediaSessionController {
public:
CastMediaSessionController(
mojo::Remote<media_router::mojom::MediaController> route_controller);
CastMediaSessionController(const CastMediaSessionController&) = delete;
CastMediaSessionController& operator=(const CastMediaSessionController&) =
delete;
virtual ~CastMediaSessionController();
// Forwards |action| to the MediaController. No-ops if OnMediaStatusUpdated()
// has not been called.
virtual void Send(media_session::mojom::MediaSessionAction action);
virtual void OnMediaStatusUpdated(
media_router::mojom::MediaStatusPtr media_status);
void FlushForTesting();
private:
base::TimeDelta PutWithinBounds(const base::TimeDelta& time);
mojo::Remote<media_router::mojom::MediaController> route_controller_;
media_router::mojom::MediaStatusPtr media_status_;
};
#endif // CHROME_BROWSER_UI_GLOBAL_MEDIA_CONTROLS_CAST_MEDIA_SESSION_CONTROLLER_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 "chrome/browser/ui/global_media_controls/cast_media_session_controller.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chrome/common/media_router/mojom/media_status.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using media_session::mojom::MediaSessionAction;
class MockMediaController : public media_router::mojom::MediaController {
public:
MOCK_METHOD0(Play, void());
MOCK_METHOD0(Pause, void());
MOCK_METHOD1(SetMute, void(bool));
MOCK_METHOD1(SetVolume, void(float));
MOCK_METHOD1(Seek, void(base::TimeDelta));
MOCK_METHOD0(NextTrack, void());
MOCK_METHOD0(PreviousTrack, void());
};
class CastMediaSessionControllerTest : public testing::Test {
public:
void SetUp() override {
mojo::Remote<media_router::mojom::MediaController> mock_controller_remote;
mock_controller_receiver_.Bind(
mock_controller_remote.BindNewPipeAndPassReceiver());
controller_ = std::make_unique<CastMediaSessionController>(
std::move(mock_controller_remote));
media_status_ = media_router::mojom::MediaStatus::New();
media_status_->duration = base::TimeDelta::FromSeconds(100);
media_status_->current_time = base::TimeDelta::FromSeconds(20);
controller_->OnMediaStatusUpdated(media_status_.Clone());
}
void SendToController(MediaSessionAction command) {
controller_->Send(command);
controller_->FlushForTesting();
}
protected:
base::test::TaskEnvironment task_environment_;
MockMediaController mock_controller_;
media_router::mojom::MediaStatusPtr media_status_;
mojo::Receiver<media_router::mojom::MediaController>
mock_controller_receiver_{&mock_controller_};
std::unique_ptr<CastMediaSessionController> controller_;
};
TEST_F(CastMediaSessionControllerTest, SendPlayCommand) {
EXPECT_CALL(mock_controller_, Play());
SendToController(MediaSessionAction::kPlay);
}
TEST_F(CastMediaSessionControllerTest, SendPauseCommand) {
EXPECT_CALL(mock_controller_, Pause());
SendToController(MediaSessionAction::kPause);
}
TEST_F(CastMediaSessionControllerTest, SendPreviousTrackCommand) {
EXPECT_CALL(mock_controller_, PreviousTrack());
SendToController(MediaSessionAction::kPreviousTrack);
}
TEST_F(CastMediaSessionControllerTest, SendNextTrackCommand) {
EXPECT_CALL(mock_controller_, NextTrack());
SendToController(MediaSessionAction::kNextTrack);
}
TEST_F(CastMediaSessionControllerTest, SendSeekBackwardCommand) {
EXPECT_CALL(mock_controller_, Seek(media_status_->current_time -
base::TimeDelta::FromSeconds(5)));
SendToController(MediaSessionAction::kSeekBackward);
}
TEST_F(CastMediaSessionControllerTest, SeekBackwardOutOfRange) {
media_status_->current_time = base::TimeDelta::FromSeconds(2);
controller_->OnMediaStatusUpdated(media_status_.Clone());
EXPECT_CALL(mock_controller_, Seek(base::TimeDelta()));
SendToController(MediaSessionAction::kSeekBackward);
}
TEST_F(CastMediaSessionControllerTest, SendSeekForwardCommand) {
EXPECT_CALL(mock_controller_, Seek(media_status_->current_time +
base::TimeDelta::FromSeconds(5)));
SendToController(MediaSessionAction::kSeekForward);
}
TEST_F(CastMediaSessionControllerTest, SeekForwardOutOfRange) {
media_status_->current_time =
media_status_->duration - base::TimeDelta::FromSeconds(2);
controller_->OnMediaStatusUpdated(media_status_.Clone());
EXPECT_CALL(mock_controller_, Seek(media_status_->duration));
SendToController(MediaSessionAction::kSeekForward);
}
TEST_F(CastMediaSessionControllerTest, SendStopCommand) {
EXPECT_CALL(mock_controller_, Pause());
SendToController(MediaSessionAction::kStop);
}
...@@ -3880,6 +3880,7 @@ test("unit_tests") { ...@@ -3880,6 +3880,7 @@ test("unit_tests") {
"../browser/ui/global_error/global_error_service_unittest.cc", "../browser/ui/global_error/global_error_service_unittest.cc",
"../browser/ui/global_media_controls/cast_media_notification_item_unittest.cc", "../browser/ui/global_media_controls/cast_media_notification_item_unittest.cc",
"../browser/ui/global_media_controls/cast_media_notification_provider_unittest.cc", "../browser/ui/global_media_controls/cast_media_notification_provider_unittest.cc",
"../browser/ui/global_media_controls/cast_media_session_controller_unittest.cc",
"../browser/ui/global_media_controls/media_notification_service_unittest.cc", "../browser/ui/global_media_controls/media_notification_service_unittest.cc",
"../browser/ui/global_media_controls/media_toolbar_button_controller_unittest.cc", "../browser/ui/global_media_controls/media_toolbar_button_controller_unittest.cc",
"../browser/ui/hid/hid_chooser_controller_unittest.cc", "../browser/ui/hid/hid_chooser_controller_unittest.cc",
......
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