1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "ContentPlaybackController.h"
7 #include "MediaControlUtils.h"
8 #include "mozilla/dom/ContentMediaController.h"
9 #include "mozilla/dom/MediaSession.h"
10 #include "mozilla/dom/Navigator.h"
11 #include "mozilla/dom/WindowContext.h"
12 #include "nsFocusManager.h"
14 // avoid redefined macro in unified build
16 #define LOG(msg, ...) \
17 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
18 ("ContentPlaybackController=%p, " msg, this, ##__VA_ARGS__))
20 namespace mozilla::dom
{
22 ContentPlaybackController::ContentPlaybackController(
23 BrowsingContext
* aContext
) {
28 MediaSession
* ContentPlaybackController::GetMediaSession() const {
29 RefPtr
<nsPIDOMWindowOuter
> window
= mBC
->GetDOMWindow();
34 RefPtr
<Navigator
> navigator
= window
->GetNavigator();
39 return navigator
->HasCreatedMediaSession() ? navigator
->MediaSession()
43 void ContentPlaybackController::NotifyContentMediaControlKeyReceiver(
44 MediaControlKey aKey
, Maybe
<SeekDetails
> aDetails
) {
45 if (RefPtr
<ContentMediaControlKeyReceiver
> receiver
=
46 ContentMediaControlKeyReceiver::Get(mBC
)) {
47 LOG("Handle '%s' in default behavior for BC %" PRIu64
,
48 GetEnumString(aKey
).get(), mBC
->Id());
49 receiver
->HandleMediaKey(aKey
, aDetails
);
53 void ContentPlaybackController::NotifyMediaSession(MediaSessionAction aAction
) {
54 MediaSessionActionDetails details
;
55 details
.mAction
= aAction
;
56 NotifyMediaSession(details
);
59 void ContentPlaybackController::NotifyMediaSession(
60 const MediaSessionActionDetails
& aDetails
) {
61 if (RefPtr
<MediaSession
> session
= GetMediaSession()) {
62 LOG("Handle '%s' in media session behavior for BC %" PRIu64
,
63 GetEnumString(aDetails
.mAction
).get(), mBC
->Id());
64 MOZ_ASSERT(session
->IsActive(), "Notify inactive media session!");
65 session
->NotifyHandler(aDetails
);
69 void ContentPlaybackController::NotifyMediaSessionWhenActionIsSupported(
70 MediaSessionAction aAction
) {
71 if (IsMediaSessionActionSupported(aAction
)) {
72 NotifyMediaSession(aAction
);
76 bool ContentPlaybackController::IsMediaSessionActionSupported(
77 MediaSessionAction aAction
) const {
78 RefPtr
<MediaSession
> session
= GetMediaSession();
79 return session
? session
->IsActive() && session
->IsSupportedAction(aAction
)
83 Maybe
<uint64_t> ContentPlaybackController::GetActiveMediaSessionId() const {
84 RefPtr
<WindowContext
> wc
= mBC
->GetTopWindowContext();
85 return wc
? wc
->GetActiveMediaSessionContextId() : Nothing();
88 void ContentPlaybackController::Focus() {
89 // Focus is not part of the MediaSession standard, so always use the
90 // default behavior and focus the window currently playing media.
91 if (nsCOMPtr
<nsPIDOMWindowOuter
> win
= mBC
->GetDOMWindow()) {
92 nsFocusManager::FocusWindow(win
, CallerType::System
);
96 void ContentPlaybackController::Play() {
97 const MediaSessionAction action
= MediaSessionAction::Play
;
98 RefPtr
<MediaSession
> session
= GetMediaSession();
99 if (IsMediaSessionActionSupported(action
)) {
100 NotifyMediaSession(action
);
102 // We don't want to arbitrarily call play default handler, because we want to
103 // resume the frame which a user really gets interest in, not all media in the
104 // same page. Therefore, we would only call default handler for `play` when
105 // (1) We don't have an active media session (If we have one, the play action
106 // handler should only be triggered on that session)
107 // (2) Active media session without setting action handler for `play`
108 else if (!GetActiveMediaSessionId() || (session
&& session
->IsActive())) {
109 NotifyContentMediaControlKeyReceiver(MediaControlKey::Play
);
113 void ContentPlaybackController::Pause() {
114 const MediaSessionAction action
= MediaSessionAction::Pause
;
115 if (IsMediaSessionActionSupported(action
)) {
116 NotifyMediaSession(action
);
118 NotifyContentMediaControlKeyReceiver(MediaControlKey::Pause
);
122 void ContentPlaybackController::SeekBackward(double aSeekOffset
) {
123 MediaSessionActionDetails details
;
124 details
.mAction
= MediaSessionAction::Seekbackward
;
125 details
.mSeekOffset
.Construct(aSeekOffset
);
126 RefPtr
<MediaSession
> session
= GetMediaSession();
127 if (IsMediaSessionActionSupported(details
.mAction
)) {
128 NotifyMediaSession(details
);
129 } else if (!GetActiveMediaSessionId() || (session
&& session
->IsActive())) {
130 NotifyContentMediaControlKeyReceiver(MediaControlKey::Seekbackward
,
131 Some(SeekDetails(aSeekOffset
)));
135 void ContentPlaybackController::SeekForward(double aSeekOffset
) {
136 MediaSessionActionDetails details
;
137 details
.mAction
= MediaSessionAction::Seekforward
;
138 details
.mSeekOffset
.Construct(aSeekOffset
);
139 RefPtr
<MediaSession
> session
= GetMediaSession();
140 if (IsMediaSessionActionSupported(details
.mAction
)) {
141 NotifyMediaSession(details
);
142 } else if (!GetActiveMediaSessionId() || (session
&& session
->IsActive())) {
143 NotifyContentMediaControlKeyReceiver(MediaControlKey::Seekforward
,
144 Some(SeekDetails(aSeekOffset
)));
148 void ContentPlaybackController::PreviousTrack() {
149 NotifyMediaSessionWhenActionIsSupported(MediaSessionAction::Previoustrack
);
152 void ContentPlaybackController::NextTrack() {
153 NotifyMediaSessionWhenActionIsSupported(MediaSessionAction::Nexttrack
);
156 void ContentPlaybackController::SkipAd() {
157 NotifyMediaSessionWhenActionIsSupported(MediaSessionAction::Skipad
);
160 void ContentPlaybackController::Stop() {
161 const MediaSessionAction action
= MediaSessionAction::Stop
;
162 if (IsMediaSessionActionSupported(action
)) {
163 NotifyMediaSession(action
);
165 NotifyContentMediaControlKeyReceiver(MediaControlKey::Stop
);
169 void ContentPlaybackController::SeekTo(double aSeekTime
, bool aFastSeek
) {
170 MediaSessionActionDetails details
;
171 details
.mAction
= MediaSessionAction::Seekto
;
172 details
.mSeekTime
.Construct(aSeekTime
);
173 RefPtr
<MediaSession
> session
= GetMediaSession();
175 details
.mFastSeek
.Construct(aFastSeek
);
177 if (IsMediaSessionActionSupported(details
.mAction
)) {
178 NotifyMediaSession(details
);
179 } else if (!GetActiveMediaSessionId() || (session
&& session
->IsActive())) {
180 NotifyContentMediaControlKeyReceiver(
181 MediaControlKey::Seekto
, Some(SeekDetails(aSeekTime
, aFastSeek
)));
185 void ContentMediaControlKeyHandler::HandleMediaControlAction(
186 BrowsingContext
* aContext
, const MediaControlAction
& aAction
) {
187 MOZ_ASSERT(aContext
);
188 // The web content doesn't exist in this browsing context.
189 if (!aContext
->GetDocShell()) {
192 if (aAction
.mKey
.isNothing()) {
193 MOZ_ASSERT_UNREACHABLE("Invalid media control key.");
196 ContentPlaybackController
controller(aContext
);
197 switch (aAction
.mKey
.value()) {
198 case MediaControlKey::Focus
:
201 case MediaControlKey::Play
:
204 case MediaControlKey::Pause
:
207 case MediaControlKey::Playpause
:
208 MOZ_ASSERT_UNREACHABLE("Invalid media control key.");
210 case MediaControlKey::Stop
:
213 case MediaControlKey::Previoustrack
:
214 controller
.PreviousTrack();
216 case MediaControlKey::Nexttrack
:
217 controller
.NextTrack();
219 case MediaControlKey::Seekbackward
: {
220 const SeekDetails
& details
= *aAction
.mDetails
;
221 MOZ_ASSERT(details
.mRelativeSeekOffset
);
222 controller
.SeekBackward(details
.mRelativeSeekOffset
.value());
225 case MediaControlKey::Seekforward
: {
226 const SeekDetails
& details
= *aAction
.mDetails
;
227 MOZ_ASSERT(details
.mRelativeSeekOffset
);
228 controller
.SeekForward(details
.mRelativeSeekOffset
.value());
231 case MediaControlKey::Skipad
:
234 case MediaControlKey::Seekto
: {
235 const SeekDetails
& details
= *aAction
.mDetails
;
236 MOZ_ASSERT(details
.mAbsolute
);
237 controller
.SeekTo(details
.mAbsolute
->mSeekTime
,
238 details
.mAbsolute
->mFastSeek
);
242 MOZ_ASSERT_UNREACHABLE("Invalid media control key.");
246 } // namespace mozilla::dom