HTMLMediaElement.cpp (286257B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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/. */ 6 7 #include "mozilla/dom/HTMLMediaElement.h" 8 9 #include <algorithm> 10 #include <cmath> 11 #include <limits> 12 #include <type_traits> 13 14 #include "AudioDeviceInfo.h" 15 #include "AudioStreamTrack.h" 16 #include "AutoplayPolicy.h" 17 #include "ChannelMediaDecoder.h" 18 #include "CrossGraphPort.h" 19 #include "DOMMediaStream.h" 20 #include "DecoderDoctorDiagnostics.h" 21 #include "DecoderDoctorLogger.h" 22 #include "DecoderTraits.h" 23 #include "FrameStatistics.h" 24 #include "GMPCrashHelper.h" 25 #include "GVAutoplayPermissionRequest.h" 26 #include "nsString.h" 27 #ifdef MOZ_ANDROID_HLS_SUPPORT 28 # include "HLSDecoder.h" 29 #endif 30 #include "HTMLMediaElement.h" 31 #include "ImageContainer.h" 32 #include "MP4Decoder.h" 33 #include "MediaContainerType.h" 34 #include "MediaError.h" 35 #include "MediaManager.h" 36 #include "MediaMetadataManager.h" 37 #include "MediaProfilerMarkers.h" 38 #include "MediaResource.h" 39 #include "MediaShutdownManager.h" 40 #include "MediaSourceDecoder.h" 41 #include "MediaStreamError.h" 42 #include "MediaStreamWindowCapturer.h" 43 #include "MediaTrack.h" 44 #include "MediaTrackGraphImpl.h" 45 #include "MediaTrackList.h" 46 #include "MediaTrackListener.h" 47 #include "Navigator.h" 48 #include "ReferrerInfo.h" 49 #include "TimeRanges.h" 50 #include "TimeUnits.h" 51 #include "VideoFrameContainer.h" 52 #include "VideoOutput.h" 53 #include "VideoStreamTrack.h" 54 #include "base/basictypes.h" 55 #include "js/PropertyAndElement.h" // JS_DefineProperty 56 #include "jsapi.h" 57 #include "mozilla/AppShutdown.h" 58 #include "mozilla/AsyncEventDispatcher.h" 59 #include "mozilla/EMEUtils.h" 60 #include "mozilla/EventDispatcher.h" 61 #include "mozilla/MathAlgorithms.h" 62 #include "mozilla/MediaFragmentURIParser.h" 63 #include "mozilla/Preferences.h" 64 #include "mozilla/PresShell.h" 65 #include "mozilla/SVGObserverUtils.h" 66 #include "mozilla/SchedulerGroup.h" 67 #include "mozilla/ScopeExit.h" 68 #include "mozilla/Sprintf.h" 69 #include "mozilla/StaticPrefs_media.h" 70 #include "mozilla/dom/AncestorIterator.h" 71 #include "mozilla/dom/AudioTrack.h" 72 #include "mozilla/dom/AudioTrackList.h" 73 #include "mozilla/dom/BlobURLProtocolHandler.h" 74 #include "mozilla/dom/ContentMediaController.h" 75 #include "mozilla/dom/Document.h" 76 #include "mozilla/dom/ElementInlines.h" 77 #include "mozilla/dom/FeaturePolicyUtils.h" 78 #include "mozilla/dom/HTMLAudioElement.h" 79 #include "mozilla/dom/HTMLInputElement.h" 80 #include "mozilla/dom/HTMLMediaElementBinding.h" 81 #include "mozilla/dom/HTMLSourceElement.h" 82 #include "mozilla/dom/HTMLVideoElement.h" 83 #include "mozilla/dom/MediaControlUtils.h" 84 #include "mozilla/dom/MediaDevices.h" 85 #include "mozilla/dom/MediaEncryptedEvent.h" 86 #include "mozilla/dom/MediaErrorBinding.h" 87 #include "mozilla/dom/MediaSource.h" 88 #include "mozilla/dom/PlayPromise.h" 89 #include "mozilla/dom/Promise.h" 90 #include "mozilla/dom/TextTrack.h" 91 #include "mozilla/dom/UserActivation.h" 92 #include "mozilla/dom/VideoPlaybackQuality.h" 93 #include "mozilla/dom/VideoTrack.h" 94 #include "mozilla/dom/VideoTrackList.h" 95 #include "mozilla/dom/WakeLock.h" 96 #include "mozilla/dom/WindowGlobalChild.h" 97 #include "mozilla/dom/power/PowerManagerService.h" 98 #include "mozilla/glean/DomMediaMetrics.h" 99 #include "mozilla/net/UrlClassifierFeatureFactory.h" 100 #include "mozilla/nsVideoFrame.h" 101 #include "nsAttrValueInlines.h" 102 #include "nsAttrValueOrString.h" 103 #include "nsContentPolicyUtils.h" 104 #include "nsContentUtils.h" 105 #include "nsCycleCollectionParticipant.h" 106 #include "nsDisplayList.h" 107 #include "nsDocShell.h" 108 #include "nsError.h" 109 #include "nsGenericHTMLElement.h" 110 #include "nsGkAtoms.h" 111 #include "nsGlobalWindowInner.h" 112 #include "nsIAsyncVerifyRedirectCallback.h" 113 #include "nsICachingChannel.h" 114 #include "nsIClassOfService.h" 115 #include "nsIContentPolicy.h" 116 #include "nsIDocShell.h" 117 #include "nsIFrame.h" 118 #include "nsIHttpChannel.h" 119 #include "nsIObserverService.h" 120 #include "nsIRequest.h" 121 #include "nsIScriptError.h" 122 #include "nsISupportsPrimitives.h" 123 #include "nsIThreadRetargetableStreamListener.h" 124 #include "nsITimer.h" 125 #include "nsJSUtils.h" 126 #include "nsLayoutUtils.h" 127 #include "nsMimeTypes.h" 128 #include "nsNetUtil.h" 129 #include "nsNodeInfoManager.h" 130 #include "nsPresContext.h" 131 #include "nsQueryObject.h" 132 #include "nsRange.h" 133 #include "nsSize.h" 134 #include "nsThreadUtils.h" 135 #include "nsURIHashKey.h" 136 #include "nsURLHelper.h" 137 #ifdef XP_WIN 138 # include "objbase.h" 139 #endif 140 #include "xpcpublic.h" 141 142 mozilla::LazyLogModule gMediaElementLog("HTMLMediaElement"); 143 mozilla::LazyLogModule gMediaElementEventsLog("HTMLMediaElementEvents"); 144 145 extern mozilla::LazyLogModule gAutoplayPermissionLog; 146 #define AUTOPLAY_LOG(msg, ...) \ 147 MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) 148 149 // avoid redefined macro in unified build 150 #undef MEDIACONTROL_LOG 151 #define MEDIACONTROL_LOG(msg, ...) \ 152 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \ 153 ("HTMLMediaElement=%p, " msg, this, ##__VA_ARGS__)) 154 155 #undef CONTROLLER_TIMER_LOG 156 #define CONTROLLER_TIMER_LOG(element, msg, ...) \ 157 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \ 158 ("HTMLMediaElement=%p, " msg, element, ##__VA_ARGS__)) 159 160 #define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg) 161 #define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg) 162 163 using namespace mozilla::layers; 164 using namespace mozilla::dom::HTMLMediaElement_Binding; 165 166 namespace mozilla::dom { 167 168 using AudibleState = AudioChannelService::AudibleState; 169 using SinkInfoPromise = MediaDevices::SinkInfoPromise; 170 171 // Number of milliseconds between progress events as defined by spec 172 static const uint32_t PROGRESS_MS = 350; 173 174 // Number of milliseconds of no data before a stall event is fired as defined by 175 // spec 176 static const uint32_t STALL_MS = 3000; 177 178 // Used by AudioChannel for suppresssing the volume to this ratio. 179 #define FADED_VOLUME_RATIO 0.25 180 181 // These constants are arbitrary 182 // Minimum playbackRate for a media 183 static const double MIN_PLAYBACKRATE = 1.0 / 16; 184 // Maximum playbackRate for a media 185 static const double MAX_PLAYBACKRATE = 16.0; 186 187 static double ClampPlaybackRate(double aPlaybackRate) { 188 MOZ_ASSERT(aPlaybackRate >= 0.0); 189 MOZ_ASSERT(std::isfinite(aPlaybackRate)); 190 191 if (aPlaybackRate == 0.0) { 192 return aPlaybackRate; 193 } 194 if (aPlaybackRate < MIN_PLAYBACKRATE) { 195 return MIN_PLAYBACKRATE; 196 } 197 if (aPlaybackRate > MAX_PLAYBACKRATE) { 198 return MAX_PLAYBACKRATE; 199 } 200 return aPlaybackRate; 201 } 202 203 // Media error values. These need to match the ones in MediaError.webidl. 204 static const unsigned short MEDIA_ERR_ABORTED = 1; 205 static const unsigned short MEDIA_ERR_NETWORK = 2; 206 static const unsigned short MEDIA_ERR_DECODE = 3; 207 static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4; 208 209 /** 210 * EventBlocker helps media element to postpone the event delivery by storing 211 * the event runner, and execute them once media element decides not to postpone 212 * the event delivery. If media element never resumes the event delivery, then 213 * those runner would be cancelled. 214 * For example, we postpone the event delivery when media element entering to 215 * the bf-cache. 216 */ 217 class HTMLMediaElement::EventBlocker final : public nsISupports { 218 public: 219 NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL 220 NS_DECL_CYCLE_COLLECTION_CLASS(EventBlocker) 221 222 explicit EventBlocker(HTMLMediaElement* aElement) : mElement(aElement) {} 223 224 void SetBlockEventDelivery(bool aShouldBlock) { 225 MOZ_ASSERT(NS_IsMainThread()); 226 if (mShouldBlockEventDelivery == aShouldBlock) { 227 return; 228 } 229 LOG_EVENT(LogLevel::Debug, 230 ("%p %s event delivery", mElement.get(), 231 mShouldBlockEventDelivery ? "block" : "unblock")); 232 mShouldBlockEventDelivery = aShouldBlock; 233 if (!mShouldBlockEventDelivery) { 234 DispatchPendingMediaEvents(); 235 } 236 } 237 238 void PostponeEvent(nsMediaEventRunner* aRunner) { 239 MOZ_ASSERT(NS_IsMainThread()); 240 // Element has been CCed, which would break the weak pointer. 241 if (!mElement) { 242 return; 243 } 244 MOZ_ASSERT(mShouldBlockEventDelivery); 245 MOZ_ASSERT(mElement); 246 LOG_EVENT(LogLevel::Debug, 247 ("%p postpone runner %s for %s", mElement.get(), aRunner->Name(), 248 NS_ConvertUTF16toUTF8(aRunner->EventName()).get())); 249 mPendingEventRunners.AppendElement(aRunner); 250 } 251 252 void Shutdown() { 253 MOZ_ASSERT(NS_IsMainThread()); 254 for (auto& runner : mPendingEventRunners) { 255 runner->Cancel(); 256 } 257 mPendingEventRunners.Clear(); 258 } 259 260 bool ShouldBlockEventDelivery() const { 261 MOZ_ASSERT(NS_IsMainThread()); 262 return mShouldBlockEventDelivery; 263 } 264 265 size_t SizeOfExcludingThis(MallocSizeOf& aMallocSizeOf) const { 266 MOZ_ASSERT(NS_IsMainThread()); 267 size_t total = 0; 268 for (const auto& runner : mPendingEventRunners) { 269 total += aMallocSizeOf(runner); 270 } 271 return total; 272 } 273 274 private: 275 ~EventBlocker() = default; 276 277 void DispatchPendingMediaEvents() { 278 MOZ_ASSERT(mElement); 279 for (auto& runner : mPendingEventRunners) { 280 LOG_EVENT(LogLevel::Debug, 281 ("%p execute runner %s for %s", mElement.get(), runner->Name(), 282 NS_ConvertUTF16toUTF8(runner->EventName()).get())); 283 GetMainThreadSerialEventTarget()->Dispatch(runner.forget()); 284 } 285 mPendingEventRunners.Clear(); 286 } 287 288 WeakPtr<HTMLMediaElement> mElement; 289 bool mShouldBlockEventDelivery = false; 290 // Contains event runners which should not be run for now because we want 291 // to block all events delivery. They would be dispatched once media element 292 // decides unblocking them. 293 nsTArray<RefPtr<nsMediaEventRunner>> mPendingEventRunners; 294 }; 295 296 NS_IMPL_CYCLE_COLLECTION(HTMLMediaElement::EventBlocker, mPendingEventRunners) 297 NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::EventBlocker) 298 NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::EventBlocker) 299 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLMediaElement::EventBlocker) 300 NS_INTERFACE_MAP_ENTRY(nsISupports) 301 NS_INTERFACE_MAP_END 302 303 /** 304 * We use MediaControlKeyListener to listen to media control key in order to 305 * play and pause media element when user press media control keys and update 306 * media's playback and audible state to the media controller. 307 * 308 * Use `Start()` to start listening event and use `Stop()` to stop listening 309 * event. In addition, notifying any change to media controller MUST be done 310 * after successfully calling `Start()`. 311 */ 312 class HTMLMediaElement::MediaControlKeyListener final 313 : public ContentMediaControlKeyReceiver { 314 public: 315 NS_INLINE_DECL_REFCOUNTING(MediaControlKeyListener, override) 316 317 MOZ_INIT_OUTSIDE_CTOR explicit MediaControlKeyListener( 318 HTMLMediaElement* aElement) 319 : mElement(aElement), mElementId(nsID::GenerateUUID()) { 320 MOZ_ASSERT(NS_IsMainThread()); 321 MOZ_ASSERT(aElement); 322 } 323 324 /** 325 * Start listening to the media control keys which would make media being able 326 * to be controlled via pressing media control keys. 327 */ 328 void Start() { 329 MOZ_ASSERT(NS_IsMainThread()); 330 if (IsStarted()) { 331 // We have already been started, do not notify start twice. 332 return; 333 } 334 335 // Fail to init media agent, we are not able to notify the media controller 336 // any update and also are not able to receive media control key events. 337 if (!InitMediaAgent()) { 338 MEDIACONTROL_LOG("Failed to start due to not able to init media agent!"); 339 return; 340 } 341 342 NotifyPlaybackStateChanged(MediaPlaybackState::eStarted); 343 // If owner has started playing before the listener starts, we should update 344 // the playing state as well. Eg. media starts inaudily and becomes audible 345 // later. 346 if (!Owner()->Paused()) { 347 NotifyMediaStartedPlaying(); 348 } 349 if (StaticPrefs::media_mediacontrol_testingevents_enabled()) { 350 auto dispatcher = MakeRefPtr<AsyncEventDispatcher>( 351 Owner(), u"MozStartMediaControl"_ns, CanBubble::eYes, 352 ChromeOnlyDispatch::eYes); 353 dispatcher->PostDOMEvent(); 354 } 355 } 356 357 /** 358 * Stop listening to the media control keys which would make media not be able 359 * to be controlled via pressing media control keys. If we haven't started 360 * listening to the media control keys, then nothing would happen. 361 */ 362 void StopIfNeeded() { 363 MOZ_ASSERT(NS_IsMainThread()); 364 if (!IsStarted()) { 365 // We have already been stopped, do not notify stop twice. 366 return; 367 } 368 NotifyMediaStoppedPlaying(); 369 NotifyPlaybackStateChanged(MediaPlaybackState::eStopped); 370 371 // Remove ourselves from media agent, which would stop receiving event. 372 mControlAgent->RemoveReceiver(this); 373 mControlAgent = nullptr; 374 } 375 376 bool IsStarted() const { return mState != MediaPlaybackState::eStopped; } 377 378 bool IsPlaying() const override { 379 return Owner() ? !Owner()->Paused() : false; 380 } 381 382 /** 383 * Following methods should only be used after starting listener. 384 */ 385 void NotifyMediaStartedPlaying() { 386 MOZ_ASSERT(NS_IsMainThread()); 387 MOZ_ASSERT(IsStarted()); 388 if (mState == MediaPlaybackState::eStarted || 389 mState == MediaPlaybackState::ePaused) { 390 NotifyPlaybackStateChanged(MediaPlaybackState::ePlayed); 391 // If media is `inaudible` in the beginning, then we don't need to notify 392 // the state, because notifying `inaudible` should always come after 393 // notifying `audible`. 394 if (mIsOwnerAudible) { 395 NotifyAudibleStateChanged(MediaAudibleState::eAudible); 396 } 397 } 398 } 399 400 void NotifyMediaStoppedPlaying() { 401 MOZ_ASSERT(NS_IsMainThread()); 402 MOZ_ASSERT(IsStarted()); 403 if (mState == MediaPlaybackState::ePlayed) { 404 NotifyPlaybackStateChanged(MediaPlaybackState::ePaused); 405 // As media are going to be paused, so no sound is possible to be heard. 406 if (mIsOwnerAudible) { 407 NotifyAudibleStateChanged(MediaAudibleState::eInaudible); 408 } 409 } 410 } 411 412 void NotifyMediaPositionState() { 413 if (!IsStarted()) { 414 return; 415 } 416 417 MOZ_ASSERT(mControlAgent); 418 auto* owner = Owner(); 419 PositionState state(owner->Duration(), 420 owner->Paused() ? 0.0 : owner->PlaybackRate(), 421 owner->CurrentTime(), TimeStamp::Now()); 422 MEDIACONTROL_LOG( 423 "Notify media position state (duration=%f, playbackRate=%f, " 424 "position=%f)", 425 state.mDuration, state.mPlaybackRate, 426 state.mLastReportedPlaybackPosition); 427 mControlAgent->UpdateGuessedPositionState(mOwnerBrowsingContextId, 428 mElementId, Some(state)); 429 } 430 431 void Shutdown() { 432 StopIfNeeded(); 433 if (!mControlAgent) { 434 return; 435 } 436 mControlAgent->UpdateGuessedPositionState(mOwnerBrowsingContextId, 437 mElementId, Nothing()); 438 } 439 440 // This method can be called before the listener starts, which would cache 441 // the audible state and update after the listener starts. 442 void UpdateMediaAudibleState(bool aIsOwnerAudible) { 443 MOZ_ASSERT(NS_IsMainThread()); 444 if (mIsOwnerAudible == aIsOwnerAudible) { 445 return; 446 } 447 mIsOwnerAudible = aIsOwnerAudible; 448 MEDIACONTROL_LOG("Media becomes %s", 449 mIsOwnerAudible ? "audible" : "inaudible"); 450 // If media hasn't started playing, it doesn't make sense to update media 451 // audible state. Therefore, in that case we would noitfy the audible state 452 // when media starts playing. 453 if (mState == MediaPlaybackState::ePlayed) { 454 NotifyAudibleStateChanged(mIsOwnerAudible 455 ? MediaAudibleState::eAudible 456 : MediaAudibleState::eInaudible); 457 } 458 } 459 460 void SetPictureInPictureModeEnabled(bool aIsEnabled) { 461 MOZ_ASSERT(NS_IsMainThread()); 462 if (mIsPictureInPictureEnabled == aIsEnabled) { 463 return; 464 } 465 // PIP state changes might happen before the listener starts or stops where 466 // we haven't call `InitMediaAgent()` yet. Eg. Reset the PIP video's src, 467 // then cancel the PIP. In addition, not like playback and audible state 468 // which should be restricted to update via the same agent in order to keep 469 // those states correct in each `ContextMediaInfo`, PIP state can be updated 470 // through any browsing context, so we would use `ContentMediaAgent::Get()` 471 // directly to update PIP state. 472 mIsPictureInPictureEnabled = aIsEnabled; 473 if (RefPtr<IMediaInfoUpdater> updater = 474 ContentMediaAgent::Get(GetCurrentBrowsingContext())) { 475 updater->SetIsInPictureInPictureMode(mOwnerBrowsingContextId, 476 mIsPictureInPictureEnabled); 477 } 478 } 479 480 void HandleMediaKey(MediaControlKey aKey, 481 Maybe<SeekDetails> aDetails) override { 482 MOZ_ASSERT(NS_IsMainThread()); 483 MOZ_ASSERT(IsStarted()); 484 MEDIACONTROL_LOG("HandleEvent '%s'", GetEnumString(aKey).get()); 485 switch (aKey) { 486 case MediaControlKey::Play: 487 Owner()->Play(); 488 break; 489 case MediaControlKey::Pause: 490 Owner()->Pause(); 491 break; 492 case MediaControlKey::Stop: 493 Owner()->Pause(); 494 StopIfNeeded(); 495 break; 496 case MediaControlKey::Seekto: 497 MOZ_ASSERT(aDetails->mAbsolute); 498 if (aDetails->mAbsolute->mFastSeek) { 499 Owner()->FastSeek(aDetails->mAbsolute->mSeekTime, IgnoreErrors()); 500 } else { 501 Owner()->SetCurrentTime(aDetails->mAbsolute->mSeekTime); 502 } 503 break; 504 case MediaControlKey::Seekforward: 505 MOZ_ASSERT(aDetails->mRelativeSeekOffset); 506 Owner()->SetCurrentTime(Owner()->CurrentTime() + 507 aDetails->mRelativeSeekOffset.value()); 508 break; 509 case MediaControlKey::Seekbackward: 510 MOZ_ASSERT(aDetails->mRelativeSeekOffset); 511 Owner()->SetCurrentTime(Owner()->CurrentTime() - 512 aDetails->mRelativeSeekOffset.value()); 513 break; 514 default: 515 MOZ_ASSERT_UNREACHABLE( 516 "Unsupported media control key for media element!"); 517 } 518 } 519 520 void UpdateOwnerBrowsingContextIfNeeded() { 521 // Has not notified any information about the owner context yet. 522 if (!IsStarted()) { 523 return; 524 } 525 526 BrowsingContext* currentBC = GetCurrentBrowsingContext(); 527 MOZ_ASSERT(currentBC); 528 // Still in the same browsing context, no need to update. 529 if (currentBC->Id() == mOwnerBrowsingContextId) { 530 return; 531 } 532 MEDIACONTROL_LOG("Change browsing context from %" PRIu64 " to %" PRIu64, 533 mOwnerBrowsingContextId, currentBC->Id()); 534 // This situation would happen when we start a media in an original browsing 535 // context, then we move it to another browsing context, such as an iframe, 536 // so its owner browsing context would be changed. Therefore, we should 537 // reset the media status for the previous browsing context by calling 538 // `Stop()`, in which the listener would notify `ePaused` (if it's playing) 539 // and `eStop`. Then calls `Start()`, in which the listener would notify 540 // `eStart` to the new browsing context. If the media was playing before, 541 // we would also notify `ePlayed`. 542 bool wasInPlayingState = mState == MediaPlaybackState::ePlayed; 543 StopIfNeeded(); 544 Start(); 545 if (wasInPlayingState) { 546 NotifyMediaStartedPlaying(); 547 } 548 } 549 550 private: 551 ~MediaControlKeyListener() = default; 552 553 // The media can be moved around different browsing contexts, so this context 554 // might be different from the one that we used to initialize 555 // `ContentMediaAgent`. 556 BrowsingContext* GetCurrentBrowsingContext() const { 557 // Owner has been CCed, which would break the link of the weaker pointer. 558 if (!Owner()) { 559 return nullptr; 560 } 561 nsPIDOMWindowInner* window = Owner()->OwnerDoc()->GetInnerWindow(); 562 return window ? window->GetBrowsingContext() : nullptr; 563 } 564 565 bool InitMediaAgent() { 566 MOZ_ASSERT(NS_IsMainThread()); 567 BrowsingContext* currentBC = GetCurrentBrowsingContext(); 568 mControlAgent = ContentMediaAgent::Get(currentBC); 569 if (!mControlAgent) { 570 return false; 571 } 572 MOZ_ASSERT(currentBC); 573 mOwnerBrowsingContextId = currentBC->Id(); 574 MEDIACONTROL_LOG("Init agent in browsing context %" PRIu64, 575 mOwnerBrowsingContextId); 576 mControlAgent->AddReceiver(this); 577 return true; 578 } 579 580 HTMLMediaElement* Owner() const { 581 // `mElement` would be clear during CC unlinked, but it would only happen 582 // after stopping the listener. 583 MOZ_ASSERT(mElement || !IsStarted()); 584 return mElement.get(); 585 } 586 587 void NotifyPlaybackStateChanged(MediaPlaybackState aState) { 588 MOZ_ASSERT(NS_IsMainThread()); 589 MOZ_ASSERT(mControlAgent); 590 MEDIACONTROL_LOG("NotifyMediaState from state='%s' to state='%s'", 591 dom::EnumValueToString(mState), 592 dom::EnumValueToString(aState)); 593 MOZ_ASSERT(mState != aState, "Should not notify same state again!"); 594 mState = aState; 595 mControlAgent->NotifyMediaPlaybackChanged(mOwnerBrowsingContextId, mState); 596 597 if (aState == MediaPlaybackState::ePlayed || 598 aState == MediaPlaybackState::ePaused) { 599 NotifyMediaPositionState(); 600 } 601 } 602 603 void NotifyAudibleStateChanged(MediaAudibleState aState) { 604 MOZ_ASSERT(NS_IsMainThread()); 605 MOZ_ASSERT(IsStarted()); 606 mControlAgent->NotifyMediaAudibleChanged(mOwnerBrowsingContextId, aState); 607 } 608 609 MediaPlaybackState mState = MediaPlaybackState::eStopped; 610 WeakPtr<HTMLMediaElement> mElement; 611 RefPtr<ContentMediaAgent> mControlAgent; 612 bool mIsPictureInPictureEnabled = false; 613 bool mIsOwnerAudible = false; 614 MOZ_INIT_OUTSIDE_CTOR uint64_t mOwnerBrowsingContextId; 615 const nsID mElementId; 616 }; 617 618 class HTMLMediaElement::MediaStreamTrackListener 619 : public DOMMediaStream::TrackListener { 620 public: 621 explicit MediaStreamTrackListener(HTMLMediaElement* aElement) 622 : mElement(aElement) {} 623 624 NS_DECL_ISUPPORTS_INHERITED 625 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamTrackListener, 626 DOMMediaStream::TrackListener) 627 628 void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override { 629 if (!mElement) { 630 return; 631 } 632 mElement->NotifyMediaStreamTrackAdded(aTrack); 633 } 634 635 void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override { 636 if (!mElement) { 637 return; 638 } 639 mElement->NotifyMediaStreamTrackRemoved(aTrack); 640 } 641 642 void OnActive() { 643 MOZ_ASSERT(mElement); 644 645 // mediacapture-main says: 646 // Note that once ended equals true the HTMLVideoElement will not play media 647 // even if new MediaStreamTracks are added to the MediaStream (causing it to 648 // return to the active state) unless autoplay is true or the web 649 // application restarts the element, e.g., by calling play(). 650 // 651 // This is vague on exactly how to go from becoming active to playing, when 652 // autoplaying. However, per the media element spec, to play an autoplaying 653 // media element, we must load the source and reach readyState 654 // HAVE_ENOUGH_DATA [1]. Hence, a MediaStream being assigned to a media 655 // element and becoming active runs the load algorithm, so that it can 656 // eventually be played. 657 // 658 // [1] 659 // https://html.spec.whatwg.org/multipage/media.html#ready-states:event-media-play 660 661 LOG(LogLevel::Debug, ("%p, mSrcStream %p became active, checking if we " 662 "need to run the load algorithm", 663 mElement.get(), mElement->mSrcStream.get())); 664 if (!mElement->IsPlaybackEnded()) { 665 return; 666 } 667 if (!mElement->Autoplay()) { 668 return; 669 } 670 LOG(LogLevel::Info, ("%p, mSrcStream %p became active on autoplaying, " 671 "ended element. Reloading.", 672 mElement.get(), mElement->mSrcStream.get())); 673 mElement->DoLoad(); 674 } 675 676 void NotifyActive() override { 677 if (!mElement) { 678 return; 679 } 680 681 if (!mElement->IsVideo()) { 682 // Audio elements use NotifyAudible(). 683 return; 684 } 685 686 OnActive(); 687 } 688 689 void NotifyAudible() override { 690 if (!mElement) { 691 return; 692 } 693 694 if (mElement->IsVideo()) { 695 // Video elements use NotifyActive(). 696 return; 697 } 698 699 OnActive(); 700 } 701 702 void OnInactive() { 703 MOZ_ASSERT(mElement); 704 705 if (mElement->IsPlaybackEnded()) { 706 return; 707 } 708 LOG(LogLevel::Debug, ("%p, mSrcStream %p became inactive", mElement.get(), 709 mElement->mSrcStream.get())); 710 711 mElement->PlaybackEnded(); 712 } 713 714 void NotifyInactive() override { 715 if (!mElement) { 716 return; 717 } 718 719 if (!mElement->IsVideo()) { 720 // Audio elements use NotifyInaudible(). 721 return; 722 } 723 724 OnInactive(); 725 } 726 727 void NotifyInaudible() override { 728 if (!mElement) { 729 return; 730 } 731 732 if (mElement->IsVideo()) { 733 // Video elements use NotifyInactive(). 734 return; 735 } 736 737 OnInactive(); 738 } 739 740 protected: 741 virtual ~MediaStreamTrackListener() = default; 742 RefPtr<HTMLMediaElement> mElement; 743 }; 744 745 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::MediaStreamTrackListener, 746 DOMMediaStream::TrackListener, mElement) 747 NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::MediaStreamTrackListener, 748 DOMMediaStream::TrackListener) 749 NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::MediaStreamTrackListener, 750 DOMMediaStream::TrackListener) 751 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( 752 HTMLMediaElement::MediaStreamTrackListener) 753 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream::TrackListener) 754 755 /** 756 * Helper class that manages audio and video outputs for all enabled tracks in a 757 * media element. It also manages calculating the current time when playing a 758 * MediaStream. 759 */ 760 class HTMLMediaElement::MediaStreamRenderer { 761 public: 762 NS_INLINE_DECL_REFCOUNTING(MediaStreamRenderer) 763 764 MediaStreamRenderer(AbstractThread* aMainThread, 765 VideoFrameContainer* aVideoContainer, 766 FirstFrameVideoOutput* aFirstFrameVideoOutput, 767 void* aAudioOutputKey) 768 : mVideoContainer(aVideoContainer), 769 mAudioOutputKey(aAudioOutputKey), 770 mWatchManager(this, aMainThread), 771 mFirstFrameVideoOutput(aFirstFrameVideoOutput) { 772 if (mFirstFrameVideoOutput) { 773 mWatchManager.Watch(mFirstFrameVideoOutput->mFirstFrameRendered, 774 &MediaStreamRenderer::SetFirstFrameRendered); 775 } 776 } 777 778 void Shutdown() { 779 for (const auto& t : mAudioTracks.Clone()) { 780 if (t) { 781 RemoveTrack(t->AsAudioStreamTrack()); 782 } 783 } 784 if (mVideoTrack) { 785 RemoveTrack(mVideoTrack->AsVideoStreamTrack()); 786 } 787 mWatchManager.Shutdown(); 788 mFirstFrameVideoOutput = nullptr; 789 } 790 791 void UpdateGraphTime() { 792 mGraphTime = 793 mGraphTimeDummy->mTrack->Graph()->CurrentTime() - *mGraphTimeOffset; 794 } 795 796 void SetFirstFrameRendered() { 797 if (!mFirstFrameVideoOutput) { 798 return; 799 } 800 if (mVideoTrack) { 801 mVideoTrack->AsVideoStreamTrack()->RemoveVideoOutput( 802 mFirstFrameVideoOutput); 803 } 804 mWatchManager.Unwatch(mFirstFrameVideoOutput->mFirstFrameRendered, 805 &MediaStreamRenderer::SetFirstFrameRendered); 806 mFirstFrameVideoOutput = nullptr; 807 } 808 809 void SetProgressingCurrentTime(bool aProgress) { 810 if (aProgress == mProgressingCurrentTime) { 811 return; 812 } 813 814 MOZ_DIAGNOSTIC_ASSERT(mGraphTimeDummy); 815 mProgressingCurrentTime = aProgress; 816 MediaTrackGraph* graph = mGraphTimeDummy->mTrack->Graph(); 817 if (mProgressingCurrentTime) { 818 mGraphTimeOffset = Some(graph->CurrentTime().Ref() - mGraphTime); 819 mWatchManager.Watch(graph->CurrentTime(), 820 &MediaStreamRenderer::UpdateGraphTime); 821 } else { 822 mWatchManager.Unwatch(graph->CurrentTime(), 823 &MediaStreamRenderer::UpdateGraphTime); 824 } 825 } 826 827 void Start() { 828 if (mRendering) { 829 return; 830 } 831 832 LOG(LogLevel::Info, ("MediaStreamRenderer=%p Start", this)); 833 mRendering = true; 834 835 if (!mGraphTimeDummy) { 836 return; 837 } 838 839 for (const auto& t : mAudioTracks) { 840 if (t) { 841 t->AsAudioStreamTrack()->AddAudioOutput(mAudioOutputKey, 842 mAudioOutputSink); 843 t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey, 844 mAudioOutputVolume); 845 } 846 } 847 848 if (mVideoTrack) { 849 mVideoTrack->AsVideoStreamTrack()->AddVideoOutput(mVideoContainer); 850 } 851 } 852 853 void Stop() { 854 if (!mRendering) { 855 return; 856 } 857 858 LOG(LogLevel::Info, ("MediaStreamRenderer=%p Stop", this)); 859 mRendering = false; 860 861 if (!mGraphTimeDummy) { 862 return; 863 } 864 865 for (const auto& t : mAudioTracks) { 866 if (t) { 867 t->AsAudioStreamTrack()->RemoveAudioOutput(mAudioOutputKey); 868 } 869 } 870 // There is no longer an audio output that needs the device so the 871 // device may not start. Ensure the promise is resolved. 872 ResolveAudioDevicePromiseIfExists(__func__); 873 874 if (mVideoTrack) { 875 mVideoTrack->AsVideoStreamTrack()->RemoveVideoOutput(mVideoContainer); 876 } 877 } 878 879 void SetAudioOutputVolume(float aVolume) { 880 if (mAudioOutputVolume == aVolume) { 881 return; 882 } 883 mAudioOutputVolume = aVolume; 884 if (!mRendering) { 885 return; 886 } 887 for (const auto& t : mAudioTracks) { 888 if (t) { 889 t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey, 890 mAudioOutputVolume); 891 } 892 } 893 } 894 895 RefPtr<GenericPromise> SetAudioOutputDevice(AudioDeviceInfo* aSink) { 896 MOZ_ASSERT(aSink); 897 MOZ_ASSERT(mAudioOutputSink != aSink); 898 LOG(LogLevel::Info, 899 ("MediaStreamRenderer=%p SetAudioOutputDevice name=%s\n", this, 900 NS_ConvertUTF16toUTF8(aSink->Name()).get())); 901 902 mAudioOutputSink = aSink; 903 904 if (!mRendering) { 905 MOZ_ASSERT(mSetAudioDevicePromise.IsEmpty()); 906 return GenericPromise::CreateAndResolve(true, __func__); 907 } 908 909 nsTArray<RefPtr<GenericPromise>> promises; 910 for (const auto& t : mAudioTracks) { 911 t->AsAudioStreamTrack()->RemoveAudioOutput(mAudioOutputKey); 912 promises.AppendElement(t->AsAudioStreamTrack()->AddAudioOutput( 913 mAudioOutputKey, mAudioOutputSink)); 914 t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey, 915 mAudioOutputVolume); 916 } 917 if (!promises.Length()) { 918 // Not active track, save it for later 919 MOZ_ASSERT(mSetAudioDevicePromise.IsEmpty()); 920 return GenericPromise::CreateAndResolve(true, __func__); 921 } 922 923 // Resolve any existing promise for a previous device so that promises 924 // resolve in order of setSinkId() invocation. 925 ResolveAudioDevicePromiseIfExists(__func__); 926 927 RefPtr promise = mSetAudioDevicePromise.Ensure(__func__); 928 GenericPromise::AllSettled(GetCurrentSerialEventTarget(), promises) 929 ->Then(GetMainThreadSerialEventTarget(), __func__, 930 [self = RefPtr{this}, 931 this](const GenericPromise::AllSettledPromiseType:: 932 ResolveOrRejectValue& aValue) { 933 // This handler should have been disconnected if 934 // mSetAudioDevicePromise has been settled. 935 MOZ_ASSERT(!mSetAudioDevicePromise.IsEmpty()); 936 mDeviceStartedRequest.Complete(); 937 // The AudioStreamTrack::AddAudioOutput() promise is rejected 938 // either when the graph no longer needs the device, in which 939 // case this handler would have already been disconnected, or 940 // the graph is force shutdown. 941 // mSetAudioDevicePromise is resolved regardless of whether 942 // the AddAudioOutput() promises resolve or reject because 943 // the underlying device has been changed. 944 LOG(LogLevel::Info, 945 ("MediaStreamRenderer=%p SetAudioOutputDevice settled", 946 this)); 947 mSetAudioDevicePromise.Resolve(true, __func__); 948 }) 949 ->Track(mDeviceStartedRequest); 950 951 return promise; 952 } 953 954 void AddTrack(AudioStreamTrack* aTrack) { 955 MOZ_DIAGNOSTIC_ASSERT(!mAudioTracks.Contains(aTrack)); 956 mAudioTracks.AppendElement(aTrack); 957 EnsureGraphTimeDummy(); 958 if (mRendering) { 959 aTrack->AddAudioOutput(mAudioOutputKey, mAudioOutputSink); 960 aTrack->SetAudioOutputVolume(mAudioOutputKey, mAudioOutputVolume); 961 } 962 } 963 void AddTrack(VideoStreamTrack* aTrack) { 964 MOZ_DIAGNOSTIC_ASSERT(!mVideoTrack); 965 if (!mVideoContainer) { 966 return; 967 } 968 mVideoTrack = aTrack; 969 EnsureGraphTimeDummy(); 970 if (mFirstFrameVideoOutput) { 971 // Add the first frame output even if we are rendering. It will only 972 // accept one frame. If we are rendering, then the main output will 973 // overwrite that with the same frame (and possibly more frames). 974 aTrack->AddVideoOutput(mFirstFrameVideoOutput); 975 } 976 if (mRendering) { 977 aTrack->AddVideoOutput(mVideoContainer); 978 } 979 } 980 981 void RemoveTrack(AudioStreamTrack* aTrack) { 982 MOZ_DIAGNOSTIC_ASSERT(mAudioTracks.Contains(aTrack)); 983 if (mRendering) { 984 aTrack->RemoveAudioOutput(mAudioOutputKey); 985 } 986 mAudioTracks.RemoveElement(aTrack); 987 988 if (mAudioTracks.IsEmpty()) { 989 // There is no longer an audio output that needs the device so the 990 // device may not start. Ensure the promise is resolved. 991 ResolveAudioDevicePromiseIfExists(__func__); 992 } 993 } 994 void RemoveTrack(VideoStreamTrack* aTrack) { 995 MOZ_DIAGNOSTIC_ASSERT(mVideoTrack == aTrack); 996 if (!mVideoContainer) { 997 return; 998 } 999 if (mFirstFrameVideoOutput) { 1000 aTrack->RemoveVideoOutput(mFirstFrameVideoOutput); 1001 } 1002 if (mRendering) { 1003 aTrack->RemoveVideoOutput(mVideoContainer); 1004 } 1005 mVideoTrack = nullptr; 1006 } 1007 1008 double CurrentTime() const { 1009 if (!mGraphTimeDummy) { 1010 return 0.0; 1011 } 1012 1013 return mGraphTimeDummy->mTrack->GraphImpl()->MediaTimeToSeconds(mGraphTime); 1014 } 1015 1016 Watchable<GraphTime>& CurrentGraphTime() { return mGraphTime; } 1017 1018 // Set if we're rendering video. 1019 const RefPtr<VideoFrameContainer> mVideoContainer; 1020 1021 // Set if we're rendering audio, nullptr otherwise. 1022 void* const mAudioOutputKey; 1023 1024 private: 1025 ~MediaStreamRenderer() { Shutdown(); } 1026 1027 void EnsureGraphTimeDummy() { 1028 if (mGraphTimeDummy) { 1029 return; 1030 } 1031 1032 MediaTrackGraph* graph = nullptr; 1033 for (const auto& t : mAudioTracks) { 1034 if (t && !t->Ended()) { 1035 graph = t->Graph(); 1036 break; 1037 } 1038 } 1039 1040 if (!graph && mVideoTrack && !mVideoTrack->Ended()) { 1041 graph = mVideoTrack->Graph(); 1042 } 1043 1044 if (!graph) { 1045 return; 1046 } 1047 1048 // This dummy keeps `graph` alive and ensures access to it. 1049 mGraphTimeDummy = MakeRefPtr<SharedDummyTrack>( 1050 graph->CreateSourceTrack(MediaSegment::AUDIO)); 1051 } 1052 1053 void ResolveAudioDevicePromiseIfExists(StaticString aMethodName) { 1054 if (mSetAudioDevicePromise.IsEmpty()) { 1055 return; 1056 } 1057 LOG(LogLevel::Info, 1058 ("MediaStreamRenderer=%p resolve audio device promise", this)); 1059 mSetAudioDevicePromise.Resolve(true, aMethodName); 1060 mDeviceStartedRequest.Disconnect(); 1061 } 1062 1063 // True when all tracks are being rendered, i.e., when the media element is 1064 // playing. 1065 bool mRendering = false; 1066 1067 // True while we're progressing mGraphTime. False otherwise. 1068 bool mProgressingCurrentTime = false; 1069 1070 // The audio output volume for all audio tracks. 1071 float mAudioOutputVolume = 1.0f; 1072 1073 // The sink device for all audio tracks. 1074 RefPtr<AudioDeviceInfo> mAudioOutputSink; 1075 // The promise returned from SetAudioOutputDevice() when an output is 1076 // active. 1077 MozPromiseHolder<GenericPromise> mSetAudioDevicePromise; 1078 // Request tracking the promise to indicate when the device passed to 1079 // SetAudioOutputDevice() is running. 1080 MozPromiseRequestHolder<GenericPromise::AllSettledPromiseType> 1081 mDeviceStartedRequest; 1082 1083 // WatchManager for mGraphTime. 1084 WatchManager<MediaStreamRenderer> mWatchManager; 1085 1086 // A dummy MediaTrack to guarantee a MediaTrackGraph is kept alive while 1087 // we're actively rendering, so we can track the graph's current time. Set 1088 // when the first track is added, never unset. 1089 RefPtr<SharedDummyTrack> mGraphTimeDummy; 1090 1091 // Watchable that relays the graph's currentTime updates to the media element 1092 // only while we're rendering. This is the current time of the rendering in 1093 // GraphTime units. 1094 Watchable<GraphTime> mGraphTime = {0, "MediaStreamRenderer::mGraphTime"}; 1095 1096 // Nothing until a track has been added. Then, the current GraphTime at the 1097 // time when we were last Start()ed. 1098 Maybe<GraphTime> mGraphTimeOffset; 1099 1100 // Currently enabled (and rendered) audio tracks. 1101 nsTArray<WeakPtr<MediaStreamTrack>> mAudioTracks; 1102 1103 // Currently selected (and rendered) video track. 1104 WeakPtr<MediaStreamTrack> mVideoTrack; 1105 1106 // Holds a reference to the first-frame-getting video output attached to 1107 // mVideoTrack. Set by the constructor, unset when the media element tells us 1108 // it has rendered the first frame. 1109 RefPtr<FirstFrameVideoOutput> mFirstFrameVideoOutput; 1110 }; 1111 1112 static uint32_t sDecoderCaptureSourceId = 0; 1113 static uint32_t sStreamCaptureSourceId = 0; 1114 class HTMLMediaElement::MediaElementTrackSource 1115 : public MediaStreamTrackSource, 1116 public MediaStreamTrackSource::Sink, 1117 public MediaStreamTrackConsumer { 1118 public: 1119 NS_DECL_ISUPPORTS_INHERITED 1120 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaElementTrackSource, 1121 MediaStreamTrackSource) 1122 1123 /* MediaDecoder track source */ 1124 MediaElementTrackSource(HTMLMediaElement* aOwner, ProcessedMediaTrack* aTrack, 1125 nsIPrincipal* aPrincipal, OutputMuteState aMuteState, 1126 bool aHasAlpha) 1127 : MediaStreamTrackSource( 1128 aPrincipal, nsString(), 1129 TrackingId(TrackingId::Source::MediaElementDecoder, 1130 sDecoderCaptureSourceId++, 1131 TrackingId::TrackAcrossProcesses::Yes)), 1132 mOwner(aOwner), 1133 mTrack(aTrack), 1134 mIntendedElementMuteState(aMuteState), 1135 mElementMuteState(aMuteState), 1136 mMediaDecoderHasAlpha(Some(aHasAlpha)) { 1137 MOZ_ASSERT(mTrack); 1138 } 1139 1140 /* MediaStream track source */ 1141 MediaElementTrackSource(HTMLMediaElement* aOwner, 1142 MediaStreamTrack* aCapturedTrack, 1143 MediaStreamTrackSource* aCapturedTrackSource, 1144 ProcessedMediaTrack* aTrack, MediaInputPort* aPort, 1145 OutputMuteState aMuteState) 1146 : MediaStreamTrackSource( 1147 aCapturedTrackSource->GetPrincipal(), nsString(), 1148 TrackingId(TrackingId::Source::MediaElementStream, 1149 sStreamCaptureSourceId++, 1150 TrackingId::TrackAcrossProcesses::Yes)), 1151 mOwner(aOwner), 1152 mCapturedTrack(aCapturedTrack), 1153 mCapturedTrackSource(aCapturedTrackSource), 1154 mTrack(aTrack), 1155 mPort(aPort), 1156 mIntendedElementMuteState(aMuteState), 1157 mElementMuteState(aMuteState) { 1158 MOZ_ASSERT(mTrack); 1159 MOZ_ASSERT(mCapturedTrack); 1160 MOZ_ASSERT(mCapturedTrackSource); 1161 MOZ_ASSERT(mPort); 1162 1163 mCapturedTrack->AddConsumer(this); 1164 mCapturedTrackSource->RegisterSink(this); 1165 } 1166 1167 void SetEnabled(bool aEnabled) { 1168 if (!mTrack) { 1169 return; 1170 } 1171 mTrack->SetDisabledTrackMode(aEnabled ? DisabledTrackMode::ENABLED 1172 : DisabledTrackMode::SILENCE_FREEZE); 1173 } 1174 1175 void SetPrincipal(RefPtr<nsIPrincipal> aPrincipal) { 1176 mPrincipal = std::move(aPrincipal); 1177 MediaStreamTrackSource::PrincipalChanged(); 1178 } 1179 1180 void SetMutedByElement(OutputMuteState aMuteState) { 1181 if (mIntendedElementMuteState == aMuteState) { 1182 return; 1183 } 1184 mIntendedElementMuteState = aMuteState; 1185 GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( 1186 "MediaElementTrackSource::SetMutedByElement", 1187 [self = RefPtr<MediaElementTrackSource>(this), this, aMuteState] { 1188 mElementMuteState = aMuteState; 1189 MediaStreamTrackSource::MutedChanged(Muted()); 1190 })); 1191 } 1192 1193 void Destroy() override { 1194 if (mCapturedTrack) { 1195 mCapturedTrack->RemoveConsumer(this); 1196 mCapturedTrack = nullptr; 1197 } 1198 if (mCapturedTrackSource) { 1199 mCapturedTrackSource->UnregisterSink(this); 1200 mCapturedTrackSource = nullptr; 1201 } 1202 if (mTrack && !mTrack->IsDestroyed()) { 1203 mTrack->Destroy(); 1204 } 1205 if (mPort) { 1206 mPort->Destroy(); 1207 mPort = nullptr; 1208 } 1209 } 1210 1211 MediaSourceEnum GetMediaSource() const override { 1212 return MediaSourceEnum::Other; 1213 } 1214 1215 void Stop() override { 1216 // Do nothing. There may appear new output streams 1217 // that need tracks sourced from this source, so we 1218 // cannot destroy things yet. 1219 } 1220 1221 /** 1222 * Do not keep the track source alive. The source lifetime is controlled by 1223 * its associated tracks. 1224 */ 1225 bool KeepsSourceAlive() const override { return false; } 1226 1227 /** 1228 * Do not keep the track source on. It is controlled by its associated tracks. 1229 */ 1230 bool Enabled() const override { return false; } 1231 1232 void Disable() override {} 1233 1234 void Enable() override {} 1235 1236 void PrincipalChanged() override { 1237 if (!mCapturedTrackSource) { 1238 // This could happen during shutdown. 1239 return; 1240 } 1241 1242 SetPrincipal(mCapturedTrackSource->GetPrincipal()); 1243 } 1244 1245 void MutedChanged(bool aNewState) override { 1246 MediaStreamTrackSource::MutedChanged(Muted()); 1247 } 1248 1249 void ConstraintsChanged(const MediaTrackConstraints& aConstraints) override {} 1250 1251 void OverrideEnded() override { 1252 Destroy(); 1253 MediaStreamTrackSource::OverrideEnded(); 1254 } 1255 1256 void NotifyEnabledChanged(MediaStreamTrack* aTrack, bool aEnabled) override { 1257 MediaStreamTrackSource::MutedChanged(Muted()); 1258 } 1259 1260 bool Muted() const { 1261 return mElementMuteState == OutputMuteState::Muted || 1262 (mCapturedTrack && 1263 (mCapturedTrack->Muted() || !mCapturedTrack->Enabled())); 1264 } 1265 1266 bool HasAlpha() const override { 1267 if (mCapturedTrack) { 1268 return mCapturedTrack->AsVideoStreamTrack() 1269 ? mCapturedTrack->AsVideoStreamTrack()->HasAlpha() 1270 : false; 1271 } 1272 return mMediaDecoderHasAlpha.valueOr(false); 1273 } 1274 1275 void GetSettings(dom::MediaTrackSettings& aResult) override { 1276 if (!mOwner) { 1277 return; 1278 } 1279 1280 auto* elem = mOwner->AsHTMLVideoElement(); 1281 if (!elem) { 1282 return; 1283 } 1284 1285 aResult.mWidth.Construct(elem->VideoWidth()); 1286 aResult.mHeight.Construct(elem->VideoHeight()); 1287 } 1288 1289 ProcessedMediaTrack* Track() const { return mTrack; } 1290 1291 private: 1292 virtual ~MediaElementTrackSource() { Destroy(); }; 1293 1294 WeakPtr<HTMLMediaElement> mOwner; 1295 RefPtr<MediaStreamTrack> mCapturedTrack; 1296 RefPtr<MediaStreamTrackSource> mCapturedTrackSource; 1297 const RefPtr<ProcessedMediaTrack> mTrack; 1298 RefPtr<MediaInputPort> mPort; 1299 // The mute state as intended by the media element. 1300 OutputMuteState mIntendedElementMuteState; 1301 // The mute state as applied to this track source. It is applied async, so 1302 // needs to be tracked separately from the intended state. 1303 OutputMuteState mElementMuteState; 1304 // Some<bool> if this is a MediaDecoder track source. 1305 const Maybe<bool> mMediaDecoderHasAlpha; 1306 }; 1307 1308 HTMLMediaElement::OutputMediaStream::OutputMediaStream( 1309 RefPtr<DOMMediaStream> aStream, bool aCapturingAudioOnly, 1310 bool aFinishWhenEnded) 1311 : mStream(std::move(aStream)), 1312 mCapturingAudioOnly(aCapturingAudioOnly), 1313 mFinishWhenEnded(aFinishWhenEnded) {} 1314 HTMLMediaElement::OutputMediaStream::~OutputMediaStream() = default; 1315 1316 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, 1317 HTMLMediaElement::OutputMediaStream& aField, 1318 const char* aName, uint32_t aFlags) { 1319 ImplCycleCollectionTraverse(aCallback, aField.mStream, "mStream", aFlags); 1320 ImplCycleCollectionTraverse(aCallback, aField.mLiveTracks, "mLiveTracks", 1321 aFlags); 1322 ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedLoadingSrc, 1323 "mFinishWhenEndedLoadingSrc", aFlags); 1324 ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedAttrStream, 1325 "mFinishWhenEndedAttrStream", aFlags); 1326 ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedMediaSource, 1327 "mFinishWhenEndedMediaSource", aFlags); 1328 } 1329 1330 void ImplCycleCollectionUnlink(HTMLMediaElement::OutputMediaStream& aField) { 1331 ImplCycleCollectionUnlink(aField.mStream); 1332 ImplCycleCollectionUnlink(aField.mLiveTracks); 1333 ImplCycleCollectionUnlink(aField.mFinishWhenEndedLoadingSrc); 1334 ImplCycleCollectionUnlink(aField.mFinishWhenEndedAttrStream); 1335 ImplCycleCollectionUnlink(aField.mFinishWhenEndedMediaSource); 1336 } 1337 1338 NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::MediaElementTrackSource, 1339 MediaStreamTrackSource) 1340 NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::MediaElementTrackSource, 1341 MediaStreamTrackSource) 1342 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( 1343 HTMLMediaElement::MediaElementTrackSource) 1344 NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource) 1345 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::MediaElementTrackSource) 1346 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED( 1347 HTMLMediaElement::MediaElementTrackSource, MediaStreamTrackSource) 1348 tmp->Destroy(); 1349 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCapturedTrack) 1350 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCapturedTrackSource) 1351 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1352 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED( 1353 HTMLMediaElement::MediaElementTrackSource, MediaStreamTrackSource) 1354 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCapturedTrack) 1355 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCapturedTrackSource) 1356 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1357 1358 /** 1359 * There is a reference cycle involving this class: MediaLoadListener 1360 * holds a reference to the HTMLMediaElement, which holds a reference 1361 * to an nsIChannel, which holds a reference to this listener. 1362 * We break the reference cycle in OnStartRequest by clearing mElement. 1363 */ 1364 class HTMLMediaElement::MediaLoadListener final 1365 : public nsIChannelEventSink, 1366 public nsIInterfaceRequestor, 1367 public nsIObserver, 1368 public nsIThreadRetargetableStreamListener { 1369 ~MediaLoadListener() = default; 1370 1371 NS_DECL_THREADSAFE_ISUPPORTS 1372 NS_DECL_NSIREQUESTOBSERVER 1373 NS_DECL_NSISTREAMLISTENER 1374 NS_DECL_NSICHANNELEVENTSINK 1375 NS_DECL_NSIOBSERVER 1376 NS_DECL_NSIINTERFACEREQUESTOR 1377 NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER 1378 1379 public: 1380 explicit MediaLoadListener(HTMLMediaElement* aElement) 1381 : mElement(aElement), mLoadID(aElement->GetCurrentLoadID()) { 1382 MOZ_ASSERT(mElement, "Must pass an element to call back"); 1383 } 1384 1385 private: 1386 RefPtr<HTMLMediaElement> mElement; 1387 nsCOMPtr<nsIStreamListener> mNextListener; 1388 const uint32_t mLoadID; 1389 }; 1390 1391 NS_IMPL_ISUPPORTS(HTMLMediaElement::MediaLoadListener, nsIRequestObserver, 1392 nsIStreamListener, nsIChannelEventSink, nsIInterfaceRequestor, 1393 nsIObserver, nsIThreadRetargetableStreamListener) 1394 1395 NS_IMETHODIMP 1396 HTMLMediaElement::MediaLoadListener::Observe(nsISupports* aSubject, 1397 const char* aTopic, 1398 const char16_t* aData) { 1399 nsContentUtils::UnregisterShutdownObserver(this); 1400 1401 // Clear mElement to break cycle so we don't leak on shutdown 1402 mElement = nullptr; 1403 return NS_OK; 1404 } 1405 1406 NS_IMETHODIMP 1407 HTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest) { 1408 nsContentUtils::UnregisterShutdownObserver(this); 1409 1410 if (!mElement) { 1411 // We've been notified by the shutdown observer, and are shutting down. 1412 return NS_BINDING_ABORTED; 1413 } 1414 1415 // The element is only needed until we've had a chance to call 1416 // InitializeDecoderForChannel. So make sure mElement is cleared here. 1417 RefPtr<HTMLMediaElement> element; 1418 element.swap(mElement); 1419 1420 if (mLoadID != element->GetCurrentLoadID()) { 1421 // The channel has been cancelled before we had a chance to create 1422 // a decoder. Abort, don't dispatch an "error" event, as the new load 1423 // may not be in an error state. 1424 return NS_BINDING_ABORTED; 1425 } 1426 1427 // Don't continue to load if the request failed or has been canceled. 1428 nsresult status; 1429 nsresult rv = aRequest->GetStatus(&status); 1430 NS_ENSURE_SUCCESS(rv, rv); 1431 if (NS_FAILED(status)) { 1432 if (element) { 1433 // Handle media not loading error because source was a tracking URL (or 1434 // fingerprinting, cryptomining, etc). 1435 // We make a note of this media node by including it in a dedicated 1436 // array of blocked tracking nodes under its parent document. 1437 if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode( 1438 status)) { 1439 element->OwnerDoc()->AddBlockedNodeByClassifier(element); 1440 } 1441 element->NotifyLoadError( 1442 nsPrintfCString("%u: %s", uint32_t(status), "Request failed")); 1443 } 1444 return status; 1445 } 1446 1447 nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest); 1448 bool succeeded; 1449 if (hc && NS_SUCCEEDED(hc->GetRequestSucceeded(&succeeded)) && !succeeded) { 1450 uint32_t responseStatus = 0; 1451 (void)hc->GetResponseStatus(&responseStatus); 1452 nsAutoCString statusText; 1453 (void)hc->GetResponseStatusText(statusText); 1454 // we need status text for resist fingerprinting mode's message allowlist 1455 if (statusText.IsEmpty()) { 1456 net_GetDefaultStatusTextForCode(responseStatus, statusText); 1457 } 1458 element->NotifyLoadError( 1459 nsPrintfCString("%u: %s", responseStatus, statusText.get())); 1460 1461 nsAutoString code; 1462 code.AppendInt(responseStatus); 1463 nsAutoString src; 1464 element->GetCurrentSrc(src); 1465 AutoTArray<nsString, 2> params = {code, src}; 1466 element->ReportLoadError("MediaLoadHttpError", params); 1467 return NS_BINDING_ABORTED; 1468 } 1469 1470 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); 1471 if (channel && 1472 NS_SUCCEEDED(rv = element->InitializeDecoderForChannel( 1473 channel, getter_AddRefs(mNextListener))) && 1474 mNextListener) { 1475 rv = mNextListener->OnStartRequest(aRequest); 1476 } else { 1477 // If InitializeDecoderForChannel() returned an error, fire a network error. 1478 if (NS_FAILED(rv) && !mNextListener) { 1479 // Load failed, attempt to load the next candidate resource. If there 1480 // are none, this will trigger a MEDIA_ERR_SRC_NOT_SUPPORTED error. 1481 element->NotifyLoadError("Failed to init decoder"_ns); 1482 } 1483 // If InitializeDecoderForChannel did not return a listener (but may 1484 // have otherwise succeeded), we abort the connection since we aren't 1485 // interested in keeping the channel alive ourselves. 1486 rv = NS_BINDING_ABORTED; 1487 } 1488 1489 return rv; 1490 } 1491 1492 NS_IMETHODIMP 1493 HTMLMediaElement::MediaLoadListener::OnStopRequest(nsIRequest* aRequest, 1494 nsresult aStatus) { 1495 if (mNextListener) { 1496 return mNextListener->OnStopRequest(aRequest, aStatus); 1497 } 1498 return NS_OK; 1499 } 1500 1501 NS_IMETHODIMP 1502 HTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest* aRequest, 1503 nsIInputStream* aStream, 1504 uint64_t aOffset, 1505 uint32_t aCount) { 1506 if (!mNextListener) { 1507 NS_ERROR( 1508 "Must have a chained listener; OnStartRequest should have " 1509 "canceled this request"); 1510 return NS_BINDING_ABORTED; 1511 } 1512 return mNextListener->OnDataAvailable(aRequest, aStream, aOffset, aCount); 1513 } 1514 1515 NS_IMETHODIMP 1516 HTMLMediaElement::MediaLoadListener::OnDataFinished(nsresult aStatus) { 1517 if (!mNextListener) { 1518 return NS_ERROR_FAILURE; 1519 } 1520 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetable = 1521 do_QueryInterface(mNextListener); 1522 if (retargetable) { 1523 return retargetable->OnDataFinished(aStatus); 1524 } 1525 1526 return NS_OK; 1527 } 1528 1529 NS_IMETHODIMP 1530 HTMLMediaElement::MediaLoadListener::AsyncOnChannelRedirect( 1531 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags, 1532 nsIAsyncVerifyRedirectCallback* cb) { 1533 // TODO is this really correct?? See bug #579329. 1534 if (mElement) { 1535 mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags); 1536 } 1537 nsCOMPtr<nsIChannelEventSink> sink = do_QueryInterface(mNextListener); 1538 if (sink) { 1539 return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, cb); 1540 } 1541 cb->OnRedirectVerifyCallback(NS_OK); 1542 return NS_OK; 1543 } 1544 1545 NS_IMETHODIMP 1546 HTMLMediaElement::MediaLoadListener::CheckListenerChain() { 1547 MOZ_ASSERT(mNextListener); 1548 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetable = 1549 do_QueryInterface(mNextListener); 1550 if (retargetable) { 1551 return retargetable->CheckListenerChain(); 1552 } 1553 return NS_ERROR_NO_INTERFACE; 1554 } 1555 1556 NS_IMETHODIMP 1557 HTMLMediaElement::MediaLoadListener::GetInterface(const nsIID& aIID, 1558 void** aResult) { 1559 return QueryInterface(aIID, aResult); 1560 } 1561 1562 void HTMLMediaElement::ReportLoadError(const char* aMsg, 1563 const nsTArray<nsString>& aParams) { 1564 ReportToConsole(nsIScriptError::warningFlag, aMsg, aParams); 1565 } 1566 1567 void HTMLMediaElement::ReportToConsole( 1568 uint32_t aErrorFlags, const char* aMsg, 1569 const nsTArray<nsString>& aParams) const { 1570 nsContentUtils::ReportToConsole(aErrorFlags, "Media"_ns, OwnerDoc(), 1571 nsContentUtils::eDOM_PROPERTIES, aMsg, 1572 aParams); 1573 } 1574 1575 class HTMLMediaElement::AudioChannelAgentCallback final 1576 : public nsIAudioChannelAgentCallback { 1577 public: 1578 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 1579 NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgentCallback) 1580 1581 explicit AudioChannelAgentCallback(HTMLMediaElement* aOwner) 1582 : mOwner(aOwner), 1583 mAudioChannelVolume(1.0), 1584 mPlayingThroughTheAudioChannel(false), 1585 mIsOwnerAudible(IsOwnerAudible()), 1586 mIsShutDown(false) { 1587 MOZ_ASSERT(mOwner); 1588 MaybeCreateAudioChannelAgent(); 1589 } 1590 1591 void UpdateAudioChannelPlayingState() { 1592 MOZ_ASSERT(!mIsShutDown); 1593 bool playingThroughTheAudioChannel = IsPlayingThroughTheAudioChannel(); 1594 1595 if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) { 1596 if (!MaybeCreateAudioChannelAgent()) { 1597 return; 1598 } 1599 1600 mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel; 1601 if (mPlayingThroughTheAudioChannel) { 1602 StartAudioChannelAgent(); 1603 } else { 1604 StopAudioChanelAgent(); 1605 } 1606 } 1607 } 1608 1609 void NotifyPlayStateChanged() { 1610 MOZ_ASSERT(!mIsShutDown); 1611 UpdateAudioChannelPlayingState(); 1612 } 1613 1614 NS_IMETHODIMP WindowVolumeChanged(float aVolume, bool aMuted) override { 1615 MOZ_ASSERT(mAudioChannelAgent); 1616 1617 MOZ_LOG( 1618 AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, 1619 ("HTMLMediaElement::AudioChannelAgentCallback, WindowVolumeChanged, " 1620 "this = %p, aVolume = %f, aMuted = %s\n", 1621 this, aVolume, aMuted ? "true" : "false")); 1622 1623 if (mAudioChannelVolume != aVolume) { 1624 mAudioChannelVolume = aVolume; 1625 mOwner->SetVolumeInternal(); 1626 } 1627 1628 const uint32_t muted = mOwner->mMuted; 1629 if (aMuted && !mOwner->ComputedMuted()) { 1630 mOwner->SetMutedInternal(muted | MUTED_BY_AUDIO_CHANNEL); 1631 } else if (!aMuted && mOwner->ComputedMuted()) { 1632 mOwner->SetMutedInternal(muted & ~MUTED_BY_AUDIO_CHANNEL); 1633 } 1634 1635 return NS_OK; 1636 } 1637 1638 NS_IMETHODIMP WindowSuspendChanged(SuspendTypes aSuspend) override { 1639 // Currently this method is only be used for delaying autoplay, and we've 1640 // separated related codes to `MediaPlaybackDelayPolicy`. 1641 return NS_OK; 1642 } 1643 1644 NS_IMETHODIMP WindowAudioCaptureChanged(bool aCapture) override { 1645 MOZ_ASSERT(mAudioChannelAgent); 1646 AudioCaptureTrackChangeIfNeeded(); 1647 return NS_OK; 1648 } 1649 1650 void AudioCaptureTrackChangeIfNeeded() { 1651 MOZ_ASSERT(!mIsShutDown); 1652 if (!IsPlayingStarted()) { 1653 return; 1654 } 1655 1656 MOZ_ASSERT(mAudioChannelAgent); 1657 bool isCapturing = mAudioChannelAgent->IsWindowAudioCapturingEnabled(); 1658 mOwner->AudioCaptureTrackChange(isCapturing); 1659 } 1660 1661 void NotifyAudioPlaybackChanged(AudibleChangedReasons aReason) { 1662 MOZ_ASSERT(!mIsShutDown); 1663 AudibleState newAudibleState = IsOwnerAudible(); 1664 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, 1665 ("HTMLMediaElement::AudioChannelAgentCallback, " 1666 "NotifyAudioPlaybackChanged, this=%p, current=%s, new=%s", 1667 this, AudioChannelService::EnumValueToString(mIsOwnerAudible), 1668 AudioChannelService::EnumValueToString(newAudibleState))); 1669 if (mIsOwnerAudible == newAudibleState) { 1670 return; 1671 } 1672 1673 mIsOwnerAudible = newAudibleState; 1674 if (IsPlayingStarted()) { 1675 mAudioChannelAgent->NotifyStartedAudible(mIsOwnerAudible, aReason); 1676 } 1677 } 1678 1679 void Shutdown() { 1680 MOZ_ASSERT(!mIsShutDown); 1681 if (mAudioChannelAgent && mAudioChannelAgent->IsPlayingStarted()) { 1682 StopAudioChanelAgent(); 1683 } 1684 mAudioChannelAgent = nullptr; 1685 mIsShutDown = true; 1686 } 1687 1688 float GetEffectiveVolume() const { 1689 MOZ_ASSERT(!mIsShutDown); 1690 return static_cast<float>(mOwner->Volume()) * mAudioChannelVolume; 1691 } 1692 1693 private: 1694 ~AudioChannelAgentCallback() { MOZ_ASSERT(mIsShutDown); }; 1695 1696 bool MaybeCreateAudioChannelAgent() { 1697 if (mAudioChannelAgent) { 1698 return true; 1699 } 1700 1701 mAudioChannelAgent = new AudioChannelAgent(); 1702 nsresult rv = 1703 mAudioChannelAgent->Init(mOwner->OwnerDoc()->GetInnerWindow(), this); 1704 if (NS_WARN_IF(NS_FAILED(rv))) { 1705 mAudioChannelAgent = nullptr; 1706 MOZ_LOG( 1707 AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, 1708 ("HTMLMediaElement::AudioChannelAgentCallback, Fail to initialize " 1709 "the audio channel agent, this = %p\n", 1710 this)); 1711 return false; 1712 } 1713 1714 return true; 1715 } 1716 1717 void StartAudioChannelAgent() { 1718 MOZ_ASSERT(mAudioChannelAgent); 1719 MOZ_ASSERT(!mAudioChannelAgent->IsPlayingStarted()); 1720 if (NS_WARN_IF(NS_FAILED( 1721 mAudioChannelAgent->NotifyStartedPlaying(IsOwnerAudible())))) { 1722 return; 1723 } 1724 mAudioChannelAgent->PullInitialUpdate(); 1725 } 1726 1727 void StopAudioChanelAgent() { 1728 MOZ_ASSERT(mAudioChannelAgent); 1729 MOZ_ASSERT(mAudioChannelAgent->IsPlayingStarted()); 1730 mAudioChannelAgent->NotifyStoppedPlaying(); 1731 // If we have started audio capturing before, we have to tell media element 1732 // to clear the output capturing track. 1733 mOwner->AudioCaptureTrackChange(false); 1734 } 1735 1736 bool IsPlayingStarted() { 1737 if (MaybeCreateAudioChannelAgent()) { 1738 return mAudioChannelAgent->IsPlayingStarted(); 1739 } 1740 return false; 1741 } 1742 1743 AudibleState IsOwnerAudible() const { 1744 // paused media doesn't produce any sound. 1745 if (mOwner->mPaused) { 1746 return AudibleState::eNotAudible; 1747 } 1748 return mOwner->IsAudible() ? AudibleState::eAudible 1749 : AudibleState::eNotAudible; 1750 } 1751 1752 bool IsPlayingThroughTheAudioChannel() const { 1753 // If we have an error, we are not playing. 1754 if (mOwner->GetError()) { 1755 return false; 1756 } 1757 1758 // We should consider any bfcached page or inactive document as non-playing. 1759 if (!mOwner->OwnerDoc()->IsActive()) { 1760 return false; 1761 } 1762 1763 // Media is suspended by the docshell. 1764 if (mOwner->ShouldBeSuspendedByInactiveDocShell()) { 1765 return false; 1766 } 1767 1768 // Are we paused 1769 if (mOwner->mPaused) { 1770 return false; 1771 } 1772 1773 // No audio track 1774 if (!mOwner->HasAudio()) { 1775 return false; 1776 } 1777 1778 // A loop always is playing 1779 if (mOwner->HasAttr(nsGkAtoms::loop)) { 1780 return true; 1781 } 1782 1783 // If we are actually playing... 1784 if (mOwner->IsCurrentlyPlaying()) { 1785 return true; 1786 } 1787 1788 // If we are playing an external stream. 1789 if (mOwner->mSrcAttrStream) { 1790 return true; 1791 } 1792 1793 return false; 1794 } 1795 1796 RefPtr<AudioChannelAgent> mAudioChannelAgent; 1797 HTMLMediaElement* mOwner; 1798 1799 // The audio channel volume 1800 float mAudioChannelVolume; 1801 // Is this media element playing? 1802 bool mPlayingThroughTheAudioChannel; 1803 // Indicate whether media element is audible for users. 1804 AudibleState mIsOwnerAudible; 1805 bool mIsShutDown; 1806 }; 1807 1808 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::AudioChannelAgentCallback) 1809 1810 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN( 1811 HTMLMediaElement::AudioChannelAgentCallback) 1812 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent) 1813 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1814 1815 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN( 1816 HTMLMediaElement::AudioChannelAgentCallback) 1817 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent) 1818 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1819 1820 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( 1821 HTMLMediaElement::AudioChannelAgentCallback) 1822 NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback) 1823 NS_INTERFACE_MAP_END 1824 1825 NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::AudioChannelAgentCallback) 1826 NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::AudioChannelAgentCallback) 1827 1828 class HTMLMediaElement::ChannelLoader final { 1829 public: 1830 NS_INLINE_DECL_REFCOUNTING(ChannelLoader); 1831 1832 explicit ChannelLoader(const JSCallingLocation& aCallingLocation) 1833 : mCallingLocation(aCallingLocation) {} 1834 1835 void LoadInternal(HTMLMediaElement* aElement) { 1836 if (mCancelled) { 1837 return; 1838 } 1839 1840 JSCallingLocation::AutoFallback fallback(&mCallingLocation); 1841 1842 // determine what security checks need to be performed in AsyncOpen(). 1843 nsSecurityFlags securityFlags = 1844 aElement->ShouldCheckAllowOrigin() 1845 ? nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT 1846 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT; 1847 1848 if (aElement->GetCORSMode() == CORS_USE_CREDENTIALS) { 1849 securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE; 1850 } 1851 1852 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME; 1853 1854 MOZ_ASSERT( 1855 aElement->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)); 1856 nsContentPolicyType contentPolicyType = 1857 aElement->IsHTMLElement(nsGkAtoms::audio) 1858 ? nsIContentPolicy::TYPE_INTERNAL_AUDIO 1859 : nsIContentPolicy::TYPE_INTERNAL_VIDEO; 1860 1861 // If aElement has 'triggeringprincipal' attribute, we will use the value as 1862 // triggeringPrincipal for the channel, otherwise it will default to use 1863 // aElement->NodePrincipal(). 1864 // This function returns true when aElement has 'triggeringprincipal', so if 1865 // setAttrs is true we will override the origin attributes on the channel 1866 // later. 1867 nsCOMPtr<nsIPrincipal> triggeringPrincipal; 1868 bool setAttrs = nsContentUtils::QueryTriggeringPrincipal( 1869 aElement, aElement->mLoadingSrcTriggeringPrincipal, 1870 getter_AddRefs(triggeringPrincipal)); 1871 1872 nsCOMPtr<nsILoadGroup> loadGroup = aElement->GetDocumentLoadGroup(); 1873 nsCOMPtr<nsIChannel> channel; 1874 nsresult rv = NS_NewChannelWithTriggeringPrincipal( 1875 getter_AddRefs(channel), aElement->mLoadingSrc, 1876 static_cast<Element*>(aElement), triggeringPrincipal, securityFlags, 1877 contentPolicyType, 1878 nullptr, // aPerformanceStorage 1879 loadGroup, 1880 nullptr, // aCallbacks 1881 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY | 1882 nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE | 1883 nsIChannel::LOAD_CALL_CONTENT_SNIFFERS); 1884 1885 if (NS_FAILED(rv)) { 1886 // Notify load error so the element will try next resource candidate. 1887 aElement->NotifyLoadError("Fail to create channel"_ns); 1888 return; 1889 } 1890 1891 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); 1892 if (setAttrs) { 1893 // The function simply returns NS_OK, so we ignore the return value. 1894 (void)loadInfo->SetOriginAttributes( 1895 triggeringPrincipal->OriginAttributesRef()); 1896 } 1897 loadInfo->SetIsMediaRequest(true); 1898 loadInfo->SetIsMediaInitialRequest(true); 1899 1900 if (nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel)) { 1901 nsString initiatorType = 1902 aElement->IsHTMLElement(nsGkAtoms::audio) ? u"audio"_ns : u"video"_ns; 1903 timedChannel->SetInitiatorType(initiatorType); 1904 } 1905 1906 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel)); 1907 if (cos) { 1908 if (aElement->mUseUrgentStartForChannel) { 1909 cos->AddClassFlags(nsIClassOfService::UrgentStart); 1910 1911 // Reset the flag to avoid loading again without initiated by user 1912 // interaction. 1913 aElement->mUseUrgentStartForChannel = false; 1914 } 1915 1916 // Unconditionally disable throttling since we want the media to fluently 1917 // play even when we switch the tab to background. 1918 cos->AddClassFlags(nsIClassOfService::DontThrottle); 1919 } 1920 1921 // The listener holds a strong reference to us. This creates a 1922 // reference cycle, once we've set mChannel, which is manually broken 1923 // in the listener's OnStartRequest method after it is finished with 1924 // the element. The cycle will also be broken if we get a shutdown 1925 // notification before OnStartRequest fires. Necko guarantees that 1926 // OnStartRequest will eventually fire if we don't shut down first. 1927 RefPtr<MediaLoadListener> loadListener = new MediaLoadListener(aElement); 1928 1929 channel->SetNotificationCallbacks(loadListener); 1930 1931 nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel); 1932 if (hc) { 1933 // Use a byte range request from the start of the resource. 1934 // This enables us to detect if the stream supports byte range 1935 // requests, and therefore seeking, early. 1936 rv = hc->SetRequestHeader("Range"_ns, "bytes=0-"_ns, false); 1937 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1938 aElement->SetRequestHeaders(hc); 1939 } 1940 1941 rv = channel->AsyncOpen(loadListener); 1942 if (NS_FAILED(rv)) { 1943 // Notify load error so the element will try next resource candidate. 1944 aElement->NotifyLoadError("Failed to open channel"_ns); 1945 return; 1946 } 1947 1948 // Else the channel must be open and starting to download. If it encounters 1949 // a non-catastrophic failure, it will set a new task to continue loading 1950 // another candidate. It's safe to set it as mChannel now. 1951 mChannel = channel; 1952 1953 // loadListener will be unregistered either on shutdown or when 1954 // OnStartRequest for the channel we just opened fires. 1955 nsContentUtils::RegisterShutdownObserver(loadListener); 1956 } 1957 1958 nsresult Load(HTMLMediaElement* aElement) { 1959 MOZ_ASSERT(aElement); 1960 // Per bug 1235183 comment 8, we can't spin the event loop from stable 1961 // state. Defer NS_NewChannel() to a new regular runnable. 1962 return aElement->OwnerDoc()->Dispatch(NewRunnableMethod<HTMLMediaElement*>( 1963 "ChannelLoader::LoadInternal", this, &ChannelLoader::LoadInternal, 1964 aElement)); 1965 } 1966 1967 void Cancel() { 1968 mCancelled = true; 1969 if (mChannel) { 1970 mChannel->CancelWithReason(NS_BINDING_ABORTED, 1971 "HTMLMediaElement::ChannelLoader::Cancel"_ns); 1972 mChannel = nullptr; 1973 } 1974 } 1975 1976 void Done() { 1977 MOZ_ASSERT(mChannel); 1978 // Decoder successfully created, the decoder now owns the MediaResource 1979 // which owns the channel. 1980 mChannel = nullptr; 1981 } 1982 1983 nsresult Redirect(nsIChannel* aChannel, nsIChannel* aNewChannel, 1984 uint32_t aFlags) { 1985 NS_ASSERTION(aChannel == mChannel, "Channels should match!"); 1986 mChannel = aNewChannel; 1987 1988 // Handle forwarding of Range header so that the intial detection 1989 // of seeking support (via result code 206) works across redirects. 1990 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel); 1991 NS_ENSURE_STATE(http); 1992 1993 constexpr auto rangeHdr = "Range"_ns; 1994 1995 nsAutoCString rangeVal; 1996 if (NS_SUCCEEDED(http->GetRequestHeader(rangeHdr, rangeVal))) { 1997 NS_ENSURE_STATE(!rangeVal.IsEmpty()); 1998 1999 http = do_QueryInterface(aNewChannel); 2000 NS_ENSURE_STATE(http); 2001 2002 nsresult rv = http->SetRequestHeader(rangeHdr, rangeVal, false); 2003 NS_ENSURE_SUCCESS(rv, rv); 2004 } 2005 2006 return NS_OK; 2007 } 2008 2009 private: 2010 ~ChannelLoader() { MOZ_ASSERT(!mChannel); } 2011 // Holds a reference to the first channel we open to the media resource. 2012 // Once the decoder is created, control over the channel passes to the 2013 // decoder, and we null out this reference. We must store this in case 2014 // we need to cancel the channel before control of it passes to the decoder. 2015 nsCOMPtr<nsIChannel> mChannel; 2016 2017 bool mCancelled = false; 2018 JSCallingLocation mCallingLocation; 2019 }; 2020 2021 class HTMLMediaElement::ErrorSink { 2022 public: 2023 explicit ErrorSink(HTMLMediaElement* aOwner) : mOwner(aOwner) { 2024 MOZ_ASSERT(mOwner); 2025 } 2026 2027 void SetError(uint16_t aErrorCode, const Maybe<MediaResult>& aResult) { 2028 // Since we have multiple paths calling into DecodeError, e.g. 2029 // MediaKeys::Terminated and EMEH264Decoder::Error. We should take the 1st 2030 // one only in order not to fire multiple 'error' events. 2031 if (mError) { 2032 return; 2033 } 2034 2035 if (!IsValidErrorCode(aErrorCode)) { 2036 NS_ASSERTION(false, "Undefined MediaError codes!"); 2037 return; 2038 } 2039 2040 ReportErrorProbe(aErrorCode, aResult); 2041 if (mOwner->ReadyState() == HAVE_NOTHING && 2042 aErrorCode == MEDIA_ERR_ABORTED) { 2043 // https://html.spec.whatwg.org/multipage/media.html#media-data-processing-steps-list 2044 // "If the media data fetching process is aborted by the user" 2045 mOwner->QueueEvent(u"abort"_ns); 2046 mOwner->ChangeNetworkState(NETWORK_EMPTY); 2047 mOwner->QueueEvent(u"emptied"_ns); 2048 if (mOwner->mDecoder) { 2049 mOwner->ShutdownDecoder(); 2050 } 2051 return; 2052 } 2053 2054 if (aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) { 2055 // https://html.spec.whatwg.org/multipage/media.html#dedicated-media-source-failure-steps 2056 mOwner->ChangeNetworkState(NETWORK_NO_SOURCE); 2057 } else { 2058 // https://html.spec.whatwg.org/multipage/media.html#media-data-processing-steps-list 2059 // "If the connection is interrupted after some media data has been 2060 // received" or "If the media data is corrupted" 2061 mOwner->ChangeNetworkState(NETWORK_IDLE); 2062 } 2063 mError = new MediaError(mOwner, aErrorCode, 2064 aResult ? aResult->Message() : nsCString()); 2065 mOwner->QueueEvent(u"error"_ns); 2066 } 2067 2068 void ResetError() { mError = nullptr; } 2069 2070 RefPtr<MediaError> mError; 2071 2072 private: 2073 bool IsValidErrorCode(const uint16_t& aErrorCode) const { 2074 return (aErrorCode == MEDIA_ERR_DECODE || aErrorCode == MEDIA_ERR_NETWORK || 2075 aErrorCode == MEDIA_ERR_ABORTED || 2076 aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED); 2077 } 2078 2079 void ReportErrorProbe(uint16_t aErrorCode, 2080 const Maybe<MediaResult>& aResult) { 2081 MOZ_ASSERT(IsValidErrorCode(aErrorCode)); 2082 auto getErrorType = [&]() { 2083 if (aErrorCode == MEDIA_ERR_ABORTED) { 2084 return "AbortError"_ns; 2085 } 2086 if (aErrorCode == MEDIA_ERR_NETWORK) { 2087 return "NetworkError"_ns; 2088 } 2089 if (aErrorCode == MEDIA_ERR_DECODE) { 2090 return "DecodeErr"_ns; 2091 } 2092 return "SrcNotSupportedErr"_ns; 2093 }; 2094 2095 glean::media::ErrorExtra extraData; 2096 extraData.errorType = Some(getErrorType()); 2097 if (aResult) { 2098 extraData.errorName = Some(aResult->ErrorName()); 2099 } 2100 nsAutoString keySystem; 2101 if (mOwner->mMediaKeys) { 2102 mOwner->mMediaKeys->GetKeySystem(keySystem); 2103 extraData.keySystem = Some(NS_ConvertUTF16toUTF8(keySystem)); 2104 } 2105 glean::media::error.Record(Some(extraData)); 2106 } 2107 2108 // Media elememt's life cycle would be longer than error sink, so we use the 2109 // raw pointer and this class would only be referenced by media element. 2110 HTMLMediaElement* mOwner; 2111 }; 2112 2113 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement) 2114 2115 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, 2116 nsGenericHTMLElement) 2117 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStreamWindowCapturer) 2118 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource) 2119 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcMediaSource) 2120 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcStream) 2121 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcAttrStream) 2122 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourcePointer) 2123 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadBlockedDoc) 2124 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate) 2125 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelWrapper) 2126 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mErrorSink->mError) 2127 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams) 2128 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputTrackSources); 2129 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed); 2130 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager) 2131 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList) 2132 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList) 2133 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaStreamTrackListener) 2134 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys) 2135 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingMediaKeys) 2136 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack) 2137 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises) 2138 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSeekDOMPromise) 2139 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetMediaKeysDOMPromise) 2140 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventBlocker) 2141 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 2142 2143 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, 2144 nsGenericHTMLElement) 2145 tmp->RemoveMutationObserver(tmp); 2146 if (tmp->mSrcStream) { 2147 // Need to unhook everything that EndSrcMediaStreamPlayback would normally 2148 // do, without creating any new strong references. 2149 if (tmp->mSelectedVideoStreamTrack) { 2150 tmp->mSelectedVideoStreamTrack->RemovePrincipalChangeObserver(tmp); 2151 } 2152 if (tmp->mMediaStreamRenderer) { 2153 tmp->mMediaStreamRenderer->Shutdown(); 2154 // We null out mMediaStreamRenderer here since Shutdown() will shut down 2155 // its WatchManager, and UpdateSrcStreamPotentiallyPlaying() contains a 2156 // guard for this. 2157 tmp->mMediaStreamRenderer = nullptr; 2158 } 2159 if (tmp->mSecondaryMediaStreamRenderer) { 2160 tmp->mSecondaryMediaStreamRenderer->Shutdown(); 2161 tmp->mSecondaryMediaStreamRenderer = nullptr; 2162 } 2163 if (tmp->mMediaStreamTrackListener) { 2164 tmp->mSrcStream->UnregisterTrackListener( 2165 tmp->mMediaStreamTrackListener.get()); 2166 } 2167 } 2168 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStreamWindowCapturer) 2169 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcStream) 2170 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcAttrStream) 2171 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource) 2172 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcMediaSource) 2173 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer) 2174 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc) 2175 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate) 2176 if (tmp->mAudioChannelWrapper) { 2177 tmp->mAudioChannelWrapper->Shutdown(); 2178 } 2179 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelWrapper) 2180 NS_IMPL_CYCLE_COLLECTION_UNLINK(mErrorSink->mError) 2181 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams) 2182 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputTrackSources) 2183 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed) 2184 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager) 2185 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList) 2186 NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList) 2187 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaStreamTrackListener) 2188 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys) 2189 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingMediaKeys) 2190 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack) 2191 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises) 2192 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSeekDOMPromise) 2193 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetMediaKeysDOMPromise) 2194 if (tmp->mMediaControlKeyListener) { 2195 tmp->mMediaControlKeyListener->Shutdown(); 2196 } 2197 if (tmp->mEventBlocker) { 2198 tmp->mEventBlocker->Shutdown(); 2199 } 2200 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR 2201 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 2202 2203 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLMediaElement, 2204 nsGenericHTMLElement) 2205 2206 void HTMLMediaElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes, 2207 size_t* aNodeSize) const { 2208 nsGenericHTMLElement::AddSizeOfExcludingThis(aSizes, aNodeSize); 2209 2210 // There are many other fields that might be worth reporting, but as seen in 2211 // bug 1595603, the event we postpone to dispatch can grow to be very large 2212 // sometimes, so at least report that. 2213 if (mEventBlocker) { 2214 *aNodeSize += 2215 mEventBlocker->SizeOfExcludingThis(aSizes.mState.mMallocSizeOf); 2216 } 2217 } 2218 2219 void HTMLMediaElement::ContentWillBeRemoved(nsIContent* aChild, 2220 const ContentRemoveInfo&) { 2221 if (aChild == mSourcePointer) { 2222 mSourcePointer = aChild->GetPreviousSibling(); 2223 } 2224 } 2225 2226 already_AddRefed<MediaSource> HTMLMediaElement::GetMozMediaSourceObject() 2227 const { 2228 RefPtr<MediaSource> source = mMediaSource; 2229 return source.forget(); 2230 } 2231 2232 already_AddRefed<Promise> HTMLMediaElement::MozRequestDebugInfo( 2233 ErrorResult& aRv) { 2234 RefPtr<Promise> promise = CreateDOMPromise(aRv); 2235 if (NS_WARN_IF(aRv.Failed())) { 2236 return nullptr; 2237 } 2238 auto result = MakeUnique<dom::HTMLMediaElementDebugInfo>(); 2239 if (mMediaKeys) { 2240 GetEMEInfo(result->mEMEInfo); 2241 } 2242 if (mVideoFrameContainer) { 2243 result->mCompositorDroppedFrames = 2244 mVideoFrameContainer->GetDroppedImageCount(); 2245 } 2246 if (mDecoder) { 2247 mDecoder->RequestDebugInfo(result->mDecoder) 2248 ->Then( 2249 AbstractMainThread(), __func__, 2250 [promise, ptr = std::move(result)]() { 2251 promise->MaybeResolve(ptr.get()); 2252 }, 2253 []() { 2254 MOZ_ASSERT_UNREACHABLE("Unexpected RequestDebugInfo() rejection"); 2255 }); 2256 } else { 2257 promise->MaybeResolve(result.get()); 2258 } 2259 return promise.forget(); 2260 } 2261 2262 /* static */ 2263 void HTMLMediaElement::MozEnableDebugLog(const GlobalObject&) { 2264 DecoderDoctorLogger::EnableLogging(); 2265 } 2266 2267 already_AddRefed<Promise> HTMLMediaElement::MozRequestDebugLog( 2268 ErrorResult& aRv) { 2269 RefPtr<Promise> promise = CreateDOMPromise(aRv); 2270 if (NS_WARN_IF(aRv.Failed())) { 2271 return nullptr; 2272 } 2273 2274 DecoderDoctorLogger::RetrieveMessages(this)->Then( 2275 AbstractMainThread(), __func__, 2276 [promise](const nsACString& aString) { 2277 promise->MaybeResolve(NS_ConvertUTF8toUTF16(aString)); 2278 }, 2279 [promise](nsresult rv) { promise->MaybeReject(rv); }); 2280 2281 return promise.forget(); 2282 } 2283 2284 void HTMLMediaElement::SetVisible(bool aVisible) { 2285 mForcedHidden = !aVisible; 2286 if (mDecoder) { 2287 mDecoder->SetForcedHidden(!aVisible); 2288 } 2289 } 2290 2291 bool HTMLMediaElement::IsVideoDecodingSuspended() const { 2292 return mDecoder && mDecoder->IsVideoDecodingSuspended(); 2293 } 2294 2295 double HTMLMediaElement::TotalVideoPlayTime() const { 2296 return mDecoder ? mDecoder->GetTotalVideoPlayTimeInSeconds() : -1.0; 2297 } 2298 2299 double HTMLMediaElement::TotalVideoHDRPlayTime() const { 2300 return mDecoder ? mDecoder->GetTotalVideoHDRPlayTimeInSeconds() : -1.0; 2301 } 2302 2303 double HTMLMediaElement::VisiblePlayTime() const { 2304 return mDecoder ? mDecoder->GetVisibleVideoPlayTimeInSeconds() : -1.0; 2305 } 2306 2307 double HTMLMediaElement::InvisiblePlayTime() const { 2308 return mDecoder ? mDecoder->GetInvisibleVideoPlayTimeInSeconds() : -1.0; 2309 } 2310 2311 double HTMLMediaElement::TotalAudioPlayTime() const { 2312 return mDecoder ? mDecoder->GetTotalAudioPlayTimeInSeconds() : -1.0; 2313 } 2314 2315 double HTMLMediaElement::AudiblePlayTime() const { 2316 return mDecoder ? mDecoder->GetAudiblePlayTimeInSeconds() : -1.0; 2317 } 2318 2319 double HTMLMediaElement::InaudiblePlayTime() const { 2320 return mDecoder ? mDecoder->GetInaudiblePlayTimeInSeconds() : -1.0; 2321 } 2322 2323 double HTMLMediaElement::MutedPlayTime() const { 2324 return mDecoder ? mDecoder->GetMutedPlayTimeInSeconds() : -1.0; 2325 } 2326 2327 void HTMLMediaElement::SetFormatDiagnosticsReportForMimeType( 2328 const nsAString& aMimeType, DecoderDoctorReportType aType) { 2329 DecoderDoctorDiagnostics diagnostics; 2330 diagnostics.SetDecoderDoctorReportType(aType); 2331 diagnostics.StoreFormatDiagnostics(OwnerDoc(), aMimeType, false /* can play*/, 2332 __func__); 2333 } 2334 2335 void HTMLMediaElement::SetDecodeError(const nsAString& aError, 2336 ErrorResult& aRv) { 2337 // The reason we use this map-ish structure is because we can't use 2338 // `CR.NS_ERROR.*` directly in test. In order to use them in test, we have to 2339 // add them into `xpc.msg`. As we won't use `CR.NS_ERROR.*` in the production 2340 // code, adding them to `xpc.msg` seems an overdesign and adding maintenance 2341 // effort (exposing them in CR also needs to add a description, which is 2342 // useless because we won't show them to users) 2343 static struct { 2344 const char* mName; 2345 nsresult mResult; 2346 } kSupportedErrorList[] = { 2347 {"NS_ERROR_DOM_MEDIA_ABORT_ERR", NS_ERROR_DOM_MEDIA_ABORT_ERR}, 2348 {"NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR", 2349 NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR}, 2350 {"NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR", 2351 NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR}, 2352 {"NS_ERROR_DOM_MEDIA_DECODE_ERR", NS_ERROR_DOM_MEDIA_DECODE_ERR}, 2353 {"NS_ERROR_DOM_MEDIA_FATAL_ERR", NS_ERROR_DOM_MEDIA_FATAL_ERR}, 2354 {"NS_ERROR_DOM_MEDIA_METADATA_ERR", NS_ERROR_DOM_MEDIA_METADATA_ERR}, 2355 {"NS_ERROR_DOM_MEDIA_OVERFLOW_ERR", NS_ERROR_DOM_MEDIA_OVERFLOW_ERR}, 2356 {"NS_ERROR_DOM_MEDIA_MEDIASINK_ERR", NS_ERROR_DOM_MEDIA_MEDIASINK_ERR}, 2357 {"NS_ERROR_DOM_MEDIA_DEMUXER_ERR", NS_ERROR_DOM_MEDIA_DEMUXER_ERR}, 2358 {"NS_ERROR_DOM_MEDIA_CDM_ERR", NS_ERROR_DOM_MEDIA_CDM_ERR}, 2359 {"NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR", 2360 NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR}}; 2361 for (auto& error : kSupportedErrorList) { 2362 if (strcmp(error.mName, NS_ConvertUTF16toUTF8(aError).get()) == 0) { 2363 DecoderDoctorDiagnostics diagnostics; 2364 diagnostics.StoreDecodeError(OwnerDoc(), error.mResult, u""_ns, __func__); 2365 return; 2366 } 2367 } 2368 aRv.Throw(NS_ERROR_FAILURE); 2369 } 2370 2371 void HTMLMediaElement::SetAudioSinkFailedStartup() { 2372 DecoderDoctorDiagnostics diagnostics; 2373 diagnostics.StoreEvent(OwnerDoc(), 2374 {DecoderDoctorEvent::eAudioSinkStartup, 2375 NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR}, 2376 __func__); 2377 } 2378 2379 already_AddRefed<layers::Image> HTMLMediaElement::GetCurrentImage() { 2380 MarkAsTainted(); 2381 2382 // TODO: In bug 1345404, handle case when video decoder is already suspended. 2383 ImageContainer* container = GetImageContainer(); 2384 if (!container) { 2385 return nullptr; 2386 } 2387 2388 AutoLockImage lockImage(container); 2389 RefPtr<layers::Image> image = lockImage.GetImage(TimeStamp::Now()); 2390 return image.forget(); 2391 } 2392 2393 bool HTMLMediaElement::HasSuspendTaint() const { 2394 MOZ_ASSERT(!mDecoder || (mDecoder->HasSuspendTaint() == mHasSuspendTaint)); 2395 return mHasSuspendTaint; 2396 } 2397 2398 already_AddRefed<DOMMediaStream> HTMLMediaElement::GetSrcObject() const { 2399 return do_AddRef(mSrcAttrStream); 2400 } 2401 2402 void HTMLMediaElement::SetSrcObject(DOMMediaStream& aValue) { 2403 SetSrcObject(&aValue); 2404 } 2405 2406 void HTMLMediaElement::SetSrcObject(DOMMediaStream* aValue) { 2407 for (auto& outputStream : mOutputStreams) { 2408 if (aValue == outputStream.mStream) { 2409 ReportToConsole(nsIScriptError::warningFlag, 2410 "MediaElementStreamCaptureCycle"); 2411 return; 2412 } 2413 } 2414 mSrcAttrStream = aValue; 2415 UpdateAudioChannelPlayingState(); 2416 DoLoad(); 2417 } 2418 2419 bool HTMLMediaElement::Ended() { 2420 return (mDecoder && mDecoder->IsEnded()) || 2421 (mSrcStream && mSrcStreamReportPlaybackEnded); 2422 } 2423 2424 void HTMLMediaElement::GetCurrentSrc(nsAString& aCurrentSrc) { 2425 nsAutoCString src; 2426 GetCurrentSpec(src); 2427 CopyUTF8toUTF16(src, aCurrentSrc); 2428 } 2429 2430 nsresult HTMLMediaElement::OnChannelRedirect(nsIChannel* aChannel, 2431 nsIChannel* aNewChannel, 2432 uint32_t aFlags) { 2433 MOZ_ASSERT(mChannelLoader); 2434 return mChannelLoader->Redirect(aChannel, aNewChannel, aFlags); 2435 } 2436 2437 void HTMLMediaElement::ShutdownDecoder() { 2438 RemoveMediaElementFromURITable(); 2439 NS_ASSERTION(mDecoder, "Must have decoder to shut down"); 2440 2441 mWaitingForKeyListener.DisconnectIfExists(); 2442 if (mMediaSource) { 2443 mMediaSource->CompletePendingTransactions(); 2444 } 2445 mDecoder->Shutdown(); 2446 DDUNLINKCHILD(mDecoder.get()); 2447 mDecoder = nullptr; 2448 } 2449 2450 void HTMLMediaElement::AbortExistingLoads() { 2451 MOZ_ASSERT(NS_IsMainThread()); 2452 LOG(LogLevel::Debug, ("%p Abort existing loads", this)); 2453 // Abort any already-running instance of the resource selection algorithm. 2454 mLoadWaitStatus = NOT_WAITING; 2455 2456 // Set a new load ID. This will cause events which were enqueued 2457 // with a different load ID to silently be cancelled. 2458 mCurrentLoadID++; 2459 2460 // Immediately reject or resolve the already-dispatched 2461 // nsResolveOrRejectPendingPlayPromisesRunners. These runners won't be 2462 // executed again later since the mCurrentLoadID had been changed. 2463 for (auto& runner : mPendingPlayPromisesRunners) { 2464 runner->ResolveOrReject(); 2465 } 2466 mPendingPlayPromisesRunners.Clear(); 2467 2468 if (mChannelLoader) { 2469 mChannelLoader->Cancel(); 2470 mChannelLoader = nullptr; 2471 } 2472 2473 bool fireTimeUpdate = false; 2474 2475 if (mDecoder) { 2476 fireTimeUpdate = mDecoder->GetCurrentTime() != 0.0; 2477 ShutdownDecoder(); 2478 } 2479 if (mSrcStream) { 2480 EndSrcMediaStreamPlayback(); 2481 } 2482 2483 if (mMediaSource) { 2484 OwnerDoc()->RemoveMediaElementWithMSE(); 2485 } 2486 2487 RemoveMediaElementFromURITable(); 2488 mLoadingSrcTriggeringPrincipal = nullptr; 2489 DDLOG(DDLogCategory::Property, "loading_src", ""); 2490 DDUNLINKCHILD(mMediaSource.get()); 2491 mMediaSource = nullptr; 2492 2493 if (mNetworkState == NETWORK_LOADING || mNetworkState == NETWORK_IDLE) { 2494 QueueEvent(u"abort"_ns); 2495 } 2496 2497 bool hadVideo = HasVideo(); 2498 mErrorSink->ResetError(); 2499 mCurrentPlayRangeStart = -1.0; 2500 mPlayed = new TimeRanges(ToSupports(OwnerDoc())); 2501 mLoadedDataFired = false; 2502 mCanAutoplayFlag = true; 2503 mIsLoadingFromSourceChildren = false; 2504 mSuspendedAfterFirstFrame = false; 2505 mAllowSuspendAfterFirstFrame = true; 2506 mHaveQueuedSelectResource = false; 2507 mSuspendedForPreloadNone = false; 2508 mDownloadSuspendedByCache = false; 2509 mMediaInfo = MediaInfo(); 2510 mIsEncrypted = false; 2511 mPendingEncryptedInitData.Reset(); 2512 mWaitingForKey = NOT_WAITING_FOR_KEY; 2513 mSourcePointer = nullptr; 2514 mIsBlessed = false; 2515 SetAudibleState(false); 2516 2517 mTags = nullptr; 2518 2519 if (mNetworkState != NETWORK_EMPTY) { 2520 NS_ASSERTION(!mDecoder && !mSrcStream, 2521 "How did someone setup a new stream/decoder already?"); 2522 2523 QueueEvent(u"emptied"_ns); 2524 2525 // ChangeNetworkState() will call UpdateAudioChannelPlayingState() 2526 // indirectly which depends on mPaused. So we need to update mPaused first. 2527 if (!mPaused) { 2528 mPaused = true; 2529 PlayPromise::RejectPromises(TakePendingPlayPromises(), 2530 NS_ERROR_DOM_MEDIA_ABORT_ERR); 2531 } 2532 ChangeNetworkState(NETWORK_EMPTY); 2533 RemoveMediaTracks(); 2534 UpdateOutputTrackSources(); 2535 ChangeReadyState(HAVE_NOTHING); 2536 2537 // TODO: Apply the rules for text track cue rendering Bug 865407 2538 if (mTextTrackManager) { 2539 mTextTrackManager->GetTextTracks()->SetCuesInactive(); 2540 } 2541 2542 if (fireTimeUpdate) { 2543 // Since we destroyed the decoder above, the current playback position 2544 // will now be reported as 0. The playback position was non-zero when 2545 // we destroyed the decoder, so fire a timeupdate event so that the 2546 // change will be reflected in the controls. 2547 FireTimeUpdate(TimeupdateType::eMandatory); 2548 } 2549 UpdateAudioChannelPlayingState(); 2550 } 2551 2552 if (IsVideo() && hadVideo) { 2553 // Ensure we render transparent black after resetting video resolution. 2554 Maybe<nsIntSize> size = Some(nsIntSize(0, 0)); 2555 Invalidate(ImageSizeChanged::Yes, size, ForceInvalidate::No); 2556 } 2557 2558 // As aborting current load would stop current playback, so we have no need to 2559 // resume a paused media element. 2560 ClearResumeDelayedMediaPlaybackAgentIfNeeded(); 2561 2562 mMediaControlKeyListener->StopIfNeeded(); 2563 2564 // We may have changed mPaused, mCanAutoplayFlag, and other 2565 // things which can affect AddRemoveSelfReference 2566 AddRemoveSelfReference(); 2567 2568 mIsRunningSelectResource = false; 2569 2570 AssertReadyStateIsNothing(); 2571 } 2572 2573 void HTMLMediaElement::NoSupportedMediaSourceError( 2574 const nsACString& aErrorDetails) { 2575 if (mDecoder) { 2576 ShutdownDecoder(); 2577 } 2578 2579 bool isSameOriginLoad = false; 2580 nsresult rv = NS_ERROR_NOT_AVAILABLE; 2581 if (mSrcAttrTriggeringPrincipal && mLoadingSrc) { 2582 rv = mSrcAttrTriggeringPrincipal->IsSameOrigin(mLoadingSrc, 2583 &isSameOriginLoad); 2584 } 2585 2586 if (NS_SUCCEEDED(rv) && !isSameOriginLoad) { 2587 // aErrorDetails can include sensitive details like MimeType or HTTP Status 2588 // Code. In case we're loading a 3rd party resource we should not leak this 2589 // and pass a Generic Error Message 2590 mErrorSink->SetError(MEDIA_ERR_SRC_NOT_SUPPORTED, 2591 Some(MediaResult{NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, 2592 "Failed to open media"_ns})); 2593 } else { 2594 mErrorSink->SetError( 2595 MEDIA_ERR_SRC_NOT_SUPPORTED, 2596 Some(MediaResult{NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, aErrorDetails})); 2597 } 2598 2599 RemoveMediaTracks(); 2600 ChangeDelayLoadStatus(false); 2601 UpdateAudioChannelPlayingState(); 2602 PlayPromise::RejectPromises(TakePendingPlayPromises(), 2603 NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR); 2604 } 2605 2606 // Runs a "synchronous section", a function that must run once the event loop 2607 // has reached a "stable state" 2608 // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section 2609 void HTMLMediaElement::RunInStableState(nsIRunnable* aRunnable) { 2610 if (mShuttingDown) { 2611 return; 2612 } 2613 2614 nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction( 2615 "HTMLMediaElement::RunInStableState", 2616 [self = RefPtr<HTMLMediaElement>(this), loadId = GetCurrentLoadID(), 2617 runnable = RefPtr<nsIRunnable>(aRunnable)]() { 2618 if (self->GetCurrentLoadID() != loadId) { 2619 return; 2620 } 2621 runnable->Run(); 2622 }); 2623 nsContentUtils::RunInStableState(task.forget()); 2624 } 2625 2626 void HTMLMediaElement::QueueLoadFromSourceTask() { 2627 if (!mIsLoadingFromSourceChildren || mShuttingDown) { 2628 return; 2629 } 2630 2631 if (mDecoder) { 2632 // Reset readyState to HAVE_NOTHING since we're going to load a new decoder. 2633 ShutdownDecoder(); 2634 ChangeReadyState(HAVE_NOTHING); 2635 } 2636 2637 AssertReadyStateIsNothing(); 2638 2639 ChangeDelayLoadStatus(true); 2640 ChangeNetworkState(NETWORK_LOADING); 2641 RefPtr<Runnable> r = NewRunnableMethod<JSCallingLocation>( 2642 "HTMLMediaElement::LoadFromSourceChildren", this, 2643 &HTMLMediaElement::LoadFromSourceChildren, JSCallingLocation::Get()); 2644 RunInStableState(r); 2645 } 2646 2647 void HTMLMediaElement::QueueSelectResourceTask() { 2648 // Don't allow multiple async select resource calls to be queued. 2649 if (mHaveQueuedSelectResource) { 2650 return; 2651 } 2652 mHaveQueuedSelectResource = true; 2653 ChangeNetworkState(NETWORK_NO_SOURCE); 2654 RefPtr<Runnable> r = NewRunnableMethod<JSCallingLocation>( 2655 "HTMLMediaElement::SelectResourceWrapper", this, 2656 &HTMLMediaElement::SelectResourceWrapper, JSCallingLocation::Get()); 2657 RunInStableState(r); 2658 } 2659 2660 static bool HasSourceChildren(nsIContent* aElement) { 2661 for (nsIContent* child = aElement->GetFirstChild(); child; 2662 child = child->GetNextSibling()) { 2663 if (child->IsHTMLElement(nsGkAtoms::source)) { 2664 return true; 2665 } 2666 } 2667 return false; 2668 } 2669 2670 static nsCString DocumentOrigin(Document* aDoc) { 2671 if (!aDoc) { 2672 return "null"_ns; 2673 } 2674 nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal(); 2675 if (!principal) { 2676 return "null"_ns; 2677 } 2678 nsCString origin; 2679 if (NS_FAILED(principal->GetOrigin(origin))) { 2680 return "null"_ns; 2681 } 2682 return origin; 2683 } 2684 2685 void HTMLMediaElement::Load() { 2686 LOG(LogLevel::Debug, 2687 ("%p Load() hasSrcAttrStream=%d hasSrcAttr=%d hasSourceChildren=%d " 2688 "handlingInput=%d hasAutoplayAttr=%d AllowedToPlay=%d " 2689 "ownerDoc=%p (%s) ownerDocUserActivated=%d " 2690 "muted=%d volume=%f", 2691 this, !!mSrcAttrStream, HasAttr(nsGkAtoms::src), HasSourceChildren(this), 2692 UserActivation::IsHandlingUserInput(), HasAttr(nsGkAtoms::autoplay), 2693 AllowedToPlay(), OwnerDoc(), DocumentOrigin(OwnerDoc()).get(), 2694 OwnerDoc()->HasBeenUserGestureActivated(), mMuted, mVolume)); 2695 2696 if (mIsRunningLoadMethod) { 2697 return; 2698 } 2699 2700 mIsDoingExplicitLoad = true; 2701 DoLoad(); 2702 } 2703 2704 void HTMLMediaElement::DoLoad() { 2705 // Check if media is allowed for the docshell. 2706 nsCOMPtr<nsIDocShell> docShell = OwnerDoc()->GetDocShell(); 2707 if (docShell && !docShell->GetAllowMedia()) { 2708 LOG(LogLevel::Debug, ("%p Media not allowed", this)); 2709 return; 2710 } 2711 2712 if (mIsRunningLoadMethod) { 2713 return; 2714 } 2715 2716 if (UserActivation::IsHandlingUserInput()) { 2717 // Detect if user has interacted with element so that play will not be 2718 // blocked when initiated by a script. This enables sites to capture user 2719 // intent to play by calling load() in the click handler of a "catalog 2720 // view" of a gallery of videos. 2721 mIsBlessed = true; 2722 // Mark the channel as urgent-start when autoplay so that it will play the 2723 // media from src after loading enough resource. 2724 if (HasAttr(nsGkAtoms::autoplay)) { 2725 mUseUrgentStartForChannel = true; 2726 } 2727 } 2728 2729 SetPlayedOrSeeked(false); 2730 mIsRunningLoadMethod = true; 2731 AbortExistingLoads(); 2732 SetPlaybackRate(mDefaultPlaybackRate, IgnoreErrors()); 2733 QueueSelectResourceTask(); 2734 ResetState(); 2735 mIsRunningLoadMethod = false; 2736 } 2737 2738 void HTMLMediaElement::ResetState() { 2739 // There might be a pending MediaDecoder::PlaybackPositionChanged() which 2740 // will overwrite |mMediaInfo.mVideo.mDisplay| in UpdateMediaSize() to give 2741 // staled videoWidth and videoHeight. We have to call ForgetElement() here 2742 // such that the staled callbacks won't reach us. 2743 if (mVideoFrameContainer) { 2744 mVideoFrameContainer->ForgetElement(); 2745 mVideoFrameContainer = nullptr; 2746 } 2747 if (mMediaStreamRenderer) { 2748 // mMediaStreamRenderer, has a strong reference to mVideoFrameContainer. 2749 mMediaStreamRenderer->Shutdown(); 2750 mMediaStreamRenderer = nullptr; 2751 } 2752 if (mSecondaryMediaStreamRenderer) { 2753 // mSecondaryMediaStreamRenderer, has a strong reference to 2754 // the secondary VideoFrameContainer. 2755 mSecondaryMediaStreamRenderer->Shutdown(); 2756 mSecondaryMediaStreamRenderer = nullptr; 2757 } 2758 } 2759 2760 void HTMLMediaElement::SelectResourceWrapper( 2761 const JSCallingLocation& aCallingLocation) { 2762 SelectResource(aCallingLocation); 2763 MaybeBeginCloningVisually(); 2764 mIsRunningSelectResource = false; 2765 mHaveQueuedSelectResource = false; 2766 mIsDoingExplicitLoad = false; 2767 } 2768 2769 void HTMLMediaElement::SelectResource( 2770 const JSCallingLocation& aCallingLocation) { 2771 if (!mSrcAttrStream && !HasAttr(nsGkAtoms::src) && !HasSourceChildren(this)) { 2772 // The media element has neither a src attribute nor any source 2773 // element children, abort the load. 2774 ChangeNetworkState(NETWORK_EMPTY); 2775 ChangeDelayLoadStatus(false); 2776 return; 2777 } 2778 2779 ChangeDelayLoadStatus(true); 2780 2781 ChangeNetworkState(NETWORK_LOADING); 2782 QueueEvent(u"loadstart"_ns); 2783 2784 // Delay setting mIsRunningSeletResource until after UpdatePreloadAction 2785 // so that we don't lose our state change by bailing out of the preload 2786 // state update 2787 UpdatePreloadAction(aCallingLocation); 2788 mIsRunningSelectResource = true; 2789 2790 // If we have a 'src' attribute, use that exclusively. 2791 nsAutoString src; 2792 if (mSrcAttrStream) { 2793 SetupSrcMediaStreamPlayback(mSrcAttrStream); 2794 } else if (GetAttr(nsGkAtoms::src, src)) { 2795 nsCOMPtr<nsIURI> uri; 2796 MediaResult rv = NewURIFromString(src, getter_AddRefs(uri)); 2797 if (NS_SUCCEEDED(rv)) { 2798 LOG(LogLevel::Debug, ("%p Trying load from src=%s", this, 2799 NS_ConvertUTF16toUTF8(src).get())); 2800 if (profiler_is_collecting_markers()) { 2801 profiler_add_marker("loadresource", 2802 geckoprofiler::category::MEDIA_PLAYBACK, {}, 2803 LoadSourceMarker{}, nsString{src}, nsString{}, 2804 nsString{}, Flow::FromPointer(this)); 2805 } 2806 2807 NS_ASSERTION( 2808 !mIsLoadingFromSourceChildren, 2809 "Should think we're not loading from source children by default"); 2810 2811 RemoveMediaElementFromURITable(); 2812 if (!mSrcMediaSource) { 2813 mLoadingSrc = uri; 2814 } else { 2815 mLoadingSrc = nullptr; 2816 } 2817 mLoadingSrcTriggeringPrincipal = mSrcAttrTriggeringPrincipal; 2818 DDLOG(DDLogCategory::Property, "loading_src", 2819 nsCString(NS_ConvertUTF16toUTF8(src))); 2820 bool hadMediaSource = !!mMediaSource; 2821 mMediaSource = mSrcMediaSource; 2822 if (mMediaSource && !hadMediaSource) { 2823 OwnerDoc()->AddMediaElementWithMSE(); 2824 } 2825 DDLINKCHILD("mediasource", mMediaSource.get()); 2826 UpdatePreloadAction(aCallingLocation); 2827 if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE && !mMediaSource) { 2828 // preload:none media, suspend the load here before we make any 2829 // network requests. 2830 SuspendLoad(); 2831 return; 2832 } 2833 2834 rv = LoadResource(aCallingLocation); 2835 if (NS_SUCCEEDED(rv)) { 2836 return; 2837 } 2838 } else { 2839 AutoTArray<nsString, 1> params = {src}; 2840 ReportLoadError("MediaLoadInvalidURI", params); 2841 rv = MediaResult(rv.Code(), "MediaLoadInvalidURI"); 2842 } 2843 NoSupportedMediaSourceError(rv.Description()); 2844 } else { 2845 // Otherwise, the source elements will be used. 2846 mIsLoadingFromSourceChildren = true; 2847 LoadFromSourceChildren(aCallingLocation); 2848 } 2849 } 2850 2851 void HTMLMediaElement::NotifyLoadError(const nsACString& aErrorDetails) { 2852 if (!mIsLoadingFromSourceChildren) { 2853 LOG(LogLevel::Debug, ("NotifyLoadError(), no supported media error")); 2854 NoSupportedMediaSourceError(aErrorDetails); 2855 } else if (mSourceLoadCandidate) { 2856 DispatchAsyncSourceError(mSourceLoadCandidate, aErrorDetails); 2857 QueueLoadFromSourceTask(); 2858 } else { 2859 NS_WARNING("Should know the source we were loading from!"); 2860 } 2861 if (profiler_is_collecting_markers()) { 2862 profiler_add_marker("loaderror", geckoprofiler::category::MEDIA_PLAYBACK, 2863 {}, LoadErrorMarker{}, aErrorDetails, 2864 Flow::FromPointer(this)); 2865 } 2866 } 2867 2868 void HTMLMediaElement::NotifyMediaTrackAdded(dom::MediaTrack* aTrack) { 2869 // The set of tracks changed. 2870 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources); 2871 } 2872 2873 void HTMLMediaElement::NotifyMediaTrackRemoved(dom::MediaTrack* aTrack) { 2874 // The set of tracks changed. 2875 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources); 2876 } 2877 2878 void HTMLMediaElement::NotifyMediaTrackEnabled(dom::MediaTrack* aTrack) { 2879 MOZ_ASSERT(aTrack); 2880 if (!aTrack) { 2881 return; 2882 } 2883 #ifdef DEBUG 2884 nsString id; 2885 aTrack->GetId(id); 2886 2887 LOG(LogLevel::Debug, ("MediaElement %p %sTrack with id %s enabled", this, 2888 aTrack->AsAudioTrack() ? "Audio" : "Video", 2889 NS_ConvertUTF16toUTF8(id).get())); 2890 #endif 2891 2892 MOZ_ASSERT((aTrack->AsAudioTrack() && aTrack->AsAudioTrack()->Enabled()) || 2893 (aTrack->AsVideoTrack() && aTrack->AsVideoTrack()->Selected())); 2894 2895 if (aTrack->AsAudioTrack()) { 2896 SetMutedInternal(mMuted & ~MUTED_BY_AUDIO_TRACK); 2897 } else if (aTrack->AsVideoTrack()) { 2898 if (!IsVideo()) { 2899 MOZ_ASSERT(false); 2900 return; 2901 } 2902 mDisableVideo = false; 2903 } else { 2904 MOZ_ASSERT(false, "Unknown track type"); 2905 } 2906 2907 if (mSrcStream) { 2908 if (AudioTrack* t = aTrack->AsAudioTrack()) { 2909 if (mMediaStreamRenderer) { 2910 mMediaStreamRenderer->AddTrack(t->GetAudioStreamTrack()); 2911 } 2912 } else if (VideoTrack* t = aTrack->AsVideoTrack()) { 2913 MOZ_ASSERT(!mSelectedVideoStreamTrack); 2914 2915 mSelectedVideoStreamTrack = t->GetVideoStreamTrack(); 2916 mSelectedVideoStreamTrack->AddPrincipalChangeObserver(this); 2917 if (mMediaStreamRenderer) { 2918 mMediaStreamRenderer->AddTrack(mSelectedVideoStreamTrack); 2919 } 2920 if (mSecondaryMediaStreamRenderer) { 2921 mSecondaryMediaStreamRenderer->AddTrack(mSelectedVideoStreamTrack); 2922 } 2923 if (mMediaInfo.HasVideo()) { 2924 mMediaInfo.mVideo.SetAlpha(mSelectedVideoStreamTrack->HasAlpha()); 2925 } 2926 nsContentUtils::CombineResourcePrincipals( 2927 &mSrcStreamVideoPrincipal, mSelectedVideoStreamTrack->GetPrincipal()); 2928 } 2929 } 2930 2931 // The set of enabled/selected tracks changed. 2932 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources); 2933 } 2934 2935 void HTMLMediaElement::NotifyMediaTrackDisabled(dom::MediaTrack* aTrack) { 2936 MOZ_ASSERT(aTrack); 2937 if (!aTrack) { 2938 return; 2939 } 2940 2941 nsString id; 2942 aTrack->GetId(id); 2943 2944 LOG(LogLevel::Debug, ("MediaElement %p %sTrack with id %s disabled", this, 2945 aTrack->AsAudioTrack() ? "Audio" : "Video", 2946 NS_ConvertUTF16toUTF8(id).get())); 2947 2948 MOZ_ASSERT((!aTrack->AsAudioTrack() || !aTrack->AsAudioTrack()->Enabled()) && 2949 (!aTrack->AsVideoTrack() || !aTrack->AsVideoTrack()->Selected())); 2950 2951 if (AudioTrack* t = aTrack->AsAudioTrack()) { 2952 if (mSrcStream) { 2953 if (mMediaStreamRenderer) { 2954 mMediaStreamRenderer->RemoveTrack(t->GetAudioStreamTrack()); 2955 } 2956 } 2957 // If we don't have any live tracks, we don't need to mute MediaElement. 2958 MOZ_DIAGNOSTIC_ASSERT(AudioTracks(), "Element can't have been unlinked"); 2959 if (AudioTracks()->Length() > 0) { 2960 bool shouldMute = true; 2961 for (uint32_t i = 0; i < AudioTracks()->Length(); ++i) { 2962 if ((*AudioTracks())[i]->Enabled()) { 2963 shouldMute = false; 2964 break; 2965 } 2966 } 2967 2968 if (shouldMute) { 2969 SetMutedInternal(mMuted | MUTED_BY_AUDIO_TRACK); 2970 } 2971 } 2972 } else if (aTrack->AsVideoTrack()) { 2973 if (mSrcStream) { 2974 MOZ_DIAGNOSTIC_ASSERT(mSelectedVideoStreamTrack == 2975 aTrack->AsVideoTrack()->GetVideoStreamTrack()); 2976 if (mMediaStreamRenderer) { 2977 mMediaStreamRenderer->RemoveTrack(mSelectedVideoStreamTrack); 2978 } 2979 if (mSecondaryMediaStreamRenderer) { 2980 mSecondaryMediaStreamRenderer->RemoveTrack(mSelectedVideoStreamTrack); 2981 } 2982 mSelectedVideoStreamTrack->RemovePrincipalChangeObserver(this); 2983 mSelectedVideoStreamTrack = nullptr; 2984 } 2985 } 2986 2987 // The set of enabled/selected tracks changed. 2988 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources); 2989 } 2990 2991 void HTMLMediaElement::DealWithFailedElement(nsIContent* aSourceElement) { 2992 if (mShuttingDown) { 2993 return; 2994 } 2995 2996 DispatchAsyncSourceError(aSourceElement, "Failed load on source element"_ns); 2997 GetMainThreadSerialEventTarget()->Dispatch( 2998 NewRunnableMethod("HTMLMediaElement::QueueLoadFromSourceTask", this, 2999 &HTMLMediaElement::QueueLoadFromSourceTask)); 3000 } 3001 3002 void HTMLMediaElement::LoadFromSourceChildren( 3003 const JSCallingLocation& aCallingLocation) { 3004 NS_ASSERTION(mDelayingLoadEvent, 3005 "Should delay load event (if in document) during load"); 3006 NS_ASSERTION(mIsLoadingFromSourceChildren, 3007 "Must remember we're loading from source children"); 3008 3009 AddMutationObserverUnlessExists(this); 3010 3011 RemoveMediaTracks(); 3012 3013 while (true) { 3014 HTMLSourceElement* child = GetNextSource(); 3015 if (!child) { 3016 // Exhausted candidates, wait for more candidates to be appended to 3017 // the media element. 3018 mLoadWaitStatus = WAITING_FOR_SOURCE; 3019 ChangeNetworkState(NETWORK_NO_SOURCE); 3020 ChangeDelayLoadStatus(false); 3021 ReportLoadError("MediaLoadExhaustedCandidates"); 3022 return; 3023 } 3024 3025 // Must have src attribute. 3026 nsAutoString src; 3027 if (!child->GetAttr(nsGkAtoms::src, src)) { 3028 ReportLoadError("MediaLoadSourceMissingSrc"); 3029 DealWithFailedElement(child); 3030 return; 3031 } 3032 3033 // If we have a type attribute, it must be a supported type. 3034 nsAutoString type; 3035 if (child->GetAttr(nsGkAtoms::type, type) && !type.IsEmpty()) { 3036 DecoderDoctorDiagnostics diagnostics; 3037 CanPlayStatus canPlay = GetCanPlay(type, &diagnostics); 3038 diagnostics.StoreFormatDiagnostics(OwnerDoc(), type, 3039 canPlay != CANPLAY_NO, __func__); 3040 if (canPlay == CANPLAY_NO) { 3041 // Check that at least one other source child exists and report that 3042 // we will try to load that one next. 3043 nsIContent* nextChild = mSourcePointer->GetNextSibling(); 3044 AutoTArray<nsString, 2> params = {type, src}; 3045 3046 while (nextChild) { 3047 if (nextChild && nextChild->IsHTMLElement(nsGkAtoms::source)) { 3048 ReportLoadError("MediaLoadUnsupportedTypeAttributeLoadingNextChild", 3049 params); 3050 break; 3051 } 3052 3053 nextChild = nextChild->GetNextSibling(); 3054 }; 3055 3056 if (!nextChild) { 3057 ReportLoadError("MediaLoadUnsupportedTypeAttribute", params); 3058 } 3059 3060 DealWithFailedElement(child); 3061 return; 3062 } 3063 } 3064 nsAutoString media; 3065 child->GetAttr(nsGkAtoms::media, media); 3066 HTMLSourceElement* childSrc = HTMLSourceElement::FromNode(child); 3067 MOZ_ASSERT(childSrc, "Expect child to be HTMLSourceElement"); 3068 if (childSrc && !childSrc->MatchesCurrentMedia()) { 3069 AutoTArray<nsString, 2> params = {media, src}; 3070 ReportLoadError("MediaLoadSourceMediaNotMatched", params); 3071 DealWithFailedElement(child); 3072 LOG(LogLevel::Debug, 3073 ("%p Media did not match from <source>=%s type=%s media=%s", this, 3074 NS_ConvertUTF16toUTF8(src).get(), NS_ConvertUTF16toUTF8(type).get(), 3075 NS_ConvertUTF16toUTF8(media).get())); 3076 return; 3077 } 3078 LOG(LogLevel::Debug, 3079 ("%p Trying load from <source>=%s type=%s media=%s", this, 3080 NS_ConvertUTF16toUTF8(src).get(), NS_ConvertUTF16toUTF8(type).get(), 3081 NS_ConvertUTF16toUTF8(media).get())); 3082 3083 if (profiler_is_collecting_markers()) { 3084 profiler_add_marker("loadresource", 3085 geckoprofiler::category::MEDIA_PLAYBACK, {}, 3086 LoadSourceMarker{}, nsString{src}, nsString{type}, 3087 nsString{media}, Flow::FromPointer(this)); 3088 } 3089 3090 nsCOMPtr<nsIURI> uri; 3091 NewURIFromString(src, getter_AddRefs(uri)); 3092 if (!uri) { 3093 AutoTArray<nsString, 1> params = {src}; 3094 ReportLoadError("MediaLoadInvalidURI", params); 3095 DealWithFailedElement(child); 3096 return; 3097 } 3098 3099 RemoveMediaElementFromURITable(); 3100 mLoadingSrc = uri; 3101 mLoadingSrcTriggeringPrincipal = child->GetSrcTriggeringPrincipal(); 3102 DDLOG(DDLogCategory::Property, "loading_src", 3103 nsCString(NS_ConvertUTF16toUTF8(src))); 3104 bool hadMediaSource = !!mMediaSource; 3105 mMediaSource = child->GetSrcMediaSource(); 3106 if (mMediaSource && !hadMediaSource) { 3107 OwnerDoc()->AddMediaElementWithMSE(); 3108 } 3109 DDLINKCHILD("mediasource", mMediaSource.get()); 3110 NS_ASSERTION(mNetworkState == NETWORK_LOADING, 3111 "Network state should be loading"); 3112 3113 if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE && !mMediaSource) { 3114 // preload:none media, suspend the load here before we make any 3115 // network requests. 3116 SuspendLoad(); 3117 return; 3118 } 3119 3120 if (NS_SUCCEEDED(LoadResource(aCallingLocation))) { 3121 return; 3122 } 3123 3124 // If we fail to load, loop back and try loading the next resource. 3125 DispatchAsyncSourceError(child, "Failed load on resource"_ns); 3126 } 3127 MOZ_ASSERT_UNREACHABLE("Execution should not reach here!"); 3128 } 3129 3130 void HTMLMediaElement::SuspendLoad() { 3131 mSuspendedForPreloadNone = true; 3132 ChangeNetworkState(NETWORK_IDLE); 3133 ChangeDelayLoadStatus(false); 3134 } 3135 3136 void HTMLMediaElement::ResumeLoad(PreloadAction aAction, 3137 const JSCallingLocation& aCallingLocation) { 3138 NS_ASSERTION(mSuspendedForPreloadNone, 3139 "Must be halted for preload:none to resume from preload:none " 3140 "suspended load."); 3141 mSuspendedForPreloadNone = false; 3142 mPreloadAction = aAction; 3143 ChangeDelayLoadStatus(true); 3144 ChangeNetworkState(NETWORK_LOADING); 3145 if (!mIsLoadingFromSourceChildren) { 3146 // We were loading from the element's src attribute. 3147 MediaResult rv = LoadResource(aCallingLocation); 3148 if (NS_FAILED(rv)) { 3149 NoSupportedMediaSourceError(rv.Description()); 3150 } 3151 } else { 3152 // We were loading from a child <source> element. Try to resume the 3153 // load of that child, and if that fails, try the next child. 3154 if (NS_FAILED(LoadResource(aCallingLocation))) { 3155 LoadFromSourceChildren(aCallingLocation); 3156 } 3157 } 3158 } 3159 3160 bool HTMLMediaElement::AllowedToPlay() const { 3161 return media::AutoplayPolicy::IsAllowedToPlay(*this); 3162 } 3163 3164 uint32_t HTMLMediaElement::GetPreloadDefault() const { 3165 if (mMediaSource) { 3166 return HTMLMediaElement::PRELOAD_METADATA; 3167 } 3168 if (ShouldResistFingerprinting(RFPTarget::NetworkConnection)) { 3169 return HTMLMediaElement::PRELOAD_METADATA; 3170 } 3171 if (OnCellularConnection()) { 3172 return Preferences::GetInt("media.preload.default.cellular", 3173 HTMLMediaElement::PRELOAD_NONE); 3174 } 3175 return Preferences::GetInt("media.preload.default", 3176 HTMLMediaElement::PRELOAD_METADATA); 3177 } 3178 3179 uint32_t HTMLMediaElement::GetPreloadDefaultAuto() const { 3180 if (ShouldResistFingerprinting(RFPTarget::NetworkConnection)) { 3181 return HTMLMediaElement::PRELOAD_ENOUGH; 3182 } 3183 if (OnCellularConnection()) { 3184 return Preferences::GetInt("media.preload.auto.cellular", 3185 HTMLMediaElement::PRELOAD_METADATA); 3186 } 3187 return Preferences::GetInt("media.preload.auto", 3188 HTMLMediaElement::PRELOAD_ENOUGH); 3189 } 3190 3191 void HTMLMediaElement::UpdatePreloadAction( 3192 const JSCallingLocation& aCallingLocation) { 3193 PreloadAction nextAction = PRELOAD_UNDEFINED; 3194 // If autoplay is set, or we're playing, we should always preload data, 3195 // as we'll need it to play. 3196 if ((AllowedToPlay() && HasAttr(nsGkAtoms::autoplay)) || !mPaused) { 3197 nextAction = HTMLMediaElement::PRELOAD_ENOUGH; 3198 } else { 3199 // Find the appropriate preload action by looking at the attribute. 3200 const nsAttrValue* val = 3201 mAttrs.GetAttr(nsGkAtoms::preload, kNameSpaceID_None); 3202 // MSE doesn't work if preload is none, so it ignores the pref when src is 3203 // from MSE. 3204 uint32_t preloadDefault = GetPreloadDefault(); 3205 uint32_t preloadAuto = GetPreloadDefaultAuto(); 3206 if (!val) { 3207 // Attribute is not set. Use the preload action specified by the 3208 // media.preload.default pref, or just preload metadata if not present. 3209 nextAction = static_cast<PreloadAction>(preloadDefault); 3210 } else if (val->Type() == nsAttrValue::eEnum) { 3211 MediaPreloadAttrValue attr = 3212 static_cast<MediaPreloadAttrValue>(val->GetEnumValue()); 3213 if (attr == MediaPreloadAttrValue::PRELOAD_ATTR_AUTO) { 3214 nextAction = static_cast<PreloadAction>(preloadAuto); 3215 } else if (attr == MediaPreloadAttrValue::PRELOAD_ATTR_METADATA) { 3216 nextAction = HTMLMediaElement::PRELOAD_METADATA; 3217 } else if (attr == MediaPreloadAttrValue::PRELOAD_ATTR_NONE) { 3218 nextAction = HTMLMediaElement::PRELOAD_NONE; 3219 } 3220 } else { 3221 // Use the suggested "missing value default" of "metadata", or the value 3222 // specified by the media.preload.default, if present. 3223 nextAction = static_cast<PreloadAction>(preloadDefault); 3224 } 3225 } 3226 3227 if (nextAction == HTMLMediaElement::PRELOAD_NONE && mIsDoingExplicitLoad) { 3228 LOG(LogLevel::Debug, ("%p Force to preload metadata when explicit loading " 3229 "a preload none element", 3230 this)); 3231 nextAction = HTMLMediaElement::PRELOAD_METADATA; 3232 } 3233 3234 mPreloadAction = nextAction; 3235 LOG(LogLevel::Debug, ("%p Preload action=%d", this, nextAction)); 3236 3237 if (nextAction == HTMLMediaElement::PRELOAD_ENOUGH) { 3238 if (mSuspendedForPreloadNone) { 3239 // Our load was previouly suspended due to the media having preload 3240 // value "none". The preload value has changed to preload:auto, so 3241 // resume the load. 3242 ResumeLoad(PRELOAD_ENOUGH, aCallingLocation); 3243 } else { 3244 // Preload as much of the video as we can, i.e. don't suspend after 3245 // the first frame. 3246 StopSuspendingAfterFirstFrame(); 3247 } 3248 3249 } else if (nextAction == HTMLMediaElement::PRELOAD_METADATA) { 3250 // If no preload attribute is present but Load() has been explicitly called, 3251 // assume more data may be needed and avoid suspending the decoder. This is 3252 // done because some sites may rely on the `canplaythrough` event after 3253 // calling load(), even though it’s not guaranteed to fire. 3254 // 3255 // This behavior can be reconsidered once bug 1969224 is resolved. 3256 if (!HasAttr(nsGkAtoms::preload) && mIsDoingExplicitLoad) { 3257 mAllowSuspendAfterFirstFrame = false; 3258 } else { 3259 // Ensure that the video can be suspended after first frame. 3260 mAllowSuspendAfterFirstFrame = true; 3261 } 3262 if (mSuspendedForPreloadNone) { 3263 // Our load was previouly suspended due to the media having preload 3264 // value "none". The preload value has changed to preload:metadata, so 3265 // resume the load. We'll pause the load again after we've read the 3266 // metadata. 3267 ResumeLoad(PRELOAD_METADATA, aCallingLocation); 3268 } 3269 } 3270 } 3271 3272 MediaResult HTMLMediaElement::LoadResource( 3273 const JSCallingLocation& aCallingLocation) { 3274 NS_ASSERTION(mDelayingLoadEvent, 3275 "Should delay load event (if in document) during load"); 3276 3277 if (mChannelLoader) { 3278 mChannelLoader->Cancel(); 3279 mChannelLoader = nullptr; 3280 } 3281 3282 // Set the media element's CORS mode only when loading a resource 3283 mCORSMode = AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin)); 3284 3285 HTMLMediaElement* other = LookupMediaElementURITable(mLoadingSrc); 3286 if (other && other->mDecoder) { 3287 // Clone it. 3288 // TODO: remove the cast by storing ChannelMediaDecoder in the URI table. 3289 nsresult rv = InitializeDecoderAsClone( 3290 static_cast<ChannelMediaDecoder*>(other->mDecoder.get())); 3291 if (NS_SUCCEEDED(rv)) { 3292 return rv; 3293 } 3294 } 3295 3296 LOG(LogLevel::Debug, ("%p LoadResource", this)); 3297 if (mMediaSource) { 3298 MediaDecoderInit decoderInit( 3299 this, this, mMuted ? 0.0 : mVolume, mPreservesPitch, 3300 ClampPlaybackRate(mPlaybackRate), 3301 mPreloadAction == HTMLMediaElement::PRELOAD_METADATA, mHasSuspendTaint, 3302 HasAttr(nsGkAtoms::loop), 3303 MediaContainerType(MEDIAMIMETYPE("application/x.mediasource"))); 3304 3305 RefPtr<MediaSourceDecoder> decoder = new MediaSourceDecoder(decoderInit); 3306 if (!mMediaSource->Attach(decoder)) { 3307 // TODO: Handle failure: run "If the media data cannot be fetched at 3308 // all, due to network errors, causing the user agent to give up 3309 // trying to fetch the resource" section of resource fetch algorithm. 3310 decoder->Shutdown(); 3311 return MediaResult(NS_ERROR_FAILURE, "Failed to attach MediaSource"); 3312 } 3313 ChangeDelayLoadStatus(false); 3314 nsresult rv = decoder->Load(mMediaSource->GetPrincipal()); 3315 if (NS_FAILED(rv)) { 3316 decoder->Shutdown(); 3317 LOG(LogLevel::Debug, 3318 ("%p Failed to load for decoder %p", this, decoder.get())); 3319 return MediaResult(rv, "Fail to load decoder"); 3320 } 3321 rv = FinishDecoderSetup(decoder); 3322 return MediaResult(rv, "Failed to set up decoder"); 3323 } 3324 3325 AssertReadyStateIsNothing(); 3326 3327 RefPtr<ChannelLoader> loader = new ChannelLoader(aCallingLocation); 3328 nsresult rv = loader->Load(this); 3329 if (NS_SUCCEEDED(rv)) { 3330 mChannelLoader = std::move(loader); 3331 } 3332 return MediaResult(rv, "Failed to load channel"); 3333 } 3334 3335 nsresult HTMLMediaElement::LoadWithChannel(nsIChannel* aChannel, 3336 nsIStreamListener** aListener) { 3337 NS_ENSURE_ARG_POINTER(aChannel); 3338 NS_ENSURE_ARG_POINTER(aListener); 3339 3340 *aListener = nullptr; 3341 3342 // Make sure we don't reenter during synchronous abort events. 3343 if (mIsRunningLoadMethod) { 3344 return NS_OK; 3345 } 3346 mIsRunningLoadMethod = true; 3347 AbortExistingLoads(); 3348 mIsRunningLoadMethod = false; 3349 3350 mLoadingSrcTriggeringPrincipal = nullptr; 3351 nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(mLoadingSrc)); 3352 NS_ENSURE_SUCCESS(rv, rv); 3353 3354 ChangeDelayLoadStatus(true); 3355 rv = InitializeDecoderForChannel(aChannel, aListener); 3356 if (NS_FAILED(rv)) { 3357 ChangeDelayLoadStatus(false); 3358 return rv; 3359 } 3360 3361 SetPlaybackRate(mDefaultPlaybackRate, IgnoreErrors()); 3362 QueueEvent(u"loadstart"_ns); 3363 3364 return NS_OK; 3365 } 3366 3367 bool HTMLMediaElement::Seeking() const { 3368 return mDecoder && mDecoder->IsSeeking(); 3369 } 3370 3371 double HTMLMediaElement::CurrentTime() const { 3372 if (mMediaStreamRenderer) { 3373 return ToMicrosecondResolution(mMediaStreamRenderer->CurrentTime()); 3374 } 3375 3376 if (mDefaultPlaybackStartPosition == 0.0 && mDecoder) { 3377 return std::clamp(mDecoder->GetCurrentTime(), 0.0, mDecoder->GetDuration()); 3378 } 3379 3380 return mDefaultPlaybackStartPosition; 3381 } 3382 3383 void HTMLMediaElement::FastSeek(double aTime, ErrorResult& aRv) { 3384 LOG(LogLevel::Debug, ("%p FastSeek(%f) called by JS", this, aTime)); 3385 Seek(aTime, SeekTarget::PrevSyncPoint, IgnoreErrors()); 3386 } 3387 3388 already_AddRefed<Promise> HTMLMediaElement::SeekToNextFrame(ErrorResult& aRv) { 3389 /* This will cause JIT code to be kept around longer, to help performance 3390 * when using SeekToNextFrame to iterate through every frame of a video. 3391 */ 3392 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); 3393 3394 if (win) { 3395 if (JSObject* obj = win->AsGlobal()->GetGlobalJSObject()) { 3396 js::NotifyAnimationActivity(obj); 3397 } 3398 } 3399 3400 Seek(CurrentTime(), SeekTarget::NextFrame, aRv); 3401 if (aRv.Failed()) { 3402 return nullptr; 3403 } 3404 3405 mSeekDOMPromise = CreateDOMPromise(aRv); 3406 if (NS_WARN_IF(aRv.Failed())) { 3407 return nullptr; 3408 } 3409 3410 return do_AddRef(mSeekDOMPromise); 3411 } 3412 3413 void HTMLMediaElement::SetCurrentTime(double aCurrentTime, ErrorResult& aRv) { 3414 LOG(LogLevel::Debug, 3415 ("%p SetCurrentTime(%lf) called by JS", this, aCurrentTime)); 3416 Seek(aCurrentTime, SeekTarget::Accurate, IgnoreErrors()); 3417 } 3418 3419 /** 3420 * Check if aValue is inside a range of aRanges, and if so returns true 3421 * and puts the range index in aIntervalIndex. If aValue is not 3422 * inside a range, returns false, and aIntervalIndex 3423 * is set to the index of the range which starts immediately after aValue 3424 * (and can be aRanges.Length() if aValue is after the last range). 3425 */ 3426 static bool IsInRanges(TimeRanges& aRanges, double aValue, 3427 uint32_t& aIntervalIndex) { 3428 uint32_t length = aRanges.Length(); 3429 3430 for (uint32_t i = 0; i < length; i++) { 3431 double start = aRanges.Start(i); 3432 if (start > aValue) { 3433 aIntervalIndex = i; 3434 return false; 3435 } 3436 double end = aRanges.End(i); 3437 if (aValue <= end) { 3438 aIntervalIndex = i; 3439 return true; 3440 } 3441 } 3442 aIntervalIndex = length; 3443 return false; 3444 } 3445 3446 void HTMLMediaElement::Seek(double aTime, SeekTarget::Type aSeekType, 3447 ErrorResult& aRv) { 3448 // Note: Seek is called both by synchronous code that expects errors thrown in 3449 // aRv, as well as asynchronous code that expects a promise. Make sure all 3450 // synchronous errors are returned using aRv, not promise rejections. 3451 3452 // aTime should be non-NaN. 3453 MOZ_ASSERT(!std::isnan(aTime)); 3454 3455 // Seeking step1, Set the media element's show poster flag to false. 3456 // https://html.spec.whatwg.org/multipage/media.html#dom-media-seek 3457 mShowPoster = false; 3458 3459 // Detect if user has interacted with element by seeking so that 3460 // play will not be blocked when initiated by a script. 3461 if (UserActivation::IsHandlingUserInput()) { 3462 mIsBlessed = true; 3463 } 3464 3465 StopSuspendingAfterFirstFrame(); 3466 3467 if (mSrcAttrStream) { 3468 // do nothing since media streams have an empty Seekable range. 3469 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 3470 return; 3471 } 3472 3473 if (mPlayed && mCurrentPlayRangeStart != -1.0) { 3474 double rangeEndTime = CurrentTime(); 3475 LOG(LogLevel::Debug, ("%p Adding \'played\' a range : [%f, %f]", this, 3476 mCurrentPlayRangeStart, rangeEndTime)); 3477 // Multiple seek without playing, or seek while playing. 3478 if (mCurrentPlayRangeStart != rangeEndTime) { 3479 // Don't round the left of the interval: it comes from script and needs 3480 // to be exact. 3481 mPlayed->Add(mCurrentPlayRangeStart, rangeEndTime); 3482 } 3483 // Reset the current played range start time. We'll re-set it once 3484 // the seek completes. 3485 mCurrentPlayRangeStart = -1.0; 3486 } 3487 3488 if (mReadyState == HAVE_NOTHING) { 3489 mDefaultPlaybackStartPosition = aTime; 3490 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 3491 return; 3492 } 3493 3494 if (!mDecoder) { 3495 // mDecoder must always be set in order to reach this point. 3496 NS_ASSERTION(mDecoder, "SetCurrentTime failed: no decoder"); 3497 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 3498 return; 3499 } 3500 3501 // Clamp the seek target to inside the seekable ranges. 3502 media::TimeRanges seekableRanges = mDecoder->GetSeekableTimeRanges(); 3503 if (seekableRanges.IsInvalid()) { 3504 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 3505 return; 3506 } 3507 RefPtr<TimeRanges> seekable = 3508 new TimeRanges(ToSupports(OwnerDoc()), seekableRanges); 3509 uint32_t length = seekable->Length(); 3510 if (length == 0) { 3511 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 3512 return; 3513 } 3514 3515 // If the position we want to seek to is not in a seekable range, we seek 3516 // to the closest position in the seekable ranges instead. If two positions 3517 // are equally close, we seek to the closest position from the currentTime. 3518 // See seeking spec, point 7 : 3519 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking 3520 uint32_t range = 0; 3521 bool isInRange = IsInRanges(*seekable, aTime, range); 3522 if (!isInRange) { 3523 if (range == 0) { 3524 // aTime is before the first range in |seekable|, the closest point we can 3525 // seek to is the start of the first range. 3526 aTime = seekable->Start(0); 3527 } else if (range == length) { 3528 // Seek target is after the end last range in seekable data. 3529 // Clamp the seek target to the end of the last seekable range. 3530 aTime = seekable->End(length - 1); 3531 } else { 3532 double leftBound = seekable->End(range - 1); 3533 double rightBound = seekable->Start(range); 3534 double distanceLeft = Abs(leftBound - aTime); 3535 double distanceRight = Abs(rightBound - aTime); 3536 if (distanceLeft == distanceRight) { 3537 double currentTime = CurrentTime(); 3538 distanceLeft = Abs(leftBound - currentTime); 3539 distanceRight = Abs(rightBound - currentTime); 3540 } 3541 aTime = (distanceLeft < distanceRight) ? leftBound : rightBound; 3542 } 3543 } 3544 3545 // TODO: The spec requires us to update the current time to reflect the 3546 // actual seek target before beginning the synchronous section, but 3547 // that requires changing all MediaDecoderReaders to support telling 3548 // us the fastSeek target, and it's currently not possible to get 3549 // this information as we don't yet control the demuxer for all 3550 // MediaDecoderReaders. 3551 3552 mPlayingBeforeSeek = IsPotentiallyPlaying(); 3553 3554 // The media backend is responsible for dispatching the timeupdate 3555 // event if it changes the playback position as a result of the seek. 3556 LOG(LogLevel::Debug, ("%p SetCurrentTime(%f) starting seek", this, aTime)); 3557 mDecoder->Seek(aTime, aSeekType); 3558 3559 // We changed whether we're seeking so we need to AddRemoveSelfReference. 3560 AddRemoveSelfReference(); 3561 3562 mMediaControlKeyListener->NotifyMediaPositionState(); 3563 } 3564 3565 double HTMLMediaElement::Duration() const { 3566 if (mSrcStream) { 3567 if (mSrcStreamPlaybackEnded) { 3568 return CurrentTime(); 3569 } 3570 return std::numeric_limits<double>::infinity(); 3571 } 3572 3573 if (mDecoder) { 3574 return mDecoder->GetDuration(); 3575 } 3576 3577 return std::numeric_limits<double>::quiet_NaN(); 3578 } 3579 3580 already_AddRefed<TimeRanges> HTMLMediaElement::Seekable() const { 3581 media::TimeRanges seekable = 3582 mDecoder ? mDecoder->GetSeekableTimeRanges() : media::TimeRanges(); 3583 RefPtr<TimeRanges> ranges = new TimeRanges( 3584 ToSupports(OwnerDoc()), seekable.ToMicrosecondResolution()); 3585 return ranges.forget(); 3586 } 3587 3588 already_AddRefed<TimeRanges> HTMLMediaElement::Played() { 3589 RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc())); 3590 3591 uint32_t timeRangeCount = 0; 3592 if (mPlayed) { 3593 timeRangeCount = mPlayed->Length(); 3594 } 3595 for (uint32_t i = 0; i < timeRangeCount; i++) { 3596 double begin = mPlayed->Start(i); 3597 double end = mPlayed->End(i); 3598 ranges->Add(begin, end); 3599 } 3600 3601 if (mCurrentPlayRangeStart != -1.0) { 3602 double now = CurrentTime(); 3603 if (mCurrentPlayRangeStart != now) { 3604 // Don't round the left of the interval: it comes from script and needs 3605 // to be exact. 3606 ranges->Add(mCurrentPlayRangeStart, now); 3607 } 3608 } 3609 3610 ranges->Normalize(); 3611 return ranges.forget(); 3612 } 3613 3614 void HTMLMediaElement::Pause(ErrorResult& aRv) { 3615 LOG(LogLevel::Debug, ("%p Pause() called by JS", this)); 3616 if (mNetworkState == NETWORK_EMPTY) { 3617 LOG(LogLevel::Debug, ("Loading due to Pause()")); 3618 DoLoad(); 3619 } 3620 PauseInternal(); 3621 } 3622 3623 void HTMLMediaElement::PauseInternal() { 3624 if (mDecoder && mNetworkState != NETWORK_EMPTY) { 3625 mDecoder->Pause(); 3626 } 3627 bool oldPaused = mPaused; 3628 mPaused = true; 3629 // Step 1, 3630 // https://html.spec.whatwg.org/multipage/media.html#internal-pause-steps 3631 mCanAutoplayFlag = false; 3632 // We changed mPaused and mCanAutoplayFlag which can affect 3633 // AddRemoveSelfReference 3634 AddRemoveSelfReference(); 3635 UpdateSrcMediaStreamPlaying(); 3636 if (mAudioChannelWrapper) { 3637 mAudioChannelWrapper->NotifyPlayStateChanged(); 3638 } 3639 3640 // We don't need to resume media which is paused explicitly by user. 3641 ClearResumeDelayedMediaPlaybackAgentIfNeeded(); 3642 3643 if (!oldPaused) { 3644 FireTimeUpdate(TimeupdateType::eMandatory); 3645 QueueEvent(u"pause"_ns); 3646 AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_ABORT_ERR); 3647 } 3648 } 3649 3650 void HTMLMediaElement::SetVolume(double aVolume, ErrorResult& aRv) { 3651 LOG(LogLevel::Debug, ("%p SetVolume(%f) called by JS", this, aVolume)); 3652 3653 if (aVolume < 0.0 || aVolume > 1.0) { 3654 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); 3655 return; 3656 } 3657 3658 if (aVolume == mVolume) { 3659 return; 3660 } 3661 3662 mVolume = aVolume; 3663 3664 // Here we want just to update the volume. 3665 SetVolumeInternal(); 3666 3667 QueueEvent(u"volumechange"_ns); 3668 3669 // We allow inaudible autoplay. But changing our volume may make this 3670 // media audible. So pause if we are no longer supposed to be autoplaying. 3671 PauseIfShouldNotBePlaying(); 3672 } 3673 3674 void HTMLMediaElement::MozGetMetadata(JSContext* aCx, 3675 JS::MutableHandle<JSObject*> aResult, 3676 ErrorResult& aRv) { 3677 if (mReadyState < HAVE_METADATA) { 3678 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 3679 return; 3680 } 3681 3682 JS::Rooted<JSObject*> tags(aCx, JS_NewPlainObject(aCx)); 3683 if (!tags) { 3684 aRv.Throw(NS_ERROR_FAILURE); 3685 return; 3686 } 3687 if (mTags) { 3688 for (const auto& entry : *mTags) { 3689 nsString wideValue; 3690 CopyUTF8toUTF16(entry.GetData(), wideValue); 3691 JS::Rooted<JSString*> string(aCx, 3692 JS_NewUCStringCopyZ(aCx, wideValue.Data())); 3693 if (!string || !JS_DefineProperty(aCx, tags, entry.GetKey().Data(), 3694 string, JSPROP_ENUMERATE)) { 3695 NS_WARNING("couldn't create metadata object!"); 3696 aRv.Throw(NS_ERROR_FAILURE); 3697 return; 3698 } 3699 } 3700 } 3701 3702 aResult.set(tags); 3703 } 3704 3705 void HTMLMediaElement::SetMutedInternal(uint32_t aMuted) { 3706 uint32_t oldMuted = mMuted; 3707 mMuted = aMuted; 3708 3709 if (!!aMuted == !!oldMuted) { 3710 return; 3711 } 3712 3713 SetVolumeInternal(); 3714 } 3715 3716 void HTMLMediaElement::PauseIfShouldNotBePlaying() { 3717 if (GetPaused()) { 3718 return; 3719 } 3720 if (!AllowedToPlay()) { 3721 AUTOPLAY_LOG("pause because not allowed to play, element=%p", this); 3722 ErrorResult rv; 3723 Pause(rv); 3724 } 3725 } 3726 3727 void HTMLMediaElement::SetVolumeInternal() { 3728 float effectiveVolume = ComputedVolume(); 3729 3730 if (mDecoder) { 3731 mDecoder->SetVolume(effectiveVolume); 3732 } else if (mMediaStreamRenderer) { 3733 mMediaStreamRenderer->SetAudioOutputVolume(effectiveVolume); 3734 } 3735 3736 NotifyAudioPlaybackChanged( 3737 AudioChannelService::AudibleChangedReasons::eVolumeChanged); 3738 } 3739 3740 void HTMLMediaElement::SetMuted(bool aMuted) { 3741 LOG(LogLevel::Debug, ("%p SetMuted(%d) called by JS", this, aMuted)); 3742 if (aMuted == Muted()) { 3743 return; 3744 } 3745 3746 if (aMuted) { 3747 SetMutedInternal(mMuted | MUTED_BY_CONTENT); 3748 } else { 3749 SetMutedInternal(mMuted & ~MUTED_BY_CONTENT); 3750 } 3751 3752 QueueEvent(u"volumechange"_ns); 3753 3754 // We allow inaudible autoplay. But changing our mute status may make this 3755 // media audible. So pause if we are no longer supposed to be autoplaying. 3756 PauseIfShouldNotBePlaying(); 3757 } 3758 3759 void HTMLMediaElement::GetAllEnabledMediaTracks( 3760 nsTArray<RefPtr<MediaTrack>>& aTracks) { 3761 if (AudioTrackList* tracks = AudioTracks()) { 3762 for (size_t i = 0; i < tracks->Length(); ++i) { 3763 AudioTrack* track = (*tracks)[i]; 3764 if (track->Enabled()) { 3765 aTracks.AppendElement(track); 3766 } 3767 } 3768 } 3769 if (IsVideo()) { 3770 if (VideoTrackList* tracks = VideoTracks()) { 3771 for (size_t i = 0; i < tracks->Length(); ++i) { 3772 VideoTrack* track = (*tracks)[i]; 3773 if (track->Selected()) { 3774 aTracks.AppendElement(track); 3775 } 3776 } 3777 } 3778 } 3779 } 3780 3781 void HTMLMediaElement::SetCapturedOutputStreamsEnabled(bool aEnabled) { 3782 for (const auto& entry : mOutputTrackSources.Values()) { 3783 entry->SetEnabled(aEnabled); 3784 } 3785 } 3786 3787 HTMLMediaElement::OutputMuteState HTMLMediaElement::OutputTracksMuted() { 3788 return mPaused || mReadyState <= HAVE_CURRENT_DATA ? OutputMuteState::Muted 3789 : OutputMuteState::Unmuted; 3790 } 3791 3792 void HTMLMediaElement::UpdateOutputTracksMuting() { 3793 for (const auto& entry : mOutputTrackSources.Values()) { 3794 entry->SetMutedByElement(OutputTracksMuted()); 3795 } 3796 } 3797 3798 void HTMLMediaElement::AddOutputTrackSourceToOutputStream( 3799 MediaElementTrackSource* aSource, OutputMediaStream& aOutputStream, 3800 AddTrackMode aMode) { 3801 if (aOutputStream.mStream == mSrcStream) { 3802 // Cycle detected. This can happen since tracks are added async. 3803 // We avoid forwarding it to the output here or we'd get into an infloop. 3804 LOG(LogLevel::Warning, 3805 ("NOT adding output track source %p to output stream " 3806 "%p -- cycle detected", 3807 aSource, aOutputStream.mStream.get())); 3808 return; 3809 } 3810 3811 LOG(LogLevel::Debug, ("Adding output track source %p to output stream %p", 3812 aSource, aOutputStream.mStream.get())); 3813 3814 RefPtr<MediaStreamTrack> domTrack; 3815 if (aSource->Track()->mType == MediaSegment::AUDIO) { 3816 domTrack = new AudioStreamTrack( 3817 aOutputStream.mStream->GetOwnerWindow(), aSource->Track(), aSource, 3818 MediaStreamTrackState::Live, aSource->Muted()); 3819 } else { 3820 domTrack = new VideoStreamTrack( 3821 aOutputStream.mStream->GetOwnerWindow(), aSource->Track(), aSource, 3822 MediaStreamTrackState::Live, aSource->Muted()); 3823 } 3824 3825 aOutputStream.mLiveTracks.AppendElement(domTrack); 3826 3827 switch (aMode) { 3828 case AddTrackMode::ASYNC: 3829 GetMainThreadSerialEventTarget()->Dispatch( 3830 NewRunnableMethod<StoreRefPtrPassByPtr<MediaStreamTrack>>( 3831 "DOMMediaStream::AddTrackInternal", aOutputStream.mStream, 3832 &DOMMediaStream::AddTrackInternal, domTrack)); 3833 break; 3834 case AddTrackMode::SYNC: 3835 aOutputStream.mStream->AddTrackInternal(domTrack); 3836 break; 3837 default: 3838 MOZ_CRASH("Unexpected mode"); 3839 } 3840 3841 LOG(LogLevel::Debug, 3842 ("Created capture %s track %p", 3843 domTrack->AsAudioStreamTrack() ? "audio" : "video", domTrack.get())); 3844 } 3845 3846 void HTMLMediaElement::UpdateOutputTrackSources() { 3847 // This updates the track sources in mOutputTrackSources so they're in sync 3848 // with the tracks being currently played, and state saying whether we should 3849 // be capturing tracks. This method is long so here is a breakdown: 3850 // - Figure out the tracks that should be captured 3851 // - Diff those against currently captured tracks (mOutputTrackSources), into 3852 // tracks-to-add, and tracks-to-remove 3853 // - Remove the tracks in tracks-to-remove and dispatch "removetrack" and 3854 // "ended" events for them 3855 // - If playback has ended, or there is no longer a media provider object, 3856 // remove any OutputMediaStreams that have the finish-when-ended flag set 3857 // - Create track sources for, and add to OutputMediaStreams, the tracks in 3858 // tracks-to-add 3859 3860 const bool shouldHaveTrackSources = mTracksCaptured.Ref() && 3861 !IsPlaybackEnded() && 3862 mReadyState >= HAVE_METADATA; 3863 3864 // Add track sources for all enabled/selected MediaTracks. 3865 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow(); 3866 if (!window) { 3867 return; 3868 } 3869 3870 if (mDecoder) { 3871 if (!mTracksCaptured.Ref()) { 3872 mDecoder->SetOutputCaptureState(MediaDecoder::OutputCaptureState::None); 3873 } else if (!AudioTracks() || !VideoTracks() || !shouldHaveTrackSources) { 3874 // We've been unlinked, or tracks are not yet known. 3875 mDecoder->SetOutputCaptureState(MediaDecoder::OutputCaptureState::Halt); 3876 } else { 3877 mDecoder->SetOutputCaptureState(MediaDecoder::OutputCaptureState::Capture, 3878 mTracksCaptured.Ref().get()); 3879 } 3880 } 3881 3882 // Start with all MediaTracks 3883 AutoTArray<RefPtr<MediaTrack>, 4> mediaTracksToAdd; 3884 if (shouldHaveTrackSources) { 3885 GetAllEnabledMediaTracks(mediaTracksToAdd); 3886 } 3887 3888 // ...and all MediaElementTrackSources. 3889 auto trackSourcesToRemove = 3890 ToTArray<AutoTArray<nsString, 4>>(mOutputTrackSources.Keys()); 3891 3892 // Then work out the differences. 3893 mediaTracksToAdd.RemoveLastElements( 3894 mediaTracksToAdd.end() - 3895 std::remove_if(mediaTracksToAdd.begin(), mediaTracksToAdd.end(), 3896 [this, &trackSourcesToRemove](const auto& track) { 3897 const bool remove = 3898 mOutputTrackSources.GetWeak(track->GetId()); 3899 if (remove) { 3900 trackSourcesToRemove.RemoveElement(track->GetId()); 3901 } 3902 return remove; 3903 })); 3904 3905 // First remove stale track sources. 3906 for (const auto& id : trackSourcesToRemove) { 3907 RefPtr<MediaElementTrackSource> source = mOutputTrackSources.GetWeak(id); 3908 3909 LOG(LogLevel::Debug, ("Removing output track source %p for track %s", 3910 source.get(), NS_ConvertUTF16toUTF8(id).get())); 3911 3912 if (mDecoder) { 3913 mDecoder->RemoveOutputTrack(source->Track()); 3914 } 3915 3916 // The source of this track just ended. Force-notify that it ended. 3917 // If we bounce it to the MediaTrackGraph it might not be picked up, 3918 // for instance if the MediaInputPort was destroyed in the same 3919 // iteration as it was added. 3920 GetMainThreadSerialEventTarget()->Dispatch( 3921 NewRunnableMethod("MediaElementTrackSource::OverrideEnded", source, 3922 &MediaElementTrackSource::OverrideEnded)); 3923 3924 // Remove the track from the MediaStream after it ended. 3925 for (OutputMediaStream& ms : mOutputStreams) { 3926 if (source->Track()->mType == MediaSegment::VIDEO && 3927 ms.mCapturingAudioOnly) { 3928 continue; 3929 } 3930 DebugOnly<size_t> length = ms.mLiveTracks.Length(); 3931 ms.mLiveTracks.RemoveElementsBy( 3932 [&](const RefPtr<MediaStreamTrack>& aTrack) { 3933 if (&aTrack->GetSource() != source) { 3934 return false; 3935 } 3936 GetMainThreadSerialEventTarget()->Dispatch( 3937 NewRunnableMethod<RefPtr<MediaStreamTrack>>( 3938 "DOMMediaStream::RemoveTrackInternal", ms.mStream, 3939 &DOMMediaStream::RemoveTrackInternal, aTrack)); 3940 return true; 3941 }); 3942 MOZ_ASSERT(ms.mLiveTracks.Length() == length - 1); 3943 } 3944 3945 mOutputTrackSources.Remove(id); 3946 } 3947 3948 // Then update finish-when-ended output streams as needed. 3949 for (size_t i = mOutputStreams.Length(); i-- > 0;) { 3950 if (!mOutputStreams[i].mFinishWhenEnded) { 3951 continue; 3952 } 3953 3954 if (!mOutputStreams[i].mFinishWhenEndedLoadingSrc && 3955 !mOutputStreams[i].mFinishWhenEndedAttrStream && 3956 !mOutputStreams[i].mFinishWhenEndedMediaSource) { 3957 // This finish-when-ended stream has not seen any source loaded yet. 3958 // Update the loading src if it's time. 3959 if (!IsPlaybackEnded()) { 3960 if (mLoadingSrc) { 3961 mOutputStreams[i].mFinishWhenEndedLoadingSrc = mLoadingSrc; 3962 } else if (mSrcAttrStream) { 3963 mOutputStreams[i].mFinishWhenEndedAttrStream = mSrcAttrStream; 3964 } else if (mSrcMediaSource) { 3965 mOutputStreams[i].mFinishWhenEndedMediaSource = mSrcMediaSource; 3966 } 3967 } 3968 continue; 3969 } 3970 3971 // Discard finish-when-ended output streams with a loading src set as 3972 // needed. 3973 if (!IsPlaybackEnded() && 3974 mLoadingSrc == mOutputStreams[i].mFinishWhenEndedLoadingSrc) { 3975 continue; 3976 } 3977 if (!IsPlaybackEnded() && 3978 mSrcAttrStream == mOutputStreams[i].mFinishWhenEndedAttrStream) { 3979 continue; 3980 } 3981 if (!IsPlaybackEnded() && 3982 mSrcMediaSource == mOutputStreams[i].mFinishWhenEndedMediaSource) { 3983 continue; 3984 } 3985 LOG(LogLevel::Debug, 3986 ("Playback ended or source changed. Discarding stream %p", 3987 mOutputStreams[i].mStream.get())); 3988 mOutputStreams.RemoveElementAt(i); 3989 if (mOutputStreams.IsEmpty()) { 3990 mTracksCaptured = nullptr; 3991 // mTracksCaptured is one of the Watchables triggering this method. 3992 // Unsetting it here means we'll run through this method again very soon. 3993 return; 3994 } 3995 } 3996 3997 // Finally add new MediaTracks. 3998 for (const auto& mediaTrack : mediaTracksToAdd) { 3999 nsAutoString id; 4000 mediaTrack->GetId(id); 4001 4002 MediaSegment::Type type; 4003 if (mediaTrack->AsAudioTrack()) { 4004 type = MediaSegment::AUDIO; 4005 } else if (mediaTrack->AsVideoTrack()) { 4006 type = MediaSegment::VIDEO; 4007 } else { 4008 MOZ_CRASH("Unknown track type"); 4009 } 4010 4011 RefPtr<ProcessedMediaTrack> track; 4012 RefPtr<MediaElementTrackSource> source; 4013 if (mDecoder) { 4014 track = mTracksCaptured.Ref()->mTrack->Graph()->CreateForwardedInputTrack( 4015 type); 4016 RefPtr<nsIPrincipal> principal = GetCurrentPrincipal(); 4017 if (!principal || IsCORSSameOrigin()) { 4018 principal = NodePrincipal(); 4019 } 4020 source = MakeAndAddRef<MediaElementTrackSource>( 4021 this, track, principal, OutputTracksMuted(), 4022 type == MediaSegment::VIDEO 4023 ? HTMLVideoElement::FromNode(this)->HasAlpha() 4024 : false); 4025 mDecoder->AddOutputTrack(track); 4026 } else if (mSrcStream) { 4027 MediaStreamTrack* inputTrack; 4028 if (AudioTrack* t = mediaTrack->AsAudioTrack()) { 4029 inputTrack = t->GetAudioStreamTrack(); 4030 } else if (VideoTrack* t = mediaTrack->AsVideoTrack()) { 4031 inputTrack = t->GetVideoStreamTrack(); 4032 } else { 4033 MOZ_CRASH("Unknown track type"); 4034 } 4035 MOZ_ASSERT(inputTrack); 4036 if (!inputTrack) { 4037 NS_ERROR("Input track not found in source stream"); 4038 return; 4039 } 4040 MOZ_DIAGNOSTIC_ASSERT(!inputTrack->Ended()); 4041 4042 track = inputTrack->Graph()->CreateForwardedInputTrack(type); 4043 RefPtr<MediaInputPort> port = inputTrack->ForwardTrackContentsTo(track); 4044 source = MakeAndAddRef<MediaElementTrackSource>( 4045 this, inputTrack, &inputTrack->GetSource(), track, port, 4046 OutputTracksMuted()); 4047 4048 // Track is muted initially, so we don't leak data if it's added while 4049 // paused and an MTG iteration passes before the mute comes into effect. 4050 source->SetEnabled(mSrcStreamIsPlaying); 4051 } else { 4052 MOZ_CRASH("Unknown source"); 4053 } 4054 4055 LOG(LogLevel::Debug, ("Adding output track source %p for track %s", 4056 source.get(), NS_ConvertUTF16toUTF8(id).get())); 4057 4058 track->QueueSetAutoend(false); 4059 MOZ_DIAGNOSTIC_ASSERT(!mOutputTrackSources.Contains(id)); 4060 mOutputTrackSources.InsertOrUpdate(id, RefPtr{source}); 4061 4062 // Add the new track source to any existing output streams 4063 for (OutputMediaStream& ms : mOutputStreams) { 4064 if (source->Track()->mType == MediaSegment::VIDEO && 4065 ms.mCapturingAudioOnly) { 4066 // If the output stream is for audio only we ignore video sources. 4067 continue; 4068 } 4069 AddOutputTrackSourceToOutputStream(source, ms); 4070 } 4071 } 4072 } 4073 4074 bool HTMLMediaElement::CanBeCaptured(StreamCaptureType aCaptureType) { 4075 // Don't bother capturing when the document has gone away 4076 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow(); 4077 if (!window) { 4078 return false; 4079 } 4080 4081 // Prevent capturing restricted video 4082 if (aCaptureType == StreamCaptureType::CAPTURE_ALL_TRACKS && 4083 ContainsRestrictedContent()) { 4084 return false; 4085 } 4086 return true; 4087 } 4088 4089 already_AddRefed<DOMMediaStream> HTMLMediaElement::CaptureStreamInternal( 4090 StreamCaptureBehavior aFinishBehavior, StreamCaptureType aStreamCaptureType, 4091 MediaTrackGraph* aGraph) { 4092 MOZ_ASSERT(CanBeCaptured(aStreamCaptureType)); 4093 4094 LogVisibility(CallerAPI::CAPTURE_STREAM); 4095 MarkAsTainted(); 4096 4097 if (mTracksCaptured.Ref()) { 4098 // Already have an output stream. Check whether the graph rate matches if 4099 // specified. 4100 if (aGraph && aGraph != mTracksCaptured.Ref()->mTrack->Graph()) { 4101 return nullptr; 4102 } 4103 } else { 4104 // This is the first output stream, or there are no tracks. If the former, 4105 // start capturing all tracks. If the latter, they will be added later. 4106 MediaTrackGraph* graph = aGraph; 4107 if (!graph) { 4108 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow(); 4109 if (!window) { 4110 return nullptr; 4111 } 4112 4113 MediaTrackGraph::GraphDriverType graphDriverType = 4114 HasAudio() ? MediaTrackGraph::AUDIO_THREAD_DRIVER 4115 : MediaTrackGraph::SYSTEM_THREAD_DRIVER; 4116 graph = MediaTrackGraph::GetInstance( 4117 graphDriverType, window, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, 4118 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE); 4119 } 4120 mTracksCaptured = MakeRefPtr<SharedDummyTrack>( 4121 graph->CreateSourceTrack(MediaSegment::AUDIO)); 4122 UpdateOutputTrackSources(); 4123 } 4124 4125 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow(); 4126 OutputMediaStream* out = mOutputStreams.EmplaceBack( 4127 MakeRefPtr<DOMMediaStream>(window), 4128 aStreamCaptureType == StreamCaptureType::CAPTURE_AUDIO, 4129 aFinishBehavior == StreamCaptureBehavior::FINISH_WHEN_ENDED); 4130 4131 if (aFinishBehavior == StreamCaptureBehavior::FINISH_WHEN_ENDED && 4132 !mOutputTrackSources.IsEmpty()) { 4133 // This output stream won't receive any more tracks when playback of the 4134 // current src of this media element ends, or when the src of this media 4135 // element changes. If we're currently playing something (i.e., if there are 4136 // tracks currently captured), set the current src on the output stream so 4137 // this can be tracked. If we're not playing anything, 4138 // UpdateOutputTrackSources will set the current src when it becomes 4139 // available later. 4140 if (mLoadingSrc) { 4141 out->mFinishWhenEndedLoadingSrc = mLoadingSrc; 4142 } 4143 if (mSrcAttrStream) { 4144 out->mFinishWhenEndedAttrStream = mSrcAttrStream; 4145 } 4146 if (mSrcMediaSource) { 4147 out->mFinishWhenEndedMediaSource = mSrcMediaSource; 4148 } 4149 MOZ_ASSERT(out->mFinishWhenEndedLoadingSrc || 4150 out->mFinishWhenEndedAttrStream || 4151 out->mFinishWhenEndedMediaSource); 4152 } 4153 4154 if (aStreamCaptureType == StreamCaptureType::CAPTURE_AUDIO) { 4155 if (mSrcStream) { 4156 // We don't support applying volume and mute to the captured stream, when 4157 // capturing a MediaStream. 4158 ReportToConsole(nsIScriptError::errorFlag, 4159 "MediaElementAudioCaptureOfMediaStreamError"); 4160 } 4161 4162 // mAudioCaptured tells the user that the audio played by this media element 4163 // is being routed to the captureStreams *instead* of being played to 4164 // speakers. 4165 mAudioCaptured = true; 4166 } 4167 4168 for (const RefPtr<MediaElementTrackSource>& source : 4169 mOutputTrackSources.Values()) { 4170 if (source->Track()->mType == MediaSegment::VIDEO) { 4171 // Only add video tracks if we're a video element and the output stream 4172 // wants video. 4173 if (!IsVideo()) { 4174 continue; 4175 } 4176 if (out->mCapturingAudioOnly) { 4177 continue; 4178 } 4179 } 4180 AddOutputTrackSourceToOutputStream(source, *out, AddTrackMode::SYNC); 4181 } 4182 4183 return do_AddRef(out->mStream); 4184 } 4185 4186 already_AddRefed<DOMMediaStream> HTMLMediaElement::CaptureAudio( 4187 ErrorResult& aRv, MediaTrackGraph* aGraph) { 4188 MOZ_RELEASE_ASSERT(aGraph); 4189 4190 if (!CanBeCaptured(StreamCaptureType::CAPTURE_AUDIO)) { 4191 aRv.Throw(NS_ERROR_FAILURE); 4192 return nullptr; 4193 } 4194 4195 RefPtr<DOMMediaStream> stream = 4196 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED, 4197 StreamCaptureType::CAPTURE_AUDIO, aGraph); 4198 if (!stream) { 4199 aRv.Throw(NS_ERROR_FAILURE); 4200 return nullptr; 4201 } 4202 4203 return stream.forget(); 4204 } 4205 4206 RefPtr<GenericNonExclusivePromise> HTMLMediaElement::GetAllowedToPlayPromise() { 4207 MOZ_ASSERT(NS_IsMainThread()); 4208 MOZ_ASSERT(!mOutputStreams.IsEmpty(), 4209 "This method should only be called during stream capturing!"); 4210 if (AllowedToPlay()) { 4211 AUTOPLAY_LOG("MediaElement %p has allowed to play, resolve promise", this); 4212 return GenericNonExclusivePromise::CreateAndResolve(true, __func__); 4213 } 4214 AUTOPLAY_LOG("create allow-to-play promise for MediaElement %p", this); 4215 return mAllowedToPlayPromise.Ensure(__func__); 4216 } 4217 4218 already_AddRefed<DOMMediaStream> HTMLMediaElement::MozCaptureStream( 4219 ErrorResult& aRv) { 4220 if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS)) { 4221 aRv.Throw(NS_ERROR_FAILURE); 4222 return nullptr; 4223 } 4224 4225 RefPtr<DOMMediaStream> stream = 4226 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED, 4227 StreamCaptureType::CAPTURE_ALL_TRACKS, nullptr); 4228 if (!stream) { 4229 aRv.Throw(NS_ERROR_FAILURE); 4230 return nullptr; 4231 } 4232 4233 return stream.forget(); 4234 } 4235 4236 already_AddRefed<DOMMediaStream> HTMLMediaElement::MozCaptureStreamUntilEnded( 4237 ErrorResult& aRv) { 4238 if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS)) { 4239 aRv.Throw(NS_ERROR_FAILURE); 4240 return nullptr; 4241 } 4242 4243 RefPtr<DOMMediaStream> stream = 4244 CaptureStreamInternal(StreamCaptureBehavior::FINISH_WHEN_ENDED, 4245 StreamCaptureType::CAPTURE_ALL_TRACKS, nullptr); 4246 if (!stream) { 4247 aRv.Throw(NS_ERROR_FAILURE); 4248 return nullptr; 4249 } 4250 4251 return stream.forget(); 4252 } 4253 4254 class MediaElementSetForURI : public nsURIHashKey { 4255 public: 4256 explicit MediaElementSetForURI(const nsIURI* aKey) : nsURIHashKey(aKey) {} 4257 MediaElementSetForURI(MediaElementSetForURI&& aOther) noexcept 4258 : nsURIHashKey(std::move(aOther)), 4259 mElements(std::move(aOther.mElements)) {} 4260 nsTArray<HTMLMediaElement*> mElements; 4261 }; 4262 4263 using MediaElementURITable = nsTHashtable<MediaElementSetForURI>; 4264 // Elements in this table must have non-null mDecoder and mLoadingSrc, and those 4265 // can't change while the element is in the table. The table is keyed by 4266 // the element's mLoadingSrc. Each entry has a list of all elements with the 4267 // same mLoadingSrc. 4268 static MediaElementURITable* gElementTable; 4269 4270 #ifdef DEBUG 4271 static bool URISafeEquals(nsIURI* a1, nsIURI* a2) { 4272 if (!a1 || !a2) { 4273 // Consider two empty URIs *not* equal! 4274 return false; 4275 } 4276 bool equal = false; 4277 nsresult rv = a1->Equals(a2, &equal); 4278 return NS_SUCCEEDED(rv) && equal; 4279 } 4280 // Returns the number of times aElement appears in the media element table 4281 // for aURI. If this returns other than 0 or 1, there's a bug somewhere! 4282 static unsigned MediaElementTableCount(HTMLMediaElement* aElement, 4283 nsIURI* aURI) { 4284 if (!gElementTable || !aElement) { 4285 return 0; 4286 } 4287 uint32_t uriCount = 0; 4288 uint32_t otherCount = 0; 4289 for (const auto& entry : *gElementTable) { 4290 uint32_t count = 0; 4291 for (const auto& elem : entry.mElements) { 4292 if (elem == aElement) { 4293 count++; 4294 } 4295 } 4296 if (URISafeEquals(aURI, entry.GetKey())) { 4297 uriCount = count; 4298 } else { 4299 otherCount += count; 4300 } 4301 } 4302 NS_ASSERTION(otherCount == 0, "Should not have entries for unknown URIs"); 4303 return uriCount; 4304 } 4305 #endif 4306 4307 void HTMLMediaElement::AddMediaElementToURITable() { 4308 NS_ASSERTION(mDecoder, "Call this only with decoder Load called"); 4309 NS_ASSERTION( 4310 MediaElementTableCount(this, mLoadingSrc) == 0, 4311 "Should not have entry for element in element table before addition"); 4312 if (!gElementTable) { 4313 gElementTable = new MediaElementURITable(); 4314 } 4315 MediaElementSetForURI* entry = gElementTable->PutEntry(mLoadingSrc); 4316 entry->mElements.AppendElement(this); 4317 NS_ASSERTION( 4318 MediaElementTableCount(this, mLoadingSrc) == 1, 4319 "Should have a single entry for element in element table after addition"); 4320 } 4321 4322 void HTMLMediaElement::RemoveMediaElementFromURITable() { 4323 if (!mDecoder || !mLoadingSrc || !gElementTable) { 4324 return; 4325 } 4326 MediaElementSetForURI* entry = gElementTable->GetEntry(mLoadingSrc); 4327 if (!entry) { 4328 return; 4329 } 4330 entry->mElements.RemoveElement(this); 4331 if (entry->mElements.IsEmpty()) { 4332 gElementTable->RemoveEntry(entry); 4333 if (gElementTable->Count() == 0) { 4334 delete gElementTable; 4335 gElementTable = nullptr; 4336 } 4337 } 4338 NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0, 4339 "After remove, should no longer have an entry in element table"); 4340 } 4341 4342 HTMLMediaElement* HTMLMediaElement::LookupMediaElementURITable(nsIURI* aURI) { 4343 if (!gElementTable) { 4344 return nullptr; 4345 } 4346 MediaElementSetForURI* entry = gElementTable->GetEntry(aURI); 4347 if (!entry) { 4348 return nullptr; 4349 } 4350 for (uint32_t i = 0; i < entry->mElements.Length(); ++i) { 4351 HTMLMediaElement* elem = entry->mElements[i]; 4352 bool equal; 4353 // Look for elements that have the same principal and CORS mode. 4354 // Ditto for anything else that could cause us to send different headers. 4355 if (NS_SUCCEEDED(elem->NodePrincipal()->Equals(NodePrincipal(), &equal)) && 4356 equal && elem->mCORSMode == mCORSMode) { 4357 // See SetupDecoder() below. We only add a element to the table when 4358 // mDecoder is a ChannelMediaDecoder. 4359 auto* decoder = static_cast<ChannelMediaDecoder*>(elem->mDecoder.get()); 4360 NS_ASSERTION(decoder, "Decoder gone"); 4361 if (decoder->CanClone()) { 4362 return elem; 4363 } 4364 } 4365 } 4366 return nullptr; 4367 } 4368 4369 // This GeckoView-specific observer ensures that suspended elements resume 4370 // downloading data after receiving a late autoplay permission grant, as 4371 // autoplay permissions are granted asynchronously on GeckoView. 4372 class HTMLMediaElement::GVAutoplayObserver final : public nsIObserver { 4373 enum class Phase : int8_t { Init, Subscribed, Unsubscribed }; 4374 4375 public: 4376 NS_DECL_ISUPPORTS 4377 4378 explicit GVAutoplayObserver(HTMLMediaElement* aElement) 4379 : mElement(aElement), mPhase(Phase::Init) { 4380 MOZ_ASSERT(NS_IsMainThread()); 4381 MOZ_ASSERT(aElement); 4382 #if !defined(MOZ_WIDGET_ANDROID) 4383 LOG(LogLevel::Error, 4384 ("%p GVAutoplayObserver used outside Android!", mElement.get())); 4385 MOZ_ASSERT_UNREACHABLE( 4386 "GVAutoplayObserver should never be constructed outside of Android."); 4387 #endif 4388 Subscribe(); 4389 } 4390 4391 private: 4392 NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, 4393 const char16_t*) override { 4394 if (!mElement || !aTopic || !aSubject || (mPhase != Phase::Subscribed) || 4395 strcmp(aTopic, kGVAutoplayAllowedTopic)) { 4396 LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Observe observer=%p " 4397 "Invalid element/topic/subject/phase, skip.", 4398 mElement.get(), this)); 4399 return NS_OK; 4400 } 4401 4402 if ((mElement->NetworkState() >= NETWORK_LOADING) && 4403 (mElement->ReadyState() >= HAVE_CURRENT_DATA)) { 4404 LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Observe observer=%p " 4405 "Loading or has enough data, skip.", 4406 mElement.get(), this)); 4407 return NS_OK; 4408 } 4409 4410 nsCOMPtr<nsPIDOMWindowInner> inner(do_QueryInterface(aSubject)); 4411 if (!inner) { 4412 LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Observe observer=%p " 4413 "Couldn't get inner window from subject, skip.", 4414 mElement.get(), this)); 4415 return NS_OK; 4416 } 4417 4418 RefPtr<dom::BrowsingContext> bcSubject = inner->GetBrowsingContext(); 4419 if (!bcSubject) { 4420 LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Observe observer=%p " 4421 "Couldn't get subject browsing context, skip.", 4422 mElement.get(), this)); 4423 return NS_OK; 4424 } 4425 4426 BrowsingContext* bcElem = mElement->OwnerDoc()->GetBrowsingContext(); 4427 if (!bcSubject || !bcElem || bcSubject->Top() != bcElem->Top()) { 4428 LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Observe observer=%p " 4429 "Contexts don't match, skip.", 4430 mElement.get(), this)); 4431 return NS_OK; 4432 } 4433 4434 // Element needs more data and the autoplay message is valid - update. 4435 LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Observe observer=%p " 4436 "Updating preload action.", 4437 mElement.get(), this)); 4438 mElement->UpdatePreloadAction(JSCallingLocation::Get()); 4439 return NS_OK; 4440 } 4441 4442 void Subscribe() { 4443 MOZ_ASSERT(mPhase == Phase::Init); 4444 MOZ_ASSERT(mElement); 4445 LOG(LogLevel::Debug, 4446 ("%p GVAutoplayObserver::Subscribe observer=%p", mElement.get(), this)); 4447 nsCOMPtr<nsIObserverService> observerService = 4448 mozilla::services::GetObserverService(); 4449 if (mElement && observerService) { 4450 observerService->AddObserver(this, kGVAutoplayAllowedTopic, false); 4451 } 4452 mPhase = Phase::Subscribed; 4453 } 4454 4455 void Unsubscribe() { 4456 MOZ_ASSERT(mPhase == Phase::Subscribed); 4457 // mElement might be null 4458 LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Unsubscribe observer=%p", 4459 mElement.get(), this)); 4460 nsCOMPtr<nsIObserverService> observerService = 4461 mozilla::services::GetObserverService(); 4462 if (observerService) { 4463 observerService->RemoveObserver(this, kGVAutoplayAllowedTopic); 4464 } 4465 mElement = nullptr; 4466 mPhase = Phase::Unsubscribed; 4467 } 4468 4469 virtual ~GVAutoplayObserver() { 4470 if (mPhase == Phase::Subscribed) { 4471 Unsubscribe(); 4472 } 4473 MOZ_ASSERT(!mElement); 4474 } 4475 4476 WeakPtr<HTMLMediaElement> mElement = nullptr; 4477 Phase mPhase = Phase::Init; 4478 }; 4479 4480 NS_IMPL_ISUPPORTS(HTMLMediaElement::GVAutoplayObserver, nsIObserver) 4481 4482 class HTMLMediaElement::ShutdownObserver : public nsIObserver { 4483 enum class Phase : int8_t { Init, Subscribed, Unsubscribed }; 4484 4485 public: 4486 NS_DECL_ISUPPORTS 4487 4488 NS_IMETHOD Observe(nsISupports*, const char* aTopic, 4489 const char16_t*) override { 4490 if (mPhase != Phase::Subscribed) { 4491 // Bail out if we are not subscribed for this might be called even after 4492 // |nsContentUtils::UnregisterShutdownObserver(this)|. 4493 return NS_OK; 4494 } 4495 MOZ_DIAGNOSTIC_ASSERT(mWeak); 4496 if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { 4497 mWeak->NotifyShutdownEvent(); 4498 } 4499 return NS_OK; 4500 } 4501 void Subscribe(HTMLMediaElement* aPtr) { 4502 MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Init); 4503 MOZ_DIAGNOSTIC_ASSERT(!mWeak); 4504 mWeak = aPtr; 4505 nsContentUtils::RegisterShutdownObserver(this); 4506 mPhase = Phase::Subscribed; 4507 } 4508 void Unsubscribe() { 4509 MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Subscribed); 4510 MOZ_DIAGNOSTIC_ASSERT(mWeak); 4511 MOZ_DIAGNOSTIC_ASSERT(!mAddRefed, 4512 "ReleaseMediaElement should have been called first"); 4513 mWeak = nullptr; 4514 nsContentUtils::UnregisterShutdownObserver(this); 4515 mPhase = Phase::Unsubscribed; 4516 } 4517 void AddRefMediaElement() { 4518 MOZ_DIAGNOSTIC_ASSERT(mWeak); 4519 MOZ_DIAGNOSTIC_ASSERT(!mAddRefed, "Should only ever AddRef once"); 4520 mWeak->AddRef(); 4521 mAddRefed = true; 4522 } 4523 void ReleaseMediaElement() { 4524 MOZ_DIAGNOSTIC_ASSERT(mWeak); 4525 MOZ_DIAGNOSTIC_ASSERT(mAddRefed, "Should only release after AddRef"); 4526 mWeak->Release(); 4527 mAddRefed = false; 4528 } 4529 4530 private: 4531 virtual ~ShutdownObserver() { 4532 MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Unsubscribed); 4533 MOZ_DIAGNOSTIC_ASSERT(!mWeak); 4534 MOZ_DIAGNOSTIC_ASSERT(!mAddRefed, 4535 "ReleaseMediaElement should have been called first"); 4536 } 4537 // Guaranteed to be valid by HTMLMediaElement. 4538 HTMLMediaElement* mWeak = nullptr; 4539 Phase mPhase = Phase::Init; 4540 bool mAddRefed = false; 4541 }; 4542 4543 NS_IMPL_ISUPPORTS(HTMLMediaElement::ShutdownObserver, nsIObserver) 4544 4545 class HTMLMediaElement::TitleChangeObserver final : public nsIObserver { 4546 public: 4547 NS_DECL_ISUPPORTS 4548 4549 explicit TitleChangeObserver(HTMLMediaElement* aElement) 4550 : mElement(aElement) { 4551 MOZ_ASSERT(NS_IsMainThread()); 4552 MOZ_ASSERT(aElement); 4553 } 4554 4555 NS_IMETHOD Observe(nsISupports*, const char* aTopic, 4556 const char16_t*) override { 4557 if (mElement) { 4558 mElement->UpdateStreamName(); 4559 } 4560 4561 return NS_OK; 4562 } 4563 4564 void Subscribe() { 4565 if (!mIsSubscribed) { 4566 nsCOMPtr<nsIObserverService> observerService = 4567 mozilla::services::GetObserverService(); 4568 if (observerService) { 4569 if (NS_WARN_IF(NS_FAILED(observerService->AddObserver( 4570 this, "document-title-changed", false)))) { 4571 return; 4572 } 4573 mIsSubscribed = true; 4574 } 4575 } 4576 } 4577 4578 void Unsubscribe() { 4579 if (mIsSubscribed) { 4580 mIsSubscribed = false; 4581 nsCOMPtr<nsIObserverService> observerService = 4582 mozilla::services::GetObserverService(); 4583 if (observerService) { 4584 observerService->RemoveObserver(this, "document-title-changed"); 4585 } 4586 } 4587 } 4588 4589 private: 4590 ~TitleChangeObserver() = default; 4591 4592 WeakPtr<HTMLMediaElement> mElement; 4593 bool mIsSubscribed{false}; 4594 }; 4595 4596 NS_IMPL_ISUPPORTS(HTMLMediaElement::TitleChangeObserver, nsIObserver) 4597 4598 HTMLMediaElement::HTMLMediaElement( 4599 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 4600 : nsGenericHTMLElement(std::move(aNodeInfo)), 4601 mWatchManager(this, AbstractThread::MainThread()), 4602 mShutdownObserver(new ShutdownObserver), 4603 mTitleChangeObserver(new TitleChangeObserver(this)), 4604 mEventBlocker(new EventBlocker(this)), 4605 mPlayed(new TimeRanges(ToSupports(OwnerDoc()))), 4606 mTracksCaptured(nullptr, "HTMLMediaElement::mTracksCaptured"), 4607 mErrorSink(new ErrorSink(this)), 4608 mAudioChannelWrapper(new AudioChannelAgentCallback(this)), 4609 mSink(std::pair(nsString(), RefPtr<AudioDeviceInfo>())), 4610 mShowPoster(IsVideo()), 4611 mMediaControlKeyListener(new MediaControlKeyListener(this)) { 4612 MOZ_ASSERT(GetMainThreadSerialEventTarget()); 4613 // Please don't add anything to this constructor or the initialization 4614 // list that can cause AddRef to be called. This prevents subclasses 4615 // from overriding AddRef in a way that works with our refcount 4616 // logging mechanisms. Put these things inside of the ::Init method 4617 // instead. 4618 } 4619 4620 void HTMLMediaElement::Init() { 4621 MOZ_ASSERT(mRefCnt == 0 && !mRefCnt.IsPurple(), 4622 "HTMLMediaElement::Init called when AddRef has been called " 4623 "at least once already, probably in the constructor. Please " 4624 "see the documentation in the HTMLMediaElement constructor."); 4625 MOZ_ASSERT(!mRefCnt.IsPurple()); 4626 4627 mAudioTrackList = new AudioTrackList(OwnerDoc()->GetParentObject(), this); 4628 mVideoTrackList = new VideoTrackList(OwnerDoc()->GetParentObject(), this); 4629 4630 DecoderDoctorLogger::LogConstruction(this); 4631 4632 mWatchManager.Watch(mPaused, &HTMLMediaElement::UpdateWakeLock); 4633 mWatchManager.Watch(mPaused, &HTMLMediaElement::UpdateOutputTracksMuting); 4634 mWatchManager.Watch( 4635 mPaused, &HTMLMediaElement::NotifyMediaControlPlaybackStateChanged); 4636 mWatchManager.Watch(mReadyState, &HTMLMediaElement::UpdateOutputTracksMuting); 4637 4638 mWatchManager.Watch(mTracksCaptured, 4639 &HTMLMediaElement::UpdateOutputTrackSources); 4640 mWatchManager.Watch(mReadyState, &HTMLMediaElement::UpdateOutputTrackSources); 4641 4642 mWatchManager.Watch(mDownloadSuspendedByCache, 4643 &HTMLMediaElement::UpdateReadyStateInternal); 4644 mWatchManager.Watch(mFirstFrameLoaded, 4645 &HTMLMediaElement::UpdateReadyStateInternal); 4646 mWatchManager.Watch(mSrcStreamPlaybackEnded, 4647 &HTMLMediaElement::UpdateReadyStateInternal); 4648 4649 ErrorResult rv; 4650 4651 double defaultVolume = Preferences::GetFloat("media.default_volume", 1.0); 4652 SetVolume(defaultVolume, rv); 4653 4654 RegisterActivityObserver(); 4655 NotifyOwnerDocumentActivityChanged(); 4656 4657 // We initialize the MediaShutdownManager as the HTMLMediaElement is always 4658 // constructed on the main thread, and not during stable state. 4659 // (MediaShutdownManager make use of nsIAsyncShutdownClient which is written 4660 // in JS) 4661 MediaShutdownManager::InitStatics(); 4662 4663 #if defined(MOZ_WIDGET_ANDROID) 4664 GVAutoplayPermissionRequestor::AskForPermissionIfNeeded( 4665 OwnerDoc()->GetInnerWindow()); 4666 StartObservingGVAutoplayIfNeeded(); 4667 #endif 4668 4669 OwnerDoc()->SetDocTreeHadMedia(); 4670 mShutdownObserver->Subscribe(this); 4671 mInitialized = true; 4672 } 4673 4674 HTMLMediaElement::~HTMLMediaElement() { 4675 MOZ_ASSERT(mInitialized, 4676 "HTMLMediaElement must be initialized before it is destroyed."); 4677 NS_ASSERTION( 4678 !mHasSelfReference, 4679 "How can we be destroyed if we're still holding a self reference?"); 4680 4681 PROFILER_MARKER("~HTMLMediaElement", MEDIA_PLAYBACK, {}, 4682 TerminatingFlowMarker, Flow::FromPointer(this)); 4683 4684 mWatchManager.Shutdown(); 4685 4686 #if defined(MOZ_WIDGET_ANDROID) 4687 mGVAutoplayObserver = nullptr; 4688 #endif 4689 4690 mShutdownObserver->Unsubscribe(); 4691 4692 mTitleChangeObserver->Unsubscribe(); 4693 4694 if (mVideoFrameContainer) { 4695 mVideoFrameContainer->ForgetElement(); 4696 } 4697 UnregisterActivityObserver(); 4698 4699 mSetCDMRequest.DisconnectIfExists(); 4700 mAllowedToPlayPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); 4701 4702 if (mDecoder) { 4703 ShutdownDecoder(); 4704 } 4705 if (mProgressTimer) { 4706 StopProgress(); 4707 } 4708 if (mSrcStream) { 4709 EndSrcMediaStreamPlayback(); 4710 } 4711 4712 NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0, 4713 "Destroyed media element should no longer be in element table"); 4714 4715 if (mChannelLoader) { 4716 mChannelLoader->Cancel(); 4717 } 4718 4719 if (mAudioChannelWrapper) { 4720 mAudioChannelWrapper->Shutdown(); 4721 mAudioChannelWrapper = nullptr; 4722 } 4723 4724 if (mResumeDelayedPlaybackAgent) { 4725 mResumePlaybackRequest.DisconnectIfExists(); 4726 mResumeDelayedPlaybackAgent = nullptr; 4727 } 4728 4729 mMediaControlKeyListener->StopIfNeeded(); 4730 mMediaControlKeyListener = nullptr; 4731 4732 WakeLockRelease(); 4733 4734 DecoderDoctorLogger::LogDestruction(this); 4735 } 4736 4737 void HTMLMediaElement::StopSuspendingAfterFirstFrame() { 4738 mAllowSuspendAfterFirstFrame = false; 4739 if (!mSuspendedAfterFirstFrame) { 4740 return; 4741 } 4742 mSuspendedAfterFirstFrame = false; 4743 if (mDecoder) { 4744 mDecoder->Resume(); 4745 } 4746 } 4747 4748 void HTMLMediaElement::SetPlayedOrSeeked(bool aValue) { 4749 if (aValue == mHasPlayedOrSeeked) { 4750 return; 4751 } 4752 4753 mHasPlayedOrSeeked = aValue; 4754 4755 // Force a reflow so that the poster frame hides or shows immediately. 4756 nsIFrame* frame = GetPrimaryFrame(); 4757 if (!frame) { 4758 return; 4759 } 4760 frame->PresShell()->FrameNeedsReflow(frame, IntrinsicDirty::FrameAndAncestors, 4761 NS_FRAME_IS_DIRTY); 4762 } 4763 4764 void HTMLMediaElement::NotifyXPCOMShutdown() { ShutdownDecoder(); } 4765 4766 already_AddRefed<Promise> HTMLMediaElement::Play(ErrorResult& aRv) { 4767 LOG(LogLevel::Debug, 4768 ("%p Play() called by JS readyState=%d", this, mReadyState.Ref())); 4769 4770 // 4.8.12.8 4771 // When the play() method on a media element is invoked, the user agent must 4772 // run the following steps. 4773 4774 RefPtr<PlayPromise> promise = CreatePlayPromise(aRv); 4775 if (NS_WARN_IF(aRv.Failed())) { 4776 return nullptr; 4777 } 4778 4779 // 4.8.12.8 - Step 1: 4780 // If the media element is not allowed to play, return a promise rejected 4781 // with a "NotAllowedError" DOMException and abort these steps. 4782 // NOTE: we may require requesting permission from the user, so we do the 4783 // "not allowed" check below. 4784 4785 // 4.8.12.8 - Step 2: 4786 // If the media element's error attribute is not null and its code 4787 // attribute has the value MEDIA_ERR_SRC_NOT_SUPPORTED, return a promise 4788 // rejected with a "NotSupportedError" DOMException and abort these steps. 4789 if (GetError() && GetError()->Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) { 4790 LOG(LogLevel::Debug, 4791 ("%p Play() promise rejected because source not supported.", this)); 4792 promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR); 4793 return promise.forget(); 4794 } 4795 4796 // 4.8.12.8 - Step 3: 4797 // Let promise be a new promise and append promise to the list of pending 4798 // play promises. 4799 // Note: Promise appended to list of pending promises as needed below. 4800 4801 if (ShouldBeSuspendedByInactiveDocShell()) { 4802 LOG(LogLevel::Debug, ("%p no allow to play by the docShell for now", this)); 4803 mPendingPlayPromises.AppendElement(promise); 4804 return promise.forget(); 4805 } 4806 4807 // We may delay starting playback of a media resource for an unvisited tab 4808 // until it's going to foreground or being resumed by the play tab icon. 4809 if (MediaPlaybackDelayPolicy::ShouldDelayPlayback(this)) { 4810 CreateResumeDelayedMediaPlaybackAgentIfNeeded(); 4811 LOG(LogLevel::Debug, ("%p delay Play() call", this)); 4812 MaybeDoLoad(); 4813 // When play is delayed, save a reference to the promise, and return it. 4814 // The promise will be resolved when we resume play by either the tab is 4815 // brought to the foreground, or the audio tab indicator is clicked. 4816 mPendingPlayPromises.AppendElement(promise); 4817 return promise.forget(); 4818 } 4819 4820 const bool handlingUserInput = UserActivation::IsHandlingUserInput(); 4821 mPendingPlayPromises.AppendElement(promise); 4822 4823 if (AllowedToPlay()) { 4824 AUTOPLAY_LOG("allow MediaElement %p to play", this); 4825 mAllowedToPlayPromise.ResolveIfExists(true, __func__); 4826 PlayInternal(handlingUserInput); 4827 UpdateCustomPolicyAfterPlayed(); 4828 4829 MaybeMarkSHEntryAsUserInteracted(); 4830 } else { 4831 AUTOPLAY_LOG("reject MediaElement %p to play", this); 4832 AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR); 4833 } 4834 return promise.forget(); 4835 } 4836 4837 void HTMLMediaElement::DispatchEventsWhenPlayWasNotAllowed() { 4838 if (StaticPrefs::media_autoplay_block_event_enabled()) { 4839 QueueEvent(u"blocked"_ns); 4840 } 4841 DispatchBlockEventForVideoControl(); 4842 if (!mHasEverBeenBlockedForAutoplay) { 4843 MaybeNotifyAutoplayBlocked(); 4844 ReportToConsole(nsIScriptError::warningFlag, "BlockAutoplayError"); 4845 mHasEverBeenBlockedForAutoplay = true; 4846 } 4847 } 4848 4849 void HTMLMediaElement::MaybeNotifyAutoplayBlocked() { 4850 // This event is used to notify front-end side that we've blocked autoplay, 4851 // so front-end side should show blocking icon as well. 4852 RefPtr<AsyncEventDispatcher> asyncDispatcher = 4853 new AsyncEventDispatcher(OwnerDoc(), u"GloballyAutoplayBlocked"_ns, 4854 CanBubble::eYes, ChromeOnlyDispatch::eYes); 4855 asyncDispatcher->PostDOMEvent(); 4856 } 4857 4858 void HTMLMediaElement::DispatchBlockEventForVideoControl() { 4859 #if defined(MOZ_WIDGET_ANDROID) 4860 nsVideoFrame* videoFrame = do_QueryFrame(GetPrimaryFrame()); 4861 if (!videoFrame || !videoFrame->GetVideoControls()) { 4862 return; 4863 } 4864 4865 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher( 4866 videoFrame->GetVideoControls(), u"MozNoControlsBlockedVideo"_ns, 4867 CanBubble::eYes); 4868 asyncDispatcher->PostDOMEvent(); 4869 #endif 4870 } 4871 4872 void HTMLMediaElement::PlayInternal(bool aHandlingUserInput) { 4873 #if defined(MOZ_WIDGET_ANDROID) 4874 AUTOPLAY_LOG("Stop observing GV autoplay permission (PlayInternal starting)"); 4875 StopObservingGVAutoplayIfNeeded(); 4876 #endif 4877 4878 if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE) { 4879 // The media load algorithm will be initiated by a user interaction. 4880 // We want to boost the channel priority for better responsiveness. 4881 // Note this must be done before UpdatePreloadAction() which will 4882 // update |mPreloadAction|. 4883 mUseUrgentStartForChannel = true; 4884 } 4885 4886 StopSuspendingAfterFirstFrame(); 4887 SetPlayedOrSeeked(true); 4888 4889 // 4.8.12.8 - Step 4: 4890 // If the media element's networkState attribute has the value NETWORK_EMPTY, 4891 // invoke the media element's resource selection algorithm. 4892 MaybeDoLoad(); 4893 if (mSuspendedForPreloadNone) { 4894 ResumeLoad(PRELOAD_ENOUGH, JSCallingLocation::Get()); 4895 } 4896 4897 // 4.8.12.8 - Step 5: 4898 // If the playback has ended and the direction of playback is forwards, 4899 // seek to the earliest possible position of the media resource. 4900 4901 // Even if we just did Load() or ResumeLoad(), we could already have a decoder 4902 // here if we managed to clone an existing decoder. 4903 if (mDecoder) { 4904 if (mDecoder->IsEnded()) { 4905 SetCurrentTime(0); 4906 } 4907 if (!mSuspendedByInactiveDocOrDocshell) { 4908 mDecoder->Play(); 4909 } 4910 } 4911 4912 if (mCurrentPlayRangeStart == -1.0) { 4913 mCurrentPlayRangeStart = CurrentTime(); 4914 } 4915 4916 const bool oldPaused = mPaused; 4917 mPaused = false; 4918 // Step 5, 4919 // https://html.spec.whatwg.org/multipage/media.html#internal-play-steps 4920 mCanAutoplayFlag = false; 4921 4922 // We changed mPaused and mCanAutoplayFlag which can affect 4923 // AddRemoveSelfReference and our preload status. 4924 AddRemoveSelfReference(); 4925 UpdatePreloadAction(JSCallingLocation::Get()); 4926 UpdateSrcMediaStreamPlaying(); 4927 StartMediaControlKeyListenerIfNeeded(); 4928 4929 // Once play() has been called in a user generated event handler, 4930 // it is allowed to autoplay. Note: we can reach here when not in 4931 // a user generated event handler if our readyState has not yet 4932 // reached HAVE_METADATA. 4933 mIsBlessed |= aHandlingUserInput; 4934 4935 // TODO: If the playback has ended, then the user agent must set 4936 // seek to the effective start. 4937 4938 // 4.8.12.8 - Step 6: 4939 // If the media element's paused attribute is true, run the following steps: 4940 if (oldPaused) { 4941 // 6.1. Change the value of paused to false. (Already done.) 4942 // This step is uplifted because the "block-media-playback" feature needs 4943 // the mPaused to be false before UpdateAudioChannelPlayingState() being 4944 // called. 4945 4946 // 6.2. If the show poster flag is true, set the element's show poster flag 4947 // to false and run the time marches on steps. 4948 if (mShowPoster) { 4949 mShowPoster = false; 4950 if (mTextTrackManager) { 4951 mTextTrackManager->TimeMarchesOn(); 4952 } 4953 } 4954 4955 // 6.3. Queue a task to fire a simple event named play at the element. 4956 QueueEvent(u"play"_ns); 4957 4958 // 6.4. If the media element's readyState attribute has the value 4959 // HAVE_NOTHING, HAVE_METADATA, or HAVE_CURRENT_DATA, queue a task to 4960 // fire a simple event named waiting at the element. 4961 // Otherwise, the media element's readyState attribute has the value 4962 // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA: notify about playing for the 4963 // element. 4964 switch (mReadyState) { 4965 case HAVE_NOTHING: 4966 QueueEvent(u"waiting"_ns); 4967 break; 4968 case HAVE_METADATA: 4969 case HAVE_CURRENT_DATA: 4970 QueueEvent(u"waiting"_ns); 4971 break; 4972 case HAVE_FUTURE_DATA: 4973 case HAVE_ENOUGH_DATA: 4974 NotifyAboutPlaying(); 4975 break; 4976 } 4977 } else if (mReadyState >= HAVE_FUTURE_DATA) { 4978 // 7. Otherwise, if the media element's readyState attribute has the value 4979 // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, take pending play promises and 4980 // queue a task to resolve pending play promises with the result. 4981 AsyncResolvePendingPlayPromises(); 4982 } 4983 4984 // 8. Set the media element's autoplaying flag to false. (Already done.) 4985 4986 // 9. Return promise. 4987 // (Done in caller.) 4988 } 4989 4990 void HTMLMediaElement::MaybeDoLoad() { 4991 if (mNetworkState == NETWORK_EMPTY) { 4992 DoLoad(); 4993 } 4994 } 4995 4996 void HTMLMediaElement::UpdateWakeLock() { 4997 MOZ_ASSERT(NS_IsMainThread()); 4998 // Ensure we have a wake lock if we're playing audibly. This ensures the 4999 // device doesn't sleep while playing. 5000 bool playing = !mPaused; 5001 bool isAudible = Volume() > 0.0 && !mMuted && mIsAudioTrackAudible; 5002 // WakeLock when playing audible media. 5003 if (playing && isAudible) { 5004 CreateAudioWakeLockIfNeeded(); 5005 } else { 5006 ReleaseAudioWakeLockIfExists(); 5007 } 5008 } 5009 5010 void HTMLMediaElement::CreateAudioWakeLockIfNeeded() { 5011 MOZ_ASSERT(NS_IsMainThread()); 5012 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 5013 return; 5014 } 5015 if (mAudioWakelockReleaseScheduler) { 5016 LOG(LogLevel::Debug, 5017 ("%p Reuse existing audio wakelock, cancel scheduler", this)); 5018 mAudioWakelockReleaseScheduler->Reset(); 5019 mAudioWakelockReleaseScheduler.reset(); 5020 return; 5021 } 5022 if (!mWakeLock) { 5023 RefPtr<power::PowerManagerService> pmService = 5024 power::PowerManagerService::GetInstance(); 5025 NS_ENSURE_TRUE_VOID(pmService); 5026 LOG(LogLevel::Debug, ("%p creating audio wakelock", this)); 5027 ErrorResult rv; 5028 mWakeLock = pmService->NewWakeLock(u"audio-playing"_ns, 5029 OwnerDoc()->GetInnerWindow(), rv); 5030 } 5031 } 5032 5033 void HTMLMediaElement::ReleaseAudioWakeLockIfExists() { 5034 MOZ_ASSERT(NS_IsMainThread()); 5035 if (mWakeLock) { 5036 const uint32_t delayMs = 5037 StaticPrefs::media_wakelock_audio_delay_releasing_ms(); 5038 // Already in shutdown or no delay, release wakelock directly. 5039 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed) || 5040 delayMs == 0) { 5041 ReleaseAudioWakeLockInternal(); 5042 return; 5043 } 5044 if (mAudioWakelockReleaseScheduler) { 5045 return; 5046 } 5047 LOG(LogLevel::Debug, 5048 ("%p Delaying audio wakelock release by %u ms", this, delayMs)); 5049 AwakeTimeStamp target = 5050 AwakeTimeStamp ::Now() + AwakeTimeDuration::FromMilliseconds(delayMs); 5051 mAudioWakelockReleaseScheduler.emplace( 5052 DelayedScheduler<AwakeTimeStamp>{GetMainThreadSerialEventTarget()}); 5053 mAudioWakelockReleaseScheduler->Ensure( 5054 target, 5055 [self = RefPtr<HTMLMediaElement>(this), this]() { 5056 mAudioWakelockReleaseScheduler->CompleteRequest(); 5057 ReleaseAudioWakeLockInternal(); 5058 }, 5059 [self = RefPtr<HTMLMediaElement>(this), this]() { 5060 LOG(LogLevel::Debug, 5061 ("%p Fail to delay audio wakelock releasing?!", this)); 5062 mAudioWakelockReleaseScheduler->CompleteRequest(); 5063 ReleaseAudioWakeLockInternal(); 5064 }); 5065 } 5066 } 5067 5068 void HTMLMediaElement::ReleaseAudioWakeLockInternal() { 5069 MOZ_ASSERT(NS_IsMainThread()); 5070 if (mAudioWakelockReleaseScheduler) { 5071 mAudioWakelockReleaseScheduler->Reset(); 5072 mAudioWakelockReleaseScheduler.reset(); 5073 } 5074 LOG(LogLevel::Debug, ("%p release audio wakelock", this)); 5075 ErrorResult rv; 5076 mWakeLock->Unlock(rv); 5077 rv.SuppressException(); 5078 mWakeLock = nullptr; 5079 } 5080 5081 void HTMLMediaElement::WakeLockRelease() { 5082 if (mWakeLock) { 5083 ReleaseAudioWakeLockInternal(); 5084 } 5085 } 5086 5087 void HTMLMediaElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { 5088 if (!this->Controls() || !aVisitor.mEvent->mFlags.mIsTrusted) { 5089 nsGenericHTMLElement::GetEventTargetParent(aVisitor); 5090 return; 5091 } 5092 5093 // We will need to trap pointer, touch, and mouse events within the media 5094 // element, allowing media control exclusive consumption on these events, 5095 // and preventing the content from handling them. 5096 switch (aVisitor.mEvent->mMessage) { 5097 case eTouchRawUpdate: 5098 MOZ_FALLTHROUGH_ASSERT( 5099 "eTouchRawUpdate event shouldn't be dispatched into the DOM"); 5100 // Always prevent touchmove captured in video element from being handled by 5101 // content, since we always do that for touchstart. 5102 case eTouchMove: 5103 case eTouchEnd: 5104 case eTouchStart: { 5105 // We stop the propagation (both at the capture and the bubbling phase) 5106 // when the original target is the part of the ControlBar or the 5107 // ClickToPlay button. 5108 if (ShadowRoot* shadowRoot = GetShadowRoot()) { 5109 nsINode* node = 5110 nsINode::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget); 5111 const bool trap = [&] { 5112 if (node->SubtreeRoot() != shadowRoot) { 5113 return false; 5114 } 5115 5116 for (auto* node : node->InclusiveAncestorsOfType<Element>()) { 5117 auto* id = node->GetID(); 5118 if (!id) { 5119 continue; 5120 } 5121 if (id == nsGkAtoms::clickToPlay || id == nsGkAtoms::controlBar) { 5122 return true; 5123 } 5124 } 5125 return false; 5126 }(); 5127 5128 if (trap) { 5129 aVisitor.mCanHandle = false; 5130 } else { 5131 nsGenericHTMLElement::GetEventTargetParent(aVisitor); 5132 } 5133 return; 5134 } 5135 nsGenericHTMLElement::GetEventTargetParent(aVisitor); 5136 return; 5137 } 5138 case ePointerDown: 5139 case ePointerUp: 5140 case ePointerClick: 5141 case eMouseDoubleClick: 5142 case eMouseDown: 5143 case eMouseUp: 5144 aVisitor.mCanHandle = false; 5145 return; 5146 5147 // The *move events however are only consumed when the range input is being 5148 // dragged. 5149 case eMouseRawUpdate: 5150 MOZ_FALLTHROUGH_ASSERT( 5151 "eMouseRawUpdate event shouldn't be dispatched into the DOM"); 5152 case ePointerMove: 5153 case ePointerRawUpdate: 5154 case eMouseMove: { 5155 nsINode* node = 5156 nsINode::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget); 5157 if (MOZ_UNLIKELY(!node)) { 5158 return; 5159 } 5160 HTMLInputElement* el = nullptr; 5161 if (node->ChromeOnlyAccess()) { 5162 if (node->IsHTMLElement(nsGkAtoms::input)) { 5163 // The node is a <input type="range"> 5164 el = static_cast<HTMLInputElement*>(node); 5165 } else if (node->GetParentNode() && 5166 node->GetParentNode()->IsHTMLElement(nsGkAtoms::input)) { 5167 // The node is a child of <input type="range"> 5168 el = static_cast<HTMLInputElement*>(node->GetParentNode()); 5169 } 5170 } 5171 if (el && el->IsDraggingRange()) { 5172 aVisitor.mCanHandle = false; 5173 return; 5174 } 5175 nsGenericHTMLElement::GetEventTargetParent(aVisitor); 5176 return; 5177 } 5178 default: 5179 nsGenericHTMLElement::GetEventTargetParent(aVisitor); 5180 return; 5181 } 5182 } 5183 5184 bool HTMLMediaElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, 5185 const nsAString& aValue, 5186 nsIPrincipal* aMaybeScriptedPrincipal, 5187 nsAttrValue& aResult) { 5188 if (aNamespaceID == kNameSpaceID_None) { 5189 if (aAttribute == nsGkAtoms::crossorigin) { 5190 ParseCORSValue(aValue, aResult); 5191 return true; 5192 } 5193 if (aAttribute == nsGkAtoms::preload) { 5194 return aResult.ParseEnumValue(aValue, kPreloadTable, false, 5195 // The default value is "auto" if aValue is 5196 // not a recognised value. 5197 kPreloadDefaultType); 5198 } 5199 } 5200 5201 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, 5202 aMaybeScriptedPrincipal, aResult); 5203 } 5204 5205 void HTMLMediaElement::DoneCreatingElement() { 5206 if (HasAttr(nsGkAtoms::muted)) { 5207 mMuted |= MUTED_BY_CONTENT; 5208 } 5209 } 5210 5211 bool HTMLMediaElement::IsHTMLFocusable(IsFocusableFlags aFlags, 5212 bool* aIsFocusable, int32_t* aTabIndex) { 5213 if (nsGenericHTMLElement::IsHTMLFocusable(aFlags, aIsFocusable, aTabIndex)) { 5214 return true; 5215 } 5216 5217 *aIsFocusable = true; 5218 return false; 5219 } 5220 5221 int32_t HTMLMediaElement::TabIndexDefault() { return 0; } 5222 5223 void HTMLMediaElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, 5224 const nsAttrValue* aValue, 5225 const nsAttrValue* aOldValue, 5226 nsIPrincipal* aMaybeScriptedPrincipal, 5227 bool aNotify) { 5228 if (aNameSpaceID == kNameSpaceID_None) { 5229 if (aName == nsGkAtoms::src) { 5230 mSrcMediaSource = nullptr; 5231 nsAttrValueOrString srcVal(aValue); 5232 mSrcAttrTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal( 5233 this, srcVal.String(), aMaybeScriptedPrincipal); 5234 if (aValue) { 5235 nsCOMPtr<nsIURI> uri; 5236 NewURIFromString(srcVal.String(), getter_AddRefs(uri)); 5237 if (uri && IsMediaSourceURI(uri)) { 5238 nsresult rv = NS_GetSourceForMediaSourceURI( 5239 uri, getter_AddRefs(mSrcMediaSource)); 5240 if (NS_FAILED(rv)) { 5241 nsAutoString spec; 5242 GetCurrentSrc(spec); 5243 AutoTArray<nsString, 1> params = {spec}; 5244 ReportLoadError("MediaLoadInvalidURI", params); 5245 } 5246 } 5247 } 5248 } else if (aName == nsGkAtoms::autoplay) { 5249 if (aNotify) { 5250 if (aValue) { 5251 #if defined(MOZ_WIDGET_ANDROID) 5252 StartObservingGVAutoplayIfNeeded(); 5253 #endif 5254 StopSuspendingAfterFirstFrame(); 5255 CheckAutoplayDataReady(); 5256 } 5257 // This attribute can affect AddRemoveSelfReference 5258 AddRemoveSelfReference(); 5259 UpdatePreloadAction(JSCallingLocation::Get()); 5260 } 5261 } else if (aName == nsGkAtoms::preload) { 5262 UpdatePreloadAction(JSCallingLocation::Get()); 5263 } else if (aName == nsGkAtoms::loop) { 5264 if (mDecoder) { 5265 mDecoder->SetLooping(!!aValue); 5266 } 5267 } else if (aName == nsGkAtoms::controls && IsInComposedDoc()) { 5268 NotifyUAWidgetSetupOrChange(); 5269 } 5270 } 5271 5272 // Since AfterMaybeChangeAttr may call DoLoad, make sure that it is called 5273 // *after* any possible changes to mSrcMediaSource. 5274 if (aValue) { 5275 AfterMaybeChangeAttr(aNameSpaceID, aName, aNotify); 5276 } 5277 5278 return nsGenericHTMLElement::AfterSetAttr( 5279 aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); 5280 } 5281 5282 void HTMLMediaElement::OnAttrSetButNotChanged(int32_t aNamespaceID, 5283 nsAtom* aName, 5284 const nsAttrValueOrString& aValue, 5285 bool aNotify) { 5286 AfterMaybeChangeAttr(aNamespaceID, aName, aNotify); 5287 5288 return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID, aName, 5289 aValue, aNotify); 5290 } 5291 5292 void HTMLMediaElement::AfterMaybeChangeAttr(int32_t aNamespaceID, nsAtom* aName, 5293 bool aNotify) { 5294 if (aNamespaceID == kNameSpaceID_None) { 5295 if (aName == nsGkAtoms::src) { 5296 DoLoad(); 5297 } 5298 } 5299 } 5300 5301 nsresult HTMLMediaElement::BindToTree(BindContext& aContext, nsINode& aParent) { 5302 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent); 5303 5304 if (IsInComposedDoc()) { 5305 // Construct Shadow Root so web content can be hidden in the DOM. 5306 AttachAndSetUAShadowRoot(); 5307 5308 // The preload action depends on the value of the autoplay attribute. 5309 // It's value may have changed, so update it. 5310 UpdatePreloadAction(JSCallingLocation::Get()); 5311 } 5312 5313 NotifyDecoderActivityChanges(); 5314 mMediaControlKeyListener->UpdateOwnerBrowsingContextIfNeeded(); 5315 return rv; 5316 } 5317 5318 void HTMLMediaElement::UnbindFromTree(UnbindContext& aContext) { 5319 mVisibilityState = Visibility::Untracked; 5320 5321 if (IsInComposedDoc()) { 5322 NotifyUAWidgetTeardown(); 5323 } 5324 5325 nsGenericHTMLElement::UnbindFromTree(aContext); 5326 5327 MOZ_ASSERT(IsActuallyInvisible()); 5328 NotifyDecoderActivityChanges(); 5329 5330 // https://html.spec.whatwg.org/#playing-the-media-resource:remove-an-element-from-a-document 5331 // 5332 // Dispatch a task to run once we're in a stable state which ensures we're 5333 // paused if we're no longer in a document. Note that we need to dispatch this 5334 // even if there are other tasks in flight for this because these can be 5335 // cancelled if there's a new load. 5336 // 5337 // FIXME(emilio): Per that spec section, we should only do this if we used to 5338 // be connected, though other browsers match our current behavior... 5339 // 5340 // Also, https://github.com/whatwg/html/issues/4928 5341 nsCOMPtr<nsIRunnable> task = 5342 NS_NewRunnableFunction("dom::HTMLMediaElement::UnbindFromTree", 5343 [self = RefPtr<HTMLMediaElement>(this)]() { 5344 if (!self->IsInComposedDoc()) { 5345 self->PauseInternal(); 5346 self->mMediaControlKeyListener->StopIfNeeded(); 5347 } 5348 }); 5349 RunInStableState(task); 5350 } 5351 5352 /* static */ 5353 CanPlayStatus HTMLMediaElement::GetCanPlay( 5354 const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics) { 5355 Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType); 5356 if (!containerType) { 5357 return CANPLAY_NO; 5358 } 5359 CanPlayStatus status = 5360 DecoderTraits::CanHandleContainerType(*containerType, aDiagnostics); 5361 if (status == CANPLAY_YES && 5362 (*containerType).ExtendedType().Codecs().IsEmpty()) { 5363 // Per spec: 'Generally, a user agent should never return "probably" for a 5364 // type that allows the `codecs` parameter if that parameter is not 5365 // present.' As all our currently-supported types allow for `codecs`, we can 5366 // do this check here. 5367 // TODO: Instead, missing `codecs` should be checked in each decoder's 5368 // `IsSupportedType` call from `CanHandleCodecsType()`. 5369 // See bug 1399023. 5370 return CANPLAY_MAYBE; 5371 } 5372 return status; 5373 } 5374 5375 void HTMLMediaElement::CanPlayType(const nsAString& aType, nsAString& aResult) { 5376 DecoderDoctorDiagnostics diagnostics; 5377 CanPlayStatus canPlay = GetCanPlay(aType, &diagnostics); 5378 diagnostics.StoreFormatDiagnostics(OwnerDoc(), aType, canPlay != CANPLAY_NO, 5379 __func__); 5380 switch (canPlay) { 5381 case CANPLAY_NO: 5382 aResult.Truncate(); 5383 break; 5384 case CANPLAY_YES: 5385 aResult.AssignLiteral("probably"); 5386 break; 5387 case CANPLAY_MAYBE: 5388 aResult.AssignLiteral("maybe"); 5389 break; 5390 default: 5391 MOZ_ASSERT_UNREACHABLE("Unexpected case."); 5392 break; 5393 } 5394 5395 LOG(LogLevel::Debug, 5396 ("%p CanPlayType(%s) = \"%s\"", this, NS_ConvertUTF16toUTF8(aType).get(), 5397 NS_ConvertUTF16toUTF8(aResult).get())); 5398 } 5399 5400 void HTMLMediaElement::AssertReadyStateIsNothing() { 5401 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 5402 if (mReadyState != HAVE_NOTHING) { 5403 char buf[1024]; 5404 SprintfLiteral(buf, 5405 "readyState=%d networkState=%d mLoadWaitStatus=%d " 5406 "mSourceLoadCandidate=%d " 5407 "mIsLoadingFromSourceChildren=%d mPreloadAction=%d " 5408 "mSuspendedForPreloadNone=%d error=%d", 5409 int(mReadyState), int(mNetworkState), int(mLoadWaitStatus), 5410 !!mSourceLoadCandidate, mIsLoadingFromSourceChildren, 5411 int(mPreloadAction), mSuspendedForPreloadNone, 5412 GetError() ? GetError()->Code() : 0); 5413 MOZ_CRASH_UNSAFE_PRINTF("ReadyState should be HAVE_NOTHING! %s", buf); 5414 } 5415 #endif 5416 } 5417 5418 nsresult HTMLMediaElement::InitializeDecoderAsClone( 5419 ChannelMediaDecoder* aOriginal) { 5420 NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set"); 5421 NS_ASSERTION(mDecoder == nullptr, "Shouldn't have a decoder"); 5422 AssertReadyStateIsNothing(); 5423 5424 MediaDecoderInit decoderInit( 5425 this, this, mMuted ? 0.0 : mVolume, mPreservesPitch, 5426 ClampPlaybackRate(mPlaybackRate), 5427 mPreloadAction == HTMLMediaElement::PRELOAD_METADATA, mHasSuspendTaint, 5428 HasAttr(nsGkAtoms::loop), aOriginal->ContainerType()); 5429 5430 RefPtr<ChannelMediaDecoder> decoder = aOriginal->Clone(decoderInit); 5431 if (!decoder) { 5432 return NS_ERROR_FAILURE; 5433 } 5434 5435 LOG(LogLevel::Debug, 5436 ("%p Cloned decoder %p from %p", this, decoder.get(), aOriginal)); 5437 5438 return FinishDecoderSetup(decoder); 5439 } 5440 5441 template <typename DecoderType, typename... LoadArgs> 5442 nsresult HTMLMediaElement::SetupDecoder(DecoderType* aDecoder, 5443 LoadArgs&&... aArgs) { 5444 LOG(LogLevel::Debug, ("%p Created decoder %p for type %s", this, aDecoder, 5445 aDecoder->ContainerType().OriginalString().Data())); 5446 5447 nsresult rv = aDecoder->Load(std::forward<LoadArgs>(aArgs)...); 5448 if (NS_FAILED(rv)) { 5449 aDecoder->Shutdown(); 5450 LOG(LogLevel::Debug, ("%p Failed to load for decoder %p", this, aDecoder)); 5451 return rv; 5452 } 5453 5454 rv = FinishDecoderSetup(aDecoder); 5455 // Only ChannelMediaDecoder supports resource cloning. 5456 if (std::is_same_v<DecoderType, ChannelMediaDecoder> && NS_SUCCEEDED(rv)) { 5457 AddMediaElementToURITable(); 5458 NS_ASSERTION( 5459 MediaElementTableCount(this, mLoadingSrc) == 1, 5460 "Media element should have single table entry if decode initialized"); 5461 } 5462 5463 return rv; 5464 } 5465 5466 nsresult HTMLMediaElement::InitializeDecoderForChannel( 5467 nsIChannel* aChannel, nsIStreamListener** aListener) { 5468 NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set"); 5469 AssertReadyStateIsNothing(); 5470 5471 DecoderDoctorDiagnostics diagnostics; 5472 5473 nsAutoCString mimeType; 5474 aChannel->GetContentType(mimeType); 5475 NS_ASSERTION(!mimeType.IsEmpty(), "We should have the Content-Type."); 5476 NS_ConvertUTF8toUTF16 mimeUTF16(mimeType); 5477 5478 RefPtr<HTMLMediaElement> self = this; 5479 auto reportCanPlay = [&, self](bool aCanPlay) { 5480 diagnostics.StoreFormatDiagnostics(self->OwnerDoc(), mimeUTF16, aCanPlay, 5481 __func__); 5482 if (!aCanPlay) { 5483 nsAutoString src; 5484 self->GetCurrentSrc(src); 5485 AutoTArray<nsString, 2> params = {mimeUTF16, src}; 5486 self->ReportLoadError("MediaLoadUnsupportedMimeType", params); 5487 } 5488 }; 5489 5490 auto onExit = MakeScopeExit([self] { 5491 if (self->mChannelLoader) { 5492 self->mChannelLoader->Done(); 5493 self->mChannelLoader = nullptr; 5494 } 5495 }); 5496 5497 Maybe<MediaContainerType> containerType = MakeMediaContainerType(mimeType); 5498 if (!containerType) { 5499 reportCanPlay(false); 5500 return NS_ERROR_FAILURE; 5501 } 5502 5503 MediaDecoderInit decoderInit( 5504 this, this, mMuted ? 0.0 : mVolume, mPreservesPitch, 5505 ClampPlaybackRate(mPlaybackRate), 5506 mPreloadAction == HTMLMediaElement::PRELOAD_METADATA, mHasSuspendTaint, 5507 HasAttr(nsGkAtoms::loop), *containerType); 5508 5509 #ifdef MOZ_ANDROID_HLS_SUPPORT 5510 if (HLSDecoder::IsSupportedType(*containerType)) { 5511 RefPtr<HLSDecoder> decoder = HLSDecoder::Create(decoderInit); 5512 if (!decoder) { 5513 reportCanPlay(false); 5514 return NS_ERROR_OUT_OF_MEMORY; 5515 } 5516 reportCanPlay(true); 5517 return SetupDecoder(decoder.get(), aChannel); 5518 } 5519 #endif 5520 5521 RefPtr<ChannelMediaDecoder> decoder = 5522 ChannelMediaDecoder::Create(decoderInit, &diagnostics); 5523 if (!decoder) { 5524 reportCanPlay(false); 5525 return NS_ERROR_FAILURE; 5526 } 5527 5528 reportCanPlay(true); 5529 bool isPrivateBrowsing = NodePrincipal()->GetIsInPrivateBrowsing(); 5530 return SetupDecoder(decoder.get(), aChannel, isPrivateBrowsing, aListener); 5531 } 5532 5533 nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder) { 5534 ChangeNetworkState(NETWORK_LOADING); 5535 5536 // Set mDecoder now so if methods like GetCurrentSrc get called between 5537 // here and Load(), they work. 5538 SetDecoder(aDecoder); 5539 5540 // Notify the decoder of the initial activity status. 5541 NotifyDecoderActivityChanges(); 5542 5543 // Update decoder principal before we start decoding, since it 5544 // can affect how we feed data to MediaStreams 5545 NotifyDecoderPrincipalChanged(); 5546 5547 // Set sink device if we have one. Otherwise the default is used. 5548 if (mSink.second) { 5549 mDecoder->SetSink(mSink.second); 5550 } 5551 5552 if (mMediaKeys) { 5553 if (mMediaKeys->GetCDMProxy()) { 5554 mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy()); 5555 } else { 5556 // CDM must have crashed. 5557 ShutdownDecoder(); 5558 return NS_ERROR_FAILURE; 5559 } 5560 } 5561 5562 if (mChannelLoader) { 5563 mChannelLoader->Done(); 5564 mChannelLoader = nullptr; 5565 } 5566 5567 // We may want to suspend the new stream now. 5568 // This will also do an AddRemoveSelfReference. 5569 NotifyOwnerDocumentActivityChanged(); 5570 5571 if (!mDecoder) { 5572 // NotifyOwnerDocumentActivityChanged may shutdown the decoder if the 5573 // owning document is inactive and we're in the EME case. We could try and 5574 // handle this, but at the time of writing it's a pretty niche case, so just 5575 // bail. 5576 return NS_ERROR_FAILURE; 5577 } 5578 5579 if (mSuspendedByInactiveDocOrDocshell) { 5580 mDecoder->Suspend(); 5581 } 5582 5583 if (!mPaused) { 5584 SetPlayedOrSeeked(true); 5585 if (!mSuspendedByInactiveDocOrDocshell) { 5586 mDecoder->Play(); 5587 } 5588 } 5589 5590 MaybeBeginCloningVisually(); 5591 5592 return NS_OK; 5593 } 5594 5595 void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags) { 5596 if (!mSrcStream) { 5597 return; 5598 } 5599 5600 bool shouldPlay = !(aFlags & REMOVING_SRC_STREAM) && !mPaused && 5601 !mSuspendedByInactiveDocOrDocshell; 5602 if (shouldPlay == mSrcStreamIsPlaying) { 5603 return; 5604 } 5605 mSrcStreamIsPlaying = shouldPlay; 5606 5607 LOG(LogLevel::Debug, 5608 ("MediaElement %p %s playback of DOMMediaStream %p", this, 5609 shouldPlay ? "Setting up" : "Removing", mSrcStream.get())); 5610 5611 if (shouldPlay) { 5612 mSrcStreamPlaybackEnded = false; 5613 mSrcStreamReportPlaybackEnded = false; 5614 5615 if (mMediaStreamRenderer) { 5616 mMediaStreamRenderer->Start(); 5617 } 5618 if (mSecondaryMediaStreamRenderer) { 5619 mSecondaryMediaStreamRenderer->Start(); 5620 } 5621 5622 SetCapturedOutputStreamsEnabled(true); // Unmute 5623 // If the input is a media stream, we don't check its data and always regard 5624 // it as audible when it's playing. 5625 SetAudibleState(true); 5626 } else { 5627 if (mMediaStreamRenderer) { 5628 mMediaStreamRenderer->Stop(); 5629 } 5630 if (mSecondaryMediaStreamRenderer) { 5631 mSecondaryMediaStreamRenderer->Stop(); 5632 } 5633 SetCapturedOutputStreamsEnabled(false); // Mute 5634 } 5635 } 5636 5637 void HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying() { 5638 if (!mMediaStreamRenderer) { 5639 // Notifications are async, the renderer could have been cleared. 5640 return; 5641 } 5642 5643 mMediaStreamRenderer->SetProgressingCurrentTime(IsPotentiallyPlaying()); 5644 } 5645 5646 void HTMLMediaElement::UpdateSrcStreamTime() { 5647 MOZ_ASSERT(NS_IsMainThread()); 5648 5649 if (mSrcStreamPlaybackEnded) { 5650 // We do a separate FireTimeUpdate() when this is set. 5651 return; 5652 } 5653 5654 FireTimeUpdate(TimeupdateType::ePeriodic); 5655 } 5656 5657 void HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream) { 5658 NS_ASSERTION(!mSrcStream, "Should have been ended already"); 5659 5660 mLoadingSrc = nullptr; 5661 mSrcStream = aStream; 5662 5663 VideoFrameContainer* container = GetVideoFrameContainer(); 5664 RefPtr<FirstFrameVideoOutput> firstFrameOutput = 5665 container ? MakeAndAddRef<FirstFrameVideoOutput>(container, 5666 AbstractMainThread()) 5667 : nullptr; 5668 mMediaStreamRenderer = MakeAndAddRef<MediaStreamRenderer>( 5669 AbstractMainThread(), container, firstFrameOutput, this); 5670 mWatchManager.Watch(mPaused, 5671 &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying); 5672 mWatchManager.Watch(mReadyState, 5673 &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying); 5674 mWatchManager.Watch(mSrcStreamPlaybackEnded, 5675 &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying); 5676 mWatchManager.Watch(mSrcStreamPlaybackEnded, 5677 &HTMLMediaElement::UpdateSrcStreamReportPlaybackEnded); 5678 mWatchManager.Watch(mMediaStreamRenderer->CurrentGraphTime(), 5679 &HTMLMediaElement::UpdateSrcStreamTime); 5680 SetVolumeInternal(); 5681 if (mSink.second) { 5682 mMediaStreamRenderer->SetAudioOutputDevice(mSink.second); 5683 } 5684 5685 UpdateSrcMediaStreamPlaying(); 5686 UpdateSrcStreamPotentiallyPlaying(); 5687 mSrcStreamVideoPrincipal = NodePrincipal(); 5688 5689 // If we pause this media element, track changes in the underlying stream 5690 // will continue to fire events at this element and alter its track list. 5691 // That's simpler than delaying the events, but probably confusing... 5692 nsTArray<RefPtr<MediaStreamTrack>> tracks; 5693 mSrcStream->GetTracks(tracks); 5694 for (const RefPtr<MediaStreamTrack>& track : tracks) { 5695 NotifyMediaStreamTrackAdded(track); 5696 } 5697 5698 mMediaStreamTrackListener = new MediaStreamTrackListener(this); 5699 mSrcStream->RegisterTrackListener(mMediaStreamTrackListener.get()); 5700 5701 ChangeNetworkState(NETWORK_IDLE); 5702 ChangeDelayLoadStatus(false); 5703 5704 // FirstFrameLoaded() will be called when the stream has tracks. 5705 } 5706 5707 void HTMLMediaElement::EndSrcMediaStreamPlayback() { 5708 MOZ_ASSERT(mSrcStream); 5709 5710 UpdateSrcMediaStreamPlaying(REMOVING_SRC_STREAM); 5711 5712 if (mSelectedVideoStreamTrack) { 5713 mSelectedVideoStreamTrack->RemovePrincipalChangeObserver(this); 5714 } 5715 mSelectedVideoStreamTrack = nullptr; 5716 5717 MOZ_ASSERT_IF(mSecondaryMediaStreamRenderer, 5718 !mMediaStreamRenderer == !mSecondaryMediaStreamRenderer); 5719 if (mMediaStreamRenderer) { 5720 mWatchManager.Unwatch(mPaused, 5721 &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying); 5722 mWatchManager.Unwatch(mReadyState, 5723 &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying); 5724 mWatchManager.Unwatch(mSrcStreamPlaybackEnded, 5725 &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying); 5726 mWatchManager.Unwatch( 5727 mSrcStreamPlaybackEnded, 5728 &HTMLMediaElement::UpdateSrcStreamReportPlaybackEnded); 5729 mWatchManager.Unwatch(mMediaStreamRenderer->CurrentGraphTime(), 5730 &HTMLMediaElement::UpdateSrcStreamTime); 5731 mMediaStreamRenderer->Shutdown(); 5732 mMediaStreamRenderer = nullptr; 5733 } 5734 if (mSecondaryMediaStreamRenderer) { 5735 mSecondaryMediaStreamRenderer->Shutdown(); 5736 mSecondaryMediaStreamRenderer = nullptr; 5737 } 5738 5739 mSrcStream->UnregisterTrackListener(mMediaStreamTrackListener.get()); 5740 mMediaStreamTrackListener = nullptr; 5741 mSrcStreamPlaybackEnded = false; 5742 mSrcStreamReportPlaybackEnded = false; 5743 mSrcStreamVideoPrincipal = nullptr; 5744 5745 mSrcStream = nullptr; 5746 } 5747 5748 static already_AddRefed<AudioTrack> CreateAudioTrack( 5749 AudioStreamTrack* aStreamTrack, nsIGlobalObject* aOwnerGlobal) { 5750 nsAutoString id; 5751 nsAutoString label; 5752 aStreamTrack->GetId(id); 5753 aStreamTrack->GetLabel(label, CallerType::System); 5754 5755 return MediaTrackList::CreateAudioTrack(aOwnerGlobal, id, u"main"_ns, label, 5756 u""_ns, true, aStreamTrack); 5757 } 5758 5759 static already_AddRefed<VideoTrack> CreateVideoTrack( 5760 VideoStreamTrack* aStreamTrack, nsIGlobalObject* aOwnerGlobal) { 5761 nsAutoString id; 5762 nsAutoString label; 5763 aStreamTrack->GetId(id); 5764 aStreamTrack->GetLabel(label, CallerType::System); 5765 5766 return MediaTrackList::CreateVideoTrack(aOwnerGlobal, id, u"main"_ns, label, 5767 u""_ns, aStreamTrack); 5768 } 5769 5770 void HTMLMediaElement::NotifyMediaStreamTrackAdded( 5771 const RefPtr<MediaStreamTrack>& aTrack) { 5772 MOZ_ASSERT(aTrack); 5773 5774 if (aTrack->Ended()) { 5775 return; 5776 } 5777 5778 #ifdef DEBUG 5779 nsAutoString id; 5780 aTrack->GetId(id); 5781 5782 LOG(LogLevel::Debug, ("%p, Adding %sTrack with id %s", this, 5783 aTrack->AsAudioStreamTrack() ? "Audio" : "Video", 5784 NS_ConvertUTF16toUTF8(id).get())); 5785 #endif 5786 5787 if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) { 5788 MOZ_DIAGNOSTIC_ASSERT(AudioTracks(), "Element can't have been unlinked"); 5789 RefPtr<AudioTrack> audioTrack = 5790 CreateAudioTrack(t, AudioTracks()->GetOwnerGlobal()); 5791 AudioTracks()->AddTrack(audioTrack); 5792 } else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) { 5793 // TODO: Fix this per the spec on bug 1273443. 5794 if (!IsVideo()) { 5795 return; 5796 } 5797 MOZ_DIAGNOSTIC_ASSERT(VideoTracks(), "Element can't have been unlinked"); 5798 RefPtr<VideoTrack> videoTrack = 5799 CreateVideoTrack(t, VideoTracks()->GetOwnerGlobal()); 5800 VideoTracks()->AddTrack(videoTrack); 5801 // New MediaStreamTrack added, set the new added video track as selected 5802 // video track when there is no selected track. 5803 if (VideoTracks()->SelectedIndex() == -1) { 5804 MOZ_ASSERT(!mSelectedVideoStreamTrack); 5805 videoTrack->SetEnabledInternal(true, dom::MediaTrack::FIRE_NO_EVENTS); 5806 } 5807 } 5808 5809 // The set of enabled AudioTracks and selected video track might have changed. 5810 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal); 5811 AbstractThread::DispatchDirectTask( 5812 NewRunnableMethod("HTMLMediaElement::FirstFrameLoaded", this, 5813 &HTMLMediaElement::FirstFrameLoaded)); 5814 } 5815 5816 void HTMLMediaElement::NotifyMediaStreamTrackRemoved( 5817 const RefPtr<MediaStreamTrack>& aTrack) { 5818 MOZ_ASSERT(aTrack); 5819 5820 nsAutoString id; 5821 aTrack->GetId(id); 5822 5823 LOG(LogLevel::Debug, ("%p, Removing %sTrack with id %s", this, 5824 aTrack->AsAudioStreamTrack() ? "Audio" : "Video", 5825 NS_ConvertUTF16toUTF8(id).get())); 5826 5827 MOZ_DIAGNOSTIC_ASSERT(AudioTracks() && VideoTracks(), 5828 "Element can't have been unlinked"); 5829 if (dom::MediaTrack* t = AudioTracks()->GetTrackById(id)) { 5830 AudioTracks()->RemoveTrack(t); 5831 } else if (dom::MediaTrack* t = VideoTracks()->GetTrackById(id)) { 5832 VideoTracks()->RemoveTrack(t); 5833 } else { 5834 NS_ASSERTION(aTrack->AsVideoStreamTrack() && !IsVideo(), 5835 "MediaStreamTrack ended but did not exist in track lists. " 5836 "This is only allowed if a video element ends and we are an " 5837 "audio element."); 5838 return; 5839 } 5840 } 5841 5842 void HTMLMediaElement::ProcessMediaFragmentURI() { 5843 if (!mLoadingSrc) { 5844 mFragmentStart = mFragmentEnd = -1.0; 5845 return; 5846 } 5847 MediaFragmentURIParser parser(mLoadingSrc); 5848 5849 if (mDecoder && parser.HasEndTime()) { 5850 mFragmentEnd = parser.GetEndTime(); 5851 } 5852 5853 if (parser.HasStartTime()) { 5854 SetCurrentTime(parser.GetStartTime()); 5855 mFragmentStart = parser.GetStartTime(); 5856 } 5857 } 5858 5859 void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo, 5860 UniquePtr<const MetadataTags> aTags) { 5861 MOZ_ASSERT(NS_IsMainThread()); 5862 5863 if (mDecoder) { 5864 ConstructMediaTracks(aInfo); 5865 } 5866 5867 SetMediaInfo(*aInfo); 5868 5869 mIsEncrypted = 5870 aInfo->IsEncrypted() || mPendingEncryptedInitData.IsEncrypted(); 5871 mTags = std::move(aTags); 5872 mLoadedDataFired = false; 5873 ChangeReadyState(HAVE_METADATA); 5874 5875 // Add output tracks synchronously now to be sure they're available in 5876 // "loadedmetadata" event handlers. 5877 UpdateOutputTrackSources(); 5878 5879 QueueEvent(u"durationchange"_ns); 5880 if (IsVideo() && HasVideo()) { 5881 QueueEvent(u"resize"_ns); 5882 Invalidate(ImageSizeChanged::No, Some(mMediaInfo.mVideo.mDisplay), 5883 ForceInvalidate::No); 5884 } 5885 NS_ASSERTION(!HasVideo() || (mMediaInfo.mVideo.mDisplay.width > 0 && 5886 mMediaInfo.mVideo.mDisplay.height > 0), 5887 "Video resolution must be known on 'loadedmetadata'"); 5888 QueueEvent(u"loadedmetadata"_ns); 5889 5890 if (mDecoder && mDecoder->IsTransportSeekable() && 5891 mDecoder->IsMediaSeekable()) { 5892 ProcessMediaFragmentURI(); 5893 mDecoder->SetFragmentEndTime(mFragmentEnd); 5894 } 5895 if (mIsEncrypted) { 5896 // We only support playback of encrypted content via MSE by default. 5897 if (!mMediaSource && Preferences::GetBool("media.eme.mse-only", true)) { 5898 DecodeError( 5899 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, 5900 "Encrypted content not supported outside of MSE")); 5901 return; 5902 } 5903 5904 // Dispatch a distinct 'encrypted' event for each initData we have. 5905 for (const auto& initData : mPendingEncryptedInitData.mInitDatas) { 5906 DispatchEncrypted(initData.mInitData, initData.mType); 5907 } 5908 mPendingEncryptedInitData.Reset(); 5909 } 5910 5911 if (IsVideo() && aInfo->HasVideo()) { 5912 // We are a video element playing video so update the screen wakelock 5913 NotifyOwnerDocumentActivityChanged(); 5914 } 5915 5916 if (mDefaultPlaybackStartPosition != 0.0) { 5917 SetCurrentTime(mDefaultPlaybackStartPosition); 5918 mDefaultPlaybackStartPosition = 0.0; 5919 } 5920 5921 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal); 5922 } 5923 5924 void HTMLMediaElement::FirstFrameLoaded() { 5925 LOG(LogLevel::Debug, 5926 ("%p, FirstFrameLoaded() mFirstFrameLoaded=%d mWaitingForKey=%d", this, 5927 mFirstFrameLoaded.Ref(), mWaitingForKey)); 5928 5929 NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended"); 5930 5931 if (!mFirstFrameLoaded) { 5932 mFirstFrameLoaded = true; 5933 } 5934 5935 ChangeDelayLoadStatus(false); 5936 5937 if (ShouldSuspendDownloadAfterFirstFrameLoaded()) { 5938 LOG(LogLevel::Debug, ("%p Suspend decoder after first frame loaded", this)); 5939 mSuspendedAfterFirstFrame = true; 5940 mDecoder->Suspend(); 5941 } 5942 } 5943 5944 bool HTMLMediaElement::ShouldSuspendDownloadAfterFirstFrameLoaded() const { 5945 if (!mDecoder) { 5946 return false; 5947 } 5948 5949 // If the media is set to autoplay, avoid suspending, to preload as much as 5950 // possible. 5951 if (HasAttr(nsGkAtoms::autoplay)) { 5952 return false; 5953 } 5954 5955 // If the media is currently playing, do not suspend downloading. 5956 if (!mPaused) { 5957 return false; 5958 } 5959 5960 return mPreloadAction == HTMLMediaElement::PRELOAD_METADATA && 5961 mAllowSuspendAfterFirstFrame; 5962 } 5963 5964 void HTMLMediaElement::NetworkError(const MediaResult& aError) { 5965 if (mReadyState == HAVE_NOTHING) { 5966 NoSupportedMediaSourceError(aError.Description()); 5967 } else { 5968 Error(MEDIA_ERR_NETWORK); 5969 } 5970 } 5971 5972 void HTMLMediaElement::DecodeError(const MediaResult& aError) { 5973 nsAutoString src; 5974 GetCurrentSrc(src); 5975 AutoTArray<nsString, 1> params = {src}; 5976 ReportLoadError("MediaLoadDecodeError", params); 5977 5978 DecoderDoctorDiagnostics diagnostics; 5979 diagnostics.StoreDecodeError(OwnerDoc(), aError, src, __func__); 5980 5981 if (mIsLoadingFromSourceChildren) { 5982 mErrorSink->ResetError(); 5983 if (mSourceLoadCandidate) { 5984 DispatchAsyncSourceError(mSourceLoadCandidate, aError.Message()); 5985 QueueLoadFromSourceTask(); 5986 } else { 5987 NS_WARNING("Should know the source we were loading from!"); 5988 } 5989 } else if (mReadyState == HAVE_NOTHING) { 5990 NoSupportedMediaSourceError(aError.Description()); 5991 } else if (IsCORSSameOrigin()) { 5992 Error(MEDIA_ERR_DECODE, Some(aError)); 5993 } else { 5994 Error(MEDIA_ERR_DECODE, Some(MediaResult{NS_ERROR_DOM_MEDIA_DECODE_ERR, 5995 "Failed to decode media"})); 5996 } 5997 } 5998 5999 void HTMLMediaElement::DecodeWarning(const MediaResult& aError) { 6000 nsAutoString src; 6001 GetCurrentSrc(src); 6002 DecoderDoctorDiagnostics diagnostics; 6003 diagnostics.StoreDecodeWarning(OwnerDoc(), aError, src, __func__); 6004 } 6005 6006 bool HTMLMediaElement::HasError() const { return GetError(); } 6007 6008 void HTMLMediaElement::LoadAborted() { Error(MEDIA_ERR_ABORTED); } 6009 6010 void HTMLMediaElement::Error(uint16_t aErrorCode, 6011 const Maybe<MediaResult>& aResult) { 6012 mErrorSink->SetError(aErrorCode, aResult); 6013 ChangeDelayLoadStatus(false); 6014 UpdateAudioChannelPlayingState(); 6015 } 6016 6017 void HTMLMediaElement::PlaybackEnded() { 6018 // We changed state which can affect AddRemoveSelfReference 6019 AddRemoveSelfReference(); 6020 6021 NS_ASSERTION(!mDecoder || mDecoder->IsEnded(), 6022 "Decoder fired ended, but not in ended state"); 6023 6024 // IsPlaybackEnded() became true. 6025 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources); 6026 6027 if (mSrcStream) { 6028 LOG(LogLevel::Debug, 6029 ("%p, got duration by reaching the end of the resource", this)); 6030 mSrcStreamPlaybackEnded = true; 6031 QueueEvent(u"durationchange"_ns); 6032 } else { 6033 // mediacapture-main: 6034 // Setting the loop attribute has no effect since a MediaStream has no 6035 // defined end and therefore cannot be looped. 6036 if (HasAttr(nsGkAtoms::loop)) { 6037 SetCurrentTime(0); 6038 return; 6039 } 6040 } 6041 6042 FireTimeUpdate(TimeupdateType::eMandatory); 6043 6044 if (!mPaused) { 6045 Pause(); 6046 } 6047 6048 if (mSrcStream) { 6049 // A MediaStream that goes from inactive to active shall be eligible for 6050 // autoplay again according to the mediacapture-main spec. 6051 mCanAutoplayFlag = true; 6052 } 6053 6054 if (StaticPrefs::media_mediacontrol_stopcontrol_aftermediaends()) { 6055 mMediaControlKeyListener->StopIfNeeded(); 6056 } 6057 QueueEvent(u"ended"_ns); 6058 } 6059 6060 void HTMLMediaElement::UpdateSrcStreamReportPlaybackEnded() { 6061 mSrcStreamReportPlaybackEnded = mSrcStreamPlaybackEnded; 6062 } 6063 6064 void HTMLMediaElement::SeekStarted() { QueueEvent(u"seeking"_ns); } 6065 6066 void HTMLMediaElement::SeekCompleted() { 6067 mPlayingBeforeSeek = false; 6068 SetPlayedOrSeeked(true); 6069 if (mTextTrackManager) { 6070 mTextTrackManager->DidSeek(); 6071 } 6072 // https://html.spec.whatwg.org/multipage/media.html#seeking:dom-media-seek 6073 // (Step 16) 6074 // TODO (bug 1688131): run these steps in a stable state. 6075 FireTimeUpdate(TimeupdateType::eMandatory); 6076 QueueEvent(u"seeked"_ns); 6077 // We changed whether we're seeking so we need to AddRemoveSelfReference 6078 AddRemoveSelfReference(); 6079 if (mCurrentPlayRangeStart == -1.0) { 6080 mCurrentPlayRangeStart = CurrentTime(); 6081 } 6082 6083 if (mSeekDOMPromise) { 6084 AbstractMainThread()->Dispatch(NS_NewRunnableFunction( 6085 __func__, [promise = std::move(mSeekDOMPromise)] { 6086 promise->MaybeResolveWithUndefined(); 6087 })); 6088 } 6089 MOZ_ASSERT(!mSeekDOMPromise); 6090 } 6091 6092 void HTMLMediaElement::SeekAborted() { 6093 if (mSeekDOMPromise) { 6094 AbstractMainThread()->Dispatch(NS_NewRunnableFunction( 6095 __func__, [promise = std::move(mSeekDOMPromise)] { 6096 promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); 6097 })); 6098 } 6099 MOZ_ASSERT(!mSeekDOMPromise); 6100 } 6101 6102 void HTMLMediaElement::NotifySuspendedByCache(bool aSuspendedByCache) { 6103 LOG(LogLevel::Debug, 6104 ("%p, mDownloadSuspendedByCache=%d", this, aSuspendedByCache)); 6105 mDownloadSuspendedByCache = aSuspendedByCache; 6106 } 6107 6108 void HTMLMediaElement::DownloadSuspended() { 6109 if (mNetworkState == NETWORK_LOADING) { 6110 QueueEvent(u"progress"_ns); 6111 } 6112 ChangeNetworkState(NETWORK_IDLE); 6113 } 6114 6115 void HTMLMediaElement::DownloadResumed() { 6116 ChangeNetworkState(NETWORK_LOADING); 6117 } 6118 6119 void HTMLMediaElement::CheckProgress(bool aHaveNewProgress) { 6120 MOZ_ASSERT(NS_IsMainThread()); 6121 MOZ_ASSERT(mNetworkState == NETWORK_LOADING); 6122 6123 TimeStamp now = TimeStamp::Now(); 6124 6125 if (aHaveNewProgress) { 6126 mDataTime = now; 6127 } 6128 6129 // If this is the first progress, or PROGRESS_MS has passed since the last 6130 // progress event fired and more data has arrived since then, fire a 6131 // progress event. 6132 NS_ASSERTION( 6133 (mProgressTime.IsNull() && !aHaveNewProgress) || !mDataTime.IsNull(), 6134 "null TimeStamp mDataTime should not be used in comparison"); 6135 if (mProgressTime.IsNull() 6136 ? aHaveNewProgress 6137 : (now - mProgressTime >= 6138 TimeDuration::FromMilliseconds(PROGRESS_MS) && 6139 mDataTime > mProgressTime)) { 6140 QueueEvent(u"progress"_ns); 6141 // Going back 1ms ensures that future data will have now > mProgressTime, 6142 // and so will trigger another event. mDataTime is not reset because it 6143 // is still required to detect stalled; it is similarly offset by 6144 // 1ms to indicate the new data has not yet arrived. 6145 mProgressTime = now - TimeDuration::FromMilliseconds(1); 6146 if (mDataTime > mProgressTime) { 6147 mDataTime = mProgressTime; 6148 } 6149 if (!mProgressTimer) { 6150 NS_ASSERTION(aHaveNewProgress, 6151 "timer dispatched when there was no timer"); 6152 // Were stalled. Restart timer. 6153 StartProgressTimer(); 6154 if (!mLoadedDataFired) { 6155 ChangeDelayLoadStatus(true); 6156 } 6157 } 6158 // Download statistics may have been updated, force a recheck of the 6159 // readyState. 6160 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal); 6161 } 6162 6163 if (now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) { 6164 if (!mMediaSource) { 6165 QueueEvent(u"stalled"_ns); 6166 } else { 6167 ChangeDelayLoadStatus(false); 6168 } 6169 6170 NS_ASSERTION(mProgressTimer, "detected stalled without timer"); 6171 // Stop timer events, which prevents repeated stalled events until there 6172 // is more progress. 6173 StopProgress(); 6174 } 6175 6176 AddRemoveSelfReference(); 6177 } 6178 6179 /* static */ 6180 void HTMLMediaElement::ProgressTimerCallback(nsITimer* aTimer, void* aClosure) { 6181 auto* decoder = static_cast<HTMLMediaElement*>(aClosure); 6182 decoder->CheckProgress(false); 6183 } 6184 6185 void HTMLMediaElement::StartProgressTimer() { 6186 MOZ_ASSERT(NS_IsMainThread()); 6187 MOZ_ASSERT(mNetworkState == NETWORK_LOADING); 6188 NS_ASSERTION(!mProgressTimer, "Already started progress timer."); 6189 6190 NS_NewTimerWithFuncCallback(getter_AddRefs(mProgressTimer), 6191 ProgressTimerCallback, this, PROGRESS_MS, 6192 nsITimer::TYPE_REPEATING_SLACK, 6193 "HTMLMediaElement::ProgressTimerCallback"_ns, 6194 GetMainThreadSerialEventTarget()); 6195 } 6196 6197 void HTMLMediaElement::StartProgress() { 6198 // Record the time now for detecting stalled. 6199 mDataTime = TimeStamp::Now(); 6200 // Reset mProgressTime so that mDataTime is not indicating bytes received 6201 // after the last progress event. 6202 mProgressTime = TimeStamp(); 6203 StartProgressTimer(); 6204 } 6205 6206 void HTMLMediaElement::StopProgress() { 6207 MOZ_ASSERT(NS_IsMainThread()); 6208 if (!mProgressTimer) { 6209 return; 6210 } 6211 6212 mProgressTimer->Cancel(); 6213 mProgressTimer = nullptr; 6214 } 6215 6216 void HTMLMediaElement::DownloadProgressed() { 6217 if (mNetworkState != NETWORK_LOADING) { 6218 return; 6219 } 6220 CheckProgress(true); 6221 } 6222 6223 bool HTMLMediaElement::ShouldCheckAllowOrigin() { 6224 return mCORSMode != CORS_NONE; 6225 } 6226 6227 bool HTMLMediaElement::IsCORSSameOrigin() { 6228 bool subsumes; 6229 RefPtr<nsIPrincipal> principal = GetCurrentPrincipal(); 6230 return (NS_SUCCEEDED(NodePrincipal()->Subsumes(principal, &subsumes)) && 6231 subsumes) || 6232 ShouldCheckAllowOrigin(); 6233 } 6234 6235 void HTMLMediaElement::UpdateReadyStateInternal() { 6236 if (!mDecoder && !mSrcStream) { 6237 // Not initialized - bail out. 6238 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " 6239 "Not initialized", 6240 this)); 6241 return; 6242 } 6243 6244 if (mDecoder && mReadyState < HAVE_METADATA) { 6245 // aNextFrame might have a next frame because the decoder can advance 6246 // on its own thread before MetadataLoaded gets a chance to run. 6247 // The arrival of more data can't change us out of this readyState. 6248 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " 6249 "Decoder ready state < HAVE_METADATA", 6250 this)); 6251 return; 6252 } 6253 6254 if (mDecoder) { 6255 // IsPlaybackEnded() might have become false. 6256 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources); 6257 } 6258 6259 if (mSrcStream && mReadyState < HAVE_METADATA) { 6260 bool hasAudioTracks = AudioTracks() && !AudioTracks()->IsEmpty(); 6261 bool hasVideoTracks = VideoTracks() && !VideoTracks()->IsEmpty(); 6262 if (!hasAudioTracks && !hasVideoTracks) { 6263 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " 6264 "Stream with no tracks", 6265 this)); 6266 // Give it one last chance to remove the self reference if needed. 6267 AddRemoveSelfReference(); 6268 return; 6269 } 6270 6271 if (IsVideo() && hasVideoTracks && !HasVideo()) { 6272 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " 6273 "Stream waiting for video", 6274 this)); 6275 return; 6276 } 6277 6278 LOG(LogLevel::Debug, 6279 ("MediaElement %p UpdateReadyStateInternal() Stream has " 6280 "metadata; audioTracks=%d, videoTracks=%d, " 6281 "hasVideoFrame=%d", 6282 this, AudioTracks()->Length(), VideoTracks()->Length(), HasVideo())); 6283 6284 // We are playing a stream that has video and a video frame is now set. 6285 // This means we have all metadata needed to change ready state. 6286 MediaInfo mediaInfo = mMediaInfo; 6287 if (hasAudioTracks) { 6288 mediaInfo.EnableAudio(); 6289 } 6290 if (hasVideoTracks) { 6291 mediaInfo.EnableVideo(); 6292 if (mSelectedVideoStreamTrack) { 6293 mediaInfo.mVideo.SetAlpha(mSelectedVideoStreamTrack->HasAlpha()); 6294 } 6295 } 6296 MetadataLoaded(&mediaInfo, nullptr); 6297 } 6298 6299 if (mMediaSource) { 6300 // readyState has changed, assuming it's following the pending mediasource 6301 // operations. Notify the Mediasource that the operations have completed. 6302 mMediaSource->CompletePendingTransactions(); 6303 } 6304 6305 enum NextFrameStatus nextFrameStatus = NextFrameStatus(); 6306 if (mWaitingForKey == NOT_WAITING_FOR_KEY) { 6307 if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE && mDecoder && 6308 !mDecoder->IsEnded()) { 6309 nextFrameStatus = mDecoder->NextFrameBufferedStatus(); 6310 } 6311 } else if (mWaitingForKey == WAITING_FOR_KEY) { 6312 if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE || 6313 nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING) { 6314 // http://w3c.github.io/encrypted-media/#wait-for-key 6315 // Continuing 7.3.4 Queue a "waitingforkey" Event 6316 // 4. Queue a task to fire a simple event named waitingforkey 6317 // at the media element. 6318 // 5. Set the readyState of media element to HAVE_METADATA. 6319 // NOTE: We'll change to HAVE_CURRENT_DATA or HAVE_METADATA 6320 // depending on whether we've loaded the first frame or not 6321 // below. 6322 // 6. Suspend playback. 6323 // Note: Playback will already be stalled, as the next frame is 6324 // unavailable. 6325 mWaitingForKey = WAITING_FOR_KEY_DISPATCHED; 6326 QueueEvent(u"waitingforkey"_ns); 6327 } 6328 } else { 6329 MOZ_ASSERT(mWaitingForKey == WAITING_FOR_KEY_DISPATCHED); 6330 if (nextFrameStatus == NEXT_FRAME_AVAILABLE) { 6331 // We have new frames after dispatching "waitingforkey". 6332 // This means we've got the key and can reset mWaitingForKey now. 6333 mWaitingForKey = NOT_WAITING_FOR_KEY; 6334 } 6335 } 6336 6337 if (nextFrameStatus == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING) { 6338 LOG(LogLevel::Debug, 6339 ("MediaElement %p UpdateReadyStateInternal() " 6340 "NEXT_FRAME_UNAVAILABLE_SEEKING; Forcing HAVE_METADATA", 6341 this)); 6342 ChangeReadyState(HAVE_METADATA); 6343 return; 6344 } 6345 6346 if (IsVideo() && VideoTracks() && !VideoTracks()->IsEmpty() && 6347 !IsPlaybackEnded() && GetImageContainer() && 6348 !GetImageContainer()->HasCurrentImage()) { 6349 // Don't advance if we are playing video, but don't have a video frame. 6350 // Also, if video became available after advancing to HAVE_CURRENT_DATA 6351 // while we are still playing, we need to revert to HAVE_METADATA until 6352 // a video frame is available. 6353 LOG(LogLevel::Debug, 6354 ("MediaElement %p UpdateReadyStateInternal() " 6355 "Playing video but no video frame; Forcing HAVE_METADATA", 6356 this)); 6357 ChangeReadyState(HAVE_METADATA); 6358 return; 6359 } 6360 6361 if (!mFirstFrameLoaded) { 6362 // We haven't yet loaded the first frame, making us unable to determine 6363 // if we have enough valid data at the present stage. 6364 return; 6365 } 6366 6367 if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING) { 6368 // Force HAVE_CURRENT_DATA when buffering. 6369 ChangeReadyState(HAVE_CURRENT_DATA); 6370 return; 6371 } 6372 6373 // TextTracks must be loaded for the HAVE_ENOUGH_DATA and 6374 // HAVE_FUTURE_DATA. 6375 // So force HAVE_CURRENT_DATA if text tracks not loaded. 6376 if (mTextTrackManager && !mTextTrackManager->IsLoaded()) { 6377 ChangeReadyState(HAVE_CURRENT_DATA); 6378 return; 6379 } 6380 6381 if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEnded()) { 6382 // The decoder has signaled that the download has been suspended by the 6383 // media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's 6384 // script waiting for a "canplaythrough" event; without this forced 6385 // transition, we will never fire the "canplaythrough" event if the 6386 // media cache is too small, and scripts are bound to fail. Don't force 6387 // this transition if the decoder is in ended state; the readyState 6388 // should remain at HAVE_CURRENT_DATA in this case. 6389 // Note that this state transition includes the case where we finished 6390 // downloaded the whole data stream. 6391 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " 6392 "Decoder download suspended by cache", 6393 this)); 6394 ChangeReadyState(HAVE_ENOUGH_DATA); 6395 return; 6396 } 6397 6398 if (nextFrameStatus != MediaDecoderOwner::NEXT_FRAME_AVAILABLE) { 6399 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " 6400 "Next frame not available", 6401 this)); 6402 ChangeReadyState(HAVE_CURRENT_DATA); 6403 return; 6404 } 6405 6406 if (mSrcStream) { 6407 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " 6408 "Stream HAVE_ENOUGH_DATA", 6409 this)); 6410 ChangeReadyState(HAVE_ENOUGH_DATA); 6411 return; 6412 } 6413 6414 // Now see if we should set HAVE_ENOUGH_DATA. 6415 // If it's something we don't know the size of, then we can't 6416 // make a real estimate, so we go straight to HAVE_ENOUGH_DATA once 6417 // we've downloaded enough data that our download rate is considered 6418 // reliable. We have to move to HAVE_ENOUGH_DATA at some point or 6419 // autoplay elements for live streams will never play. Otherwise we 6420 // move to HAVE_ENOUGH_DATA if we can play through the entire media 6421 // without stopping to buffer. 6422 if (mDecoder->CanPlayThrough()) { 6423 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " 6424 "Decoder can play through", 6425 this)); 6426 ChangeReadyState(HAVE_ENOUGH_DATA); 6427 return; 6428 } 6429 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " 6430 "Default; Decoder has future data", 6431 this)); 6432 ChangeReadyState(HAVE_FUTURE_DATA); 6433 } 6434 6435 static const char* const gReadyStateToString[] = { 6436 "HAVE_NOTHING", "HAVE_METADATA", "HAVE_CURRENT_DATA", "HAVE_FUTURE_DATA", 6437 "HAVE_ENOUGH_DATA"}; 6438 6439 void HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState) { 6440 if (mReadyState == aState) { 6441 return; 6442 } 6443 6444 nsMediaReadyState oldState = mReadyState; 6445 mReadyState = aState; 6446 LOG(LogLevel::Debug, 6447 ("%p Ready state changed to %s", this, gReadyStateToString[aState])); 6448 6449 DDLOG(DDLogCategory::Property, "ready_state", gReadyStateToString[aState]); 6450 6451 // https://html.spec.whatwg.org/multipage/media.html#text-track-cue-active-flag 6452 // The user agent must synchronously unset cues' active flag whenever the 6453 // media element's readyState is changed back to HAVE_NOTHING. 6454 if (mReadyState == HAVE_NOTHING && mTextTrackManager) { 6455 mTextTrackManager->NotifyReset(); 6456 } 6457 6458 if (mNetworkState == NETWORK_EMPTY) { 6459 return; 6460 } 6461 6462 UpdateAudioChannelPlayingState(); 6463 6464 // Handle raising of "waiting" event during seek (see 4.8.10.9) 6465 // or 6466 // 4.8.12.7 Ready states: 6467 // "If the previous ready state was HAVE_FUTURE_DATA or more, and the new 6468 // ready state is HAVE_CURRENT_DATA or less 6469 // If the media element was potentially playing before its readyState 6470 // attribute changed to a value lower than HAVE_FUTURE_DATA, and the element 6471 // has not ended playback, and playback has not stopped due to errors, 6472 // paused for user interaction, or paused for in-band content, the user agent 6473 // must queue a task to fire a simple event named timeupdate at the element, 6474 // and queue a task to fire a simple event named waiting at the element." 6475 if (mPlayingBeforeSeek && mReadyState < HAVE_FUTURE_DATA) { 6476 QueueEvent(u"waiting"_ns); 6477 } else if (oldState >= HAVE_FUTURE_DATA && mReadyState < HAVE_FUTURE_DATA && 6478 !Paused() && !Ended() && !mErrorSink->mError) { 6479 FireTimeUpdate(TimeupdateType::eMandatory); 6480 QueueEvent(u"waiting"_ns); 6481 } 6482 6483 if (oldState < HAVE_CURRENT_DATA && mReadyState >= HAVE_CURRENT_DATA && 6484 !mLoadedDataFired) { 6485 QueueEvent(u"loadeddata"_ns); 6486 mLoadedDataFired = true; 6487 } 6488 6489 if (oldState < HAVE_FUTURE_DATA && mReadyState >= HAVE_FUTURE_DATA) { 6490 QueueEvent(u"canplay"_ns); 6491 if (!mPaused) { 6492 if (mDecoder && !mSuspendedByInactiveDocOrDocshell) { 6493 MOZ_ASSERT(AllowedToPlay()); 6494 mDecoder->Play(); 6495 } 6496 NotifyAboutPlaying(); 6497 } 6498 } 6499 6500 CheckAutoplayDataReady(); 6501 6502 if (oldState < HAVE_ENOUGH_DATA && mReadyState >= HAVE_ENOUGH_DATA) { 6503 QueueEvent(u"canplaythrough"_ns); 6504 } 6505 } 6506 6507 static const char* const gNetworkStateToString[] = {"EMPTY", "IDLE", "LOADING", 6508 "NO_SOURCE"}; 6509 6510 void HTMLMediaElement::ChangeNetworkState(nsMediaNetworkState aState) { 6511 if (mNetworkState == aState) { 6512 return; 6513 } 6514 6515 nsMediaNetworkState oldState = mNetworkState; 6516 mNetworkState = aState; 6517 LOG(LogLevel::Debug, 6518 ("%p Network state changed to %s", this, gNetworkStateToString[aState])); 6519 DDLOG(DDLogCategory::Property, "network_state", 6520 gNetworkStateToString[aState]); 6521 6522 if (oldState == NETWORK_LOADING) { 6523 // Stop progress notification when exiting NETWORK_LOADING. 6524 StopProgress(); 6525 } 6526 6527 if (mNetworkState == NETWORK_LOADING) { 6528 // Start progress notification when entering NETWORK_LOADING. 6529 StartProgress(); 6530 } else if (mNetworkState == NETWORK_IDLE && !mErrorSink->mError) { 6531 // Fire 'suspend' event when entering NETWORK_IDLE and no error presented. 6532 QueueEvent(u"suspend"_ns); 6533 } 6534 6535 // According to the resource selection (step2, step9-18), dedicated media 6536 // source failure step (step4) and aborting existing load (step4), set show 6537 // poster flag to true. https://html.spec.whatwg.org/multipage/media.html 6538 if (mNetworkState == NETWORK_NO_SOURCE || mNetworkState == NETWORK_EMPTY) { 6539 mShowPoster = true; 6540 } 6541 6542 // Changing mNetworkState affects AddRemoveSelfReference(). 6543 AddRemoveSelfReference(); 6544 } 6545 6546 void HTMLMediaElement::StartObservingGVAutoplayIfNeeded() { 6547 #if defined(MOZ_WIDGET_ANDROID) 6548 if (mGVAutoplayObserver || !StaticPrefs::media_geckoview_autoplay_request()) { 6549 return; 6550 } 6551 nsPIDOMWindowInner* inner = OwnerDoc()->GetInnerWindow(); 6552 RefPtr<BrowsingContext> ctx = inner ? inner->GetBrowsingContext() : nullptr; 6553 if (ctx && GVAutoplayPermissionRequestor::HasUnresolvedRequest(inner)) { 6554 mGVAutoplayObserver = MakeAndAddRef<GVAutoplayObserver>(this); 6555 } 6556 #endif 6557 } 6558 6559 void HTMLMediaElement::StopObservingGVAutoplayIfNeeded() { 6560 #if defined(MOZ_WIDGET_ANDROID) 6561 if (!mGVAutoplayObserver) { 6562 return; 6563 } 6564 nsPIDOMWindowInner* inner = OwnerDoc()->GetInnerWindow(); 6565 RefPtr<BrowsingContext> ctx = inner ? inner->GetBrowsingContext() : nullptr; 6566 if (ctx == nullptr || 6567 !GVAutoplayPermissionRequestor::HasUnresolvedRequest(inner)) { 6568 mGVAutoplayObserver = nullptr; 6569 } 6570 #endif 6571 } 6572 6573 bool HTMLMediaElement::IsEligibleForAutoplay() { 6574 // We also activate autoplay when playing a media source since the data 6575 // download is controlled by the script and there is no way to evaluate 6576 // MediaDecoder::CanPlayThrough(). 6577 6578 if (!HasAttr(nsGkAtoms::autoplay)) { 6579 return false; 6580 } 6581 6582 if (!mCanAutoplayFlag) { 6583 return false; 6584 } 6585 6586 if (IsEditable()) { 6587 return false; 6588 } 6589 6590 if (!mPaused) { 6591 return false; 6592 } 6593 6594 if (mSuspendedByInactiveDocOrDocshell) { 6595 return false; 6596 } 6597 6598 // Static document is used for print preview and printing, should not be 6599 // autoplay 6600 if (OwnerDoc()->IsStaticDocument()) { 6601 return false; 6602 } 6603 6604 if (ShouldBeSuspendedByInactiveDocShell()) { 6605 LOG(LogLevel::Debug, ("%p prohibiting autoplay by the docShell", this)); 6606 return false; 6607 } 6608 6609 if (MediaPlaybackDelayPolicy::ShouldDelayPlayback(this)) { 6610 CreateResumeDelayedMediaPlaybackAgentIfNeeded(); 6611 LOG(LogLevel::Debug, ("%p delay playing from autoplay", this)); 6612 return false; 6613 } 6614 6615 return mReadyState >= HAVE_ENOUGH_DATA; 6616 } 6617 6618 void HTMLMediaElement::CheckAutoplayDataReady() { 6619 if (!IsEligibleForAutoplay()) { 6620 return; 6621 } 6622 if (!AllowedToPlay()) { 6623 DispatchEventsWhenPlayWasNotAllowed(); 6624 return; 6625 } 6626 #if defined(MOZ_WIDGET_ANDROID) 6627 StopObservingGVAutoplayIfNeeded(); 6628 #endif 6629 RunAutoplay(); 6630 } 6631 6632 void HTMLMediaElement::RunAutoplay() { 6633 mAllowedToPlayPromise.ResolveIfExists(true, __func__); 6634 mPaused = false; 6635 // We changed mPaused which can affect AddRemoveSelfReference 6636 AddRemoveSelfReference(); 6637 UpdateSrcMediaStreamPlaying(); 6638 UpdateAudioChannelPlayingState(); 6639 StartMediaControlKeyListenerIfNeeded(); 6640 6641 if (mDecoder) { 6642 SetPlayedOrSeeked(true); 6643 if (mCurrentPlayRangeStart == -1.0) { 6644 mCurrentPlayRangeStart = CurrentTime(); 6645 } 6646 MOZ_ASSERT(!mSuspendedByInactiveDocOrDocshell); 6647 mDecoder->Play(); 6648 } else if (mSrcStream) { 6649 SetPlayedOrSeeked(true); 6650 } 6651 6652 // https://html.spec.whatwg.org/multipage/media.html#ready-states:show-poster-flag 6653 if (mShowPoster) { 6654 mShowPoster = false; 6655 if (mTextTrackManager) { 6656 mTextTrackManager->TimeMarchesOn(); 6657 } 6658 } 6659 6660 // For blocked media, the event would be pending until it is resumed. 6661 QueueEvent(u"play"_ns); 6662 6663 QueueEvent(u"playing"_ns); 6664 6665 MaybeMarkSHEntryAsUserInteracted(); 6666 } 6667 6668 bool HTMLMediaElement::IsActuallyInvisible() const { 6669 // That means an element is not connected. It probably hasn't connected to a 6670 // document tree, or connects to a disconnected DOM tree. 6671 if (!IsInComposedDoc()) { 6672 return true; 6673 } 6674 6675 // An element is not in user's view port, which means it's either existing in 6676 // somewhere in the page where user hasn't seen yet, or is being set 6677 // `display:none`. 6678 if (!IsInViewPort()) { 6679 return true; 6680 } 6681 6682 // Element being used in picture-in-picture mode would be always visible. 6683 if (IsBeingUsedInPictureInPictureMode()) { 6684 return false; 6685 } 6686 6687 // That check is the page is in the background. 6688 return OwnerDoc()->Hidden(); 6689 } 6690 6691 bool HTMLMediaElement::IsInViewPort() const { 6692 return mVisibilityState == Visibility::ApproximatelyVisible; 6693 } 6694 6695 VideoFrameContainer* HTMLMediaElement::GetVideoFrameContainer() { 6696 if (mShuttingDown) { 6697 return nullptr; 6698 } 6699 6700 if (mVideoFrameContainer) { 6701 return mVideoFrameContainer; 6702 } 6703 6704 // Only video frames need an image container. 6705 if (!IsVideo()) { 6706 return nullptr; 6707 } 6708 6709 mVideoFrameContainer = new VideoFrameContainer( 6710 this, MakeAndAddRef<ImageContainer>(ImageUsageType::VideoFrameContainer, 6711 ImageContainer::ASYNCHRONOUS)); 6712 6713 return mVideoFrameContainer; 6714 } 6715 6716 void HTMLMediaElement::PrincipalChanged(MediaStreamTrack* aTrack) { 6717 if (aTrack != mSelectedVideoStreamTrack) { 6718 return; 6719 } 6720 6721 nsContentUtils::CombineResourcePrincipals(&mSrcStreamVideoPrincipal, 6722 aTrack->GetPrincipal()); 6723 6724 LOG(LogLevel::Debug, 6725 ("HTMLMediaElement %p video track principal changed to %p (combined " 6726 "into %p). Waiting for it to reach VideoFrameContainer before setting.", 6727 this, aTrack->GetPrincipal(), mSrcStreamVideoPrincipal.get())); 6728 6729 if (mVideoFrameContainer) { 6730 UpdateSrcStreamVideoPrincipal( 6731 mVideoFrameContainer->GetLastPrincipalHandle()); 6732 } 6733 } 6734 6735 void HTMLMediaElement::UpdateSrcStreamVideoPrincipal( 6736 const PrincipalHandle& aPrincipalHandle) { 6737 nsTArray<RefPtr<VideoStreamTrack>> videoTracks; 6738 mSrcStream->GetVideoTracks(videoTracks); 6739 6740 for (const RefPtr<VideoStreamTrack>& track : videoTracks) { 6741 if (PrincipalHandleMatches(aPrincipalHandle, track->GetPrincipal()) && 6742 !track->Ended()) { 6743 // When the PrincipalHandle for the VideoFrameContainer changes to that of 6744 // a live track in mSrcStream we know that a removed track was displayed 6745 // but is no longer so. 6746 LOG(LogLevel::Debug, ("HTMLMediaElement %p VideoFrameContainer's " 6747 "PrincipalHandle matches track %p. That's all we " 6748 "need.", 6749 this, track.get())); 6750 mSrcStreamVideoPrincipal = track->GetPrincipal(); 6751 break; 6752 } 6753 } 6754 } 6755 6756 void HTMLMediaElement::PrincipalHandleChangedForVideoFrameContainer( 6757 VideoFrameContainer* aContainer, 6758 const PrincipalHandle& aNewPrincipalHandle) { 6759 MOZ_ASSERT(NS_IsMainThread()); 6760 6761 if (!mSrcStream) { 6762 return; 6763 } 6764 6765 LOG(LogLevel::Debug, ("HTMLMediaElement %p PrincipalHandle changed in " 6766 "VideoFrameContainer.", 6767 this)); 6768 6769 UpdateSrcStreamVideoPrincipal(aNewPrincipalHandle); 6770 } 6771 6772 already_AddRefed<nsMediaEventRunner> HTMLMediaElement::GetEventRunner( 6773 const nsAString& aName, EventFlag aFlag) { 6774 RefPtr<nsMediaEventRunner> runner; 6775 if (aName.EqualsLiteral("playing")) { 6776 runner = new nsNotifyAboutPlayingRunner(this, TakePendingPlayPromises()); 6777 } else if (aName.EqualsLiteral("timeupdate")) { 6778 runner = new nsTimeupdateRunner(this, aFlag == EventFlag::eMandatory); 6779 } else { 6780 runner = new nsAsyncEventRunner(aName, this); 6781 } 6782 return runner.forget(); 6783 } 6784 6785 nsresult HTMLMediaElement::FireEvent(const nsAString& aName) { 6786 if (mEventBlocker->ShouldBlockEventDelivery()) { 6787 RefPtr<nsMediaEventRunner> runner = GetEventRunner(aName); 6788 mEventBlocker->PostponeEvent(runner); 6789 return NS_OK; 6790 } 6791 6792 LOG_EVENT(LogLevel::Debug, 6793 ("%p Firing event %s", this, NS_ConvertUTF16toUTF8(aName).get())); 6794 6795 return nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, aName, 6796 CanBubble::eNo, Cancelable::eNo); 6797 } 6798 6799 void HTMLMediaElement::QueueEvent(const nsAString& aName) { 6800 RefPtr<nsMediaEventRunner> runner = GetEventRunner(aName); 6801 QueueTask(std::move(runner)); 6802 } 6803 6804 void HTMLMediaElement::QueueTask(RefPtr<nsMediaEventRunner> aRunner) { 6805 NS_ConvertUTF16toUTF8 eventName(aRunner->EventName()); 6806 LOG_EVENT(LogLevel::Debug, ("%p Queuing event %s", this, eventName.get())); 6807 DDLOG(DDLogCategory::Event, "HTMLMediaElement", nsCString(eventName.get())); 6808 if (mEventBlocker->ShouldBlockEventDelivery()) { 6809 mEventBlocker->PostponeEvent(aRunner); 6810 return; 6811 } 6812 GetMainThreadSerialEventTarget()->Dispatch(aRunner.forget()); 6813 } 6814 6815 bool HTMLMediaElement::IsPotentiallyPlaying() const { 6816 // TODO: 6817 // playback has not stopped due to errors, 6818 // and the element has not paused for user interaction 6819 return !mPaused && 6820 (mReadyState == HAVE_ENOUGH_DATA || mReadyState == HAVE_FUTURE_DATA) && 6821 !IsPlaybackEnded(); 6822 } 6823 6824 bool HTMLMediaElement::IsPlaybackEnded() const { 6825 // TODO: 6826 // the current playback position is equal to the effective end of the media 6827 // resource. See bug 449157. 6828 if (mDecoder) { 6829 return mReadyState >= HAVE_METADATA && mDecoder->IsEnded(); 6830 } 6831 if (mSrcStream) { 6832 return mReadyState >= HAVE_METADATA && mSrcStreamPlaybackEnded; 6833 } 6834 return false; 6835 } 6836 6837 already_AddRefed<nsIPrincipal> HTMLMediaElement::GetCurrentPrincipal() { 6838 if (mDecoder) { 6839 return mDecoder->GetCurrentPrincipal(); 6840 } 6841 if (mSrcStream) { 6842 nsTArray<RefPtr<MediaStreamTrack>> tracks; 6843 mSrcStream->GetTracks(tracks); 6844 nsCOMPtr<nsIPrincipal> principal = mSrcStream->GetPrincipal(); 6845 return principal.forget(); 6846 } 6847 return nullptr; 6848 } 6849 6850 bool HTMLMediaElement::HadCrossOriginRedirects() { 6851 if (mDecoder) { 6852 return mDecoder->HadCrossOriginRedirects(); 6853 } 6854 return false; 6855 } 6856 6857 bool HTMLMediaElement::ShouldResistFingerprinting(RFPTarget aTarget) const { 6858 return OwnerDoc()->ShouldResistFingerprinting(aTarget); 6859 } 6860 6861 already_AddRefed<nsIPrincipal> HTMLMediaElement::GetCurrentVideoPrincipal() { 6862 if (mDecoder) { 6863 return mDecoder->GetCurrentPrincipal(); 6864 } 6865 if (mSrcStream) { 6866 nsCOMPtr<nsIPrincipal> principal = mSrcStreamVideoPrincipal; 6867 return principal.forget(); 6868 } 6869 return nullptr; 6870 } 6871 6872 void HTMLMediaElement::NotifyDecoderPrincipalChanged() { 6873 RefPtr<nsIPrincipal> principal = GetCurrentPrincipal(); 6874 bool isSameOrigin = !principal || IsCORSSameOrigin(); 6875 mDecoder->UpdateSameOriginStatus(isSameOrigin); 6876 6877 if (isSameOrigin) { 6878 principal = NodePrincipal(); 6879 } 6880 for (const auto& entry : mOutputTrackSources.Values()) { 6881 entry->SetPrincipal(principal); 6882 } 6883 mDecoder->SetOutputTracksPrincipal(principal); 6884 } 6885 6886 void HTMLMediaElement::Invalidate(ImageSizeChanged aImageSizeChanged, 6887 const Maybe<nsIntSize>& aNewIntrinsicSize, 6888 ForceInvalidate aForceInvalidate) { 6889 nsIFrame* frame = GetPrimaryFrame(); 6890 if (aNewIntrinsicSize) { 6891 UpdateMediaSize(aNewIntrinsicSize.value()); 6892 if (frame) { 6893 nsPresContext* presContext = frame->PresContext(); 6894 PresShell* presShell = presContext->PresShell(); 6895 presShell->FrameNeedsReflow(frame, 6896 IntrinsicDirty::FrameAncestorsAndDescendants, 6897 NS_FRAME_IS_DIRTY); 6898 } 6899 } 6900 6901 RefPtr<ImageContainer> imageContainer = GetImageContainer(); 6902 bool asyncInvalidate = imageContainer && imageContainer->IsAsync() && 6903 aForceInvalidate == ForceInvalidate::No; 6904 if (frame) { 6905 if (aImageSizeChanged == ImageSizeChanged::Yes) { 6906 frame->InvalidateFrame(); 6907 } else { 6908 frame->InvalidateLayer(DisplayItemType::TYPE_VIDEO, nullptr, nullptr, 6909 asyncInvalidate ? nsIFrame::UPDATE_IS_ASYNC : 0); 6910 } 6911 } 6912 6913 SVGObserverUtils::InvalidateDirectRenderingObservers(this); 6914 } 6915 6916 void HTMLMediaElement::UpdateMediaSize(const nsIntSize& aSize) { 6917 MOZ_ASSERT(NS_IsMainThread()); 6918 6919 if (IsVideo() && mReadyState != HAVE_NOTHING && 6920 mMediaInfo.mVideo.mDisplay != aSize) { 6921 QueueEvent(u"resize"_ns); 6922 } 6923 6924 mMediaInfo.mVideo.mDisplay = aSize; 6925 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal); 6926 } 6927 6928 void HTMLMediaElement::SuspendOrResumeElement(bool aSuspendElement) { 6929 LOG(LogLevel::Debug, ("%p SuspendOrResumeElement(suspend=%d) docHidden=%d", 6930 this, aSuspendElement, OwnerDoc()->Hidden())); 6931 6932 if (aSuspendElement == mSuspendedByInactiveDocOrDocshell) { 6933 return; 6934 } 6935 6936 mSuspendedByInactiveDocOrDocshell = aSuspendElement; 6937 UpdateSrcMediaStreamPlaying(); 6938 UpdateAudioChannelPlayingState(); 6939 6940 if (aSuspendElement) { 6941 if (mDecoder) { 6942 mDecoder->Pause(); 6943 mDecoder->Suspend(); 6944 mDecoder->SetDelaySeekMode(true); 6945 } 6946 mEventBlocker->SetBlockEventDelivery(true); 6947 // We won't want to resume media element from the bfcache. 6948 ClearResumeDelayedMediaPlaybackAgentIfNeeded(); 6949 mMediaControlKeyListener->StopIfNeeded(); 6950 } else { 6951 if (mDecoder) { 6952 mDecoder->Resume(); 6953 if (!mPaused && !mDecoder->IsEnded()) { 6954 mDecoder->Play(); 6955 } 6956 mDecoder->SetDelaySeekMode(false); 6957 } 6958 mEventBlocker->SetBlockEventDelivery(false); 6959 // If the media element has been blocked and isn't still allowed to play 6960 // when it comes back from the bfcache, we would notify front end to show 6961 // the blocking icon in order to inform user that the site is still being 6962 // blocked. 6963 if (mHasEverBeenBlockedForAutoplay && !AllowedToPlay()) { 6964 MaybeNotifyAutoplayBlocked(); 6965 } 6966 StartMediaControlKeyListenerIfNeeded(); 6967 } 6968 if (StaticPrefs::media_testing_only_events()) { 6969 auto dispatcher = MakeRefPtr<AsyncEventDispatcher>( 6970 this, u"MozMediaSuspendChanged"_ns, CanBubble::eYes, 6971 ChromeOnlyDispatch::eYes); 6972 dispatcher->PostDOMEvent(); 6973 } 6974 } 6975 6976 bool HTMLMediaElement::IsBeingDestroyed() { 6977 nsIDocShell* docShell = OwnerDoc()->GetDocShell(); 6978 bool isBeingDestroyed = false; 6979 if (docShell) { 6980 docShell->IsBeingDestroyed(&isBeingDestroyed); 6981 } 6982 return isBeingDestroyed; 6983 } 6984 6985 bool HTMLMediaElement::ShouldBeSuspendedByInactiveDocShell() const { 6986 BrowsingContext* bc = OwnerDoc()->GetBrowsingContext(); 6987 return bc && !bc->IsActive() && bc->Top()->GetSuspendMediaWhenInactive(); 6988 } 6989 6990 void HTMLMediaElement::NotifyOwnerDocumentActivityChanged() { 6991 if (mDecoder && !IsBeingDestroyed()) { 6992 NotifyDecoderActivityChanges(); 6993 } 6994 6995 // We would suspend media when the document is inactive, or its docshell has 6996 // been set to hidden and explicitly wants to suspend media. In those cases, 6997 // the media would be not visible and we don't want them to continue playing. 6998 bool shouldSuspend = 6999 !OwnerDoc()->IsActive() || ShouldBeSuspendedByInactiveDocShell(); 7000 SuspendOrResumeElement(shouldSuspend); 7001 7002 // If the owning document has become inactive we should shutdown the CDM. 7003 if (!OwnerDoc()->IsCurrentActiveDocument() && mMediaKeys) { 7004 // We don't shutdown MediaKeys here because it also listens for document 7005 // activity and will take care of shutting down itself. 7006 DDUNLINKCHILD(mMediaKeys.get()); 7007 mMediaKeys = nullptr; 7008 if (mDecoder) { 7009 ShutdownDecoder(); 7010 } 7011 } 7012 7013 AddRemoveSelfReference(); 7014 } 7015 7016 void HTMLMediaElement::NotifyFullScreenChanged() { 7017 const bool isInFullScreen = IsInFullScreen(); 7018 if (isInFullScreen) { 7019 StartMediaControlKeyListenerIfNeeded(); 7020 if (!mMediaControlKeyListener->IsStarted()) { 7021 MEDIACONTROL_LOG("Failed to start the listener when entering fullscreen"); 7022 } 7023 } 7024 // Updating controller fullscreen state no matter the listener starts or not. 7025 BrowsingContext* bc = OwnerDoc()->GetBrowsingContext(); 7026 if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(bc)) { 7027 updater->NotifyMediaFullScreenState(bc->Id(), isInFullScreen); 7028 } 7029 } 7030 7031 void HTMLMediaElement::AddRemoveSelfReference() { 7032 // XXX we could release earlier here in many situations if we examined 7033 // which event listeners are attached. Right now we assume there is a 7034 // potential listener for every event. We would also have to keep the 7035 // element alive if it was playing and producing audio output --- right now 7036 // that's covered by the !mPaused check. 7037 Document* ownerDoc = OwnerDoc(); 7038 7039 // See the comment at the top of this file for the explanation of this 7040 // boolean expression. 7041 bool needSelfReference = 7042 !mShuttingDown && ownerDoc->IsActive() && 7043 (mDelayingLoadEvent || (!mPaused && !Ended()) || 7044 (mDecoder && mDecoder->IsSeeking()) || IsEligibleForAutoplay() || 7045 (mMediaSource ? mProgressTimer : mNetworkState == NETWORK_LOADING)); 7046 7047 if (needSelfReference != mHasSelfReference) { 7048 mHasSelfReference = needSelfReference; 7049 RefPtr<HTMLMediaElement> self = this; 7050 if (needSelfReference) { 7051 // The shutdown observer will hold a strong reference to us. This 7052 // will do to keep us alive. We need to know about shutdown so that 7053 // we can release our self-reference. 7054 GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( 7055 "dom::HTMLMediaElement::AddSelfReference", 7056 [self]() { self->mShutdownObserver->AddRefMediaElement(); })); 7057 } else { 7058 // Dispatch Release asynchronously so that we don't destroy this object 7059 // inside a call stack of method calls on this object 7060 GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( 7061 "dom::HTMLMediaElement::AddSelfReference", 7062 [self]() { self->mShutdownObserver->ReleaseMediaElement(); })); 7063 } 7064 } 7065 } 7066 7067 void HTMLMediaElement::NotifyShutdownEvent() { 7068 mShuttingDown = true; 7069 ResetState(); 7070 AddRemoveSelfReference(); 7071 } 7072 7073 void HTMLMediaElement::DispatchAsyncSourceError( 7074 nsIContent* aSourceElement, const nsACString& aErrorDetails) { 7075 LOG_EVENT(LogLevel::Debug, ("%p Queuing simple source error event", this)); 7076 7077 nsCOMPtr<nsIRunnable> event = 7078 new nsSourceErrorEventRunner(this, aSourceElement, aErrorDetails); 7079 GetMainThreadSerialEventTarget()->Dispatch(event.forget()); 7080 } 7081 7082 void HTMLMediaElement::NotifyAddedSource() { 7083 // If a source element is inserted as a child of a media element 7084 // that has no src attribute and whose networkState has the value 7085 // NETWORK_EMPTY, the user agent must invoke the media element's 7086 // resource selection algorithm. 7087 if (!HasAttr(nsGkAtoms::src) && mNetworkState == NETWORK_EMPTY) { 7088 AssertReadyStateIsNothing(); 7089 QueueSelectResourceTask(); 7090 } 7091 7092 // A load was paused in the resource selection algorithm, waiting for 7093 // a new source child to be added, resume the resource selection algorithm. 7094 if (mLoadWaitStatus == WAITING_FOR_SOURCE) { 7095 // Rest the flag so we don't queue multiple LoadFromSourceTask() when 7096 // multiple <source> are attached in an event loop. 7097 mLoadWaitStatus = NOT_WAITING; 7098 QueueLoadFromSourceTask(); 7099 } 7100 } 7101 7102 HTMLSourceElement* HTMLMediaElement::GetNextSource() { 7103 mSourceLoadCandidate = nullptr; 7104 7105 while (true) { 7106 if (mSourcePointer == nsINode::GetLastChild()) { 7107 return nullptr; // no more children 7108 } 7109 7110 if (!mSourcePointer) { 7111 mSourcePointer = nsINode::GetFirstChild(); 7112 } else { 7113 mSourcePointer = mSourcePointer->GetNextSibling(); 7114 } 7115 nsIContent* child = mSourcePointer; 7116 7117 // If child is a <source> element, it is the next candidate. 7118 if (auto* source = HTMLSourceElement::FromNodeOrNull(child)) { 7119 mSourceLoadCandidate = source; 7120 return source; 7121 } 7122 } 7123 MOZ_ASSERT_UNREACHABLE("Execution should not reach here!"); 7124 return nullptr; 7125 } 7126 7127 void HTMLMediaElement::ChangeDelayLoadStatus(bool aDelay) { 7128 if (mDelayingLoadEvent == aDelay) { 7129 return; 7130 } 7131 7132 mDelayingLoadEvent = aDelay; 7133 7134 LOG(LogLevel::Debug, ("%p ChangeDelayLoadStatus(%d) doc=0x%p", this, aDelay, 7135 mLoadBlockedDoc.get())); 7136 if (mDecoder) { 7137 mDecoder->SetLoadInBackground(!aDelay); 7138 } 7139 if (aDelay) { 7140 mLoadBlockedDoc = OwnerDoc(); 7141 mLoadBlockedDoc->BlockOnload(); 7142 } else { 7143 // mLoadBlockedDoc might be null due to GC unlinking 7144 if (mLoadBlockedDoc) { 7145 mLoadBlockedDoc->UnblockOnload(false); 7146 mLoadBlockedDoc = nullptr; 7147 } 7148 } 7149 7150 // We changed mDelayingLoadEvent which can affect AddRemoveSelfReference 7151 AddRemoveSelfReference(); 7152 } 7153 7154 already_AddRefed<nsILoadGroup> HTMLMediaElement::GetDocumentLoadGroup() { 7155 if (!OwnerDoc()->IsActive()) { 7156 NS_WARNING("Load group requested for media element in inactive document."); 7157 } 7158 return OwnerDoc()->GetDocumentLoadGroup(); 7159 } 7160 7161 nsresult HTMLMediaElement::CopyInnerTo(Element* aDest) { 7162 nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest); 7163 NS_ENSURE_SUCCESS(rv, rv); 7164 7165 HTMLMediaElement* dest = static_cast<HTMLMediaElement*>(aDest); 7166 if (HasAttr(nsGkAtoms::muted)) { 7167 dest->mMuted |= MUTED_BY_CONTENT; 7168 } 7169 7170 if (aDest->OwnerDoc()->IsStaticDocument()) { 7171 dest->SetMediaInfo(mMediaInfo); 7172 } 7173 return rv; 7174 } 7175 7176 already_AddRefed<TimeRanges> HTMLMediaElement::Buffered() const { 7177 media::TimeIntervals buffered = 7178 mDecoder ? mDecoder->GetBuffered() : media::TimeIntervals(); 7179 RefPtr<TimeRanges> ranges = new TimeRanges( 7180 ToSupports(OwnerDoc()), buffered.ToMicrosecondResolution()); 7181 return ranges.forget(); 7182 } 7183 7184 void HTMLMediaElement::SetRequestHeaders(nsIHttpChannel* aChannel) { 7185 // Send Accept header for video and audio types only (Bug 489071) 7186 SetAcceptHeader(aChannel); 7187 7188 // Apache doesn't send Content-Length when gzip transfer encoding is used, 7189 // which prevents us from estimating the video length (if explicit 7190 // Content-Duration and a length spec in the container are not present either) 7191 // and from seeking. So, disable the standard "Accept-Encoding: gzip,deflate" 7192 // that we usually send. See bug 614760. 7193 DebugOnly<nsresult> rv = 7194 aChannel->SetRequestHeader("Accept-Encoding"_ns, ""_ns, false); 7195 MOZ_ASSERT(NS_SUCCEEDED(rv)); 7196 7197 // Set the Referrer header 7198 // 7199 // FIXME: Shouldn't this use the Element constructor? Though I guess it 7200 // doesn't matter as no HTMLMediaElement supports the referrerinfo attribute. 7201 auto referrerInfo = MakeRefPtr<ReferrerInfo>(*OwnerDoc()); 7202 rv = aChannel->SetReferrerInfoWithoutClone(referrerInfo); 7203 MOZ_ASSERT(NS_SUCCEEDED(rv)); 7204 } 7205 7206 const TimeStamp& HTMLMediaElement::LastTimeupdateDispatchTime() const { 7207 MOZ_ASSERT(NS_IsMainThread()); 7208 return mLastTimeUpdateDispatchTime; 7209 } 7210 7211 void HTMLMediaElement::UpdateLastTimeupdateDispatchTime() { 7212 MOZ_ASSERT(NS_IsMainThread()); 7213 mLastTimeUpdateDispatchTime = TimeStamp::Now(); 7214 } 7215 7216 bool HTMLMediaElement::ShouldQueueTimeupdateAsyncTask( 7217 TimeupdateType aType) const { 7218 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); 7219 // That means dispatching `timeupdate` is mandatorily required in the spec. 7220 if (aType == TimeupdateType::eMandatory) { 7221 return true; 7222 } 7223 7224 // The timeupdate only occurs when the current playback position changes. 7225 // https://html.spec.whatwg.org/multipage/media.html#event-media-timeupdate 7226 if (mLastCurrentTime == CurrentTime()) { 7227 return false; 7228 } 7229 7230 // Number of milliseconds between timeupdate events as defined by spec. 7231 if (!mQueueTimeUpdateRunnerTime.IsNull() && 7232 TimeStamp::Now() - mQueueTimeUpdateRunnerTime < 7233 TimeDuration::FromMilliseconds(TIMEUPDATE_MS)) { 7234 return false; 7235 } 7236 return true; 7237 } 7238 7239 void HTMLMediaElement::FireTimeUpdate(TimeupdateType aType) { 7240 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); 7241 7242 if (ShouldQueueTimeupdateAsyncTask(aType)) { 7243 RefPtr<nsMediaEventRunner> runner = 7244 GetEventRunner(u"timeupdate"_ns, aType == TimeupdateType::eMandatory 7245 ? EventFlag::eMandatory 7246 : EventFlag::eNone); 7247 QueueTask(std::move(runner)); 7248 mQueueTimeUpdateRunnerTime = TimeStamp::Now(); 7249 mLastCurrentTime = CurrentTime(); 7250 } 7251 if (mFragmentEnd >= 0.0 && CurrentTime() >= mFragmentEnd) { 7252 Pause(); 7253 mFragmentEnd = -1.0; 7254 mFragmentStart = -1.0; 7255 mDecoder->SetFragmentEndTime(mFragmentEnd); 7256 } 7257 7258 // Update the cues displaying on the video. 7259 // Here mTextTrackManager can be null if the cycle collector has unlinked 7260 // us before our parent. In that case UnbindFromTree will call us 7261 // when our parent is unlinked. 7262 if (mTextTrackManager) { 7263 mTextTrackManager->TimeMarchesOn(); 7264 } 7265 } 7266 7267 MediaError* HTMLMediaElement::GetError() const { return mErrorSink->mError; } 7268 7269 void HTMLMediaElement::GetCurrentSpec(nsCString& aString) { 7270 // If playing a regular URL, an ObjectURL of a Blob/File, return that. 7271 if (mLoadingSrc) { 7272 mLoadingSrc->GetSpec(aString); 7273 } else if (mSrcMediaSource) { 7274 // If playing an ObjectURL, and it's a MediaSource, return the value of the 7275 // `src` attribute. 7276 nsAutoString src; 7277 GetSrc(src); 7278 CopyUTF16toUTF8(src, aString); 7279 } else { 7280 // Playing e.g. a MediaStream via an object URL - return an empty string 7281 aString.Truncate(); 7282 } 7283 } 7284 7285 double HTMLMediaElement::MozFragmentEnd() { 7286 double duration = Duration(); 7287 7288 // If there is no end fragment, or the fragment end is greater than the 7289 // duration, return the duration. 7290 return (mFragmentEnd < 0.0 || mFragmentEnd > duration) ? duration 7291 : mFragmentEnd; 7292 } 7293 7294 void HTMLMediaElement::SetDefaultPlaybackRate(double aDefaultPlaybackRate, 7295 ErrorResult& aRv) { 7296 if (mSrcAttrStream) { 7297 return; 7298 } 7299 7300 if (aDefaultPlaybackRate < 0) { 7301 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); 7302 return; 7303 } 7304 7305 double defaultPlaybackRate = ClampPlaybackRate(aDefaultPlaybackRate); 7306 7307 if (mDefaultPlaybackRate == defaultPlaybackRate) { 7308 return; 7309 } 7310 7311 mDefaultPlaybackRate = defaultPlaybackRate; 7312 QueueEvent(u"ratechange"_ns); 7313 } 7314 7315 void HTMLMediaElement::SetPlaybackRate(double aPlaybackRate, ErrorResult& aRv) { 7316 if (mSrcAttrStream) { 7317 return; 7318 } 7319 7320 // Changing the playback rate of a media that has more than two channels is 7321 // not supported. 7322 if (aPlaybackRate < 0) { 7323 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 7324 return; 7325 } 7326 7327 if (mPlaybackRate == aPlaybackRate) { 7328 return; 7329 } 7330 7331 mPlaybackRate = aPlaybackRate; 7332 // Playback rate threshold above which audio is muted. 7333 uint32_t threshold = StaticPrefs::media_audio_playbackrate_muting_threshold(); 7334 if (mPlaybackRate != 0.0 && 7335 (mPlaybackRate > threshold || mPlaybackRate < 1. / threshold)) { 7336 SetMutedInternal(mMuted | MUTED_BY_INVALID_PLAYBACK_RATE); 7337 } else { 7338 SetMutedInternal(mMuted & ~MUTED_BY_INVALID_PLAYBACK_RATE); 7339 } 7340 7341 if (mDecoder) { 7342 mDecoder->SetPlaybackRate(ClampPlaybackRate(mPlaybackRate)); 7343 } 7344 QueueEvent(u"ratechange"_ns); 7345 mMediaControlKeyListener->NotifyMediaPositionState(); 7346 } 7347 7348 void HTMLMediaElement::SetPreservesPitch(bool aPreservesPitch) { 7349 mPreservesPitch = aPreservesPitch; 7350 if (mDecoder) { 7351 mDecoder->SetPreservesPitch(mPreservesPitch); 7352 } 7353 } 7354 7355 ImageContainer* HTMLMediaElement::GetImageContainer() { 7356 VideoFrameContainer* container = GetVideoFrameContainer(); 7357 return container ? container->GetImageContainer() : nullptr; 7358 } 7359 7360 void HTMLMediaElement::UpdateAudioChannelPlayingState() { 7361 if (mAudioChannelWrapper) { 7362 mAudioChannelWrapper->UpdateAudioChannelPlayingState(); 7363 } 7364 } 7365 7366 static const char* VisibilityString(Visibility aVisibility) { 7367 switch (aVisibility) { 7368 case Visibility::Untracked: { 7369 return "Untracked"; 7370 } 7371 case Visibility::ApproximatelyNonVisible: { 7372 return "ApproximatelyNonVisible"; 7373 } 7374 case Visibility::ApproximatelyVisible: { 7375 return "ApproximatelyVisible"; 7376 } 7377 } 7378 7379 return "NAN"; 7380 } 7381 7382 void HTMLMediaElement::OnVisibilityChange(Visibility aNewVisibility) { 7383 LOG(LogLevel::Debug, 7384 ("OnVisibilityChange(): %s\n", VisibilityString(aNewVisibility))); 7385 7386 mVisibilityState = aNewVisibility; 7387 if (StaticPrefs::media_test_video_suspend()) { 7388 QueueEvent(u"visibilitychanged"_ns); 7389 } 7390 7391 if (!mDecoder) { 7392 return; 7393 } 7394 NotifyDecoderActivityChanges(); 7395 } 7396 7397 MediaKeys* HTMLMediaElement::GetMediaKeys() const { return mMediaKeys; } 7398 7399 bool HTMLMediaElement::ContainsRestrictedContent() const { 7400 return GetMediaKeys() != nullptr; 7401 } 7402 7403 void HTMLMediaElement::SetCDMProxyFailure(const MediaResult& aResult) { 7404 LOG(LogLevel::Debug, ("%s", __func__)); 7405 MOZ_ASSERT(mSetMediaKeysDOMPromise); 7406 7407 ResetSetMediaKeysTempVariables(); 7408 7409 mSetMediaKeysDOMPromise->MaybeReject(aResult.Code(), aResult.Message()); 7410 } 7411 7412 void HTMLMediaElement::RemoveMediaKeys() { 7413 LOG(LogLevel::Debug, ("%s", __func__)); 7414 // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute 7415 // to decrypt media data and remove the association with the media element. 7416 if (mMediaKeys) { 7417 mMediaKeys->Unbind(); 7418 } 7419 mMediaKeys = nullptr; 7420 } 7421 7422 bool HTMLMediaElement::TryRemoveMediaKeysAssociation() { 7423 MOZ_ASSERT(mMediaKeys); 7424 LOG(LogLevel::Debug, ("%s", __func__)); 7425 // 5.2.1 If the user agent or CDM do not support removing the association, 7426 // let this object's attaching media keys value be false and reject promise 7427 // with a new DOMException whose name is NotSupportedError. 7428 // 5.2.2 If the association cannot currently be removed, let this object's 7429 // attaching media keys value be false and reject promise with a new 7430 // DOMException whose name is InvalidStateError. 7431 if (mDecoder) { 7432 RefPtr<HTMLMediaElement> self = this; 7433 mDecoder->SetCDMProxy(nullptr) 7434 ->Then( 7435 AbstractMainThread(), __func__, 7436 [self]() { 7437 self->mSetCDMRequest.Complete(); 7438 7439 self->RemoveMediaKeys(); 7440 if (self->AttachNewMediaKeys()) { 7441 // No incoming MediaKeys object or MediaDecoder is not 7442 // created yet. 7443 self->MakeAssociationWithCDMResolved(); 7444 } 7445 }, 7446 [self](const MediaResult& aResult) { 7447 self->mSetCDMRequest.Complete(); 7448 // 5.2.4 If the preceding step failed, let this object's 7449 // attaching media keys value be false and reject promise with 7450 // a new DOMException whose name is the appropriate error name. 7451 self->SetCDMProxyFailure(aResult); 7452 }) 7453 ->Track(mSetCDMRequest); 7454 return false; 7455 } 7456 7457 RemoveMediaKeys(); 7458 return true; 7459 } 7460 7461 bool HTMLMediaElement::DetachExistingMediaKeys() { 7462 LOG(LogLevel::Debug, ("%s", __func__)); 7463 MOZ_ASSERT(mSetMediaKeysDOMPromise); 7464 // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is 7465 // already in use by another media element, and the user agent is unable 7466 // to use it with this element, let this object's attaching media keys 7467 // value be false and reject promise with a new DOMException whose name 7468 // is QuotaExceededError. 7469 if (mIncomingMediaKeys && mIncomingMediaKeys->IsBoundToMediaElement()) { 7470 SetCDMProxyFailure(MediaResult( 7471 NS_ERROR_DOM_MEDIA_KEY_QUOTA_EXCEEDED_ERR, 7472 "MediaKeys object is already bound to another HTMLMediaElement")); 7473 return false; 7474 } 7475 7476 // 5.2 If the mediaKeys attribute is not null, run the following steps: 7477 if (mMediaKeys) { 7478 return TryRemoveMediaKeysAssociation(); 7479 } 7480 return true; 7481 } 7482 7483 void HTMLMediaElement::MakeAssociationWithCDMResolved() { 7484 LOG(LogLevel::Debug, ("%s", __func__)); 7485 MOZ_ASSERT(mSetMediaKeysDOMPromise); 7486 7487 // 5.4 Set the mediaKeys attribute to mediaKeys. 7488 mMediaKeys = mIncomingMediaKeys; 7489 #ifdef MOZ_WMF_CDM 7490 if (mMediaKeys && mMediaKeys->GetCDMProxy()) { 7491 mIsUsingWMFCDM = !!mMediaKeys->GetCDMProxy()->AsWMFCDMProxy(); 7492 } 7493 #endif 7494 // 5.5 Let this object's attaching media keys value be false. 7495 ResetSetMediaKeysTempVariables(); 7496 // 5.6 Resolve promise. 7497 mSetMediaKeysDOMPromise->MaybeResolveWithUndefined(); 7498 mSetMediaKeysDOMPromise = nullptr; 7499 7500 if (profiler_is_collecting_markers()) { 7501 if (mMediaKeys) { 7502 nsString keySystem; 7503 mMediaKeys->GetKeySystem(keySystem); 7504 profiler_add_marker("cdmresolved", 7505 geckoprofiler::category::MEDIA_PLAYBACK, {}, 7506 CDMResolvedMarker{}, keySystem, 7507 mMediaKeys->GetMediaKeySystemConfigurationString(), 7508 Flow::FromPointer(this)); 7509 } else { 7510 PROFILER_MARKER("removemediakey", MEDIA_PLAYBACK, {}, FlowMarker, 7511 Flow::FromPointer(this)); 7512 } 7513 } 7514 } 7515 7516 bool HTMLMediaElement::TryMakeAssociationWithCDM(CDMProxy* aProxy) { 7517 LOG(LogLevel::Debug, ("%s", __func__)); 7518 MOZ_ASSERT(aProxy); 7519 7520 // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary" 7521 // algorithm on the media element. 7522 // Note: Setting the CDMProxy on the MediaDecoder will unblock playback. 7523 if (mDecoder) { 7524 // CDMProxy is set asynchronously in MediaFormatReader, once it's done, 7525 // HTMLMediaElement should resolve or reject the DOM promise. 7526 RefPtr<HTMLMediaElement> self = this; 7527 mDecoder->SetCDMProxy(aProxy) 7528 ->Then( 7529 AbstractMainThread(), __func__, 7530 [self]() { 7531 self->mSetCDMRequest.Complete(); 7532 self->MakeAssociationWithCDMResolved(); 7533 }, 7534 [self](const MediaResult& aResult) { 7535 self->mSetCDMRequest.Complete(); 7536 self->SetCDMProxyFailure(aResult); 7537 }) 7538 ->Track(mSetCDMRequest); 7539 return false; 7540 } 7541 return true; 7542 } 7543 7544 bool HTMLMediaElement::AttachNewMediaKeys() { 7545 LOG(LogLevel::Debug, 7546 ("%s incoming MediaKeys(%p)", __func__, mIncomingMediaKeys.get())); 7547 MOZ_ASSERT(mSetMediaKeysDOMPromise); 7548 7549 // 5.3. If mediaKeys is not null, run the following steps: 7550 if (mIncomingMediaKeys) { 7551 auto* cdmProxy = mIncomingMediaKeys->GetCDMProxy(); 7552 if (!cdmProxy) { 7553 SetCDMProxyFailure(MediaResult( 7554 NS_ERROR_DOM_INVALID_STATE_ERR, 7555 "CDM crashed before binding MediaKeys object to HTMLMediaElement")); 7556 return false; 7557 } 7558 7559 // 5.3.1 Associate the CDM instance represented by mediaKeys with the 7560 // media element for decrypting media data. 7561 if (NS_FAILED(mIncomingMediaKeys->Bind(this))) { 7562 // 5.3.2 If the preceding step failed, run the following steps: 7563 7564 // 5.3.2.1 Set the mediaKeys attribute to null. 7565 mMediaKeys = nullptr; 7566 // 5.3.2.2 Let this object's attaching media keys value be false. 7567 // 5.3.2.3 Reject promise with a new DOMException whose name is 7568 // the appropriate error name. 7569 SetCDMProxyFailure( 7570 MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR, 7571 "Failed to bind MediaKeys object to HTMLMediaElement")); 7572 return false; 7573 } 7574 return TryMakeAssociationWithCDM(cdmProxy); 7575 } 7576 return true; 7577 } 7578 7579 void HTMLMediaElement::ResetSetMediaKeysTempVariables() { 7580 mAttachingMediaKey = false; 7581 mIncomingMediaKeys = nullptr; 7582 } 7583 7584 already_AddRefed<Promise> HTMLMediaElement::SetMediaKeys( 7585 mozilla::dom::MediaKeys* aMediaKeys, ErrorResult& aRv) { 7586 LOG(LogLevel::Debug, ("%p SetMediaKeys(%p) mMediaKeys=%p mDecoder=%p", this, 7587 aMediaKeys, mMediaKeys.get(), mDecoder.get())); 7588 7589 if (MozAudioCaptured()) { 7590 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 7591 return nullptr; 7592 } 7593 7594 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); 7595 if (!win) { 7596 aRv.Throw(NS_ERROR_UNEXPECTED); 7597 return nullptr; 7598 } 7599 RefPtr<DetailedPromise> promise = DetailedPromise::Create( 7600 win->AsGlobal(), aRv, "HTMLMediaElement.setMediaKeys"_ns); 7601 if (aRv.Failed()) { 7602 return nullptr; 7603 } 7604 7605 // 1. If mediaKeys and the mediaKeys attribute are the same object, 7606 // return a resolved promise. 7607 if (mMediaKeys == aMediaKeys) { 7608 promise->MaybeResolveWithUndefined(); 7609 return promise.forget(); 7610 } 7611 7612 // 2. If this object's attaching media keys value is true, return a 7613 // promise rejected with a new DOMException whose name is InvalidStateError. 7614 if (mAttachingMediaKey) { 7615 promise->MaybeRejectWithInvalidStateError( 7616 "A MediaKeys object is in attaching operation."); 7617 return promise.forget(); 7618 } 7619 7620 // 3. Let this object's attaching media keys value be true. 7621 mAttachingMediaKey = true; 7622 mIncomingMediaKeys = aMediaKeys; 7623 7624 // 4. Let promise be a new promise. 7625 mSetMediaKeysDOMPromise = promise; 7626 7627 // 5. Run the following steps in parallel: 7628 7629 // 5.1 & 5.2 & 5.3 7630 if (!DetachExistingMediaKeys() || !AttachNewMediaKeys()) { 7631 return promise.forget(); 7632 } 7633 7634 // 5.4, 5.5, 5.6 7635 MakeAssociationWithCDMResolved(); 7636 7637 // 6. Return promise. 7638 return promise.forget(); 7639 } 7640 7641 EventHandlerNonNull* HTMLMediaElement::GetOnencrypted() { 7642 return EventTarget::GetEventHandler(nsGkAtoms::onencrypted); 7643 } 7644 7645 void HTMLMediaElement::SetOnencrypted(EventHandlerNonNull* aCallback) { 7646 EventTarget::SetEventHandler(nsGkAtoms::onencrypted, aCallback); 7647 } 7648 7649 EventHandlerNonNull* HTMLMediaElement::GetOnwaitingforkey() { 7650 return EventTarget::GetEventHandler(nsGkAtoms::onwaitingforkey); 7651 } 7652 7653 void HTMLMediaElement::SetOnwaitingforkey(EventHandlerNonNull* aCallback) { 7654 EventTarget::SetEventHandler(nsGkAtoms::onwaitingforkey, aCallback); 7655 } 7656 7657 void HTMLMediaElement::DispatchEncrypted(const nsTArray<uint8_t>& aInitData, 7658 const nsAString& aInitDataType) { 7659 LOG(LogLevel::Debug, ("%p DispatchEncrypted initDataType='%s'", this, 7660 NS_ConvertUTF16toUTF8(aInitDataType).get())); 7661 7662 if (mReadyState == HAVE_NOTHING) { 7663 // Ready state not HAVE_METADATA (yet), don't dispatch encrypted now. 7664 // Queueing for later dispatch in MetadataLoaded. 7665 mPendingEncryptedInitData.AddInitData(aInitDataType, aInitData); 7666 return; 7667 } 7668 7669 RefPtr<MediaEncryptedEvent> event; 7670 if (IsCORSSameOrigin()) { 7671 event = MediaEncryptedEvent::Constructor(this, aInitDataType, aInitData); 7672 } else { 7673 event = MediaEncryptedEvent::Constructor(this); 7674 } 7675 7676 RefPtr<AsyncEventDispatcher> asyncDispatcher = 7677 new AsyncEventDispatcher(this, event.forget()); 7678 asyncDispatcher->PostDOMEvent(); 7679 if (profiler_is_collecting_markers()) { 7680 nsPrintfCString markerName{"%p:encrypted", this}; 7681 PROFILER_MARKER_UNTYPED(markerName, MEDIA_PLAYBACK); 7682 } 7683 } 7684 7685 bool HTMLMediaElement::IsEventAttributeNameInternal(nsAtom* aName) { 7686 return nsContentUtils::IsEventAttributeName( 7687 aName, EventNameType_HTML | EventNameType_HTMLMedia); 7688 } 7689 7690 void HTMLMediaElement::NotifyWaitingForKey() { 7691 LOG(LogLevel::Debug, ("%p, NotifyWaitingForKey()", this)); 7692 7693 // http://w3c.github.io/encrypted-media/#wait-for-key 7694 // 7.3.4 Queue a "waitingforkey" Event 7695 // 1. Let the media element be the specified HTMLMediaElement object. 7696 // 2. If the media element's waiting for key value is true, abort these steps. 7697 if (mWaitingForKey == NOT_WAITING_FOR_KEY) { 7698 // 3. Set the media element's waiting for key value to true. 7699 // Note: algorithm continues in UpdateReadyStateInternal() when all decoded 7700 // data enqueued in the MDSM is consumed. 7701 mWaitingForKey = WAITING_FOR_KEY; 7702 // mWaitingForKey changed outside of UpdateReadyStateInternal. This may 7703 // affect mReadyState. 7704 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal); 7705 } 7706 } 7707 7708 AudioTrackList* HTMLMediaElement::AudioTracks() { return mAudioTrackList; } 7709 7710 VideoTrackList* HTMLMediaElement::VideoTracks() { return mVideoTrackList; } 7711 7712 TextTrackList* HTMLMediaElement::GetTextTracks() { 7713 return GetOrCreateTextTrackManager()->GetTextTracks(); 7714 } 7715 7716 already_AddRefed<TextTrack> HTMLMediaElement::AddTextTrack( 7717 TextTrackKind aKind, const nsAString& aLabel, const nsAString& aLanguage) { 7718 return GetOrCreateTextTrackManager()->AddTextTrack( 7719 aKind, aLabel, aLanguage, TextTrackMode::Hidden, 7720 TextTrackReadyState::Loaded, TextTrackSource::AddTextTrack); 7721 } 7722 7723 void HTMLMediaElement::PopulatePendingTextTrackList() { 7724 if (mTextTrackManager) { 7725 mTextTrackManager->PopulatePendingList(); 7726 } 7727 } 7728 7729 TextTrackManager* HTMLMediaElement::GetOrCreateTextTrackManager() { 7730 if (!mTextTrackManager) { 7731 mTextTrackManager = new TextTrackManager(this); 7732 mTextTrackManager->AddListeners(); 7733 } 7734 return mTextTrackManager; 7735 } 7736 7737 MediaDecoderOwner::NextFrameStatus HTMLMediaElement::NextFrameStatus() { 7738 if (mDecoder) { 7739 return mDecoder->NextFrameStatus(); 7740 } 7741 if (mSrcStream) { 7742 AutoTArray<RefPtr<MediaTrack>, 4> tracks; 7743 GetAllEnabledMediaTracks(tracks); 7744 if (!tracks.IsEmpty() && !mSrcStreamPlaybackEnded) { 7745 return NEXT_FRAME_AVAILABLE; 7746 } 7747 return NEXT_FRAME_UNAVAILABLE; 7748 } 7749 return NEXT_FRAME_UNINITIALIZED; 7750 } 7751 7752 void HTMLMediaElement::SetDecoder(MediaDecoder* aDecoder) { 7753 MOZ_ASSERT(aDecoder); // Use ShutdownDecoder() to clear. 7754 if (mDecoder) { 7755 ShutdownDecoder(); 7756 } 7757 mDecoder = aDecoder; 7758 DDLINKCHILD("decoder", mDecoder.get()); 7759 if (mDecoder && mForcedHidden) { 7760 mDecoder->SetForcedHidden(mForcedHidden); 7761 } 7762 } 7763 7764 float HTMLMediaElement::ComputedVolume() const { 7765 return mMuted ? 0.0f 7766 : mAudioChannelWrapper ? mAudioChannelWrapper->GetEffectiveVolume() 7767 : static_cast<float>(mVolume); 7768 } 7769 7770 bool HTMLMediaElement::ComputedMuted() const { 7771 return (mMuted & MUTED_BY_AUDIO_CHANNEL); 7772 } 7773 7774 bool HTMLMediaElement::IsSuspendedByInactiveDocOrDocShell() const { 7775 return mSuspendedByInactiveDocOrDocshell; 7776 } 7777 7778 bool HTMLMediaElement::IsCurrentlyPlaying() const { 7779 // We have playable data, but we still need to check whether data is "real" 7780 // current data. 7781 return mReadyState >= HAVE_CURRENT_DATA && !IsPlaybackEnded(); 7782 } 7783 7784 void HTMLMediaElement::SetAudibleState(bool aAudible) { 7785 if (mIsAudioTrackAudible != aAudible) { 7786 mIsAudioTrackAudible = aAudible; 7787 NotifyAudioPlaybackChanged( 7788 AudioChannelService::AudibleChangedReasons::eDataAudibleChanged); 7789 } 7790 } 7791 7792 void HTMLMediaElement::NotifyAudioPlaybackChanged( 7793 AudibleChangedReasons aReason) { 7794 if (mAudioChannelWrapper) { 7795 mAudioChannelWrapper->NotifyAudioPlaybackChanged(aReason); 7796 } 7797 // We would start the listener after media becomes audible. 7798 const bool isAudible = IsAudible(); 7799 if (isAudible && !mMediaControlKeyListener->IsStarted()) { 7800 StartMediaControlKeyListenerIfNeeded(); 7801 } 7802 mMediaControlKeyListener->UpdateMediaAudibleState(isAudible); 7803 // only request wake lock for audible media. 7804 UpdateWakeLock(); 7805 } 7806 7807 void HTMLMediaElement::SetMediaInfo(const MediaInfo& aInfo) { 7808 const bool oldHasAudio = mMediaInfo.HasAudio(); 7809 mMediaInfo = aInfo; 7810 if ((aInfo.HasAudio() != oldHasAudio) && mResumeDelayedPlaybackAgent) { 7811 mResumeDelayedPlaybackAgent->UpdateAudibleState(this, IsAudible()); 7812 } 7813 nsILoadContext* loadContext = OwnerDoc()->GetLoadContext(); 7814 if (HasAudio() && loadContext && !loadContext->UsePrivateBrowsing()) { 7815 mTitleChangeObserver->Subscribe(); 7816 UpdateStreamName(); 7817 } else { 7818 mTitleChangeObserver->Unsubscribe(); 7819 } 7820 if (mAudioChannelWrapper) { 7821 mAudioChannelWrapper->AudioCaptureTrackChangeIfNeeded(); 7822 } 7823 UpdateWakeLock(); 7824 } 7825 7826 MediaInfo HTMLMediaElement::GetMediaInfo() const { return mMediaInfo; } 7827 7828 FrameStatistics* HTMLMediaElement::GetFrameStatistics() const { 7829 return mDecoder ? &(mDecoder->GetFrameStatistics()) : nullptr; 7830 } 7831 7832 void HTMLMediaElement::DispatchAsyncTestingEvent(const nsAString& aName) { 7833 if (!StaticPrefs::media_testing_only_events()) { 7834 return; 7835 } 7836 QueueEvent(aName); 7837 } 7838 7839 void HTMLMediaElement::AudioCaptureTrackChange(bool aCapture) { 7840 // No need to capture a silent media element. 7841 if (!HasAudio()) { 7842 return; 7843 } 7844 7845 if (aCapture && !mStreamWindowCapturer) { 7846 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow(); 7847 if (!window) { 7848 return; 7849 } 7850 7851 MediaTrackGraph* mtg = MediaTrackGraph::GetInstance( 7852 MediaTrackGraph::AUDIO_THREAD_DRIVER, window, 7853 MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, 7854 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE); 7855 RefPtr<DOMMediaStream> stream = 7856 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED, 7857 StreamCaptureType::CAPTURE_AUDIO, mtg); 7858 mStreamWindowCapturer = 7859 new MediaStreamWindowCapturer(stream, window->WindowID()); 7860 mStreamWindowCapturer->mStream->RegisterTrackListener( 7861 mStreamWindowCapturer); 7862 } else if (!aCapture && mStreamWindowCapturer) { 7863 for (size_t i = 0; i < mOutputStreams.Length(); i++) { 7864 if (mOutputStreams[i].mStream == mStreamWindowCapturer->mStream) { 7865 // We own this MediaStream, it is not exposed to JS. 7866 AutoTArray<RefPtr<MediaStreamTrack>, 2> tracks; 7867 mStreamWindowCapturer->mStream->GetTracks(tracks); 7868 for (auto& track : tracks) { 7869 track->Stop(); 7870 } 7871 mOutputStreams.RemoveElementAt(i); 7872 break; 7873 } 7874 } 7875 7876 mStreamWindowCapturer->mStream->UnregisterTrackListener( 7877 mStreamWindowCapturer); 7878 mStreamWindowCapturer = nullptr; 7879 if (mOutputStreams.IsEmpty()) { 7880 mTracksCaptured = nullptr; 7881 } 7882 } 7883 } 7884 7885 void HTMLMediaElement::NotifyCueDisplayStatesChanged() { 7886 if (!mTextTrackManager) { 7887 return; 7888 } 7889 7890 mTextTrackManager->DispatchUpdateCueDisplay(); 7891 } 7892 7893 void HTMLMediaElement::LogVisibility(CallerAPI aAPI) { 7894 const bool isVisible = mVisibilityState == Visibility::ApproximatelyVisible; 7895 7896 LOG(LogLevel::Debug, ("%p visibility = %u, API: '%d' and 'All'", this, 7897 isVisible, static_cast<int>(aAPI))); 7898 7899 if (!isVisible) { 7900 LOG(LogLevel::Debug, ("%p inTree = %u, API: '%d' and 'All'", this, 7901 IsInComposedDoc(), static_cast<int>(aAPI))); 7902 } 7903 } 7904 7905 void HTMLMediaElement::UpdateCustomPolicyAfterPlayed() { 7906 if (mAudioChannelWrapper) { 7907 mAudioChannelWrapper->NotifyPlayStateChanged(); 7908 } 7909 } 7910 7911 nsTArray<RefPtr<PlayPromise>> HTMLMediaElement::TakePendingPlayPromises() { 7912 return std::move(mPendingPlayPromises); 7913 } 7914 7915 void HTMLMediaElement::NotifyAboutPlaying() { 7916 // Stick to the QueueEvent() call path for now because we want to 7917 // trigger some telemetry-related codes in the QueueEvent() method. 7918 QueueEvent(u"playing"_ns); 7919 } 7920 7921 already_AddRefed<PlayPromise> HTMLMediaElement::CreatePlayPromise( 7922 ErrorResult& aRv) const { 7923 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); 7924 7925 if (!win) { 7926 aRv.Throw(NS_ERROR_UNEXPECTED); 7927 return nullptr; 7928 } 7929 7930 RefPtr<PlayPromise> promise = PlayPromise::Create(win->AsGlobal(), aRv); 7931 LOG(LogLevel::Debug, ("%p created PlayPromise %p", this, promise.get())); 7932 7933 return promise.forget(); 7934 } 7935 7936 already_AddRefed<Promise> HTMLMediaElement::CreateDOMPromise( 7937 ErrorResult& aRv) const { 7938 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); 7939 7940 if (!win) { 7941 aRv.Throw(NS_ERROR_UNEXPECTED); 7942 return nullptr; 7943 } 7944 7945 return Promise::Create(win->AsGlobal(), aRv); 7946 } 7947 7948 void HTMLMediaElement::AsyncResolvePendingPlayPromises() { 7949 if (mShuttingDown) { 7950 return; 7951 } 7952 7953 nsCOMPtr<nsIRunnable> event = new nsResolveOrRejectPendingPlayPromisesRunner( 7954 this, TakePendingPlayPromises()); 7955 7956 GetMainThreadSerialEventTarget()->Dispatch(event.forget()); 7957 } 7958 7959 void HTMLMediaElement::AsyncRejectPendingPlayPromises(nsresult aError) { 7960 if (!mPaused) { 7961 mPaused = true; 7962 QueueEvent(u"pause"_ns); 7963 } 7964 7965 if (mShuttingDown) { 7966 return; 7967 } 7968 7969 if (aError == NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR) { 7970 DispatchEventsWhenPlayWasNotAllowed(); 7971 } 7972 7973 nsCOMPtr<nsIRunnable> event = new nsResolveOrRejectPendingPlayPromisesRunner( 7974 this, TakePendingPlayPromises(), aError); 7975 7976 GetMainThreadSerialEventTarget()->Dispatch(event.forget()); 7977 } 7978 7979 void HTMLMediaElement::GetEMEInfo(dom::EMEDebugInfo& aInfo) { 7980 MOZ_ASSERT(NS_IsMainThread(), 7981 "MediaKeys expects to be interacted with on main thread!"); 7982 if (!mMediaKeys) { 7983 return; 7984 } 7985 mMediaKeys->GetKeySystem(aInfo.mKeySystem); 7986 mMediaKeys->GetSessionsInfo(aInfo.mSessionsInfo); 7987 } 7988 7989 void HTMLMediaElement::NotifyDecoderActivityChanges() const { 7990 if (mDecoder) { 7991 mDecoder->NotifyOwnerActivityChanged( 7992 IsActuallyInvisible(), IsInComposedDoc(), 7993 OwnerDoc()->IsInBackgroundWindow(), HasPendingCallbacks()); 7994 } 7995 } 7996 7997 Document* HTMLMediaElement::GetDocument() const { return OwnerDoc(); } 7998 7999 bool HTMLMediaElement::IsAudible() const { 8000 // No audio track. 8001 if (!HasAudio()) { 8002 return false; 8003 } 8004 8005 // Muted or the volume should not be ~0 8006 if (mMuted || (std::fabs(Volume()) <= 1e-7)) { 8007 return false; 8008 } 8009 8010 return mIsAudioTrackAudible; 8011 } 8012 8013 Maybe<nsAutoString> HTMLMediaElement::GetKeySystem() const { 8014 if (!mMediaKeys) { 8015 return Nothing(); 8016 } 8017 nsAutoString keySystem; 8018 mMediaKeys->GetKeySystem(keySystem); 8019 return Some(keySystem); 8020 } 8021 8022 void HTMLMediaElement::ConstructMediaTracks(const MediaInfo* aInfo) { 8023 if (!aInfo) { 8024 return; 8025 } 8026 8027 AudioTrackList* audioList = AudioTracks(); 8028 if (audioList && aInfo->HasAudio()) { 8029 const TrackInfo& info = aInfo->mAudio; 8030 RefPtr<AudioTrack> track = MediaTrackList::CreateAudioTrack( 8031 audioList->GetOwnerGlobal(), info.mId, info.mKind, info.mLabel, 8032 info.mLanguage, info.mEnabled); 8033 8034 audioList->AddTrack(track); 8035 } 8036 8037 VideoTrackList* videoList = VideoTracks(); 8038 if (videoList && aInfo->HasVideo()) { 8039 const TrackInfo& info = aInfo->mVideo; 8040 RefPtr<VideoTrack> track = MediaTrackList::CreateVideoTrack( 8041 videoList->GetOwnerGlobal(), info.mId, info.mKind, info.mLabel, 8042 info.mLanguage); 8043 8044 videoList->AddTrack(track); 8045 track->SetEnabledInternal(info.mEnabled, MediaTrack::FIRE_NO_EVENTS); 8046 } 8047 } 8048 8049 void HTMLMediaElement::RemoveMediaTracks() { 8050 if (mAudioTrackList) { 8051 mAudioTrackList->RemoveTracks(); 8052 } 8053 if (mVideoTrackList) { 8054 mVideoTrackList->RemoveTracks(); 8055 } 8056 } 8057 8058 class MediaElementGMPCrashHelper : public GMPCrashHelper { 8059 public: 8060 explicit MediaElementGMPCrashHelper(HTMLMediaElement* aElement) 8061 : mElement(aElement) { 8062 MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe. 8063 } 8064 already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override { 8065 MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe. 8066 if (!mElement) { 8067 return nullptr; 8068 } 8069 return do_AddRef(mElement->OwnerDoc()->GetInnerWindow()); 8070 } 8071 8072 private: 8073 WeakPtr<HTMLMediaElement> mElement; 8074 }; 8075 8076 already_AddRefed<GMPCrashHelper> HTMLMediaElement::CreateGMPCrashHelper() { 8077 return MakeAndAddRef<MediaElementGMPCrashHelper>(this); 8078 } 8079 8080 void HTMLMediaElement::MarkAsTainted() { 8081 mHasSuspendTaint = true; 8082 8083 if (mDecoder) { 8084 mDecoder->SetSuspendTaint(true); 8085 } 8086 } 8087 8088 bool HasDebuggerOrTabsPrivilege(JSContext* aCx, JSObject* aObj) { 8089 return nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::debugger) || 8090 nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::tabs); 8091 } 8092 8093 already_AddRefed<Promise> HTMLMediaElement::SetSinkId(const nsAString& aSinkId, 8094 ErrorResult& aRv) { 8095 LOG(LogLevel::Info, 8096 ("%p, setSinkId(%s)", this, NS_ConvertUTF16toUTF8(aSinkId).get())); 8097 nsCOMPtr<nsPIDOMWindowInner> win = OwnerDoc()->GetInnerWindow(); 8098 if (!win) { 8099 aRv.Throw(NS_ERROR_UNEXPECTED); 8100 return nullptr; 8101 } 8102 8103 RefPtr<Promise> promise = Promise::Create(win->AsGlobal(), aRv); 8104 if (aRv.Failed()) { 8105 return nullptr; 8106 } 8107 8108 if (!FeaturePolicyUtils::IsFeatureAllowed(win->GetExtantDoc(), 8109 u"speaker-selection"_ns)) { 8110 promise->MaybeRejectWithNotAllowedError( 8111 "Document's Permissions Policy does not allow setSinkId()"); 8112 } 8113 8114 if (mSink.first.Equals(aSinkId)) { 8115 promise->MaybeResolveWithUndefined(); 8116 return promise.forget(); 8117 } 8118 8119 RefPtr<MediaDevices> mediaDevices = win->Navigator()->GetMediaDevices(aRv); 8120 if (aRv.Failed()) { 8121 return nullptr; 8122 } 8123 8124 nsString sinkId(aSinkId); 8125 mediaDevices->GetSinkDevice(sinkId) 8126 ->Then( 8127 AbstractMainThread(), __func__, 8128 [self = RefPtr<HTMLMediaElement>(this), 8129 this](RefPtr<AudioDeviceInfo>&& aInfo) { 8130 // Sink found switch output device. 8131 MOZ_ASSERT(aInfo); 8132 if (mDecoder) { 8133 RefPtr<SinkInfoPromise> p = mDecoder->SetSink(aInfo)->Then( 8134 AbstractMainThread(), __func__, 8135 [aInfo](const GenericPromise::ResolveOrRejectValue& aValue) { 8136 if (aValue.IsResolve()) { 8137 return SinkInfoPromise::CreateAndResolve(aInfo, __func__); 8138 } 8139 return SinkInfoPromise::CreateAndReject( 8140 aValue.RejectValue(), __func__); 8141 }); 8142 return p; 8143 } 8144 if (mSrcStream) { 8145 MOZ_ASSERT(mMediaStreamRenderer); 8146 RefPtr<SinkInfoPromise> p = 8147 mMediaStreamRenderer->SetAudioOutputDevice(aInfo)->Then( 8148 AbstractMainThread(), __func__, 8149 [aInfo]( 8150 const GenericPromise::ResolveOrRejectValue& aValue) { 8151 if (aValue.IsResolve()) { 8152 return SinkInfoPromise::CreateAndResolve(aInfo, 8153 __func__); 8154 } 8155 return SinkInfoPromise::CreateAndReject( 8156 aValue.RejectValue(), __func__); 8157 }); 8158 return p; 8159 } 8160 // No media attached to the element save it for later. 8161 return SinkInfoPromise::CreateAndResolve(aInfo, __func__); 8162 }, 8163 [](nsresult res) { 8164 // Promise is rejected, sink not found. 8165 return SinkInfoPromise::CreateAndReject(res, __func__); 8166 }) 8167 ->Then(AbstractMainThread(), __func__, 8168 [promise, self = RefPtr<HTMLMediaElement>(this), this, 8169 sinkId](const SinkInfoPromise::ResolveOrRejectValue& aValue) { 8170 if (aValue.IsResolve()) { 8171 LOG(LogLevel::Info, ("%p, set sinkid=%s", this, 8172 NS_ConvertUTF16toUTF8(sinkId).get())); 8173 mSink = std::pair(sinkId, aValue.ResolveValue()); 8174 promise->MaybeResolveWithUndefined(); 8175 } else { 8176 switch (aValue.RejectValue()) { 8177 case NS_ERROR_ABORT: 8178 promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); 8179 break; 8180 case NS_ERROR_NOT_AVAILABLE: { 8181 promise->MaybeRejectWithNotFoundError( 8182 "The object can not be found here."); 8183 break; 8184 } 8185 default: 8186 MOZ_ASSERT_UNREACHABLE("Invalid error."); 8187 } 8188 } 8189 }); 8190 8191 aRv = NS_OK; 8192 return promise.forget(); 8193 } 8194 8195 void HTMLMediaElement::NotifyTextTrackModeChanged() { 8196 if (mPendingTextTrackChanged) { 8197 return; 8198 } 8199 mPendingTextTrackChanged = true; 8200 AbstractMainThread()->Dispatch( 8201 NS_NewRunnableFunction("HTMLMediaElement::NotifyTextTrackModeChanged", 8202 [this, self = RefPtr<HTMLMediaElement>(this)]() { 8203 mPendingTextTrackChanged = false; 8204 if (!mTextTrackManager) { 8205 return; 8206 } 8207 GetTextTracks()->CreateAndDispatchChangeEvent(); 8208 // https://html.spec.whatwg.org/multipage/media.html#text-track-model:show-poster-flag 8209 if (!mShowPoster) { 8210 mTextTrackManager->TimeMarchesOn(); 8211 } 8212 })); 8213 } 8214 8215 void HTMLMediaElement::CreateResumeDelayedMediaPlaybackAgentIfNeeded() { 8216 if (mResumeDelayedPlaybackAgent) { 8217 return; 8218 } 8219 mResumeDelayedPlaybackAgent = 8220 MediaPlaybackDelayPolicy::CreateResumeDelayedPlaybackAgent(this, 8221 IsAudible()); 8222 if (!mResumeDelayedPlaybackAgent) { 8223 LOG(LogLevel::Debug, 8224 ("%p Failed to create a delayed playback agant", this)); 8225 return; 8226 } 8227 mResumeDelayedPlaybackAgent->GetResumePromise() 8228 ->Then( 8229 AbstractMainThread(), __func__, 8230 [self = RefPtr<HTMLMediaElement>(this)]() { 8231 LOG(LogLevel::Debug, ("%p Resume delayed Play() call", self.get())); 8232 self->mResumePlaybackRequest.Complete(); 8233 self->mResumeDelayedPlaybackAgent = nullptr; 8234 IgnoredErrorResult dummy; 8235 RefPtr<Promise> toBeIgnored = self->Play(dummy); 8236 }, 8237 [self = RefPtr<HTMLMediaElement>(this)]() { 8238 LOG(LogLevel::Debug, 8239 ("%p Can not resume delayed Play() call", self.get())); 8240 self->mResumePlaybackRequest.Complete(); 8241 self->mResumeDelayedPlaybackAgent = nullptr; 8242 }) 8243 ->Track(mResumePlaybackRequest); 8244 } 8245 8246 void HTMLMediaElement::ClearResumeDelayedMediaPlaybackAgentIfNeeded() { 8247 if (mResumeDelayedPlaybackAgent) { 8248 mResumePlaybackRequest.DisconnectIfExists(); 8249 mResumeDelayedPlaybackAgent = nullptr; 8250 } 8251 } 8252 8253 void HTMLMediaElement::NotifyMediaControlPlaybackStateChanged() { 8254 if (!mMediaControlKeyListener->IsStarted()) { 8255 return; 8256 } 8257 if (mPaused) { 8258 mMediaControlKeyListener->NotifyMediaStoppedPlaying(); 8259 } else { 8260 mMediaControlKeyListener->NotifyMediaStartedPlaying(); 8261 } 8262 } 8263 8264 bool HTMLMediaElement::IsInFullScreen() const { 8265 return State().HasState(ElementState::FULLSCREEN); 8266 } 8267 8268 bool HTMLMediaElement::IsPlayable() const { 8269 return (mDecoder || mSrcStream) && !HasError(); 8270 } 8271 8272 bool HTMLMediaElement::ShouldStartMediaControlKeyListener() const { 8273 if (!IsPlayable()) { 8274 MEDIACONTROL_LOG("Not start listener because media is not playable"); 8275 return false; 8276 } 8277 8278 if (mSrcStream) { 8279 MEDIACONTROL_LOG("Not listening because media is real-time"); 8280 return false; 8281 } 8282 8283 if (IsBeingUsedInPictureInPictureMode()) { 8284 MEDIACONTROL_LOG("Start listener because of being used in PiP mode"); 8285 return true; 8286 } 8287 8288 if (IsInFullScreen()) { 8289 MEDIACONTROL_LOG("Start listener because of being used in fullscreen"); 8290 return true; 8291 } 8292 8293 // In order to filter out notification-ish sound, we use this pref to set the 8294 // eligible media duration to prevent showing media control for those short 8295 // sound. 8296 if (Duration() < 8297 StaticPrefs::media_mediacontrol_eligible_media_duration_s()) { 8298 MEDIACONTROL_LOG("Not listening because media's duration %f is too short.", 8299 Duration()); 8300 return false; 8301 } 8302 8303 // This includes cases such like `video is muted`, `video has zero volume`, 8304 // `video's audio track is still inaudible` and `tab is muted by audio channel 8305 // (tab sound indicator)`, all these cases would make media inaudible. 8306 // `ComputedVolume()` would return the final volume applied the affection made 8307 // by audio channel, which is used to detect if the tab is muted by audio 8308 // channel. 8309 if (!IsAudible() || ComputedVolume() == 0.0f) { 8310 MEDIACONTROL_LOG("Not listening because media is inaudible"); 8311 return false; 8312 } 8313 return true; 8314 } 8315 8316 void HTMLMediaElement::StartMediaControlKeyListenerIfNeeded() { 8317 if (!ShouldStartMediaControlKeyListener()) { 8318 return; 8319 } 8320 mMediaControlKeyListener->Start(); 8321 } 8322 8323 void HTMLMediaElement::UpdateStreamName() { 8324 MOZ_ASSERT(NS_IsMainThread()); 8325 8326 nsAutoString aTitle; 8327 OwnerDoc()->GetTitle(aTitle); 8328 8329 if (mDecoder) { 8330 mDecoder->SetStreamName(aTitle); 8331 } 8332 } 8333 8334 void HTMLMediaElement::SetSecondaryMediaStreamRenderer( 8335 VideoFrameContainer* aContainer, 8336 FirstFrameVideoOutput* aFirstFrameOutput /* = nullptr */) { 8337 MOZ_ASSERT(mSrcStream); 8338 MOZ_ASSERT(mMediaStreamRenderer); 8339 if (mSecondaryMediaStreamRenderer) { 8340 mSecondaryMediaStreamRenderer->Shutdown(); 8341 mSecondaryMediaStreamRenderer = nullptr; 8342 } 8343 if (aContainer) { 8344 mSecondaryMediaStreamRenderer = MakeAndAddRef<MediaStreamRenderer>( 8345 AbstractMainThread(), aContainer, aFirstFrameOutput, this); 8346 if (mSrcStreamIsPlaying) { 8347 mSecondaryMediaStreamRenderer->Start(); 8348 } 8349 if (mSelectedVideoStreamTrack) { 8350 mSecondaryMediaStreamRenderer->AddTrack(mSelectedVideoStreamTrack); 8351 } 8352 } 8353 } 8354 8355 void HTMLMediaElement::UpdateMediaControlAfterPictureInPictureModeChanged() { 8356 if (IsBeingUsedInPictureInPictureMode()) { 8357 // When media enters PIP mode, we should ensure that the listener has been 8358 // started because we always want to control PIP video. 8359 StartMediaControlKeyListenerIfNeeded(); 8360 if (!mMediaControlKeyListener->IsStarted()) { 8361 MEDIACONTROL_LOG("Failed to start listener when entering PIP mode"); 8362 } 8363 // Updating controller PIP state no matter the listener starts or not. 8364 mMediaControlKeyListener->SetPictureInPictureModeEnabled(true); 8365 } else { 8366 mMediaControlKeyListener->SetPictureInPictureModeEnabled(false); 8367 } 8368 } 8369 8370 bool HTMLMediaElement::IsBeingUsedInPictureInPictureMode() const { 8371 if (!IsVideo()) { 8372 return false; 8373 } 8374 return static_cast<const HTMLVideoElement*>(this)->IsCloningElementVisually(); 8375 } 8376 8377 void HTMLMediaElement::NodeInfoChanged(Document* aOldDoc) { 8378 if (mMediaSource) { 8379 OwnerDoc()->AddMediaElementWithMSE(); 8380 aOldDoc->RemoveMediaElementWithMSE(); 8381 } 8382 8383 nsGenericHTMLElement::NodeInfoChanged(aOldDoc); 8384 } 8385 8386 void HTMLMediaElement::MaybeMarkSHEntryAsUserInteracted() { 8387 if (media::AutoplayPolicy::GetAutoplayPolicy(*this) == 8388 dom::AutoplayPolicy::Allowed) { 8389 // Only mark entries when autoplay is allowed for both audio and video, 8390 // i.e. when AutoplayPolicy is not Blocked or Allowed_muted. 8391 OwnerDoc()->SetSHEntryHasUserInteraction(true); 8392 } 8393 } 8394 8395 #ifdef MOZ_WMF_CDM 8396 bool HTMLMediaElement::IsUsingWMFCDM() const { return mIsUsingWMFCDM; }; 8397 8398 CDMProxy* HTMLMediaElement::GetCDMProxy() const { 8399 return mMediaKeys ? mMediaKeys->GetCDMProxy() : nullptr; 8400 }; 8401 #endif 8402 8403 } // namespace mozilla::dom 8404 8405 #undef LOG 8406 #undef LOG_EVENT