1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/BrowsingContext.h"
8 #include "mozilla/dom/ContentMediaController.h"
9 #include "mozilla/dom/Document.h"
10 #include "mozilla/dom/MediaSession.h"
11 #include "mozilla/dom/MediaControlUtils.h"
12 #include "mozilla/dom/WindowContext.h"
13 #include "mozilla/EnumeratedArrayCycleCollection.h"
15 // avoid redefined macro in unified build
17 #define LOG(msg, ...) \
18 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
19 ("MediaSession=%p, " msg, this, ##__VA_ARGS__))
21 namespace mozilla::dom
{
23 double PositionState::CurrentPlaybackPosition(TimeStamp aNow
) const {
24 // https://w3c.github.io/mediasession/#current-playback-position
26 // Set time elapsed to the system time in seconds minus the last position
28 auto timeElapsed
= aNow
- mPositionUpdatedTime
;
29 // Mutliply time elapsed with actual playback rate.
30 timeElapsed
= timeElapsed
.MultDouble(mPlaybackRate
);
31 // Set position to time elapsed added to last reported playback position.
32 auto position
= timeElapsed
.ToSeconds() + mLastReportedPlaybackPosition
;
34 // If position is less than zero, return zero.
38 // If position is greater than duration, return duration.
39 if (position
> mDuration
) {
46 // We don't use NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE because we need to
47 // unregister MediaSession from document's activity listeners.
48 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MediaSession
)
49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaSession
)
50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent
)
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaMetadata
)
52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActionHandlers
)
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc
)
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
56 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaSession
)
58 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent
)
59 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaMetadata
)
60 NS_IMPL_CYCLE_COLLECTION_UNLINK(mActionHandlers
)
61 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc
)
62 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
63 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
64 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaSession
)
65 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaSession
)
66 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaSession
)
67 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
68 NS_INTERFACE_MAP_ENTRY(nsISupports
)
69 NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity
)
72 MediaSession::MediaSession(nsPIDOMWindowInner
* aParent
)
73 : mParent(aParent
), mDoc(mParent
->GetExtantDoc()) {
76 mDoc
->RegisterActivityObserver(this);
77 if (mDoc
->IsCurrentActiveDocument()) {
78 SetMediaSessionDocStatus(SessionDocStatus::eActive
);
82 void MediaSession::Shutdown() {
84 mDoc
->UnregisterActivityObserver(this);
87 SetMediaSessionDocStatus(SessionDocStatus::eInactive
);
91 void MediaSession::NotifyOwnerDocumentActivityChanged() {
92 const bool isDocActive
= mDoc
->IsCurrentActiveDocument();
93 LOG("Document activity changed, isActive=%d", isDocActive
);
95 SetMediaSessionDocStatus(SessionDocStatus::eActive
);
97 SetMediaSessionDocStatus(SessionDocStatus::eInactive
);
101 void MediaSession::SetMediaSessionDocStatus(SessionDocStatus aState
) {
102 if (mSessionDocState
== aState
) {
105 mSessionDocState
= aState
;
106 NotifyMediaSessionDocStatus(mSessionDocState
);
109 nsPIDOMWindowInner
* MediaSession::GetParentObject() const { return mParent
; }
111 JSObject
* MediaSession::WrapObject(JSContext
* aCx
,
112 JS::Handle
<JSObject
*> aGivenProto
) {
113 return MediaSession_Binding::Wrap(aCx
, this, aGivenProto
);
116 MediaMetadata
* MediaSession::GetMetadata() const { return mMediaMetadata
; }
118 void MediaSession::SetMetadata(MediaMetadata
* aMetadata
) {
119 mMediaMetadata
= aMetadata
;
120 NotifyMetadataUpdated();
123 void MediaSession::SetPlaybackState(
124 const MediaSessionPlaybackState
& aPlaybackState
) {
125 if (mDeclaredPlaybackState
== aPlaybackState
) {
128 mDeclaredPlaybackState
= aPlaybackState
;
129 NotifyPlaybackStateUpdated();
132 MediaSessionPlaybackState
MediaSession::PlaybackState() const {
133 return mDeclaredPlaybackState
;
136 void MediaSession::SetActionHandler(MediaSessionAction aAction
,
137 MediaSessionActionHandler
* aHandler
) {
138 MOZ_ASSERT(size_t(aAction
) < std::size(mActionHandlers
));
139 // If the media session changes its supported action, then we would propagate
140 // this information to the chrome process in order to run the media session
141 // actions update algorithm.
142 // https://w3c.github.io/mediasession/#supported-media-session-actions
143 RefPtr
<MediaSessionActionHandler
>& handler
= mActionHandlers
[aAction
];
144 if (!handler
&& aHandler
) {
145 NotifyEnableSupportedAction(aAction
);
146 } else if (handler
&& !aHandler
) {
147 NotifyDisableSupportedAction(aAction
);
149 mActionHandlers
[aAction
] = aHandler
;
152 MediaSessionActionHandler
* MediaSession::GetActionHandler(
153 MediaSessionAction aAction
) const {
154 MOZ_ASSERT(size_t(aAction
) < std::size(mActionHandlers
));
155 return mActionHandlers
[aAction
];
158 void MediaSession::SetPositionState(const MediaPositionState
& aState
,
160 // https://w3c.github.io/mediasession/#dom-mediasession-setpositionstate
161 // If the state is an empty dictionary then clear the position state.
162 if (!aState
.IsAnyMemberPresent()) {
163 mPositionState
.reset();
164 NotifyPositionStateChanged();
168 // If the duration is not present, throw a TypeError.
169 if (!aState
.mDuration
.WasPassed()) {
170 return aRv
.ThrowTypeError("Duration is not present");
173 // If the duration is negative, throw a TypeError.
174 if (aState
.mDuration
.WasPassed() && aState
.mDuration
.Value() < 0.0) {
175 return aRv
.ThrowTypeError(nsPrintfCString(
176 "Invalid duration %f, it can't be negative", aState
.mDuration
.Value()));
179 // If the position is negative or greater than duration, throw a TypeError.
180 if (aState
.mPosition
.WasPassed() &&
181 (aState
.mPosition
.Value() < 0.0 ||
182 aState
.mPosition
.Value() > aState
.mDuration
.Value())) {
183 return aRv
.ThrowTypeError(nsPrintfCString(
184 "Invalid position %f, it can't be negative or greater than duration",
185 aState
.mPosition
.Value()));
188 // If the playbackRate is zero, throw a TypeError.
189 if (aState
.mPlaybackRate
.WasPassed() && aState
.mPlaybackRate
.Value() == 0.0) {
190 return aRv
.ThrowTypeError("The playbackRate is zero");
193 // If the position is not present, set it to zero.
194 double position
= aState
.mPosition
.WasPassed() ? aState
.mPosition
.Value() : 0;
196 // If the playbackRate is not present, set it to 1.0.
197 double playbackRate
=
198 aState
.mPlaybackRate
.WasPassed() ? aState
.mPlaybackRate
.Value() : 1.0;
200 // Update the position state and last position updated time.
201 MOZ_ASSERT(aState
.mDuration
.WasPassed());
202 mPositionState
= Some(PositionState(aState
.mDuration
.Value(), playbackRate
,
203 position
, TimeStamp::Now()));
204 NotifyPositionStateChanged();
207 void MediaSession::NotifyHandler(const MediaSessionActionDetails
& aDetails
) {
208 DispatchNotifyHandler(aDetails
);
211 void MediaSession::DispatchNotifyHandler(
212 const MediaSessionActionDetails
& aDetails
) {
213 class Runnable final
: public mozilla::Runnable
{
215 Runnable(const MediaSession
* aSession
,
216 const MediaSessionActionDetails
& aDetails
)
217 : mozilla::Runnable("MediaSession::DispatchNotifyHandler"),
219 mDetails(aDetails
) {}
221 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
{
222 if (RefPtr
<MediaSessionActionHandler
> handler
=
223 mSession
->GetActionHandler(mDetails
.mAction
)) {
224 handler
->Call(mDetails
);
230 RefPtr
<const MediaSession
> mSession
;
231 MediaSessionActionDetails mDetails
;
234 RefPtr
<nsIRunnable
> runnable
= new Runnable(this, aDetails
);
235 NS_DispatchToMainThread(runnable
);
238 bool MediaSession::IsSupportedAction(MediaSessionAction aAction
) const {
239 MOZ_ASSERT(size_t(aAction
) < std::size(mActionHandlers
));
240 return mActionHandlers
[aAction
] != nullptr;
243 bool MediaSession::IsActive() const {
244 RefPtr
<BrowsingContext
> currentBC
= GetParentObject()->GetBrowsingContext();
245 MOZ_ASSERT(currentBC
);
246 RefPtr
<WindowContext
> wc
= currentBC
->GetTopWindowContext();
250 Maybe
<uint64_t> activeSessionContextId
= wc
->GetActiveMediaSessionContextId();
251 if (!activeSessionContextId
) {
254 LOG("session context Id=%" PRIu64
", active session context Id=%" PRIu64
,
255 currentBC
->Id(), *activeSessionContextId
);
256 return *activeSessionContextId
== currentBC
->Id();
259 void MediaSession::NotifyMediaSessionDocStatus(SessionDocStatus aState
) {
260 RefPtr
<BrowsingContext
> currentBC
= GetParentObject()->GetBrowsingContext();
261 MOZ_ASSERT(currentBC
, "Update session status after context destroyed!");
263 RefPtr
<IMediaInfoUpdater
> updater
= ContentMediaAgent::Get(currentBC
);
267 if (aState
== SessionDocStatus::eActive
) {
268 updater
->NotifySessionCreated(currentBC
->Id());
269 // If media session set its attributes before its document becomes active,
270 // then we would notify those attributes which hasn't been notified as well
271 // because attributes update would only happen if its document is already
273 NotifyMediaSessionAttributes();
275 updater
->NotifySessionDestroyed(currentBC
->Id());
279 void MediaSession::NotifyMediaSessionAttributes() {
280 MOZ_ASSERT(mSessionDocState
== SessionDocStatus::eActive
);
281 if (mDeclaredPlaybackState
!= MediaSessionPlaybackState::None
) {
282 NotifyPlaybackStateUpdated();
284 if (mMediaMetadata
) {
285 NotifyMetadataUpdated();
287 for (size_t idx
= 0; idx
< std::size(mActionHandlers
); idx
++) {
288 MediaSessionAction action
= static_cast<MediaSessionAction
>(idx
);
289 if (mActionHandlers
[action
]) {
290 NotifyEnableSupportedAction(action
);
293 if (mPositionState
) {
294 NotifyPositionStateChanged();
298 void MediaSession::NotifyPlaybackStateUpdated() {
299 if (mSessionDocState
!= SessionDocStatus::eActive
) {
302 RefPtr
<BrowsingContext
> currentBC
= GetParentObject()->GetBrowsingContext();
303 MOZ_ASSERT(currentBC
,
304 "Update session playback state after context destroyed!");
305 if (RefPtr
<IMediaInfoUpdater
> updater
= ContentMediaAgent::Get(currentBC
)) {
306 updater
->SetDeclaredPlaybackState(currentBC
->Id(), mDeclaredPlaybackState
);
310 void MediaSession::NotifyMetadataUpdated() {
311 if (mSessionDocState
!= SessionDocStatus::eActive
) {
314 RefPtr
<BrowsingContext
> currentBC
= GetParentObject()->GetBrowsingContext();
315 MOZ_ASSERT(currentBC
, "Update session metadata after context destroyed!");
317 Maybe
<MediaMetadataBase
> metadata
;
319 metadata
.emplace(*(GetMetadata()->AsMetadataBase()));
321 if (RefPtr
<IMediaInfoUpdater
> updater
= ContentMediaAgent::Get(currentBC
)) {
322 updater
->UpdateMetadata(currentBC
->Id(), metadata
);
326 void MediaSession::NotifyEnableSupportedAction(MediaSessionAction aAction
) {
327 if (mSessionDocState
!= SessionDocStatus::eActive
) {
330 RefPtr
<BrowsingContext
> currentBC
= GetParentObject()->GetBrowsingContext();
331 MOZ_ASSERT(currentBC
, "Update action after context destroyed!");
332 if (RefPtr
<IMediaInfoUpdater
> updater
= ContentMediaAgent::Get(currentBC
)) {
333 updater
->EnableAction(currentBC
->Id(), aAction
);
337 void MediaSession::NotifyDisableSupportedAction(MediaSessionAction aAction
) {
338 if (mSessionDocState
!= SessionDocStatus::eActive
) {
341 RefPtr
<BrowsingContext
> currentBC
= GetParentObject()->GetBrowsingContext();
342 MOZ_ASSERT(currentBC
, "Update action after context destroyed!");
343 if (RefPtr
<IMediaInfoUpdater
> updater
= ContentMediaAgent::Get(currentBC
)) {
344 updater
->DisableAction(currentBC
->Id(), aAction
);
348 void MediaSession::NotifyPositionStateChanged() {
349 if (mSessionDocState
!= SessionDocStatus::eActive
) {
352 RefPtr
<BrowsingContext
> currentBC
= GetParentObject()->GetBrowsingContext();
353 MOZ_ASSERT(currentBC
, "Update action after context destroyed!");
354 if (RefPtr
<IMediaInfoUpdater
> updater
= ContentMediaAgent::Get(currentBC
)) {
355 updater
->UpdatePositionState(currentBC
->Id(), mPositionState
);
359 } // namespace mozilla::dom