Bug 1943650 - Command-line --help output misformatted after --dbus-service. r=emilio
[gecko.git] / dom / media / mediacontrol / ContentMediaController.cpp
blob1818e5cfa3107a766757d601077ec0c799115e17
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 "ContentMediaController.h"
7 #include "MediaControlUtils.h"
8 #include "mozilla/ClearOnShutdown.h"
9 #include "mozilla/StaticPtr.h"
10 #include "mozilla/Telemetry.h"
11 #include "mozilla/ToString.h"
12 #include "mozilla/dom/BrowsingContext.h"
13 #include "mozilla/dom/CanonicalBrowsingContext.h"
14 #include "mozilla/dom/ContentChild.h"
15 #include "nsGlobalWindowInner.h"
17 namespace mozilla::dom {
19 #undef LOG
20 #define LOG(msg, ...) \
21 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
22 ("ContentMediaController=%p, " msg, this, ##__VA_ARGS__))
24 static Maybe<bool> sXPCOMShutdown;
26 static void InitXPCOMShutdownMonitor() {
27 if (sXPCOMShutdown) {
28 return;
30 sXPCOMShutdown.emplace(false);
31 RunOnShutdown([&] { sXPCOMShutdown = Some(true); });
34 static ContentMediaController* GetContentMediaControllerFromBrowsingContext(
35 BrowsingContext* aBrowsingContext) {
36 MOZ_ASSERT(NS_IsMainThread());
37 InitXPCOMShutdownMonitor();
38 if (!aBrowsingContext || aBrowsingContext->IsDiscarded()) {
39 return nullptr;
42 nsPIDOMWindowOuter* outer = aBrowsingContext->GetDOMWindow();
43 if (!outer) {
44 return nullptr;
47 nsGlobalWindowInner* inner =
48 nsGlobalWindowInner::Cast(outer->GetCurrentInnerWindow());
49 return inner ? inner->GetContentMediaController() : nullptr;
52 static already_AddRefed<BrowsingContext> GetBrowsingContextForAgent(
53 uint64_t aBrowsingContextId) {
54 // If XPCOM has been shutdown, then we're not able to access browsing context.
55 if (sXPCOMShutdown && *sXPCOMShutdown) {
56 return nullptr;
58 return BrowsingContext::Get(aBrowsingContextId);
61 /* static */
62 ContentMediaControlKeyReceiver* ContentMediaControlKeyReceiver::Get(
63 BrowsingContext* aBC) {
64 MOZ_ASSERT(NS_IsMainThread());
65 return GetContentMediaControllerFromBrowsingContext(aBC);
68 /* static */
69 ContentMediaAgent* ContentMediaAgent::Get(BrowsingContext* aBC) {
70 MOZ_ASSERT(NS_IsMainThread());
71 return GetContentMediaControllerFromBrowsingContext(aBC);
74 void ContentMediaAgent::NotifyMediaPlaybackChanged(uint64_t aBrowsingContextId,
75 MediaPlaybackState aState) {
76 MOZ_ASSERT(NS_IsMainThread());
77 RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
78 if (!bc || bc->IsDiscarded()) {
79 return;
82 LOG("Notify media %s in BC %" PRId64, ToString(aState).c_str(), bc->Id());
83 if (XRE_IsContentProcess()) {
84 ContentChild* contentChild = ContentChild::GetSingleton();
85 Unused << contentChild->SendNotifyMediaPlaybackChanged(bc, aState);
86 } else {
87 // Currently this only happen when we disable e10s, otherwise all controlled
88 // media would be run in the content process.
89 if (RefPtr<IMediaInfoUpdater> updater =
90 bc->Canonical()->GetMediaController()) {
91 updater->NotifyMediaPlaybackChanged(bc->Id(), aState);
96 void ContentMediaAgent::NotifyMediaAudibleChanged(uint64_t aBrowsingContextId,
97 MediaAudibleState aState) {
98 MOZ_ASSERT(NS_IsMainThread());
99 RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
100 if (!bc || bc->IsDiscarded()) {
101 return;
104 LOG("Notify media became %s in BC %" PRId64,
105 aState == MediaAudibleState::eAudible ? "audible" : "inaudible",
106 bc->Id());
107 if (XRE_IsContentProcess()) {
108 ContentChild* contentChild = ContentChild::GetSingleton();
109 Unused << contentChild->SendNotifyMediaAudibleChanged(bc, aState);
110 } else {
111 // Currently this only happen when we disable e10s, otherwise all controlled
112 // media would be run in the content process.
113 if (RefPtr<IMediaInfoUpdater> updater =
114 bc->Canonical()->GetMediaController()) {
115 updater->NotifyMediaAudibleChanged(bc->Id(), aState);
120 void ContentMediaAgent::SetIsInPictureInPictureMode(
121 uint64_t aBrowsingContextId, bool aIsInPictureInPictureMode) {
122 MOZ_ASSERT(NS_IsMainThread());
123 RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
124 if (!bc || bc->IsDiscarded()) {
125 return;
128 LOG("Notify media Picture-in-Picture mode '%s' in BC %" PRId64,
129 aIsInPictureInPictureMode ? "enabled" : "disabled", bc->Id());
130 if (XRE_IsContentProcess()) {
131 ContentChild* contentChild = ContentChild::GetSingleton();
132 Unused << contentChild->SendNotifyPictureInPictureModeChanged(
133 bc, aIsInPictureInPictureMode);
134 } else {
135 // Currently this only happen when we disable e10s, otherwise all controlled
136 // media would be run in the content process.
137 if (RefPtr<IMediaInfoUpdater> updater =
138 bc->Canonical()->GetMediaController()) {
139 updater->SetIsInPictureInPictureMode(bc->Id(), aIsInPictureInPictureMode);
144 void ContentMediaAgent::SetDeclaredPlaybackState(
145 uint64_t aBrowsingContextId, MediaSessionPlaybackState aState) {
146 RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
147 if (!bc || bc->IsDiscarded()) {
148 return;
151 LOG("Notify declared playback state '%s' in BC %" PRId64,
152 ToMediaSessionPlaybackStateStr(aState), bc->Id());
153 if (XRE_IsContentProcess()) {
154 ContentChild* contentChild = ContentChild::GetSingleton();
155 Unused << contentChild->SendNotifyMediaSessionPlaybackStateChanged(bc,
156 aState);
157 return;
159 // This would only happen when we disable e10s.
160 if (RefPtr<IMediaInfoUpdater> updater =
161 bc->Canonical()->GetMediaController()) {
162 updater->SetDeclaredPlaybackState(bc->Id(), aState);
166 void ContentMediaAgent::NotifySessionCreated(uint64_t aBrowsingContextId) {
167 RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
168 if (!bc || bc->IsDiscarded()) {
169 return;
172 LOG("Notify media session being created in BC %" PRId64, bc->Id());
173 if (XRE_IsContentProcess()) {
174 ContentChild* contentChild = ContentChild::GetSingleton();
175 Unused << contentChild->SendNotifyMediaSessionUpdated(bc, true);
176 return;
178 // This would only happen when we disable e10s.
179 if (RefPtr<IMediaInfoUpdater> updater =
180 bc->Canonical()->GetMediaController()) {
181 updater->NotifySessionCreated(bc->Id());
185 void ContentMediaAgent::NotifySessionDestroyed(uint64_t aBrowsingContextId) {
186 RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
187 if (!bc || bc->IsDiscarded()) {
188 return;
191 LOG("Notify media session being destroyed in BC %" PRId64, bc->Id());
192 if (XRE_IsContentProcess()) {
193 ContentChild* contentChild = ContentChild::GetSingleton();
194 Unused << contentChild->SendNotifyMediaSessionUpdated(bc, false);
195 return;
197 // This would only happen when we disable e10s.
198 if (RefPtr<IMediaInfoUpdater> updater =
199 bc->Canonical()->GetMediaController()) {
200 updater->NotifySessionDestroyed(bc->Id());
204 void ContentMediaAgent::UpdateMetadata(
205 uint64_t aBrowsingContextId, const Maybe<MediaMetadataBase>& aMetadata) {
206 RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
207 if (!bc || bc->IsDiscarded()) {
208 return;
211 LOG("Notify media session metadata change in BC %" PRId64, bc->Id());
212 if (XRE_IsContentProcess()) {
213 ContentChild* contentChild = ContentChild::GetSingleton();
214 Unused << contentChild->SendNotifyUpdateMediaMetadata(bc, aMetadata);
215 return;
217 // This would only happen when we disable e10s.
218 if (RefPtr<IMediaInfoUpdater> updater =
219 bc->Canonical()->GetMediaController()) {
220 updater->UpdateMetadata(bc->Id(), aMetadata);
224 void ContentMediaAgent::EnableAction(uint64_t aBrowsingContextId,
225 MediaSessionAction aAction) {
226 RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
227 if (!bc || bc->IsDiscarded()) {
228 return;
231 LOG("Notify to enable action '%s' in BC %" PRId64,
232 GetEnumString(aAction).get(), bc->Id());
233 if (XRE_IsContentProcess()) {
234 ContentChild* contentChild = ContentChild::GetSingleton();
235 Unused << contentChild->SendNotifyMediaSessionSupportedActionChanged(
236 bc, aAction, true);
237 return;
239 // This would only happen when we disable e10s.
240 if (RefPtr<IMediaInfoUpdater> updater =
241 bc->Canonical()->GetMediaController()) {
242 updater->EnableAction(bc->Id(), aAction);
246 void ContentMediaAgent::DisableAction(uint64_t aBrowsingContextId,
247 MediaSessionAction aAction) {
248 RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
249 if (!bc || bc->IsDiscarded()) {
250 return;
253 LOG("Notify to disable action '%s' in BC %" PRId64,
254 GetEnumString(aAction).get(), bc->Id());
255 if (XRE_IsContentProcess()) {
256 ContentChild* contentChild = ContentChild::GetSingleton();
257 Unused << contentChild->SendNotifyMediaSessionSupportedActionChanged(
258 bc, aAction, false);
259 return;
261 // This would only happen when we disable e10s.
262 if (RefPtr<IMediaInfoUpdater> updater =
263 bc->Canonical()->GetMediaController()) {
264 updater->DisableAction(bc->Id(), aAction);
268 void ContentMediaAgent::NotifyMediaFullScreenState(uint64_t aBrowsingContextId,
269 bool aIsInFullScreen) {
270 RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
271 if (!bc || bc->IsDiscarded()) {
272 return;
275 LOG("Notify %s fullscreen in BC %" PRId64,
276 aIsInFullScreen ? "entered" : "left", bc->Id());
277 if (XRE_IsContentProcess()) {
278 ContentChild* contentChild = ContentChild::GetSingleton();
279 Unused << contentChild->SendNotifyMediaFullScreenState(bc, aIsInFullScreen);
280 return;
282 // This would only happen when we disable e10s.
283 if (RefPtr<IMediaInfoUpdater> updater =
284 bc->Canonical()->GetMediaController()) {
285 updater->NotifyMediaFullScreenState(bc->Id(), aIsInFullScreen);
289 void ContentMediaAgent::UpdatePositionState(
290 uint64_t aBrowsingContextId, const Maybe<PositionState>& aState) {
291 RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
292 if (!bc || bc->IsDiscarded()) {
293 return;
295 if (XRE_IsContentProcess()) {
296 ContentChild* contentChild = ContentChild::GetSingleton();
297 Unused << contentChild->SendNotifyPositionStateChanged(bc, aState);
298 return;
300 // This would only happen when we disable e10s.
301 if (RefPtr<IMediaInfoUpdater> updater =
302 bc->Canonical()->GetMediaController()) {
303 updater->UpdatePositionState(bc->Id(), aState);
307 void ContentMediaAgent::UpdateGuessedPositionState(
308 uint64_t aBrowsingContextId, const nsID& aMediaId,
309 const Maybe<PositionState>& aState) {
310 RefPtr<BrowsingContext> bc = GetBrowsingContextForAgent(aBrowsingContextId);
311 if (!bc || bc->IsDiscarded()) {
312 return;
315 if (aState) {
316 LOG("Update guessed position state for BC %" PRId64
317 " media id %s (duration=%f, playbackRate=%f, position=%f)",
318 bc->Id(), aMediaId.ToString().get(), aState->mDuration,
319 aState->mPlaybackRate, aState->mLastReportedPlaybackPosition);
320 } else {
321 LOG("Clear guessed position state for BC %" PRId64 " media id %s", bc->Id(),
322 aMediaId.ToString().get());
325 if (XRE_IsContentProcess()) {
326 ContentChild* contentChild = ContentChild::GetSingleton();
327 Unused << contentChild->SendNotifyGuessedPositionStateChanged(bc, aMediaId,
328 aState);
329 return;
331 // This would only happen when we disable e10s.
332 if (RefPtr<IMediaInfoUpdater> updater =
333 bc->Canonical()->GetMediaController()) {
334 updater->UpdateGuessedPositionState(bc->Id(), aMediaId, aState);
338 ContentMediaController::ContentMediaController(uint64_t aId) {
339 LOG("Create content media controller for BC %" PRId64, aId);
342 void ContentMediaController::AddReceiver(
343 ContentMediaControlKeyReceiver* aListener) {
344 MOZ_ASSERT(NS_IsMainThread());
345 mReceivers.AppendElement(aListener);
348 void ContentMediaController::RemoveReceiver(
349 ContentMediaControlKeyReceiver* aListener) {
350 MOZ_ASSERT(NS_IsMainThread());
351 mReceivers.RemoveElement(aListener);
354 void ContentMediaController::HandleMediaKey(MediaControlKey aKey,
355 Maybe<SeekDetails> aDetails) {
356 MOZ_ASSERT(NS_IsMainThread());
357 if (mReceivers.IsEmpty()) {
358 return;
360 LOG("Handle '%s' event, receiver num=%zu", GetEnumString(aKey).get(),
361 mReceivers.Length());
362 // We have default handlers for these actions
363 // https://w3c.github.io/mediasession/#ref-for-dom-mediasessionaction-play%E2%91%A3
364 switch (aKey) {
365 case MediaControlKey::Pause:
366 PauseOrStopMedia();
367 return;
368 case MediaControlKey::Play:
369 case MediaControlKey::Stop:
370 case MediaControlKey::Seekto:
371 case MediaControlKey::Seekforward:
372 case MediaControlKey::Seekbackward:
373 // When receiving `Stop`, the amount of receiver would vary during the
374 // iteration, so we use the backward iteration to avoid accessing the
375 // index which is over the array length.
376 for (auto& receiver : Reversed(mReceivers)) {
377 receiver->HandleMediaKey(aKey, aDetails);
379 return;
380 default:
381 MOZ_ASSERT_UNREACHABLE("Not supported media key for default handler");
385 void ContentMediaController::PauseOrStopMedia() {
386 // When receiving `pause`, if a page contains playing media and paused media
387 // at that moment, that means a user intends to pause those playing
388 // media, not the already paused ones. Then, we're going to stop those already
389 // paused media and keep those latest paused media in `mReceivers`.
390 // The reason for doing that is, when resuming paused media, we only want to
391 // resume latest paused media, not all media, in order to get a better user
392 // experience, which matches Chrome's behavior.
393 bool isAnyMediaPlaying = false;
394 for (const auto& receiver : mReceivers) {
395 if (receiver->IsPlaying()) {
396 isAnyMediaPlaying = true;
397 break;
401 for (auto& receiver : Reversed(mReceivers)) {
402 if (isAnyMediaPlaying && !receiver->IsPlaying()) {
403 receiver->HandleMediaKey(MediaControlKey::Stop);
404 } else {
405 receiver->HandleMediaKey(MediaControlKey::Pause);
410 } // namespace mozilla::dom