Commit f230e625 authored by Becca Hughes's avatar Becca Hughes Committed by Commit Bot

[Media Session] Add SeekTo and ScrubTo actions

Add SeekTo action that seeks to a position from the
beginning of the media.

Also adds a ScrubTo action that may be supported by
some media sessions. It is used for scrubbing on a
position scrubber. The client should call ScrubTo
while the user is scrubbing and then call SeekTo to
release the scrub.

BUG=977375

Change-Id: If926271b1aeebee7b3ef6529962213e1f2b0fde7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1693171
Commit-Queue: Becca Hughes <beccahughes@chromium.org>
Reviewed-by: default avatarSean Topping <seantopping@chromium.org>
Reviewed-by: default avatarTao Wu <wutao@chromium.org>
Reviewed-by: default avatarMatt Falkenhagen <falken@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarTommy Steimel <steimel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#676951}
parent e6852d5d
......@@ -52,6 +52,8 @@ class MockMediaSession : public content::MediaSession {
int minimum_size_px,
int desired_size_px,
GetMediaImageBitmapCallback callback));
MOCK_METHOD1(SeekTo, void(base::TimeDelta));
MOCK_METHOD1(ScrubTo, void(base::TimeDelta));
private:
DISALLOW_COPY_AND_ASSIGN(MockMediaSession);
......
......@@ -268,6 +268,8 @@ void AssistantManagerServiceImpl::UpdateInternalMediaPlayerStatus(
case media_session::mojom::MediaSessionAction::kSeekForward:
case media_session::mojom::MediaSessionAction::kSkipAd:
case media_session::mojom::MediaSessionAction::kStop:
case media_session::mojom::MediaSessionAction::kSeekTo:
case media_session::mojom::MediaSessionAction::kScrubTo:
NOTIMPLEMENTED();
break;
}
......
......@@ -61,6 +61,8 @@ class AssistantMediaSession : public media_session::mojom::MediaSession {
int minimum_size_px,
int desired_size_px,
GetMediaImageBitmapCallback callback) override {}
void SeekTo(base::TimeDelta seek_time) override {}
void ScrubTo(base::TimeDelta seek_time) override {}
// Requests/abandons audio focus to the AudioFocusManager.
void RequestAudioFocus(media_session::mojom::AudioFocusType audio_focus_type);
......
......@@ -207,6 +207,8 @@ void MediaNotificationItem::OnMediaSessionActionButtonPressed(
media_controller_ptr_->Stop();
break;
case MediaSessionAction::kSkipAd:
case MediaSessionAction::kSeekTo:
case MediaSessionAction::kScrubTo:
break;
}
}
......
......@@ -87,6 +87,8 @@ const gfx::VectorIcon* GetVectorIconForMediaAction(MediaSessionAction action) {
return &vector_icons::kMediaNextTrackIcon;
case MediaSessionAction::kStop:
case MediaSessionAction::kSkipAd:
case MediaSessionAction::kSeekTo:
case MediaSessionAction::kScrubTo:
NOTREACHED();
break;
}
......
......@@ -148,6 +148,8 @@ void HardwareKeyMediaController::PerformAction(MediaSessionAction action) {
case MediaSessionAction::kSeekBackward:
case MediaSessionAction::kSeekForward:
case MediaSessionAction::kSkipAd:
case MediaSessionAction::kSeekTo:
case MediaSessionAction::kScrubTo:
NOTREACHED();
return;
}
......@@ -191,6 +193,8 @@ HardwareKeyMediaController::MediaSessionActionToKeyCode(
case MediaSessionAction::kSeekBackward:
case MediaSessionAction::kSeekForward:
case MediaSessionAction::kSkipAd:
case MediaSessionAction::kSeekTo:
case MediaSessionAction::kScrubTo:
return base::nullopt;
}
}
......
......@@ -95,6 +95,10 @@ MediaSessionUserAction MediaSessionActionToUserAction(
return MediaSessionUserAction::SkipAd;
case media_session::mojom::MediaSessionAction::kStop:
return MediaSessionUserAction::Stop;
case media_session::mojom::MediaSessionAction::kSeekTo:
return MediaSessionUserAction::SeekTo;
case media_session::mojom::MediaSessionAction::kScrubTo:
return MediaSessionUserAction::ScrubTo;
}
NOTREACHED();
return MediaSessionUserAction::Play;
......
......@@ -256,6 +256,12 @@ class MediaSessionImpl : public MediaSession,
// Returns whether the action should be routed to |routed_service_|.
bool ShouldRouteAction(media_session::mojom::MediaSessionAction action) const;
// Seek the media session to a specific time.
void SeekTo(base::TimeDelta seek_time) override {}
// Scrub ("fast seek") the media session to a specific time.
void ScrubTo(base::TimeDelta seek_time) override {}
private:
friend class content::WebContentsUserData<MediaSessionImpl>;
friend class ::MediaSessionImplBrowserTest;
......
......@@ -43,7 +43,9 @@ class CONTENT_EXPORT MediaSessionUmaHelper {
SeekForward = 8,
SkipAd = 9,
Stop = 10,
kMaxValue = Stop,
SeekTo = 11,
ScrubTo = 12,
kMaxValue = ScrubTo,
};
MediaSessionUmaHelper();
......
......@@ -81,9 +81,11 @@ class MediaSession : public media_session::mojom::MediaSession {
// Skip ad.
void SkipAd() override = 0;
// Seek the media session. If the media cannot seek then this will be a no-op.
// The |seek_time| is the time delta that the media will seek by and supports
// both positive and negative values.
// Seek the media session from the current position. If the media cannot
// seek then this will be a no-op. The |seek_time| is the time delta that
// the media will seek by and supports both positive and negative values.
// This value cannot be zero. The |kDefaultSeekTimeSeconds| provides a
// default value for seeking by a few seconds.
void Seek(base::TimeDelta seek_time) override = 0;
// Stop the media session.
......@@ -98,6 +100,17 @@ class MediaSession : public media_session::mojom::MediaSession {
int desired_size_px,
GetMediaImageBitmapCallback callback) override = 0;
// Seek the media session to a non-negative |seek_time| from the beginning of
// the current playing media. If the media cannot seek then this will be a
// no-op.
void SeekTo(base::TimeDelta seek_time) override = 0;
// Scrub ("fast seek") the media session to a non-negative |seek_time| from
// the beginning of the current playing media. If the media cannot scrub then
// this will be a no-op. The client should call |SeekTo| to finish the
// scrubbing operation.
void ScrubTo(base::TimeDelta seek_time) override = 0;
protected:
MediaSession() = default;
};
......
......@@ -244,6 +244,20 @@ void MediaController::ObserveImages(
it == session_images_.end() ? std::vector<MediaImage>() : it->second));
}
void MediaController::SeekTo(base::TimeDelta seek_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (session_)
session_->ipc()->SeekTo(seek_time);
}
void MediaController::ScrubTo(base::TimeDelta seek_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (session_)
session_->ipc()->ScrubTo(seek_time);
}
void MediaController::SetMediaSession(AudioFocusRequest* session) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
......
......@@ -47,6 +47,8 @@ class MediaController : public mojom::MediaController,
int desired_size_px,
mojo::PendingRemote<mojom::MediaControllerImageObserver>
observer) override;
void SeekTo(base::TimeDelta seek_time) override;
void ScrubTo(base::TimeDelta seek_time) override;
// mojom::MediaSessionObserver overrides.
void MediaSessionInfoChanged(
......
......@@ -512,6 +512,65 @@ TEST_F(MediaControllerTest, ActiveController_Seek) {
EXPECT_EQ(1, media_session.seek_count());
}
TEST_F(MediaControllerTest, ActiveController_SeekTo) {
test::MockMediaSession media_session;
media_session.SetIsControllable(true);
EXPECT_EQ(0, media_session.seek_to_count());
{
test::MockMediaSessionMojoObserver observer(media_session);
RequestAudioFocus(media_session, mojom::AudioFocusType::kGain);
observer.WaitForState(mojom::MediaSessionInfo::SessionState::kActive);
EXPECT_EQ(0, media_session.seek_to_count());
}
controller()->SeekTo(
base::TimeDelta::FromSeconds(mojom::kDefaultSeekTimeSeconds));
controller().FlushForTesting();
EXPECT_EQ(1, media_session.seek_to_count());
}
TEST_F(MediaControllerTest, ActiveController_ScrubTo) {
test::MockMediaSession media_session;
media_session.SetIsControllable(true);
EXPECT_FALSE(media_session.is_scrubbing());
EXPECT_EQ(0, media_session.seek_to_count());
{
test::MockMediaSessionMojoObserver observer(media_session);
RequestAudioFocus(media_session, mojom::AudioFocusType::kGain);
observer.WaitForState(mojom::MediaSessionInfo::SessionState::kActive);
EXPECT_FALSE(media_session.is_scrubbing());
EXPECT_EQ(0, media_session.seek_to_count());
}
controller()->ScrubTo(
base::TimeDelta::FromSeconds(mojom::kDefaultSeekTimeSeconds));
controller().FlushForTesting();
EXPECT_TRUE(media_session.is_scrubbing());
EXPECT_EQ(0, media_session.seek_to_count());
controller()->ScrubTo(
base::TimeDelta::FromSeconds(mojom::kDefaultSeekTimeSeconds));
controller().FlushForTesting();
EXPECT_TRUE(media_session.is_scrubbing());
EXPECT_EQ(0, media_session.seek_to_count());
controller()->SeekTo(
base::TimeDelta::FromSeconds(mojom::kDefaultSeekTimeSeconds));
controller().FlushForTesting();
EXPECT_FALSE(media_session.is_scrubbing());
EXPECT_EQ(1, media_session.seek_to_count());
}
TEST_F(MediaControllerTest, ActiveController_Metadata_Observer_Abandoned) {
MediaMetadata metadata;
metadata.title = base::ASCIIToUTF16("title");
......
......@@ -241,6 +241,15 @@ void MockMediaSession::GetMediaImageBitmap(
std::move(callback).Run(bitmap);
}
void MockMediaSession::SeekTo(base::TimeDelta seek_time) {
seek_to_count_++;
is_scrubbing_ = false;
}
void MockMediaSession::ScrubTo(base::TimeDelta seek_time) {
is_scrubbing_ = true;
}
void MockMediaSession::SetIsControllable(bool value) {
is_controllable_ = value;
NotifyObservers();
......
......@@ -127,6 +127,8 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession
int minimum_size_px,
int desired_size_px,
GetMediaImageBitmapCallback callback) override;
void SeekTo(base::TimeDelta seek_time) override;
void ScrubTo(base::TimeDelta scrub_to) override;
void SetIsControllable(bool value);
void SetPreferStop(bool value) { prefer_stop_ = value; }
......@@ -162,7 +164,9 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession
int next_track_count() const { return next_track_count_; }
int add_observer_count() const { return add_observer_count_; }
int seek_count() const { return seek_count_; }
int seek_to_count() const { return seek_to_count_; }
bool is_scrubbing() const { return is_scrubbing_; }
const GURL& last_image_src() const { return last_image_src_; }
const base::UnguessableToken& request_id() const { return request_id_; }
......@@ -182,12 +186,14 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession
const bool force_duck_ = false;
bool is_ducking_ = false;
bool is_controllable_ = false;
bool is_scrubbing_ = false;
bool prefer_stop_ = false;
int prev_track_count_ = 0;
int next_track_count_ = 0;
int add_observer_count_ = 0;
int seek_count_ = 0;
int seek_to_count_ = 0;
std::set<mojom::MediaSessionAction> actions_;
......
......@@ -138,6 +138,8 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) TestMediaController
int desired_size_px,
mojo::PendingRemote<mojom::MediaControllerImageObserver>
observer) override {}
void SeekTo(base::TimeDelta seek_time) override {}
void ScrubTo(base::TimeDelta seek_time) override {}
int toggle_suspend_resume_count() const {
return toggle_suspend_resume_count_;
......
......@@ -66,6 +66,17 @@ interface MediaController {
ObserveImages(
MediaSessionImageType type, int32 minimum_size_px, int32 desired_size_px,
pending_remote<MediaControllerImageObserver> observer);
// Seek the media session to a non-negative |seek_time| from the beginning of
// the current playing media. If the media cannot seek then this will be a
// no-op.
SeekTo(mojo_base.mojom.TimeDelta seek_time);
// Scrub ("fast seek") the media session to a non-negative |seek_time| from
// the beginning of the current playing media. If the media cannot scrub then
// this will be a no-op. The client should call |SeekTo| to finish the
// scrubbing operation.
ScrubTo(mojo_base.mojom.TimeDelta seek_time);
};
// The observer for observing media controller events. This is different to a
......
......@@ -27,6 +27,8 @@ enum MediaSessionAction {
kSeekForward,
kSkipAd,
kStop,
kSeekTo,
kScrubTo,
};
[Extensible]
......@@ -141,7 +143,7 @@ interface MediaSessionObserver {
// WebContents or ARC app.
// TODO(https://crbug.com/875004): migrate media session from content/public
// to mojo.
// Next Method ID: 13
// Next Method ID: 15
interface MediaSession {
[Extensible]
enum SuspendType {
......@@ -185,11 +187,11 @@ interface MediaSession {
// no-op.
NextTrack@8();
// Seek the media session. If the media cannot seek then this will be a
// no-op. The |seek_time| is the time delta that the media will seek by and
// supports both positive and negative values. This value cannot be zero.
// The |kDefaultSeekTimeSeconds| provides a default value for seeking by a
// few seconds.
// Seek the media session from the current position. If the media cannot
// seek then this will be a no-op. The |seek_time| is the time delta that
// the media will seek by and supports both positive and negative values.
// This value cannot be zero. The |kDefaultSeekTimeSeconds| provides a
// default value for seeking by a few seconds.
Seek@9(mojo_base.mojom.TimeDelta seek_time);
// Stop the media session.
......@@ -206,4 +208,15 @@ interface MediaSession {
GetMediaImageBitmap@12(
MediaImage image, int32 minimum_size_px, int32 desired_size_px)
=> (MediaImageBitmap? image);
// Seek the media session to a non-negative |seek_time| from the beginning of
// the current playing media. If the media cannot seek then this will be a
// no-op.
SeekTo@13(mojo_base.mojom.TimeDelta seek_time);
// Scrub ("fast seek") the media session to a non-negative |seek_time| from
// the beginning of the current playing media. If the media cannot scrub then
// this will be a no-op. The client should call |SeekTo| to finish the
// scrubbing operation.
ScrubTo@14(mojo_base.mojom.TimeDelta seek_time);
};
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