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 { ...@@ -52,6 +52,8 @@ class MockMediaSession : public content::MediaSession {
int minimum_size_px, int minimum_size_px,
int desired_size_px, int desired_size_px,
GetMediaImageBitmapCallback callback)); GetMediaImageBitmapCallback callback));
MOCK_METHOD1(SeekTo, void(base::TimeDelta));
MOCK_METHOD1(ScrubTo, void(base::TimeDelta));
private: private:
DISALLOW_COPY_AND_ASSIGN(MockMediaSession); DISALLOW_COPY_AND_ASSIGN(MockMediaSession);
......
...@@ -268,6 +268,8 @@ void AssistantManagerServiceImpl::UpdateInternalMediaPlayerStatus( ...@@ -268,6 +268,8 @@ void AssistantManagerServiceImpl::UpdateInternalMediaPlayerStatus(
case media_session::mojom::MediaSessionAction::kSeekForward: case media_session::mojom::MediaSessionAction::kSeekForward:
case media_session::mojom::MediaSessionAction::kSkipAd: case media_session::mojom::MediaSessionAction::kSkipAd:
case media_session::mojom::MediaSessionAction::kStop: case media_session::mojom::MediaSessionAction::kStop:
case media_session::mojom::MediaSessionAction::kSeekTo:
case media_session::mojom::MediaSessionAction::kScrubTo:
NOTIMPLEMENTED(); NOTIMPLEMENTED();
break; break;
} }
......
...@@ -61,6 +61,8 @@ class AssistantMediaSession : public media_session::mojom::MediaSession { ...@@ -61,6 +61,8 @@ class AssistantMediaSession : public media_session::mojom::MediaSession {
int minimum_size_px, int minimum_size_px,
int desired_size_px, int desired_size_px,
GetMediaImageBitmapCallback callback) override {} GetMediaImageBitmapCallback callback) override {}
void SeekTo(base::TimeDelta seek_time) override {}
void ScrubTo(base::TimeDelta seek_time) override {}
// Requests/abandons audio focus to the AudioFocusManager. // Requests/abandons audio focus to the AudioFocusManager.
void RequestAudioFocus(media_session::mojom::AudioFocusType audio_focus_type); void RequestAudioFocus(media_session::mojom::AudioFocusType audio_focus_type);
......
...@@ -207,6 +207,8 @@ void MediaNotificationItem::OnMediaSessionActionButtonPressed( ...@@ -207,6 +207,8 @@ void MediaNotificationItem::OnMediaSessionActionButtonPressed(
media_controller_ptr_->Stop(); media_controller_ptr_->Stop();
break; break;
case MediaSessionAction::kSkipAd: case MediaSessionAction::kSkipAd:
case MediaSessionAction::kSeekTo:
case MediaSessionAction::kScrubTo:
break; break;
} }
} }
......
...@@ -87,6 +87,8 @@ const gfx::VectorIcon* GetVectorIconForMediaAction(MediaSessionAction action) { ...@@ -87,6 +87,8 @@ const gfx::VectorIcon* GetVectorIconForMediaAction(MediaSessionAction action) {
return &vector_icons::kMediaNextTrackIcon; return &vector_icons::kMediaNextTrackIcon;
case MediaSessionAction::kStop: case MediaSessionAction::kStop:
case MediaSessionAction::kSkipAd: case MediaSessionAction::kSkipAd:
case MediaSessionAction::kSeekTo:
case MediaSessionAction::kScrubTo:
NOTREACHED(); NOTREACHED();
break; break;
} }
......
...@@ -148,6 +148,8 @@ void HardwareKeyMediaController::PerformAction(MediaSessionAction action) { ...@@ -148,6 +148,8 @@ void HardwareKeyMediaController::PerformAction(MediaSessionAction action) {
case MediaSessionAction::kSeekBackward: case MediaSessionAction::kSeekBackward:
case MediaSessionAction::kSeekForward: case MediaSessionAction::kSeekForward:
case MediaSessionAction::kSkipAd: case MediaSessionAction::kSkipAd:
case MediaSessionAction::kSeekTo:
case MediaSessionAction::kScrubTo:
NOTREACHED(); NOTREACHED();
return; return;
} }
...@@ -191,6 +193,8 @@ HardwareKeyMediaController::MediaSessionActionToKeyCode( ...@@ -191,6 +193,8 @@ HardwareKeyMediaController::MediaSessionActionToKeyCode(
case MediaSessionAction::kSeekBackward: case MediaSessionAction::kSeekBackward:
case MediaSessionAction::kSeekForward: case MediaSessionAction::kSeekForward:
case MediaSessionAction::kSkipAd: case MediaSessionAction::kSkipAd:
case MediaSessionAction::kSeekTo:
case MediaSessionAction::kScrubTo:
return base::nullopt; return base::nullopt;
} }
} }
......
...@@ -95,6 +95,10 @@ MediaSessionUserAction MediaSessionActionToUserAction( ...@@ -95,6 +95,10 @@ MediaSessionUserAction MediaSessionActionToUserAction(
return MediaSessionUserAction::SkipAd; return MediaSessionUserAction::SkipAd;
case media_session::mojom::MediaSessionAction::kStop: case media_session::mojom::MediaSessionAction::kStop:
return MediaSessionUserAction::Stop; return MediaSessionUserAction::Stop;
case media_session::mojom::MediaSessionAction::kSeekTo:
return MediaSessionUserAction::SeekTo;
case media_session::mojom::MediaSessionAction::kScrubTo:
return MediaSessionUserAction::ScrubTo;
} }
NOTREACHED(); NOTREACHED();
return MediaSessionUserAction::Play; return MediaSessionUserAction::Play;
......
...@@ -256,6 +256,12 @@ class MediaSessionImpl : public MediaSession, ...@@ -256,6 +256,12 @@ class MediaSessionImpl : public MediaSession,
// Returns whether the action should be routed to |routed_service_|. // Returns whether the action should be routed to |routed_service_|.
bool ShouldRouteAction(media_session::mojom::MediaSessionAction action) const; 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: private:
friend class content::WebContentsUserData<MediaSessionImpl>; friend class content::WebContentsUserData<MediaSessionImpl>;
friend class ::MediaSessionImplBrowserTest; friend class ::MediaSessionImplBrowserTest;
......
...@@ -43,7 +43,9 @@ class CONTENT_EXPORT MediaSessionUmaHelper { ...@@ -43,7 +43,9 @@ class CONTENT_EXPORT MediaSessionUmaHelper {
SeekForward = 8, SeekForward = 8,
SkipAd = 9, SkipAd = 9,
Stop = 10, Stop = 10,
kMaxValue = Stop, SeekTo = 11,
ScrubTo = 12,
kMaxValue = ScrubTo,
}; };
MediaSessionUmaHelper(); MediaSessionUmaHelper();
......
...@@ -81,9 +81,11 @@ class MediaSession : public media_session::mojom::MediaSession { ...@@ -81,9 +81,11 @@ class MediaSession : public media_session::mojom::MediaSession {
// Skip ad. // Skip ad.
void SkipAd() override = 0; void SkipAd() override = 0;
// Seek the media session. If the media cannot seek then this will be a no-op. // Seek the media session from the current position. If the media cannot
// The |seek_time| is the time delta that the media will seek by and supports // seek then this will be a no-op. The |seek_time| is the time delta that
// both positive and negative values. // 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; void Seek(base::TimeDelta seek_time) override = 0;
// Stop the media session. // Stop the media session.
...@@ -98,6 +100,17 @@ class MediaSession : public media_session::mojom::MediaSession { ...@@ -98,6 +100,17 @@ class MediaSession : public media_session::mojom::MediaSession {
int desired_size_px, int desired_size_px,
GetMediaImageBitmapCallback callback) override = 0; 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: protected:
MediaSession() = default; MediaSession() = default;
}; };
......
...@@ -244,6 +244,20 @@ void MediaController::ObserveImages( ...@@ -244,6 +244,20 @@ void MediaController::ObserveImages(
it == session_images_.end() ? std::vector<MediaImage>() : it->second)); 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) { void MediaController::SetMediaSession(AudioFocusRequest* session) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
......
...@@ -47,6 +47,8 @@ class MediaController : public mojom::MediaController, ...@@ -47,6 +47,8 @@ class MediaController : public mojom::MediaController,
int desired_size_px, int desired_size_px,
mojo::PendingRemote<mojom::MediaControllerImageObserver> mojo::PendingRemote<mojom::MediaControllerImageObserver>
observer) override; observer) override;
void SeekTo(base::TimeDelta seek_time) override;
void ScrubTo(base::TimeDelta seek_time) override;
// mojom::MediaSessionObserver overrides. // mojom::MediaSessionObserver overrides.
void MediaSessionInfoChanged( void MediaSessionInfoChanged(
......
...@@ -512,6 +512,65 @@ TEST_F(MediaControllerTest, ActiveController_Seek) { ...@@ -512,6 +512,65 @@ TEST_F(MediaControllerTest, ActiveController_Seek) {
EXPECT_EQ(1, media_session.seek_count()); 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) { TEST_F(MediaControllerTest, ActiveController_Metadata_Observer_Abandoned) {
MediaMetadata metadata; MediaMetadata metadata;
metadata.title = base::ASCIIToUTF16("title"); metadata.title = base::ASCIIToUTF16("title");
......
...@@ -241,6 +241,15 @@ void MockMediaSession::GetMediaImageBitmap( ...@@ -241,6 +241,15 @@ void MockMediaSession::GetMediaImageBitmap(
std::move(callback).Run(bitmap); 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) { void MockMediaSession::SetIsControllable(bool value) {
is_controllable_ = value; is_controllable_ = value;
NotifyObservers(); NotifyObservers();
......
...@@ -127,6 +127,8 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession ...@@ -127,6 +127,8 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession
int minimum_size_px, int minimum_size_px,
int desired_size_px, int desired_size_px,
GetMediaImageBitmapCallback callback) override; GetMediaImageBitmapCallback callback) override;
void SeekTo(base::TimeDelta seek_time) override;
void ScrubTo(base::TimeDelta scrub_to) override;
void SetIsControllable(bool value); void SetIsControllable(bool value);
void SetPreferStop(bool value) { prefer_stop_ = value; } void SetPreferStop(bool value) { prefer_stop_ = value; }
...@@ -162,7 +164,9 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession ...@@ -162,7 +164,9 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession
int next_track_count() const { return next_track_count_; } int next_track_count() const { return next_track_count_; }
int add_observer_count() const { return add_observer_count_; } int add_observer_count() const { return add_observer_count_; }
int seek_count() const { return seek_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 GURL& last_image_src() const { return last_image_src_; }
const base::UnguessableToken& request_id() const { return request_id_; } const base::UnguessableToken& request_id() const { return request_id_; }
...@@ -182,12 +186,14 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession ...@@ -182,12 +186,14 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession
const bool force_duck_ = false; const bool force_duck_ = false;
bool is_ducking_ = false; bool is_ducking_ = false;
bool is_controllable_ = false; bool is_controllable_ = false;
bool is_scrubbing_ = false;
bool prefer_stop_ = false; bool prefer_stop_ = false;
int prev_track_count_ = 0; int prev_track_count_ = 0;
int next_track_count_ = 0; int next_track_count_ = 0;
int add_observer_count_ = 0; int add_observer_count_ = 0;
int seek_count_ = 0; int seek_count_ = 0;
int seek_to_count_ = 0;
std::set<mojom::MediaSessionAction> actions_; std::set<mojom::MediaSessionAction> actions_;
......
...@@ -138,6 +138,8 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) TestMediaController ...@@ -138,6 +138,8 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) TestMediaController
int desired_size_px, int desired_size_px,
mojo::PendingRemote<mojom::MediaControllerImageObserver> mojo::PendingRemote<mojom::MediaControllerImageObserver>
observer) override {} observer) override {}
void SeekTo(base::TimeDelta seek_time) override {}
void ScrubTo(base::TimeDelta seek_time) override {}
int toggle_suspend_resume_count() const { int toggle_suspend_resume_count() const {
return toggle_suspend_resume_count_; return toggle_suspend_resume_count_;
......
...@@ -66,6 +66,17 @@ interface MediaController { ...@@ -66,6 +66,17 @@ interface MediaController {
ObserveImages( ObserveImages(
MediaSessionImageType type, int32 minimum_size_px, int32 desired_size_px, MediaSessionImageType type, int32 minimum_size_px, int32 desired_size_px,
pending_remote<MediaControllerImageObserver> observer); 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 // The observer for observing media controller events. This is different to a
......
...@@ -27,6 +27,8 @@ enum MediaSessionAction { ...@@ -27,6 +27,8 @@ enum MediaSessionAction {
kSeekForward, kSeekForward,
kSkipAd, kSkipAd,
kStop, kStop,
kSeekTo,
kScrubTo,
}; };
[Extensible] [Extensible]
...@@ -141,7 +143,7 @@ interface MediaSessionObserver { ...@@ -141,7 +143,7 @@ interface MediaSessionObserver {
// WebContents or ARC app. // WebContents or ARC app.
// TODO(https://crbug.com/875004): migrate media session from content/public // TODO(https://crbug.com/875004): migrate media session from content/public
// to mojo. // to mojo.
// Next Method ID: 13 // Next Method ID: 15
interface MediaSession { interface MediaSession {
[Extensible] [Extensible]
enum SuspendType { enum SuspendType {
...@@ -185,11 +187,11 @@ interface MediaSession { ...@@ -185,11 +187,11 @@ interface MediaSession {
// no-op. // no-op.
NextTrack@8(); NextTrack@8();
// Seek the media session. If the media cannot seek then this will be a // Seek the media session from the current position. If the media cannot
// no-op. The |seek_time| is the time delta that the media will seek by and // seek then this will be a no-op. The |seek_time| is the time delta that
// supports both positive and negative values. This value cannot be zero. // the media will seek by and supports both positive and negative values.
// The |kDefaultSeekTimeSeconds| provides a default value for seeking by a // This value cannot be zero. The |kDefaultSeekTimeSeconds| provides a
// few seconds. // default value for seeking by a few seconds.
Seek@9(mojo_base.mojom.TimeDelta seek_time); Seek@9(mojo_base.mojom.TimeDelta seek_time);
// Stop the media session. // Stop the media session.
...@@ -206,4 +208,15 @@ interface MediaSession { ...@@ -206,4 +208,15 @@ interface MediaSession {
GetMediaImageBitmap@12( GetMediaImageBitmap@12(
MediaImage image, int32 minimum_size_px, int32 desired_size_px) MediaImage image, int32 minimum_size_px, int32 desired_size_px)
=> (MediaImageBitmap? image); => (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