2 * Copyright (C) 2007-2015 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.
30 #include "HTMLMediaElement.h"
32 #include "ApplicationCacheHost.h"
33 #include "ApplicationCacheResource.h"
34 #include "Attribute.h"
35 #include "CSSPropertyNames.h"
36 #include "CSSValueKeywords.h"
37 #include "ChromeClient.h"
38 #include "ClientRect.h"
39 #include "ClientRectList.h"
40 #include "ContentSecurityPolicy.h"
41 #include "ContentType.h"
42 #include "CookieJar.h"
43 #include "DiagnosticLoggingClient.h"
44 #include "DiagnosticLoggingKeys.h"
45 #include "DisplaySleepDisabler.h"
47 #include "DocumentLoader.h"
48 #include "ElementIterator.h"
49 #include "EventNames.h"
50 #include "ExceptionCodePlaceholder.h"
51 #include "FrameLoader.h"
52 #include "FrameLoaderClient.h"
53 #include "FrameView.h"
54 #include "HTMLParserIdioms.h"
55 #include "HTMLSourceElement.h"
56 #include "HTMLVideoElement.h"
57 #include "JSDOMError.h"
58 #include "JSHTMLMediaElement.h"
61 #include "MIMETypeRegistry.h"
62 #include "MainFrame.h"
63 #include "MediaController.h"
64 #include "MediaControls.h"
65 #include "MediaDocument.h"
66 #include "MediaError.h"
67 #include "MediaFragmentURIParser.h"
68 #include "MediaKeyEvent.h"
69 #include "MediaList.h"
70 #include "MediaPlayer.h"
71 #include "MediaQueryEvaluator.h"
72 #include "MediaResourceLoader.h"
73 #include "MemoryPressureHandler.h"
74 #include "NetworkingContext.h"
75 #include "NoEventDispatchAssertion.h"
77 #include "PageGroup.h"
78 #include "PageThrottler.h"
79 #include "PlatformMediaSessionManager.h"
80 #include "ProgressTracker.h"
81 #include "RenderLayerCompositor.h"
82 #include "RenderVideo.h"
83 #include "RenderView.h"
84 #include "ResourceLoadInfo.h"
85 #include "ScriptController.h"
86 #include "ScriptSourceCode.h"
87 #include "SecurityPolicy.h"
88 #include "SessionID.h"
90 #include "ShadowRoot.h"
91 #include "TimeRanges.h"
92 #include "UserContentController.h"
93 #include "UserGestureIndicator.h"
95 #include <runtime/Uint8Array.h>
96 #include <wtf/CurrentTime.h>
97 #include <wtf/MathExtras.h>
99 #include <wtf/text/CString.h>
101 #if ENABLE(VIDEO_TRACK)
102 #include "AudioTrackList.h"
103 #include "HTMLTrackElement.h"
104 #include "InbandGenericTextTrack.h"
105 #include "InbandTextTrackPrivate.h"
106 #include "InbandWebVTTTextTrack.h"
107 #include "RuntimeEnabledFeatures.h"
108 #include "TextTrackCueList.h"
109 #include "TextTrackList.h"
110 #include "VideoTrackList.h"
113 #if ENABLE(WEB_AUDIO)
114 #include "AudioSourceProvider.h"
115 #include "MediaElementAudioSourceNode.h"
118 #if PLATFORM(IOS) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
119 #include "WebVideoFullscreenInterface.h"
123 #include "RuntimeApplicationChecks.h"
124 #include "WebVideoFullscreenInterfaceAVKit.h"
127 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
128 #include "WebKitPlaybackTargetAvailabilityEvent.h"
131 #if ENABLE(MEDIA_SESSION)
132 #include "MediaSession.h"
135 #if ENABLE(MEDIA_SOURCE)
136 #include "DOMWindow.h"
137 #include "MediaSource.h"
138 #include "Performance.h"
139 #include "VideoPlaybackQuality.h"
142 #if ENABLE(MEDIA_STREAM)
144 #include "MediaStream.h"
145 #include "MediaStreamRegistry.h"
148 #if ENABLE(ENCRYPTED_MEDIA_V2)
149 #include "MediaKeyNeededEvent.h"
150 #include "MediaKeys.h"
153 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
154 #include "JSMediaControlsHost.h"
155 #include "MediaControlsHost.h"
156 #include <bindings/ScriptObject.h>
161 static const double SeekRepeatDelay = 0.1;
162 static const double SeekTime = 0.2;
163 static const double ScanRepeatDelay = 1.5;
164 static const double ScanMaximumRate = 8;
166 static void setFlags(unsigned& value, unsigned flags)
171 static void clearFlags(unsigned& value, unsigned flags)
177 static String urlForLoggingMedia(const URL& url)
179 static const unsigned maximumURLLengthForLogging = 128;
181 if (url.string().length() < maximumURLLengthForLogging)
183 return url.string().substring(0, maximumURLLengthForLogging) + "...";
186 static const char* boolString(bool val)
188 return val ? "true" : "false";
191 static String actionName(HTMLMediaElementEnums::DelayedActionType action)
193 StringBuilder actionBuilder;
195 #define ACTION(_actionType) \
196 if (action & (HTMLMediaElementEnums::_actionType)) { \
197 if (!actionBuilder.isEmpty()) \
198 actionBuilder.append(", "); \
199 actionBuilder.append(#_actionType); \
202 ACTION(LoadMediaResource);
203 ACTION(ConfigureTextTracks);
204 ACTION(TextTrackChangesNotification);
205 ACTION(ConfigureTextTrackDisplay);
206 ACTION(CheckPlaybackTargetCompatablity);
207 ACTION(CheckMediaState);
208 ACTION(MediaEngineUpdated);
210 return actionBuilder.toString();
217 #ifndef LOG_MEDIA_EVENTS
218 // Default to not logging events because so many are generated they can overwhelm the rest of
220 #define LOG_MEDIA_EVENTS 0
223 #ifndef LOG_CACHED_TIME_WARNINGS
224 // Default to not logging warnings about excessive drift in the cached media time because it adds a
225 // fair amount of overhead and logging.
226 #define LOG_CACHED_TIME_WARNINGS 0
229 #if ENABLE(MEDIA_SOURCE)
230 // URL protocol used to signal that the media source API is being used.
231 static const char* mediaSourceBlobProtocol = "blob";
234 #if ENABLE(MEDIA_STREAM)
235 // URL protocol used to signal that the media stream API is being used.
236 static const char* mediaStreamBlobProtocol = "blob";
239 using namespace HTMLNames;
241 typedef HashMap<Document*, HashSet<HTMLMediaElement*>> DocumentElementSetMap;
242 static DocumentElementSetMap& documentToElementSetMap()
244 static NeverDestroyed<DocumentElementSetMap> map;
248 static void addElementToDocumentMap(HTMLMediaElement& element, Document& document)
250 DocumentElementSetMap& map = documentToElementSetMap();
251 HashSet<HTMLMediaElement*> set = map.take(&document);
253 map.add(&document, set);
256 static void removeElementFromDocumentMap(HTMLMediaElement& element, Document& document)
258 DocumentElementSetMap& map = documentToElementSetMap();
259 HashSet<HTMLMediaElement*> set = map.take(&document);
260 set.remove(&element);
262 map.add(&document, set);
265 #if ENABLE(ENCRYPTED_MEDIA)
266 static ExceptionCode exceptionCodeForMediaKeyException(MediaPlayer::MediaKeyException exception)
269 case MediaPlayer::NoError:
271 case MediaPlayer::InvalidPlayerState:
272 return INVALID_STATE_ERR;
273 case MediaPlayer::KeySystemNotSupported:
274 return NOT_SUPPORTED_ERR;
277 ASSERT_NOT_REACHED();
278 return INVALID_STATE_ERR;
282 #if ENABLE(VIDEO_TRACK)
283 class TrackDisplayUpdateScope {
285 TrackDisplayUpdateScope(HTMLMediaElement* mediaElement)
287 m_mediaElement = mediaElement;
288 m_mediaElement->beginIgnoringTrackDisplayUpdateRequests();
290 ~TrackDisplayUpdateScope()
292 ASSERT(m_mediaElement);
293 m_mediaElement->endIgnoringTrackDisplayUpdateRequests();
297 HTMLMediaElement* m_mediaElement;
301 struct HTMLMediaElement::TrackGroup {
302 enum GroupKind { CaptionsAndSubtitles, Description, Chapter, Metadata, Other };
304 TrackGroup(GroupKind kind)
312 Vector<RefPtr<TextTrack>> tracks;
313 RefPtr<TextTrack> visibleTrack;
314 RefPtr<TextTrack> defaultTrack;
319 HashSet<HTMLMediaElement*>& HTMLMediaElement::allMediaElements()
321 static NeverDestroyed<HashSet<HTMLMediaElement*>> elements;
325 #if ENABLE(MEDIA_SESSION)
326 typedef HashMap<uint64_t, HTMLMediaElement*> IDToElementMap;
328 static IDToElementMap& elementIDsToElements()
330 static NeverDestroyed<IDToElementMap> map;
334 HTMLMediaElement* HTMLMediaElement::elementWithID(uint64_t id)
336 if (id == HTMLMediaElementInvalidID)
339 return elementIDsToElements().get(id);
342 static uint64_t nextElementID()
344 static uint64_t elementID = 0;
349 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document, bool createdByParser)
350 : HTMLElement(tagName, document)
351 , ActiveDOMObject(&document)
352 , m_pendingActionTimer(*this, &HTMLMediaElement::pendingActionTimerFired)
353 , m_progressEventTimer(*this, &HTMLMediaElement::progressEventTimerFired)
354 , m_playbackProgressTimer(*this, &HTMLMediaElement::playbackProgressTimerFired)
355 , m_scanTimer(*this, &HTMLMediaElement::scanTimerFired)
356 , m_playedTimeRanges()
357 , m_asyncEventQueue(*this)
358 , m_requestedPlaybackRate(1)
359 , m_reportedPlaybackRate(1)
360 , m_defaultPlaybackRate(1)
361 , m_webkitPreservesPitch(true)
362 , m_networkState(NETWORK_EMPTY)
363 , m_readyState(HAVE_NOTHING)
364 , m_readyStateMaximum(HAVE_NOTHING)
366 , m_volumeInitialized(false)
367 , m_previousProgressTime(std::numeric_limits<double>::max())
368 , m_clockTimeAtLastUpdateEvent(0)
369 , m_lastTimeUpdateEventMovieTime(MediaTime::positiveInfiniteTime())
370 , m_loadState(WaitingForSource)
371 , m_videoFullscreenMode(VideoFullscreenModeNone)
372 #if PLATFORM(IOS) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
373 , m_videoFullscreenGravity(MediaPlayer::VideoGravityResizeAspect)
375 , m_preload(MediaPlayer::Auto)
376 , m_displayMode(Unknown)
377 , m_processingMediaPlayerCallback(0)
378 #if ENABLE(MEDIA_SOURCE)
379 , m_droppedVideoFrames(0)
381 , m_clockTimeAtLastCachedTimeUpdate(0)
382 , m_minimumClockTimeToUpdateCachedTime(0)
383 , m_pendingActionFlags(0)
384 , m_actionAfterScan(Nothing)
386 , m_scanDirection(Forward)
387 , m_firstTimePlaying(true)
389 , m_isWaitingUntilMediaCanStart(false)
390 , m_shouldDelayLoadEvent(false)
391 , m_haveFiredLoadedData(false)
392 , m_inActiveDocument(true)
393 , m_autoplaying(true)
395 , m_explicitlyMuted(false)
396 , m_initiallyMuted(false)
399 , m_sentStalledEvent(false)
400 , m_sentEndEvent(false)
401 , m_pausedInternal(false)
402 , m_sendProgressEvents(true)
403 , m_closedCaptionsVisible(false)
404 , m_webkitLegacyClosedCaptionOverride(false)
405 , m_completelyLoaded(false)
406 , m_havePreparedToPlay(false)
407 , m_parsingInProgress(createdByParser)
408 , m_elementIsHidden(document.hidden())
409 , m_creatingControls(false)
410 , m_receivedLayoutSizeChanged(false)
411 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
412 , m_mediaControlsDependOnPageScaleFactor(false)
413 , m_haveSetUpCaptionContainer(false)
415 #if ENABLE(VIDEO_TRACK)
416 , m_tracksAreReady(true)
417 , m_haveVisibleTextTrack(false)
418 , m_processingPreferenceChange(false)
419 , m_lastTextTrackUpdateTime(MediaTime(-1, 1))
420 , m_captionDisplayMode(CaptionUserPreferences::Automatic)
424 , m_ignoreTrackDisplayUpdate(0)
426 #if ENABLE(WEB_AUDIO)
427 , m_audioSourceNode(0)
429 , m_mediaSession(std::make_unique<MediaElementSession>(*this))
430 , m_reportedExtraMemoryCost(0)
431 #if ENABLE(MEDIA_STREAM)
432 , m_mediaStreamSrcObject(nullptr)
435 allMediaElements().add(this);
437 LOG(Media, "HTMLMediaElement::HTMLMediaElement(%p)", this);
438 setHasCustomStyleResolveCallbacks();
440 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForFullscreen);
441 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePageConsentToLoadMedia);
442 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
443 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToAutoplayToExternalDevice);
445 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager);
446 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePlaybackToControlControlsManager);
448 Settings* settings = document.settings();
450 m_sendProgressEvents = false;
453 if (settings && settings->invisibleAutoplayNotPermitted())
454 m_mediaSession->addBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted);
456 if (document.ownerElement() || !document.isMediaDocument()) {
457 if (settings && settings->videoPlaybackRequiresUserGesture()) {
458 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForVideoRateChange);
459 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForLoad);
462 if (settings && settings->audioPlaybackRequiresUserGesture())
463 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForAudioRateChange);
465 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
466 if (settings && (settings->videoPlaybackRequiresUserGesture() || settings->audioPlaybackRequiresUserGesture()))
467 m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToShowPlaybackTargetPicker);
470 if (!settings || !settings->mediaDataLoadsAutomatically())
471 m_mediaSession->addBehaviorRestriction(MediaElementSession::AutoPreloadingNotPermitted);
473 if (settings && settings->mainContentUserGestureOverrideEnabled())
474 m_mediaSession->addBehaviorRestriction(MediaElementSession::OverrideUserGestureRequirementForMainContent);
478 if (settings && !settings->videoPlaybackRequiresUserGesture() && !settings->audioPlaybackRequiresUserGesture()) {
479 // Relax RequireUserGestureForFullscreen when videoPlaybackRequiresUserGesture and audioPlaybackRequiresUserGesture is not set:
480 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequireUserGestureForFullscreen);
484 #if ENABLE(VIDEO_TRACK)
486 m_captionDisplayMode = document.page()->group().captionPreferences().captionDisplayMode();
489 #if ENABLE(MEDIA_SESSION)
490 m_elementID = nextElementID();
491 elementIDsToElements().add(m_elementID, this);
493 setSessionInternal(document.defaultMediaSession());
496 registerWithDocument(document);
499 HTMLMediaElement::~HTMLMediaElement()
501 LOG(Media, "HTMLMediaElement::~HTMLMediaElement(%p)", this);
503 beginIgnoringTrackDisplayUpdateRequests();
504 allMediaElements().remove(this);
506 m_asyncEventQueue.close();
508 setShouldDelayLoadEvent(false);
509 unregisterWithDocument(document());
511 #if ENABLE(VIDEO_TRACK)
513 m_audioTracks->clearElement();
515 m_textTracks->clearElement();
517 m_videoTracks->clearElement();
520 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
521 if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) {
522 m_hasPlaybackTargetAvailabilityListeners = false;
523 m_mediaSession->setHasPlaybackTargetAvailabilityListeners(*this, false);
528 if (m_mediaController) {
529 m_mediaController->removeMediaElement(this);
530 m_mediaController = nullptr;
533 #if ENABLE(MEDIA_SOURCE)
537 #if ENABLE(ENCRYPTED_MEDIA_V2)
541 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
543 m_isolatedWorld->clearWrappers();
546 #if ENABLE(MEDIA_SESSION)
548 m_session->removeMediaElement(*this);
552 elementIDsToElements().remove(m_elementID);
555 m_seekTaskQueue.close();
556 m_promiseTaskQueue.close();
557 m_pauseAfterDetachedTaskQueue.close();
558 m_updatePlaybackControlsManagerQueue.close();
560 m_completelyLoaded = true;
563 updatePlaybackControlsManager();
566 void HTMLMediaElement::registerWithDocument(Document& document)
568 m_mediaSession->registerWithDocument(document);
570 if (m_isWaitingUntilMediaCanStart)
571 document.addMediaCanStartListener(this);
574 document.registerForMediaVolumeCallbacks(this);
575 document.registerForPrivateBrowsingStateChangedCallbacks(this);
578 document.registerForVisibilityStateChangedCallbacks(this);
580 #if ENABLE(VIDEO_TRACK)
581 if (m_requireCaptionPreferencesChangedCallbacks)
582 document.registerForCaptionPreferencesChangedCallbacks(this);
585 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
586 if (m_mediaControlsDependOnPageScaleFactor)
587 document.registerForPageScaleFactorChangedCallbacks(this);
588 document.registerForUserInterfaceLayoutDirectionChangedCallbacks(*this);
591 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
592 document.registerForDocumentSuspensionCallbacks(this);
595 document.registerForAllowsMediaDocumentInlinePlaybackChangedCallbacks(*this);
597 document.addAudioProducer(this);
598 addElementToDocumentMap(*this, document);
601 void HTMLMediaElement::unregisterWithDocument(Document& document)
603 m_mediaSession->unregisterWithDocument(document);
605 if (m_isWaitingUntilMediaCanStart)
606 document.removeMediaCanStartListener(this);
609 document.unregisterForMediaVolumeCallbacks(this);
610 document.unregisterForPrivateBrowsingStateChangedCallbacks(this);
613 document.unregisterForVisibilityStateChangedCallbacks(this);
615 #if ENABLE(VIDEO_TRACK)
616 if (m_requireCaptionPreferencesChangedCallbacks)
617 document.unregisterForCaptionPreferencesChangedCallbacks(this);
620 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
621 if (m_mediaControlsDependOnPageScaleFactor)
622 document.unregisterForPageScaleFactorChangedCallbacks(this);
623 document.unregisterForUserInterfaceLayoutDirectionChangedCallbacks(*this);
626 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
627 document.unregisterForDocumentSuspensionCallbacks(this);
630 document.unregisterForAllowsMediaDocumentInlinePlaybackChangedCallbacks(*this);
632 document.removeAudioProducer(this);
633 removeElementFromDocumentMap(*this, document);
636 void HTMLMediaElement::didMoveToNewDocument(Document* oldDocument)
638 if (m_shouldDelayLoadEvent) {
640 oldDocument->decrementLoadEventDelayCount();
641 document().incrementLoadEventDelayCount();
645 unregisterWithDocument(*oldDocument);
647 registerWithDocument(document());
649 HTMLElement::didMoveToNewDocument(oldDocument);
650 updateShouldAutoplay();
653 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
654 void HTMLMediaElement::prepareForDocumentSuspension()
656 m_mediaSession->unregisterWithDocument(document());
659 void HTMLMediaElement::resumeFromDocumentSuspension()
661 m_mediaSession->registerWithDocument(document());
662 updateShouldAutoplay();
666 bool HTMLMediaElement::supportsFocus() const
668 if (document().isMediaDocument())
671 // If no controls specified, we should still be able to focus the element if it has tabIndex.
672 return controls() || HTMLElement::supportsFocus();
675 bool HTMLMediaElement::isMouseFocusable() const
680 void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
682 if (name == srcAttr) {
684 // Note, unless the restriction on requiring user action has been removed,
685 // do not begin downloading data on iOS.
686 if (!value.isNull() && m_mediaSession->dataLoadingPermitted(*this))
688 // Trigger a reload, as long as the 'src' attribute is present.
692 clearMediaPlayer(LoadMediaResource);
693 scheduleDelayedAction(LoadMediaResource);
695 } else if (name == controlsAttr)
696 configureMediaControls();
697 else if (name == loopAttr)
698 updateSleepDisabling();
699 else if (name == preloadAttr) {
700 if (equalLettersIgnoringASCIICase(value, "none"))
701 m_preload = MediaPlayer::None;
702 else if (equalLettersIgnoringASCIICase(value, "metadata"))
703 m_preload = MediaPlayer::MetaData;
705 // The spec does not define an "invalid value default" but "auto" is suggested as the
706 // "missing value default", so use it for everything except "none" and "metadata"
707 m_preload = MediaPlayer::Auto;
710 // The attribute must be ignored if the autoplay attribute is present
711 if (!autoplay() && m_player)
712 m_player->setPreload(m_mediaSession->effectivePreloadForElement(*this));
714 } else if (name == mediagroupAttr)
715 setMediaGroup(value);
717 HTMLElement::parseAttribute(name, value);
720 void HTMLMediaElement::finishParsingChildren()
722 HTMLElement::finishParsingChildren();
723 m_parsingInProgress = false;
725 #if ENABLE(VIDEO_TRACK)
726 if (descendantsOfType<HTMLTrackElement>(*this).first())
727 scheduleDelayedAction(ConfigureTextTracks);
731 bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style)
733 return controls() && HTMLElement::rendererIsNeeded(style);
736 RenderPtr<RenderElement> HTMLMediaElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
738 return createRenderer<RenderMedia>(*this, WTFMove(style));
741 bool HTMLMediaElement::childShouldCreateRenderer(const Node& child) const
743 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
744 return hasShadowRootParent(child) && HTMLElement::childShouldCreateRenderer(child);
746 if (!hasMediaControls())
748 // <media> doesn't allow its content, including shadow subtree, to
749 // be rendered. So this should return false for most of the children.
750 // One exception is a shadow tree built for rendering controls which should be visible.
751 // So we let them go here by comparing its subtree root with one of the controls.
752 return &mediaControls()->treeScope() == &child.treeScope()
753 && hasShadowRootParent(child)
754 && HTMLElement::childShouldCreateRenderer(child);
758 Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode& insertionPoint)
760 LOG(Media, "HTMLMediaElement::insertedInto(%p)", this);
762 HTMLElement::insertedInto(insertionPoint);
763 if (insertionPoint.inDocument()) {
764 m_inActiveDocument = true;
767 if (m_networkState == NETWORK_EMPTY && !attributeWithoutSynchronization(srcAttr).isEmpty() && m_mediaSession->dataLoadingPermitted(*this))
769 if (m_networkState == NETWORK_EMPTY && !attributeWithoutSynchronization(srcAttr).isEmpty())
771 scheduleDelayedAction(LoadMediaResource);
774 if (!m_explicitlyMuted) {
775 m_explicitlyMuted = true;
776 m_muted = hasAttributeWithoutSynchronization(mutedAttr);
779 configureMediaControls();
780 return InsertionDone;
783 void HTMLMediaElement::pauseAfterDetachedTask()
785 // If we were re-inserted into an active document, no need to pause.
786 if (m_inActiveDocument)
789 if (hasMediaControls())
790 mediaControls()->hide();
791 if (m_networkState > NETWORK_EMPTY)
793 if (m_videoFullscreenMode != VideoFullscreenModeNone)
799 size_t extraMemoryCost = m_player->extraMemoryCost();
800 if (extraMemoryCost > m_reportedExtraMemoryCost) {
801 JSC::VM& vm = JSDOMWindowBase::commonVM();
802 JSC::JSLockHolder lock(vm);
804 size_t extraMemoryCostDelta = extraMemoryCost - m_reportedExtraMemoryCost;
805 m_reportedExtraMemoryCost = extraMemoryCost;
806 // FIXME: Adopt reportExtraMemoryVisited, and switch to reportExtraMemoryAllocated.
807 // https://bugs.webkit.org/show_bug.cgi?id=142595
808 vm.heap.deprecatedReportExtraMemory(extraMemoryCostDelta);
812 void HTMLMediaElement::removedFrom(ContainerNode& insertionPoint)
814 LOG(Media, "HTMLMediaElement::removedFrom(%p)", this);
816 m_inActiveDocument = false;
817 if (insertionPoint.inDocument()) {
818 // Pause asynchronously to let the operation that removed us finish, in case we get inserted back into a document.
819 m_pauseAfterDetachedTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::pauseAfterDetachedTask, this));
822 HTMLElement::removedFrom(insertionPoint);
825 void HTMLMediaElement::willAttachRenderers()
830 inline void HTMLMediaElement::updateRenderer()
832 if (auto* renderer = this->renderer())
833 renderer->updateFromElement();
836 void HTMLMediaElement::didAttachRenderers()
838 if (auto* renderer = this->renderer()) {
839 renderer->updateFromElement();
840 if (m_mediaSession->hasBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted)
841 || m_mediaSession->hasBehaviorRestriction(MediaElementSession::OverrideUserGestureRequirementForMainContent))
842 renderer->registerForVisibleInViewportCallback();
844 updateShouldAutoplay();
847 void HTMLMediaElement::willDetachRenderers()
849 if (auto* renderer = this->renderer())
850 renderer->unregisterForVisibleInViewportCallback();
853 void HTMLMediaElement::didDetachRenderers()
855 updateShouldAutoplay();
858 void HTMLMediaElement::didRecalcStyle(Style::Change)
863 void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType)
865 LOG(Media, "HTMLMediaElement::scheduleDelayedAction(%p) - setting %s flag", this, actionName(actionType).utf8().data());
867 if ((actionType & LoadMediaResource) && !(m_pendingActionFlags & LoadMediaResource)) {
869 setFlags(m_pendingActionFlags, LoadMediaResource);
872 #if ENABLE(VIDEO_TRACK)
873 if (actionType & ConfigureTextTracks)
874 setFlags(m_pendingActionFlags, ConfigureTextTracks);
877 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
878 if (actionType & CheckPlaybackTargetCompatablity)
879 setFlags(m_pendingActionFlags, CheckPlaybackTargetCompatablity);
882 if (actionType & CheckMediaState)
883 setFlags(m_pendingActionFlags, CheckMediaState);
885 if (actionType & MediaEngineUpdated)
886 setFlags(m_pendingActionFlags, MediaEngineUpdated);
888 if (actionType & UpdatePlayState)
889 setFlags(m_pendingActionFlags, UpdatePlayState);
891 m_pendingActionTimer.startOneShot(0);
894 void HTMLMediaElement::scheduleNextSourceChild()
896 // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
897 LOG(Media, "HTMLMediaElement::scheduleNextSourceChild(%p) - setting %s flag", this, actionName(LoadMediaResource).utf8().data());
898 setFlags(m_pendingActionFlags, LoadMediaResource);
899 m_pendingActionTimer.startOneShot(0);
902 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
905 LOG(Media, "HTMLMediaElement::scheduleEvent(%p) - scheduling '%s'", this, eventName.string().ascii().data());
907 RefPtr<Event> event = Event::create(eventName, false, true);
909 // Don't set the event target, the event queue will set it in GenericEventQueue::timerFired and setting it here
910 // will trigger an ASSERT if this element has been marked for deletion.
912 m_asyncEventQueue.enqueueEvent(WTFMove(event));
915 void HTMLMediaElement::scheduleResolvePendingPlayPromises()
917 m_promiseTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::resolvePendingPlayPromises, this));
920 void HTMLMediaElement::rejectPendingPlayPromises(DOMError& error)
922 Vector<PlayPromise> pendingPlayPromises = WTFMove(m_pendingPlayPromises);
924 for (auto& promise : pendingPlayPromises)
925 promise.reject(error);
928 void HTMLMediaElement::resolvePendingPlayPromises()
930 Vector<PlayPromise> pendingPlayPromises = WTFMove(m_pendingPlayPromises);
932 for (auto& promise : pendingPlayPromises)
933 promise.resolve(nullptr);
936 void HTMLMediaElement::scheduleNotifyAboutPlaying()
938 m_promiseTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::notifyAboutPlaying, this));
941 void HTMLMediaElement::notifyAboutPlaying()
943 dispatchEvent(Event::create(eventNames().playingEvent, false, true));
944 resolvePendingPlayPromises();
947 void HTMLMediaElement::pendingActionTimerFired()
949 Ref<HTMLMediaElement> protectedThis(*this); // loadNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations.
950 PendingActionFlags pendingActions = m_pendingActionFlags;
951 m_pendingActionFlags = 0;
953 #if ENABLE(VIDEO_TRACK)
954 if (pendingActions & ConfigureTextTracks)
955 configureTextTracks();
958 if (pendingActions & LoadMediaResource) {
959 if (m_loadState == LoadingFromSourceElement)
960 loadNextSourceChild();
965 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
966 if (pendingActions & CheckPlaybackTargetCompatablity && m_isPlayingToWirelessTarget && !m_player->canPlayToWirelessPlaybackTarget()) {
967 LOG(Media, "HTMLMediaElement::pendingActionTimerFired(%p) - calling setShouldPlayToPlaybackTarget(false)", this);
968 m_failedToPlayToWirelessTarget = true;
969 m_player->setShouldPlayToPlaybackTarget(false);
972 if (pendingActions & CheckMediaState)
976 if (pendingActions & MediaEngineUpdated)
977 mediaEngineWasUpdated();
979 if (pendingActions & UpdatePlayState)
983 MediaError* HTMLMediaElement::error() const
985 return m_error.get();
988 void HTMLMediaElement::setSrc(const String& url)
990 setAttributeWithoutSynchronization(srcAttr, url);
993 #if ENABLE(MEDIA_STREAM)
994 void HTMLMediaElement::setSrcObject(ScriptExecutionContext& context, MediaStream* mediaStream)
996 // FIXME: Setting the srcObject attribute may cause other changes to the media element's internal state:
997 // Specifically, if srcObject is specified, the UA must use it as the source of media, even if the src
998 // attribute is also set or children are present. If the value of srcObject is replaced or set to null
999 // the UA must re-run the media element load algorithm.
1001 // https://bugs.webkit.org/show_bug.cgi?id=124896
1003 m_mediaStreamSrcObject = mediaStream;
1004 setSrc(DOMURL::createPublicURL(context, mediaStream));
1008 void HTMLMediaElement::setCrossOrigin(const AtomicString& value)
1010 setAttributeWithoutSynchronization(crossoriginAttr, value);
1013 String HTMLMediaElement::crossOrigin() const
1015 return parseCORSSettingsAttribute(attributeWithoutSynchronization(crossoriginAttr));
1018 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
1020 return m_networkState;
1023 String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySystem, const URL& url) const
1025 MediaEngineSupportParameters parameters;
1026 ContentType contentType(mimeType);
1027 parameters.type = contentType.type().convertToASCIILowercase();
1028 parameters.codecs = contentType.parameter(ASCIILiteral("codecs"));
1029 parameters.url = url;
1030 #if ENABLE(ENCRYPTED_MEDIA)
1031 parameters.keySystem = keySystem;
1033 UNUSED_PARAM(keySystem);
1035 MediaPlayer::SupportsType support = MediaPlayer::supportsType(parameters, this);
1041 case MediaPlayer::IsNotSupported:
1042 canPlay = emptyString();
1044 case MediaPlayer::MayBeSupported:
1045 canPlay = ASCIILiteral("maybe");
1047 case MediaPlayer::IsSupported:
1048 canPlay = ASCIILiteral("probably");
1052 LOG(Media, "HTMLMediaElement::canPlayType(%p) - [%s, %s, %s] -> %s", this, mimeType.utf8().data(), keySystem.utf8().data(), url.stringCenterEllipsizedToLength().utf8().data(), canPlay.utf8().data());
1057 double HTMLMediaElement::getStartDate() const
1059 return m_player->getStartDate().toDouble();
1062 void HTMLMediaElement::load()
1064 Ref<HTMLMediaElement> protectedThis(*this); // loadInternal may result in a 'beforeload' event, which can make arbitrary DOM mutations.
1066 LOG(Media, "HTMLMediaElement::load(%p)", this);
1068 if (!m_mediaSession->dataLoadingPermitted(*this))
1070 if (ScriptController::processingUserGestureForMedia())
1071 removeBehaviorsRestrictionsAfterFirstUserGesture();
1078 void HTMLMediaElement::prepareForLoad()
1080 LOG(Media, "HTMLMediaElement::prepareForLoad(%p)", this);
1082 // Perform the cleanup required for the resource load algorithm to run.
1083 stopPeriodicTimers();
1084 m_pendingActionTimer.stop();
1085 // FIXME: Figure out appropriate place to reset LoadTextTrackResource if necessary and set m_pendingActionFlags to 0 here.
1086 m_pendingActionFlags &= ~LoadMediaResource;
1087 m_sentEndEvent = false;
1088 m_sentStalledEvent = false;
1089 m_haveFiredLoadedData = false;
1090 m_completelyLoaded = false;
1091 m_havePreparedToPlay = false;
1092 m_displayMode = Unknown;
1093 m_currentSrc = URL();
1095 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
1096 m_failedToPlayToWirelessTarget = false;
1099 // 1 - Abort any already-running instance of the resource selection algorithm for this element.
1100 m_loadState = WaitingForSource;
1101 m_currentSourceNode = nullptr;
1103 // 2 - If there are any tasks from the media element's media element event task source in
1104 // one of the task queues, then remove those tasks.
1105 cancelPendingEventsAndCallbacks();
1107 // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
1108 // a task to fire a simple event named abort at the media element.
1109 if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
1110 scheduleEvent(eventNames().abortEvent);
1112 #if ENABLE(MEDIA_SOURCE)
1116 createMediaPlayer();
1118 // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
1119 if (m_networkState != NETWORK_EMPTY) {
1120 // 4.1 - Queue a task to fire a simple event named emptied at the media element.
1121 scheduleEvent(eventNames().emptiedEvent);
1123 // 4.2 - If a fetching process is in progress for the media element, the user agent should stop it.
1124 m_networkState = NETWORK_EMPTY;
1126 // 4.3 - Forget the media element's media-resource-specific tracks.
1127 forgetResourceSpecificTracks();
1129 // 4.4 - If readyState is not set to HAVE_NOTHING, then set it to that state.
1130 m_readyState = HAVE_NOTHING;
1131 m_readyStateMaximum = HAVE_NOTHING;
1133 // 4.5 - If the paused attribute is false, then set it to true.
1136 // 4.6 - If seeking is true, set it to false.
1139 // 4.7 - Set the current playback position to 0.
1140 // Set the official playback position to 0.
1141 // If this changed the official playback position, then queue a task to fire a simple event named timeupdate at the media element.
1142 // FIXME: Add support for firing this event. e.g., scheduleEvent(eventNames().timeUpdateEvent);
1144 // 4.8 - Set the initial playback position to 0.
1145 // FIXME: Make this less subtle. The position only becomes 0 because of the createMediaPlayer() call
1147 refreshCachedTime();
1149 invalidateCachedTime();
1151 // 4.9 - Set the timeline offset to Not-a-Number (NaN).
1152 // 4.10 - Update the duration attribute to Not-a-Number (NaN).
1154 updateMediaController();
1155 #if ENABLE(VIDEO_TRACK)
1156 updateActiveTextTrackCues(MediaTime::zeroTime());
1160 // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
1161 setPlaybackRate(defaultPlaybackRate());
1163 // 6 - Set the error attribute to null and the autoplaying flag to true.
1165 m_autoplaying = true;
1166 mediaSession().clientWillBeginAutoplaying();
1168 // 7 - Invoke the media element's resource selection algorithm.
1170 // 8 - Note: Playback of any previously playing media resource for this element stops.
1172 // The resource selection algorithm
1173 // 1 - Set the networkState to NETWORK_NO_SOURCE
1174 m_networkState = NETWORK_NO_SOURCE;
1176 // 2 - Asynchronously await a stable state.
1178 m_playedTimeRanges = TimeRanges::create();
1180 // FIXME: Investigate whether these can be moved into m_networkState != NETWORK_EMPTY block above
1181 // so they are closer to the relevant spec steps.
1182 m_lastSeekTime = MediaTime::zeroTime();
1184 // The spec doesn't say to block the load event until we actually run the asynchronous section
1185 // algorithm, but do it now because we won't start that until after the timer fires and the
1186 // event may have already fired by then.
1187 MediaPlayer::Preload effectivePreload = m_mediaSession->effectivePreloadForElement(*this);
1188 if (effectivePreload != MediaPlayer::None)
1189 setShouldDelayLoadEvent(true);
1192 if (effectivePreload != MediaPlayer::None && m_mediaSession->allowsAutomaticMediaDataLoading(*this))
1196 configureMediaControls();
1199 void HTMLMediaElement::loadInternal()
1201 LOG(Media, "HTMLMediaElement::loadInternal(%p)", this);
1203 // Some of the code paths below this function dispatch the BeforeLoad event. This ASSERT helps
1204 // us catch those bugs more quickly without needing all the branches to align to actually
1205 // trigger the event.
1206 ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden());
1208 // If we can't start a load right away, start it later.
1209 if (!m_mediaSession->pageAllowsDataLoading(*this)) {
1210 LOG(Media, "HTMLMediaElement::loadInternal(%p) - not allowed to load in background, waiting", this);
1211 setShouldDelayLoadEvent(false);
1212 if (m_isWaitingUntilMediaCanStart)
1214 m_isWaitingUntilMediaCanStart = true;
1215 document().addMediaCanStartListener(this);
1219 clearFlags(m_pendingActionFlags, LoadMediaResource);
1221 // Once the page has allowed an element to load media, it is free to load at will. This allows a
1222 // playlist that starts in a foreground tab to continue automatically if the tab is subsequently
1223 // put into the background.
1224 m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequirePageConsentToLoadMedia);
1226 #if ENABLE(VIDEO_TRACK)
1227 if (hasMediaControls())
1228 mediaControls()->changedClosedCaptionsVisibility();
1230 // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the
1231 // disabled state when the element's resource selection algorithm last started".
1232 m_textTracksWhenResourceSelectionBegan.clear();
1234 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
1235 TextTrack* track = m_textTracks->item(i);
1236 if (track->mode() != TextTrack::Mode::Disabled)
1237 m_textTracksWhenResourceSelectionBegan.append(track);
1242 selectMediaResource();
1245 void HTMLMediaElement::selectMediaResource()
1247 LOG(Media, "HTMLMediaElement::selectMediaResource(%p)", this);
1253 enum Mode { attribute, children };
1255 // 3 - If the media element has a src attribute, then let mode be attribute.
1256 Mode mode = attribute;
1257 if (!hasAttributeWithoutSynchronization(srcAttr)) {
1258 // Otherwise, if the media element does not have a src attribute but has a source
1259 // element child, then let mode be children and let candidate be the first such
1260 // source element child in tree order.
1261 if (auto firstSource = childrenOfType<HTMLSourceElement>(*this).first()) {
1263 m_nextChildNodeToConsider = firstSource;
1264 m_currentSourceNode = nullptr;
1266 // Otherwise the media element has neither a src attribute nor a source element
1267 // child: set the networkState to NETWORK_EMPTY, and abort these steps; the
1268 // synchronous section ends.
1269 m_loadState = WaitingForSource;
1270 setShouldDelayLoadEvent(false);
1271 m_networkState = NETWORK_EMPTY;
1273 LOG(Media, "HTMLMediaElement::selectMediaResource(%p) - nothing to load", this);
1278 // 4 - Set the media element's delaying-the-load-event flag to true (this delays the load event),
1279 // and set its networkState to NETWORK_LOADING.
1280 setShouldDelayLoadEvent(true);
1281 m_networkState = NETWORK_LOADING;
1283 // 5 - Queue a task to fire a simple event named loadstart at the media element.
1284 scheduleEvent(eventNames().loadstartEvent);
1286 // 6 - If mode is attribute, then run these substeps
1287 if (mode == attribute) {
1288 m_loadState = LoadingFromSrcAttr;
1290 // If the src attribute's value is the empty string ... jump down to the failed step below
1291 URL mediaURL = getNonEmptyURLAttribute(srcAttr);
1292 if (mediaURL.isEmpty()) {
1293 mediaLoadingFailed(MediaPlayer::FormatError);
1294 LOG(Media, "HTMLMediaElement::selectMediaResource(%p) - empty 'src'", this);
1298 if (!isSafeToLoadURL(mediaURL, Complain) || !dispatchBeforeLoadEvent(mediaURL.string())) {
1299 mediaLoadingFailed(MediaPlayer::FormatError);
1303 // No type or key system information is available when the url comes
1304 // from the 'src' attribute so MediaPlayer
1305 // will have to pick a media engine based on the file extension.
1306 ContentType contentType((String()));
1307 loadResource(mediaURL, contentType, String());
1308 LOG(Media, "HTMLMediaElement::selectMediaResource(%p) - using 'src' attribute url", this);
1312 // Otherwise, the source elements will be used
1313 loadNextSourceChild();
1316 void HTMLMediaElement::loadNextSourceChild()
1318 ContentType contentType((String()));
1320 URL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain);
1321 if (!mediaURL.isValid()) {
1322 waitForSourceChange();
1326 // Recreate the media player for the new url
1327 createMediaPlayer();
1329 m_loadState = LoadingFromSourceElement;
1330 loadResource(mediaURL, contentType, keySystem);
1333 void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentType, const String& keySystem)
1335 ASSERT(isSafeToLoadURL(initialURL, Complain));
1337 LOG(Media, "HTMLMediaElement::loadResource(%p) - %s, %s, %s", this, urlForLoggingMedia(initialURL).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data());
1339 Frame* frame = document().frame();
1341 mediaLoadingFailed(MediaPlayer::FormatError);
1345 Page* page = frame->page();
1347 mediaLoadingFailed(MediaPlayer::FormatError);
1351 URL url = initialURL;
1352 if (!frame->loader().willLoadMediaElementURL(url)) {
1353 mediaLoadingFailed(MediaPlayer::FormatError);
1357 #if ENABLE(CONTENT_EXTENSIONS)
1358 ResourceRequest request(url);
1359 DocumentLoader* documentLoader = frame->loader().documentLoader();
1361 if (documentLoader && page->userContentProvider().processContentExtensionRulesForLoad(request, ResourceType::Media, *documentLoader) == ContentExtensions::BlockedStatus::Blocked) {
1363 mediaLoadingFailed(MediaPlayer::FormatError);
1368 // The resource fetch algorithm
1369 m_networkState = NETWORK_LOADING;
1371 // If the url should be loaded from the application cache, pass the url of the cached file
1372 // to the media engine.
1373 ApplicationCacheHost* cacheHost = frame->loader().documentLoader()->applicationCacheHost();
1374 ApplicationCacheResource* resource = 0;
1375 if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource)) {
1376 // Resources that are not present in the manifest will always fail to load (at least, after the
1377 // cache has been primed the first time), making the testing of offline applications simpler.
1378 if (!resource || resource->path().isEmpty()) {
1379 mediaLoadingFailed(MediaPlayer::NetworkError);
1384 // Log that we started loading a media element.
1385 page->diagnosticLoggingClient().logDiagnosticMessageWithValue(DiagnosticLoggingKeys::mediaKey(), isVideo() ? DiagnosticLoggingKeys::videoKey() : DiagnosticLoggingKeys::audioKey(), DiagnosticLoggingKeys::loadingKey(), ShouldSample::No);
1387 m_firstTimePlaying = true;
1389 // Set m_currentSrc *before* changing to the cache url, the fact that we are loading from the app
1390 // cache is an internal detail not exposed through the media element API.
1394 url = ApplicationCacheHost::createFileURL(resource->path());
1395 LOG(Media, "HTMLMediaElement::loadResource(%p) - will load from app cache -> %s", this, urlForLoggingMedia(url).utf8().data());
1398 LOG(Media, "HTMLMediaElement::loadResource(%p) - m_currentSrc -> %s", this, urlForLoggingMedia(m_currentSrc).utf8().data());
1400 if (m_sendProgressEvents)
1401 startProgressEventTimer();
1403 bool privateMode = document().page() && document().page()->usesEphemeralSession();
1404 m_player->setPrivateBrowsingMode(privateMode);
1406 // Reset display mode to force a recalculation of what to show because we are resetting the player.
1407 setDisplayMode(Unknown);
1410 m_player->setPreload(m_mediaSession->effectivePreloadForElement(*this));
1411 m_player->setPreservesPitch(m_webkitPreservesPitch);
1413 if (!m_explicitlyMuted) {
1414 m_explicitlyMuted = true;
1415 m_muted = hasAttributeWithoutSynchronization(mutedAttr);
1420 bool loadAttempted = false;
1421 #if ENABLE(MEDIA_SOURCE)
1422 ASSERT(!m_mediaSource);
1424 if (url.protocolIs(mediaSourceBlobProtocol))
1425 m_mediaSource = MediaSource::lookup(url.string());
1427 if (m_mediaSource) {
1428 if (m_mediaSource->attachToElement(this))
1429 m_player->load(url, contentType, m_mediaSource.get());
1431 // Forget our reference to the MediaSource, so we leave it alone
1432 // while processing remainder of load failure.
1433 m_mediaSource = nullptr;
1434 mediaLoadingFailed(MediaPlayer::FormatError);
1436 loadAttempted = true;
1440 #if ENABLE(MEDIA_STREAM)
1441 if (!loadAttempted) {
1442 if (!m_mediaStreamSrcObject && url.protocolIs(mediaStreamBlobProtocol))
1443 m_mediaStreamSrcObject = MediaStreamRegistry::shared().lookUp(url);
1445 if (m_mediaStreamSrcObject) {
1446 loadAttempted = true;
1447 if (!m_player->load(m_mediaStreamSrcObject->privateStream()))
1448 mediaLoadingFailed(MediaPlayer::FormatError);
1453 if (!loadAttempted && !m_player->load(url, contentType, keySystem))
1454 mediaLoadingFailed(MediaPlayer::FormatError);
1456 // If there is no poster to display, allow the media engine to render video frames as soon as
1457 // they are available.
1458 updateDisplayState();
1463 #if ENABLE(VIDEO_TRACK)
1464 static bool trackIndexCompare(TextTrack* a,
1467 return a->trackIndex() - b->trackIndex() < 0;
1470 static bool eventTimeCueCompare(const std::pair<MediaTime, TextTrackCue*>& a, const std::pair<MediaTime, TextTrackCue*>& b)
1472 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1474 if (a.first != b.first)
1475 return a.first - b.first < MediaTime::zeroTime();
1477 // If the cues belong to different text tracks, it doesn't make sense to
1478 // compare the two tracks by the relative cue order, so return the relative
1480 if (a.second->track() != b.second->track())
1481 return trackIndexCompare(a.second->track(), b.second->track());
1483 // 12 - Further sort tasks in events that have the same time by the
1484 // relative text track cue order of the text track cues associated
1485 // with these tasks.
1486 return a.second->cueIndex() - b.second->cueIndex() < 0;
1489 static bool compareCueInterval(const CueInterval& one, const CueInterval& two)
1491 return one.data()->isOrderedBefore(two.data());
1495 void HTMLMediaElement::updateActiveTextTrackCues(const MediaTime& movieTime)
1497 // 4.8.10.8 Playing the media resource
1499 // If the current playback position changes while the steps are running,
1500 // then the user agent must wait for the steps to complete, and then must
1501 // immediately rerun the steps.
1502 if (ignoreTrackDisplayUpdateRequests())
1505 LOG(Media, "HTMLMediaElement::updateActiveTextTrackCues(%p)", this);
1507 // 1 - Let current cues be a list of cues, initialized to contain all the
1508 // cues of all the hidden, showing, or showing by default text tracks of the
1509 // media element (not the disabled ones) whose start times are less than or
1510 // equal to the current playback position and whose end times are greater
1511 // than the current playback position.
1512 CueList currentCues;
1514 // The user agent must synchronously unset [the text track cue active] flag
1515 // whenever ... the media element's readyState is changed back to HAVE_NOTHING.
1516 if (m_readyState != HAVE_NOTHING && m_player) {
1517 currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime));
1518 if (currentCues.size() > 1)
1519 std::sort(currentCues.begin(), currentCues.end(), &compareCueInterval);
1522 CueList previousCues;
1525 // 2 - Let other cues be a list of cues, initialized to contain all the cues
1526 // of hidden, showing, and showing by default text tracks of the media
1527 // element that are not present in current cues.
1528 previousCues = m_currentlyActiveCues;
1530 // 3 - Let last time be the current playback position at the time this
1531 // algorithm was last run for this media element, if this is not the first
1533 MediaTime lastTime = m_lastTextTrackUpdateTime;
1535 // 4 - If the current playback position has, since the last time this
1536 // algorithm was run, only changed through its usual monotonic increase
1537 // during normal playback, then let missed cues be the list of cues in other
1538 // cues whose start times are greater than or equal to last time and whose
1539 // end times are less than or equal to the current playback position.
1540 // Otherwise, let missed cues be an empty list.
1541 if (lastTime >= MediaTime::zeroTime() && m_lastSeekTime < movieTime) {
1542 CueList potentiallySkippedCues =
1543 m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime));
1545 for (size_t i = 0; i < potentiallySkippedCues.size(); ++i) {
1546 MediaTime cueStartTime = potentiallySkippedCues[i].low();
1547 MediaTime cueEndTime = potentiallySkippedCues[i].high();
1549 // Consider cues that may have been missed since the last seek time.
1550 if (cueStartTime > std::max(m_lastSeekTime, lastTime) && cueEndTime < movieTime)
1551 missedCues.append(potentiallySkippedCues[i]);
1555 m_lastTextTrackUpdateTime = movieTime;
1557 // 5 - If the time was reached through the usual monotonic increase of the
1558 // current playback position during normal playback, and if the user agent
1559 // has not fired a timeupdate event at the element in the past 15 to 250ms
1560 // and is not still running event handlers for such an event, then the user
1561 // agent must queue a task to fire a simple event named timeupdate at the
1562 // element. (In the other cases, such as explicit seeks, relevant events get
1563 // fired as part of the overall process of changing the current playback
1565 if (!m_paused && m_lastSeekTime <= lastTime)
1566 scheduleTimeupdateEvent(false);
1568 // Explicitly cache vector sizes, as their content is constant from here.
1569 size_t currentCuesSize = currentCues.size();
1570 size_t missedCuesSize = missedCues.size();
1571 size_t previousCuesSize = previousCues.size();
1573 // 6 - If all of the cues in current cues have their text track cue active
1574 // flag set, none of the cues in other cues have their text track cue active
1575 // flag set, and missed cues is empty, then abort these steps.
1576 bool activeSetChanged = missedCuesSize;
1578 for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i)
1579 if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive())
1580 activeSetChanged = true;
1582 for (size_t i = 0; i < currentCuesSize; ++i) {
1583 TextTrackCue* cue = currentCues[i].data();
1585 if (cue->isRenderable())
1586 toVTTCue(cue)->updateDisplayTree(movieTime);
1588 if (!cue->isActive())
1589 activeSetChanged = true;
1592 if (!activeSetChanged)
1595 // 7 - If the time was reached through the usual monotonic increase of the
1596 // current playback position during normal playback, and there are cues in
1597 // other cues that have their text track cue pause-on-exi flag set and that
1598 // either have their text track cue active flag set or are also in missed
1599 // cues, then immediately pause the media element.
1600 for (size_t i = 0; !m_paused && i < previousCuesSize; ++i) {
1601 if (previousCues[i].data()->pauseOnExit()
1602 && previousCues[i].data()->isActive()
1603 && !currentCues.contains(previousCues[i]))
1607 for (size_t i = 0; !m_paused && i < missedCuesSize; ++i) {
1608 if (missedCues[i].data()->pauseOnExit())
1612 // 8 - Let events be a list of tasks, initially empty. Each task in this
1613 // list will be associated with a text track, a text track cue, and a time,
1614 // which are used to sort the list before the tasks are queued.
1615 Vector<std::pair<MediaTime, TextTrackCue*>> eventTasks;
1617 // 8 - Let affected tracks be a list of text tracks, initially empty.
1618 Vector<TextTrack*> affectedTracks;
1620 for (size_t i = 0; i < missedCuesSize; ++i) {
1621 // 9 - For each text track cue in missed cues, prepare an event named enter
1622 // for the TextTrackCue object with the text track cue start time.
1623 eventTasks.append(std::make_pair(missedCues[i].data()->startMediaTime(),
1624 missedCues[i].data()));
1626 // 10 - For each text track [...] in missed cues, prepare an event
1627 // named exit for the TextTrackCue object with the with the later of
1628 // the text track cue end time and the text track cue start time.
1630 // Note: An explicit task is added only if the cue is NOT a zero or
1631 // negative length cue. Otherwise, the need for an exit event is
1632 // checked when these tasks are actually queued below. This doesn't
1633 // affect sorting events before dispatch either, because the exit
1634 // event has the same time as the enter event.
1635 if (missedCues[i].data()->startMediaTime() < missedCues[i].data()->endMediaTime())
1636 eventTasks.append(std::make_pair(missedCues[i].data()->endMediaTime(), missedCues[i].data()));
1639 for (size_t i = 0; i < previousCuesSize; ++i) {
1640 // 10 - For each text track cue in other cues that has its text
1641 // track cue active flag set prepare an event named exit for the
1642 // TextTrackCue object with the text track cue end time.
1643 if (!currentCues.contains(previousCues[i]))
1644 eventTasks.append(std::make_pair(previousCues[i].data()->endMediaTime(),
1645 previousCues[i].data()));
1648 for (size_t i = 0; i < currentCuesSize; ++i) {
1649 // 11 - For each text track cue in current cues that does not have its
1650 // text track cue active flag set, prepare an event named enter for the
1651 // TextTrackCue object with the text track cue start time.
1652 if (!previousCues.contains(currentCues[i]))
1653 eventTasks.append(std::make_pair(currentCues[i].data()->startMediaTime(),
1654 currentCues[i].data()));
1657 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1659 std::sort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare);
1661 for (size_t i = 0; i < eventTasks.size(); ++i) {
1662 if (!affectedTracks.contains(eventTasks[i].second->track()))
1663 affectedTracks.append(eventTasks[i].second->track());
1665 // 13 - Queue each task in events, in list order.
1666 RefPtr<Event> event;
1668 // Each event in eventTasks may be either an enterEvent or an exitEvent,
1669 // depending on the time that is associated with the event. This
1670 // correctly identifies the type of the event, if the startTime is
1671 // less than the endTime in the cue.
1672 if (eventTasks[i].second->startTime() >= eventTasks[i].second->endTime()) {
1673 event = Event::create(eventNames().enterEvent, false, false);
1674 event->setTarget(eventTasks[i].second);
1675 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1677 event = Event::create(eventNames().exitEvent, false, false);
1678 event->setTarget(eventTasks[i].second);
1679 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1681 if (eventTasks[i].first == eventTasks[i].second->startMediaTime())
1682 event = Event::create(eventNames().enterEvent, false, false);
1684 event = Event::create(eventNames().exitEvent, false, false);
1686 event->setTarget(eventTasks[i].second);
1687 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1691 // 14 - Sort affected tracks in the same order as the text tracks appear in
1692 // the media element's list of text tracks, and remove duplicates.
1693 std::sort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare);
1695 // 15 - For each text track in affected tracks, in the list order, queue a
1696 // task to fire a simple event named cuechange at the TextTrack object, and, ...
1697 for (size_t i = 0; i < affectedTracks.size(); ++i) {
1698 RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false);
1699 event->setTarget(affectedTracks[i]);
1701 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1703 // ... if the text track has a corresponding track element, to then fire a
1704 // simple event named cuechange at the track element as well.
1705 if (is<LoadableTextTrack>(*affectedTracks[i])) {
1706 RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false);
1707 HTMLTrackElement* trackElement = downcast<LoadableTextTrack>(*affectedTracks[i]).trackElement();
1708 ASSERT(trackElement);
1709 event->setTarget(trackElement);
1711 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1715 // 16 - Set the text track cue active flag of all the cues in the current
1716 // cues, and unset the text track cue active flag of all the cues in the
1718 for (size_t i = 0; i < currentCuesSize; ++i)
1719 currentCues[i].data()->setIsActive(true);
1721 for (size_t i = 0; i < previousCuesSize; ++i)
1722 if (!currentCues.contains(previousCues[i]))
1723 previousCues[i].data()->setIsActive(false);
1725 // Update the current active cues.
1726 m_currentlyActiveCues = currentCues;
1728 if (activeSetChanged)
1729 updateTextTrackDisplay();
1732 bool HTMLMediaElement::textTracksAreReady() const
1734 // 4.8.10.12.1 Text track model
1736 // The text tracks of a media element are ready if all the text tracks whose mode was not
1737 // in the disabled state when the element's resource selection algorithm last started now
1738 // have a text track readiness state of loaded or failed to load.
1739 for (unsigned i = 0; i < m_textTracksWhenResourceSelectionBegan.size(); ++i) {
1740 if (m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::Loading
1741 || m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::NotLoaded)
1748 void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track)
1750 if (m_player && m_textTracksWhenResourceSelectionBegan.contains(track)) {
1751 if (track->readinessState() != TextTrack::Loading)
1752 setReadyState(m_player->readyState());
1754 // The track readiness state might have changed as a result of the user
1755 // clicking the captions button. In this case, a check whether all the
1756 // resources have failed loading should be done in order to hide the CC button.
1757 if (hasMediaControls() && track->readinessState() == TextTrack::FailedToLoad)
1758 mediaControls()->refreshClosedCaptionsButtonVisibility();
1762 void HTMLMediaElement::audioTrackEnabledChanged(AudioTrack* track)
1765 if (m_audioTracks && m_audioTracks->contains(*track))
1766 m_audioTracks->scheduleChangeEvent();
1767 if (ScriptController::processingUserGestureForMedia())
1768 removeBehaviorsRestrictionsAfterFirstUserGesture(MediaElementSession::AllRestrictions & ~MediaElementSession::RequireUserGestureToControlControlsManager);
1771 void HTMLMediaElement::textTrackModeChanged(TextTrack* track)
1773 bool trackIsLoaded = true;
1774 if (track->trackType() == TextTrack::TrackElement) {
1775 trackIsLoaded = false;
1776 for (auto& trackElement : childrenOfType<HTMLTrackElement>(*this)) {
1777 if (trackElement.track() == track) {
1778 if (trackElement.readyState() == HTMLTrackElement::LOADING || trackElement.readyState() == HTMLTrackElement::LOADED)
1779 trackIsLoaded = true;
1785 // If this is the first added track, create the list of text tracks.
1787 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
1789 // Mark this track as "configured" so configureTextTracks won't change the mode again.
1790 track->setHasBeenConfigured(true);
1792 if (track->mode() != TextTrack::Mode::Disabled && trackIsLoaded)
1793 textTrackAddCues(track, track->cues());
1795 configureTextTrackDisplay(AssumeTextTrackVisibilityChanged);
1797 if (m_textTracks && m_textTracks->contains(*track))
1798 m_textTracks->scheduleChangeEvent();
1800 #if ENABLE(AVF_CAPTIONS)
1801 if (track->trackType() == TextTrack::TrackElement && m_player)
1802 m_player->notifyTrackModeChanged();
1806 void HTMLMediaElement::videoTrackSelectedChanged(VideoTrack* track)
1809 if (m_videoTracks && m_videoTracks->contains(*track))
1810 m_videoTracks->scheduleChangeEvent();
1813 void HTMLMediaElement::textTrackKindChanged(TextTrack* track)
1815 if (track->kind() != TextTrack::Kind::Captions && track->kind() != TextTrack::Kind::Subtitles && track->mode() == TextTrack::Mode::Showing)
1816 track->setMode(TextTrack::Mode::Hidden);
1819 void HTMLMediaElement::beginIgnoringTrackDisplayUpdateRequests()
1821 ++m_ignoreTrackDisplayUpdate;
1824 void HTMLMediaElement::endIgnoringTrackDisplayUpdateRequests()
1826 ASSERT(m_ignoreTrackDisplayUpdate);
1827 --m_ignoreTrackDisplayUpdate;
1828 if (!m_ignoreTrackDisplayUpdate && m_inActiveDocument)
1829 updateActiveTextTrackCues(currentMediaTime());
1832 void HTMLMediaElement::textTrackAddCues(TextTrack* track, const TextTrackCueList* cues)
1834 if (track->mode() == TextTrack::Mode::Disabled)
1837 TrackDisplayUpdateScope scope(this);
1838 for (size_t i = 0; i < cues->length(); ++i)
1839 textTrackAddCue(track, cues->item(i));
1842 void HTMLMediaElement::textTrackRemoveCues(TextTrack*, const TextTrackCueList* cues)
1844 TrackDisplayUpdateScope scope(this);
1845 for (size_t i = 0; i < cues->length(); ++i)
1846 textTrackRemoveCue(cues->item(i)->track(), cues->item(i));
1849 void HTMLMediaElement::textTrackAddCue(TextTrack* track, PassRefPtr<TextTrackCue> prpCue)
1851 if (track->mode() == TextTrack::Mode::Disabled)
1854 RefPtr<TextTrackCue> cue = prpCue;
1856 // Negative duration cues need be treated in the interval tree as
1857 // zero-length cues.
1858 MediaTime endTime = std::max(cue->startMediaTime(), cue->endMediaTime());
1860 CueInterval interval = m_cueTree.createInterval(cue->startMediaTime(), endTime, cue.get());
1861 if (!m_cueTree.contains(interval))
1862 m_cueTree.add(interval);
1863 updateActiveTextTrackCues(currentMediaTime());
1866 void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtr<TextTrackCue> prpCue)
1868 RefPtr<TextTrackCue> cue = prpCue;
1869 // Negative duration cues need to be treated in the interval tree as
1870 // zero-length cues.
1871 MediaTime endTime = std::max(cue->startMediaTime(), cue->endMediaTime());
1873 CueInterval interval = m_cueTree.createInterval(cue->startMediaTime(), endTime, cue.get());
1874 m_cueTree.remove(interval);
1876 // Since the cue will be removed from the media element and likely the
1877 // TextTrack might also be destructed, notifying the region of the cue
1878 // removal shouldn't be done.
1879 if (cue->isRenderable())
1880 toVTTCue(cue.get())->notifyRegionWhenRemovingDisplayTree(false);
1882 size_t index = m_currentlyActiveCues.find(interval);
1883 if (index != notFound) {
1884 cue->setIsActive(false);
1885 m_currentlyActiveCues.remove(index);
1888 if (cue->isRenderable())
1889 toVTTCue(cue.get())->removeDisplayTree();
1890 updateActiveTextTrackCues(currentMediaTime());
1892 if (cue->isRenderable())
1893 toVTTCue(cue.get())->notifyRegionWhenRemovingDisplayTree(true);
1898 bool HTMLMediaElement::isSafeToLoadURL(const URL& url, InvalidURLAction actionIfInvalid)
1900 if (!url.isValid()) {
1901 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p) - %s -> FALSE because url is invalid", this, urlForLoggingMedia(url).utf8().data());
1905 Frame* frame = document().frame();
1906 if (!frame || !document().securityOrigin()->canDisplay(url)) {
1907 if (actionIfInvalid == Complain)
1908 FrameLoader::reportLocalLoadFailed(frame, url.stringCenterEllipsizedToLength());
1909 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p) - %s -> FALSE rejected by SecurityOrigin", this, urlForLoggingMedia(url).utf8().data());
1913 if (!document().contentSecurityPolicy()->allowMediaFromSource(url, isInUserAgentShadowTree())) {
1914 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p) - %s -> rejected by Content Security Policy", this, urlForLoggingMedia(url).utf8().data());
1921 void HTMLMediaElement::startProgressEventTimer()
1923 if (m_progressEventTimer.isActive())
1926 m_previousProgressTime = monotonicallyIncreasingTime();
1927 // 350ms is not magic, it is in the spec!
1928 m_progressEventTimer.startRepeating(0.350);
1931 void HTMLMediaElement::waitForSourceChange()
1933 LOG(Media, "HTMLMediaElement::waitForSourceChange(%p)", this);
1935 stopPeriodicTimers();
1936 m_loadState = WaitingForSource;
1938 // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value
1939 m_networkState = NETWORK_NO_SOURCE;
1941 // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1942 setShouldDelayLoadEvent(false);
1944 updateDisplayState();
1948 void HTMLMediaElement::noneSupported()
1950 LOG(Media, "HTMLMediaElement::noneSupported(%p)", this);
1952 stopPeriodicTimers();
1953 m_loadState = WaitingForSource;
1954 m_currentSourceNode = nullptr;
1957 // 6 - Reaching this step indicates that the media resource failed to load or that the given
1958 // URL could not be resolved. In one atomic operation, run the following steps:
1960 // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to
1961 // MEDIA_ERR_SRC_NOT_SUPPORTED.
1962 m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
1964 // 6.2 - Forget the media element's media-resource-specific text tracks.
1965 forgetResourceSpecificTracks();
1967 // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
1968 m_networkState = NETWORK_NO_SOURCE;
1970 // 7 - Queue a task to fire a simple event named error at the media element.
1971 scheduleEvent(eventNames().errorEvent);
1973 rejectPendingPlayPromises(DOMError::create("NotSupportedError", "The operation is not supported."));
1975 #if ENABLE(MEDIA_SOURCE)
1979 // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1980 setShouldDelayLoadEvent(false);
1982 // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed,
1983 // the element won't attempt to load another resource.
1985 updateDisplayState();
1989 void HTMLMediaElement::mediaLoadingFailedFatally(MediaPlayer::NetworkState error)
1991 LOG(Media, "HTMLMediaElement::mediaLoadingFailedFatally(%p) - error = %d", this, static_cast<int>(error));
1993 // 1 - The user agent should cancel the fetching process.
1994 stopPeriodicTimers();
1995 m_loadState = WaitingForSource;
1997 // 2 - Set the error attribute to a new MediaError object whose code attribute is
1998 // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
1999 if (error == MediaPlayer::NetworkError)
2000 m_error = MediaError::create(MediaError::MEDIA_ERR_NETWORK);
2001 else if (error == MediaPlayer::DecodeError)
2002 m_error = MediaError::create(MediaError::MEDIA_ERR_DECODE);
2004 ASSERT_NOT_REACHED();
2006 // 3 - Queue a task to fire a simple event named error at the media element.
2007 scheduleEvent(eventNames().errorEvent);
2009 #if ENABLE(MEDIA_SOURCE)
2013 // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a
2014 // task to fire a simple event called emptied at the element.
2015 m_networkState = NETWORK_EMPTY;
2016 scheduleEvent(eventNames().emptiedEvent);
2018 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
2019 setShouldDelayLoadEvent(false);
2021 // 6 - Abort the overall resource selection algorithm.
2022 m_currentSourceNode = nullptr;
2025 if (is<MediaDocument>(document()))
2026 downcast<MediaDocument>(document()).mediaElementSawUnsupportedTracks();
2030 void HTMLMediaElement::cancelPendingEventsAndCallbacks()
2032 LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks(%p)", this);
2033 m_asyncEventQueue.cancelAllEvents();
2035 for (auto& source : childrenOfType<HTMLSourceElement>(*this))
2036 source.cancelPendingErrorEvent();
2038 rejectPendingPlayPromises(DOMError::create("AbortError", "The operation was aborted."));
2041 void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*)
2043 beginProcessingMediaPlayerCallback();
2044 setNetworkState(m_player->networkState());
2045 endProcessingMediaPlayerCallback();
2048 static void logMediaLoadRequest(Page* page, const String& mediaEngine, const String& errorMessage, bool succeeded)
2053 DiagnosticLoggingClient& diagnosticLoggingClient = page->diagnosticLoggingClient();
2055 diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::mediaLoadingFailedKey(), errorMessage, DiagnosticLoggingResultFail, ShouldSample::No);
2059 diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadedKey(), mediaEngine, ShouldSample::No);
2061 if (!page->hasSeenAnyMediaEngine())
2062 diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOneMediaEngineKey(), emptyString(), ShouldSample::No);
2064 if (!page->hasSeenMediaEngine(mediaEngine))
2065 diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsMediaEngineKey(), mediaEngine, ShouldSample::No);
2067 page->sawMediaEngine(mediaEngine);
2070 static String stringForNetworkState(MediaPlayer::NetworkState state)
2073 case MediaPlayer::Empty: return ASCIILiteral("Empty");
2074 case MediaPlayer::Idle: return ASCIILiteral("Idle");
2075 case MediaPlayer::Loading: return ASCIILiteral("Loading");
2076 case MediaPlayer::Loaded: return ASCIILiteral("Loaded");
2077 case MediaPlayer::FormatError: return ASCIILiteral("FormatError");
2078 case MediaPlayer::NetworkError: return ASCIILiteral("NetworkError");
2079 case MediaPlayer::DecodeError: return ASCIILiteral("DecodeError");
2080 default: return emptyString();
2084 void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error)
2086 stopPeriodicTimers();
2088 // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
2089 // <source> children, schedule the next one
2090 if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
2092 // resource selection algorithm
2093 // 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.
2094 if (m_currentSourceNode)
2095 m_currentSourceNode->scheduleErrorEvent();
2097 LOG(Media, "HTMLMediaElement::setNetworkState(%p) - error event not sent, <source> was removed", this);
2099 // 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.
2101 // 9.Otherwise.11 - Forget the media element's media-resource-specific tracks.
2102 forgetResourceSpecificTracks();
2104 if (havePotentialSourceChild()) {
2105 LOG(Media, "HTMLMediaElement::setNetworkState(%p) - scheduling next <source>", this);
2106 scheduleNextSourceChild();
2108 LOG(Media, "HTMLMediaElement::setNetworkState(%p) - no more <source> elements, waiting", this);
2109 waitForSourceChange();
2115 if ((error == MediaPlayer::NetworkError && m_readyState >= HAVE_METADATA) || error == MediaPlayer::DecodeError)
2116 mediaLoadingFailedFatally(error);
2117 else if ((error == MediaPlayer::FormatError || error == MediaPlayer::NetworkError) && m_loadState == LoadingFromSrcAttr)
2120 updateDisplayState();
2121 if (hasMediaControls()) {
2122 mediaControls()->reset();
2123 mediaControls()->reportedError();
2126 logMediaLoadRequest(document().page(), String(), stringForNetworkState(error), false);
2128 m_mediaSession->clientCharacteristicsChanged();
2131 void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
2133 LOG(Media, "HTMLMediaElement::setNetworkState(%p) - new state = %d, current state = %d", this, static_cast<int>(state), static_cast<int>(m_networkState));
2135 if (state == MediaPlayer::Empty) {
2136 // Just update the cached state and leave, we can't do anything.
2137 m_networkState = NETWORK_EMPTY;
2141 if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) {
2142 mediaLoadingFailed(state);
2146 if (state == MediaPlayer::Idle) {
2147 if (m_networkState > NETWORK_IDLE) {
2148 changeNetworkStateFromLoadingToIdle();
2149 setShouldDelayLoadEvent(false);
2151 m_networkState = NETWORK_IDLE;
2155 if (state == MediaPlayer::Loading) {
2156 if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE)
2157 startProgressEventTimer();
2158 m_networkState = NETWORK_LOADING;
2161 if (state == MediaPlayer::Loaded) {
2162 if (m_networkState != NETWORK_IDLE)
2163 changeNetworkStateFromLoadingToIdle();
2164 m_completelyLoaded = true;
2167 if (hasMediaControls())
2168 mediaControls()->updateStatusDisplay();
2171 void HTMLMediaElement::changeNetworkStateFromLoadingToIdle()
2173 m_progressEventTimer.stop();
2174 if (hasMediaControls() && m_player->didLoadingProgress())
2175 mediaControls()->bufferingProgressed();
2177 // Schedule one last progress event so we guarantee that at least one is fired
2178 // for files that load very quickly.
2179 scheduleEvent(eventNames().progressEvent);
2180 scheduleEvent(eventNames().suspendEvent);
2181 m_networkState = NETWORK_IDLE;
2184 void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*)
2186 beginProcessingMediaPlayerCallback();
2188 setReadyState(m_player->readyState());
2190 endProcessingMediaPlayerCallback();
2193 bool HTMLMediaElement::canTransitionFromAutoplayToPlay() const
2195 return isAutoplaying()
2198 && !pausedForUserInteraction()
2199 && !document().isSandboxed(SandboxAutomaticFeatures)
2200 && mediaSession().playbackPermitted(*this);
2203 void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
2205 LOG(Media, "HTMLMediaElement::setReadyState(%p) - new state = %d, current state = %d,", this, static_cast<int>(state), static_cast<int>(m_readyState));
2207 // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it
2208 bool wasPotentiallyPlaying = potentiallyPlaying();
2210 ReadyState oldState = m_readyState;
2211 ReadyState newState = static_cast<ReadyState>(state);
2213 #if ENABLE(VIDEO_TRACK)
2214 bool tracksAreReady = textTracksAreReady();
2216 if (newState == oldState && m_tracksAreReady == tracksAreReady)
2219 m_tracksAreReady = tracksAreReady;
2221 if (newState == oldState)
2223 bool tracksAreReady = true;
2227 m_readyState = newState;
2229 // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until
2230 // the text tracks are ready, regardless of the state of the media file.
2231 if (newState <= HAVE_METADATA)
2232 m_readyState = newState;
2234 m_readyState = HAVE_CURRENT_DATA;
2237 if (oldState > m_readyStateMaximum)
2238 m_readyStateMaximum = oldState;
2240 if (m_networkState == NETWORK_EMPTY)
2244 // 4.8.10.9, step 11
2245 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA)
2246 scheduleEvent(eventNames().waitingEvent);
2248 // 4.8.10.10 step 14 & 15.
2249 if (!m_player->seeking() && m_readyState >= HAVE_CURRENT_DATA)
2252 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
2254 invalidateCachedTime();
2255 scheduleTimeupdateEvent(false);
2256 scheduleEvent(eventNames().waitingEvent);
2260 if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
2261 prepareMediaFragmentURI();
2262 scheduleEvent(eventNames().durationchangeEvent);
2263 scheduleResizeEvent();
2264 scheduleEvent(eventNames().loadedmetadataEvent);
2265 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
2266 if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent))
2267 enqueuePlaybackTargetAvailabilityChangedEvent();
2269 m_initiallyMuted = m_volume < 0.05 || muted();
2271 if (hasMediaControls())
2272 mediaControls()->loadedMetadata();
2275 if (is<MediaDocument>(document()))
2276 downcast<MediaDocument>(document()).mediaElementNaturalSizeChanged(expandedIntSize(m_player->naturalSize()));
2278 logMediaLoadRequest(document().page(), m_player->engineDescription(), String(), true);
2280 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
2281 updateMediaState(UpdateState::Asynchronously);
2284 m_mediaSession->clientCharacteristicsChanged();
2287 bool shouldUpdateDisplayState = false;
2289 if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) {
2290 m_haveFiredLoadedData = true;
2291 shouldUpdateDisplayState = true;
2292 scheduleEvent(eventNames().loadeddataEvent);
2293 setShouldDelayLoadEvent(false);
2294 applyMediaFragmentURI();
2297 bool isPotentiallyPlaying = potentiallyPlaying();
2298 if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
2299 scheduleEvent(eventNames().canplayEvent);
2300 if (isPotentiallyPlaying)
2301 scheduleNotifyAboutPlaying();
2302 shouldUpdateDisplayState = true;
2305 if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA && tracksAreReady) {
2306 if (oldState <= HAVE_CURRENT_DATA)
2307 scheduleEvent(eventNames().canplayEvent);
2309 scheduleEvent(eventNames().canplaythroughEvent);
2311 if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA)
2312 scheduleNotifyAboutPlaying();
2314 if (canTransitionFromAutoplayToPlay()) {
2316 invalidateCachedTime();
2317 scheduleEvent(eventNames().playEvent);
2318 scheduleNotifyAboutPlaying();
2321 shouldUpdateDisplayState = true;
2324 if (shouldUpdateDisplayState) {
2325 updateDisplayState();
2326 if (hasMediaControls()) {
2327 mediaControls()->refreshClosedCaptionsButtonVisibility();
2328 mediaControls()->updateStatusDisplay();
2333 updateMediaController();
2334 #if ENABLE(VIDEO_TRACK)
2335 updateActiveTextTrackCues(currentMediaTime());
2339 #if ENABLE(ENCRYPTED_MEDIA)
2340 void HTMLMediaElement::mediaPlayerKeyAdded(MediaPlayer*, const String& keySystem, const String& sessionId)
2342 Ref<Event> event = MediaKeyEvent::create(eventNames().webkitkeyaddedEvent, keySystem, sessionId, nullptr, nullptr, emptyString(), nullptr, 0);
2343 event->setTarget(this);
2344 m_asyncEventQueue.enqueueEvent(WTFMove(event));
2347 void HTMLMediaElement::mediaPlayerKeyError(MediaPlayer*, const String& keySystem, const String& sessionId, MediaPlayerClient::MediaKeyErrorCode errorCode, unsigned short systemCode)
2349 MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
2350 switch (errorCode) {
2351 case MediaPlayerClient::UnknownError:
2352 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
2354 case MediaPlayerClient::ClientError:
2355 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT;
2357 case MediaPlayerClient::ServiceError:
2358 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_SERVICE;
2360 case MediaPlayerClient::OutputError:
2361 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_OUTPUT;
2363 case MediaPlayerClient::HardwareChangeError:
2364 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_HARDWARECHANGE;
2366 case MediaPlayerClient::DomainError:
2367 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_DOMAIN;
2371 Ref<Event> event = MediaKeyEvent::create(eventNames().webkitkeyerrorEvent, keySystem, sessionId, nullptr, nullptr, emptyString(), MediaKeyError::create(mediaKeyErrorCode), systemCode);
2372 event->setTarget(this);
2373 m_asyncEventQueue.enqueueEvent(WTFMove(event));
2376 void HTMLMediaElement::mediaPlayerKeyMessage(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* message, unsigned messageLength, const URL& defaultURL)
2378 Ref<Event> event = MediaKeyEvent::create(eventNames().webkitkeymessageEvent, keySystem, sessionId, nullptr, Uint8Array::create(message, messageLength), defaultURL, nullptr, 0);
2379 event->setTarget(this);
2380 m_asyncEventQueue.enqueueEvent(WTFMove(event));
2383 bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* initData, unsigned initDataLength)
2385 if (!hasEventListeners(eventNames().webkitneedkeyEvent)) {
2386 m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED);
2387 scheduleEvent(eventNames().errorEvent);
2391 Ref<Event> event = MediaKeyEvent::create(eventNames().webkitneedkeyEvent, keySystem, sessionId, Uint8Array::create(initData, initDataLength), nullptr, emptyString(), nullptr, 0);
2392 event->setTarget(this);
2393 m_asyncEventQueue.enqueueEvent(WTFMove(event));
2398 #if ENABLE(ENCRYPTED_MEDIA_V2)
2399 RefPtr<ArrayBuffer> HTMLMediaElement::mediaPlayerCachedKeyForKeyId(const String& keyId) const
2401 return m_mediaKeys ? m_mediaKeys->cachedKeyForKeyId(keyId) : nullptr;
2404 bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, Uint8Array* initData)
2406 if (!hasEventListeners("webkitneedkey")) {
2407 m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED);
2408 scheduleEvent(eventNames().errorEvent);
2412 auto event = MediaKeyNeededEvent::create(eventNames().webkitneedkeyEvent, initData);
2413 event->setTarget(this);
2414 m_asyncEventQueue.enqueueEvent(WTFMove(event));
2419 String HTMLMediaElement::mediaPlayerMediaKeysStorageDirectory() const
2421 Settings* settings = document().settings();
2423 return emptyString();
2425 String storageDirectory = settings->mediaKeysStorageDirectory();
2426 if (storageDirectory.isEmpty())
2427 return emptyString();
2429 SecurityOrigin* origin = document().securityOrigin();
2431 return emptyString();
2433 return pathByAppendingComponent(storageDirectory, origin->databaseIdentifier());
2436 void HTMLMediaElement::setMediaKeys(MediaKeys* mediaKeys)
2438 if (m_mediaKeys == mediaKeys)
2442 m_mediaKeys->setMediaElement(0);
2443 m_mediaKeys = mediaKeys;
2445 m_mediaKeys->setMediaElement(this);
2448 void HTMLMediaElement::keyAdded()
2451 m_player->keyAdded();
2455 void HTMLMediaElement::progressEventTimerFired()
2458 if (m_networkState != NETWORK_LOADING)
2461 double time = monotonicallyIncreasingTime();
2462 double timedelta = time - m_previousProgressTime;
2464 if (m_player->didLoadingProgress()) {
2465 scheduleEvent(eventNames().progressEvent);
2466 m_previousProgressTime = time;
2467 m_sentStalledEvent = false;
2469 if (hasMediaControls())
2470 mediaControls()->bufferingProgressed();
2471 } else if (timedelta > 3.0 && !m_sentStalledEvent) {
2472 scheduleEvent(eventNames().stalledEvent);
2473 m_sentStalledEvent = true;
2474 setShouldDelayLoadEvent(false);
2478 void HTMLMediaElement::rewind(double timeDelta)
2480 LOG(Media, "HTMLMediaElement::rewind(%p) - %f", this, timeDelta);
2481 setCurrentTime(std::max(currentMediaTime() - MediaTime::createWithDouble(timeDelta), minTimeSeekable()));
2484 void HTMLMediaElement::returnToRealtime()
2486 LOG(Media, "HTMLMediaElement::returnToRealtime(%p)", this);
2487 setCurrentTime(maxTimeSeekable());
2490 void HTMLMediaElement::addPlayedRange(const MediaTime& start, const MediaTime& end)
2492 LOG(Media, "HTMLMediaElement::addPlayedRange(%p) - [%s, %s]", this, toString(start).utf8().data(), toString(end).utf8().data());
2493 if (!m_playedTimeRanges)
2494 m_playedTimeRanges = TimeRanges::create();
2495 m_playedTimeRanges->ranges().add(start, end);
2498 bool HTMLMediaElement::supportsScanning() const
2500 return m_player ? m_player->supportsScanning() : false;
2503 void HTMLMediaElement::prepareToPlay()
2505 LOG(Media, "HTMLMediaElement::prepareToPlay(%p)", this);
2506 if (m_havePreparedToPlay)
2508 m_havePreparedToPlay = true;
2509 m_player->prepareToPlay();
2512 void HTMLMediaElement::fastSeek(double time)
2514 fastSeek(MediaTime::createWithDouble(time));
2517 void HTMLMediaElement::fastSeek(const MediaTime& time)
2519 LOG(Media, "HTMLMediaElement::fastSeek(%p) - %s", this, toString(time).utf8().data());
2521 // 9. If the approximate-for-speed flag is set, adjust the new playback position to a value that will
2522 // allow for playback to resume promptly. If new playback position before this step is before current
2523 // playback position, then the adjusted new playback position must also be before the current playback
2524 // position. Similarly, if the new playback position before this step is after current playback position,
2525 // then the adjusted new playback position must also be after the current playback position.
2526 refreshCachedTime();
2527 MediaTime delta = time - currentMediaTime();
2528 MediaTime negativeTolerance = delta >= MediaTime::zeroTime() ? delta : MediaTime::positiveInfiniteTime();
2529 MediaTime positiveTolerance = delta < MediaTime::zeroTime() ? -delta : MediaTime::positiveInfiniteTime();
2531 seekWithTolerance(time, negativeTolerance, positiveTolerance, true);
2534 void HTMLMediaElement::seek(const MediaTime& time)
2536 LOG(Media, "HTMLMediaElement::seek(%p) - %s", this, toString(time).utf8().data());
2537 seekWithTolerance(time, MediaTime::zeroTime(), MediaTime::zeroTime(), true);
2540 void HTMLMediaElement::seekInternal(const MediaTime& time)
2542 LOG(Media, "HTMLMediaElement::seekInternal(%p) - %s", this, toString(time).utf8().data());
2543 seekWithTolerance(time, MediaTime::zeroTime(), MediaTime::zeroTime(), false);
2546 void HTMLMediaElement::seekWithTolerance(const MediaTime& inTime, const MediaTime& negativeTolerance, const MediaTime& positiveTolerance, bool fromDOM)
2549 MediaTime time = inTime;
2551 // 1 - Set the media element's show poster flag to false.
2552 setDisplayMode(Video);
2554 // 2 - If the media element's readyState is HAVE_NOTHING, abort these steps.
2555 if (m_readyState == HAVE_NOTHING || !m_player)
2558 // If the media engine has been told to postpone loading data, let it go ahead now.
2559 if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA)
2562 // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set.
2563 refreshCachedTime();
2564 MediaTime now = currentMediaTime();
2566 // 3 - If the element's seeking IDL attribute is true, then another instance of this algorithm is
2567 // already running. Abort that other instance of the algorithm without waiting for the step that
2568 // it is running to complete.
2569 if (m_seekTaskQueue.hasPendingTasks()) {
2570 LOG(Media, "HTMLMediaElement::seekWithTolerance(%p) - cancelling pending seeks", this);
2571 m_seekTaskQueue.cancelAllTasks();
2572 if (m_pendingSeek) {
2573 now = m_pendingSeek->now;
2574 m_pendingSeek = nullptr;
2576 m_pendingSeekType = NoSeek;
2579 // 4 - Set the seeking IDL attribute to true.
2580 // The flag will be cleared when the engine tells us the time has actually changed.
2583 if (m_lastSeekTime < now)
2584 addPlayedRange(m_lastSeekTime, now);
2586 m_lastSeekTime = time;
2588 // 5 - If the seek was in response to a DOM method call or setting of an IDL attribute, then continue
2589 // the script. The remainder of these steps must be run asynchronously.
2590 m_pendingSeek = std::make_unique<PendingSeek>(now, time, negativeTolerance, positiveTolerance);
2592 LOG(Media, "HTMLMediaElement::seekWithTolerance(%p) - enqueuing seek from %s to %s", this, toString(now).utf8().data(), toString(time).utf8().data());
2593 m_seekTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::seekTask, this));
2598 void HTMLMediaElement::seekTask()
2600 LOG(Media, "HTMLMediaElement::seekTask(%p)", this);
2607 ASSERT(m_pendingSeek);
2608 MediaTime now = m_pendingSeek->now;
2609 MediaTime time = m_pendingSeek->targetTime;
2610 MediaTime negativeTolerance = m_pendingSeek->negativeTolerance;
2611 MediaTime positiveTolerance = m_pendingSeek->positiveTolerance;
2612 m_pendingSeek = nullptr;
2614 // 6 - If the new playback position is later than the end of the media resource, then let it be the end
2615 // of the media resource instead.
2616 time = std::min(time, durationMediaTime());
2618 // 7 - If the new playback position is less than the earliest possible position, let it be that position instead.
2619 MediaTime earliestTime = m_player->startTime();
2620 time = std::max(time, earliestTime);
2622 // Ask the media engine for the time value in the movie's time scale before comparing with current time. This
2623 // is necessary because if the seek time is not equal to currentTime but the delta is less than the movie's
2624 // time scale, we will ask the media engine to "seek" to the current movie time, which may be a noop and
2625 // not generate a timechanged callback. This means m_seeking will never be cleared and we will never
2626 // fire a 'seeked' event.
2628 MediaTime mediaTime = m_player->mediaTimeForTimeValue(time);
2629 if (time != mediaTime)
2630 LOG(Media, "HTMLMediaElement::seekTask(%p) - %s - media timeline equivalent is %s", this, toString(time).utf8().data(), toString(mediaTime).utf8().data());
2632 time = m_player->mediaTimeForTimeValue(time);
2634 // 8 - If the (possibly now changed) new playback position is not in one of the ranges given in the
2635 // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute
2636 // that is the nearest to the new playback position. ... If there are no ranges given in the seekable
2637 // attribute then set the seeking IDL attribute to false and abort these steps.
2638 RefPtr<TimeRanges> seekableRanges = seekable();
2639 bool noSeekRequired = !seekableRanges->length();
2641 // Short circuit seeking to the current time by just firing the events if no seek is required.
2642 // Don't skip calling the media engine if 1) we are in poster mode (because a seek should always cancel
2643 // poster display), or 2) if there is a pending fast seek, or 3) if this seek is not an exact seek
2644 SeekType thisSeekType = (negativeTolerance == MediaTime::zeroTime() && positiveTolerance == MediaTime::zeroTime()) ? Precise : Fast;
2645 if (!noSeekRequired && time == now && thisSeekType == Precise && m_pendingSeekType != Fast && displayMode() != Poster)
2646 noSeekRequired = true;
2648 #if ENABLE(MEDIA_SOURCE)
2649 // Always notify the media engine of a seek if the source is not closed. This ensures that the source is
2650 // always in a flushed state when the 'seeking' event fires.
2651 if (m_mediaSource && !m_mediaSource->isClosed())
2652 noSeekRequired = false;
2655 if (noSeekRequired) {
2656 LOG(Media, "HTMLMediaElement::seekTask(%p) - seek to %s ignored", this, toString(time).utf8().data());
2658 scheduleEvent(eventNames().seekingEvent);
2659 scheduleTimeupdateEvent(false);
2660 scheduleEvent(eventNames().seekedEvent);
2665 time = seekableRanges->ranges().nearest(time);
2667 m_sentEndEvent = false;
2668 m_lastSeekTime = time;
2669 m_pendingSeekType = thisSeekType;
2672 // 10 - Queue a task to fire a simple event named seeking at the element.
2673 scheduleEvent(eventNames().seekingEvent);
2675 // 11 - Set the current playback position to the given new playback position
2676 m_player->seekWithTolerance(time, negativeTolerance, positiveTolerance);
2678 // 12 - Wait until the user agent has established whether or not the media data for the new playback
2679 // position is available, and, if it is, until it has decoded enough data to play back that position.
2680 // 13 - Await a stable state. The synchronous section consists of all the remaining steps of this algorithm.
2683 void HTMLMediaElement::clearSeeking()
2686 m_pendingSeekType = NoSeek;
2687 invalidateCachedTime();
2690 void HTMLMediaElement::finishSeek()
2693 // 14 - Set the seeking IDL attribute to false.
2696 LOG(Media, "HTMLMediaElement::finishSeek(%p) - current time = %s", this, toString(currentMediaTime()).utf8().data());
2698 // 15 - Run the time maches on steps.
2699 // Handled by mediaPlayerTimeChanged().
2701 // 16 - Queue a task to fire a simple event named timeupdate at the element.
2702 scheduleEvent(eventNames().timeupdateEvent);
2704 // 17 - Queue a task to fire a simple event named seeked at the element.
2705 scheduleEvent(eventNames().seekedEvent);
2707 #if ENABLE(MEDIA_SOURCE)
2709 m_mediaSource->monitorSourceBuffers();
2713 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
2715 return m_readyState;
2718 MediaPlayer::MovieLoadType HTMLMediaElement::movieLoadType() const
2720 return m_player ? m_player->movieLoadType() : MediaPlayer::Unknown;
2723 bool HTMLMediaElement::hasAudio() const
2725 return m_player ? m_player->hasAudio() : false;
2728 bool HTMLMediaElement::seeking() const
2733 void HTMLMediaElement::refreshCachedTime() const
2738 m_cachedTime = m_player->currentTime();
2739 if (!m_cachedTime) {
2740 // Do not use m_cachedTime until the media engine returns a non-zero value because we can't
2741 // estimate current time until playback actually begins.
2742 invalidateCachedTime();
2746 m_clockTimeAtLastCachedTimeUpdate = monotonicallyIncreasingTime();
2749 void HTMLMediaElement::invalidateCachedTime() const
2751 m_cachedTime = MediaTime::invalidTime();
2752 if (!m_player || !m_player->maximumDurationToCacheMediaTime())
2756 if (m_cachedTime.isValid())
2757 LOG(Media, "HTMLMediaElement::invalidateCachedTime(%p)", this);
2760 // Don't try to cache movie time when playback first starts as the time reported by the engine
2761 // sometimes fluctuates for a short amount of time, so the cached time will be off if we take it
2763 static const double minimumTimePlayingBeforeCacheSnapshot = 0.5;
2765 m_minimumClockTimeToUpdateCachedTime = monotonicallyIncreasingTime() + minimumTimePlayingBeforeCacheSnapshot;
2769 double HTMLMediaElement::currentTime() const
2771 return currentMediaTime().toDouble();
2774 MediaTime HTMLMediaElement::currentMediaTime() const
2776 #if LOG_CACHED_TIME_WARNINGS
2777 static const MediaTime minCachedDeltaForWarning = MediaTime::create(1, 100);
2781 return MediaTime::zeroTime();
2784 LOG(Media, "HTMLMediaElement::currentTime(%p) - seeking, returning %s", this, toString(m_lastSeekTime).utf8().data());
2785 return m_lastSeekTime;
2788 if (m_cachedTime.isValid() && m_paused) {
2789 #if LOG_CACHED_TIME_WARNINGS
2790 MediaTime delta = m_cachedTime - m_player->currentTime();
2791 if (delta > minCachedDeltaForWarning)
2792 LOG(Media, "HTMLMediaElement::currentTime(%p) - WARNING, cached time is %s seconds off of media time when paused", this, toString(delta).utf8().data());
2794 return m_cachedTime;
2797 // Is it too soon use a cached time?
2798 double now = monotonicallyIncreasingTime();
2799 double maximumDurationToCacheMediaTime = m_player->maximumDurationToCacheMediaTime();
2801 if (maximumDurationToCacheMediaTime && m_cachedTime.isValid() && !m_paused && now > m_minimumClockTimeToUpdateCachedTime) {
2802 double clockDelta = now - m_clockTimeAtLastCachedTimeUpdate;
2804 // Not too soon, use the cached time only if it hasn't expired.
2805 if (clockDelta < maximumDurationToCacheMediaTime) {
2806 MediaTime adjustedCacheTime = m_cachedTime + MediaTime::createWithDouble(effectivePlaybackRate() * clockDelta);
2808 #if LOG_CACHED_TIME_WARNINGS
2809 MediaTime delta = adjustedCacheTime - m_player->currentTime();
2810 if (delta > minCachedDeltaForWarning)
2811 LOG(Media, "HTMLMediaElement::currentTime(%p) - WARNING, cached time is %f seconds off of media time when playing", this, delta);
2813 return adjustedCacheTime;
2817 #if LOG_CACHED_TIME_WARNINGS
2818 if (maximumDurationToCacheMediaTime && now > m_minimumClockTimeToUpdateCachedTime && m_cachedTime != MediaPlayer::invalidTime()) {
2819 double clockDelta = now - m_clockTimeAtLastCachedTimeUpdate;
2820 MediaTime delta = m_cachedTime + MediaTime::createWithDouble(effectivePlaybackRate() * clockDelta) - m_player->currentTime();
2821 LOG(Media, "HTMLMediaElement::currentTime(%p) - cached time was %s seconds off of media time when it expired", this, toString(delta).utf8().data());
2825 refreshCachedTime();
2827 if (m_cachedTime.isInvalid())
2828 return MediaTime::zeroTime();
2830 return m_cachedTime;
2833 void HTMLMediaElement::setCurrentTime(double time)
2835 setCurrentTime(MediaTime::createWithDouble(time));
2838 void HTMLMediaElement::setCurrentTime(const MediaTime& time)
2840 if (m_mediaController)
2846 void HTMLMediaElement::setCurrentTime(double time, ExceptionCode& ec)
2848 // On setting, if the media element has a current media controller, then the user agent must
2849 // throw an InvalidStateError exception
2850 if (m_mediaController) {
2851 ec = INVALID_STATE_ERR;
2855 seek(MediaTime::createWithDouble(time));
2858 double HTMLMediaElement::duration() const
2860 return durationMediaTime().toDouble();
2863 MediaTime HTMLMediaElement::durationMediaTime() const
2865 if (m_player && m_readyState >= HAVE_METADATA)
2866 return m_player->duration();
2868 return MediaTime::invalidTime();
2871 bool HTMLMediaElement::paused() const
2873 // As of this writing, JavaScript garbage collection calls this function directly. In the past
2874 // we had problems where this was called on an object after a bad cast. The assertion below
2875 // made our regression test detect the problem, so we should keep it because of that. But note
2876 // that the value of the assertion relies on the compiler not being smart enough to know that
2877 // isHTMLUnknownElement is guaranteed to return false for an HTMLMediaElement.
2878 ASSERT(!isHTMLUnknownElement());
2883 double HTMLMediaElement::defaultPlaybackRate() const
2885 #if ENABLE(MEDIA_STREAM)
2886 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
2887 // "defaultPlaybackRate" - On setting: ignored. On getting: return 1.0
2888 // A MediaStream is not seekable. Therefore, this attribute must always have the
2889 // value 1.0 and any attempt to alter it must be ignored. Note that this also means
2890 // that the ratechange event will not fire.
2891 if (m_mediaStreamSrcObject)
2895 return m_defaultPlaybackRate;
2898 void HTMLMediaElement::setDefaultPlaybackRate(double rate)
2900 #if ENABLE(MEDIA_STREAM)
2901 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
2902 // "defaultPlaybackRate" - On setting: ignored. On getting: return 1.0
2903 // A MediaStream is not seekable. Therefore, this attribute must always have the
2904 // value 1.0 and any attempt to alter it must be ignored. Note that this also means
2905 // that the ratechange event will not fire.
2906 if (m_mediaStreamSrcObject)
2910 if (m_defaultPlaybackRate != rate) {
2911 LOG(Media, "HTMLMediaElement::setDefaultPlaybackRate(%p) - %f", this, rate);
2912 m_defaultPlaybackRate = rate;
2913 scheduleEvent(eventNames().ratechangeEvent);
2917 double HTMLMediaElement::effectivePlaybackRate() const
2919 return m_mediaController ? m_mediaController->playbackRate() : m_reportedPlaybackRate;
2922 double HTMLMediaElement::requestedPlaybackRate() const
2924 return m_mediaController ? m_mediaController->playbackRate() : m_requestedPlaybackRate;
2927 double HTMLMediaElement::playbackRate() const
2929 #if ENABLE(MEDIA_STREAM)
2930 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
2931 // "playbackRate" - A MediaStream is not seekable. Therefore, this attribute must always
2932 // have the value 1.0 and any attempt to alter it must be ignored. Note that this also
2933 // means that the ratechange event will not fire.
2934 if (m_mediaStreamSrcObject)
2938 return m_requestedPlaybackRate;
2941 void HTMLMediaElement::setPlaybackRate(double rate)
2943 LOG(Media, "HTMLMediaElement::setPlaybackRate(%p) - %f", this, rate);
2945 #if ENABLE(MEDIA_STREAM)
2946 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
2947 // "playbackRate" - A MediaStream is not seekable. Therefore, this attribute must always
2948 // have the value 1.0 and any attempt to alter it must be ignored. Note that this also
2949 // means that the ratechange event will not fire.
2950 if (m_mediaStreamSrcObject)
2954 if (m_player && potentiallyPlaying() && m_player->rate() != rate && !m_mediaController)
2955 m_player->setRate(rate);
2957 if (m_requestedPlaybackRate != rate) {
2958 m_reportedPlaybackRate = m_requestedPlaybackRate = rate;
2959 invalidateCachedTime();
2960 scheduleEvent(eventNames().ratechangeEvent);
2964 void HTMLMediaElement::updatePlaybackRate()
2966 double requestedRate = requestedPlaybackRate();
2967 if (m_player && potentiallyPlaying() && m_player->rate() != requestedRate)
2968 m_player->setRate(requestedRate);
2971 bool HTMLMediaElement::webkitPreservesPitch() const
2973 return m_webkitPreservesPitch;
2976 void HTMLMediaElement::setWebkitPreservesPitch(bool preservesPitch)
2978 LOG(Media, "HTMLMediaElement::setWebkitPreservesPitch(%p) - %s", this, boolString(preservesPitch));
2980 m_webkitPreservesPitch = preservesPitch;
2985 m_player->setPreservesPitch(preservesPitch);
2988 bool HTMLMediaElement::ended() const
2990 #if ENABLE(MEDIA_STREAM)
2991 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
2992 // When the MediaStream state moves from the active to the inactive state, the User Agent
2993 // must raise an ended event on the HTMLMediaElement and set its ended attribute to true.
2994 if (m_mediaStreamSrcObject && m_player && m_player->ended())
2998 // 4.8.10.8 Playing the media resource
2999 // The ended attribute must return true if the media element has ended
3000 // playback and the direction of playback is forwards, and false otherwise.
3001 return endedPlayback() && requestedPlaybackRate() > 0;
3004 bool HTMLMediaElement::autoplay() const
3006 return hasAttributeWithoutSynchronization(autoplayAttr);
3009 String HTMLMediaElement::preload() const
3011 #if ENABLE(MEDIA_STREAM)
3012 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3013 // "preload" - On getting: none. On setting: ignored.
3014 if (m_mediaStreamSrcObject)
3015 return ASCIILiteral("none");
3018 switch (m_preload) {
3019 case MediaPlayer::None:
3020 return ASCIILiteral("none");
3021 case MediaPlayer::MetaData:
3022 return ASCIILiteral("metadata");
3023 case MediaPlayer::Auto:
3024 return ASCIILiteral("auto");
3027 ASSERT_NOT_REACHED();
3031 void HTMLMediaElement::setPreload(const String& preload)
3033 LOG(Media, "HTMLMediaElement::setPreload(%p) - %s", this, preload.utf8().data());
3034 #if ENABLE(MEDIA_STREAM)
3035 // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements
3036 // "preload" - On getting: none. On setting: ignored.
3037 if (m_mediaStreamSrcObject)
3041 setAttributeWithoutSynchronization(preloadAttr, preload);
3044 void HTMLMediaElement::play(PlayPromise&& promise)
3046 LOG(Media, "HTMLMediaElement::play(%p)", this);
3048 if (!m_mediaSession->playbackPermitted(*this)) {
3049 promise.reject(NotAllowedError);
3053 if (m_error && m_error->code() == MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED) {
3054 promise.reject(NOT_SUPPORTED_ERR, "The operation is not supported.");
3058 if (ScriptController::processingUserGestureForMedia())
3059 removeBehaviorsRestrictionsAfterFirstUserGesture();
3061 if (!playInternal()) {
3062 promise.reject(NotAllowedError);
3066 m_pendingPlayPromises.append(WTFMove(promise));
3069 void HTMLMediaElement::play()
3071 LOG(Media, "HTMLMediaElement::play(%p)", this);
3073 if (!m_mediaSession->playbackPermitted(*this))
3075 if (ScriptController::processingUserGestureForMedia())
3076 removeBehaviorsRestrictionsAfterFirstUserGesture();
3081 bool HTMLMediaElement::playInternal()
3083 LOG(Media, "HTMLMediaElement::playInternal(%p)", this);
3085 if (!m_mediaSession->clientWillBeginPlayback()) {
3086 LOG(Media, " returning because of interruption");
3087 return true; // Treat as success because we will begin playback on cessation of the interruption.
3090 // 4.8.10.9. Playing the media resource
3091 if (!m_player || m_networkState == NETWORK_EMPTY)
3092 scheduleDelayedAction(LoadMediaResource);
3094 if (endedPlayback())
3095 seekInternal(MediaTime::zeroTime());
3097 if (m_mediaController)
3098 m_mediaController->bringElementUpToSpeed(this);
3102 invalidateCachedTime();
3103 scheduleEvent(eventNames().playEvent);
3105 if (m_readyState <= HAVE_CURRENT_DATA)
3106 scheduleEvent(eventNames().waitingEvent);
3107 else if (m_readyState >= HAVE_FUTURE_DATA)
3108 scheduleNotifyAboutPlaying();
3110 #if ENABLE(MEDIA_SESSION)
3111 // 6.3 Activating a media session from a media element
3112 // When the play() method is invoked, the paused attribute is true, and the readyState attribute has the value
3113 // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, then
3114 // 1. Let media session be the value of the current media session.
3115 // 2. If we are not currently in media session's list of active participating media elements then append
3116 // ourselves to this list.
3117 // 3. Let activated be the result of running the media session invocation algorithm for media session.
3118 // 4. If activated is failure, pause ourselves.
3119 if (m_readyState == HAVE_ENOUGH_DATA || m_readyState == HAVE_FUTURE_DATA) {
3121 m_session->addActiveMediaElement(*this);
3123 if (m_session->kind() == MediaSessionKind::Content) {
3124 if (Page* page = document().page())
3125 page->chrome().client().focusedContentMediaElementDidChange(m_elementID);
3128 if (!m_session->invoke()) {
3135 } else if (m_readyState >= HAVE_FUTURE_DATA)
3136 scheduleResolvePendingPlayPromises();
3138 m_autoplaying = false;
3144 void HTMLMediaElement::pause()
3146 LOG(Media, "HTMLMediaElement::pause(%p)", this);
3148 if (!m_mediaSession->playbackPermitted(*this))
3151 if (ScriptController::processingUserGestureForMedia())
3152 removeBehaviorsRestrictionsAfterFirstUserGesture(MediaElementSession::RequireUserGestureToControlControlsManager);
3158 void HTMLMediaElement::pauseInternal()
3160 LOG(Media, "HTMLMediaElement::pauseInternal(%p)", this);
3162 if (!m_mediaSession->clientWillPausePlayback()) {
3163 LOG(Media, " returning because of interruption");
3167 // 4.8.10.9. Playing the media resource
3168 if (!m_player || m_networkState == NETWORK_EMPTY) {
3169 // Unless the restriction on media requiring user action has been lifted
3170 // don't trigger loading if a script calls pause().
3171 if (!m_mediaSession->playbackPermitted(*this))
3173 scheduleDelayedAction(LoadMediaResource);
3176 m_autoplaying = false;
3180 scheduleTimeupdateEvent(false);
3181 scheduleEvent(eventNames().pauseEvent);
3182 rejectPendingPlayPromises(DOMError::create("AbortError", "The operation was aborted."));
3184 if (MemoryPressureHandler::singleton().isUnderMemoryPressure())
3185 purgeBufferedDataIfPossible();
3191 #if ENABLE(MEDIA_SOURCE)
3192 void HTMLMediaElement::closeMediaSource()
3197 m_mediaSource->close();
3198 m_mediaSource = nullptr;
3202 #if ENABLE(ENCRYPTED_MEDIA)
3203 void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, const RefPtr<Uint8Array>& initData, ExceptionCode& ec)
3205 #if ENABLE(ENCRYPTED_MEDIA_V2)
3206 static bool firstTime = true;
3207 if (firstTime && scriptExecutionContext()) {
3208 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, ASCIILiteral("'HTMLMediaElement.webkitGenerateKeyRequest()' is deprecated. Use 'MediaKeys.createSession()' instead."));
3213 if (keySystem.isEmpty()) {
3219 ec = INVALID_STATE_ERR;
3223 const unsigned char* initDataPointer = nullptr;
3224 unsigned initDataLength = 0;
3226 initDataPointer = initData->data();
3227 initDataLength = initData->length();
3230 MediaPlayer::MediaKeyException result = m_player->generateKeyRequest(keySystem, initDataPointer, initDataLength);
3231 ec = exceptionCodeForMediaKeyException(result);
3234 void HTMLMediaElement::webkitAddKey(const String& keySystem, Uint8Array& key, const RefPtr<Uint8Array>& initData, const String& sessionId, ExceptionCode& ec)
3236 #if ENABLE(ENCRYPTED_MEDIA_V2)
3237 static bool firstTime = true;
3238 if (firstTime && scriptExecutionContext()) {
3239 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, ASCIILiteral("'HTMLMediaElement.webkitAddKey()' is deprecated. Use 'MediaKeySession.update()' instead."));
3244 if (keySystem.isEmpty()) {
3249 if (!key.length()) {
3250 ec = TYPE_MISMATCH_ERR;
3255 ec = INVALID_STATE_ERR;
3259 const unsigned char* initDataPointer = nullptr;
3260 unsigned initDataLength = 0;
3262 initDataPointer = initData->data();
3263 initDataLength = initData->length();
3266 MediaPlayer::MediaKeyException result = m_player->addKey(keySystem, key.data(), key.length(), initDataPointer, initDataLength, sessionId);
3267 ec = exceptionCodeForMediaKeyException(result);
3270 void HTMLMediaElement::webkitCancelKeyRequest(const String& keySystem, const String& sessionId, ExceptionCode& ec)
3272 if (keySystem.isEmpty()) {
3278 ec = INVALID_STATE_ERR;
3282 MediaPlayer::MediaKeyException result = m_player->cancelKeyRequest(keySystem, sessionId);
3283 ec = exceptionCodeForMediaKeyException(result);
3288 bool HTMLMediaElement::loop() const
3290 return hasAttributeWithoutSynchronization(loopAttr);
3293 void HTMLMediaElement::setLoop(bool b)
3295 LOG(Media, "HTMLMediaElement::setLoop(%p) - %s", this, boolString(b));
3296 setBooleanAttribute(loopAttr, b);
3299 bool HTMLMediaElement::controls() const
3301 Frame* frame = document().frame();
3303 // always show controls when scripting is disabled
3304 if (frame && !frame->script().canExecuteScripts(NotAboutToExecuteScript))
3307 return hasAttributeWithoutSynchronization(controlsAttr);
3310 void HTMLMediaElement::setControls(bool b)
3312 LOG(Media, "HTMLMediaElement::setControls(%p) - %s", this, boolString(b));
3313 setBooleanAttribute(controlsAttr, b);
3316 double HTMLMediaElement::volume() const
3321 void HTMLMediaElement::setVolume(double vol, ExceptionCode& ec)
3323 LOG(Media, "HTMLMediaElement::setVolume(%p) - %f", this, vol);
3325 if (vol < 0.0f || vol > 1.0f) {
3326 ec = INDEX_SIZE_ERR;
3331 if (m_volume != vol) {
3333 m_volumeInitialized = true;
3335 scheduleEvent(eventNames().volumechangeEvent);
3340 bool HTMLMediaElement::muted() const
3342 return m_explicitlyMuted ? m_muted : hasAttributeWithoutSynchronization(mutedAttr);
3345 void HTMLMediaElement::setMuted(bool muted)
3347 LOG(Media, "HTMLMediaElement::setMuted(%p) - %s", this, boolString(muted));
3349 if (m_muted != muted || !m_explicitlyMuted) {
3351 m_explicitlyMuted = true;
3353 if (ScriptController::processingUserGestureForMedia())
3354 removeBehaviorsRestrictionsAfterFirstUserGesture(MediaElementSession::AllRestrictions & ~MediaElementSession::RequireUserGestureToControlControlsManager);
3356 // Avoid recursion when the player reports volume changes.
3357 if (!processingMediaPlayerCallback()) {
3359 m_player->setMuted(effectiveMuted());
3360 if (hasMediaControls())
3361 mediaControls()->changedMute();
3364 scheduleEvent(eventNames().volumechangeEvent);
3368 #if ENABLE(MEDIA_SESSION)
3369 document().updateIsPlayingMedia(m_elementID);
3371 document().updateIsPlayingMedia();
3374 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
3375 updateMediaState(UpdateState::Asynchronously);
3379 scheduleUpdatePlaybackControlsManager();
3382 void HTMLMediaElement::togglePlayState()
3384 LOG(Media, "HTMLMediaElement::togglePlayState(%p) - canPlay() is %s", this, boolString(canPlay()));
3386 // We can safely call the internal play/pause methods, which don't check restrictions, because
3387 // this method is only called from the built-in media controller
3389 updatePlaybackRate();
3395 void HTMLMediaElement::beginScrubbing()
3397 LOG(Media, "HTMLMediaElement::beginScrubbing(%p) - paused() is %s", this, boolString(paused()));
3401 // Because a media element stays in non-paused state when it reaches end, playback resumes
3402 // when the slider is dragged from the end to another position unless we pause first. Do
3403 // a "hard pause" so an event is generated, since we want to stay paused after scrubbing finishes.
3406 // Not at the end but we still want to pause playback so the media engine doesn't try to
3407 // continue playing during scrubbing. Pause without generating an event as we will
3408 // unpause after scrubbing finishes.
3409 setPausedInternal(true);
3414 void HTMLMediaElement::endScrubbing()
3416 LOG(Media, "HTMLMediaElement::endScrubbing(%p) - m_pausedInternal is %s", this, boolString(m_pausedInternal));
3418 if (m_pausedInternal)
3419 setPausedInternal(false);
3422 void HTMLMediaElement::beginScanning(ScanDirection direction)
3424 m_scanType = supportsScanning() ? Scan : Seek;
3425 m_scanDirection = direction;
3427 if (m_scanType == Seek) {
3428 // Scanning by seeking requires the video to be paused during scanning.
3429 m_actionAfterScan = paused() ? Nothing : Play;
3432 // Scanning by scanning requires the video to be playing during scanninging.
3433 m_actionAfterScan = paused() ? Pause : Nothing;
3435 setPlaybackRate(nextScanRate());
3438 m_scanTimer.start(0, m_scanType == Seek ? SeekRepeatDelay : ScanRepeatDelay);
3441 void HTMLMediaElement::endScanning()
3443 if (m_scanType == Scan)
3444 setPlaybackRate(defaultPlaybackRate());
3446 if (m_actionAfterScan == Play)
3448 else if (m_actionAfterScan == Pause)
3451 if (m_scanTimer.isActive())
3455 double HTMLMediaElement::nextScanRate()
3457 double rate = std::min(ScanMaximumRate, fabs(playbackRate() * 2));
3458 if (m_scanDirection == Backward)
3461 rate = std::min(std::max(rate, minFastReverseRate()), maxFastForwardRate());
3466 void HTMLMediaElement::scanTimerFired()
3468 if (m_scanType == Seek) {
3469 double seekTime = m_scanDirection == Forward ? SeekTime : -SeekTime;
3470 setCurrentTime(currentTime() + seekTime);
3472 setPlaybackRate(nextScanRate());
3475 // The spec says to fire periodic timeupdate events (those sent while playing) every
3476 // "15 to 250ms", we choose the slowest frequency
3477 static const double maxTimeupdateEventFrequency = 0.25;
3479 void HTMLMediaElement::startPlaybackProgressTimer()
3481 if (m_playbackProgressTimer.isActive())
3484 m_previousProgressTime = monotonicallyIncreasingTime();
3485 m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency);
3488 void HTMLMediaElement::playbackProgressTimerFired()
3492 if (m_fragmentEndTime.isValid() && currentMediaTime() >= m_fragmentEndTime && requestedPlaybackRate() > 0) {
3493 m_fragmentEndTime = MediaTime::invalidTime();
3494 if (!m_mediaController && !m_paused) {
3495 // changes paused to true and fires a simple event named pause at the media element.
3500 scheduleTimeupdateEvent(true);
3502 if (!requestedPlaybackRate())
3505 if (!m_paused && hasMediaControls())
3506 mediaControls()->playbackProgressed();
3508 #if ENABLE(VIDEO_TRACK)
3509 updateActiveTextTrackCues(currentMediaTime());
3512 #if ENABLE(MEDIA_SOURCE)
3514 m_mediaSource->monitorSourceBuffers();
3518 void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent)
3520 double now = monotonicallyIncreasingTime();
3521 double timedelta = now - m_clockTimeAtLastUpdateEvent;
3523 // throttle the periodic events
3524 if (periodicEvent && timedelta < maxTimeupdateEventFrequency)
3527 // Some media engines make multiple "time changed" callbacks at the same time, but we only want one
3528 // event at a given time so filter here
3529 MediaTime movieTime = currentMediaTime();
3530 if (movieTime != m_lastTimeUpdateEventMovieTime) {
3531 scheduleEvent(eventNames().timeupdateEvent);
3532 m_clockTimeAtLastUpdateEvent = now;
3533 m_lastTimeUpdateEventMovieTime = movieTime;
3537 bool HTMLMediaElement::canPlay() const
3539 return paused() || ended() || m_readyState < HAVE_METADATA;
3542 double HTMLMediaElement::percentLoaded() const
3546 MediaTime duration = m_player->duration();
3548 if (!duration || duration.isPositiveInfinite() || duration.isNegativeInfinite())
3551 MediaTime buffered = MediaTime::zeroTime();
3553 std::unique_ptr<PlatformTimeRanges> timeRanges = m_player->buffered();
3554 for (unsigned i = 0; i < timeRanges->length(); ++i) {
3555 MediaTime start = timeRanges->start(i, ignored);
3556 MediaTime end = timeRanges->end(i, ignored);
3557 buffered += end - start;
3559 return buffered.toDouble() / duration.toDouble();
3562 #if ENABLE(VIDEO_TRACK)
3564 void HTMLMediaElement::mediaPlayerDidAddAudioTrack(PassRefPtr<AudioTrackPrivate> prpTrack)
3566 if (isPlaying() && !m_mediaSession->playbackPermitted(*this))
3569 addAudioTrack(AudioTrack::create(this, prpTrack));
3572 void HTMLMediaElement::mediaPlayerDidAddTextTrack(PassRefPtr<InbandTextTrackPrivate> prpTrack)
3574 // 4.8.10.12.2 Sourcing in-band text tracks
3575 // 1. Associate the relevant data with a new text track and its corresponding new TextTrack object.
3576 RefPtr<InbandTextTrack> textTrack = InbandTextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, prpTrack);
3577 textTrack->setMediaElement(this);
3579 // 2. Set the new text track's kind, label, and language based on the semantics of the relevant data,
3580 // as defined by the relevant specification. If there is no label in that data, then the label must
3581 // be set to the empty string.
3582 // 3. Associate the text track list of cues with the rules for updating the text track rendering appropriate
3583 // for the format in question.
3584 // 4. If the new text track's kind is metadata, then set the text track in-band metadata track dispatch type
3585 // as follows, based on the type of the media resource:
3586 // 5. Populate the new text track's list of cues with the cues parsed so far, folllowing the guidelines for exposing
3587 // cues, and begin updating it dynamically as necessary.
3588 // - Thess are all done by the media engine.
3590 // 6. Set the new text track's readiness state to loaded.
3591 textTrack->setReadinessState(TextTrack::Loaded);
3593 // 7. Set the new text track's mode to the mode consistent with the user's preferences and the requirements of
3594 // the relevant specification for the data.
3595 // - This will happen in configureTextTracks()
3596 scheduleDelayedAction(ConfigureTextTracks);
3598 // 8. Add the new text track to the media element's list of text tracks.
3599 // 9. Fire an event with the name addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent
3600 // interface, with the track attribute initialized to the text track's TextTrack object, at the media element's
3601 // textTracks attribute's TextTrackList object.
3602 addTextTrack(textTrack.releaseNonNull());
3605 void HTMLMediaElement::mediaPlayerDidAddVideoTrack(PassRefPtr<VideoTrackPrivate> prpTrack)
3607 addVideoTrack(VideoTrack::create(this, prpTrack));
3610 void HTMLMediaElement::mediaPlayerDidRemoveAudioTrack(PassRefPtr<AudioTrackPrivate> prpTrack)
3612 prpTrack->willBeRemoved();
3615 void HTMLMediaElement::mediaPlayerDidRemoveTextTrack(PassRefPtr<InbandTextTrackPrivate> prpTrack)
3617 prpTrack->willBeRemoved();
3620 void HTMLMediaElement::mediaPlayerDidRemoveVideoTrack(PassRefPtr<VideoTrackPrivate> prpTrack)
3622 prpTrack->willBeRemoved();
3625 void HTMLMediaElement::closeCaptionTracksChanged()
3627 if (hasMediaControls())
3628 mediaControls()->closedCaptionTracksChanged();
3631 void HTMLMediaElement::addAudioTrack(Ref<AudioTrack>&& track)
3633 audioTracks().append(WTFMove(track));
3636 void HTMLMediaElement::addTextTrack(Ref<TextTrack>&& track)
3638 if (!m_requireCaptionPreferencesChangedCallbacks) {
3639 m_requireCaptionPreferencesChangedCallbacks = true;
3640 Document& document = this->document();
3641 document.registerForCaptionPreferencesChangedCallbacks(this);
3642 if (Page* page = document.page())
3643 m_captionDisplayMode = page->group().captionPreferences().captionDisplayMode();
3646 textTracks().append(WTFMove(track));
3648 closeCaptionTracksChanged();
3651 void HTMLMediaElement::addVideoTrack(Ref<VideoTrack>&& track)
3653 videoTracks().append(WTFMove(track));
3656 void HTMLMediaElement::removeAudioTrack(AudioTrack& track)
3658 m_audioTracks->remove(track);
3661 void HTMLMediaElement::removeTextTrack(TextTrack& track, bool scheduleEvent)
3663 TrackDisplayUpdateScope scope(this);
3664 if (TextTrackCueList* cues = track.cues())
3665 textTrackRemoveCues(&track, cues);
3666 track.clearClient();
3668 m_textTracks->remove(track, scheduleEvent);
3670 closeCaptionTracksChanged();
3673 void HTMLMediaElement::removeVideoTrack(VideoTrack& track)
3675 m_videoTracks->remove(track);
3678 void HTMLMediaElement::forgetResourceSpecificTracks()
3680 while (m_audioTracks && m_audioTracks->length())
3681 removeAudioTrack(*m_audioTracks->lastItem());
3684 TrackDisplayUpdateScope scope(this);
3685 for (int i = m_textTracks->length() - 1; i >= 0; --i) {
3686 auto& track = *m_textTracks->item(i);
3687 if (track.trackType() == TextTrack::InBand)
3688 removeTextTrack(track, false);
3692 while (m_videoTracks && m_videoTracks->length())
3693 removeVideoTrack(*m_videoTracks->lastItem());
3696 RefPtr<TextTrack> HTMLMediaElement::addTextTrack(const String& kind, const String& label, const String& language, ExceptionCode& ec)
3698 // 4.8.10.12.4 Text track API
3699 // The addTextTrack(kind, label, language) method of media elements, when invoked, must run the following steps:
3701 // 1. If kind is not one of the following strings, then throw a SyntaxError exception and abort these steps
3702 if (!TextTrack::isValidKindKeyword(kind)) {
3707 // 2. If the label argument was omitted, let label be the empty string.
3708 // 3. If the language argument was omitted, let language be the empty string.
3709 // 4. Create a new TextTrack object.
3711 // 5. Create a new text track corresponding to the new object, and set its text track kind to kind, its text
3712 // track label to label, its text track language to language...
3713 auto textTrack = TextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, kind, emptyString(), label, language);
3715 // Note, due to side effects when changing track parameters, we have to
3716 // first append the track to the text track list.
3718 // 6. Add the new text track to the media element's list of text tracks.
3719 addTextTrack(textTrack.copyRef());
3721 // ... its text track readiness state to the text track loaded state ...
3722 textTrack->setReadinessState(TextTrack::Loaded);
3724 // ... its text track mode to the text track hidden mode, and its text track list of cues to an empty list ...
3725 textTrack->setMode(TextTrack::Mode::Hidden);
3727 return WTFMove(textTrack);
3730 AudioTrackList& HTMLMediaElement::audioTracks()
3733 m_audioTracks = AudioTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
3735 return *m_audioTracks;
3738 TextTrackList& HTMLMediaElement::textTracks()
3741 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
3743 return *m_textTracks;
3746 VideoTrackList& HTMLMediaElement::videoTracks()
3749 m_videoTracks = VideoTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
3751 return *m_videoTracks;
3754 void HTMLMediaElement::didAddTextTrack(HTMLTrackElement* trackElement)
3756 ASSERT(trackElement->hasTagName(trackTag));
3758 // 4.8.10.12.3 Sourcing out-of-band text tracks
3759 // When a track element's parent element changes and the new parent is a media element,
3760 // then the user agent must add the track element's corresponding text track to the
3761 // media element's list of text tracks ... [continues in TextTrackList::append]
3762 RefPtr<TextTrack> textTrack = trackElement->track();
3766 addTextTrack(textTrack.releaseNonNull());
3768 // Do not schedule the track loading until parsing finishes so we don't start before all tracks
3769 // in the markup have been added.
3770 if (!m_parsingInProgress)
3771 scheduleDelayedAction(ConfigureTextTracks);
3773 if (hasMediaControls())
3774 mediaControls()->closedCaptionTracksChanged();
3777 void HTMLMediaElement::didRemoveTextTrack(HTMLTrackElement* trackElement)
3779 ASSERT(trackElement->hasTagName(trackTag));
3782 if (trackElement->hasTagName(trackTag)) {
3783 URL url = trackElement->getNonEmptyURLAttribute(srcAttr);
3784 LOG(Media, "HTMLMediaElement::didRemoveTrack(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data());
3788 RefPtr<TextTrack> textTrack = trackElement->track();
3792 textTrack->setHasBeenConfigured(false);
3797 // 4.8.10.12.3 Sourcing out-of-band text tracks
3798 // When a track element's parent element changes and the old parent was a media element,
3799 // then the user agent must remove the track element's corresponding text track from the
3800 // media element's list of text tracks.
3801 removeTextTrack(*textTrack);
3803 size_t index = m_textTracksWhenResourceSelectionBegan.find(textTrack.get());
3804 if (index != notFound)
3805 m_textTracksWhenResourceSelectionBegan.remove(index);
3808 void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group)
3810 ASSERT(group.tracks.size());
3812 LOG(Media, "HTMLMediaElement::configureTextTrackGroup(%p)", this);
3814 Page* page = document().page();
3815 CaptionUserPreferences* captionPreferences = page ? &page->group().captionPreferences() : 0;
3816 CaptionUserPreferences::CaptionDisplayMode displayMode = captionPreferences ? captionPreferences->captionDisplayMode() : CaptionUserPreferences::Automatic;
3818 // First, find the track in the group that should be enabled (if any).
3819 Vector<RefPtr<TextTrack>> currentlyEnabledTracks;
3820 RefPtr<TextTrack> trackToEnable;
3821 RefPtr<TextTrack> defaultTrack;
3822 RefPtr<TextTrack> fallbackTrack;
3823 RefPtr<TextTrack> forcedSubitleTrack;
3824 int highestTrackScore = 0;
3825 int highestForcedScore = 0;
3827 // If there is a visible track, it has already been configured so it won't be considered in the loop below. We don't want to choose another
3828 // track if it is less suitable, and we do want to disable it if another track is more suitable.
3829 int alreadyVisibleTrackScore = 0;
3830 if (group.visibleTrack && captionPreferences) {
3831 alreadyVisibleTrackScore = captionPreferences->textTrackSelectionScore(group.visibleTrack.get(), this);
3832 currentlyEnabledTracks.append(group.visibleTrack);
3835 for (size_t i = 0; i < group.tracks.size(); ++i) {
3836 RefPtr<TextTrack> textTrack = group.tracks[i];
3838 if (m_processingPreferenceChange && textTrack->mode() == TextTrack::Mode::Showing)
3839 currentlyEnabledTracks.append(textTrack);
3841 int trackScore = captionPreferences ? captionPreferences->textTrackSelectionScore(textTrack.get(), this) : 0;
3842 LOG(Media, "HTMLMediaElement::configureTextTrackGroup(%p) - '%s' track with language '%s' has score %i", this, textTrack->kindKeyword().string().utf8().data(), textTrack->language().string().utf8().data(), trackScore);
3846 // * If the text track kind is { [subtitles or captions] [descriptions] } and the user has indicated an interest in having a
3847 // track with this text track kind, text track language, and text track label enabled, and there is no
3848 // other text track in the media element's list of text tracks with a text track kind of either subtitles
3849 // or captions whose text track mode is showing
3851 // * If the text track kind is chapters and the text track language is one that the user agent has reason
3852 // to believe is appropriate for the user, and there is no other text track in the media element's list of
3853 // text tracks with a text track kind of chapters whose text track mode is showing
3854 // Let the text track mode be showing.
3855 if (trackScore > highestTrackScore && trackScore > alreadyVisibleTrackScore) {
3856 highestTrackScore = trackScore;
3857 trackToEnable = textTrack;
3860 if (!defaultTrack && textTrack->isDefault())
3861 defaultTrack = textTrack;
3862 if (!defaultTrack && !fallbackTrack)
3863 fallbackTrack = textTrack;
3864 if (textTrack->containsOnlyForcedSubtitles() && trackScore > highestForcedScore) {
3865 forcedSubitleTrack = textTrack;
3866 highestForcedScore = trackScore;
3868 } else if (!group.visibleTrack && !defaultTrack && textTrack->isDefault()) {
3869 // * If the track element has a default attribute specified, and there is no other text track in the media
3870 // element's list of text tracks whose text track mode is showing or showing by default
3871 // Let the text track mode be showing by default.
3872 if (group.kind != TrackGroup::CaptionsAndSubtitles || displayMode != CaptionUserPreferences::ForcedOnly)
3873 defaultTrack = textTrack;
3877 if (displayMode != CaptionUserPreferences::Manual) {
3878 if (!trackToEnable && defaultTrack)
3879 trackToEnable = defaultTrack;
3881 // If no track matches the user's preferred language, none was marked as 'default', and there is a forced subtitle track
3882 // in the same language as the language of the primary audio track, enable it.
3883 if (!trackToEnable && forcedSubitleTrack)
3884 trackToEnable = forcedSubitleTrack;
3886 // If no track matches, don't disable an already visible track unless preferences say they all should be off.
3887 if (group.kind != TrackGroup::CaptionsAndSubtitles || displayMode != CaptionUserPreferences::ForcedOnly) {
3888 if (!trackToEnable && !defaultTrack && group.visibleTrack)
3889 trackToEnable = group.visibleTrack;
3892 // If no track matches the user's preferred language and non was marked 'default', enable the first track
3893 // because the user has explicitly stated a preference for this kind of track.
3894 if (!trackToEnable && fallbackTrack)
3895 trackToEnable = fallbackTrack;
3898 m_subtitleTrackLanguage = trackToEnable->language();
3900 m_subtitleTrackLanguage = emptyString();
3903 if (currentlyEnabledTracks.size()) {
3904 for (size_t i = 0; i < currentlyEnabledTracks.size(); ++i) {
3905 RefPtr<TextTrack> textTrack = currentlyEnabledTracks[i];
3906 if (textTrack != trackToEnable)
3907 textTrack->setMode(TextTrack::Mode::Disabled);
3911 if (trackToEnable) {
3912 trackToEnable->setMode(TextTrack::Mode::Showing);
3914 // If user preferences indicate we should always display captions, make sure we reflect the
3915 // proper status via the webkitClosedCaptionsVisible API call:
3916 if (!webkitClosedCaptionsVisible() && closedCaptionsVisible() && displayMode == CaptionUserPreferences::AlwaysOn)
3917 m_webkitLegacyClosedCaptionOverride = true;
3920 m_processingPreferenceChange = false;
3923 static JSC::JSValue controllerJSValue(JSC::ExecState& exec, JSDOMGlobalObject& globalObject, HTMLMediaElement& media)
3925 auto mediaJSWrapper = toJS(&exec, &globalObject, media);
3927 // Retrieve the controller through the JS object graph
3928 JSC::JSObject* mediaJSWrapperObject = JSC::jsDynamicCast<JSC::JSObject*>(mediaJSWrapper);
3929 if (!mediaJSWrapperObject)
3930 return JSC::jsNull();
3932 JSC::Identifier controlsHost = JSC::Identifier::fromString(&exec.vm(), "controlsHost");
3933 JSC::JSValue controlsHostJSWrapper = mediaJSWrapperObject->get(&exec, controlsHost);
3934 if (exec.hadException())
3935 return JSC::jsNull();
3937 JSC::JSObject* controlsHostJSWrapperObject = JSC::jsDynamicCast<JSC::JSObject*>(controlsHostJSWrapper);
3938 if (!controlsHostJSWrapperObject)
3939 return JSC::jsNull();
3941 JSC::Identifier controllerID = JSC::Identifier::fromString(&exec.vm(), "controller");
3942 JSC::JSValue controllerJSWrapper = controlsHostJSWrapperObject->get(&exec, controllerID);
3943 if (exec.hadException())
3944 return JSC::jsNull();
3946 return controllerJSWrapper;
3949 void HTMLMediaElement::ensureMediaControlsShadowRoot()
3951 ASSERT(!m_creatingControls);
3952 m_creatingControls = true;
3953 ensureUserAgentShadowRoot();
3954 m_creatingControls = false;
3957 void HTMLMediaElement::updateCaptionContainer()
3959 LOG(Media, "HTMLMediaElement::updateCaptionContainer(%p)", this);
3960 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
3961 if (m_haveSetUpCaptionContainer)
3964 Page* page = document().page();
3968 DOMWrapperWorld& world = ensureIsolatedWorld();
3970 if (!ensureMediaControlsInjectedScript())
3973 ensureMediaControlsShadowRoot();
3975 if (!m_mediaControlsHost)
3976 m_mediaControlsHost = MediaControlsHost::create(this);
3978 ScriptController& scriptController = document().frame()->script();
3979 JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world));
3980 JSC::ExecState* exec = globalObject->globalExec();
3981 JSC::JSLockHolder lock(exec);
3983 JSC::JSValue controllerValue = controllerJSValue(*exec, *globalObject, *this);
3984 JSC::JSObject* controllerObject = JSC::jsDynamicCast<JSC::JSObject*>(controllerValue);
3985 if (!controllerObject)
3988 // The media controls script must provide a method on the Controller object with the following details.
3989 // Name: updateCaptionContainer
3994 JSC::JSValue methodValue = controllerObject->get(exec, JSC::Identifier::fromString(exec, "updateCaptionContainer"));
3995 JSC::JSObject* methodObject = JSC::jsDynamicCast<JSC::JSObject*>(methodValue);
3999 JSC::CallData callData;
4000 JSC::CallType callType = methodObject->methodTable()->getCallData(methodObject, callData);
4001 if (callType == JSC::CallType::None)
4004 JSC::MarkedArgumentBuffer noArguments;
4005 JSC::call(exec, methodObject, callType, callData, controllerObject, noArguments);
4006 exec->clearException();
4008 m_haveSetUpCaptionContainer = true;
4012 void HTMLMediaElement::layoutSizeChanged()
4014 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
4015 auto task = [this, protectedThis = makeRef(*this)] {
4016 if (ShadowRoot* root = userAgentShadowRoot())
4017 root->dispatchEvent(Event::create("resize", false, false));
4019 m_resizeTaskQueue.enqueueTask(WTFMove(task));
4022 if (!m_receivedLayoutSizeChanged) {
4023 m_receivedLayoutSizeChanged = true;
4024 scheduleUpdatePlaybackControlsManager();
4028 void HTMLMediaElement::visibilityDidChange()
4030 updateShouldAutoplay();
4033 void HTMLMediaElement::setSelectedTextTrack(TextTrack* trackToSelect)
4035 TextTrackList& trackList = textTracks();
4036 if (!trackList.length())
4039 if (trackToSelect != TextTrack::captionMenuOffItem() && trackToSelect != TextTrack::captionMenuAutomaticItem()) {
4040 if (!trackToSelect || !trackList.contains(*trackToSelect))
4043 for (int i = 0, length = trackList.length(); i < length; ++i) {
4044 auto& track = *trackList.item(i);
4045 if (&track != trackToSelect)
4046 track.setMode(TextTrack::Mode::Disabled);
4048 track.setMode(TextTrack::Mode::Showing);
4050 } else if (trackToSelect == TextTrack::captionMenuOffItem()) {
4051 for (int i = 0, length = trackList.length(); i < length; ++i)
4052 trackList.item(i)->setMode(TextTrack::Mode::Disabled);
4055 if (!document().page())
4058 auto& captionPreferences = document().page()->group().captionPreferences();
4059 CaptionUserPreferences::CaptionDisplayMode displayMode;
4060 if (trackToSelect == TextTrack::captionMenuOffItem())
4061 displayMode = CaptionUserPreferences::ForcedOnly;
4062 else if (trackToSelect == TextTrack::captionMenuAutomaticItem())
4063 displayMode = CaptionUserPreferences::Automatic;
4065 displayMode = CaptionUserPreferences::AlwaysOn;
4066 if (trackToSelect->language().length())
4067 captionPreferences.setPreferredLanguage(trackToSelect->language());
4070 captionPreferences.setCaptionDisplayMode(displayMode);
4073 void HTMLMediaElement::configureTextTracks()
4075 TrackGroup captionAndSubtitleTracks(TrackGroup::CaptionsAndSubtitles);
4076 TrackGroup descriptionTracks(TrackGroup::Description);
4077 TrackGroup chapterTracks(TrackGroup::Chapter);
4078 TrackGroup metadataTracks(TrackGroup::Metadata);
4079 TrackGroup otherTracks(TrackGroup::Other);
4084 for (size_t i = 0; i < m_textTracks->length(); ++i) {
4085 RefPtr<TextTrack> textTrack = m_textTracks->item(i);
4089 auto kind = textTrack->kind();
4090 TrackGroup* currentGroup;
4091 if (kind == TextTrack::Kind::Subtitles || kind == TextTrack::Kind::Captions || kind == TextTrack::Kind::Forced)
4092 currentGroup = &captionAndSubtitleTracks;
4093 else if (kind == TextTrack::Kind::Descriptions)
4094 currentGroup = &descriptionTracks;
4095 else if (kind == TextTrack::Kind::Chapters)
4096 currentGroup = &chapterTracks;
4097 else if (kind == TextTrack::Kind::Metadata)
4098 currentGroup = &metadataTracks;
4100 currentGroup = &otherTracks;
4102 if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::Mode::Showing)
4103 currentGroup->visibleTrack = textTrack;
4104 if (!currentGroup->defaultTrack && textTrack->isDefault())
4105 currentGroup->defaultTrack = textTrack;
4107 // Do not add this track to the group if it has already been automatically configured
4108 // as we only want to call configureTextTrack once per track so that adding another