Backed out 7 changesets (bug 1942424) for causing frequent crashes. a=backout
[gecko.git] / dom / media / mediasession / MediaSession.cpp
blobf8a33995d6f66531d457a86a484b65a72a8abaa3
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
16 #undef LOG
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
27 // updated time.
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.
35 if (position < 0.0) {
36 return 0.0;
38 // If position is greater than duration, return duration.
39 if (position > mDuration) {
40 return mDuration;
42 // Return position.
43 return position;
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)
57 tmp->Shutdown();
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)
70 NS_INTERFACE_MAP_END
72 MediaSession::MediaSession(nsPIDOMWindowInner* aParent)
73 : mParent(aParent), mDoc(mParent->GetExtantDoc()) {
74 MOZ_ASSERT(mParent);
75 MOZ_ASSERT(mDoc);
76 mDoc->RegisterActivityObserver(this);
77 if (mDoc->IsCurrentActiveDocument()) {
78 SetMediaSessionDocStatus(SessionDocStatus::eActive);
82 void MediaSession::Shutdown() {
83 if (mDoc) {
84 mDoc->UnregisterActivityObserver(this);
86 if (mParent) {
87 SetMediaSessionDocStatus(SessionDocStatus::eInactive);
91 void MediaSession::NotifyOwnerDocumentActivityChanged() {
92 const bool isDocActive = mDoc->IsCurrentActiveDocument();
93 LOG("Document activity changed, isActive=%d", isDocActive);
94 if (isDocActive) {
95 SetMediaSessionDocStatus(SessionDocStatus::eActive);
96 } else {
97 SetMediaSessionDocStatus(SessionDocStatus::eInactive);
101 void MediaSession::SetMediaSessionDocStatus(SessionDocStatus aState) {
102 if (mSessionDocState == aState) {
103 return;
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) {
126 return;
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,
159 ErrorResult& aRv) {
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();
165 return;
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 {
214 public:
215 Runnable(const MediaSession* aSession,
216 const MediaSessionActionDetails& aDetails)
217 : mozilla::Runnable("MediaSession::DispatchNotifyHandler"),
218 mSession(aSession),
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);
226 return NS_OK;
229 private:
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();
247 if (!wc) {
248 return false;
250 Maybe<uint64_t> activeSessionContextId = wc->GetActiveMediaSessionContextId();
251 if (!activeSessionContextId) {
252 return false;
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);
264 if (!updater) {
265 return;
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
272 // active.
273 NotifyMediaSessionAttributes();
274 } else {
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) {
300 return;
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) {
312 return;
314 RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext();
315 MOZ_ASSERT(currentBC, "Update session metadata after context destroyed!");
317 Maybe<MediaMetadataBase> metadata;
318 if (GetMetadata()) {
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) {
328 return;
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) {
339 return;
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) {
350 return;
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