2 * Copyright (C) 2007-2018 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "HTMLMediaElement.h"
31 #include "ApplicationCacheHost.h"
32 #include "ApplicationCacheResource.h"
33 #include "Attribute.h"
35 #include "CSSPropertyNames.h"
36 #include "CSSValueKeywords.h"
37 #include "ChromeClient.h"
39 #include "ContentSecurityPolicy.h"
40 #include "ContentType.h"
41 #include "CookieJar.h"
42 #include "DeprecatedGlobalSettings.h"
43 #include "DiagnosticLoggingClient.h"
44 #include "DiagnosticLoggingKeys.h"
46 #include "DocumentLoader.h"
47 #include "ElementChildIterator.h"
48 #include "EventNames.h"
50 #include "FrameLoader.h"
51 #include "FrameLoaderClient.h"
52 #include "FrameView.h"
53 #include "HTMLParserIdioms.h"
54 #include "HTMLSourceElement.h"
55 #include "HTMLVideoElement.h"
56 #include "InspectorInstrumentation.h"
57 #include "JSDOMException.h"
58 #include "JSDOMPromiseDeferred.h"
59 #include "JSHTMLMediaElement.h"
61 #include "MIMETypeRegistry.h"
62 #include "MediaController.h"
63 #include "MediaControls.h"
64 #include "MediaDocument.h"
65 #include "MediaError.h"
66 #include "MediaFragmentURIParser.h"
67 #include "MediaList.h"
68 #include "MediaPlayer.h"
69 #include "MediaQueryEvaluator.h"
70 #include "MediaResourceLoader.h"
71 #include "NetworkingContext.h"
73 #include "PageGroup.h"
74 #include "PlatformMediaSessionManager.h"
75 #include "ProgressTracker.h"
76 #include "PublicSuffix.h"
78 #include "RenderLayerCompositor.h"
79 #include "RenderTheme.h"
80 #include "RenderVideo.h"
81 #include "RenderView.h"
82 #include "ResourceLoadInfo.h"
83 #include "ScriptController.h"
84 #include "ScriptDisallowedScope.h"
85 #include "ScriptSourceCode.h"
86 #include "SecurityOriginData.h"
87 #include "SecurityPolicy.h"
89 #include "ShadowRoot.h"
90 #include "TimeRanges.h"
91 #include "UserContentController.h"
92 #include "UserGestureIndicator.h"
93 #include "VideoPlaybackQuality.h"
94 #include <JavaScriptCore/Uint8Array.h>
96 #include <pal/SessionID.h>
97 #include <pal/system/SleepDisabler.h>
98 #include <wtf/Algorithms.h>
99 #include <wtf/IsoMallocInlines.h>
100 #include <wtf/Language.h>
101 #include <wtf/MathExtras.h>
102 #include <wtf/MemoryPressureHandler.h>
104 #include <wtf/text/CString.h>
106 #if ENABLE(VIDEO_TRACK)
107 #include "AudioTrackList.h"
108 #include "HTMLTrackElement.h"
109 #include "InbandGenericTextTrack.h"
110 #include "InbandTextTrackPrivate.h"
111 #include "InbandWebVTTTextTrack.h"
112 #include "RuntimeEnabledFeatures.h"
113 #include "TextTrackCueList.h"
114 #include "TextTrackList.h"
115 #include "VideoTrackList.h"
118 #if ENABLE(WEB_AUDIO)
119 #include "AudioSourceProvider.h"
120 #include "MediaElementAudioSourceNode.h"
123 #if PLATFORM(IOS_FAMILY)
124 #include "RuntimeApplicationChecks.h"
125 #include "VideoFullscreenInterfaceAVKit.h"
128 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
129 #include "WebKitPlaybackTargetAvailabilityEvent.h"
132 #if ENABLE(MEDIA_SESSION)
133 #include "MediaSession.h"
136 #if ENABLE(MEDIA_SOURCE)
137 #include "DOMWindow.h"
138 #include "MediaSource.h"
141 #if ENABLE(MEDIA_STREAM)
143 #include "MediaStream.h"
144 #include "MediaStreamRegistry.h"
147 #if ENABLE(LEGACY_ENCRYPTED_MEDIA)
148 #include "WebKitMediaKeyNeededEvent.h"
149 #include "WebKitMediaKeys.h"
152 #if ENABLE(ENCRYPTED_MEDIA)
153 #include "MediaEncryptedEvent.h"
154 #include "MediaKeys.h"
157 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
158 #include "JSMediaControlsHost.h"
159 #include "MediaControlsHost.h"
160 #include <JavaScriptCore/ScriptObject.h>
163 #if ENABLE(ENCRYPTED_MEDIA)
164 #include "NotImplemented.h"
167 #if PLATFORM(IOS_FAMILY) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
168 #include "VideoFullscreenModel.h"
173 struct LogArgument<URL> {
174 static String toString(const URL& url)
177 static const unsigned maximumURLLengthForLogging = 512;
179 if (url.string().length() < maximumURLLengthForLogging)
181 return url.string().substring(0, maximumURLLengthForLogging) + "...";
193 WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLMediaElement);
197 static const Seconds SeekRepeatDelay { 100_ms };
198 static const double SeekTime = 0.2;
199 static const Seconds ScanRepeatDelay { 1.5_s };
200 static const double ScanMaximumRate = 8;
201 static const double AutoplayInterferenceTimeThreshold = 10;
202 static const Seconds hideMediaControlsAfterEndedDelay { 6_s };
204 #ifndef LOG_CACHED_TIME_WARNINGS
205 // Default to not logging warnings about excessive drift in the cached media time because it adds a
206 // fair amount of overhead and logging.
207 #define LOG_CACHED_TIME_WARNINGS 0
210 #if ENABLE(MEDIA_SOURCE)
211 // URL protocol used to signal that the media source API is being used.
212 static const char* mediaSourceBlobProtocol = "blob";
215 #if ENABLE(MEDIA_STREAM)
216 // URL protocol used to signal that the media stream API is being used.
217 static const char* mediaStreamBlobProtocol = "blob";
220 using namespace HTMLNames;
222 String convertEnumerationToString(HTMLMediaElement::ReadyState enumerationValue)
224 static const NeverDestroyed<String> values[] = {
225 MAKE_STATIC_STRING_IMPL("HAVE_NOTHING"),
226 MAKE_STATIC_STRING_IMPL("HAVE_METADATA"),
227 MAKE_STATIC_STRING_IMPL("HAVE_CURRENT_DATA"),
228 MAKE_STATIC_STRING_IMPL("HAVE_FUTURE_DATA"),
229 MAKE_STATIC_STRING_IMPL("HAVE_ENOUGH_DATA"),
231 static_assert(static_cast<size_t>(HTMLMediaElementEnums::HAVE_NOTHING) == 0, "HTMLMediaElement::HAVE_NOTHING is not 0 as expected");
232 static_assert(static_cast<size_t>(HTMLMediaElementEnums::HAVE_METADATA) == 1, "HTMLMediaElement::HAVE_METADATA is not 1 as expected");
233 static_assert(static_cast<size_t>(HTMLMediaElementEnums::HAVE_CURRENT_DATA) == 2, "HTMLMediaElement::HAVE_CURRENT_DATA is not 2 as expected");
234 static_assert(static_cast<size_t>(HTMLMediaElementEnums::HAVE_FUTURE_DATA) == 3, "HTMLMediaElement::HAVE_FUTURE_DATA is not 3 as expected");
235 static_assert(static_cast<size_t>(HTMLMediaElementEnums::HAVE_ENOUGH_DATA) == 4, "HTMLMediaElement::HAVE_ENOUGH_DATA is not 4 as expected");
236 ASSERT(static_cast<size_t>(enumerationValue) < WTF_ARRAY_LENGTH(values));
237 return values[static_cast<size_t>(enumerationValue)];
240 String convertEnumerationToString(HTMLMediaElement::NetworkState enumerationValue)
242 static const NeverDestroyed<String> values[] = {
243 MAKE_STATIC_STRING_IMPL("NETWORK_EMPTY"),
244 MAKE_STATIC_STRING_IMPL("NETWORK_IDLE"),
245 MAKE_STATIC_STRING_IMPL("NETWORK_LOADING"),
246 MAKE_STATIC_STRING_IMPL("NETWORK_NO_SOURCE"),
248 static_assert(static_cast<size_t>(HTMLMediaElementEnums::NETWORK_EMPTY) == 0, "HTMLMediaElementEnums::NETWORK_EMPTY is not 0 as expected");
249 static_assert(static_cast<size_t>(HTMLMediaElementEnums::NETWORK_IDLE) == 1, "HTMLMediaElementEnums::NETWORK_IDLE is not 1 as expected");
250 static_assert(static_cast<size_t>(HTMLMediaElementEnums::NETWORK_LOADING) == 2, "HTMLMediaElementEnums::NETWORK_LOADING is not 2 as expected");
251 static_assert(static_cast<size_t>(HTMLMediaElementEnums::NETWORK_NO_SOURCE) == 3, "HTMLMediaElementEnums::NETWORK_NO_SOURCE is not 3 as expected");
252 ASSERT(static_cast<size_t>(enumerationValue) < WTF_ARRAY_LENGTH(values));
253 return values[static_cast<size_t>(enumerationValue)];
256 String convertEnumerationToString(HTMLMediaElement::AutoplayEventPlaybackState enumerationValue)
258 static const NeverDestroyed<String> values[] = {
259 MAKE_STATIC_STRING_IMPL("None"),
260 MAKE_STATIC_STRING_IMPL("PreventedAutoplay"),
261 MAKE_STATIC_STRING_IMPL("StartedWithUserGesture"),
262 MAKE_STATIC_STRING_IMPL("StartedWithoutUserGesture"),
264 static_assert(static_cast<size_t>(HTMLMediaElement::AutoplayEventPlaybackState::None) == 0, "AutoplayEventPlaybackState::None is not 0 as expected");
265 static_assert(static_cast<size_t>(HTMLMediaElement::AutoplayEventPlaybackState::PreventedAutoplay) == 1, "AutoplayEventPlaybackState::PreventedAutoplay is not 1 as expected");
266 static_assert(static_cast<size_t>(HTMLMediaElement::AutoplayEventPlaybackState::StartedWithUserGesture) == 2, "AutoplayEventPlaybackState::StartedWithUserGesture is not 2 as expected");
267 static_assert(static_cast<size_t>(HTMLMediaElement::AutoplayEventPlaybackState::StartedWithoutUserGesture) == 3, "AutoplayEventPlaybackState::StartedWithoutUserGesture is not 3 as expected");
268 ASSERT(static_cast<size_t>(enumerationValue) < WTF_ARRAY_LENGTH(values));
269 return values[static_cast<size_t>(enumerationValue)];
272 typedef HashMap<Document*, HashSet<HTMLMediaElement*>> DocumentElementSetMap;
273 static DocumentElementSetMap& documentToElementSetMap()
275 static NeverDestroyed<DocumentElementSetMap> map;
279 static void addElementToDocumentMap(HTMLMediaElement& element, Document& document)
281 DocumentElementSetMap& map = documentToElementSetMap();
282 HashSet<HTMLMediaElement*> set = map.take(&document);
284 map.add(&document, set);
287 static void removeElementFromDocumentMap(HTMLMediaElement& element, Document& document)
289 DocumentElementSetMap& map = documentToElementSetMap();
290 HashSet<HTMLMediaElement*> set = map.take(&document);
291 set.remove(&element);
293 map.add(&document, set);
296 #if ENABLE(VIDEO_TRACK)
298 class TrackDisplayUpdateScope {
300 TrackDisplayUpdateScope(HTMLMediaElement& element)
303 m_element.beginIgnoringTrackDisplayUpdateRequests();
305 ~TrackDisplayUpdateScope()
307 m_element.endIgnoringTrackDisplayUpdateRequests();
311 HTMLMediaElement& m_element;
316 struct HTMLMediaElement::TrackGroup {
317 enum GroupKind { CaptionsAndSubtitles, Description, Chapter, Metadata, Other };
319 TrackGroup(GroupKind kind)
324 Vector<RefPtr<TextTrack>> tracks;
325 RefPtr<TextTrack> visibleTrack;
326 RefPtr<TextTrack> defaultTrack;
328 bool hasSrcLang { false };
331 HashSet<HTMLMediaElement*>& HTMLMediaElement::allMediaElements()
333 static NeverDestroyed<HashSet<HTMLMediaElement*>> elements;
337 #if ENABLE(MEDIA_SESSION)
338 typedef HashMap<uint64_t, HTMLMediaElement*> IDToElementMap;
340 static IDToElementMap& elementIDsToElements()
342 static NeverDestroyed<IDToElementMap> map;
346 HTMLMediaElement* HTMLMediaElement::elementWithID(uint64_t id)
348 if (id == HTMLMediaElementInvalidID)
351 return elementIDsToElements().get(id);
354 static uint64_t nextElementID()
356 static uint64_t elementID = 0;
361 struct MediaElementSessionInfo {
362 const MediaElementSession* session;
363 MediaElementSession::PlaybackControlsPurpose purpose;
365 MonotonicTime timeOfLastUserInteraction;
366 bool canShowControlsManager : 1;
367 bool isVisibleInViewportOrFullscreen : 1;
368 bool isLargeEnoughForMainContent : 1;
369 bool isPlayingAudio : 1;
372 static MediaElementSessionInfo mediaElementSessionInfoForSession(const MediaElementSession& session, MediaElementSession::PlaybackControlsPurpose purpose)
374 const HTMLMediaElement& element = session.element();
378 session.mostRecentUserInteractionTime(),
379 session.canShowControlsManager(purpose),
380 element.isFullscreen() || element.isVisibleInViewport(),
381 session.isLargeEnoughForMainContent(MediaSessionMainContentPurpose::MediaControls),
382 element.isPlaying() && element.hasAudio() && !element.muted()
386 static bool preferMediaControlsForCandidateSessionOverOtherCandidateSession(const MediaElementSessionInfo& session, const MediaElementSessionInfo& otherSession)
388 MediaElementSession::PlaybackControlsPurpose purpose = session.purpose;
389 ASSERT(purpose == otherSession.purpose);
391 // For the controls manager, prioritize visible media over offscreen media.
392 if (purpose == MediaElementSession::PlaybackControlsPurpose::ControlsManager && session.isVisibleInViewportOrFullscreen != otherSession.isVisibleInViewportOrFullscreen)
393 return session.isVisibleInViewportOrFullscreen;
395 // For Now Playing, prioritize elements that would normally satisfy main content.
396 if (purpose == MediaElementSession::PlaybackControlsPurpose::NowPlaying && session.isLargeEnoughForMainContent != otherSession.isLargeEnoughForMainContent)
397 return session.isLargeEnoughForMainContent;
399 // As a tiebreaker, prioritize elements that the user recently interacted with.
400 return session.timeOfLastUserInteraction > otherSession.timeOfLastUserInteraction;
403 static bool mediaSessionMayBeConfusedWithMainContent(const MediaElementSessionInfo& session, MediaElementSession::PlaybackControlsPurpose purpose)
405 if (purpose == MediaElementSession::PlaybackControlsPurpose::NowPlaying)
406 return session.isPlayingAudio;
408 if (!session.isVisibleInViewportOrFullscreen)
411 if (!session.isLargeEnoughForMainContent)
414 // Even if this video is not a candidate, if it is visible to the user and large enough
415 // to be main content, it poses a risk for being confused with main content.
419 #if !RELEASE_LOG_DISABLED
420 static uint64_t nextLogIdentifier()
422 static uint64_t logIdentifier = cryptographicallyRandomNumber();
423 return ++logIdentifier;
427 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document, bool createdByParser)
428 : HTMLElement(tagName, document)
429 , ActiveDOMObject(&document)
430 , m_progressEventTimer(*this, &HTMLMediaElement::progressEventTimerFired)
431 , m_playbackProgressTimer(*this, &HTMLMediaElement::playbackProgressTimerFired)
432 , m_scanTimer(*this, &HTMLMediaElement::scanTimerFired)
433 , m_playbackControlsManagerBehaviorRestrictionsTimer(*this, &HTMLMediaElement::playbackControlsManagerBehaviorRestrictionsTimerFired)
434 , m_seekToPlaybackPositionEndedTimer(*this, &HTMLMediaElement::seekToPlaybackPositionEndedTimerFired)
435 , m_asyncEventQueue(*this)
436 , m_lastTimeUpdateEventMovieTime(MediaTime::positiveInfiniteTime())
437 , m_firstTimePlaying(true)
439 , m_isWaitingUntilMediaCanStart(false)
440 , m_shouldDelayLoadEvent(false)
441 , m_haveFiredLoadedData(false)
442 , m_inActiveDocument(true)
443 , m_autoplaying(true)
445 , m_explicitlyMuted(false)
446 , m_initiallyMuted(false)
449 , m_seekRequested(false)
450 , m_sentStalledEvent(false)
451 , m_sentEndEvent(false)
452 , m_pausedInternal(false)
453 , m_closedCaptionsVisible(false)
454 , m_webkitLegacyClosedCaptionOverride(false)
455 , m_completelyLoaded(false)
456 , m_havePreparedToPlay(false)
457 , m_parsingInProgress(createdByParser)
458 , m_shouldBufferData(true)
459 , m_elementIsHidden(document.hidden())
460 , m_creatingControls(false)
461 , m_receivedLayoutSizeChanged(false)
462 , m_hasEverNotifiedAboutPlaying(false)
463 , m_hasEverHadAudio(false)
464 , m_hasEverHadVideo(false)
465 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
466 , m_mediaControlsDependOnPageScaleFactor(false)
467 , m_haveSetUpCaptionContainer(false)
469 , m_isScrubbingRemotely(false)
470 #if ENABLE(VIDEO_TRACK)
471 , m_tracksAreReady(true)
472 , m_haveVisibleTextTrack(false)
473 , m_processingPreferenceChange(false)
475 #if !RELEASE_LOG_DISABLED
476 , m_logger(&document.logger())
477 , m_logIdentifier(nextLogIdentifier())
480 allMediaElements().add(this);
482 ALWAYS_LOG(LOGIDENTIFIER);
484 setHasCustomStyleResolveCallbacks();
486 InspectorInstrumentation::addEventListenersToNode(*this);
489 void HTMLMediaElement::finishInitialization()
491 m_mediaSession = std::make_unique<MediaElementSession>(*this);
493 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForFullscreen);
494 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePageConsentToLoadMedia);
495 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
496 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToAutoplayToExternalDevice);
498 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager);
499 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePlaybackToControlControlsManager);
501 auto& document = this->document();
502 auto* page = document.page();
504 if (document.settings().invisibleAutoplayNotPermitted())
505 m_mediaSession->addBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted);
507 if (document.ownerElement() || !document.isMediaDocument()) {
508 const auto& topDocument = document.topDocument();
509 const bool isProcessingUserGesture = processingUserGestureForMedia();
510 const bool shouldAudioPlaybackRequireUserGesture = topDocument.audioPlaybackRequiresUserGesture() && !isProcessingUserGesture;
511 const bool shouldVideoPlaybackRequireUserGesture = topDocument.videoPlaybackRequiresUserGesture() && !isProcessingUserGesture;
513 if (shouldVideoPlaybackRequireUserGesture) {
514 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForVideoRateChange);
515 if (document.settings().requiresUserGestureToLoadVideo())
516 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForLoad);
519 if (page && page->isLowPowerModeEnabled())
520 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForVideoDueToLowPowerMode);
522 if (shouldAudioPlaybackRequireUserGesture)
523 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForAudioRateChange);
525 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
526 if (shouldVideoPlaybackRequireUserGesture || shouldAudioPlaybackRequireUserGesture)
527 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToShowPlaybackTargetPicker);
530 if (!document.settings().mediaDataLoadsAutomatically())
531 m_mediaSession->addBehaviorRestriction(MediaElementSession::AutoPreloadingNotPermitted);
533 if (document.settings().mainContentUserGestureOverrideEnabled())
534 m_mediaSession->addBehaviorRestriction(MediaElementSession::OverrideUserGestureRequirementForMainContent);
537 #if PLATFORM(IOS_FAMILY)
538 if (!document.settings().videoPlaybackRequiresUserGesture() && !document.settings().audioPlaybackRequiresUserGesture()) {
539 // Relax RequireUserGestureForFullscreen when videoPlaybackRequiresUserGesture and audioPlaybackRequiresUserGesture is not set:
540 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequireUserGestureForFullscreen);
544 #if ENABLE(MEDIA_SESSION)
545 m_elementID = nextElementID();
546 elementIDsToElements().add(m_elementID, this);
548 setSessionInternal(document.defaultMediaSession());
551 registerWithDocument(document);
553 #if USE(AUDIO_SESSION) && PLATFORM(MAC)
554 AudioSession::sharedSession().addMutedStateObserver(this);
557 mediaSession().clientWillBeginAutoplaying();
560 // FIXME: Remove this code once https://webkit.org/b/185284 is fixed.
561 static unsigned s_destructorCount = 0;
563 bool HTMLMediaElement::isRunningDestructor()
565 return !!s_destructorCount;
568 class HTMLMediaElementDestructorScope {
570 HTMLMediaElementDestructorScope() { ++s_destructorCount; }
571 ~HTMLMediaElementDestructorScope() { --s_destructorCount; }
574 HTMLMediaElement::~HTMLMediaElement()
576 HTMLMediaElementDestructorScope destructorScope;
577 ALWAYS_LOG(LOGIDENTIFIER);
579 beginIgnoringTrackDisplayUpdateRequests();
580 allMediaElements().remove(this);
582 m_asyncEventQueue.close();
584 setShouldDelayLoadEvent(false);
585 unregisterWithDocument(document());
587 #if USE(AUDIO_SESSION) && PLATFORM(MAC)
588 AudioSession::sharedSession().removeMutedStateObserver(this);
591 #if ENABLE(VIDEO_TRACK)
593 m_audioTracks->clearElement();
595 m_textTracks->clearElement();
597 m_videoTracks->clearElement();
600 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
601 if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) {
602 m_hasPlaybackTargetAvailabilityListeners = false;
603 m_mediaSession->setHasPlaybackTargetAvailabilityListeners(false);
608 if (m_mediaController) {
609 m_mediaController->removeMediaElement(*this);
610 m_mediaController = nullptr;
613 #if ENABLE(MEDIA_SOURCE)
617 #if ENABLE(LEGACY_ENCRYPTED_MEDIA)
618 webkitSetMediaKeys(nullptr);
621 #if ENABLE(ENCRYPTED_MEDIA)
623 m_mediaKeys->detachCDMClient(*this);
625 m_player->cdmInstanceDetached(m_mediaKeys->cdmInstance());
629 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
631 m_isolatedWorld->clearWrappers();
634 #if ENABLE(MEDIA_SESSION)
636 m_session->removeMediaElement(*this);
640 elementIDsToElements().remove(m_elementID);
643 m_seekTaskQueue.close();
644 m_resumeTaskQueue.close();
645 m_promiseTaskQueue.close();
646 m_pauseAfterDetachedTaskQueue.close();
647 m_playbackControlsManagerBehaviorRestrictionsQueue.close();
648 m_resourceSelectionTaskQueue.close();
649 m_visibilityChangeTaskQueue.close();
650 #if ENABLE(ENCRYPTED_MEDIA)
651 m_encryptedMediaQueue.close();
654 m_completelyLoaded = true;
657 m_player->invalidate();
661 m_mediaSession = nullptr;
662 schedulePlaybackControlsManagerUpdate();
665 static bool needsAutoplayPlayPauseEventsQuirk(const Document& document)
667 auto* page = document.page();
668 if (!page || !page->settings().needsSiteSpecificQuirks())
671 auto loader = makeRefPtr(document.loader());
672 return loader && loader->allowedAutoplayQuirks().contains(AutoplayQuirk::SynthesizedPauseEvents);
675 RefPtr<HTMLMediaElement> HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose purpose)
677 auto allSessions = PlatformMediaSessionManager::sharedManager().currentSessionsMatching([] (const PlatformMediaSession& session) {
678 return is<MediaElementSession>(session);
681 Vector<MediaElementSessionInfo> candidateSessions;
682 bool atLeastOneNonCandidateMayBeConfusedForMainContent = false;
683 for (auto& session : allSessions) {
684 auto mediaElementSessionInfo = mediaElementSessionInfoForSession(downcast<MediaElementSession>(*session), purpose);
685 if (mediaElementSessionInfo.canShowControlsManager)
686 candidateSessions.append(mediaElementSessionInfo);
687 else if (mediaSessionMayBeConfusedWithMainContent(mediaElementSessionInfo, purpose))
688 atLeastOneNonCandidateMayBeConfusedForMainContent = true;
691 if (!candidateSessions.size())
694 std::sort(candidateSessions.begin(), candidateSessions.end(), preferMediaControlsForCandidateSessionOverOtherCandidateSession);
695 auto strongestSessionCandidate = candidateSessions.first();
696 if (!strongestSessionCandidate.isVisibleInViewportOrFullscreen && !strongestSessionCandidate.isPlayingAudio && atLeastOneNonCandidateMayBeConfusedForMainContent)
699 return &strongestSessionCandidate.session->element();
702 void HTMLMediaElement::registerWithDocument(Document& document)
704 m_mediaSession->registerWithDocument(document);
706 if (m_isWaitingUntilMediaCanStart)
707 document.addMediaCanStartListener(*this);
709 #if !PLATFORM(IOS_FAMILY)
710 document.registerForMediaVolumeCallbacks(*this);
711 document.registerForPrivateBrowsingStateChangedCallbacks(*this);
714 document.registerForVisibilityStateChangedCallbacks(*this);
716 #if ENABLE(VIDEO_TRACK)
717 if (m_requireCaptionPreferencesChangedCallbacks)
718 document.registerForCaptionPreferencesChangedCallbacks(*this);
721 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
722 if (m_mediaControlsDependOnPageScaleFactor)
723 document.registerForPageScaleFactorChangedCallbacks(*this);
724 document.registerForUserInterfaceLayoutDirectionChangedCallbacks(*this);
727 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
728 document.registerForDocumentSuspensionCallbacks(*this);
731 document.registerForAllowsMediaDocumentInlinePlaybackChangedCallbacks(*this);
733 document.addAudioProducer(*this);
734 addElementToDocumentMap(*this, document);
736 #if ENABLE(MEDIA_STREAM)
737 document.registerForMediaStreamStateChangeCallbacks(*this);
740 document.addApplicationStateChangeListener(*this);
743 void HTMLMediaElement::unregisterWithDocument(Document& document)
745 m_mediaSession->unregisterWithDocument(document);
747 if (m_isWaitingUntilMediaCanStart)
748 document.removeMediaCanStartListener(*this);
750 #if !PLATFORM(IOS_FAMILY)
751 document.unregisterForMediaVolumeCallbacks(*this);
752 document.unregisterForPrivateBrowsingStateChangedCallbacks(*this);
755 document.unregisterForVisibilityStateChangedCallbacks(*this);
757 #if ENABLE(VIDEO_TRACK)
758 if (m_requireCaptionPreferencesChangedCallbacks)
759 document.unregisterForCaptionPreferencesChangedCallbacks(*this);
762 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
763 if (m_mediaControlsDependOnPageScaleFactor)
764 document.unregisterForPageScaleFactorChangedCallbacks(*this);
765 document.unregisterForUserInterfaceLayoutDirectionChangedCallbacks(*this);
768 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
769 document.unregisterForDocumentSuspensionCallbacks(*this);
772 document.unregisterForAllowsMediaDocumentInlinePlaybackChangedCallbacks(*this);
774 document.removeAudioProducer(*this);
775 removeElementFromDocumentMap(*this, document);
777 #if ENABLE(MEDIA_STREAM)
778 document.unregisterForMediaStreamStateChangeCallbacks(*this);
781 document.removeApplicationStateChangeListener(*this);
784 void HTMLMediaElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
786 ASSERT_WITH_SECURITY_IMPLICATION(&document() == &newDocument);
787 if (m_shouldDelayLoadEvent) {
788 oldDocument.decrementLoadEventDelayCount();
789 newDocument.incrementLoadEventDelayCount();
792 unregisterWithDocument(oldDocument);
793 registerWithDocument(newDocument);
795 HTMLElement::didMoveToNewDocument(oldDocument, newDocument);
796 updateShouldAutoplay();
799 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
800 void HTMLMediaElement::prepareForDocumentSuspension()
802 m_mediaSession->unregisterWithDocument(document());
805 void HTMLMediaElement::resumeFromDocumentSuspension()
807 m_mediaSession->registerWithDocument(document());
808 updateShouldAutoplay();
812 bool HTMLMediaElement::supportsFocus() const
814 if (document().isMediaDocument())
817 // If no controls specified, we should still be able to focus the element if it has tabIndex.
818 return controls() || HTMLElement::supportsFocus();
821 bool HTMLMediaElement::isMouseFocusable() const
826 void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
828 if (name == srcAttr) {
829 // https://html.spec.whatwg.org/multipage/embedded-content.html#location-of-the-media-resource
830 // Location of the Media Resource
833 // If a src attribute of a media element is set or changed, the user
834 // agent must invoke the media element's media element load algorithm.
837 } else if (name == controlsAttr)
838 configureMediaControls();
839 else if (name == loopAttr)
840 updateSleepDisabling();
841 else if (name == preloadAttr) {
842 if (equalLettersIgnoringASCIICase(value, "none"))
843 m_preload = MediaPlayer::None;
844 else if (equalLettersIgnoringASCIICase(value, "metadata"))
845 m_preload = MediaPlayer::MetaData;
847 // The spec does not define an "invalid value default" but "auto" is suggested as the
848 // "missing value default", so use it for everything except "none" and "metadata"
849 m_preload = MediaPlayer::Auto;
852 // The attribute must be ignored if the autoplay attribute is present
853 if (!autoplay() && !m_havePreparedToPlay && m_player)
854 m_player->setPreload(m_mediaSession->effectivePreloadForElement());
856 } else if (name == mediagroupAttr)
857 setMediaGroup(value);
858 else if (name == autoplayAttr) {
859 if (processingUserGestureForMedia())
860 removeBehaviorsRestrictionsAfterFirstUserGesture();
861 } else if (name == titleAttr) {
863 m_mediaSession->clientCharacteristicsChanged();
866 HTMLElement::parseAttribute(name, value);
869 void HTMLMediaElement::finishParsingChildren()
871 HTMLElement::finishParsingChildren();
872 m_parsingInProgress = false;
874 #if ENABLE(VIDEO_TRACK)
875 if (childrenOfType<HTMLTrackElement>(*this).first())
876 scheduleConfigureTextTracks();
880 bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style)
882 return controls() && HTMLElement::rendererIsNeeded(style);
885 RenderPtr<RenderElement> HTMLMediaElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
887 return createRenderer<RenderMedia>(*this, WTFMove(style));
890 bool HTMLMediaElement::childShouldCreateRenderer(const Node& child) const
892 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
893 return hasShadowRootParent(child) && HTMLElement::childShouldCreateRenderer(child);
895 if (!hasMediaControls())
897 // <media> doesn't allow its content, including shadow subtree, to
898 // be rendered. So this should return false for most of the children.
899 // One exception is a shadow tree built for rendering controls which should be visible.
900 // So we let them go here by comparing its subtree root with one of the controls.
901 return &mediaControls()->treeScope() == &child.treeScope()
902 && hasShadowRootParent(child)
903 && HTMLElement::childShouldCreateRenderer(child);
907 Node::InsertedIntoAncestorResult HTMLMediaElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
909 INFO_LOG(LOGIDENTIFIER);
911 HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
912 if (insertionType.connectedToDocument)
913 setInActiveDocument(true);
915 return InsertedIntoAncestorResult::NeedsPostInsertionCallback;
918 void HTMLMediaElement::didFinishInsertingNode()
920 Ref<HTMLMediaElement> protectedThis(*this); // prepareForLoad may result in a 'beforeload' event, which can make arbitrary DOM mutations.
922 INFO_LOG(LOGIDENTIFIER);
924 if (m_inActiveDocument && m_networkState == NETWORK_EMPTY && !attributeWithoutSynchronization(srcAttr).isEmpty())
927 if (!m_explicitlyMuted) {
928 m_explicitlyMuted = true;
929 m_muted = hasAttributeWithoutSynchronization(mutedAttr);
930 m_mediaSession->canProduceAudioChanged();
933 configureMediaControls();
936 void HTMLMediaElement::pauseAfterDetachedTask()
938 // If we were re-inserted into an active document, no need to pause.
939 if (m_inActiveDocument)
942 if (hasMediaControls())
943 mediaControls()->hide();
944 if (m_networkState > NETWORK_EMPTY)
946 if (m_videoFullscreenMode != VideoFullscreenModeNone)
952 size_t extraMemoryCost = m_player->extraMemoryCost();
953 if (extraMemoryCost > m_reportedExtraMemoryCost) {
954 JSC::VM& vm = commonVM();
955 JSC::JSLockHolder lock(vm);
957 size_t extraMemoryCostDelta = extraMemoryCost - m_reportedExtraMemoryCost;
958 m_reportedExtraMemoryCost = extraMemoryCost;
959 // FIXME: Adopt reportExtraMemoryVisited, and switch to reportExtraMemoryAllocated.
960 // https://bugs.webkit.org/show_bug.cgi?id=142595
961 vm.heap.deprecatedReportExtraMemory(extraMemoryCostDelta);
965 void HTMLMediaElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
967 INFO_LOG(LOGIDENTIFIER);
969 setInActiveDocument(false);
970 if (removalType.disconnectedFromDocument) {
971 // Pause asynchronously to let the operation that removed us finish, in case we get inserted back into a document.
972 m_pauseAfterDetachedTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::pauseAfterDetachedTask, this));
976 m_mediaSession->clientCharacteristicsChanged();
978 HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
981 void HTMLMediaElement::willAttachRenderers()
986 inline void HTMLMediaElement::updateRenderer()
988 if (auto* renderer = this->renderer())
989 renderer->updateFromElement();
992 void HTMLMediaElement::didAttachRenderers()
994 if (auto* renderer = this->renderer()) {
995 renderer->updateFromElement();
996 if (m_mediaSession && m_mediaSession->wantsToObserveViewportVisibilityForAutoplay())
997 renderer->registerForVisibleInViewportCallback();
999 updateShouldAutoplay();
1002 void HTMLMediaElement::willDetachRenderers()
1004 if (auto* renderer = this->renderer())
1005 renderer->unregisterForVisibleInViewportCallback();
1008 void HTMLMediaElement::didDetachRenderers()
1010 updateShouldAutoplay();
1013 void HTMLMediaElement::didRecalcStyle(Style::Change)
1018 void HTMLMediaElement::scheduleNextSourceChild()
1020 // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
1021 m_resourceSelectionTaskQueue.enqueueTask([this] {
1022 loadNextSourceChild();
1026 void HTMLMediaElement::mediaPlayerActiveSourceBuffersChanged(const MediaPlayer*)
1028 m_hasEverHadAudio |= hasAudio();
1029 m_hasEverHadVideo |= hasVideo();
1032 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
1034 auto event = Event::create(eventName, Event::CanBubble::No, Event::IsCancelable::Yes);
1036 // Don't set the event target, the event queue will set it in GenericEventQueue::timerFired and setting it here
1037 // will trigger an ASSERT if this element has been marked for deletion.
1039 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1042 void HTMLMediaElement::scheduleResolvePendingPlayPromises()
1044 m_promiseTaskQueue.enqueueTask([this, pendingPlayPromises = WTFMove(m_pendingPlayPromises)] () mutable {
1045 resolvePendingPlayPromises(WTFMove(pendingPlayPromises));
1049 void HTMLMediaElement::scheduleRejectPendingPlayPromises(Ref<DOMException>&& error)
1051 m_promiseTaskQueue.enqueueTask([this, error = WTFMove(error), pendingPlayPromises = WTFMove(m_pendingPlayPromises)] () mutable {
1052 rejectPendingPlayPromises(WTFMove(pendingPlayPromises), WTFMove(error));
1056 void HTMLMediaElement::rejectPendingPlayPromises(PlayPromiseVector&& pendingPlayPromises, Ref<DOMException>&& error)
1058 for (auto& promise : pendingPlayPromises)
1059 promise.rejectType<IDLInterface<DOMException>>(error);
1062 void HTMLMediaElement::resolvePendingPlayPromises(PlayPromiseVector&& pendingPlayPromises)
1064 for (auto& promise : pendingPlayPromises)
1068 void HTMLMediaElement::scheduleNotifyAboutPlaying()
1070 m_promiseTaskQueue.enqueueTask([this, pendingPlayPromises = WTFMove(m_pendingPlayPromises)] () mutable {
1071 notifyAboutPlaying(WTFMove(pendingPlayPromises));
1075 void HTMLMediaElement::notifyAboutPlaying(PlayPromiseVector&& pendingPlayPromises)
1077 Ref<HTMLMediaElement> protectedThis(*this); // The 'playing' event can make arbitrary DOM mutations.
1078 m_playbackStartedTime = currentMediaTime().toDouble();
1079 m_hasEverNotifiedAboutPlaying = true;
1080 dispatchEvent(Event::create(eventNames().playingEvent, Event::CanBubble::No, Event::IsCancelable::Yes));
1081 resolvePendingPlayPromises(WTFMove(pendingPlayPromises));
1083 schedulePlaybackControlsManagerUpdate();
1086 bool HTMLMediaElement::hasEverNotifiedAboutPlaying() const
1088 return m_hasEverNotifiedAboutPlaying;
1091 void HTMLMediaElement::scheduleCheckPlaybackTargetCompatability()
1093 if (m_checkPlaybackTargetCompatablityTask.hasPendingTask())
1096 auto logSiteIdentifier = LOGIDENTIFIER;
1097 ALWAYS_LOG(logSiteIdentifier, "task scheduled");
1098 m_checkPlaybackTargetCompatablityTask.scheduleTask([this, logSiteIdentifier] {
1099 UNUSED_PARAM(logSiteIdentifier);
1100 ALWAYS_LOG(logSiteIdentifier, "- lambda(), task fired");
1101 checkPlaybackTargetCompatablity();
1105 void HTMLMediaElement::checkPlaybackTargetCompatablity()
1107 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
1108 if (m_isPlayingToWirelessTarget && !m_player->canPlayToWirelessPlaybackTarget()) {
1109 INFO_LOG(LOGIDENTIFIER, "calling setShouldPlayToPlaybackTarget(false)");
1110 m_failedToPlayToWirelessTarget = true;
1111 m_player->setShouldPlayToPlaybackTarget(false);
1116 MediaError* HTMLMediaElement::error() const
1118 return m_error.get();
1121 void HTMLMediaElement::setSrcObject(MediaProvider&& mediaProvider)
1123 // FIXME: Setting the srcObject attribute may cause other changes to the media element's internal state:
1124 // Specifically, if srcObject is specified, the UA must use it as the source of media, even if the src
1125 // attribute is also set or children are present. If the value of srcObject is replaced or set to null
1126 // the UA must re-run the media element load algorithm.
1128 // https://bugs.webkit.org/show_bug.cgi?id=124896
1131 // https://www.w3.org/TR/html51/semantics-embedded-content.html#dom-htmlmediaelement-srcobject
1132 // 4.7.14.2. Location of the media resource
1133 // srcObject: On setting, it must set the element’s assigned media provider object to the new
1134 // value, and then invoke the element’s media element load algorithm.
1135 INFO_LOG(LOGIDENTIFIER);
1136 m_mediaProvider = WTFMove(mediaProvider);
1140 void HTMLMediaElement::setCrossOrigin(const AtomicString& value)
1142 setAttributeWithoutSynchronization(crossoriginAttr, value);
1145 String HTMLMediaElement::crossOrigin() const
1147 return parseCORSSettingsAttribute(attributeWithoutSynchronization(crossoriginAttr));
1150 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
1152 return m_networkState;
1155 String HTMLMediaElement::canPlayType(const String& mimeType) const
1157 MediaEngineSupportParameters parameters;
1158 ContentType contentType(mimeType);
1159 parameters.type = contentType;
1160 parameters.contentTypesRequiringHardwareSupport = mediaContentTypesRequiringHardwareSupport();
1161 MediaPlayer::SupportsType support = MediaPlayer::supportsType(parameters);
1167 case MediaPlayer::IsNotSupported:
1168 canPlay = emptyString();
1170 case MediaPlayer::MayBeSupported:
1171 canPlay = "maybe"_s;
1173 case MediaPlayer::IsSupported:
1174 canPlay = "probably"_s;
1178 DEBUG_LOG(LOGIDENTIFIER, "[", mimeType, "] -> ", canPlay);
1183 double HTMLMediaElement::getStartDate() const
1186 return std::numeric_limits<double>::quiet_NaN();
1187 return m_player->getStartDate().toDouble();
1190 void HTMLMediaElement::load()
1192 Ref<HTMLMediaElement> protectedThis(*this); // prepareForLoad may result in a 'beforeload' event, which can make arbitrary DOM mutations.
1194 INFO_LOG(LOGIDENTIFIER);
1196 if (processingUserGestureForMedia())
1197 removeBehaviorsRestrictionsAfterFirstUserGesture();
1200 m_resourceSelectionTaskQueue.enqueueTask([this] {
1205 void HTMLMediaElement::prepareForLoad()
1207 // https://html.spec.whatwg.org/multipage/embedded-content.html#media-element-load-algorithm
1208 // The Media Element Load Algorithm
1211 INFO_LOG(LOGIDENTIFIER);
1213 // 1 - Abort any already-running instance of the resource selection algorithm for this element.
1214 // Perform the cleanup required for the resource load algorithm to run.
1215 stopPeriodicTimers();
1216 m_resourceSelectionTaskQueue.cancelAllTasks();
1217 // FIXME: Figure out appropriate place to reset LoadTextTrackResource if necessary and set m_pendingActionFlags to 0 here.
1218 m_sentEndEvent = false;
1219 m_sentStalledEvent = false;
1220 m_haveFiredLoadedData = false;
1221 m_completelyLoaded = false;
1222 m_havePreparedToPlay = false;
1223 m_displayMode = Unknown;
1224 m_currentSrc = URL();
1226 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
1227 m_failedToPlayToWirelessTarget = false;
1230 m_loadState = WaitingForSource;
1231 m_currentSourceNode = nullptr;
1233 if (!document().hasBrowsingContext())
1236 createMediaPlayer();
1238 // 2 - Let pending tasks be a list of all tasks from the media element's media element event task source in one of the task queues.
1239 // 3 - For each task in pending tasks that would resolve pending play promises or reject pending play promises, immediately resolve or reject those promises in the order the corresponding tasks were queued.
1240 // 4 - Remove each task in pending tasks from its task queue
1241 cancelPendingEventsAndCallbacks();
1243 // 5 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
1244 // a task to fire a simple event named abort at the media element.
1245 if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
1246 scheduleEvent(eventNames().abortEvent);
1248 // 6 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
1249 if (m_networkState != NETWORK_EMPTY) {
1250 // 6.1 - Queue a task to fire a simple event named emptied at the media element.
1251 scheduleEvent(eventNames().emptiedEvent);
1253 // 6.2 - If a fetching process is in progress for the media element, the user agent should stop it.
1254 m_networkState = NETWORK_EMPTY;
1256 // 6.3 - If the media element’s assigned media provider object is a MediaSource object, then detach it.
1257 #if ENABLE(MEDIA_SOURCE)
1258 detachMediaSource();
1261 // 6.4 - Forget the media element's media-resource-specific tracks.
1262 forgetResourceSpecificTracks();
1264 // 6.5 - If readyState is not set to HAVE_NOTHING, then set it to that state.
1265 m_readyState = HAVE_NOTHING;
1266 m_readyStateMaximum = HAVE_NOTHING;
1268 // 6.6 - If the paused attribute is false, then set it to true.
1271 // 6.7 - If seeking is true, set it to false.
1274 // 6.8 - Set the current playback position to 0.
1275 // Set the official playback position to 0.
1276 // If this changed the official playback position, then queue a task to fire a simple event named timeupdate at the media element.
1277 m_lastSeekTime = MediaTime::zeroTime();
1278 m_playedTimeRanges = TimeRanges::create();
1279 // FIXME: Add support for firing this event. e.g., scheduleEvent(eventNames().timeUpdateEvent);
1281 // 4.9 - Set the initial playback position to 0.
1282 // FIXME: Make this less subtle. The position only becomes 0 because of the createMediaPlayer() call
1284 refreshCachedTime();
1286 invalidateCachedTime();
1288 // 4.10 - Set the timeline offset to Not-a-Number (NaN).
1289 // 4.11 - Update the duration attribute to Not-a-Number (NaN).
1291 updateMediaController();
1292 #if ENABLE(VIDEO_TRACK)
1293 updateActiveTextTrackCues(MediaTime::zeroTime());
1297 // 7 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
1298 setPlaybackRate(defaultPlaybackRate());
1300 // 8 - Set the error attribute to null and the autoplaying flag to true.
1302 m_autoplaying = true;
1303 mediaSession().clientWillBeginAutoplaying();
1305 if (!MediaPlayer::isAvailable())
1308 // 9 - Invoke the media element's resource selection algorithm.
1309 // Note, unless the restriction on requiring user action has been removed,
1310 // do not begin downloading data.
1311 if (m_mediaSession->dataLoadingPermitted())
1312 selectMediaResource();
1315 // 10 - Note: Playback of any previously playing media resource for this element stops.
1317 configureMediaControls();
1320 void HTMLMediaElement::selectMediaResource()
1322 // https://www.w3.org/TR/2016/REC-html51-20161101/semantics-embedded-content.html#resource-selection-algorithm
1323 // The Resource Selection Algorithm
1325 // 1. Set the element’s networkState attribute to the NETWORK_NO_SOURCE value.
1326 m_networkState = NETWORK_NO_SOURCE;
1328 // 2. Set the element’s show poster flag to true.
1329 setDisplayMode(Poster);
1331 // 3. Set the media element’s delaying-the-load-event flag to true (this delays the load event).
1332 setShouldDelayLoadEvent(true);
1334 // 4. in parallel await a stable state, allowing the task that invoked this algorithm to continue.
1335 if (m_resourceSelectionTaskQueue.hasPendingTasks())
1338 if (!m_mediaSession->pageAllowsDataLoading()) {
1339 ALWAYS_LOG(LOGIDENTIFIER, "not allowed to load in background, waiting");
1340 setShouldDelayLoadEvent(false);
1341 if (m_isWaitingUntilMediaCanStart)
1343 m_isWaitingUntilMediaCanStart = true;
1344 document().addMediaCanStartListener(*this);
1348 // Once the page has allowed an element to load media, it is free to load at will. This allows a
1349 // playlist that starts in a foreground tab to continue automatically if the tab is subsequently
1350 // put into the background.
1351 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequirePageConsentToLoadMedia);
1353 m_resourceSelectionTaskQueue.enqueueTask([this] {
1354 // 5. If the media element’s blocked-on-parser flag is false, then populate the list of pending text tracks.
1355 #if ENABLE(VIDEO_TRACK)
1356 if (hasMediaControls())
1357 mediaControls()->changedClosedCaptionsVisibility();
1359 // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the
1360 // disabled state when the element's resource selection algorithm last started".
1361 // FIXME: Update this to match "populate the list of pending text tracks" step.
1362 m_textTracksWhenResourceSelectionBegan.clear();
1364 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
1365 RefPtr<TextTrack> track = m_textTracks->item(i);
1366 if (track->mode() != TextTrack::Mode::Disabled)
1367 m_textTracksWhenResourceSelectionBegan.append(track);
1372 enum Mode { None, Object, Attribute, Children };
1375 if (m_mediaProvider) {
1376 // 6. If the media element has an assigned media provider object, then let mode be object.
1378 } else if (hasAttributeWithoutSynchronization(srcAttr)) {
1379 // Otherwise, if the media element has no assigned media provider object but has a src attribute, then let mode be attribute.
1383 ERROR_LOG(LOGIDENTIFIER, " - has srcAttr but m_player is not created");
1386 } else if (auto firstSource = childrenOfType<HTMLSourceElement>(*this).first()) {
1387 // Otherwise, if the media element does not have an assigned media provider object and does not have a src attribute,
1388 // but does have a source element child, then let mode be children and let candidate be the first such source element
1389 // child in tree order.
1391 m_nextChildNodeToConsider = firstSource;
1392 m_currentSourceNode = nullptr;
1394 // Otherwise the media element has no assigned media provider object and has neither a src attribute nor a source
1395 // element child: set the networkState to NETWORK_EMPTY, and abort these steps; the synchronous section ends.
1396 m_loadState = WaitingForSource;
1397 setShouldDelayLoadEvent(false);
1398 m_networkState = NETWORK_EMPTY;
1400 ALWAYS_LOG(LOGIDENTIFIER, "nothing to load");
1404 // 7. Set the media element’s networkState to NETWORK_LOADING.
1405 m_networkState = NETWORK_LOADING;
1407 // 8. Queue a task to fire a simple event named loadstart at the media element.
1408 scheduleEvent(eventNames().loadstartEvent);
1410 // 9. Run the appropriate steps from the following list:
1411 // ↳ If mode is object
1412 if (mode == Object) {
1413 // 1. Set the currentSrc attribute to the empty string.
1414 m_currentSrc = URL();
1416 // 2. End the synchronous section, continuing the remaining steps in parallel.
1417 // 3. Run the resource fetch algorithm with the assigned media provider object.
1418 switchOn(m_mediaProvider.value(),
1419 #if ENABLE(MEDIA_STREAM)
1420 [this](RefPtr<MediaStream> stream) { m_mediaStreamSrcObject = stream; },
1422 #if ENABLE(MEDIA_SOURCE)
1423 [this](RefPtr<MediaSource> source) { m_mediaSource = source; },
1425 [this](RefPtr<Blob> blob) { m_blob = blob; }
1428 ContentType contentType;
1429 loadResource(URL(), contentType, String());
1430 ALWAYS_LOG(LOGIDENTIFIER, "using 'srcObject' property");
1432 // If that algorithm returns without aborting this one, then the load failed.
1433 // 4. Failed with media provider: Reaching this step indicates that the media resource
1434 // failed to load. Queue a task to run the dedicated media source failure steps.
1435 // 5. Wait for the task queued by the previous step to have executed.
1436 // 6. Abort these steps. The element won’t attempt to load another resource until this
1437 // algorithm is triggered again.
1441 // ↳ If mode is attribute
1442 if (mode == Attribute) {
1443 m_loadState = LoadingFromSrcAttr;
1445 // 1. If the src attribute’s value is the empty string, then end the synchronous section,
1446 // and jump down to the failed with attribute step below.
1447 // 2. Let absolute URL be the absolute URL that would have resulted from parsing the URL
1448 // specified by the src attribute’s value relative to the media element when the src
1449 // attribute was last changed.
1450 URL absoluteURL = getNonEmptyURLAttribute(srcAttr);
1451 if (absoluteURL.isEmpty()) {
1452 mediaLoadingFailed(MediaPlayer::FormatError);
1453 ALWAYS_LOG(LOGIDENTIFIER, "empty 'src'");
1457 if (!isSafeToLoadURL(absoluteURL, Complain) || !dispatchBeforeLoadEvent(absoluteURL.string())) {
1458 mediaLoadingFailed(MediaPlayer::FormatError);
1462 // 3. If absolute URL was obtained successfully, set the currentSrc attribute to absolute URL.
1463 m_currentSrc = absoluteURL;
1465 // 4. End the synchronous section, continuing the remaining steps in parallel.
1466 // 5. If absolute URL was obtained successfully, run the resource fetch algorithm with absolute
1467 // URL. If that algorithm returns without aborting this one, then the load failed.
1469 // No type or key system information is available when the url comes
1470 // from the 'src' attribute so MediaPlayer
1471 // will have to pick a media engine based on the file extension.
1472 ContentType contentType;
1473 loadResource(absoluteURL, contentType, String());
1474 ALWAYS_LOG(LOGIDENTIFIER, "using 'src' attribute url");
1476 // 6. Failed with attribute: Reaching this step indicates that the media resource failed to load
1477 // or that the given URL could not be resolved. Queue a task to run the dedicated media source failure steps.
1478 // 7. Wait for the task queued by the previous step to have executed.
1479 // 8. Abort these steps. The element won’t attempt to load another resource until this algorithm is triggered again.
1483 // ↳ Otherwise (mode is children)
1484 // (Ctd. in loadNextSourceChild())
1485 loadNextSourceChild();
1489 void HTMLMediaElement::loadNextSourceChild()
1491 ContentType contentType;
1493 URL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain);
1494 if (!mediaURL.isValid()) {
1495 waitForSourceChange();
1499 // Recreate the media player for the new url
1500 createMediaPlayer();
1502 m_loadState = LoadingFromSourceElement;
1503 loadResource(mediaURL, contentType, keySystem);
1506 void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentType, const String& keySystem)
1508 ASSERT(initialURL.isEmpty() || isSafeToLoadURL(initialURL, Complain));
1510 INFO_LOG(LOGIDENTIFIER, initialURL, contentType.raw(), keySystem);
1512 RefPtr<Frame> frame = document().frame();
1514 mediaLoadingFailed(MediaPlayer::FormatError);
1518 Page* page = frame->page();
1520 mediaLoadingFailed(MediaPlayer::FormatError);
1524 URL url = initialURL;
1525 if (!url.isEmpty() && !frame->loader().willLoadMediaElementURL(url, *this)) {
1526 mediaLoadingFailed(MediaPlayer::FormatError);
1530 #if ENABLE(CONTENT_EXTENSIONS)
1531 if (auto documentLoader = makeRefPtr(frame->loader().documentLoader())) {
1532 if (page->userContentProvider().processContentExtensionRulesForLoad(url, ResourceType::Media, *documentLoader).blockedLoad) {
1533 mediaLoadingFailed(MediaPlayer::FormatError);
1539 // The resource fetch algorithm
1540 m_networkState = NETWORK_LOADING;
1542 // If the URL should be loaded from the application cache, pass the URL of the cached file to the media engine.
1543 ApplicationCacheResource* resource = nullptr;
1544 if (!url.isEmpty() && frame->loader().documentLoader()->applicationCacheHost().shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource)) {
1545 // Resources that are not present in the manifest will always fail to load (at least, after the
1546 // cache has been primed the first time), making the testing of offline applications simpler.
1547 if (!resource || resource->path().isEmpty()) {
1548 mediaLoadingFailed(MediaPlayer::NetworkError);
1553 // Log that we started loading a media element.
1554 page->diagnosticLoggingClient().logDiagnosticMessage(isVideo() ? DiagnosticLoggingKeys::videoKey() : DiagnosticLoggingKeys::audioKey(), DiagnosticLoggingKeys::loadingKey(), ShouldSample::No);
1556 m_firstTimePlaying = true;
1558 // Set m_currentSrc *before* changing to the cache URL, the fact that we are loading from the app
1559 // cache is an internal detail not exposed through the media element API.
1563 url = ApplicationCacheHost::createFileURL(resource->path());
1564 INFO_LOG(LOGIDENTIFIER, "will load ", url, " from app cache");
1567 INFO_LOG(LOGIDENTIFIER, "m_currentSrc is ", m_currentSrc);
1569 startProgressEventTimer();
1571 bool privateMode = document().page() && document().page()->usesEphemeralSession();
1572 m_player->setPrivateBrowsingMode(privateMode);
1574 // Reset display mode to force a recalculation of what to show because we are resetting the player.
1575 setDisplayMode(Unknown);
1577 if (!autoplay() && !m_havePreparedToPlay)
1578 m_player->setPreload(m_mediaSession->effectivePreloadForElement());
1579 m_player->setPreservesPitch(m_webkitPreservesPitch);
1581 if (!m_explicitlyMuted) {
1582 m_explicitlyMuted = true;
1583 m_muted = hasAttributeWithoutSynchronization(mutedAttr);
1584 m_mediaSession->canProduceAudioChanged();
1589 bool loadAttempted = false;
1590 #if ENABLE(MEDIA_SOURCE)
1591 if (!m_mediaSource && url.protocolIs(mediaSourceBlobProtocol))
1592 m_mediaSource = MediaSource::lookup(url.string());
1594 if (m_mediaSource) {
1595 loadAttempted = true;
1596 if (!m_mediaSource->attachToElement(*this) || !m_player->load(url, contentType, m_mediaSource.get())) {
1597 // Forget our reference to the MediaSource, so we leave it alone
1598 // while processing remainder of load failure.
1599 m_mediaSource = nullptr;
1600 mediaLoadingFailed(MediaPlayer::FormatError);
1605 #if ENABLE(MEDIA_STREAM)
1606 if (!loadAttempted) {
1607 if (!m_mediaStreamSrcObject && url.protocolIs(mediaStreamBlobProtocol))
1608 m_mediaStreamSrcObject = MediaStreamRegistry::shared().lookUp(url);
1610 if (m_mediaStreamSrcObject) {
1611 loadAttempted = true;
1612 if (!m_player->load(m_mediaStreamSrcObject->privateStream()))
1613 mediaLoadingFailed(MediaPlayer::FormatError);
1618 if (!loadAttempted && m_blob) {
1619 loadAttempted = true;
1620 if (!m_player->load(m_blob->url(), contentType, keySystem))
1621 mediaLoadingFailed(MediaPlayer::FormatError);
1624 if (!loadAttempted && !m_player->load(url, contentType, keySystem))
1625 mediaLoadingFailed(MediaPlayer::FormatError);
1627 // If there is no poster to display, allow the media engine to render video frames as soon as
1628 // they are available.
1629 updateDisplayState();
1634 #if ENABLE(VIDEO_TRACK)
1636 static bool trackIndexCompare(TextTrack* a, TextTrack* b)
1638 return a->trackIndex() - b->trackIndex() < 0;
1641 static bool eventTimeCueCompare(const std::pair<MediaTime, TextTrackCue*>& a, const std::pair<MediaTime, TextTrackCue*>& b)
1643 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1645 if (a.first != b.first)
1646 return a.first - b.first < MediaTime::zeroTime();
1648 // If the cues belong to different text tracks, it doesn't make sense to
1649 // compare the two tracks by the relative cue order, so return the relative
1651 if (a.second->track() != b.second->track())
1652 return trackIndexCompare(a.second->track(), b.second->track());
1654 // 12 - Further sort tasks in events that have the same time by the
1655 // relative text track cue order of the text track cues associated
1656 // with these tasks.
1657 return a.second->isOrderedBefore(b.second);
1660 static bool compareCueInterval(const CueInterval& one, const CueInterval& two)
1662 return one.data()->isOrderedBefore(two.data());
1665 static bool compareCueIntervalEndTime(const CueInterval& one, const CueInterval& two)
1667 return one.data()->endMediaTime() > two.data()->endMediaTime();
1670 void HTMLMediaElement::updateActiveTextTrackCues(const MediaTime& movieTime)
1672 // 4.8.10.8 Playing the media resource
1674 // If the current playback position changes while the steps are running,
1675 // then the user agent must wait for the steps to complete, and then must
1676 // immediately rerun the steps.
1677 if (ignoreTrackDisplayUpdateRequests())
1680 // 1 - Let current cues be a list of cues, initialized to contain all the
1681 // cues of all the hidden, showing, or showing by default text tracks of the
1682 // media element (not the disabled ones) whose start times are less than or
1683 // equal to the current playback position and whose end times are greater
1684 // than the current playback position.
1685 CueList currentCues;
1687 // The user agent must synchronously unset [the text track cue active] flag
1688 // whenever ... the media element's readyState is changed back to HAVE_NOTHING.
1689 auto movieTimeInterval = m_cueTree.createInterval(movieTime, movieTime);
1690 if (m_readyState != HAVE_NOTHING && m_player) {
1691 currentCues = m_cueTree.allOverlaps(movieTimeInterval);
1692 if (currentCues.size() > 1)
1693 std::sort(currentCues.begin(), currentCues.end(), &compareCueInterval);
1696 CueList previousCues;
1699 // 2 - Let other cues be a list of cues, initialized to contain all the cues
1700 // of hidden, showing, and showing by default text tracks of the media
1701 // element that are not present in current cues.
1702 previousCues = m_currentlyActiveCues;
1704 // 3 - Let last time be the current playback position at the time this
1705 // algorithm was last run for this media element, if this is not the first
1707 MediaTime lastTime = m_lastTextTrackUpdateTime;
1709 // 4 - If the current playback position has, since the last time this
1710 // algorithm was run, only changed through its usual monotonic increase
1711 // during normal playback, then let missed cues be the list of cues in other
1712 // cues whose start times are greater than or equal to last time and whose
1713 // end times are less than or equal to the current playback position.
1714 // Otherwise, let missed cues be an empty list.
1715 if (lastTime >= MediaTime::zeroTime() && m_lastSeekTime < movieTime) {
1716 for (auto& cue : m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime))) {
1717 // Consider cues that may have been missed since the last seek time.
1718 if (cue.low() > std::max(m_lastSeekTime, lastTime) && cue.high() < movieTime)
1719 missedCues.append(cue);
1723 m_lastTextTrackUpdateTime = movieTime;
1725 // 5 - If the time was reached through the usual monotonic increase of the
1726 // current playback position during normal playback, and if the user agent
1727 // has not fired a timeupdate event at the element in the past 15 to 250ms
1728 // and is not still running event handlers for such an event, then the user
1729 // agent must queue a task to fire a simple event named timeupdate at the
1730 // element. (In the other cases, such as explicit seeks, relevant events get
1731 // fired as part of the overall process of changing the current playback
1733 if (!m_paused && m_lastSeekTime <= lastTime)
1734 scheduleTimeupdateEvent(false);
1736 // Explicitly cache vector sizes, as their content is constant from here.
1737 size_t currentCuesSize = currentCues.size();
1738 size_t missedCuesSize = missedCues.size();
1739 size_t previousCuesSize = previousCues.size();
1741 // 6 - If all of the cues in current cues have their text track cue active
1742 // flag set, none of the cues in other cues have their text track cue active
1743 // flag set, and missed cues is empty, then abort these steps.
1744 bool activeSetChanged = missedCuesSize;
1746 for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i)
1747 if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive())
1748 activeSetChanged = true;
1750 for (size_t i = 0; i < currentCuesSize; ++i) {
1751 RefPtr<TextTrackCue> cue = currentCues[i].data();
1753 if (cue->isRenderable())
1754 toVTTCue(cue.get())->updateDisplayTree(movieTime);
1756 if (!cue->isActive())
1757 activeSetChanged = true;
1760 MediaTime nextInterestingTime = MediaTime::invalidTime();
1761 if (auto nearestEndingCue = std::min_element(currentCues.begin(), currentCues.end(), compareCueIntervalEndTime))
1762 nextInterestingTime = nearestEndingCue->data()->endMediaTime();
1764 Optional<CueInterval> nextCue = m_cueTree.nextIntervalAfter(movieTimeInterval);
1766 nextInterestingTime = std::min(nextInterestingTime, nextCue->low());
1768 INFO_LOG(LOGIDENTIFIER, "nextInterestingTime:", nextInterestingTime);
1770 if (nextInterestingTime.isValid() && m_player) {
1771 m_player->performTaskAtMediaTime([this, weakThis = makeWeakPtr(this), nextInterestingTime] {
1775 auto currentMediaTime = weakThis->currentMediaTime();
1776 INFO_LOG(LOGIDENTIFIER, " - lambda, currentMediaTime:", currentMediaTime);
1777 weakThis->updateActiveTextTrackCues(currentMediaTime);
1778 }, nextInterestingTime);
1781 if (!activeSetChanged)
1784 // 7 - If the time was reached through the usual monotonic increase of the
1785 // current playback position during normal playback, and there are cues in
1786 // other cues that have their text track cue pause-on-exi flag set and that
1787 // either have their text track cue active flag set or are also in missed
1788 // cues, then immediately pause the media element.
1789 for (size_t i = 0; !m_paused && i < previousCuesSize; ++i) {
1790 if (previousCues[i].data()->pauseOnExit()
1791 && previousCues[i].data()->isActive()
1792 && !currentCues.contains(previousCues[i]))
1796 for (size_t i = 0; !m_paused && i < missedCuesSize; ++i) {
1797 if (missedCues[i].data()->pauseOnExit())
1801 // 8 - Let events be a list of tasks, initially empty. Each task in this
1802 // list will be associated with a text track, a text track cue, and a time,
1803 // which are used to sort the list before the tasks are queued.
1804 Vector<std::pair<MediaTime, TextTrackCue*>> eventTasks;
1806 // 8 - Let affected tracks be a list of text tracks, initially empty.
1807 Vector<TextTrack*> affectedTracks;
1809 for (size_t i = 0; i < missedCuesSize; ++i) {
1810 // 9 - For each text track cue in missed cues, prepare an event named enter
1811 // for the TextTrackCue object with the text track cue start time.
1812 eventTasks.append({ missedCues[i].data()->startMediaTime(), missedCues[i].data() });
1814 // 10 - For each text track [...] in missed cues, prepare an event
1815 // named exit for the TextTrackCue object with the with the later of
1816 // the text track cue end time and the text track cue start time.
1818 // Note: An explicit task is added only if the cue is NOT a zero or
1819 // negative length cue. Otherwise, the need for an exit event is
1820 // checked when these tasks are actually queued below. This doesn't
1821 // affect sorting events before dispatch either, because the exit
1822 // event has the same time as the enter event.
1823 if (missedCues[i].data()->startMediaTime() < missedCues[i].data()->endMediaTime())
1824 eventTasks.append({ missedCues[i].data()->endMediaTime(), missedCues[i].data() });
1827 for (size_t i = 0; i < previousCuesSize; ++i) {
1828 // 10 - For each text track cue in other cues that has its text
1829 // track cue active flag set prepare an event named exit for the
1830 // TextTrackCue object with the text track cue end time.
1831 if (!currentCues.contains(previousCues[i]))
1832 eventTasks.append({ previousCues[i].data()->endMediaTime(), previousCues[i].data() });
1835 for (size_t i = 0; i < currentCuesSize; ++i) {
1836 // 11 - For each text track cue in current cues that does not have its
1837 // text track cue active flag set, prepare an event named enter for the
1838 // TextTrackCue object with the text track cue start time.
1839 if (!previousCues.contains(currentCues[i]))
1840 eventTasks.append({ currentCues[i].data()->startMediaTime(), currentCues[i].data() });
1843 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1845 std::sort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare);
1847 for (auto& eventTask : eventTasks) {
1848 if (!affectedTracks.contains(eventTask.second->track()))
1849 affectedTracks.append(eventTask.second->track());
1851 // 13 - Queue each task in events, in list order.
1853 // Each event in eventTasks may be either an enterEvent or an exitEvent,
1854 // depending on the time that is associated with the event. This
1855 // correctly identifies the type of the event, if the startTime is
1856 // less than the endTime in the cue.
1857 if (eventTask.second->startTime() >= eventTask.second->endTime()) {
1858 auto enterEvent = Event::create(eventNames().enterEvent, Event::CanBubble::No, Event::IsCancelable::No);
1859 enterEvent->setTarget(eventTask.second);
1860 m_asyncEventQueue.enqueueEvent(WTFMove(enterEvent));
1862 auto exitEvent = Event::create(eventNames().exitEvent, Event::CanBubble::No, Event::IsCancelable::No);
1863 exitEvent->setTarget(eventTask.second);
1864 m_asyncEventQueue.enqueueEvent(WTFMove(exitEvent));
1866 RefPtr<Event> event;
1867 if (eventTask.first == eventTask.second->startMediaTime())
1868 event = Event::create(eventNames().enterEvent, Event::CanBubble::No, Event::IsCancelable::No);
1870 event = Event::create(eventNames().exitEvent, Event::CanBubble::No, Event::IsCancelable::No);
1871 event->setTarget(eventTask.second);
1872 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1876 // 14 - Sort affected tracks in the same order as the text tracks appear in
1877 // the media element's list of text tracks, and remove duplicates.
1878 std::sort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare);
1880 // 15 - For each text track in affected tracks, in the list order, queue a
1881 // task to fire a simple event named cuechange at the TextTrack object, and, ...
1882 for (auto& affectedTrack : affectedTracks) {
1883 auto event = Event::create(eventNames().cuechangeEvent, Event::CanBubble::No, Event::IsCancelable::No);
1884 event->setTarget(affectedTrack);
1885 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1887 // ... if the text track has a corresponding track element, to then fire a
1888 // simple event named cuechange at the track element as well.
1889 if (is<LoadableTextTrack>(*affectedTrack)) {
1890 auto event = Event::create(eventNames().cuechangeEvent, Event::CanBubble::No, Event::IsCancelable::No);
1891 auto trackElement = makeRefPtr(downcast<LoadableTextTrack>(*affectedTrack).trackElement());
1892 ASSERT(trackElement);
1893 event->setTarget(trackElement);
1894 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1898 // 16 - Set the text track cue active flag of all the cues in the current
1899 // cues, and unset the text track cue active flag of all the cues in the
1901 for (size_t i = 0; i < currentCuesSize; ++i)
1902 currentCues[i].data()->setIsActive(true);
1904 for (size_t i = 0; i < previousCuesSize; ++i)
1905 if (!currentCues.contains(previousCues[i]))
1906 previousCues[i].data()->setIsActive(false);
1908 // Update the current active cues.
1909 m_currentlyActiveCues = currentCues;
1911 if (activeSetChanged)
1912 updateTextTrackDisplay();
1915 bool HTMLMediaElement::textTracksAreReady() const
1917 // 4.8.10.12.1 Text track model
1919 // The text tracks of a media element are ready if all the text tracks whose mode was not
1920 // in the disabled state when the element's resource selection algorithm last started now
1921 // have a text track readiness state of loaded or failed to load.
1922 for (unsigned i = 0; i < m_textTracksWhenResourceSelectionBegan.size(); ++i) {
1923 if (m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::Loading
1924 || m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::NotLoaded)
1931 void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track)
1933 if (track->readinessState() != TextTrack::Loading
1934 && track->mode() != TextTrack::Mode::Disabled) {
1935 // The display trees exist as long as the track is active, in this case,
1936 // and if the same track is loaded again (for example if the src attribute was changed),
1937 // cues can be accumulated with the old ones, that's why they needs to be flushed
1938 if (hasMediaControls())
1939 mediaControls()->clearTextDisplayContainer();
1940 updateTextTrackDisplay();
1942 if (m_player && m_textTracksWhenResourceSelectionBegan.contains(track)) {
1943 if (track->readinessState() != TextTrack::Loading)
1944 setReadyState(m_player->readyState());
1946 // The track readiness state might have changed as a result of the user
1947 // clicking the captions button. In this case, a check whether all the
1948 // resources have failed loading should be done in order to hide the CC button.
1949 if (hasMediaControls() && track->readinessState() == TextTrack::FailedToLoad)
1950 mediaControls()->refreshClosedCaptionsButtonVisibility();
1954 void HTMLMediaElement::audioTrackEnabledChanged(AudioTrack& track)
1956 if (m_audioTracks && m_audioTracks->contains(track))
1957 m_audioTracks->scheduleChangeEvent();
1958 if (processingUserGestureForMedia())
1959 removeBehaviorsRestrictionsAfterFirstUserGesture(MediaElementSession::AllRestrictions & ~MediaElementSession::RequireUserGestureToControlControlsManager);
1962 void HTMLMediaElement::textTrackModeChanged(TextTrack& track)
1964 bool trackIsLoaded = true;
1965 if (track.trackType() == TextTrack::TrackElement) {
1966 trackIsLoaded = false;
1967 for (auto& trackElement : childrenOfType<HTMLTrackElement>(*this)) {
1968 if (&trackElement.track() == &track) {
1969 if (trackElement.readyState() == HTMLTrackElement::LOADING || trackElement.readyState() == HTMLTrackElement::LOADED)
1970 trackIsLoaded = true;
1976 // If this is the first added track, create the list of text tracks.
1978 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
1980 // Mark this track as "configured" so configureTextTracks won't change the mode again.
1981 track.setHasBeenConfigured(true);
1983 if (track.mode() != TextTrack::Mode::Disabled && trackIsLoaded)
1984 textTrackAddCues(track, *track.cues());
1986 configureTextTrackDisplay(AssumeTextTrackVisibilityChanged);
1988 if (m_textTracks && m_textTracks->contains(track))
1989 m_textTracks->scheduleChangeEvent();
1991 #if ENABLE(AVF_CAPTIONS)
1992 if (track.trackType() == TextTrack::TrackElement && m_player)
1993 m_player->notifyTrackModeChanged();
1997 void HTMLMediaElement::videoTrackSelectedChanged(VideoTrack& track)
1999 if (m_videoTracks && m_videoTracks->contains(track))
2000 m_videoTracks->scheduleChangeEvent();
2003 void HTMLMediaElement::textTrackKindChanged(TextTrack& track)
2005 if (track.kind() != TextTrack::Kind::Captions && track.kind() != TextTrack::Kind::Subtitles && track.mode() == TextTrack::Mode::Showing)
2006 track.setMode(TextTrack::Mode::Hidden);
2009 void HTMLMediaElement::beginIgnoringTrackDisplayUpdateRequests()
2011 ++m_ignoreTrackDisplayUpdate;
2014 void HTMLMediaElement::endIgnoringTrackDisplayUpdateRequests()
2016 ASSERT(m_ignoreTrackDisplayUpdate);
2017 --m_ignoreTrackDisplayUpdate;
2018 if (!m_ignoreTrackDisplayUpdate && m_inActiveDocument)
2019 updateActiveTextTrackCues(currentMediaTime());
2022 void HTMLMediaElement::textTrackAddCues(TextTrack& track, const TextTrackCueList& cues)
2024 if (track.mode() == TextTrack::Mode::Disabled)
2027 TrackDisplayUpdateScope scope { *this };
2028 for (unsigned i = 0; i < cues.length(); ++i)
2029 textTrackAddCue(track, *cues.item(i));
2032 void HTMLMediaElement::textTrackRemoveCues(TextTrack&, const TextTrackCueList& cues)
2034 TrackDisplayUpdateScope scope { *this };
2035 for (unsigned i = 0; i < cues.length(); ++i) {
2036 auto& cue = *cues.item(i);
2037 textTrackRemoveCue(*cue.track(), cue);
2041 void HTMLMediaElement::textTrackAddCue(TextTrack& track, TextTrackCue& cue)
2043 if (track.mode() == TextTrack::Mode::Disabled)
2046 // Negative duration cues need be treated in the interval tree as
2047 // zero-length cues.
2048 MediaTime endTime = std::max(cue.startMediaTime(), cue.endMediaTime());
2050 CueInterval interval = m_cueTree.createInterval(cue.startMediaTime(), endTime, &cue);
2051 if (!m_cueTree.contains(interval))
2052 m_cueTree.add(interval);
2053 updateActiveTextTrackCues(currentMediaTime());
2056 void HTMLMediaElement::textTrackRemoveCue(TextTrack&, TextTrackCue& cue)
2058 // Negative duration cues need to be treated in the interval tree as
2059 // zero-length cues.
2060 MediaTime endTime = std::max(cue.startMediaTime(), cue.endMediaTime());
2062 CueInterval interval = m_cueTree.createInterval(cue.startMediaTime(), endTime, &cue);
2063 m_cueTree.remove(interval);
2065 // Since the cue will be removed from the media element and likely the
2066 // TextTrack might also be destructed, notifying the region of the cue
2067 // removal shouldn't be done.
2068 if (cue.isRenderable())
2069 toVTTCue(&cue)->notifyRegionWhenRemovingDisplayTree(false);
2071 size_t index = m_currentlyActiveCues.find(interval);
2072 if (index != notFound) {
2073 cue.setIsActive(false);
2074 m_currentlyActiveCues.remove(index);
2077 if (cue.isRenderable())
2078 toVTTCue(&cue)->removeDisplayTree();
2079 updateActiveTextTrackCues(currentMediaTime());
2081 if (cue.isRenderable())
2082 toVTTCue(&cue)->notifyRegionWhenRemovingDisplayTree(true);
2087 static inline bool isAllowedToLoadMediaURL(HTMLMediaElement& element, const URL& url, bool isInUserAgentShadowTree)
2089 // Elements in user agent show tree should load whatever the embedding document policy is.
2090 if (isInUserAgentShadowTree)
2093 ASSERT(element.document().contentSecurityPolicy());
2094 return element.document().contentSecurityPolicy()->allowMediaFromSource(url);
2097 bool HTMLMediaElement::isSafeToLoadURL(const URL& url, InvalidURLAction actionIfInvalid)
2099 if (!url.isValid()) {
2100 ERROR_LOG(LOGIDENTIFIER, url, " is invalid");
2104 RefPtr<Frame> frame = document().frame();
2105 if (!frame || !document().securityOrigin().canDisplay(url)) {
2106 if (actionIfInvalid == Complain)
2107 FrameLoader::reportLocalLoadFailed(frame.get(), url.stringCenterEllipsizedToLength());
2108 ERROR_LOG(LOGIDENTIFIER, url , " was rejected by SecurityOrigin");
2112 if (!isAllowedToLoadMediaURL(*this, url, isInUserAgentShadowTree())) {
2113 ERROR_LOG(LOGIDENTIFIER, url, " was rejected by Content Security Policy");
2120 void HTMLMediaElement::startProgressEventTimer()
2122 if (m_progressEventTimer.isActive())
2125 m_previousProgressTime = MonotonicTime::now();
2126 // 350ms is not magic, it is in the spec!
2127 m_progressEventTimer.startRepeating(350_ms);
2130 void HTMLMediaElement::waitForSourceChange()
2132 INFO_LOG(LOGIDENTIFIER);
2134 stopPeriodicTimers();
2135 m_loadState = WaitingForSource;
2137 // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value
2138 m_networkState = NETWORK_NO_SOURCE;
2140 // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
2141 setShouldDelayLoadEvent(false);
2143 updateDisplayState();
2147 void HTMLMediaElement::noneSupported()
2152 INFO_LOG(LOGIDENTIFIER);
2154 stopPeriodicTimers();
2155 m_loadState = WaitingForSource;
2156 m_currentSourceNode = nullptr;
2159 // 6 - Reaching this step indicates that the media resource failed to load or that the given
2160 // URL could not be resolved. In one atomic operation, run the following steps:
2162 // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to
2163 // MEDIA_ERR_SRC_NOT_SUPPORTED.
2164 m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
2166 // 6.2 - Forget the media element's media-resource-specific text tracks.
2167 forgetResourceSpecificTracks();
2169 // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
2170 m_networkState = NETWORK_NO_SOURCE;
2172 // 7 - Queue a task to fire a simple event named error at the media element.
2173 scheduleEvent(eventNames().errorEvent);
2175 rejectPendingPlayPromises(WTFMove(m_pendingPlayPromises), DOMException::create(NotSupportedError));
2177 #if ENABLE(MEDIA_SOURCE)
2178 detachMediaSource();
2181 // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
2182 setShouldDelayLoadEvent(false);
2184 // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed,
2185 // the element won't attempt to load another resource.
2187 updateDisplayState();
2191 void HTMLMediaElement::mediaLoadingFailedFatally(MediaPlayer::NetworkState error)
2193 // 1 - The user agent should cancel the fetching process.
2194 stopPeriodicTimers();
2195 m_loadState = WaitingForSource;
2197 // 2 - Set the error attribute to a new MediaError object whose code attribute is
2198 // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
2199 if (error == MediaPlayer::NetworkError)
2200 m_error = MediaError::create(MediaError::MEDIA_ERR_NETWORK);
2201 else if (error == MediaPlayer::DecodeError)
2202 m_error = MediaError::create(MediaError::MEDIA_ERR_DECODE);
2204 ASSERT_NOT_REACHED();
2206 // 3 - Queue a task to fire a simple event named error at the media element.
2207 scheduleEvent(eventNames().errorEvent);
2209 #if ENABLE(MEDIA_SOURCE)
2210 detachMediaSource();
2213 // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a
2214 // task to fire a simple event called emptied at the element.
2215 m_networkState = NETWORK_EMPTY;
2216 scheduleEvent(eventNames().emptiedEvent);
2218 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
2219 setShouldDelayLoadEvent(false);
2221 // 6 - Abort the overall resource selection algorithm.
2222 m_currentSourceNode = nullptr;
2225 if (is<MediaDocument>(document()))
2226 downcast<MediaDocument>(document()).mediaElementSawUnsupportedTracks();
2230 void HTMLMediaElement::cancelPendingEventsAndCallbacks()
2232 INFO_LOG(LOGIDENTIFIER);
2233 m_asyncEventQueue.cancelAllEvents();
2235 for (auto& source : childrenOfType<HTMLSourceElement>(*this))
2236 source.cancelPendingErrorEvent();
2238 rejectPendingPlayPromises(WTFMove(m_pendingPlayPromises), DOMException::create(AbortError));
2241 void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*)
2243 beginProcessingMediaPlayerCallback();
2244 setNetworkState(m_player->networkState());
2245 endProcessingMediaPlayerCallback();
2248 static void logMediaLoadRequest(Page* page, const String& mediaEngine, const String& errorMessage, bool succeeded)
2253 DiagnosticLoggingClient& diagnosticLoggingClient = page->diagnosticLoggingClient();
2255 diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::mediaLoadingFailedKey(), errorMessage, DiagnosticLoggingResultFail, ShouldSample::No);
2259 diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadedKey(), mediaEngine, ShouldSample::No);
2261 if (!page->hasSeenAnyMediaEngine())
2262 diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOneMediaEngineKey(), emptyString(), ShouldSample::No);
2264 if (!page->hasSeenMediaEngine(mediaEngine))
2265 diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsMediaEngineKey(), mediaEngine, ShouldSample::No);
2267 page->sawMediaEngine(mediaEngine);
2270 static String stringForNetworkState(MediaPlayer::NetworkState state)
2273 case MediaPlayer::Empty: return "Empty"_s;
2274 case MediaPlayer::Idle: return "Idle"_s;
2275 case MediaPlayer::Loading: return "Loading"_s;
2276 case MediaPlayer::Loaded: return "Loaded"_s;
2277 case MediaPlayer::FormatError: return "FormatError"_s;
2278 case MediaPlayer::NetworkError: return "NetworkError"_s;
2279 case MediaPlayer::DecodeError: return "DecodeError"_s;
2280 default: return emptyString();
2284 void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error)
2286 stopPeriodicTimers();
2288 // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
2289 // <source> children, schedule the next one
2290 if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
2292 // resource selection algorithm
2293 // Step 9.Otherwise.9 - Failed with elements: Queue a task, using the DOM manipulation task source, to fire a simple event named error at the candidate element.
2294 if (m_currentSourceNode)
2295 m_currentSourceNode->scheduleErrorEvent();
2297 INFO_LOG(LOGIDENTIFIER, "error event not sent, <source> was removed");
2299 // 9.Otherwise.10 - Asynchronously await a stable state. The synchronous section consists of all the remaining steps of this algorithm until the algorithm says the synchronous section has ended.
2301 // 9.Otherwise.11 - Forget the media element's media-resource-specific tracks.
2302 forgetResourceSpecificTracks();
2304 if (havePotentialSourceChild()) {
2305 INFO_LOG(LOGIDENTIFIER, "scheduling next <source>");
2306 scheduleNextSourceChild();
2308 INFO_LOG(LOGIDENTIFIER, "no more <source> elements, waiting");
2309 waitForSourceChange();
2315 if ((error == MediaPlayer::NetworkError && m_readyState >= HAVE_METADATA) || error == MediaPlayer::DecodeError)
2316 mediaLoadingFailedFatally(error);
2317 else if ((error == MediaPlayer::FormatError || error == MediaPlayer::NetworkError) && m_loadState == LoadingFromSrcAttr)
2320 updateDisplayState();
2321 if (hasMediaControls()) {
2322 mediaControls()->reset();
2323 mediaControls()->reportedError();
2326 ERROR_LOG(LOGIDENTIFIER, "error = ", static_cast<int>(error));
2328 logMediaLoadRequest(document().page(), String(), stringForNetworkState(error), false);
2330 m_mediaSession->clientCharacteristicsChanged();
2333 void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
2335 if (static_cast<int>(state) != static_cast<int>(m_networkState))
2336 ALWAYS_LOG(LOGIDENTIFIER, "new state = ", state, ", current state = ", m_networkState);
2338 if (state == MediaPlayer::Empty) {
2339 // Just update the cached state and leave, we can't do anything.
2340 m_networkState = NETWORK_EMPTY;
2344 if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) {
2345 mediaLoadingFailed(state);
2349 if (state == MediaPlayer::Idle) {
2350 if (m_networkState > NETWORK_IDLE) {
2351 changeNetworkStateFromLoadingToIdle();
2352 setShouldDelayLoadEvent(false);
2354 m_networkState = NETWORK_IDLE;
2358 if (state == MediaPlayer::Loading) {
2359 if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE)
2360 startProgressEventTimer();
2361 m_networkState = NETWORK_LOADING;
2364 if (state == MediaPlayer::Loaded) {
2365 if (m_networkState != NETWORK_IDLE)
2366 changeNetworkStateFromLoadingToIdle();
2367 m_completelyLoaded = true;
2370 if (hasMediaControls())
2371 mediaControls()->updateStatusDisplay();
2374 void HTMLMediaElement::changeNetworkStateFromLoadingToIdle()
2376 m_progressEventTimer.stop();
2377 if (hasMediaControls() && m_player->didLoadingProgress())
2378 mediaControls()->bufferingProgressed();
2380 // Schedule one last progress event so we guarantee that at least one is fired
2381 // for files that load very quickly.
2382 scheduleEvent(eventNames().progressEvent);
2383 scheduleEvent(eventNames().suspendEvent);
2384 m_networkState = NETWORK_IDLE;
2387 void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*)
2389 beginProcessingMediaPlayerCallback();
2391 setReadyState(m_player->readyState());
2393 endProcessingMediaPlayerCallback();
2396 SuccessOr<MediaPlaybackDenialReason> HTMLMediaElement::canTransitionFromAutoplayToPlay() const
2399 && mediaSession().autoplayPermitted()
2402 && !pausedForUserInteraction()
2403 && !document().isSandboxed(SandboxAutomaticFeatures)
2404 && m_readyState == HAVE_ENOUGH_DATA)
2405 return mediaSession().playbackPermitted();
2407 ALWAYS_LOG(LOGIDENTIFIER, "page consent required");
2408 return MediaPlaybackDenialReason::PageConsentRequired;
2411 void HTMLMediaElement::dispatchPlayPauseEventsIfNeedsQuirks()
2413 auto& document = this->document();
2414 if (!needsAutoplayPlayPauseEventsQuirk(document) && !needsAutoplayPlayPauseEventsQuirk(document.topDocument()))
2417 ALWAYS_LOG(LOGIDENTIFIER);
2418 scheduleEvent(eventNames().playingEvent);
2419 scheduleEvent(eventNames().pauseEvent);
2422 void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
2424 // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it
2425 bool wasPotentiallyPlaying = potentiallyPlaying();
2427 ReadyState oldState = m_readyState;
2428 ReadyState newState = static_cast<ReadyState>(state);
2430 #if ENABLE(VIDEO_TRACK)
2431 bool tracksAreReady = textTracksAreReady();
2433 if (newState == oldState && m_tracksAreReady == tracksAreReady)
2436 m_tracksAreReady = tracksAreReady;
2438 if (newState == oldState)
2440 bool tracksAreReady = true;
2443 ALWAYS_LOG(LOGIDENTIFIER, "new state = ", state, ", current state = ", m_readyState);
2446 m_readyState = newState;
2448 // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until
2449 // the text tracks are ready, regardless of the state of the media file.
2450 if (newState <= HAVE_METADATA)
2451 m_readyState = newState;
2453 m_readyState = HAVE_CURRENT_DATA;
2456 if (oldState > m_readyStateMaximum)
2457 m_readyStateMaximum = oldState;
2459 if (m_networkState == NETWORK_EMPTY)
2463 // 4.8.10.9, step 11
2464 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA)
2465 scheduleEvent(eventNames().waitingEvent);
2467 // 4.8.10.10 step 14 & 15.
2468 if (m_seekRequested && !m_player->seeking() && m_readyState >= HAVE_CURRENT_DATA)
2471 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
2473 invalidateCachedTime();
2474 scheduleTimeupdateEvent(false);
2475 scheduleEvent(eventNames().waitingEvent);
2479 if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
2480 prepareMediaFragmentURI();
2481 scheduleEvent(eventNames().durationchangeEvent);
2482 scheduleResizeEvent();
2483 scheduleEvent(eventNames().loadedmetadataEvent);
2484 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
2485 if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent))
2486 enqueuePlaybackTargetAvailabilityChangedEvent();
2488 m_initiallyMuted = m_volume < 0.05 || muted();
2490 if (hasMediaControls())
2491 mediaControls()->loadedMetadata();
2494 if (is<MediaDocument>(document()))
2495 downcast<MediaDocument>(document()).mediaElementNaturalSizeChanged(expandedIntSize(m_player->naturalSize()));
2497 logMediaLoadRequest(document().page(), m_player->engineDescription(), String(), true);
2499 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
2500 scheduleUpdateMediaState();
2503 m_mediaSession->clientCharacteristicsChanged();
2506 bool shouldUpdateDisplayState = false;
2508 if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA) {
2509 if (!m_haveFiredLoadedData) {
2510 m_haveFiredLoadedData = true;
2511 scheduleEvent(eventNames().loadeddataEvent);
2512 // FIXME: It's not clear that it's correct to skip these two operations just
2513 // because m_haveFiredLoadedData is already true. At one time we were skipping
2514 // the call to setShouldDelayLoadEvent, which was definitely incorrect.
2515 shouldUpdateDisplayState = true;
2516 applyMediaFragmentURI();
2518 setShouldDelayLoadEvent(false);
2521 if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
2522 scheduleEvent(eventNames().canplayEvent);
2523 shouldUpdateDisplayState = true;
2526 if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA && tracksAreReady) {
2527 if (oldState <= HAVE_CURRENT_DATA)
2528 scheduleEvent(eventNames().canplayEvent);
2530 scheduleEvent(eventNames().canplaythroughEvent);
2532 auto success = canTransitionFromAutoplayToPlay();
2535 invalidateCachedTime();
2536 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::StartedWithoutUserGesture);
2537 m_playbackStartedTime = currentMediaTime().toDouble();
2538 scheduleEvent(eventNames().playEvent);
2539 } else if (success.value() == MediaPlaybackDenialReason::UserGestureRequired) {
2540 ALWAYS_LOG(LOGIDENTIFIER, "Autoplay blocked, user gesture required");
2541 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::PreventedAutoplay);
2544 shouldUpdateDisplayState = true;
2547 // If we transition to the Future Data state and we're about to begin playing, ensure playback is actually permitted first,
2548 // honoring any playback denial reasons such as the requirement of a user gesture.
2549 if (m_readyState == HAVE_FUTURE_DATA && oldState < HAVE_FUTURE_DATA && potentiallyPlaying() && !m_mediaSession->playbackPermitted()) {
2550 auto canTransition = canTransitionFromAutoplayToPlay();
2551 if (canTransition && canTransition.value() == MediaPlaybackDenialReason::UserGestureRequired)
2552 ALWAYS_LOG(LOGIDENTIFIER, "Autoplay blocked, user gesture required");
2555 setAutoplayEventPlaybackState(AutoplayEventPlaybackState::PreventedAutoplay);
2558 if (shouldUpdateDisplayState) {
2559 updateDisplayState();
2560 if (hasMediaControls()) {
2561 mediaControls()->refreshClosedCaptionsButtonVisibility();
2562 mediaControls()->updateStatusDisplay();
2567 updateMediaController();
2568 #if ENABLE(VIDEO_TRACK)
2569 updateActiveTextTrackCues(currentMediaTime());
2573 #if ENABLE(LEGACY_ENCRYPTED_MEDIA)
2574 RefPtr<ArrayBuffer> HTMLMediaElement::mediaPlayerCachedKeyForKeyId(const String& keyId) const
2576 return m_webKitMediaKeys ? m_webKitMediaKeys->cachedKeyForKeyId(keyId) : nullptr;
2579 bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, Uint8Array* initData)
2581 if (!RuntimeEnabledFeatures::sharedFeatures().legacyEncryptedMediaAPIEnabled())
2584 if (!hasEventListeners("webkitneedkey")
2585 #if ENABLE(ENCRYPTED_MEDIA)
2586 // Only fire an error if ENCRYPTED_MEDIA is not enabled, to give clients of the
2587 // "encrypted" event a chance to handle it without resulting in a synthetic error.
2588 && (!RuntimeEnabledFeatures::sharedFeatures().encryptedMediaAPIEnabled() || document().quirks().hasBrokenEncryptedMediaAPISupportQuirk())
2591 m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED);
2592 scheduleEvent(eventNames().errorEvent);
2596 auto event = WebKitMediaKeyNeededEvent::create(eventNames().webkitneedkeyEvent, initData);
2597 event->setTarget(this);
2598 m_asyncEventQueue.enqueueEvent(WTFMove(event));
2603 String HTMLMediaElement::mediaPlayerMediaKeysStorageDirectory() const
2605 auto* page = document().page();
2606 if (!page || page->usesEphemeralSession())
2607 return emptyString();
2609 String storageDirectory = document().settings().mediaKeysStorageDirectory();
2610 if (storageDirectory.isEmpty())
2611 return emptyString();
2613 return FileSystem::pathByAppendingComponent(storageDirectory, document().securityOrigin().data().databaseIdentifier());
2616 void HTMLMediaElement::webkitSetMediaKeys(WebKitMediaKeys* mediaKeys)
2618 if (!RuntimeEnabledFeatures::sharedFeatures().legacyEncryptedMediaAPIEnabled())
2621 if (m_webKitMediaKeys == mediaKeys)
2624 if (m_webKitMediaKeys)
2625 m_webKitMediaKeys->setMediaElement(nullptr);
2626 m_webKitMediaKeys = mediaKeys;
2627 if (m_webKitMediaKeys)
2628 m_webKitMediaKeys->setMediaElement(this);
2631 void HTMLMediaElement::keyAdded()
2633 if (!RuntimeEnabledFeatures::sharedFeatures().legacyEncryptedMediaAPIEnabled())
2637 m_player->keyAdded();
2642 #if ENABLE(ENCRYPTED_MEDIA)
2644 MediaKeys* HTMLMediaElement::mediaKeys() const
2646 return m_mediaKeys.get();
2649 void HTMLMediaElement::setMediaKeys(MediaKeys* mediaKeys, Ref<DeferredPromise>&& promise)
2651 // https://w3c.github.io/encrypted-media/#dom-htmlmediaelement-setmediakeys
2652 // W3C Editor's Draft 23 June 2017
2654 // 1. If this object's attaching media keys value is true, return a promise rejected with an InvalidStateError.
2655 if (m_attachingMediaKeys) {
2656 promise->reject(InvalidStateError);
2660 // 2. If mediaKeys and the mediaKeys attribute are the same object, return a resolved promise.
2661 if (mediaKeys == m_mediaKeys) {
2666 // 3. Let this object's attaching media keys value be true.
2667 m_attachingMediaKeys = true;
2669 // 4. Let promise be a new promise.
2670 // 5. Run the following steps in parallel:
2671 m_encryptedMediaQueue.enqueueTask([this, mediaKeys = RefPtr<MediaKeys>(mediaKeys), promise = WTFMove(promise)]() mutable {
2672 // 5.1. If all the following conditions hold:
2673 // - mediaKeys is not null,
2674 // - the CDM instance represented by mediaKeys is already in use by another media element
2675 // - the user agent is unable to use it with this element
2676 // then let this object's attaching media keys value be false and reject promise with a QuotaExceededError.
2679 // 5.2. If the mediaKeys attribute is not null, run the following steps:
2681 // 5.2.1. If the user agent or CDM do not support removing the association, let this object's attaching media keys value be false and reject promise with a NotSupportedError.
2682 // 5.2.2. If the association cannot currently be removed, let this object's attaching media keys value be false and reject promise with an InvalidStateError.
2683 // 5.2.3. Stop using the CDM instance represented by the mediaKeys attribute to decrypt media data and remove the association with the media element.
2684 // 5.2.4. If the preceding step failed, let this object's attaching media keys value be false and reject promise with the appropriate error name.
2687 m_mediaKeys->detachCDMClient(*this);
2689 m_player->cdmInstanceDetached(m_mediaKeys->cdmInstance());
2692 // 5.3. If mediaKeys is not null, run the following steps:
2694 // 5.3.1. Associate the CDM instance represented by mediaKeys with the media element for decrypting media data.
2695 mediaKeys->attachCDMClient(*this);
2697 m_player->cdmInstanceAttached(mediaKeys->cdmInstance());
2699 // 5.3.2. If the preceding step failed, run the following steps:
2700 // 5.3.2.1. Set the mediaKeys attribute to null.
2701 // 5.3.2.2. Let this object's attaching media keys value be false.
2702 // 5.3.2.3. Reject promise with a new DOMException whose name is the appropriate error name.
2705 // 5.3.3. Queue a task to run the Attempt to Resume Playback If Necessary algorithm on the media element.
2706 m_encryptedMediaQueue.enqueueTask([this] {
2707 attemptToResumePlaybackIfNecessary();
2711 // 5.4. Set the mediaKeys attribute to mediaKeys.
2712 // 5.5. Let this object's attaching media keys value be false.
2713 // 5.6. Resolve promise.
2714 m_mediaKeys = WTFMove(mediaKeys);
2715 m_attachingMediaKeys = false;
2719 // 6. Return promise.
2722 void HTMLMediaElement::mediaPlayerInitializationDataEncountered(const String& initDataType, RefPtr<ArrayBuffer>&& initData)
2724 if (!RuntimeEnabledFeatures::sharedFeatures().encryptedMediaAPIEnabled() || document().quirks().hasBrokenEncryptedMediaAPISupportQuirk())
2727 // https://w3c.github.io/encrypted-media/#initdata-encountered
2728 // W3C Editor's Draft 23 June 2017
2730 // 1. Let the media element be the specified HTMLMediaElement object.
2731 // 2. Let initDataType be the empty string.
2732 // 3. Let initData be null.
2733 // 4. If the media data is CORS-same-origin and not mixed content, run the following steps:
2734 // 4.1. Let initDataType be the string representing the Initialization Data Type of the Initialization Data.
2735 // 4.2. Let initData be the Initialization Data.
2738 // 5. Queue a task to create an event named encrypted that does not bubble and is not cancellable using the
2739 // MediaEncryptedEvent interface with its type attribute set to encrypted and its isTrusted attribute
2740 // initialized to true, and dispatch it at the media element.
2741 // The event interface MediaEncryptedEvent has:
2742 // initDataType = initDataType
2743 // initData = initData
2744 MediaEncryptedEventInit initializer { initDataType, WTFMove(initData) };
2745 m_asyncEventQueue.enqueueEvent(MediaEncryptedEvent::create(eventNames().encryptedEvent, initializer, Event::IsTrusted::Yes));
2748 void HTMLMediaElement::mediaPlayerWaitingForKeyChanged()
2753 if (!m_player->waitingForKey() && m_playbackBlockedWaitingForKey) {
2754 // https://w3c.github.io/encrypted-media/#resume-playback
2755 // W3C Editor's Draft 23 June 2017
2757 // NOTE: continued from HTMLMediaElement::attemptToDecrypt().
2758 // 4. If the user agent can advance the current playback position in the direction of playback:
2759 // 4.1. Set the media element's decryption blocked waiting for key value to false.
2761 // 4.2. Set the media element's playback blocked waiting for key value to false.
2762 m_playbackBlockedWaitingForKey = false;
2764 // 4.3. Set the media element's readyState value to HAVE_CURRENT_DATA, HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA as appropriate.
2765 setReadyState(m_player->readyState());
2770 // https://www.w3.org/TR/encrypted-media/#wait-for-key
2771 // W3C Recommendation 18 September 2017
2773 // The Wait for Key algorithm queues a waitingforkey event and
2774 // updates readyState. It should only be called when the
2775 // HTMLMediaElement object is potentially playing and its
2776 // readyState is equal to HAVE_FUTURE_DATA or greater. Requests to
2777 // run this algorithm include a target HTMLMediaElement object.
2779 // The following steps are run:
2781 // 1. Let the media element be the specified HTMLMediaElement
2783 // 2. If the media element's playback blocked waiting for key
2784 // value is true, abort these steps.
2785 if (m_playbackBlockedWaitingForKey)
2788 // 3. Set the media element's playback blocked waiting for key
2790 m_playbackBlockedWaitingForKey = true;
2793 // As a result of the above step, the media element will become a
2794 // blocked media element if it wasn't already. In that case, the
2795 // media element will stop playback.
2797 // 4. Follow the steps for the first matching condition from the
2800 // If data for the immediate current playback position is
2802 // Set the readyState of media element to HAVE_CURRENT_DATA.
2804 // Set the readyState of media element to HAVE_METADATA.
2805 ReadyState nextReadyState = buffered()->contain(currentTime()) ? HAVE_CURRENT_DATA : HAVE_METADATA;
2806 if (nextReadyState < m_readyState)
2807 setReadyState(static_cast<MediaPlayer::ReadyState>(nextReadyState));
2810 // In other words, if the video frame and audio data for the
2811 // current playback position have been decoded because they were
2812 // unencrypted and/or successfully decrypted, set readyState to
2813 // HAVE_CURRENT_DATA. Otherwise, including if this was previously
2814 // the case but the data is no longer available, set readyState to
2817 // 5. Queue a task to fire a simple event named waitingforkey at the
2819 scheduleEvent(eventNames().waitingforkeyEvent);
2821 // 6. Suspend playback.
2822 // GStreamer handles this without suspending explicitly.
2825 void HTMLMediaElement::attemptToDecrypt()
2827 // https://w3c.github.io/encrypted-media/#attempt-to-decrypt
2828 // W3C Editor's Draft 23 June 2017
2830 // 1. Let the media element be the specified HTMLMediaElement object.
2831 // 2. If the media element's encrypted block queue is empty, abort these steps.
2834 // 3. If the media element's mediaKeys attribute is not null, run the following steps:
2836 // 3.1. Let media keys be the MediaKeys object referenced by that attribute.
2837 // 3.2. Let cdm be the CDM instance represented by media keys's cdm instance value.
2838 auto& cdmInstance = m_mediaKeys->cdmInstance();
2840 // 3.3. If cdm is no longer usable for any reason, run the following steps:
2841 // 3.3.1. Run the media data is corrupted steps of the resource fetch algorithm.
2842 // 3.3.2. Run the CDM Unavailable algorithm on media keys.
2843 // 3.3.3. Abort these steps.
2846 // 3.4. If there is at least one MediaKeySession created by the media keys that is not closed, run the following steps:
2847 if (m_mediaKeys->hasOpenSessions()) {
2848 // Continued in MediaPlayer::attemptToDecryptWithInstance().
2850 m_player->attemptToDecryptWithInstance(cdmInstance);
2854 // 4. Set the media element's decryption blocked waiting for key value to true.
2858 void HTMLMediaElement::attemptToResumePlaybackIfNecessary()
2860 // https://w3c.github.io/encrypted-media/#resume-playback
2861 // W3C Editor's Draft 23 June 2017
2863 // 1. Let the media element be the specified HTMLMediaElement object.
2864 // 2. If the media element's playback blocked waiting for key is false, abort these steps.
2865 if (!m_playbackBlockedWaitingForKey)
2868 // 3. Run the Attempt to Decrypt algorithm on the media element.
2871 // NOTE: continued in HTMLMediaElement::waitingForKeyChanged()
2874 void HTMLMediaElement::cdmClientAttemptToResumePlaybackIfNecessary()
2876 attemptToResumePlaybackIfNecessary();
2879 #endif // ENABLE(ENCRYPTED_MEDIA)
2881 void HTMLMediaElement::progressEventTimerFired()
2884 if (m_networkState != NETWORK_LOADING)
2887 MonotonicTime time = MonotonicTime::now();
2888 Seconds timedelta = time - m_previousProgressTime;
2890 if (m_player->didLoadingProgress()) {
2891 scheduleEvent(eventNames().progressEvent);
2892 m_previousProgressTime = time;
2893 m_sentStalledEvent = false;
2895 if (hasMediaControls())
2896 mediaControls()->bufferingProgressed();
2897 } else if (timedelta > 3_s && !m_sentStalledEvent) {
2898 scheduleEvent(eventNames().stalledEvent);
2899 m_sentStalledEvent = true;
2900 setShouldDelayLoadEvent(false);
2904 void HTMLMediaElement::rewind(double timeDelta)
2906 setCurrentTime(std::max(currentMediaTime() - MediaTime::createWithDouble(timeDelta), minTimeSeekable()));
2909 void HTMLMediaElement::returnToRealtime()
2911 setCurrentTime(maxTimeSeekable());
2914 void HTMLMediaElement::addPlayedRange(const MediaTime& start, const MediaTime& end)
2916 DEBUG_LOG(LOGIDENTIFIER, MediaTimeRange { start, end });
2917 if (!m_playedTimeRanges)
2918 m_playedTimeRanges = TimeRanges::create();
2919 m_playedTimeRanges->ranges().add(start, end);
2922 bool HTMLMediaElement::supportsScanning() const
2924 return m_player ? m_player->supportsScanning() : false;
2927 void HTMLMediaElement::prepareToPlay()
2929 ScriptDisallowedScope::InMainThread scriptDisallowedScope;
2931 INFO_LOG(LOGIDENTIFIER);
2932 if (m_havePreparedToPlay || !document().hasBrowsingContext())
2934 m_havePreparedToPlay = true;
2936 m_player->prepareToPlay();
2939 void HTMLMediaElement::fastSeek(double time)
2941 fastSeek(MediaTime::createWithDouble(time));
2944 void HTMLMediaElement::fastSeek(const MediaTime& time)
2946 INFO_LOG(LOGIDENTIFIER, time);
2948 // 9. If the approximate-for-speed flag is set, adjust the new playback position to a value that will
2949 // allow for playback to resume promptly. If new playback position before this step is before current
2950 // playback position, then the adjusted new playback position must also be before the current playback
2951 // position. Similarly, if the new playback position before this step is after current playback position,
2952 // then the adjusted new playback position must also be after the current playback position.
2953 refreshCachedTime();
2955 MediaTime delta = time - currentMediaTime();
2956 MediaTime negativeTolerance = delta < MediaTime::zeroTime() ? MediaTime::positiveInfiniteTime() : delta;
2957 seekWithTolerance(time, negativeTolerance, MediaTime::zeroTime(), true);
2960 void HTMLMediaElement::seek(const MediaTime& time)
2962 INFO_LOG(LOGIDENTIFIER, time);
2963 seekWithTolerance(time, MediaTime::zeroTime(), MediaTime::zeroTime(), true);
2966 void HTMLMediaElement::seekInternal(const MediaTime& time)
2968 INFO_LOG(LOGIDENTIFIER, time);
2969 seekWithTolerance(time, MediaTime::zeroTime(), MediaTime::zeroTime(), false);
2972 void HTMLMediaElement::seekWithTolerance(const MediaTime& inTime, const MediaTime& negativeTolerance, const MediaTime& positiveTolerance, bool fromDOM)
2975 MediaTime time = inTime;
2977 // 1 - Set the media element's show poster flag to false.
2978 setDisplayMode(Video);
2980 // 2 - If the media element's readyState is HAVE_NOTHING, abort these steps.
2981 if (m_readyState == HAVE_NOTHING || !m_player)
2984 // If the media engine has been told to postpone loading data, let it go ahead now.
2985 if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA)
2988 // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set.
2989 refreshCachedTime();
2990 MediaTime now = currentMediaTime();
2992 // 3 - If the element's seeking IDL attribute is true, then another instance of this algorithm is
2993 // already running. Abort that other instance of the algorithm without waiting for the step that
2994 // it is running to complete.
2995 if (m_seekTaskQueue.hasPendingTask()) {
2996 INFO_LOG(LOGIDENTIFIER, "cancelling pending seeks");
2997 m_seekTaskQueue.cancelTask();
2998 if (m_pendingSeek) {
2999 now = m_pendingSeek->now;
3000 m_pendingSeek = nullptr;
3002 m_pendingSeekType = NoSeek;
3005 // 4 - Set the seeking IDL attribute to true.
3006 // The flag will be cleared when the engine tells us the time has actually changed.
3009 if (m_lastSeekTime < now)
3010 addPlayedRange(m_lastSeekTime, now);
3012 m_lastSeekTime = time;
3014 // 5 - If the seek was in response to a DOM method call or setting of an IDL attribute, then continue
3015 // the script. The remainder of these steps must be run asynchronously.
3016 m_pendingSeek = std::make_unique<PendingSeek>(now, time, negativeTolerance, positiveTolerance);
3018 INFO_LOG(LOGIDENTIFIER, "enqueuing seek from ", now, " to ", time);
3019 m_seekTaskQueue.scheduleTask(std::bind(&HTMLMediaElement::seekTask, this));
3023 if (processingUserGestureForMedia())
3024 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager);
3027 void HTMLMediaElement::seekTask()
3029 INFO_LOG(LOGIDENTIFIER);
3036 ASSERT(m_pendingSeek);
3037 MediaTime now = m_pendingSeek->now;
3038 MediaTime time = m_pendingSeek->targetTime;
3039 MediaTime negativeTolerance = m_pendingSeek->negativeTolerance;
3040 MediaTime positiveTolerance = m_pendingSeek->positiveTolerance;
3041 m_pendingSeek = nullptr;
3043 ASSERT(negativeTolerance >= MediaTime::zeroTime());
3045 // 6 - If the new playback position is later than the end of the media resource, then let it be the end
3046 // of the media resource instead.
3047 time = std::min(time, durationMediaTime());
3049 // 7 - If the new playback position is less than the earliest possible position, let it be that position instead.
3050 MediaTime earliestTime = m_player->startTime();
3051 time = std::max(time, earliestTime);
3053 // Ask the media engine for the time value in the movie's time scale before comparing with current time. This
3054 // is necessary because if the seek time is not equal to currentTime but the delta is less than the movie's
3055 // time scale, we will ask the media engine to "seek" to the current movie time, which may be a noop and
3056 // not generate a timechanged callback. This means m_seeking will never be cleared and we will never
3057 // fire a 'seeked' event.
3058 if (willLog(WTFLogLevelDebug)) {
3059 MediaTime mediaTime = m_player->mediaTimeForTimeValue(time);
3060 if (time != mediaTime)
3061 DEBUG_LOG(LOGIDENTIFIER, time, " media timeline equivalent is ", mediaTime);
3064 time = m_player->mediaTimeForTimeValue(time);
3066 // 8 - If the (possibly now changed) new playback position is not in one of the ranges given in the
3067 // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute
3068 // that is the nearest to the new playback position. ... If there are no ranges given in the seekable
3069 // attribute then set the seeking IDL attribute to false and abort these steps.
3070 RefPtr<TimeRanges> seekableRanges = seekable();
3071 bool noSeekRequired = !seekableRanges->length();
3073 // Short circuit seeking to the current time by just firing the events if no seek is required.
3074 // Don't skip calling the media engine if 1) we are in poster mode (because a seek should always cancel
3075 // poster display), or 2) if there is a pending fast seek, or 3) if this seek is not an exact seek
3076 SeekType thisSeekType = (negativeTolerance == MediaTime::zeroTime() && positiveTolerance == MediaTime::zeroTime()) ? Precise : Fast;
3077 if (!noSeekRequired && time == now && thisSeekType == Precise && m_pendingSeekType != Fast && displayMode() != Poster)
3078 noSeekRequired = true;
3080 #if ENABLE(MEDIA_SOURCE)
3081 // Always notify the media engine of a seek if the source is not closed. This ensures that the source is
3082 // always in a flushed state when the 'seeking' event fires.
3083 if (m_mediaSource && !m_mediaSource->isClosed())
3084 noSeekRequired = false;
3087 if (noSeekRequired) {
3088 INFO_LOG(LOGIDENTIFIER, "seek to ", time, " ignored");
3090 scheduleEvent(eventNames().seekingEvent);
3091 scheduleTimeupdateEvent(false);
3092 scheduleEvent(eventNames().seekedEvent);
3097 time = seekableRanges->ranges().nearest(time);
3099 m_sentEndEvent = false;
3100 m_lastSeekTime = time;
3101 m_pendingSeekType = thisSeekType;
3104 // 10 - Queue a task to fire a simple event named seeking at the element.
3105 scheduleEvent(eventNames().seekingEvent);
3107 // 11 - Set the current playback position to the given new playback position
3108 m_seekRequested = true;
3109 m_player->seekWithTolerance(time, negativeTolerance, positiveTolerance);
3111 // 12 - Wait until the user agent has established whether or not the media data for the new playback
3112 // position is available, and, if it is, until it has decoded enough data to play back that position.
3113 // 13 - Await a stable state. The synchronous section consists of all the remaining steps of this algorithm.
3116 void HTMLMediaElement::clearSeeking()
3119 m_seekRequested = false;
3120 m_pendingSeekType = NoSeek;
3121 invalidateCachedTime();
3124 void HTMLMediaElement::finishSeek()
3127 // 14 - Set the seeking IDL attribute to false.
3130 INFO_LOG(LOGIDENTIFIER, "current time = ", currentMediaTime());
3132 // 15 - Run the time maches on steps.
3133 // Handled by mediaPlayerTimeChanged().
3135 // 16 - Queue a task to fire a simple event named timeupdate at the element.
3136 scheduleEvent(eventNames().timeupdateEvent);
3138 // 17 - Queue a task to fire a simple event named seeked at the element.
3139 scheduleEvent(eventNames().seekedEvent);
3142 m_mediaSession->clientCharacteristicsChanged();
3144 #if ENABLE(MEDIA_SOURCE)
3146 m_mediaSource->monitorSourceBuffers();
3150 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
3152 return m_readyState;
3155 MediaPlayer::MovieLoadType HTMLMediaElement::movieLoadType() const
3157 return m_player ? m_player->movieLoadType() : MediaPlayer::Unknown;
3160 bool HTMLMediaElement::hasAudio() const
3162 return m_player ? m_player->hasAudio() : false;
3165 bool HTMLMediaElement::seeking() const
3170 void HTMLMediaElement::refreshCachedTime() const
3175 m_cachedTime = m_player->currentTime();
3176 if (!m_cachedTime) {
3177 // Do not use m_cachedTime until the media engine returns a non-zero value because we can't
3178 // estimate current time until playback actually begins.
3179 invalidateCachedTime();
3183 m_clockTimeAtLastCachedTimeUpdate = MonotonicTime::now();
3186 void HTMLMediaElement::invalidateCachedTime() const
3188 m_cachedTime = MediaTime::invalidTime();
3189 if (!m_player || !m_player->maximumDurationToCacheMediaTime())
3192 // Don't try to cache movie time when playback first starts as the time reported by the engine
3193 // sometimes fluctuates for a short amount of time, so the cached time will be off if we take it
3195 static const Seconds minimumTimePlayingBeforeCacheSnapshot = 500_ms;
3197 m_minimumClockTimeToUpdateCachedTime = MonotonicTime::now() + minimumTimePlayingBeforeCacheSnapshot;
3201 double HTMLMediaElement::currentTime() const
3203 return currentMediaTime().toDouble();
3206 MediaTime HTMLMediaElement::currentMediaTime() const
3208 #if LOG_CACHED_TIME_WARNINGS
3209 static const MediaTime minCachedDeltaForWarning = MediaTime::create(1, 100);
3213 return MediaTime::zeroTime();
3216 INFO_LOG(LOGIDENTIFIER, "seeking, returning", m_lastSeekTime);
3217 return m_lastSeekTime;
3220 if (m_cachedTime.isValid() && m_paused) {
3221 #if LOG_CACHED_TIME_WARNINGS
3222 MediaTime delta = m_cachedTime - m_player->currentTime();
3223 if (delta > minCachedDeltaForWarning)
3224 WARNING_LOG(LOGIDENTIFIER, "cached time is ", delta, " seconds off of media time when paused");
3226 return m_cachedTime;
3229 // Is it too soon use a cached time?
3230 MonotonicTime now = MonotonicTime::now();
3231 double maximumDurationToCacheMediaTime = m_player->maximumDurationToCacheMediaTime();
3233 if (maximumDurationToCacheMediaTime && m_cachedTime.isValid() && !m_paused && now > m_minimumClockTimeToUpdateCachedTime) {
3234 Seconds clockDelta = now - m_clockTimeAtLastCachedTimeUpdate;
3236 // Not too soon, use the cached time only if it hasn't expired.
3237 if (clockDelta.seconds() < maximumDurationToCacheMediaTime) {
3238 MediaTime adjustedCacheTime = m_cachedTime + MediaTime::createWithDouble(effectivePlaybackRate() * clockDelta.seconds());
3240 #if LOG_CACHED_TIME_WARNINGS
3241 MediaTime delta = adjustedCacheTime - m_player->currentTime();
3242 if (delta > minCachedDeltaForWarning)
3243 WARNING_LOG(LOGIDENTIFIER, "cached time is ", delta, " seconds off of media time when playing");
3245 return adjustedCacheTime;
3249 #if LOG_CACHED_TIME_WARNINGS
3250 if (maximumDurationToCacheMediaTime && now > m_minimumClockTimeToUpdateCachedTime && m_cachedTime != MediaPlayer::invalidTime()) {
3251 Seconds clockDelta = now - m_clockTimeAtLastCachedTimeUpdate;
3252 MediaTime delta = m_cachedTime + MediaTime::createWithDouble(effectivePlaybackRate() * clockDelta.seconds()) - m_player->currentTime();
3253 WARNING_LOG(LOGIDENTIFIER, "cached time was ", delta, " seconds off of media time when it expired");
3257 refreshCachedTime();
3259 if (m_cachedTime.isInvalid())
3260 return MediaTime::zeroTime();
3262 return m_cachedTime;
3265 void HTMLMediaElement::setCurrentTime(double time)
3267 setCurrentTime(MediaTime::createWithDouble(time));
3270 void HTMLMediaElement::setCurrentTimeWithTolerance(double time, double toleranceBefore, double toleranceAfter)
3272 seekWithTolerance(MediaTime::createWithDouble(time), MediaTime::createWithDouble(toleranceBefore), MediaTime::createWithDouble(toleranceAfter), true);
3275 void HTMLMediaElement::setCurrentTime(const MediaTime& time)
3277 if (m_mediaController)
3283 ExceptionOr<void> HTMLMediaElement::setCurrentTimeForBindings(double time)
3285 if (m_mediaController)
3286 return Exception { InvalidStateError };
3287 seek(MediaTime::createWithDouble(time));
3291 double HTMLMediaElement::duration() const
3293 return durationMediaTime().toDouble();
3296 MediaTime HTMLMediaElement::durationMediaTime() const
3298 if (m_player && m_readyState >= HAVE_METADATA)
3299 return m_player->duration();
3301 return MediaTime::invalidTime();
3304 bool HTMLMediaElement::paused() const
3306 // As of this writing, JavaScript garbage collection calls this function directly. In the past
3307 // we had problems where this was called on an object after a bad cast. The assertion below
3308 // made our regression test detect the problem, so we should keep it because of that. But note
3309 // that the value of the assertion relies on the compiler not being smart enough to know that
3310 // isHTMLUnknownElement is guaranteed to return false for an HTMLMediaElement.
3311 ASSERT(!isHTMLUnknownElement());
3316 double HTMLMediaElement::defaultPlaybackRate() const
3318 #if ENABLE(MEDIA_STREAM)
3319 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3320 // "defaultPlaybackRate" - On setting: ignored. On getting: return 1.0
3321 // A MediaStream is not seekable. Therefore, this attribute must always have the
3322 // value 1.0 and any attempt to alter it must be ignored. Note that this also means
3323 // that the ratechange event will not fire.
3324 if (m_mediaStreamSrcObject)
3328 return m_defaultPlaybackRate;
3331 void HTMLMediaElement::setDefaultPlaybackRate(double rate)
3333 #if ENABLE(MEDIA_STREAM)
3334 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3335 // "defaultPlaybackRate" - On setting: ignored. On getting: return 1.0
3336 // A MediaStream is not seekable. Therefore, this attribute must always have the
3337 // value 1.0 and any attempt to alter it must be ignored. Note that this also means
3338 // that the ratechange event will not fire.
3339 if (m_mediaStreamSrcObject)
3343 if (m_defaultPlaybackRate == rate)
3346 ALWAYS_LOG(LOGIDENTIFIER, rate);
3347 m_defaultPlaybackRate = rate;
3348 scheduleEvent(eventNames().ratechangeEvent);
3351 double HTMLMediaElement::effectivePlaybackRate() const
3353 return m_mediaController ? m_mediaController->playbackRate() : m_reportedPlaybackRate;
3356 double HTMLMediaElement::requestedPlaybackRate() const
3358 return m_mediaController ? m_mediaController->playbackRate() : m_requestedPlaybackRate;
3361 double HTMLMediaElement::playbackRate() const
3363 #if ENABLE(MEDIA_STREAM)
3364 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3365 // "playbackRate" - A MediaStream is not seekable. Therefore, this attribute must always
3366 // have the value 1.0 and any attempt to alter it must be ignored. Note that this also
3367 // means that the ratechange event will not fire.
3368 if (m_mediaStreamSrcObject)
3372 return m_requestedPlaybackRate;
3375 void HTMLMediaElement::setPlaybackRate(double rate)
3377 ALWAYS_LOG(LOGIDENTIFIER, rate);
3379 #if ENABLE(MEDIA_STREAM)
3380 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3381 // "playbackRate" - A MediaStream is not seekable. Therefore, this attribute must always
3382 // have the value 1.0 and any attempt to alter it must be ignored. Note that this also
3383 // means that the ratechange event will not fire.
3384 if (m_mediaStreamSrcObject)
3388 if (m_player && potentiallyPlaying() && m_player->rate() != rate && !m_mediaController)
3389 m_player->setRate(rate);
3391 if (m_requestedPlaybackRate != rate) {
3392 m_reportedPlaybackRate = m_requestedPlaybackRate = rate;
3393 invalidateCachedTime();
3394 scheduleEvent(eventNames().ratechangeEvent);
3398 void HTMLMediaElement::updatePlaybackRate()
3400 double requestedRate = requestedPlaybackRate();
3401 if (m_player && potentiallyPlaying() && m_player->rate() != requestedRate)
3402 m_player->setRate(requestedRate);
3405 bool HTMLMediaElement::webkitPreservesPitch() const
3407 return m_webkitPreservesPitch;
3410 void HTMLMediaElement::setWebkitPreservesPitch(bool preservesPitch)
3412 INFO_LOG(LOGIDENTIFIER, preservesPitch);
3414 m_webkitPreservesPitch = preservesPitch;
3419 m_player->setPreservesPitch(preservesPitch);
3422 bool HTMLMediaElement::ended() const
3424 #if ENABLE(MEDIA_STREAM)
3425 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3426 // When the MediaStream state moves from the active to the inactive state, the User Agent
3427 // must raise an ended event on the HTMLMediaElement and set its ended attribute to true.
3428 if (m_mediaStreamSrcObject && m_player && m_player->ended())
3432 // 4.8.10.8 Playing the media resource
3433 // The ended attribute must return true if the media element has ended
3434 // playback and the direction of playback is forwards, and false otherwise.
3435 return endedPlayback() && requestedPlaybackRate() > 0;
3438 bool HTMLMediaElement::autoplay() const
3440 return hasAttributeWithoutSynchronization(autoplayAttr);
3443 String HTMLMediaElement::preload() const
3445 #if ENABLE(MEDIA_STREAM)
3446 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3447 // "preload" - On getting: none. On setting: ignored.
3448 if (m_mediaStreamSrcObject)
3452 switch (m_preload) {
3453 case MediaPlayer::None:
3455 case MediaPlayer::MetaData:
3456 return "metadata"_s;
3457 case MediaPlayer::Auto: