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