2 * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 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 COMPUTER, 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 COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "core/html/HTMLMediaElement.h"
29 #include "bindings/core/v8/ExceptionState.h"
30 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
31 #include "bindings/core/v8/ScriptController.h"
32 #include "bindings/core/v8/ScriptEventListener.h"
33 #include "core/HTMLNames.h"
34 #include "core/css/MediaList.h"
35 #include "core/dom/Attribute.h"
36 #include "core/dom/ElementTraversal.h"
37 #include "core/dom/ExceptionCode.h"
38 #include "core/dom/Fullscreen.h"
39 #include "core/dom/shadow/ShadowRoot.h"
40 #include "core/events/Event.h"
41 #include "core/frame/LocalFrame.h"
42 #include "core/frame/Settings.h"
43 #include "core/frame/UseCounter.h"
44 #include "core/frame/csp/ContentSecurityPolicy.h"
45 #include "core/html/HTMLMediaSource.h"
46 #include "core/html/HTMLSourceElement.h"
47 #include "core/html/HTMLTrackElement.h"
48 #include "core/html/MediaController.h"
49 #include "core/html/MediaError.h"
50 #include "core/html/MediaFragmentURIParser.h"
51 #include "core/html/TimeRanges.h"
52 #include "core/html/shadow/MediaControls.h"
53 #include "core/html/track/AudioTrack.h"
54 #include "core/html/track/AudioTrackList.h"
55 #include "core/html/track/InbandTextTrack.h"
56 #include "core/html/track/TextTrackCueList.h"
57 #include "core/html/track/TextTrackList.h"
58 #include "core/html/track/VideoTrack.h"
59 #include "core/html/track/VideoTrackList.h"
60 #include "core/loader/FrameLoader.h"
61 #include "core/rendering/RenderVideo.h"
62 #include "core/rendering/RenderView.h"
63 #include "core/rendering/compositing/RenderLayerCompositor.h"
64 #include "platform/ContentType.h"
65 #include "platform/Language.h"
66 #include "platform/Logging.h"
67 #include "platform/MIMETypeFromURL.h"
68 #include "platform/MIMETypeRegistry.h"
69 #include "platform/RuntimeEnabledFeatures.h"
70 #include "platform/UserGestureIndicator.h"
71 #include "platform/graphics/GraphicsLayer.h"
72 #include "platform/weborigin/SecurityOrigin.h"
73 #include "public/platform/Platform.h"
74 #include "public/platform/WebContentDecryptionModule.h"
75 #include "public/platform/WebInbandTextTrack.h"
76 #include "wtf/CurrentTime.h"
77 #include "wtf/MathExtras.h"
78 #include "wtf/NonCopyingSort.h"
79 #include "wtf/Uint8Array.h"
80 #include "wtf/text/CString.h"
84 #include "platform/audio/AudioSourceProvider.h"
85 #include "platform/audio/AudioSourceProviderClient.h"
88 using blink::WebInbandTextTrack;
89 using blink::WebMediaPlayer;
90 using blink::WebMimeRegistry;
91 using blink::WebMediaPlayerClient;
96 static String urlForLoggingMedia(const KURL& url)
98 static const unsigned maximumURLLengthForLogging = 128;
100 if (url.string().length() < maximumURLLengthForLogging)
102 return url.string().substring(0, maximumURLLengthForLogging) + "...";
105 static const char* boolString(bool val)
107 return val ? "true" : "false";
111 #ifndef LOG_MEDIA_EVENTS
112 // Default to not logging events because so many are generated they can overwhelm the rest of
114 #define LOG_MEDIA_EVENTS 0
117 #ifndef LOG_CACHED_TIME_WARNINGS
118 // Default to not logging warnings about excessive drift in the cached media time because it adds a
119 // fair amount of overhead and logging.
120 #define LOG_CACHED_TIME_WARNINGS 0
123 // URL protocol used to signal that the media source API is being used.
124 static const char mediaSourceBlobProtocol[] = "blob";
126 using namespace HTMLNames;
128 typedef WillBeHeapHashSet<RawPtrWillBeWeakMember<HTMLMediaElement>> WeakMediaElementSet;
129 typedef WillBeHeapHashMap<RawPtrWillBeWeakMember<Document>, WeakMediaElementSet> DocumentElementSetMap;
130 static DocumentElementSetMap& documentToElementSetMap()
132 DEFINE_STATIC_LOCAL(OwnPtrWillBePersistent<DocumentElementSetMap>, map, (adoptPtrWillBeNoop(new DocumentElementSetMap())));
136 static void addElementToDocumentMap(HTMLMediaElement* element, Document* document)
138 DocumentElementSetMap& map = documentToElementSetMap();
139 WeakMediaElementSet set = map.take(document);
141 map.add(document, set);
144 static void removeElementFromDocumentMap(HTMLMediaElement* element, Document* document)
146 DocumentElementSetMap& map = documentToElementSetMap();
147 WeakMediaElementSet set = map.take(document);
150 map.add(document, set);
153 class TrackDisplayUpdateScope {
156 TrackDisplayUpdateScope(HTMLMediaElement* mediaElement)
158 m_mediaElement = mediaElement;
159 m_mediaElement->beginIgnoringTrackDisplayUpdateRequests();
161 ~TrackDisplayUpdateScope()
163 ASSERT(m_mediaElement);
164 m_mediaElement->endIgnoringTrackDisplayUpdateRequests();
168 RawPtrWillBeMember<HTMLMediaElement> m_mediaElement;
171 class AudioSourceProviderClientLockScope {
174 #if ENABLE(WEB_AUDIO)
175 AudioSourceProviderClientLockScope(HTMLMediaElement& element)
176 : m_client(element.audioSourceNode())
181 ~AudioSourceProviderClientLockScope()
188 Member<AudioSourceProviderClient> m_client;
190 explicit AudioSourceProviderClientLockScope(HTMLMediaElement&) { }
191 ~AudioSourceProviderClientLockScope() { }
195 static const AtomicString& AudioKindToString(WebMediaPlayerClient::AudioTrackKind kind)
198 case WebMediaPlayerClient::AudioTrackKindNone:
200 case WebMediaPlayerClient::AudioTrackKindAlternative:
201 return AudioTrack::alternativeKeyword();
202 case WebMediaPlayerClient::AudioTrackKindDescriptions:
203 return AudioTrack::descriptionsKeyword();
204 case WebMediaPlayerClient::AudioTrackKindMain:
205 return AudioTrack::mainKeyword();
206 case WebMediaPlayerClient::AudioTrackKindMainDescriptions:
207 return AudioTrack::mainDescriptionsKeyword();
208 case WebMediaPlayerClient::AudioTrackKindTranslation:
209 return AudioTrack::translationKeyword();
210 case WebMediaPlayerClient::AudioTrackKindCommentary:
211 return AudioTrack::commentaryKeyword();
214 ASSERT_NOT_REACHED();
218 static const AtomicString& VideoKindToString(WebMediaPlayerClient::VideoTrackKind kind)
221 case WebMediaPlayerClient::VideoTrackKindNone:
223 case WebMediaPlayerClient::VideoTrackKindAlternative:
224 return VideoTrack::alternativeKeyword();
225 case WebMediaPlayerClient::VideoTrackKindCaptions:
226 return VideoTrack::captionsKeyword();
227 case WebMediaPlayerClient::VideoTrackKindMain:
228 return VideoTrack::mainKeyword();
229 case WebMediaPlayerClient::VideoTrackKindSign:
230 return VideoTrack::signKeyword();
231 case WebMediaPlayerClient::VideoTrackKindSubtitles:
232 return VideoTrack::subtitlesKeyword();
233 case WebMediaPlayerClient::VideoTrackKindCommentary:
234 return VideoTrack::commentaryKeyword();
237 ASSERT_NOT_REACHED();
241 static bool canLoadURL(const KURL& url, const ContentType& contentType, const String& keySystem)
243 DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
245 String contentMIMEType = contentType.type().lower();
246 String contentTypeCodecs = contentType.parameter(codecs);
248 // If the MIME type is missing or is not meaningful, try to figure it out from the URL.
249 if (contentMIMEType.isEmpty() || contentMIMEType == "application/octet-stream" || contentMIMEType == "text/plain") {
250 if (url.protocolIsData())
251 contentMIMEType = mimeTypeFromDataURL(url.string());
254 // If no MIME type is specified, always attempt to load.
255 if (contentMIMEType.isEmpty())
258 // 4.8.10.3 MIME types - In the absence of a specification to the contrary, the MIME type "application/octet-stream"
259 // when used with parameters, e.g. "application/octet-stream;codecs=theora", is a type that the user agent knows
261 if (contentMIMEType != "application/octet-stream" || contentTypeCodecs.isEmpty()) {
262 WebMimeRegistry::SupportsType supported = blink::Platform::current()->mimeRegistry()->supportsMediaMIMEType(contentMIMEType, contentTypeCodecs, keySystem.lower());
263 return supported > WebMimeRegistry::IsNotSupported;
269 // These values are used for a histogram. Do not reorder.
270 enum AutoplayMetrics {
271 // Media element with autoplay seen.
272 AutoplayMediaFound = 0,
273 // Autoplay enabled and user stopped media play at any point.
275 // Autoplay enabled but user bailed out on media play early.
277 // Autoplay disabled but user manually started media.
278 AutoplayManualStart = 3,
279 // Autoplay was (re)enabled through a user-gesture triggered load()
280 AutoplayEnabledThroughLoad = 4,
281 // This enum value must be last.
282 NumberOfAutoplayMetrics,
285 static void recordAutoplayMetric(AutoplayMetrics metric)
287 blink::Platform::current()->histogramEnumeration("Blink.MediaElement.Autoplay", metric, NumberOfAutoplayMetrics);
290 WebMimeRegistry::SupportsType HTMLMediaElement::supportsType(const ContentType& contentType, const String& keySystem)
292 DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
294 if (!RuntimeEnabledFeatures::mediaEnabled())
295 return WebMimeRegistry::IsNotSupported;
297 String type = contentType.type().lower();
298 // The codecs string is not lower-cased because MP4 values are case sensitive
299 // per http://tools.ietf.org/html/rfc4281#page-7.
300 String typeCodecs = contentType.parameter(codecs);
301 String system = keySystem.lower();
304 return WebMimeRegistry::IsNotSupported;
306 // 4.8.10.3 MIME types - The canPlayType(type) method must return the empty string if type is a type that the
307 // user agent knows it cannot render or is the type "application/octet-stream"
308 if (type == "application/octet-stream")
309 return WebMimeRegistry::IsNotSupported;
311 return blink::Platform::current()->mimeRegistry()->supportsMediaMIMEType(type, typeCodecs, system);
314 URLRegistry* HTMLMediaElement::s_mediaStreamRegistry = 0;
316 void HTMLMediaElement::setMediaStreamRegistry(URLRegistry* registry)
318 ASSERT(!s_mediaStreamRegistry);
319 s_mediaStreamRegistry = registry;
322 bool HTMLMediaElement::isMediaStreamURL(const String& url)
324 return s_mediaStreamRegistry ? s_mediaStreamRegistry->contains(url) : false;
327 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document)
328 : HTMLElement(tagName, document)
329 , ActiveDOMObject(&document)
330 , m_loadTimer(this, &HTMLMediaElement::loadTimerFired)
331 , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired)
332 , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired)
333 , m_audioTracksTimer(this, &HTMLMediaElement::audioTracksTimerFired)
334 , m_playedTimeRanges()
335 , m_asyncEventQueue(GenericEventQueue::create(this))
336 , m_playbackRate(1.0f)
337 , m_defaultPlaybackRate(1.0f)
338 , m_networkState(NETWORK_EMPTY)
339 , m_readyState(HAVE_NOTHING)
340 , m_readyStateMaximum(HAVE_NOTHING)
343 , m_previousProgressTime(std::numeric_limits<double>::max())
344 , m_duration(std::numeric_limits<double>::quiet_NaN())
345 , m_lastTimeUpdateEventWallTime(0)
346 , m_lastTimeUpdateEventMovieTime(0)
347 , m_defaultPlaybackStartPosition(0)
348 , m_loadState(WaitingForSource)
349 , m_deferredLoadState(NotDeferred)
350 , m_deferredLoadTimer(this, &HTMLMediaElement::deferredLoadTimerFired)
351 , m_webLayer(nullptr)
352 , m_preload(MediaPlayer::Auto)
353 , m_displayMode(Unknown)
354 , m_cachedTime(MediaPlayer::invalidTime())
355 , m_fragmentEndTime(MediaPlayer::invalidTime())
356 , m_pendingActionFlags(0)
357 , m_userGestureRequiredForPlay(false)
359 , m_shouldDelayLoadEvent(false)
360 , m_haveFiredLoadedData(false)
362 , m_autoplaying(true)
366 , m_sentStalledEvent(false)
367 , m_sentEndEvent(false)
368 , m_closedCaptionsVisible(false)
369 , m_completelyLoaded(false)
370 , m_havePreparedToPlay(false)
371 , m_tracksAreReady(true)
372 , m_haveVisibleTextTrack(false)
373 , m_processingPreferenceChange(false)
374 , m_remoteRoutesAvailable(false)
375 , m_playingRemotely(false)
377 , m_isFinalizing(false)
378 , m_closeMediaSourceWhenFinalizing(false)
380 , m_lastTextTrackUpdateTime(-1)
381 , m_initialPlayWithoutUserGestures(false)
382 , m_autoplayMediaCounted(false)
383 , m_audioTracks(AudioTrackList::create(*this))
384 , m_videoTracks(VideoTrackList::create(*this))
385 , m_textTracks(nullptr)
386 , m_ignoreTrackDisplayUpdate(0)
387 #if ENABLE(WEB_AUDIO)
388 , m_audioSourceNode(nullptr)
391 ASSERT(RuntimeEnabledFeatures::mediaEnabled());
393 WTF_LOG(Media, "HTMLMediaElement::HTMLMediaElement(%p)", this);
395 if (document.settings() && document.settings()->mediaPlaybackRequiresUserGesture())
396 m_userGestureRequiredForPlay = true;
398 setHasCustomStyleCallbacks();
399 addElementToDocumentMap(this, &document);
402 HTMLMediaElement::~HTMLMediaElement()
404 WTF_LOG(Media, "HTMLMediaElement::~HTMLMediaElement(%p)", this);
407 // If the HTMLMediaElement dies with the document we are not
408 // allowed to touch the document to adjust delay load event counts
409 // because the document could have been already
410 // destructed. However, if the HTMLMediaElement dies with the
411 // document there is no need to change the delayed load counts
412 // because no load event will fire anyway. If the document is
413 // still alive we do have to decrement the load delay counts. We
414 // determine if the document is alive via the ActiveDOMObject
415 // which is a context lifecycle observer. If the Document has been
416 // destructed ActiveDOMObject::executionContext() returns 0.
417 if (ActiveDOMObject::executionContext())
418 setShouldDelayLoadEvent(false);
420 // HTMLMediaElement and m_asyncEventQueue always become unreachable
421 // together. So HTMLMediaElemenet and m_asyncEventQueue are destructed in
422 // the same GC. We don't need to close it explicitly in Oilpan.
423 m_asyncEventQueue->close();
425 setShouldDelayLoadEvent(false);
428 m_textTracks->clearOwner();
429 m_audioTracks->shutdown();
430 m_videoTracks->shutdown();
432 if (m_mediaController) {
433 m_mediaController->removeMediaElement(this);
434 m_mediaController = nullptr;
439 if (m_closeMediaSourceWhenFinalizing)
444 removeElementFromDocumentMap(this, &document());
447 // Destroying the player may cause a resource load to be canceled,
448 // which could result in userCancelledLoad() being called back.
449 // Setting m_completelyLoaded ensures that such a call will not cause
450 // us to dispatch an abort event, which would result in a crash.
451 // See http://crbug.com/233654 for more details.
452 m_completelyLoaded = true;
454 // With Oilpan load events on the Document are always delayed during
455 // sweeping so we don't need to explicitly increment and decrement
456 // load event delay counts.
458 // Destroying the player may cause a resource load to be canceled,
459 // which could result in Document::dispatchWindowLoadEvent() being
460 // called via ResourceFetch::didLoadResource() then
461 // FrameLoader::loadDone(). To prevent load event dispatching during
462 // object destruction, we use Document::incrementLoadEventDelayCount().
463 // See http://crbug.com/275223 for more details.
464 document().incrementLoadEventDelayCount();
468 // Oilpan: the player must be released, but the player object
469 // cannot safely access this player client any longer as parts of
470 // it may have been finalized already (like the media element's
471 // supplementable table.) Handled for now by entering an
472 // is-finalizing state, which is explicitly checked for if the
473 // player tries to access the media element during shutdown.
475 // FIXME: Oilpan: move the media player to the heap instead and
476 // avoid having to finalize it from here; this whole #if block
477 // could then be removed (along with the state bit it depends on.)
479 m_isFinalizing = true;
482 // m_audioSourceNode is explicitly cleared by AudioNode::dispose().
483 // Since AudioNode::dispose() is guaranteed to be always called before
484 // the AudioNode is destructed, m_audioSourceNode is explicitly cleared
485 // even if the AudioNode and the HTMLMediaElement die together.
486 #if ENABLE(WEB_AUDIO)
487 ASSERT(!m_audioSourceNode);
489 clearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
492 document().decrementLoadEventDelayCount();
497 void HTMLMediaElement::setCloseMediaSourceWhenFinalizing()
499 ASSERT(!m_closeMediaSourceWhenFinalizing);
500 m_closeMediaSourceWhenFinalizing = true;
504 void HTMLMediaElement::didMoveToNewDocument(Document& oldDocument)
506 WTF_LOG(Media, "HTMLMediaElement::didMoveToNewDocument(%p)", this);
508 if (m_shouldDelayLoadEvent) {
509 document().incrementLoadEventDelayCount();
510 // Note: Keeping the load event delay count increment on oldDocument that was added
511 // when m_shouldDelayLoadEvent was set so that destruction of m_player can not
512 // cause load event dispatching in oldDocument.
514 // Incrementing the load event delay count so that destruction of m_player can not
515 // cause load event dispatching in oldDocument.
516 oldDocument.incrementLoadEventDelayCount();
519 removeElementFromDocumentMap(this, &oldDocument);
520 addElementToDocumentMap(this, &document());
522 // FIXME: This is a temporary fix to prevent this object from causing the
523 // MediaPlayer to dereference LocalFrame and FrameLoader pointers from the
524 // previous document. A proper fix would provide a mechanism to allow this
525 // object to refresh the MediaPlayer's LocalFrame and FrameLoader references on
526 // document changes so that playback can be resumed properly.
529 // Decrement the load event delay count on oldDocument now that m_player has been destroyed
530 // and there is no risk of dispatching a load event from within the destructor.
531 oldDocument.decrementLoadEventDelayCount();
533 ActiveDOMObject::didMoveToNewExecutionContext(&document());
534 HTMLElement::didMoveToNewDocument(oldDocument);
537 bool HTMLMediaElement::supportsFocus() const
539 if (ownerDocument()->isMediaDocument())
542 // If no controls specified, we should still be able to focus the element if it has tabIndex.
543 return shouldShowControls() || HTMLElement::supportsFocus();
546 bool HTMLMediaElement::isMouseFocusable() const
551 void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
553 if (name == srcAttr) {
554 // Trigger a reload, as long as the 'src' attribute is present.
555 if (!value.isNull()) {
556 clearMediaPlayer(LoadMediaResource);
557 scheduleDelayedAction(LoadMediaResource);
559 } else if (name == controlsAttr) {
560 configureMediaControls();
561 } else if (name == preloadAttr) {
562 if (equalIgnoringCase(value, "none")) {
563 m_preload = MediaPlayer::None;
564 } else if (equalIgnoringCase(value, "metadata")) {
565 m_preload = MediaPlayer::MetaData;
567 // The spec does not define an "invalid value default" but "auto" is suggested as the
568 // "missing value default", so use it for everything except "none" and "metadata"
569 m_preload = MediaPlayer::Auto;
572 // The attribute must be ignored if the autoplay attribute is present
573 if (!autoplay() && m_player)
576 } else if (name == mediagroupAttr && RuntimeEnabledFeatures::mediaControllerEnabled()) {
577 setMediaGroup(value);
579 HTMLElement::parseAttribute(name, value);
583 void HTMLMediaElement::finishParsingChildren()
585 HTMLElement::finishParsingChildren();
587 if (Traversal<HTMLTrackElement>::firstChild(*this))
588 scheduleDelayedAction(LoadTextTrackResource);
591 bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style)
593 return shouldShowControls() && HTMLElement::rendererIsNeeded(style);
596 RenderObject* HTMLMediaElement::createRenderer(RenderStyle*)
598 return new RenderMedia(this);
601 Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode* insertionPoint)
603 WTF_LOG(Media, "HTMLMediaElement::insertedInto(%p, %p)", this, insertionPoint);
605 HTMLElement::insertedInto(insertionPoint);
606 if (insertionPoint->inDocument()) {
609 if (!getAttribute(srcAttr).isEmpty() && m_networkState == NETWORK_EMPTY)
610 scheduleDelayedAction(LoadMediaResource);
613 return InsertionShouldCallDidNotifySubtreeInsertions;
616 void HTMLMediaElement::didNotifySubtreeInsertionsToDocument()
618 configureMediaControls();
621 void HTMLMediaElement::removedFrom(ContainerNode* insertionPoint)
623 WTF_LOG(Media, "HTMLMediaElement::removedFrom(%p, %p)", this, insertionPoint);
626 if (insertionPoint->inDocument() && insertionPoint->document().isActive()) {
627 configureMediaControls();
628 if (m_networkState > NETWORK_EMPTY)
632 HTMLElement::removedFrom(insertionPoint);
635 void HTMLMediaElement::attach(const AttachContext& context)
637 HTMLElement::attach(context);
640 renderer()->updateFromElement();
643 void HTMLMediaElement::didRecalcStyle(StyleRecalcChange)
646 renderer()->updateFromElement();
649 void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType)
651 WTF_LOG(Media, "HTMLMediaElement::scheduleDelayedAction(%p)", this);
653 if ((actionType & LoadMediaResource) && !(m_pendingActionFlags & LoadMediaResource)) {
655 m_pendingActionFlags |= LoadMediaResource;
658 if (actionType & LoadTextTrackResource)
659 m_pendingActionFlags |= LoadTextTrackResource;
661 if (!m_loadTimer.isActive())
662 m_loadTimer.startOneShot(0, FROM_HERE);
665 void HTMLMediaElement::scheduleNextSourceChild()
667 // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
668 m_pendingActionFlags |= LoadMediaResource;
669 m_loadTimer.startOneShot(0, FROM_HERE);
672 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
674 scheduleEvent(Event::createCancelable(eventName));
677 void HTMLMediaElement::scheduleEvent(PassRefPtrWillBeRawPtr<Event> event)
680 WTF_LOG(Media, "HTMLMediaElement::scheduleEvent(%p) - scheduling '%s'", this, event->type().ascii().data());
682 m_asyncEventQueue->enqueueEvent(event);
685 void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*)
687 if (m_pendingActionFlags & LoadTextTrackResource)
688 configureTextTracks();
690 if (m_pendingActionFlags & LoadMediaResource) {
691 if (m_loadState == LoadingFromSourceElement)
692 loadNextSourceChild();
697 m_pendingActionFlags = 0;
700 PassRefPtrWillBeRawPtr<MediaError> HTMLMediaElement::error() const
705 void HTMLMediaElement::setSrc(const AtomicString& url)
707 setAttribute(srcAttr, url);
710 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
712 return m_networkState;
715 String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySystem) const
717 if (!keySystem.isNull())
718 UseCounter::count(document(), UseCounter::CanPlayTypeKeySystem);
720 WebMimeRegistry::SupportsType support = supportsType(ContentType(mimeType), keySystem);
725 case WebMimeRegistry::IsNotSupported:
726 canPlay = emptyString();
728 case WebMimeRegistry::MayBeSupported:
731 case WebMimeRegistry::IsSupported:
732 canPlay = "probably";
736 WTF_LOG(Media, "HTMLMediaElement::canPlayType(%p, %s, %s) -> %s", this, mimeType.utf8().data(), keySystem.utf8().data(), canPlay.utf8().data());
741 void HTMLMediaElement::load()
743 WTF_LOG(Media, "HTMLMediaElement::load(%p)", this);
745 if (m_initialPlayWithoutUserGestures && m_playing)
746 gesturelessInitialPlayHalted();
748 if (UserGestureIndicator::processingUserGesture() && m_userGestureRequiredForPlay) {
749 recordAutoplayMetric(AutoplayEnabledThroughLoad);
750 m_userGestureRequiredForPlay = false;
751 // While usergesture-initiated load()s technically count as autoplayed,
752 // they don't feel like such to the users and hence we don't want to
753 // count them for the purposes of metrics.
754 m_autoplayMediaCounted = true;
762 void HTMLMediaElement::prepareForLoad()
764 WTF_LOG(Media, "HTMLMediaElement::prepareForLoad(%p)", this);
766 // Perform the cleanup required for the resource load algorithm to run.
767 stopPeriodicTimers();
769 cancelDeferredLoad();
770 // FIXME: Figure out appropriate place to reset LoadTextTrackResource if necessary and set m_pendingActionFlags to 0 here.
771 m_pendingActionFlags &= ~LoadMediaResource;
772 m_sentEndEvent = false;
773 m_sentStalledEvent = false;
774 m_haveFiredLoadedData = false;
775 m_completelyLoaded = false;
776 m_havePreparedToPlay = false;
777 m_displayMode = Unknown;
779 // 1 - Abort any already-running instance of the resource selection algorithm for this element.
780 m_loadState = WaitingForSource;
781 m_currentSourceNode = nullptr;
783 // 2 - If there are any tasks from the media element's media element event task source in
784 // one of the task queues, then remove those tasks.
785 cancelPendingEventsAndCallbacks();
787 // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
788 // a task to fire a simple event named abort at the media element.
789 if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
790 scheduleEvent(EventTypeNames::abort);
794 // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
795 if (m_networkState != NETWORK_EMPTY) {
796 // 4.1 - Queue a task to fire a simple event named emptied at the media element.
797 scheduleEvent(EventTypeNames::emptied);
799 // 4.2 - If a fetching process is in progress for the media element, the user agent should stop it.
800 m_networkState = NETWORK_EMPTY;
802 // 4.3 - Forget the media element's media-resource-specific tracks.
803 forgetResourceSpecificTracks();
805 // 4.4 - If readyState is not set to HAVE_NOTHING, then set it to that state.
806 m_readyState = HAVE_NOTHING;
807 m_readyStateMaximum = HAVE_NOTHING;
809 // 4.5 - If the paused attribute is false, then set it to true.
812 // 4.6 - If seeking is true, set it to false.
815 // 4.7 - Set the current playback position to 0.
816 // Set the official playback position to 0.
817 // If this changed the official playback position, then queue a task to fire a simple event named timeupdate at the media element.
818 // FIXME: Add support for firing this event.
820 // 4.8 - Set the initial playback position to 0.
821 // FIXME: Make this less subtle. The position only becomes 0 because the ready state is HAVE_NOTHING.
822 invalidateCachedTime();
824 // 4.9 - Set the timeline offset to Not-a-Number (NaN).
825 // 4.10 - Update the duration attribute to Not-a-Number (NaN).
828 updateMediaController();
829 updateActiveTextTrackCues(0);
832 // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
833 setPlaybackRate(defaultPlaybackRate());
835 // 6 - Set the error attribute to null and the autoplaying flag to true.
837 m_autoplaying = true;
839 // 7 - Invoke the media element's resource selection algorithm.
841 // 8 - Note: Playback of any previously playing media resource for this element stops.
843 // The resource selection algorithm
844 // 1 - Set the networkState to NETWORK_NO_SOURCE
845 m_networkState = NETWORK_NO_SOURCE;
847 // 2 - Asynchronously await a stable state.
849 m_playedTimeRanges = TimeRanges::create();
851 // FIXME: Investigate whether these can be moved into m_networkState != NETWORK_EMPTY block above
852 // so they are closer to the relevant spec steps.
854 m_duration = std::numeric_limits<double>::quiet_NaN();
856 // The spec doesn't say to block the load event until we actually run the asynchronous section
857 // algorithm, but do it now because we won't start that until after the timer fires and the
858 // event may have already fired by then.
859 setShouldDelayLoadEvent(true);
860 if (hasMediaControls())
861 mediaControls()->reset();
864 void HTMLMediaElement::loadInternal()
866 // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the
867 // disabled state when the element's resource selection algorithm last started".
868 m_textTracksWhenResourceSelectionBegan.clear();
870 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
871 TextTrack* track = m_textTracks->item(i);
872 if (track->mode() != TextTrack::disabledKeyword())
873 m_textTracksWhenResourceSelectionBegan.append(track);
877 selectMediaResource();
880 void HTMLMediaElement::selectMediaResource()
882 WTF_LOG(Media, "HTMLMediaElement::selectMediaResource(%p)", this);
884 enum Mode { attribute, children };
886 // 3 - If the media element has a src attribute, then let mode be attribute.
887 Mode mode = attribute;
888 if (!fastHasAttribute(srcAttr)) {
889 // Otherwise, if the media element does not have a src attribute but has a source
890 // element child, then let mode be children and let candidate be the first such
891 // source element child in tree order.
892 if (HTMLSourceElement* element = Traversal<HTMLSourceElement>::firstChild(*this)) {
894 m_nextChildNodeToConsider = element;
895 m_currentSourceNode = nullptr;
897 // Otherwise the media element has neither a src attribute nor a source element
898 // child: set the networkState to NETWORK_EMPTY, and abort these steps; the
899 // synchronous section ends.
900 m_loadState = WaitingForSource;
901 setShouldDelayLoadEvent(false);
902 m_networkState = NETWORK_EMPTY;
904 WTF_LOG(Media, "HTMLMediaElement::selectMediaResource(%p), nothing to load", this);
909 // 4 - Set the media element's delaying-the-load-event flag to true (this delays the load event),
910 // and set its networkState to NETWORK_LOADING.
911 setShouldDelayLoadEvent(true);
912 m_networkState = NETWORK_LOADING;
914 // 5 - Queue a task to fire a simple event named loadstart at the media element.
915 scheduleEvent(EventTypeNames::loadstart);
917 // 6 - If mode is attribute, then run these substeps
918 if (mode == attribute) {
919 m_loadState = LoadingFromSrcAttr;
921 // If the src attribute's value is the empty string ... jump down to the failed step below
922 KURL mediaURL = getNonEmptyURLAttribute(srcAttr);
923 if (mediaURL.isEmpty()) {
924 mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError);
925 WTF_LOG(Media, "HTMLMediaElement::selectMediaResource(%p), empty 'src'", this);
929 if (!isSafeToLoadURL(mediaURL, Complain)) {
930 mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError);
934 // No type or key system information is available when the url comes
935 // from the 'src' attribute so MediaPlayer
936 // will have to pick a media engine based on the file extension.
937 ContentType contentType((String()));
938 loadResource(mediaURL, contentType, String());
939 WTF_LOG(Media, "HTMLMediaElement::selectMediaResource(%p), using 'src' attribute url", this);
943 // Otherwise, the source elements will be used
944 loadNextSourceChild();
947 void HTMLMediaElement::loadNextSourceChild()
949 ContentType contentType((String()));
951 KURL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain);
952 if (!mediaURL.isValid()) {
953 waitForSourceChange();
957 // Recreate the media player for the new url
960 m_loadState = LoadingFromSourceElement;
961 loadResource(mediaURL, contentType, keySystem);
964 void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType, const String& keySystem)
966 ASSERT(isSafeToLoadURL(url, Complain));
968 WTF_LOG(Media, "HTMLMediaElement::loadResource(%p, %s, %s, %s)", this, urlForLoggingMedia(url).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data());
970 LocalFrame* frame = document().frame();
972 mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError);
976 // The resource fetch algorithm
977 m_networkState = NETWORK_LOADING;
979 // Set m_currentSrc *before* changing to the cache url, the fact that we are loading from the app
980 // cache is an internal detail not exposed through the media element API.
983 WTF_LOG(Media, "HTMLMediaElement::loadResource(%p) - m_currentSrc -> %s", this, urlForLoggingMedia(m_currentSrc).utf8().data());
985 startProgressEventTimer();
987 // Reset display mode to force a recalculation of what to show because we are resetting the player.
988 setDisplayMode(Unknown);
993 if (fastHasAttribute(mutedAttr))
997 ASSERT(!m_mediaSource);
999 bool attemptLoad = true;
1001 if (url.protocolIs(mediaSourceBlobProtocol)) {
1002 if (isMediaStreamURL(url.string())) {
1003 m_userGestureRequiredForPlay = false;
1005 m_mediaSource = HTMLMediaSource::lookup(url.string());
1007 if (m_mediaSource) {
1008 if (!m_mediaSource->attachToElement(this)) {
1009 // Forget our reference to the MediaSource, so we leave it alone
1010 // while processing remainder of load failure.
1011 m_mediaSource = nullptr;
1012 attemptLoad = false;
1018 if (attemptLoad && canLoadURL(url, contentType, keySystem)) {
1019 ASSERT(!webMediaPlayer());
1021 if (!m_havePreparedToPlay && !autoplay() && m_preload == MediaPlayer::None) {
1022 WTF_LOG(Media, "HTMLMediaElement::loadResource(%p) : Delaying load because preload == 'none'", this);
1028 mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError);
1031 // If there is no poster to display, allow the media engine to render video frames as soon as
1032 // they are available.
1033 updateDisplayState();
1036 renderer()->updateFromElement();
1039 void HTMLMediaElement::startPlayerLoad()
1041 // Filter out user:pass as those two URL components aren't
1042 // considered for media resource fetches (including for the CORS
1043 // use-credentials mode.) That behavior aligns with Gecko, with IE
1044 // being more restrictive and not allowing fetches to such URLs.
1046 // Spec reference: http://whatwg.org/c/#concept-media-load-resource
1048 // FIXME: when the HTML spec switches to specifying resource
1049 // fetches in terms of Fetch (http://fetch.spec.whatwg.org), and
1050 // along with that potentially also specifying a setting for its
1051 // 'authentication flag' to control how user:pass embedded in a
1052 // media resource URL should be treated, then update the handling
1054 KURL requestURL = m_currentSrc;
1055 if (!requestURL.user().isEmpty())
1056 requestURL.setUser(String());
1057 if (!requestURL.pass().isEmpty())
1058 requestURL.setPass(String());
1060 m_player->load(loadType(), requestURL, corsMode());
1063 void HTMLMediaElement::setPlayerPreload()
1065 m_player->setPreload(m_preload);
1067 if (loadIsDeferred() && m_preload != MediaPlayer::None)
1068 startDeferredLoad();
1071 bool HTMLMediaElement::loadIsDeferred() const
1073 return m_deferredLoadState != NotDeferred;
1076 void HTMLMediaElement::deferLoad()
1078 // This implements the "optional" step 3 from the resource fetch algorithm.
1079 ASSERT(!m_deferredLoadTimer.isActive());
1080 ASSERT(m_deferredLoadState == NotDeferred);
1081 // 1. Set the networkState to NETWORK_IDLE.
1082 // 2. Queue a task to fire a simple event named suspend at the element.
1083 changeNetworkStateFromLoadingToIdle();
1084 // 3. Queue a task to set the element's delaying-the-load-event
1085 // flag to false. This stops delaying the load event.
1086 m_deferredLoadTimer.startOneShot(0, FROM_HERE);
1087 // 4. Wait for the task to be run.
1088 m_deferredLoadState = WaitingForStopDelayingLoadEventTask;
1089 // Continued in executeDeferredLoad().
1092 void HTMLMediaElement::cancelDeferredLoad()
1094 m_deferredLoadTimer.stop();
1095 m_deferredLoadState = NotDeferred;
1098 void HTMLMediaElement::executeDeferredLoad()
1100 ASSERT(m_deferredLoadState >= WaitingForTrigger);
1102 // resource fetch algorithm step 3 - continued from deferLoad().
1104 // 5. Wait for an implementation-defined event (e.g. the user requesting that the media element begin playback).
1105 // This is assumed to be whatever 'event' ended up calling this method.
1106 cancelDeferredLoad();
1107 // 6. Set the element's delaying-the-load-event flag back to true (this
1108 // delays the load event again, in case it hasn't been fired yet).
1109 setShouldDelayLoadEvent(true);
1110 // 7. Set the networkState to NETWORK_LOADING.
1111 m_networkState = NETWORK_LOADING;
1113 startProgressEventTimer();
1118 void HTMLMediaElement::startDeferredLoad()
1120 if (m_deferredLoadState == WaitingForTrigger) {
1121 executeDeferredLoad();
1124 ASSERT(m_deferredLoadState == WaitingForStopDelayingLoadEventTask);
1125 m_deferredLoadState = ExecuteOnStopDelayingLoadEventTask;
1128 void HTMLMediaElement::deferredLoadTimerFired(Timer<HTMLMediaElement>*)
1130 setShouldDelayLoadEvent(false);
1132 if (m_deferredLoadState == ExecuteOnStopDelayingLoadEventTask) {
1133 executeDeferredLoad();
1136 ASSERT(m_deferredLoadState == WaitingForStopDelayingLoadEventTask);
1137 m_deferredLoadState = WaitingForTrigger;
1140 WebMediaPlayer::LoadType HTMLMediaElement::loadType() const
1143 return WebMediaPlayer::LoadTypeMediaSource;
1145 if (isMediaStreamURL(m_currentSrc.string()))
1146 return WebMediaPlayer::LoadTypeMediaStream;
1148 return WebMediaPlayer::LoadTypeURL;
1151 static bool trackIndexCompare(TextTrack* a,
1154 return a->trackIndex() - b->trackIndex() < 0;
1157 static bool eventTimeCueCompare(const std::pair<double, TextTrackCue*>& a,
1158 const std::pair<double, TextTrackCue*>& b)
1160 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1162 if (a.first != b.first)
1163 return a.first - b.first < 0;
1165 // If the cues belong to different text tracks, it doesn't make sense to
1166 // compare the two tracks by the relative cue order, so return the relative
1168 if (a.second->track() != b.second->track())
1169 return trackIndexCompare(a.second->track(), b.second->track());
1171 // 12 - Further sort tasks in events that have the same time by the
1172 // relative text track cue order of the text track cues associated
1173 // with these tasks.
1174 return a.second->cueIndex() - b.second->cueIndex() < 0;
1178 void HTMLMediaElement::updateActiveTextTrackCues(double movieTime)
1180 // 4.8.10.8 Playing the media resource
1182 // If the current playback position changes while the steps are running,
1183 // then the user agent must wait for the steps to complete, and then must
1184 // immediately rerun the steps.
1185 if (ignoreTrackDisplayUpdateRequests())
1188 // 1 - Let current cues be a list of cues, initialized to contain all the
1189 // cues of all the hidden, showing, or showing by default text tracks of the
1190 // media element (not the disabled ones) whose start times are less than or
1191 // equal to the current playback position and whose end times are greater
1192 // than the current playback position.
1193 CueList currentCues;
1195 // The user agent must synchronously unset [the text track cue active] flag
1196 // whenever ... the media element's readyState is changed back to HAVE_NOTHING.
1197 if (m_readyState != HAVE_NOTHING && m_player)
1198 currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime));
1200 CueList previousCues;
1203 // 2 - Let other cues be a list of cues, initialized to contain all the cues
1204 // of hidden, showing, and showing by default text tracks of the media
1205 // element that are not present in current cues.
1206 previousCues = m_currentlyActiveCues;
1208 // 3 - Let last time be the current playback position at the time this
1209 // algorithm was last run for this media element, if this is not the first
1211 double lastTime = m_lastTextTrackUpdateTime;
1213 // 4 - If the current playback position has, since the last time this
1214 // algorithm was run, only changed through its usual monotonic increase
1215 // during normal playback, then let missed cues be the list of cues in other
1216 // cues whose start times are greater than or equal to last time and whose
1217 // end times are less than or equal to the current playback position.
1218 // Otherwise, let missed cues be an empty list.
1219 if (lastTime >= 0 && m_lastSeekTime < movieTime) {
1220 CueList potentiallySkippedCues =
1221 m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime));
1223 for (CueInterval cue : potentiallySkippedCues) {
1224 // Consider cues that may have been missed since the last seek time.
1225 if (cue.low() > std::max(m_lastSeekTime, lastTime) && cue.high() < movieTime)
1226 missedCues.append(cue);
1230 m_lastTextTrackUpdateTime = movieTime;
1232 // 5 - If the time was reached through the usual monotonic increase of the
1233 // current playback position during normal playback, and if the user agent
1234 // has not fired a timeupdate event at the element in the past 15 to 250ms
1235 // and is not still running event handlers for such an event, then the user
1236 // agent must queue a task to fire a simple event named timeupdate at the
1237 // element. (In the other cases, such as explicit seeks, relevant events get
1238 // fired as part of the overall process of changing the current playback
1240 if (!m_seeking && m_lastSeekTime < lastTime)
1241 scheduleTimeupdateEvent(true);
1243 // Explicitly cache vector sizes, as their content is constant from here.
1244 size_t currentCuesSize = currentCues.size();
1245 size_t missedCuesSize = missedCues.size();
1246 size_t previousCuesSize = previousCues.size();
1248 // 6 - If all of the cues in current cues have their text track cue active
1249 // flag set, none of the cues in other cues have their text track cue active
1250 // flag set, and missed cues is empty, then abort these steps.
1251 bool activeSetChanged = missedCuesSize;
1253 for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i) {
1254 if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive())
1255 activeSetChanged = true;
1258 for (CueInterval currentCue : currentCues) {
1259 currentCue.data()->updateDisplayTree(movieTime);
1261 if (!currentCue.data()->isActive())
1262 activeSetChanged = true;
1265 if (!activeSetChanged)
1268 // 7 - If the time was reached through the usual monotonic increase of the
1269 // current playback position during normal playback, and there are cues in
1270 // other cues that have their text track cue pause-on-exi flag set and that
1271 // either have their text track cue active flag set or are also in missed
1272 // cues, then immediately pause the media element.
1273 for (size_t i = 0; !m_paused && i < previousCuesSize; ++i) {
1274 if (previousCues[i].data()->pauseOnExit()
1275 && previousCues[i].data()->isActive()
1276 && !currentCues.contains(previousCues[i]))
1280 for (size_t i = 0; !m_paused && i < missedCuesSize; ++i) {
1281 if (missedCues[i].data()->pauseOnExit())
1285 // 8 - Let events be a list of tasks, initially empty. Each task in this
1286 // list will be associated with a text track, a text track cue, and a time,
1287 // which are used to sort the list before the tasks are queued.
1288 WillBeHeapVector<std::pair<double, RawPtrWillBeMember<TextTrackCue>>> eventTasks;
1290 // 8 - Let affected tracks be a list of text tracks, initially empty.
1291 WillBeHeapVector<RawPtrWillBeMember<TextTrack>> affectedTracks;
1293 for (size_t i = 0; i < missedCuesSize; ++i) {
1294 // 9 - For each text track cue in missed cues, prepare an event named enter
1295 // for the TextTrackCue object with the text track cue start time.
1296 eventTasks.append(std::make_pair(missedCues[i].data()->startTime(),
1297 missedCues[i].data()));
1299 // 10 - For each text track [...] in missed cues, prepare an event
1300 // named exit for the TextTrackCue object with the with the later of
1301 // the text track cue end time and the text track cue start time.
1303 // Note: An explicit task is added only if the cue is NOT a zero or
1304 // negative length cue. Otherwise, the need for an exit event is
1305 // checked when these tasks are actually queued below. This doesn't
1306 // affect sorting events before dispatch either, because the exit
1307 // event has the same time as the enter event.
1308 if (missedCues[i].data()->startTime() < missedCues[i].data()->endTime())
1309 eventTasks.append(std::make_pair(missedCues[i].data()->endTime(),
1310 missedCues[i].data()));
1313 for (size_t i = 0; i < previousCuesSize; ++i) {
1314 // 10 - For each text track cue in other cues that has its text
1315 // track cue active flag set prepare an event named exit for the
1316 // TextTrackCue object with the text track cue end time.
1317 if (!currentCues.contains(previousCues[i]))
1318 eventTasks.append(std::make_pair(previousCues[i].data()->endTime(),
1319 previousCues[i].data()));
1322 for (size_t i = 0; i < currentCuesSize; ++i) {
1323 // 11 - For each text track cue in current cues that does not have its
1324 // text track cue active flag set, prepare an event named enter for the
1325 // TextTrackCue object with the text track cue start time.
1326 if (!previousCues.contains(currentCues[i]))
1327 eventTasks.append(std::make_pair(currentCues[i].data()->startTime(),
1328 currentCues[i].data()));
1331 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1333 nonCopyingSort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare);
1335 for (size_t i = 0; i < eventTasks.size(); ++i) {
1336 if (!affectedTracks.contains(eventTasks[i].second->track()))
1337 affectedTracks.append(eventTasks[i].second->track());
1339 // 13 - Queue each task in events, in list order.
1340 RefPtrWillBeRawPtr<Event> event = nullptr;
1342 // Each event in eventTasks may be either an enterEvent or an exitEvent,
1343 // depending on the time that is associated with the event. This
1344 // correctly identifies the type of the event, if the startTime is
1345 // less than the endTime in the cue.
1346 if (eventTasks[i].second->startTime() >= eventTasks[i].second->endTime()) {
1347 event = Event::create(EventTypeNames::enter);
1348 event->setTarget(eventTasks[i].second);
1349 m_asyncEventQueue->enqueueEvent(event.release());
1351 event = Event::create(EventTypeNames::exit);
1352 event->setTarget(eventTasks[i].second);
1353 m_asyncEventQueue->enqueueEvent(event.release());
1355 if (eventTasks[i].first == eventTasks[i].second->startTime())
1356 event = Event::create(EventTypeNames::enter);
1358 event = Event::create(EventTypeNames::exit);
1360 event->setTarget(eventTasks[i].second);
1361 m_asyncEventQueue->enqueueEvent(event.release());
1365 // 14 - Sort affected tracks in the same order as the text tracks appear in
1366 // the media element's list of text tracks, and remove duplicates.
1367 nonCopyingSort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare);
1369 // 15 - For each text track in affected tracks, in the list order, queue a
1370 // task to fire a simple event named cuechange at the TextTrack object, and, ...
1371 for (size_t i = 0; i < affectedTracks.size(); ++i) {
1372 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::cuechange);
1373 event->setTarget(affectedTracks[i]);
1375 m_asyncEventQueue->enqueueEvent(event.release());
1377 // ... if the text track has a corresponding track element, to then fire a
1378 // simple event named cuechange at the track element as well.
1379 if (affectedTracks[i]->trackType() == TextTrack::TrackElement) {
1380 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::cuechange);
1381 HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(affectedTracks[i].get())->trackElement();
1382 ASSERT(trackElement);
1383 event->setTarget(trackElement);
1385 m_asyncEventQueue->enqueueEvent(event.release());
1389 // 16 - Set the text track cue active flag of all the cues in the current
1390 // cues, and unset the text track cue active flag of all the cues in the
1392 for (size_t i = 0; i < currentCuesSize; ++i)
1393 currentCues[i].data()->setIsActive(true);
1395 for (size_t i = 0; i < previousCuesSize; ++i) {
1396 if (!currentCues.contains(previousCues[i]))
1397 previousCues[i].data()->setIsActive(false);
1400 // Update the current active cues.
1401 m_currentlyActiveCues = currentCues;
1403 if (activeSetChanged)
1404 updateTextTrackDisplay();
1407 bool HTMLMediaElement::textTracksAreReady() const
1409 // 4.8.10.12.1 Text track model
1411 // The text tracks of a media element are ready if all the text tracks whose mode was not
1412 // in the disabled state when the element's resource selection algorithm last started now
1413 // have a text track readiness state of loaded or failed to load.
1414 for (unsigned i = 0; i < m_textTracksWhenResourceSelectionBegan.size(); ++i) {
1415 if (m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::Loading
1416 || m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::NotLoaded)
1423 void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track)
1425 if (webMediaPlayer()&& m_textTracksWhenResourceSelectionBegan.contains(track)) {
1426 if (track->readinessState() != TextTrack::Loading)
1427 setReadyState(static_cast<ReadyState>(webMediaPlayer()->readyState()));
1429 // The track readiness state might have changed as a result of the user
1430 // clicking the captions button. In this case, a check whether all the
1431 // resources have failed loading should be done in order to hide the CC button.
1432 if (hasMediaControls() && track->readinessState() == TextTrack::FailedToLoad)
1433 mediaControls()->refreshClosedCaptionsButtonVisibility();
1437 void HTMLMediaElement::textTrackModeChanged(TextTrack* track)
1439 if (track->trackType() == TextTrack::TrackElement) {
1440 // 4.8.10.12.3 Sourcing out-of-band text tracks
1441 // ... when a text track corresponding to a track element is created with text track
1442 // mode set to disabled and subsequently changes its text track mode to hidden, showing,
1443 // or showing by default for the first time, the user agent must immediately and synchronously
1444 // run the following algorithm ...
1446 for (HTMLTrackElement* trackElement = Traversal<HTMLTrackElement>::firstChild(*this); trackElement; trackElement = Traversal<HTMLTrackElement>::nextSibling(*trackElement)) {
1447 if (trackElement->track() != track)
1450 // Mark this track as "configured" so configureTextTracks won't change the mode again.
1451 track->setHasBeenConfigured(true);
1452 if (track->mode() != TextTrack::disabledKeyword()) {
1453 if (trackElement->readyState() == HTMLTrackElement::LOADED)
1454 textTrackAddCues(track, track->cues());
1456 // If this is the first added track, create the list of text tracks.
1458 m_textTracks = TextTrackList::create(this);
1462 } else if (track->trackType() == TextTrack::AddTrack && track->mode() != TextTrack::disabledKeyword()) {
1463 textTrackAddCues(track, track->cues());
1466 configureTextTrackDisplay(AssumeVisibleChange);
1468 ASSERT(textTracks()->contains(track));
1469 textTracks()->scheduleChangeEvent();
1472 void HTMLMediaElement::textTrackKindChanged(TextTrack* track)
1474 if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword() && track->mode() == TextTrack::showingKeyword())
1475 track->setMode(TextTrack::hiddenKeyword());
1478 void HTMLMediaElement::beginIgnoringTrackDisplayUpdateRequests()
1480 ++m_ignoreTrackDisplayUpdate;
1483 void HTMLMediaElement::endIgnoringTrackDisplayUpdateRequests()
1485 ASSERT(m_ignoreTrackDisplayUpdate);
1486 --m_ignoreTrackDisplayUpdate;
1487 if (!m_ignoreTrackDisplayUpdate && m_active)
1488 updateActiveTextTrackCues(currentTime());
1491 void HTMLMediaElement::textTrackAddCues(TextTrack* track, const TextTrackCueList* cues)
1493 WTF_LOG(Media, "HTMLMediaElement::textTrackAddCues(%p)", this);
1494 if (track->mode() == TextTrack::disabledKeyword())
1497 TrackDisplayUpdateScope scope(this);
1498 for (size_t i = 0; i < cues->length(); ++i)
1499 textTrackAddCue(cues->item(i)->track(), cues->item(i));
1502 void HTMLMediaElement::textTrackRemoveCues(TextTrack*, const TextTrackCueList* cues)
1504 WTF_LOG(Media, "HTMLMediaElement::textTrackRemoveCues(%p)", this);
1506 TrackDisplayUpdateScope scope(this);
1507 for (size_t i = 0; i < cues->length(); ++i)
1508 textTrackRemoveCue(cues->item(i)->track(), cues->item(i));
1511 void HTMLMediaElement::textTrackAddCue(TextTrack* track, PassRefPtrWillBeRawPtr<TextTrackCue> cue)
1513 if (track->mode() == TextTrack::disabledKeyword())
1516 // Negative duration cues need be treated in the interval tree as
1517 // zero-length cues.
1518 double endTime = std::max(cue->startTime(), cue->endTime());
1520 CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get());
1521 if (!m_cueTree.contains(interval))
1522 m_cueTree.add(interval);
1523 updateActiveTextTrackCues(currentTime());
1526 void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtrWillBeRawPtr<TextTrackCue> cue)
1528 // Negative duration cues need to be treated in the interval tree as
1529 // zero-length cues.
1530 double endTime = std::max(cue->startTime(), cue->endTime());
1532 CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get());
1533 m_cueTree.remove(interval);
1535 // Since the cue will be removed from the media element and likely the
1536 // TextTrack might also be destructed, notifying the region of the cue
1537 // removal shouldn't be done.
1538 cue->notifyRegionWhenRemovingDisplayTree(false);
1540 size_t index = m_currentlyActiveCues.find(interval);
1541 if (index != kNotFound) {
1542 m_currentlyActiveCues.remove(index);
1543 cue->setIsActive(false);
1545 cue->removeDisplayTree();
1546 updateActiveTextTrackCues(currentTime());
1548 cue->notifyRegionWhenRemovingDisplayTree(true);
1552 bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidURLAction actionIfInvalid)
1554 if (!url.isValid()) {
1555 WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p, %s) -> FALSE because url is invalid", this, urlForLoggingMedia(url).utf8().data());
1559 LocalFrame* frame = document().frame();
1560 if (!frame || !document().securityOrigin()->canDisplay(url)) {
1561 if (actionIfInvalid == Complain)
1562 FrameLoader::reportLocalLoadFailed(frame, url.elidedString());
1563 WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p, %s) -> FALSE rejected by SecurityOrigin", this, urlForLoggingMedia(url).utf8().data());
1567 if (!document().contentSecurityPolicy()->allowMediaFromSource(url)) {
1568 WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p, %s) -> rejected by Content Security Policy", this, urlForLoggingMedia(url).utf8().data());
1575 void HTMLMediaElement::startProgressEventTimer()
1577 if (m_progressEventTimer.isActive())
1580 m_previousProgressTime = WTF::currentTime();
1581 // 350ms is not magic, it is in the spec!
1582 m_progressEventTimer.startRepeating(0.350, FROM_HERE);
1585 void HTMLMediaElement::waitForSourceChange()
1587 WTF_LOG(Media, "HTMLMediaElement::waitForSourceChange(%p)", this);
1589 stopPeriodicTimers();
1590 m_loadState = WaitingForSource;
1592 // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value
1593 m_networkState = NETWORK_NO_SOURCE;
1595 // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1596 setShouldDelayLoadEvent(false);
1598 updateDisplayState();
1601 renderer()->updateFromElement();
1604 void HTMLMediaElement::noneSupported()
1606 WTF_LOG(Media, "HTMLMediaElement::noneSupported(%p)", this);
1608 stopPeriodicTimers();
1609 m_loadState = WaitingForSource;
1610 m_currentSourceNode = nullptr;
1613 // 6 - Reaching this step indicates that the media resource failed to load or that the given
1614 // URL could not be resolved. In one atomic operation, run the following steps:
1616 // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to
1617 // MEDIA_ERR_SRC_NOT_SUPPORTED.
1618 m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
1620 // 6.2 - Forget the media element's media-resource-specific text tracks.
1621 forgetResourceSpecificTracks();
1623 // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
1624 m_networkState = NETWORK_NO_SOURCE;
1626 // 7 - Queue a task to fire a simple event named error at the media element.
1627 scheduleEvent(EventTypeNames::error);
1631 // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1632 setShouldDelayLoadEvent(false);
1634 // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed,
1635 // the element won't attempt to load another resource.
1637 updateDisplayState();
1640 renderer()->updateFromElement();
1643 void HTMLMediaElement::mediaEngineError(PassRefPtrWillBeRawPtr<MediaError> err)
1645 ASSERT(m_readyState >= HAVE_METADATA);
1646 WTF_LOG(Media, "HTMLMediaElement::mediaEngineError(%p, %d)", this, static_cast<int>(err->code()));
1648 // 1 - The user agent should cancel the fetching process.
1649 stopPeriodicTimers();
1650 m_loadState = WaitingForSource;
1652 // 2 - Set the error attribute to a new MediaError object whose code attribute is
1653 // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
1656 // 3 - Queue a task to fire a simple event named error at the media element.
1657 scheduleEvent(EventTypeNames::error);
1659 // 4 - Set the element's networkState attribute to the NETWORK_IDLE value.
1660 m_networkState = NETWORK_IDLE;
1662 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1663 setShouldDelayLoadEvent(false);
1665 // 6 - Abort the overall resource selection algorithm.
1666 m_currentSourceNode = nullptr;
1669 void HTMLMediaElement::cancelPendingEventsAndCallbacks()
1671 WTF_LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks(%p)", this);
1672 m_asyncEventQueue->cancelAllEvents();
1674 for (HTMLSourceElement* source = Traversal<HTMLSourceElement>::firstChild(*this); source; source = Traversal<HTMLSourceElement>::nextSibling(*source))
1675 source->cancelPendingErrorEvent();
1678 void HTMLMediaElement::mediaPlayerNetworkStateChanged()
1680 setNetworkState(webMediaPlayer()->networkState());
1683 void HTMLMediaElement::mediaLoadingFailed(WebMediaPlayer::NetworkState error)
1685 stopPeriodicTimers();
1687 // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
1688 // <source> children, schedule the next one
1689 if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
1691 // resource selection algorithm
1692 // 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.
1693 if (m_currentSourceNode)
1694 m_currentSourceNode->scheduleErrorEvent();
1696 WTF_LOG(Media, "HTMLMediaElement::setNetworkState(%p) - error event not sent, <source> was removed", this);
1698 // 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.
1700 // 9.Otherwise.11 - Forget the media element's media-resource-specific tracks.
1701 forgetResourceSpecificTracks();
1703 if (havePotentialSourceChild()) {
1704 WTF_LOG(Media, "HTMLMediaElement::setNetworkState(%p) - scheduling next <source>", this);
1705 scheduleNextSourceChild();
1707 WTF_LOG(Media, "HTMLMediaElement::setNetworkState(%p) - no more <source> elements, waiting", this);
1708 waitForSourceChange();
1714 if (error == WebMediaPlayer::NetworkStateNetworkError && m_readyState >= HAVE_METADATA)
1715 mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK));
1716 else if (error == WebMediaPlayer::NetworkStateDecodeError)
1717 mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE));
1718 else if ((error == WebMediaPlayer::NetworkStateFormatError
1719 || error == WebMediaPlayer::NetworkStateNetworkError)
1720 && m_loadState == LoadingFromSrcAttr)
1723 updateDisplayState();
1724 if (hasMediaControls())
1725 mediaControls()->reset();
1728 void HTMLMediaElement::setNetworkState(WebMediaPlayer::NetworkState state)
1730 WTF_LOG(Media, "HTMLMediaElement::setNetworkState(%p, %d) - current state is %d", this, static_cast<int>(state), static_cast<int>(m_networkState));
1732 if (state == WebMediaPlayer::NetworkStateEmpty) {
1733 // Just update the cached state and leave, we can't do anything.
1734 m_networkState = NETWORK_EMPTY;
1738 if (state == WebMediaPlayer::NetworkStateFormatError
1739 || state == WebMediaPlayer::NetworkStateNetworkError
1740 || state == WebMediaPlayer::NetworkStateDecodeError) {
1741 mediaLoadingFailed(state);
1745 if (state == WebMediaPlayer::NetworkStateIdle) {
1746 if (m_networkState > NETWORK_IDLE) {
1747 changeNetworkStateFromLoadingToIdle();
1748 setShouldDelayLoadEvent(false);
1750 m_networkState = NETWORK_IDLE;
1754 if (state == WebMediaPlayer::NetworkStateLoading) {
1755 if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE)
1756 startProgressEventTimer();
1757 m_networkState = NETWORK_LOADING;
1760 if (state == WebMediaPlayer::NetworkStateLoaded) {
1761 if (m_networkState != NETWORK_IDLE)
1762 changeNetworkStateFromLoadingToIdle();
1763 m_completelyLoaded = true;
1767 void HTMLMediaElement::changeNetworkStateFromLoadingToIdle()
1770 m_progressEventTimer.stop();
1772 // Schedule one last progress event so we guarantee that at least one is fired
1773 // for files that load very quickly.
1774 if (webMediaPlayer() && webMediaPlayer()->didLoadingProgress())
1775 scheduleEvent(EventTypeNames::progress);
1776 scheduleEvent(EventTypeNames::suspend);
1777 m_networkState = NETWORK_IDLE;
1780 void HTMLMediaElement::mediaPlayerReadyStateChanged()
1782 setReadyState(static_cast<ReadyState>(webMediaPlayer()->readyState()));
1785 void HTMLMediaElement::setReadyState(ReadyState state)
1787 WTF_LOG(Media, "HTMLMediaElement::setReadyState(%p, %d) - current state is %d,", this, static_cast<int>(state), static_cast<int>(m_readyState));
1789 // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it
1790 bool wasPotentiallyPlaying = potentiallyPlaying();
1792 ReadyState oldState = m_readyState;
1793 ReadyState newState = state;
1795 bool tracksAreReady = textTracksAreReady();
1797 if (newState == oldState && m_tracksAreReady == tracksAreReady)
1800 m_tracksAreReady = tracksAreReady;
1802 if (tracksAreReady) {
1803 m_readyState = newState;
1805 // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until
1806 // the text tracks are ready, regardless of the state of the media file.
1807 if (newState <= HAVE_METADATA)
1808 m_readyState = newState;
1810 m_readyState = HAVE_CURRENT_DATA;
1813 if (oldState > m_readyStateMaximum)
1814 m_readyStateMaximum = oldState;
1816 if (m_networkState == NETWORK_EMPTY)
1820 // 4.8.10.9, step 9 note: If the media element was potentially playing immediately before
1821 // it started seeking, but seeking caused its readyState attribute to change to a value
1822 // lower than HAVE_FUTURE_DATA, then a waiting will be fired at the element.
1823 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA)
1824 scheduleEvent(EventTypeNames::waiting);
1826 // 4.8.10.9 steps 12-14
1827 if (m_readyState >= HAVE_CURRENT_DATA)
1830 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
1832 scheduleTimeupdateEvent(false);
1833 scheduleEvent(EventTypeNames::waiting);
1837 if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
1838 createPlaceholderTracksIfNecessary();
1840 selectInitialTracksIfNecessary();
1842 MediaFragmentURIParser fragmentParser(m_currentSrc);
1843 m_fragmentEndTime = fragmentParser.endTime();
1845 m_duration = duration();
1846 scheduleEvent(EventTypeNames::durationchange);
1848 if (isHTMLVideoElement())
1849 scheduleEvent(EventTypeNames::resize);
1850 scheduleEvent(EventTypeNames::loadedmetadata);
1852 bool jumped = false;
1853 if (m_defaultPlaybackStartPosition > 0) {
1854 seek(m_defaultPlaybackStartPosition);
1857 m_defaultPlaybackStartPosition = 0;
1859 double initialPlaybackPosition = fragmentParser.startTime();
1860 if (initialPlaybackPosition == MediaPlayer::invalidTime())
1861 initialPlaybackPosition = 0;
1863 if (!jumped && initialPlaybackPosition > 0) {
1864 m_sentEndEvent = false;
1865 UseCounter::count(document(), UseCounter::HTMLMediaElementSeekToFragmentStart);
1866 seek(initialPlaybackPosition);
1870 if (m_mediaController) {
1871 if (jumped && initialPlaybackPosition > m_mediaController->currentTime())
1872 m_mediaController->setCurrentTime(initialPlaybackPosition);
1874 seek(m_mediaController->currentTime());
1877 if (hasMediaControls())
1878 mediaControls()->reset();
1880 renderer()->updateFromElement();
1883 bool shouldUpdateDisplayState = false;
1885 if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) {
1886 m_haveFiredLoadedData = true;
1887 shouldUpdateDisplayState = true;
1888 scheduleEvent(EventTypeNames::loadeddata);
1889 setShouldDelayLoadEvent(false);
1892 bool isPotentiallyPlaying = potentiallyPlaying();
1893 if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
1894 scheduleEvent(EventTypeNames::canplay);
1895 if (isPotentiallyPlaying)
1896 scheduleEvent(EventTypeNames::playing);
1897 shouldUpdateDisplayState = true;
1900 if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA && tracksAreReady) {
1901 if (oldState <= HAVE_CURRENT_DATA) {
1902 scheduleEvent(EventTypeNames::canplay);
1903 if (isPotentiallyPlaying)
1904 scheduleEvent(EventTypeNames::playing);
1907 if (m_autoplaying && m_paused && autoplay() && !document().isSandboxed(SandboxAutomaticFeatures)) {
1908 autoplayMediaEncountered();
1909 if (!m_userGestureRequiredForPlay) {
1911 invalidateCachedTime();
1912 scheduleEvent(EventTypeNames::play);
1913 scheduleEvent(EventTypeNames::playing);
1917 scheduleEvent(EventTypeNames::canplaythrough);
1919 shouldUpdateDisplayState = true;
1922 if (shouldUpdateDisplayState) {
1923 updateDisplayState();
1924 if (hasMediaControls())
1925 mediaControls()->refreshClosedCaptionsButtonVisibility();
1929 updateMediaController();
1930 updateActiveTextTrackCues(currentTime());
1933 void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*)
1936 if (m_networkState != NETWORK_LOADING)
1939 double time = WTF::currentTime();
1940 double timedelta = time - m_previousProgressTime;
1942 if (webMediaPlayer() && webMediaPlayer()->didLoadingProgress()) {
1943 scheduleEvent(EventTypeNames::progress);
1944 m_previousProgressTime = time;
1945 m_sentStalledEvent = false;
1947 renderer()->updateFromElement();
1948 } else if (timedelta > 3.0 && !m_sentStalledEvent) {
1949 scheduleEvent(EventTypeNames::stalled);
1950 m_sentStalledEvent = true;
1951 setShouldDelayLoadEvent(false);
1955 void HTMLMediaElement::addPlayedRange(double start, double end)
1957 WTF_LOG(Media, "HTMLMediaElement::addPlayedRange(%p, %f, %f)", this, start, end);
1958 if (!m_playedTimeRanges)
1959 m_playedTimeRanges = TimeRanges::create();
1960 m_playedTimeRanges->add(start, end);
1963 bool HTMLMediaElement::supportsSave() const
1965 return webMediaPlayer() && webMediaPlayer()->supportsSave();
1968 void HTMLMediaElement::prepareToPlay()
1970 WTF_LOG(Media, "HTMLMediaElement::prepareToPlay(%p)", this);
1971 if (m_havePreparedToPlay)
1973 m_havePreparedToPlay = true;
1975 if (loadIsDeferred())
1976 startDeferredLoad();
1979 void HTMLMediaElement::seek(double time)
1981 WTF_LOG(Media, "HTMLMediaElement::seek(%p, %f)", this, time);
1983 // 2 - If the media element's readyState is HAVE_NOTHING, abort these steps.
1984 if (m_readyState == HAVE_NOTHING)
1987 // If the media engine has been told to postpone loading data, let it go ahead now.
1988 if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA)
1991 // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set.
1992 refreshCachedTime();
1993 // This is needed to avoid getting default playback start position from currentTime().
1994 double now = m_cachedTime;
1996 // 3 - If the element's seeking IDL attribute is true, then another instance of this algorithm is
1997 // already running. Abort that other instance of the algorithm without waiting for the step that
1998 // it is running to complete.
1999 // Nothing specific to be done here.
2001 // 4 - Set the seeking IDL attribute to true.
2002 // The flag will be cleared when the engine tells us the time has actually changed.
2003 bool previousSeekStillPending = m_seeking;
2006 // 6 - If the new playback position is later than the end of the media resource, then let it be the end
2007 // of the media resource instead.
2008 time = std::min(time, duration());
2010 // 7 - If the new playback position is less than the earliest possible position, let it be that position instead.
2011 time = std::max(time, 0.0);
2013 // Ask the media engine for the time value in the movie's time scale before comparing with current time. This
2014 // is necessary because if the seek time is not equal to currentTime but the delta is less than the movie's
2015 // time scale, we will ask the media engine to "seek" to the current movie time, which may be a noop and
2016 // not generate a timechanged callback. This means m_seeking will never be cleared and we will never
2017 // fire a 'seeked' event.
2018 double mediaTime = webMediaPlayer()->mediaTimeForTimeValue(time);
2019 if (time != mediaTime) {
2020 WTF_LOG(Media, "HTMLMediaElement::seek(%p, %f) - media timeline equivalent is %f", this, time, mediaTime);
2024 // 8 - If the (possibly now changed) new playback position is not in one of the ranges given in the
2025 // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute
2026 // that is the nearest to the new playback position. ... If there are no ranges given in the seekable
2027 // attribute then set the seeking IDL attribute to false and abort these steps.
2028 RefPtrWillBeRawPtr<TimeRanges> seekableRanges = seekable();
2030 // Short circuit seeking to the current time by just firing the events if no seek is required.
2031 // Don't skip calling the media engine if we are in poster mode because a seek should always
2032 // cancel poster display.
2033 bool noSeekRequired = !seekableRanges->length() || (time == now && displayMode() != Poster);
2035 if (noSeekRequired) {
2037 scheduleEvent(EventTypeNames::seeking);
2038 if (previousSeekStillPending)
2040 // FIXME: There must be a stable state before timeupdate+seeked are dispatched and seeking
2041 // is reset to false. See http://crbug.com/266631
2042 scheduleTimeupdateEvent(false);
2043 scheduleEvent(EventTypeNames::seeked);
2048 time = seekableRanges->nearest(time, now);
2051 if (m_lastSeekTime < now)
2052 addPlayedRange(m_lastSeekTime, now);
2054 m_lastSeekTime = time;
2055 m_sentEndEvent = false;
2057 // 10 - Queue a task to fire a simple event named seeking at the element.
2058 scheduleEvent(EventTypeNames::seeking);
2060 // 11 - Set the current playback position to the given new playback position.
2061 webMediaPlayer()->seek(time);
2063 m_initialPlayWithoutUserGestures = false;
2065 // 14-17 are handled, if necessary, when the engine signals a readystate change or otherwise
2066 // satisfies seek completion and signals a time change.
2069 void HTMLMediaElement::finishSeek()
2071 WTF_LOG(Media, "HTMLMediaElement::finishSeek(%p)", this);
2073 // 14 - Set the seeking IDL attribute to false.
2076 // 16 - Queue a task to fire a simple event named timeupdate at the element.
2077 scheduleTimeupdateEvent(false);
2079 // 17 - Queue a task to fire a simple event named seeked at the element.
2080 scheduleEvent(EventTypeNames::seeked);
2082 setDisplayMode(Video);
2085 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
2087 return m_readyState;
2090 bool HTMLMediaElement::hasAudio() const
2092 return webMediaPlayer() && webMediaPlayer()->hasAudio();
2095 bool HTMLMediaElement::seeking() const
2100 void HTMLMediaElement::refreshCachedTime() const
2102 if (!webMediaPlayer() || m_readyState < HAVE_METADATA)
2105 m_cachedTime = webMediaPlayer()->currentTime();
2108 void HTMLMediaElement::invalidateCachedTime()
2110 WTF_LOG(Media, "HTMLMediaElement::invalidateCachedTime(%p)", this);
2111 m_cachedTime = MediaPlayer::invalidTime();
2115 double HTMLMediaElement::currentTime() const
2117 if (m_defaultPlaybackStartPosition)
2118 return m_defaultPlaybackStartPosition;
2120 if (m_readyState == HAVE_NOTHING)
2124 WTF_LOG(Media, "HTMLMediaElement::currentTime(%p) - seeking, returning %f", this, m_lastSeekTime);
2125 return m_lastSeekTime;
2128 if (m_cachedTime != MediaPlayer::invalidTime() && m_paused) {
2129 #if LOG_CACHED_TIME_WARNINGS
2130 static const double minCachedDeltaForWarning = 0.01;
2131 double delta = m_cachedTime - webMediaPlayer()->currentTime();
2132 if (delta > minCachedDeltaForWarning)
2133 WTF_LOG(Media, "HTMLMediaElement::currentTime(%p) - WARNING, cached time is %f seconds off of media time when paused", this, delta);
2135 return m_cachedTime;
2138 refreshCachedTime();
2140 return m_cachedTime;
2143 void HTMLMediaElement::setCurrentTime(double time, ExceptionState& exceptionState)
2145 if (m_mediaController) {
2146 exceptionState.throwDOMException(InvalidStateError, "The element is slaved to a MediaController.");
2150 // If the media element's readyState is HAVE_NOTHING, then set the default
2151 // playback start position to that time.
2152 if (m_readyState == HAVE_NOTHING) {
2153 m_defaultPlaybackStartPosition = time;
2160 double HTMLMediaElement::duration() const
2162 // FIXME: remove m_player check once we figure out how m_player is going
2163 // out of sync with readystate. m_player is cleared but readystate is not set
2165 if (!m_player || m_readyState < HAVE_METADATA)
2166 return std::numeric_limits<double>::quiet_NaN();
2168 // FIXME: Refactor so m_duration is kept current (in both MSE and
2169 // non-MSE cases) once we have transitioned from HAVE_NOTHING ->
2170 // HAVE_METADATA. Currently, m_duration may be out of date for at least MSE
2171 // case because MediaSource and SourceBuffer do not notify the element
2172 // directly upon duration changes caused by endOfStream, remove, or append
2173 // operations; rather the notification is triggered by the WebMediaPlayer
2174 // implementation observing that the underlying engine has updated duration
2175 // and notifying the element to consult its MediaSource for current
2176 // duration. See http://crbug.com/266644
2179 return m_mediaSource->duration();
2181 return webMediaPlayer()->duration();
2184 bool HTMLMediaElement::paused() const
2189 double HTMLMediaElement::defaultPlaybackRate() const
2191 return m_defaultPlaybackRate;
2194 void HTMLMediaElement::setDefaultPlaybackRate(double rate)
2196 if (m_defaultPlaybackRate == rate)
2199 m_defaultPlaybackRate = rate;
2200 scheduleEvent(EventTypeNames::ratechange);
2203 double HTMLMediaElement::playbackRate() const
2205 return m_playbackRate;
2208 void HTMLMediaElement::setPlaybackRate(double rate)
2210 WTF_LOG(Media, "HTMLMediaElement::setPlaybackRate(%p, %f)", this, rate);
2212 if (m_playbackRate != rate) {
2213 m_playbackRate = rate;
2214 invalidateCachedTime();
2215 scheduleEvent(EventTypeNames::ratechange);
2218 updatePlaybackRate();
2221 double HTMLMediaElement::effectivePlaybackRate() const
2223 return m_mediaController ? m_mediaController->playbackRate() : m_playbackRate;
2226 HTMLMediaElement::DirectionOfPlayback HTMLMediaElement::directionOfPlayback() const
2228 return m_playbackRate >= 0 ? Forward : Backward;
2231 void HTMLMediaElement::updatePlaybackRate()
2233 double effectiveRate = effectivePlaybackRate();
2234 if (m_player && potentiallyPlaying())
2235 webMediaPlayer()->setRate(effectiveRate);
2238 bool HTMLMediaElement::ended() const
2240 // 4.8.10.8 Playing the media resource
2241 // The ended attribute must return true if the media element has ended
2242 // playback and the direction of playback is forwards, and false otherwise.
2243 return endedPlayback() && directionOfPlayback() == Forward;
2246 bool HTMLMediaElement::autoplay() const
2248 return fastHasAttribute(autoplayAttr);
2251 String HTMLMediaElement::preload() const
2253 switch (m_preload) {
2254 case MediaPlayer::None:
2257 case MediaPlayer::MetaData:
2260 case MediaPlayer::Auto:
2265 ASSERT_NOT_REACHED();
2269 void HTMLMediaElement::setPreload(const AtomicString& preload)
2271 WTF_LOG(Media, "HTMLMediaElement::setPreload(%p, %s)", this, preload.utf8().data());
2272 setAttribute(preloadAttr, preload);
2275 void HTMLMediaElement::play()
2277 WTF_LOG(Media, "HTMLMediaElement::play(%p)", this);
2279 if (!UserGestureIndicator::processingUserGesture()) {
2280 autoplayMediaEncountered();
2281 if (m_userGestureRequiredForPlay)
2283 } else if (m_userGestureRequiredForPlay) {
2284 recordAutoplayMetric(AutoplayManualStart);
2285 m_userGestureRequiredForPlay = false;
2291 void HTMLMediaElement::playInternal()
2293 WTF_LOG(Media, "HTMLMediaElement::playInternal(%p)", this);
2295 // 4.8.10.9. Playing the media resource
2296 if (!m_player || m_networkState == NETWORK_EMPTY)
2297 scheduleDelayedAction(LoadMediaResource);
2299 if (endedPlayback())
2302 if (m_mediaController)
2303 m_mediaController->bringElementUpToSpeed(this);
2307 invalidateCachedTime();
2308 scheduleEvent(EventTypeNames::play);
2310 if (m_readyState <= HAVE_CURRENT_DATA)
2311 scheduleEvent(EventTypeNames::waiting);
2312 else if (m_readyState >= HAVE_FUTURE_DATA)
2313 scheduleEvent(EventTypeNames::playing);
2315 m_autoplaying = false;
2318 updateMediaController();
2321 void HTMLMediaElement::autoplayMediaEncountered()
2323 if (!m_autoplayMediaCounted) {
2324 m_autoplayMediaCounted = true;
2325 recordAutoplayMetric(AutoplayMediaFound);
2327 if (!m_userGestureRequiredForPlay)
2328 m_initialPlayWithoutUserGestures = true;
2332 void HTMLMediaElement::gesturelessInitialPlayHalted()
2334 ASSERT(m_initialPlayWithoutUserGestures);
2335 m_initialPlayWithoutUserGestures = false;
2337 recordAutoplayMetric(AutoplayStopped);
2339 // We count the user as having bailed-out on the video if they watched
2340 // less than one minute and less than 50% of it.
2341 double playedTime = currentTime();
2342 if (playedTime < 60) {
2343 double progress = playedTime / duration();
2345 recordAutoplayMetric(AutoplayBailout);
2349 void HTMLMediaElement::pause()
2351 WTF_LOG(Media, "HTMLMediaElement::pause(%p)", this);
2353 if (!m_player || m_networkState == NETWORK_EMPTY)
2354 scheduleDelayedAction(LoadMediaResource);
2356 m_autoplaying = false;
2359 if (m_initialPlayWithoutUserGestures)
2360 gesturelessInitialPlayHalted();
2363 scheduleTimeupdateEvent(false);
2364 scheduleEvent(EventTypeNames::pause);
2370 void HTMLMediaElement::requestRemotePlayback()
2372 ASSERT(m_remoteRoutesAvailable);
2373 webMediaPlayer()->requestRemotePlayback();
2376 void HTMLMediaElement::requestRemotePlaybackControl()
2378 ASSERT(m_remoteRoutesAvailable);
2379 webMediaPlayer()->requestRemotePlaybackControl();
2382 void HTMLMediaElement::closeMediaSource()
2387 m_mediaSource->close();
2388 m_mediaSource = nullptr;
2391 bool HTMLMediaElement::loop() const
2393 return fastHasAttribute(loopAttr);
2396 void HTMLMediaElement::setLoop(bool b)
2398 WTF_LOG(Media, "HTMLMediaElement::setLoop(%p, %s)", this, boolString(b));
2399 setBooleanAttribute(loopAttr, b);
2402 bool HTMLMediaElement::shouldShowControls() const
2404 LocalFrame* frame = document().frame();
2406 // always show controls when scripting is disabled
2407 if (frame && !frame->script().canExecuteScripts(NotAboutToExecuteScript))
2410 // Always show controls when in full screen mode.
2414 return fastHasAttribute(controlsAttr);
2417 double HTMLMediaElement::volume() const
2422 void HTMLMediaElement::setVolume(double vol, ExceptionState& exceptionState)
2424 WTF_LOG(Media, "HTMLMediaElement::setVolume(%p, %f)", this, vol);
2426 if (m_volume == vol)
2429 if (vol < 0.0f || vol > 1.0f) {
2430 exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexOutsideRange("volume", vol, 0.0, ExceptionMessages::InclusiveBound, 1.0, ExceptionMessages::InclusiveBound));
2436 scheduleEvent(EventTypeNames::volumechange);
2439 bool HTMLMediaElement::muted() const
2444 void HTMLMediaElement::setMuted(bool muted)
2446 WTF_LOG(Media, "HTMLMediaElement::setMuted(%p, %s)", this, boolString(muted));
2448 if (m_muted == muted)
2455 scheduleEvent(EventTypeNames::volumechange);
2458 void HTMLMediaElement::updateVolume()
2460 if (webMediaPlayer())
2461 webMediaPlayer()->setVolume(effectiveMediaVolume());
2463 if (hasMediaControls())
2464 mediaControls()->updateVolume();
2467 double HTMLMediaElement::effectiveMediaVolume() const
2472 if (m_mediaController && m_mediaController->muted())
2475 double volume = m_volume;
2477 if (m_mediaController)
2478 volume *= m_mediaController->volume();
2483 // The spec says to fire periodic timeupdate events (those sent while playing) every
2484 // "15 to 250ms", we choose the slowest frequency
2485 static const double maxTimeupdateEventFrequency = 0.25;
2487 void HTMLMediaElement::startPlaybackProgressTimer()
2489 if (m_playbackProgressTimer.isActive())
2492 m_previousProgressTime = WTF::currentTime();
2493 m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency, FROM_HERE);
2496 void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*)
2500 if (m_fragmentEndTime != MediaPlayer::invalidTime() && currentTime() >= m_fragmentEndTime && directionOfPlayback() == Forward) {
2501 m_fragmentEndTime = MediaPlayer::invalidTime();
2502 if (!m_mediaController && !m_paused) {
2503 UseCounter::count(document(), UseCounter::HTMLMediaElementPauseAtFragmentEnd);
2504 // changes paused to true and fires a simple event named pause at the media element.
2510 scheduleTimeupdateEvent(true);
2512 if (!effectivePlaybackRate())
2515 if (!m_paused && hasMediaControls())
2516 mediaControls()->playbackProgressed();
2518 updateActiveTextTrackCues(currentTime());
2521 void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent)
2523 double now = WTF::currentTime();
2524 double movieTime = currentTime();
2526 bool haveNotRecentlyFiredTimeupdate = (now - m_lastTimeUpdateEventWallTime) >= maxTimeupdateEventFrequency;
2527 bool movieTimeHasProgressed = movieTime != m_lastTimeUpdateEventMovieTime;
2529 // Non-periodic timeupdate events must always fire as mandated by the spec,
2530 // otherwise we shouldn't fire duplicate periodic timeupdate events when the
2531 // movie time hasn't changed.
2532 if (!periodicEvent || (haveNotRecentlyFiredTimeupdate && movieTimeHasProgressed)) {
2533 scheduleEvent(EventTypeNames::timeupdate);
2534 m_lastTimeUpdateEventWallTime = now;
2535 m_lastTimeUpdateEventMovieTime = movieTime;
2539 bool HTMLMediaElement::togglePlayStateWillPlay() const
2541 if (m_mediaController)
2542 return m_mediaController->paused() || m_mediaController->isRestrained();
2546 void HTMLMediaElement::togglePlayState()
2548 if (m_mediaController) {
2549 if (m_mediaController->isRestrained())
2550 m_mediaController->play();
2551 else if (m_mediaController->paused())
2552 m_mediaController->unpause();
2554 m_mediaController->pause();
2563 AudioTrackList& HTMLMediaElement::audioTracks()
2565 ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
2566 return *m_audioTracks;
2569 void HTMLMediaElement::audioTrackChanged()
2571 WTF_LOG(Media, "HTMLMediaElement::audioTrackChanged(%p)", this);
2572 ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
2574 audioTracks().scheduleChangeEvent();
2576 // FIXME: Add call on m_mediaSource to notify it of track changes once the SourceBuffer.audioTracks attribute is added.
2578 if (!m_audioTracksTimer.isActive())
2579 m_audioTracksTimer.startOneShot(0, FROM_HERE);
2582 void HTMLMediaElement::audioTracksTimerFired(Timer<HTMLMediaElement>*)
2584 Vector<WebMediaPlayer::TrackId> enabledTrackIds;
2585 for (unsigned i = 0; i < audioTracks().length(); ++i) {
2586 AudioTrack* track = audioTracks().anonymousIndexedGetter(i);
2587 if (track->enabled())
2588 enabledTrackIds.append(track->trackId());
2591 webMediaPlayer()->enabledAudioTracksChanged(enabledTrackIds);
2594 WebMediaPlayer::TrackId HTMLMediaElement::addAudioTrack(const String& id, blink::WebMediaPlayerClient::AudioTrackKind kind, const AtomicString& label, const AtomicString& language, bool enabled)
2596 AtomicString kindString = AudioKindToString(kind);
2597 WTF_LOG(Media, "HTMLMediaElement::addAudioTrack(%p, '%s', '%s', '%s', '%s', %d)",
2598 this, id.ascii().data(), kindString.ascii().data(), label.ascii().data(), language.ascii().data(), enabled);
2600 if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
2603 RefPtrWillBeRawPtr<AudioTrack> audioTrack = AudioTrack::create(id, kindString, label, language, enabled);
2604 audioTracks().add(audioTrack);
2606 return audioTrack->trackId();
2609 void HTMLMediaElement::removeAudioTrack(WebMediaPlayer::TrackId trackId)
2611 WTF_LOG(Media, "HTMLMediaElement::removeAudioTrack(%p)", this);
2613 if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
2616 audioTracks().remove(trackId);
2619 VideoTrackList& HTMLMediaElement::videoTracks()
2621 ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
2622 return *m_videoTracks;
2625 void HTMLMediaElement::selectedVideoTrackChanged(WebMediaPlayer::TrackId* selectedTrackId)
2627 WTF_LOG(Media, "HTMLMediaElement::selectedVideoTrackChanged(%p)", this);
2628 ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
2630 if (selectedTrackId)
2631 videoTracks().trackSelected(*selectedTrackId);
2633 // FIXME: Add call on m_mediaSource to notify it of track changes once the SourceBuffer.videoTracks attribute is added.
2635 webMediaPlayer()->selectedVideoTrackChanged(selectedTrackId);
2638 WebMediaPlayer::TrackId HTMLMediaElement::addVideoTrack(const String& id, blink::WebMediaPlayerClient::VideoTrackKind kind, const AtomicString& label, const AtomicString& language, bool selected)
2640 AtomicString kindString = VideoKindToString(kind);
2641 WTF_LOG(Media, "HTMLMediaElement::addVideoTrack(%p, '%s', '%s', '%s', '%s', %d)",
2642 this, id.ascii().data(), kindString.ascii().data(), label.ascii().data(), language.ascii().data(), selected);
2644 if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
2647 // If another track was selected (potentially by the user), leave it selected.
2648 if (selected && videoTracks().selectedIndex() != -1)
2651 RefPtrWillBeRawPtr<VideoTrack> videoTrack = VideoTrack::create(id, kindString, label, language, selected);
2652 videoTracks().add(videoTrack);
2654 return videoTrack->trackId();
2657 void HTMLMediaElement::removeVideoTrack(WebMediaPlayer::TrackId trackId)
2659 WTF_LOG(Media, "HTMLMediaElement::removeVideoTrack(%p)", this);
2661 if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
2664 videoTracks().remove(trackId);
2667 void HTMLMediaElement::mediaPlayerDidAddTextTrack(WebInbandTextTrack* webTrack)
2669 // 4.8.10.12.2 Sourcing in-band text tracks
2670 // 1. Associate the relevant data with a new text track and its corresponding new TextTrack object.
2671 RefPtrWillBeRawPtr<InbandTextTrack> textTrack = InbandTextTrack::create(webTrack);
2673 // 2. Set the new text track's kind, label, and language based on the semantics of the relevant data,
2674 // as defined by the relevant specification. If there is no label in that data, then the label must
2675 // be set to the empty string.
2676 // 3. Associate the text track list of cues with the rules for updating the text track rendering appropriate
2677 // for the format in question.
2678 // 4. If the new text track's kind is metadata, then set the text track in-band metadata track dispatch type
2679 // as follows, based on the type of the media resource:
2680 // 5. Populate the new text track's list of cues with the cues parsed so far, folllowing the guidelines for exposing
2681 // cues, and begin updating it dynamically as necessary.
2682 // - Thess are all done by the media engine.
2684 // 6. Set the new text track's readiness state to loaded.
2685 textTrack->setReadinessState(TextTrack::Loaded);
2687 // 7. Set the new text track's mode to the mode consistent with the user's preferences and the requirements of
2688 // the relevant specification for the data.
2689 // - This will happen in configureTextTracks()
2690 scheduleDelayedAction(LoadTextTrackResource);
2692 // 8. Add the new text track to the media element's list of text tracks.
2693 // 9. Fire an event with the name addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent
2694 // interface, with the track attribute initialized to the text track's TextTrack object, at the media element's
2695 // textTracks attribute's TextTrackList object.
2696 addTextTrack(textTrack.get());
2699 void HTMLMediaElement::mediaPlayerDidRemoveTextTrack(WebInbandTextTrack* webTrack)
2704 // This cast is safe because we created the InbandTextTrack with the WebInbandTextTrack
2705 // passed to mediaPlayerDidAddTextTrack.
2706 RefPtrWillBeRawPtr<InbandTextTrack> textTrack = static_cast<InbandTextTrack*>(webTrack->client());
2710 removeTextTrack(textTrack.get());
2713 void HTMLMediaElement::textTracksChanged()
2715 if (hasMediaControls())
2716 mediaControls()->textTracksChanged();
2719 void HTMLMediaElement::addTextTrack(TextTrack* track)
2721 textTracks()->append(track);
2723 textTracksChanged();
2726 void HTMLMediaElement::removeTextTrack(TextTrack* track)
2728 TrackDisplayUpdateScope scope(this);
2729 m_textTracks->remove(track);
2731 textTracksChanged();
2734 void HTMLMediaElement::forgetResourceSpecificTracks()
2736 // Implements the "forget the media element's media-resource-specific tracks" algorithm.
2737 // The order is explicitly specified as text, then audio, and finally video. Also
2738 // 'removetrack' events should not be fired.
2740 TrackDisplayUpdateScope scope(this);
2741 m_textTracks->removeAllInbandTracks();
2742 textTracksChanged();
2745 m_audioTracks->removeAll();
2746 m_videoTracks->removeAll();
2748 m_audioTracksTimer.stop();
2751 PassRefPtrWillBeRawPtr<TextTrack> HTMLMediaElement::addTextTrack(const AtomicString& kind, const AtomicString& label, const AtomicString& language, ExceptionState& exceptionState)
2753 // 4.8.10.12.4 Text track API
2754 // The addTextTrack(kind, label, language) method of media elements, when invoked, must run the following steps:
2756 // 1. If kind is not one of the following strings, then throw a SyntaxError exception and abort these steps
2757 if (!TextTrack::isValidKindKeyword(kind)) {
2758 exceptionState.throwDOMException(SyntaxError, "The 'kind' provided ('" + kind + "') is invalid.");
2762 // 2. If the label argument was omitted, let label be the empty string.
2763 // 3. If the language argument was omitted, let language be the empty string.
2764 // 4. Create a new TextTrack object.
2766 // 5. Create a new text track corresponding to the new object, and set its text track kind to kind, its text
2767 // track label to label, its text track language to language...
2768 RefPtrWillBeRawPtr<TextTrack> textTrack = TextTrack::create(kind, label, language);
2770 // Note, due to side effects when changing track parameters, we have to
2771 // first append the track to the text track list.
2773 // 6. Add the new text track to the media element's list of text tracks.
2774 addTextTrack(textTrack.get());
2776 // ... its text track readiness state to the text track loaded state ...
2777 textTrack->setReadinessState(TextTrack::Loaded);
2779 // ... its text track mode to the text track hidden mode, and its text track list of cues to an empty list ...
2780 textTrack->setMode(TextTrack::hiddenKeyword());
2782 return textTrack.release();
2785 TextTrackList* HTMLMediaElement::textTracks()
2788 m_textTracks = TextTrackList::create(this);
2790 return m_textTracks.get();
2793 void HTMLMediaElement::didAddTrackElement(HTMLTrackElement* trackElement)
2795 // 4.8.10.12.3 Sourcing out-of-band text tracks
2796 // When a track element's parent element changes and the new parent is a media element,
2797 // then the user agent must add the track element's corresponding text track to the
2798 // media element's list of text tracks ... [continues in TextTrackList::append]
2799 RefPtrWillBeRawPtr<TextTrack> textTrack = trackElement->track();
2803 addTextTrack(textTrack.get());
2805 // Do not schedule the track loading until parsing finishes so we don't start before all tracks
2806 // in the markup have been added.
2807 if (isFinishedParsingChildren())
2808 scheduleDelayedAction(LoadTextTrackResource);
2811 void HTMLMediaElement::didRemoveTrackElement(HTMLTrackElement* trackElement)
2814 KURL url = trackElement->getNonEmptyURLAttribute(srcAttr);
2815 WTF_LOG(Media, "HTMLMediaElement::didRemoveTrackElement(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data());
2818 RefPtrWillBeRawPtr<TextTrack> textTrack = trackElement->track();
2822 textTrack->setHasBeenConfigured(false);
2827 // 4.8.10.12.3 Sourcing out-of-band text tracks
2828 // When a track element's parent element changes and the old parent was a media element,
2829 // then the user agent must remove the track element's corresponding text track from the
2830 // media element's list of text tracks.
2831 removeTextTrack(textTrack.get());
2833 size_t index = m_textTracksWhenResourceSelectionBegan.find(textTrack.get());
2834 if (index != kNotFound)
2835 m_textTracksWhenResourceSelectionBegan.remove(index);
2838 static int textTrackLanguageSelectionScore(const TextTrack& track)
2840 if (track.language().isEmpty())
2843 Vector<AtomicString> languages = userPreferredLanguages();
2844 size_t languageMatchIndex = indexOfBestMatchingLanguageInList(track.language(), languages);
2845 if (languageMatchIndex >= languages.size())
2848 return languages.size() - languageMatchIndex;
2851 static int textTrackSelectionScore(const TextTrack& track)
2853 if (track.kind() != TextTrack::captionsKeyword() && track.kind() != TextTrack::subtitlesKeyword())
2856 return textTrackLanguageSelectionScore(track);
2859 void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group)
2861 ASSERT(group.tracks.size());
2863 WTF_LOG(Media, "HTMLMediaElement::configureTextTrackGroup(%p, %d)", this, group.kind);
2865 // First, find the track in the group that should be enabled (if any).
2866 WillBeHeapVector<RefPtrWillBeMember<TextTrack>> currentlyEnabledTracks;
2867 RefPtrWillBeRawPtr<TextTrack> trackToEnable = nullptr;
2868 RefPtrWillBeRawPtr<TextTrack> defaultTrack = nullptr;
2869 RefPtrWillBeRawPtr<TextTrack> fallbackTrack = nullptr;
2870 int highestTrackScore = 0;
2871 for (size_t i = 0; i < group.tracks.size(); ++i) {
2872 RefPtrWillBeRawPtr<TextTrack> textTrack = group.tracks[i];
2874 if (m_processingPreferenceChange && textTrack->mode() == TextTrack::showingKeyword())
2875 currentlyEnabledTracks.append(textTrack);
2877 int trackScore = textTrackSelectionScore(*textTrack);
2879 // * If the text track kind is { [subtitles or captions] [descriptions] } and the user has indicated an interest in having a
2880 // track with this text track kind, text track language, and text track label enabled, and there is no
2881 // other text track in the media element's list of text tracks with a text track kind of either subtitles
2882 // or captions whose text track mode is showing
2884 // * If the text track kind is chapters and the text track language is one that the user agent has reason
2885 // to believe is appropriate for the user, and there is no other text track in the media element's list of
2886 // text tracks with a text track kind of chapters whose text track mode is showing
2887 // Let the text track mode be showing.
2888 if (trackScore > highestTrackScore) {
2889 highestTrackScore = trackScore;
2890 trackToEnable = textTrack;
2893 if (!defaultTrack && textTrack->isDefault())
2894 defaultTrack = textTrack;
2895 if (!defaultTrack && !fallbackTrack)
2896 fallbackTrack = textTrack;
2897 } else if (!group.visibleTrack && !defaultTrack && textTrack->isDefault()) {
2898 // * If the track element has a default attribute specified, and there is no other text track in the media
2899 // element's list of text tracks whose text track mode is showing or showing by default
2900 // Let the text track mode be showing by default.
2901 defaultTrack = textTrack;
2905 if (!trackToEnable && defaultTrack)
2906 trackToEnable = defaultTrack;
2908 // If no track matches the user's preferred language and non was marked 'default', enable the first track
2909 // because the user has explicitly stated a preference for this kind of track.
2910 if (!fallbackTrack && m_closedCaptionsVisible && group.kind == TrackGroup::CaptionsAndSubtitles)
2911 fallbackTrack = group.tracks[0];
2913 if (!trackToEnable && fallbackTrack)
2914 trackToEnable = fallbackTrack;
2916 if (currentlyEnabledTracks.size()) {
2917 for (size_t i = 0; i < currentlyEnabledTracks.size(); ++i) {
2918 RefPtrWillBeRawPtr<TextTrack> textTrack = currentlyEnabledTracks[i];
2919 if (textTrack != trackToEnable)
2920 textTrack->setMode(TextTrack::disabledKeyword());
2925 trackToEnable->setMode(TextTrack::showingKeyword());
2928 void HTMLMediaElement::configureTextTracks()
2930 TrackGroup captionAndSubtitleTracks(TrackGroup::CaptionsAndSubtitles);
2931 TrackGroup descriptionTracks(TrackGroup::Description);
2932 TrackGroup chapterTracks(TrackGroup::Chapter);
2933 TrackGroup metadataTracks(TrackGroup::Metadata);
2934 TrackGroup otherTracks(TrackGroup::Other);
2939 for (size_t i = 0; i < m_textTracks->length(); ++i) {
2940 RefPtrWillBeRawPtr<TextTrack> textTrack = m_textTracks->item(i);
2944 String kind = textTrack->kind();
2945 TrackGroup* currentGroup;
2946 if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword())
2947 currentGroup = &captionAndSubtitleTracks;
2948 else if (kind == TextTrack::descriptionsKeyword())
2949 currentGroup = &descriptionTracks;
2950 else if (kind == TextTrack::chaptersKeyword())
2951 currentGroup = &chapterTracks;
2952 else if (kind == TextTrack::metadataKeyword())
2953 currentGroup = &metadataTracks;
2955 currentGroup = &otherTracks;
2957 if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::showingKeyword())
2958 currentGroup->visibleTrack = textTrack;
2959 if (!currentGroup->defaultTrack && textTrack->isDefault())
2960 currentGroup->defaultTrack = textTrack;
2962 // Do not add this track to the group if it has already been automatically configured
2963 // as we only want to call configureTextTrack once per track so that adding another
2964 // track after the initial configuration doesn't reconfigure every track - only those
2965 // that should be changed by the new addition. For example all metadata tracks are
2966 // disabled by default, and we don't want a track that has been enabled by script
2967 // to be disabled automatically when a new metadata track is added later.
2968 if (textTrack->hasBeenConfigured())
2971 if (textTrack->language().length())
2972 currentGroup->hasSrcLang = true;
2973 currentGroup->tracks.append(textTrack);
2976 if (captionAndSubtitleTracks.tracks.size())
2977 configureTextTrackGroup(captionAndSubtitleTracks);
2978 if (descriptionTracks.tracks.size())
2979 configureTextTrackGroup(descriptionTracks);
2980 if (chapterTracks.tracks.size())
2981 configureTextTrackGroup(chapterTracks);
2982 if (metadataTracks.tracks.size())
2983 configureTextTrackGroup(metadataTracks);
2984 if (otherTracks.tracks.size())
2985 configureTextTrackGroup(otherTracks);
2987 textTracksChanged();
2990 bool HTMLMediaElement::havePotentialSourceChild()
2992 // Stash the current <source> node and next nodes so we can restore them after checking
2993 // to see there is another potential.
2994 RefPtrWillBeRawPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode;
2995 RefPtrWillBeRawPtr<Node> nextNode = m_nextChildNodeToConsider;
2997 KURL nextURL = selectNextSourceChild(0, 0, DoNothing);
2999 m_currentSourceNode = currentSourceNode;
3000 m_nextChildNodeToConsider = nextNode;
3002 return nextURL.isValid();
3005 KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid)
3008 // Don't log if this was just called to find out if there are any valid <source> elements.
3009 bool shouldLog = actionIfInvalid != DoNothing;
3011 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p)", this);
3014 if (!m_nextChildNodeToConsider) {
3017 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) -> 0x0000, \"\"", this);
3024 HTMLSourceElement* source = 0;
3027 bool lookingForStartNode = m_nextChildNodeToConsider;
3028 bool canUseSourceElement = false;
3030 NodeVector potentialSourceNodes;
3031 getChildNodes(*this, potentialSourceNodes);
3033 for (unsigned i = 0; !canUseSourceElement && i < potentialSourceNodes.size(); ++i) {
3034 node = potentialSourceNodes[i].get();
3035 if (lookingForStartNode && m_nextChildNodeToConsider != node)
3037 lookingForStartNode = false;
3039 if (!isHTMLSourceElement(*node))
3041 if (node->parentNode() != this)
3044 source = toHTMLSourceElement(node);
3046 // If candidate does not have a src attribute, or if its src attribute's value is the empty string ... jump down to the failed step below
3047 mediaURL = source->getNonEmptyURLAttribute(srcAttr);
3050 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) - 'src' is %s", this, urlForLoggingMedia(mediaURL).utf8().data());
3052 if (mediaURL.isEmpty())
3055 type = source->type();
3056 // FIXME(82965): Add support for keySystem in <source> and set system from source.
3057 if (type.isEmpty() && mediaURL.protocolIsData())
3058 type = mimeTypeFromDataURL(mediaURL);
3059 if (!type.isEmpty() || !system.isEmpty()) {
3062 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) - 'type' is '%s' - key system is '%s'", this, type.utf8().data(), system.utf8().data());
3064 if (!supportsType(ContentType(type), system))
3068 // Is it safe to load this url?
3069 if (!isSafeToLoadURL(mediaURL, actionIfInvalid))
3072 // Making it this far means the <source> looks reasonable.
3073 canUseSourceElement = true;
3076 if (!canUseSourceElement && actionIfInvalid == Complain && source)
3077 source->scheduleErrorEvent();
3080 if (canUseSourceElement) {
3082 *contentType = ContentType(type);
3084 *keySystem = system;
3085 m_currentSourceNode = source;
3086 m_nextChildNodeToConsider = source->nextSibling();
3088 m_currentSourceNode = nullptr;
3089 m_nextChildNodeToConsider = nullptr;
3094 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) -> %p, %s", this, m_currentSourceNode.get(), canUseSourceElement ? urlForLoggingMedia(mediaURL).utf8().data() : "");
3096 return canUseSourceElement ? mediaURL : KURL();
3099 void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source)
3101 WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded(%p, %p)", this, source);
3104 KURL url = source->getNonEmptyURLAttribute(srcAttr);
3105 WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data());
3108 // We should only consider a <source> element when there is not src attribute at all.
3109 if (fastHasAttribute(srcAttr))
3112 // 4.8.8 - If a source element is inserted as a child of a media element that has no src
3113 // attribute and whose networkState has the value NETWORK_EMPTY, the user agent must invoke
3114 // the media element's resource selection algorithm.
3115 if (networkState() == HTMLMediaElement::NETWORK_EMPTY) {
3116 scheduleDelayedAction(LoadMediaResource);
3117 m_nextChildNodeToConsider = source;
3121 if (m_currentSourceNode && source == m_currentSourceNode->nextSibling()) {
3122 WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded(%p) - <source> inserted immediately after current source", this);
3123 m_nextChildNodeToConsider = source;
3127 if (m_nextChildNodeToConsider)
3130 if (m_loadState != WaitingForSource)
3133 // 4.8.9.5, resource selection algorithm, source elements section:
3134 // 21. Wait until the node after pointer is a node other than the end of the list. (This step might wait forever.)
3135 // 22. Asynchronously await a stable state...
3136 // 23. Set the element's delaying-the-load-event flag back to true (this delays the load event again, in case
3137 // it hasn't been fired yet).
3138 setShouldDelayLoadEvent(true);
3140 // 24. Set the networkState back to NETWORK_LOADING.
3141 m_networkState = NETWORK_LOADING;
3143 // 25. Jump back to the find next candidate step above.
3144 m_nextChildNodeToConsider = source;
3145 scheduleNextSourceChild();
3148 void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement* source)
3150 WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p, %p)", this, source);
3153 KURL url = source->getNonEmptyURLAttribute(srcAttr);
3154 WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data());
3157 if (source != m_currentSourceNode && source != m_nextChildNodeToConsider)
3160 if (source == m_nextChildNodeToConsider) {
3161 if (m_currentSourceNode)
3162 m_nextChildNodeToConsider = m_currentSourceNode->nextSibling();
3163 WTF_LOG(Media, "HTMLMediaElement::sourceRemoved(%p) - m_nextChildNodeToConsider set to %p", this, m_nextChildNodeToConsider.get());
3164 } else if (source == m_currentSourceNode) {
3165 // Clear the current source node pointer, but don't change the movie as the spec says:
3166 // 4.8.8 - Dynamically modifying a source element and its attribute when the element is already
3167 // inserted in a video or audio element will have no effect.
3168 m_currentSourceNode = nullptr;
3169 WTF_LOG(Media, "HTMLMediaElement::sourceRemoved(%p) - m_currentSourceNode set to 0", this);
3173 void HTMLMediaElement::mediaPlayerTimeChanged()
3175 WTF_LOG(Media, "HTMLMediaElement::mediaPlayerTimeChanged(%p)", this);
3177 updateActiveTextTrackCues(currentTime());
3179 invalidateCachedTime();
3181 // 4.8.10.9 steps 12-14. Needed if no ReadyState change is associated with the seek.
3182 if (m_seeking && m_readyState >= HAVE_CURRENT_DATA && !webMediaPlayer()->seeking())
3185 // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity,
3186 // it will only queue a 'timeupdate' event if we haven't already posted one at the current
3188 scheduleTimeupdateEvent(false);
3190 double now = currentTime();
3191 double dur = duration();
3193 // When the current playback position reaches the end of the media resource when the direction of
3194 // playback is forwards, then the user agent must follow these steps:
3195 if (!std::isnan(dur) && dur && now >= dur && directionOfPlayback() == Forward) {
3196 // If the media element has a loop attribute specified and does not have a current media controller,
3197 if (loop() && !m_mediaController) {
3198 m_sentEndEvent = false;
3199 // then seek to the earliest possible position of the media resource and abort these steps.
3202 // If the media element does not have a current media controller, and the media element
3203 // has still ended playback, and the direction of playback is still forwards, and paused
3205 if (!m_mediaController && !m_paused) {
3206 // changes paused to true and fires a simple event named pause at the media element.
3208 scheduleEvent(EventTypeNames::pause);
3210 // Queue a task to fire a simple event named ended at the media element.
3211 if (!m_sentEndEvent) {
3212 m_sentEndEvent = true;
3213 scheduleEvent(EventTypeNames::ended);
3215 // If the media element has a current media controller, then report the controller state
3216 // for the media element's current media controller.
3217 updateMediaController();
3220 m_sentEndEvent = false;
3226 void HTMLMediaElement::mediaPlayerDurationChanged()
3228 WTF_LOG(Media, "HTMLMediaElement::mediaPlayerDurationChanged(%p)", this);
3229 // FIXME: Change MediaPlayerClient & WebMediaPlayer to convey
3230 // the currentTime when the duration change occured. The current
3231 // WebMediaPlayer implementations always clamp currentTime() to
3232 // duration() so the requestSeek condition here is always false.
3233 durationChanged(duration(), currentTime() > duration());
3236 void HTMLMediaElement::durationChanged(double duration, bool requestSeek)
3238 WTF_LOG(Media, "HTMLMediaElement::durationChanged(%p, %f, %d)", this, duration, requestSeek);
3240 // Abort if duration unchanged.
3241 if (m_duration == duration)
3244 WTF_LOG(Media, "HTMLMediaElement::durationChanged(%p) : %f -> %f", this, m_duration, duration);
3245 m_duration = duration;
3246 scheduleEvent(EventTypeNames::durationchange);
3248 if (hasMediaControls())
3249 mediaControls()->reset();
3251 renderer()->updateFromElement();
3257 void HTMLMediaElement::mediaPlayerPlaybackStateChanged()
3259 WTF_LOG(Media, "HTMLMediaElement::mediaPlayerPlaybackStateChanged(%p)", this);
3261 if (!webMediaPlayer())
3264 if (webMediaPlayer()->paused())
3270 void HTMLMediaElement::mediaPlayerRequestFullscreen()
3272 WTF_LOG(Media, "HTMLMediaElement::mediaPlayerRequestFullscreen(%p)", this);
3274 // The player is responsible for only invoking this callback in response to
3275 // user interaction or when it is technically required to play the video.
3276 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
3281 void HTMLMediaElement::mediaPlayerRequestSeek(double time)
3283 // The player is the source of this seek request.
3284 if (m_mediaController) {
3285 m_mediaController->setCurrentTime(time);
3288 setCurrentTime(time, ASSERT_NO_EXCEPTION);
3291 void HTMLMediaElement::remoteRouteAvailabilityChanged(bool routesAvailable)
3293 m_remoteRoutesAvailable = routesAvailable;
3294 if (hasMediaControls())
3295 mediaControls()->refreshCastButtonVisibility();
3298 void HTMLMediaElement::connectedToRemoteDevice()
3300 m_playingRemotely = true;
3301 if (hasMediaControls())
3302 mediaControls()->startedCasting();
3305 void HTMLMediaElement::disconnectedFromRemoteDevice()
3307 m_playingRemotely = false;
3308 if (hasMediaControls())
3309 mediaControls()->stoppedCasting();
3312 // MediaPlayerPresentation methods
3313 void HTMLMediaElement::mediaPlayerRepaint()
3316 m_webLayer->invalidate();
3318 updateDisplayState();
3320 renderer()->setShouldDoFullPaintInvalidation();
3323 void HTMLMediaElement::mediaPlayerSizeChanged()
3325 WTF_LOG(Media, "HTMLMediaElement::mediaPlayerSizeChanged(%p)", this);
3327 ASSERT(hasVideo()); // "resize" makes no sense absent video.
3328 if (m_readyState > HAVE_NOTHING && isHTMLVideoElement())
3329 scheduleEvent(EventTypeNames::resize);
3332 renderer()->updateFromElement();
3335 PassRefPtrWillBeRawPtr<TimeRanges> HTMLMediaElement::buffered() const
3338 return m_mediaSource->buffered();
3340 if (!webMediaPlayer())
3341 return TimeRanges::create();
3343 return TimeRanges::create(webMediaPlayer()->buffered());
3346 PassRefPtrWillBeRawPtr<TimeRanges> HTMLMediaElement::played()
3349 double time = currentTime();
3350 if (time > m_lastSeekTime)
3351 addPlayedRange(m_lastSeekTime, time);
3354 if (!m_playedTimeRanges)
3355 m_playedTimeRanges = TimeRanges::create();
3357 return m_playedTimeRanges->copy();
3360 PassRefPtrWillBeRawPtr<TimeRanges> HTMLMediaElement::seekable() const
3362 if (!webMediaPlayer())
3363 return TimeRanges::create();
3365 return TimeRanges::create(webMediaPlayer()->seekable());
3368 bool HTMLMediaElement::potentiallyPlaying() const
3370 // "pausedToBuffer" means the media engine's rate is 0, but only because it had to stop playing
3371 // when it ran out of buffered data. A movie is this state is "potentially playing", modulo the
3372 // checks in couldPlayIfEnoughData().
3373 bool pausedToBuffer = m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA;
3374 return (pausedToBuffer || m_readyState >= HAVE_FUTURE_DATA) && couldPlayIfEnoughData() && !isBlockedOnMediaController();
3377 bool HTMLMediaElement::couldPlayIfEnoughData() const
3379 return !paused() && !endedPlayback() && !stoppedDueToErrors();
3382 bool HTMLMediaElement::endedPlayback() const
3384 double dur = duration();
3385 if (!m_player || std::isnan(dur))
3388 // 4.8.10.8 Playing the media resource
3390 // A media element is said to have ended playback when the element's
3391 // readyState attribute is HAVE_METADATA or greater,
3392 if (m_readyState < HAVE_METADATA)
3395 // and the current playback position is the end of the media resource and the direction
3396 // of playback is forwards, Either the media element does not have a loop attribute specified,
3397 // or the media element has a current media controller.
3398 double now = currentTime();
3399 if (directionOfPlayback() == Forward)
3400 return dur > 0 && now >= dur && (!loop() || m_mediaController);
3402 // or the current playback position is the earliest possible position and the direction
3403 // of playback is backwards
3404 ASSERT(directionOfPlayback() == Backward);
3408 bool HTMLMediaElement::stoppedDueToErrors() const
3410 if (m_readyState >= HAVE_METADATA && m_error) {
3411 RefPtrWillBeRawPtr<TimeRanges> seekableRanges = seekable();
3412 if (!seekableRanges->contain(currentTime()))
3419 void HTMLMediaElement::updatePlayState()
3424 bool isPlaying = webMediaPlayer() && !webMediaPlayer()->paused();
3425 bool shouldBePlaying = potentiallyPlaying();
3427 WTF_LOG(Media, "HTMLMediaElement::updatePlayState(%p) - shouldBePlaying = %s, isPlaying = %s",
3428 this, boolString(shouldBePlaying), boolString(isPlaying));
3430 if (shouldBePlaying) {
3431 setDisplayMode(Video);
3432 invalidateCachedTime();
3435 // Set rate, muted before calling play in case they were set before the media engine was setup.
3436 // The media engine should just stash the rate and muted values since it isn't already playing.
3437 webMediaPlayer()->setRate(effectivePlaybackRate());
3439 webMediaPlayer()->play();
3442 if (hasMediaControls())
3443 mediaControls()->playbackStarted();
3444 startPlaybackProgressTimer();
3447 } else { // Should not be playing right now
3449 webMediaPlayer()->pause();
3450 refreshCachedTime();
3452 m_playbackProgressTimer.stop();
3454 double time = currentTime();
3455 if (time > m_lastSeekTime)
3456 addPlayedRange(m_lastSeekTime, time);
3458 if (couldPlayIfEnoughData())
3461 if (hasMediaControls())
3462 mediaControls()->playbackStopped();
3465 updateMediaController();
3468 renderer()->updateFromElement();
3471 void HTMLMediaElement::stopPeriodicTimers()
3473 m_progressEventTimer.stop();
3474 m_playbackProgressTimer.stop();
3477 void HTMLMediaElement::userCancelledLoad()
3479 WTF_LOG(Media, "HTMLMediaElement::userCancelledLoad(%p)", this);
3481 // If the media data fetching process is aborted by the user:
3483 // 1 - The user agent should cancel the fetching process.
3484 clearMediaPlayer(-1);
3486 if (m_networkState == NETWORK_EMPTY || m_completelyLoaded)
3489 // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED.
3490 m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED);
3492 // 3 - Queue a task to fire a simple event named error at the media element.
3493 scheduleEvent(EventTypeNames::abort);
3497 // 4 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the
3498 // element's networkState attribute to the NETWORK_EMPTY value and queue a task to fire a
3499 // simple event named emptied at the element. Otherwise, set the element's networkState
3500 // attribute to the NETWORK_IDLE value.
3501 if (m_readyState == HAVE_NOTHING) {
3502 m_networkState = NETWORK_EMPTY;
3503 scheduleEvent(EventTypeNames::emptied);
3505 m_networkState = NETWORK_IDLE;
3508 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
3509 setShouldDelayLoadEvent(false);
3511 // 6 - Abort the overall resource selection algorithm.
3512 m_currentSourceNode = nullptr;
3514 // Reset m_readyState since m_player is gone.
3515 m_readyState = HAVE_NOTHING;
3516 invalidateCachedTime();
3517 updateMediaController();
3518 updateActiveTextTrackCues(0);
3521 void HTMLMediaElement::clearMediaPlayerAndAudioSourceProviderClientWithoutLocking()
3523 #if ENABLE(WEB_AUDIO)
3524 if (audioSourceProvider())
3525 audioSourceProvider()->setClient(0);
3530 void HTMLMediaElement::clearMediaPlayer(int flags)
3532 forgetResourceSpecificTracks();
3536 cancelDeferredLoad();
3539 AudioSourceProviderClientLockScope scope(*this);
3540 clearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
3543 stopPeriodicTimers();
3546 m_pendingActionFlags &= ~flags;
3547 m_loadState = WaitingForSource;
3549 // We can't cast if we don't have a media player.
3550 m_remoteRoutesAvailable = false;
3551 m_playingRemotely = false;
3552 if (hasMediaControls()) {
3553 mediaControls()->refreshCastButtonVisibility();
3557 configureTextTrackDisplay(AssumeNoVisibleChange);
3560 void HTMLMediaElement::stop()
3562 WTF_LOG(Media, "HTMLMediaElement::stop(%p)", this);
3564 if (m_playing && m_initialPlayWithoutUserGestures)
3565 gesturelessInitialPlayHalted();
3568 userCancelledLoad();
3570 // Stop the playback without generating events
3576 renderer()->updateFromElement();
3578 stopPeriodicTimers();
3579 cancelPendingEventsAndCallbacks();
3581 m_asyncEventQueue->close();
3583 // Ensure that hasPendingActivity() is not preventing garbage collection, since otherwise this
3584 // media element will simply leak.
3585 ASSERT(!hasPendingActivity());
3588 bool HTMLMediaElement::hasPendingActivity() const
3590 // The delaying-the-load-event flag is set by resource selection algorithm when looking for a
3591 // resource to load, before networkState has reached to NETWORK_LOADING.
3592 if (m_shouldDelayLoadEvent)
3595 // When networkState is NETWORK_LOADING, progress and stalled events may be fired.
3596 if (m_networkState == NETWORK_LOADING)
3599 // When playing or if playback may continue, timeupdate events may be fired.
3600 if (couldPlayIfEnoughData())
3603 // When the seek finishes timeupdate and seeked events will be fired.
3607 // When connected to a MediaSource, e.g. setting MediaSource.duration will cause a
3608 // durationchange event to be fired.
3612 // Wait for any pending events to be fired.
3613 if (m_asyncEventQueue->hasPendingEvents())
3619 void HTMLMediaElement::contextDestroyed()
3621 // With Oilpan the ExecutionContext is weakly referenced from the media
3622 // controller and so it will clear itself on destruction.
3624 if (m_mediaController)
3625 m_mediaController->clearExecutionContext();
3627 ActiveDOMObject::contextDestroyed();
3630 bool HTMLMediaElement::isFullscreen() const
3632 return Fullscreen::isActiveFullScreenElement(*this);
3635 void HTMLMediaElement::enterFullscreen()
3637 WTF_LOG(Media, "HTMLMediaElement::enterFullscreen(%p)", this);
3639 Fullscreen::from(document()).requestFullscreen(*this, Fullscreen::PrefixedVideoRequest);
3642 void HTMLMediaElement::exitFullscreen()
3644 WTF_LOG(Media, "HTMLMediaElement::exitFullscreen(%p)", this);
3646 Fullscreen::from(document()).exitFullscreen();
3649 void HTMLMediaElement::didBecomeFullscreenElement()
3651 if (hasMediaControls())
3652 mediaControls()->enteredFullscreen();
3653 if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isHTMLVideoElement())
3654 document().renderView()->compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree);
3657 void HTMLMediaElement::willStopBeingFullscreenElement()
3659 if (hasMediaControls())
3660 mediaControls()->exitedFullscreen();
3661 if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isHTMLVideoElement())
3662 document().renderView()->compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree);
3665 blink::WebLayer* HTMLMediaElement::platformLayer() const
3670 bool HTMLMediaElement::hasClosedCaptions() const
3673 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
3674 if (m_textTracks->item(i)->readinessState() == TextTrack::FailedToLoad)
3677 if (m_textTracks->item(i)->kind() == TextTrack::captionsKeyword()
3678 || m_textTracks->item(i)->kind() == TextTrack::subtitlesKeyword())
3685 bool HTMLMediaElement::closedCaptionsVisible() const
3687 return m_closedCaptionsVisible;
3690 void HTMLMediaElement::updateTextTrackDisplay()
3692 WTF_LOG(Media, "HTMLMediaElement::updateTextTrackDisplay(%p)", this);
3694 if (!createMediaControls())
3697 mediaControls()->updateTextTrackDisplay();
3700 void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible)
3702 WTF_LOG(Media, "HTMLMediaElement::setClosedCaptionsVisible(%p, %s)", this, boolString(closedCaptionVisible));
3704 if (!m_player || !hasClosedCaptions())
3707 m_closedCaptionsVisible = closedCaptionVisible;
3709 markCaptionAndSubtitleTracksAsUnconfigured();
3710 m_processingPreferenceChange = true;
3711 configureTextTracks();
3712 m_processingPreferenceChange = false;
3714 updateTextTrackDisplay();
3717 unsigned HTMLMediaElement::webkitAudioDecodedByteCount() const
3719 if (!webMediaPlayer())
3721 return webMediaPlayer()->audioDecodedByteCount();
3724 unsigned HTMLMediaElement::webkitVideoDecodedByteCount() const
3726 if (!webMediaPlayer())
3728 return webMediaPlayer()->videoDecodedByteCount();
3731 bool HTMLMediaElement::isURLAttribute(const Attribute& attribute) const
3733 return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute);
3736 void HTMLMediaElement::setShouldDelayLoadEvent(bool shouldDelay)
3738 if (m_shouldDelayLoadEvent == shouldDelay)
3741 WTF_LOG(Media, "HTMLMediaElement::setShouldDelayLoadEvent(%p, %s)", this, boolString(shouldDelay));
3743 m_shouldDelayLoadEvent = shouldDelay;
3745 document().incrementLoadEventDelayCount();
3747 document().decrementLoadEventDelayCount();
3751 MediaControls* HTMLMediaElement::mediaControls() const
3753 return toMediaControls(userAgentShadowRoot()->firstChild());
3756 bool HTMLMediaElement::hasMediaControls() const
3758 if (ShadowRoot* userAgent = userAgentShadowRoot()) {
3759 Node* node = userAgent->firstChild();
3760 ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isMediaControls());
3767 bool HTMLMediaElement::createMediaControls()
3769 if (hasMediaControls())
3772 RefPtrWillBeRawPtr<MediaControls> mediaControls = MediaControls::create(*this);
3776 mediaControls->reset();
3778 mediaControls->enteredFullscreen();
3780 ensureUserAgentShadowRoot().appendChild(mediaControls);
3782 if (!shouldShowControls() || !inDocument())
3783 mediaControls->hide();
3788 void HTMLMediaElement::configureMediaControls()
3790 if (!inDocument()) {
3791 if (hasMediaControls())
3792 mediaControls()->hide();
3796 if (!createMediaControls())
3799 mediaControls()->reset();
3800 if (shouldShowControls())
3801 mediaControls()->show();
3803 mediaControls()->hide();
3806 void HTMLMediaElement::configureTextTrackDisplay(VisibilityChangeAssumption assumption)
3808 ASSERT(m_textTracks);
3809 WTF_LOG(Media, "HTMLMediaElement::configureTextTrackDisplay(%p)", this);
3811 if (m_processingPreferenceChange)
3814 bool haveVisibleTextTrack = false;
3815 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
3816 if (m_textTracks->item(i)->mode() == TextTrack::showingKeyword()) {
3817 haveVisibleTextTrack = true;
3822 if (assumption == AssumeNoVisibleChange
3823 && m_haveVisibleTextTrack == haveVisibleTextTrack) {
3824 updateActiveTextTrackCues(currentTime());
3827 m_haveVisibleTextTrack = haveVisibleTextTrack;
3828 m_closedCaptionsVisible = m_haveVisibleTextTrack;
3830 if (!m_haveVisibleTextTrack && !hasMediaControls())
3832 if (!createMediaControls())
3835 mediaControls()->changedClosedCaptionsVisibility();
3837 updateActiveTextTrackCues(currentTime());
3838 updateTextTrackDisplay();
3841 void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured()
3846 // Mark all tracks as not "configured" so that configureTextTracks()
3847 // will reconsider which tracks to display in light of new user preferences
3848 // (e.g. default tracks should not be displayed if the user has turned off
3849 // captions and non-default tracks should be displayed based on language
3850 // preferences if the user has turned captions on).
3851 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
3852 RefPtrWillBeRawPtr<TextTrack> textTrack = m_textTracks->item(i);
3853 String kind = textTrack->kind();
3855 if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword())
3856 textTrack->setHasBeenConfigured(false);
3860 void* HTMLMediaElement::preDispatchEventHandler(Event* event)
3862 if (event && event->type() == EventTypeNames::webkitfullscreenchange)
3863 configureMediaControls();
3868 void HTMLMediaElement::createMediaPlayer()
3870 AudioSourceProviderClientLockScope scope(*this);
3874 m_player = MediaPlayer::create(this);
3876 // We haven't yet found out if any remote routes are available.
3877 m_remoteRoutesAvailable = false;
3878 m_playingRemotely = false;
3880 #if ENABLE(WEB_AUDIO)
3881 if (m_audioSourceNode && audioSourceProvider()) {
3882 // When creating the player, make sure its AudioSourceProvider knows about the client.
3883 audioSourceProvider()->setClient(m_audioSourceNode);
3888 #if ENABLE(WEB_AUDIO)
3889 void HTMLMediaElement::setAudioSourceNode(AudioSourceProviderClient* sourceNode)
3891 m_audioSourceNode = sourceNode;
3893 AudioSourceProviderClientLockScope scope(*this);
3894 if (audioSourceProvider())
3895 audioSourceProvider()->setClient(m_audioSourceNode);
3898 AudioSourceProvider* HTMLMediaElement::audioSourceProvider()
3901 return m_player->audioSourceProvider();
3907 const AtomicString& HTMLMediaElement::mediaGroup() const
3909 return fastGetAttribute(mediagroupAttr);
3912 void HTMLMediaElement::setMediaGroup(const AtomicString& group)
3914 // When a media element is created with a mediagroup attribute, and when a media element's mediagroup
3915 // attribute is set, changed, or removed, the user agent must run the following steps:
3916 // 1. Let _R [this] be the media element in question.
3917 // 2. Let m have no current media controller, if it currently has one.
3918 setControllerInternal(nullptr);
3920 // 3. If m's mediagroup attribute is being removed, then abort these steps.
3921 if (group.isNull() || group.isEmpty())
3924 // 4. If there is another media element whose Document is the same as m's Document (even if one or both
3925 // of these elements are not actually in the Document),
3926 WeakMediaElementSet elements = documentToElementSetMap().get(&document());
3927 for (const auto& element : elements) {
3928 if (element == this)
3931 // and which also has a mediagroup attribute, and whose mediagroup attribute has the same value as
3932 // the new value of m's mediagroup attribute,
3933 if (element->mediaGroup() == group) {
3934 // then let controller be that media element's current media controller.
3935 setControllerInternal(element->controller());
3940 // Otherwise, let controller be a newly created MediaController.
3941 setControllerInternal(MediaController::create(Node::executionContext()));
3944 MediaController* HTMLMediaElement::controller() const
3946 return m_mediaController.get();
3949 void HTMLMediaElement::setController(PassRefPtrWillBeRawPtr<MediaController> controller)
3951 // 4.8.10.11.2 Media controllers: controller attribute.
3952 // On setting, it must first remove the element's mediagroup attribute, if any,
3953 removeAttribute(mediagroupAttr);
3954 // and then set the current media controller to the given value.
3955 setControllerInternal(controller);
3958 void HTMLMediaElement::setControllerInternal(PassRefPtrWillBeRawPtr<MediaController> controller)
3960 if (m_mediaController)
3961 m_mediaController->removeMediaElement(this);
3963 m_mediaController = controller;
3965 if (m_mediaController)
3966 m_mediaController->addMediaElement(this);
3969 void HTMLMediaElement::updateMediaController()
3971 if (m_mediaController)
3972 m_mediaController->reportControllerState();
3975 bool HTMLMediaElement::isBlocked() const
3977 // A media element is a blocked media element if its readyState attribute is in the
3978 // HAVE_NOTHING state, the HAVE_METADATA state, or the HAVE_CURRENT_DATA state,
3979 // or if the element has paused for user interaction or paused for in-band content.
3980 if (m_readyState <= HAVE_CURRENT_DATA)
3986 bool HTMLMediaElement::isBlockedOnMediaController() const
3988 if (!m_mediaController)
3991 // A media element is blocked on its media controller if the MediaController is a blocked
3992 // media controller,
3993 if (m_mediaController->isBlocked())
3996 // or if its media controller position is either before the media resource's earliest possible
3997 // position relative to the MediaController's timeline or after the end of the media resource
3998 // relative to the MediaController's timeline.
3999 double mediaControllerPosition = m_mediaController->currentTime();
4000 if (mediaControllerPosition < 0 || mediaControllerPosition > duration())
4006 WebMediaPlayer::CORSMode HTMLMediaElement::corsMode() const
4008 const AtomicString& crossOriginMode = fastGetAttribute(crossoriginAttr);
4009 if (crossOriginMode.isNull())
4010 return WebMediaPlayer::CORSModeUnspecified;
4011 if (equalIgnoringCase(crossOriginMode, "use-credentials"))
4012 return WebMediaPlayer::CORSModeUseCredentials;
4013 return WebMediaPlayer::CORSModeAnonymous;
4016 void HTMLMediaElement::mediaPlayerSetWebLayer(blink::WebLayer* webLayer)
4018 if (webLayer == m_webLayer)
4021 // If either of the layers is null we need to enable or disable compositing. This is done by triggering a style recalc.
4022 if ((!m_webLayer || !webLayer)
4027 setNeedsCompositingUpdate();
4030 GraphicsLayer::unregisterContentsLayer(m_webLayer);
4031 m_webLayer = webLayer;
4033 GraphicsLayer::registerContentsLayer(m_webLayer);
4036 void HTMLMediaElement::mediaPlayerMediaSourceOpened(blink::WebMediaSource* webMediaSource)
4038 m_mediaSource->setWebMediaSourceAndOpen(adoptPtr(webMediaSource));
4041 bool HTMLMediaElement::isInteractiveContent() const
4043 return fastHasAttribute(controlsAttr);
4046 void HTMLMediaElement::defaultEventHandler(Event* event)
4048 if (event->type() == EventTypeNames::focusin) {
4049 if (hasMediaControls())
4050 mediaControls()->mediaElementFocused();
4052 HTMLElement::defaultEventHandler(event);
4055 void HTMLMediaElement::trace(Visitor* visitor)
4058 visitor->trace(m_playedTimeRanges);
4059 visitor->trace(m_asyncEventQueue);
4060 visitor->trace(m_error);
4061 visitor->trace(m_currentSourceNode);
4062 visitor->trace(m_nextChildNodeToConsider);
4063 visitor->trace(m_mediaSource);
4064 visitor->trace(m_audioTracks);
4065 visitor->trace(m_videoTracks);
4066 visitor->trace(m_textTracks);
4067 visitor->trace(m_textTracksWhenResourceSelectionBegan);
4068 visitor->trace(m_mediaController);
4069 #if ENABLE(WEB_AUDIO)
4070 visitor->registerWeakMembers<HTMLMediaElement, &HTMLMediaElement::clearWeakMembers>(this);
4072 HeapSupplementable<HTMLMediaElement>::trace(visitor);
4074 HTMLElement::trace(visitor);
4077 void HTMLMediaElement::createPlaceholderTracksIfNecessary()
4079 if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
4082 // Create a placeholder audio track if the player says it has audio but it didn't explicitly announce the tracks.
4083 if (hasAudio() && !audioTracks().length())
4084 addAudioTrack("audio", WebMediaPlayerClient::AudioTrackKindMain, "Audio Track", "", true);
4086 // Create a placeholder video track if the player says it has video but it didn't explicitly announce the tracks.
4087 if (webMediaPlayer() && webMediaPlayer()->hasVideo() && !videoTracks().length())
4088 addVideoTrack("video", WebMediaPlayerClient::VideoTrackKindMain, "Video Track", "", true);
4091 void HTMLMediaElement::selectInitialTracksIfNecessary()
4093 if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
4096 // Enable the first audio track if an audio track hasn't been enabled yet.
4097 if (audioTracks().length() > 0 && !audioTracks().hasEnabledTrack())
4098 audioTracks().anonymousIndexedGetter(0)->setEnabled(true);
4100 // Select the first video track if a video track hasn't been selected yet.
4101 if (videoTracks().length() > 0 && videoTracks().selectedIndex() == -1)
4102 videoTracks().anonymousIndexedGetter(0)->setSelected(true);
4105 #if ENABLE(WEB_AUDIO)
4106 void HTMLMediaElement::clearWeakMembers(Visitor* visitor)
4108 if (!visitor->isAlive(m_audioSourceNode) && audioSourceProvider())
4109 audioSourceProvider()->setClient(nullptr);