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"
30 #include "bindings/v8/ExceptionState.h"
31 #include "bindings/v8/ExceptionStatePlaceholder.h"
32 #include "bindings/v8/ScriptController.h"
33 #include "bindings/v8/ScriptEventListener.h"
34 #include "core/HTMLNames.h"
35 #include "core/css/MediaList.h"
36 #include "core/dom/Attribute.h"
37 #include "core/dom/ExceptionCode.h"
38 #include "core/dom/FullscreenElementStack.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/NotImplemented.h"
70 #include "platform/RuntimeEnabledFeatures.h"
71 #include "platform/UserGestureIndicator.h"
72 #include "platform/graphics/GraphicsLayer.h"
73 #include "platform/weborigin/SecurityOrigin.h"
74 #include "public/platform/Platform.h"
75 #include "public/platform/WebContentDecryptionModule.h"
76 #include "public/platform/WebInbandTextTrack.h"
77 #include "wtf/CurrentTime.h"
78 #include "wtf/MathExtras.h"
79 #include "wtf/NonCopyingSort.h"
80 #include "wtf/Uint8Array.h"
81 #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 static const AtomicString& AudioKindToString(WebMediaPlayerClient::AudioTrackKind kind)
174 case WebMediaPlayerClient::AudioTrackKindNone:
176 case WebMediaPlayerClient::AudioTrackKindAlternative:
177 return AudioTrack::alternativeKeyword();
178 case WebMediaPlayerClient::AudioTrackKindDescriptions:
179 return AudioTrack::descriptionsKeyword();
180 case WebMediaPlayerClient::AudioTrackKindMain:
181 return AudioTrack::mainKeyword();
182 case WebMediaPlayerClient::AudioTrackKindMainDescriptions:
183 return AudioTrack::mainDescriptionsKeyword();
184 case WebMediaPlayerClient::AudioTrackKindTranslation:
185 return AudioTrack::translationKeyword();
186 case WebMediaPlayerClient::AudioTrackKindCommentary:
187 return AudioTrack::commentaryKeyword();
190 ASSERT_NOT_REACHED();
194 static const AtomicString& VideoKindToString(WebMediaPlayerClient::VideoTrackKind kind)
197 case WebMediaPlayerClient::VideoTrackKindNone:
199 case WebMediaPlayerClient::VideoTrackKindAlternative:
200 return VideoTrack::alternativeKeyword();
201 case WebMediaPlayerClient::VideoTrackKindCaptions:
202 return VideoTrack::captionsKeyword();
203 case WebMediaPlayerClient::VideoTrackKindMain:
204 return VideoTrack::mainKeyword();
205 case WebMediaPlayerClient::VideoTrackKindSign:
206 return VideoTrack::signKeyword();
207 case WebMediaPlayerClient::VideoTrackKindSubtitles:
208 return VideoTrack::subtitlesKeyword();
209 case WebMediaPlayerClient::VideoTrackKindCommentary:
210 return VideoTrack::commentaryKeyword();
213 ASSERT_NOT_REACHED();
217 static bool canLoadURL(const KURL& url, const ContentType& contentType, const String& keySystem)
219 DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
221 String contentMIMEType = contentType.type().lower();
222 String contentTypeCodecs = contentType.parameter(codecs);
224 // If the MIME type is missing or is not meaningful, try to figure it out from the URL.
225 if (contentMIMEType.isEmpty() || contentMIMEType == "application/octet-stream" || contentMIMEType == "text/plain") {
226 if (url.protocolIsData())
227 contentMIMEType = mimeTypeFromDataURL(url.string());
230 // If no MIME type is specified, always attempt to load.
231 if (contentMIMEType.isEmpty())
234 // 4.8.10.3 MIME types - In the absence of a specification to the contrary, the MIME type "application/octet-stream"
235 // when used with parameters, e.g. "application/octet-stream;codecs=theora", is a type that the user agent knows
237 if (contentMIMEType != "application/octet-stream" || contentTypeCodecs.isEmpty()) {
238 WebMimeRegistry::SupportsType supported = blink::Platform::current()->mimeRegistry()->supportsMediaMIMEType(contentMIMEType, contentTypeCodecs, keySystem.lower());
239 return supported > WebMimeRegistry::IsNotSupported;
245 WebMimeRegistry::SupportsType HTMLMediaElement::supportsType(const ContentType& contentType, const String& keySystem)
247 DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
249 if (!RuntimeEnabledFeatures::mediaEnabled())
250 return WebMimeRegistry::IsNotSupported;
252 String type = contentType.type().lower();
253 // The codecs string is not lower-cased because MP4 values are case sensitive
254 // per http://tools.ietf.org/html/rfc4281#page-7.
255 String typeCodecs = contentType.parameter(codecs);
256 String system = keySystem.lower();
259 return WebMimeRegistry::IsNotSupported;
261 // 4.8.10.3 MIME types - The canPlayType(type) method must return the empty string if type is a type that the
262 // user agent knows it cannot render or is the type "application/octet-stream"
263 if (type == "application/octet-stream")
264 return WebMimeRegistry::IsNotSupported;
266 return blink::Platform::current()->mimeRegistry()->supportsMediaMIMEType(type, typeCodecs, system);
269 URLRegistry* HTMLMediaElement::s_mediaStreamRegistry = 0;
271 void HTMLMediaElement::setMediaStreamRegistry(URLRegistry* registry)
273 ASSERT(!s_mediaStreamRegistry);
274 s_mediaStreamRegistry = registry;
277 bool HTMLMediaElement::isMediaStreamURL(const String& url)
279 return s_mediaStreamRegistry ? s_mediaStreamRegistry->contains(url) : false;
282 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document)
283 : HTMLElement(tagName, document)
284 , ActiveDOMObject(&document)
285 , m_loadTimer(this, &HTMLMediaElement::loadTimerFired)
286 , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired)
287 , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired)
288 , m_audioTracksTimer(this, &HTMLMediaElement::audioTracksTimerFired)
289 , m_playedTimeRanges()
290 , m_asyncEventQueue(GenericEventQueue::create(this))
291 , m_playbackRate(1.0f)
292 , m_defaultPlaybackRate(1.0f)
293 , m_networkState(NETWORK_EMPTY)
294 , m_readyState(HAVE_NOTHING)
295 , m_readyStateMaximum(HAVE_NOTHING)
298 , m_previousProgressTime(std::numeric_limits<double>::max())
299 , m_duration(std::numeric_limits<double>::quiet_NaN())
300 , m_lastTimeUpdateEventWallTime(0)
301 , m_lastTimeUpdateEventMovieTime(std::numeric_limits<double>::max())
302 , m_loadState(WaitingForSource)
303 , m_deferredLoadState(NotDeferred)
304 , m_deferredLoadTimer(this, &HTMLMediaElement::deferredLoadTimerFired)
306 , m_preload(MediaPlayer::Auto)
307 , m_displayMode(Unknown)
308 , m_cachedTime(MediaPlayer::invalidTime())
309 , m_cachedTimeWallClockUpdateTime(0)
310 , m_minimumWallClockTimeToCacheMediaTime(0)
311 , m_fragmentStartTime(MediaPlayer::invalidTime())
312 , m_fragmentEndTime(MediaPlayer::invalidTime())
313 , m_pendingActionFlags(0)
314 , m_userGestureRequiredForPlay(false)
316 , m_shouldDelayLoadEvent(false)
317 , m_haveFiredLoadedData(false)
319 , m_autoplaying(true)
323 , m_sentStalledEvent(false)
324 , m_sentEndEvent(false)
325 , m_pausedInternal(false)
326 , m_closedCaptionsVisible(false)
327 , m_completelyLoaded(false)
328 , m_havePreparedToPlay(false)
329 , m_tracksAreReady(true)
330 , m_haveVisibleTextTrack(false)
331 , m_processingPreferenceChange(false)
333 , m_isFinalizing(false)
335 , m_lastTextTrackUpdateTime(-1)
336 , m_audioTracks(AudioTrackList::create(*this))
337 , m_videoTracks(VideoTrackList::create(*this))
338 , m_textTracks(nullptr)
339 , m_ignoreTrackDisplayUpdate(0)
340 #if ENABLE(WEB_AUDIO)
341 , m_audioSourceNode(nullptr)
344 ASSERT(RuntimeEnabledFeatures::mediaEnabled());
346 WTF_LOG(Media, "HTMLMediaElement::HTMLMediaElement");
347 ScriptWrappable::init(this);
349 if (document.settings() && document.settings()->mediaPlaybackRequiresUserGesture())
350 m_userGestureRequiredForPlay = true;
352 setHasCustomStyleCallbacks();
353 addElementToDocumentMap(this, &document);
356 HTMLMediaElement::~HTMLMediaElement()
358 WTF_LOG(Media, "HTMLMediaElement::~HTMLMediaElement");
361 // If the HTMLMediaElement dies with the document we are not
362 // allowed to touch the document to adjust delay load event counts
363 // because the document could have been already
364 // destructed. However, if the HTMLMediaElement dies with the
365 // document there is no need to change the delayed load counts
366 // because no load event will fire anyway. If the document is
367 // still alive we do have to decrement the load delay counts. We
368 // determine if the document is alive via the ActiveDOMObject
369 // which is a context lifecycle observer. If the Document has been
370 // destructed ActiveDOMObject::executionContext() returns 0.
371 if (ActiveDOMObject::executionContext())
372 setShouldDelayLoadEvent(false);
374 // HTMLMediaElement and m_asyncEventQueue always become unreachable
375 // together. So HTMLMediaElemenet and m_asyncEventQueue are destructed in
376 // the same GC. We don't need to close it explicitly in Oilpan.
377 m_asyncEventQueue->close();
379 setShouldDelayLoadEvent(false);
382 m_textTracks->clearOwner();
383 m_audioTracks->shutdown();
384 m_videoTracks->shutdown();
386 if (m_mediaController) {
387 m_mediaController->removeMediaElement(this);
388 m_mediaController = nullptr;
395 removeElementFromDocumentMap(this, &document());
398 // Destroying the player may cause a resource load to be canceled,
399 // which could result in userCancelledLoad() being called back.
400 // Setting m_completelyLoaded ensures that such a call will not cause
401 // us to dispatch an abort event, which would result in a crash.
402 // See http://crbug.com/233654 for more details.
403 m_completelyLoaded = true;
405 // With Oilpan load events on the Document are always delayed during
406 // sweeping so we don't need to explicitly increment and decrement
407 // load event delay counts.
409 // Destroying the player may cause a resource load to be canceled,
410 // which could result in Document::dispatchWindowLoadEvent() being
411 // called via ResourceFetch::didLoadResource() then
412 // FrameLoader::loadDone(). To prevent load event dispatching during
413 // object destruction, we use Document::incrementLoadEventDelayCount().
414 // See http://crbug.com/275223 for more details.
415 document().incrementLoadEventDelayCount();
419 // Oilpan: the player must be released, but the player object
420 // cannot safely access this player client any longer as parts of
421 // it may have been finalized already (like the media element's
422 // supplementable table.) Handled for now by entering an
423 // is-finalizing state, which is explicitly checked for if the
424 // player tries to access the media element during shutdown.
426 // FIXME: Oilpan: move the media player to the heap instead and
427 // avoid having to finalize it from here; this whole #if block
428 // could then be removed (along with the state bit it depends on.)
430 m_isFinalizing = true;
433 // The m_audioSourceNode is either dead already or it is dying together with
434 // this HTMLMediaElement which it strongly keeps alive.
435 #if ENABLE(WEB_AUDIO) && !ENABLE(OILPAN)
436 ASSERT(!m_audioSourceNode);
438 clearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
441 document().decrementLoadEventDelayCount();
445 void HTMLMediaElement::didMoveToNewDocument(Document& oldDocument)
447 WTF_LOG(Media, "HTMLMediaElement::didMoveToNewDocument");
449 if (m_shouldDelayLoadEvent) {
450 document().incrementLoadEventDelayCount();
451 // Note: Keeping the load event delay count increment on oldDocument that was added
452 // when m_shouldDelayLoadEvent was set so that destruction of m_player can not
453 // cause load event dispatching in oldDocument.
455 // Incrementing the load event delay count so that destruction of m_player can not
456 // cause load event dispatching in oldDocument.
457 oldDocument.incrementLoadEventDelayCount();
460 removeElementFromDocumentMap(this, &oldDocument);
461 addElementToDocumentMap(this, &document());
463 // FIXME: This is a temporary fix to prevent this object from causing the
464 // MediaPlayer to dereference LocalFrame and FrameLoader pointers from the
465 // previous document. A proper fix would provide a mechanism to allow this
466 // object to refresh the MediaPlayer's LocalFrame and FrameLoader references on
467 // document changes so that playback can be resumed properly.
470 // Decrement the load event delay count on oldDocument now that m_player has been destroyed
471 // and there is no risk of dispatching a load event from within the destructor.
472 oldDocument.decrementLoadEventDelayCount();
474 ActiveDOMObject::didMoveToNewExecutionContext(&document());
475 HTMLElement::didMoveToNewDocument(oldDocument);
478 bool HTMLMediaElement::supportsFocus() const
480 if (ownerDocument()->isMediaDocument())
483 // If no controls specified, we should still be able to focus the element if it has tabIndex.
484 return controls() || HTMLElement::supportsFocus();
487 bool HTMLMediaElement::isMouseFocusable() const
492 void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
494 if (name == srcAttr) {
495 // Trigger a reload, as long as the 'src' attribute is present.
496 if (!value.isNull()) {
497 clearMediaPlayer(LoadMediaResource);
498 scheduleDelayedAction(LoadMediaResource);
500 } else if (name == controlsAttr) {
501 configureMediaControls();
502 } else if (name == preloadAttr) {
503 if (equalIgnoringCase(value, "none"))
504 m_preload = MediaPlayer::None;
505 else if (equalIgnoringCase(value, "metadata"))
506 m_preload = MediaPlayer::MetaData;
508 // The spec does not define an "invalid value default" but "auto" is suggested as the
509 // "missing value default", so use it for everything except "none" and "metadata"
510 m_preload = MediaPlayer::Auto;
513 // The attribute must be ignored if the autoplay attribute is present
514 if (!autoplay() && m_player)
517 } else if (name == mediagroupAttr && RuntimeEnabledFeatures::mediaControllerEnabled()) {
518 setMediaGroup(value);
520 HTMLElement::parseAttribute(name, value);
524 void HTMLMediaElement::finishParsingChildren()
526 HTMLElement::finishParsingChildren();
528 if (Traversal<HTMLTrackElement>::firstChild(*this))
529 scheduleDelayedAction(LoadTextTrackResource);
532 bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style)
534 return controls() ? HTMLElement::rendererIsNeeded(style) : false;
537 RenderObject* HTMLMediaElement::createRenderer(RenderStyle*)
539 return new RenderMedia(this);
542 Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode* insertionPoint)
544 WTF_LOG(Media, "HTMLMediaElement::insertedInto");
546 HTMLElement::insertedInto(insertionPoint);
547 if (insertionPoint->inDocument()) {
550 if (!getAttribute(srcAttr).isEmpty() && m_networkState == NETWORK_EMPTY)
551 scheduleDelayedAction(LoadMediaResource);
554 return InsertionShouldCallDidNotifySubtreeInsertions;
557 void HTMLMediaElement::didNotifySubtreeInsertionsToDocument()
559 configureMediaControls();
562 void HTMLMediaElement::removedFrom(ContainerNode* insertionPoint)
564 WTF_LOG(Media, "HTMLMediaElement::removedFrom");
567 if (insertionPoint->inDocument() && insertionPoint->document().isActive()) {
568 configureMediaControls();
569 if (m_networkState > NETWORK_EMPTY)
573 HTMLElement::removedFrom(insertionPoint);
576 void HTMLMediaElement::attach(const AttachContext& context)
578 HTMLElement::attach(context);
581 renderer()->updateFromElement();
584 void HTMLMediaElement::didRecalcStyle(StyleRecalcChange)
587 renderer()->updateFromElement();
590 void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType)
592 WTF_LOG(Media, "HTMLMediaElement::scheduleDelayedAction");
594 if ((actionType & LoadMediaResource) && !(m_pendingActionFlags & LoadMediaResource)) {
596 m_pendingActionFlags |= LoadMediaResource;
599 if (actionType & LoadTextTrackResource)
600 m_pendingActionFlags |= LoadTextTrackResource;
602 if (!m_loadTimer.isActive())
603 m_loadTimer.startOneShot(0, FROM_HERE);
606 void HTMLMediaElement::scheduleNextSourceChild()
608 // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
609 m_pendingActionFlags |= LoadMediaResource;
610 m_loadTimer.startOneShot(0, FROM_HERE);
613 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
615 scheduleEvent(Event::createCancelable(eventName));
618 void HTMLMediaElement::scheduleEvent(PassRefPtrWillBeRawPtr<Event> event)
621 WTF_LOG(Media, "HTMLMediaElement::scheduleEvent - scheduling '%s'", event->type().ascii().data());
623 m_asyncEventQueue->enqueueEvent(event);
626 void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*)
628 if (m_pendingActionFlags & LoadTextTrackResource)
629 configureTextTracks();
631 if (m_pendingActionFlags & LoadMediaResource) {
632 if (m_loadState == LoadingFromSourceElement)
633 loadNextSourceChild();
638 m_pendingActionFlags = 0;
641 PassRefPtrWillBeRawPtr<MediaError> HTMLMediaElement::error() const
646 void HTMLMediaElement::setSrc(const AtomicString& url)
648 setAttribute(srcAttr, url);
651 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
653 return m_networkState;
656 String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySystem) const
658 if (!keySystem.isNull())
659 UseCounter::count(document(), UseCounter::CanPlayTypeKeySystem);
661 WebMimeRegistry::SupportsType support = supportsType(ContentType(mimeType), keySystem);
667 case WebMimeRegistry::IsNotSupported:
668 canPlay = emptyString();
670 case WebMimeRegistry::MayBeSupported:
673 case WebMimeRegistry::IsSupported:
674 canPlay = "probably";
678 WTF_LOG(Media, "HTMLMediaElement::canPlayType(%s, %s) -> %s", mimeType.utf8().data(), keySystem.utf8().data(), canPlay.utf8().data());
683 void HTMLMediaElement::load()
685 WTF_LOG(Media, "HTMLMediaElement::load()");
687 if (UserGestureIndicator::processingUserGesture())
688 m_userGestureRequiredForPlay = false;
695 void HTMLMediaElement::prepareForLoad()
697 WTF_LOG(Media, "HTMLMediaElement::prepareForLoad");
699 // Perform the cleanup required for the resource load algorithm to run.
700 stopPeriodicTimers();
702 cancelDeferredLoad();
703 // FIXME: Figure out appropriate place to reset LoadTextTrackResource if necessary and set m_pendingActionFlags to 0 here.
704 m_pendingActionFlags &= ~LoadMediaResource;
705 m_sentEndEvent = false;
706 m_sentStalledEvent = false;
707 m_haveFiredLoadedData = false;
708 m_completelyLoaded = false;
709 m_havePreparedToPlay = false;
710 m_displayMode = Unknown;
712 // 1 - Abort any already-running instance of the resource selection algorithm for this element.
713 m_loadState = WaitingForSource;
714 m_currentSourceNode = nullptr;
716 // 2 - If there are any tasks from the media element's media element event task source in
717 // one of the task queues, then remove those tasks.
718 cancelPendingEventsAndCallbacks();
720 // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
721 // a task to fire a simple event named abort at the media element.
722 if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
723 scheduleEvent(EventTypeNames::abort);
727 // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
728 if (m_networkState != NETWORK_EMPTY) {
729 // 4.1 - Queue a task to fire a simple event named emptied at the media element.
730 scheduleEvent(EventTypeNames::emptied);
732 // 4.2 - If a fetching process is in progress for the media element, the user agent should stop it.
733 m_networkState = NETWORK_EMPTY;
735 // 4.3 - Forget the media element's media-resource-specific tracks.
736 forgetResourceSpecificTracks();
738 // 4.4 - If readyState is not set to HAVE_NOTHING, then set it to that state.
739 m_readyState = HAVE_NOTHING;
740 m_readyStateMaximum = HAVE_NOTHING;
742 // 4.5 - If the paused attribute is false, then set it to true.
745 // 4.6 - If seeking is true, set it to false.
748 // 4.7 - Set the current playback position to 0.
749 // Set the official playback position to 0.
750 // If this changed the official playback position, then queue a task to fire a simple event named timeupdate at the media element.
751 // FIXME: Add support for firing this event.
753 // 4.8 - Set the initial playback position to 0.
754 // FIXME: Make this less subtle. The position only becomes 0 because of the createMediaPlayer() call
757 invalidateCachedTime();
759 // 4.9 - Set the timeline offset to Not-a-Number (NaN).
760 // 4.10 - Update the duration attribute to Not-a-Number (NaN).
763 updateMediaController();
764 updateActiveTextTrackCues(0);
767 // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
768 setPlaybackRate(defaultPlaybackRate());
770 // 6 - Set the error attribute to null and the autoplaying flag to true.
772 m_autoplaying = true;
774 // 7 - Invoke the media element's resource selection algorithm.
776 // 8 - Note: Playback of any previously playing media resource for this element stops.
778 // The resource selection algorithm
779 // 1 - Set the networkState to NETWORK_NO_SOURCE
780 m_networkState = NETWORK_NO_SOURCE;
782 // 2 - Asynchronously await a stable state.
784 m_playedTimeRanges = TimeRanges::create();
786 // FIXME: Investigate whether these can be moved into m_networkState != NETWORK_EMPTY block above
787 // so they are closer to the relevant spec steps.
789 m_duration = std::numeric_limits<double>::quiet_NaN();
791 // The spec doesn't say to block the load event until we actually run the asynchronous section
792 // algorithm, but do it now because we won't start that until after the timer fires and the
793 // event may have already fired by then.
794 setShouldDelayLoadEvent(true);
796 configureMediaControls();
799 void HTMLMediaElement::loadInternal()
801 // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the
802 // disabled state when the element's resource selection algorithm last started".
803 m_textTracksWhenResourceSelectionBegan.clear();
805 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
806 TextTrack* track = m_textTracks->item(i);
807 if (track->mode() != TextTrack::disabledKeyword())
808 m_textTracksWhenResourceSelectionBegan.append(track);
812 selectMediaResource();
815 void HTMLMediaElement::selectMediaResource()
817 WTF_LOG(Media, "HTMLMediaElement::selectMediaResource");
819 enum Mode { attribute, children };
821 // 3 - If the media element has a src attribute, then let mode be attribute.
822 Mode mode = attribute;
823 if (!fastHasAttribute(srcAttr)) {
824 // Otherwise, if the media element does not have a src attribute but has a source
825 // element child, then let mode be children and let candidate be the first such
826 // source element child in tree order.
827 if (HTMLSourceElement* element = Traversal<HTMLSourceElement>::firstChild(*this)) {
829 m_nextChildNodeToConsider = element;
830 m_currentSourceNode = nullptr;
832 // Otherwise the media element has neither a src attribute nor a source element
833 // child: set the networkState to NETWORK_EMPTY, and abort these steps; the
834 // synchronous section ends.
835 m_loadState = WaitingForSource;
836 setShouldDelayLoadEvent(false);
837 m_networkState = NETWORK_EMPTY;
839 WTF_LOG(Media, "HTMLMediaElement::selectMediaResource, nothing to load");
844 // 4 - Set the media element's delaying-the-load-event flag to true (this delays the load event),
845 // and set its networkState to NETWORK_LOADING.
846 setShouldDelayLoadEvent(true);
847 m_networkState = NETWORK_LOADING;
849 // 5 - Queue a task to fire a simple event named loadstart at the media element.
850 scheduleEvent(EventTypeNames::loadstart);
852 // 6 - If mode is attribute, then run these substeps
853 if (mode == attribute) {
854 m_loadState = LoadingFromSrcAttr;
856 // If the src attribute's value is the empty string ... jump down to the failed step below
857 KURL mediaURL = getNonEmptyURLAttribute(srcAttr);
858 if (mediaURL.isEmpty()) {
859 mediaLoadingFailed(MediaPlayer::FormatError);
860 WTF_LOG(Media, "HTMLMediaElement::selectMediaResource, empty 'src'");
864 if (!isSafeToLoadURL(mediaURL, Complain)) {
865 mediaLoadingFailed(MediaPlayer::FormatError);
869 // No type or key system information is available when the url comes
870 // from the 'src' attribute so MediaPlayer
871 // will have to pick a media engine based on the file extension.
872 ContentType contentType((String()));
873 loadResource(mediaURL, contentType, String());
874 WTF_LOG(Media, "HTMLMediaElement::selectMediaResource, using 'src' attribute url");
878 // Otherwise, the source elements will be used
879 loadNextSourceChild();
882 void HTMLMediaElement::loadNextSourceChild()
884 ContentType contentType((String()));
886 KURL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain);
887 if (!mediaURL.isValid()) {
888 waitForSourceChange();
892 // Recreate the media player for the new url
895 m_loadState = LoadingFromSourceElement;
896 loadResource(mediaURL, contentType, keySystem);
899 void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType, const String& keySystem)
901 ASSERT(isSafeToLoadURL(url, Complain));
903 WTF_LOG(Media, "HTMLMediaElement::loadResource(%s, %s, %s)", urlForLoggingMedia(url).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data());
905 LocalFrame* frame = document().frame();
907 mediaLoadingFailed(MediaPlayer::FormatError);
911 // The resource fetch algorithm
912 m_networkState = NETWORK_LOADING;
914 // Set m_currentSrc *before* changing to the cache url, the fact that we are loading from the app
915 // cache is an internal detail not exposed through the media element API.
918 WTF_LOG(Media, "HTMLMediaElement::loadResource - m_currentSrc -> %s", urlForLoggingMedia(m_currentSrc).utf8().data());
920 startProgressEventTimer();
922 // Reset display mode to force a recalculation of what to show because we are resetting the player.
923 setDisplayMode(Unknown);
928 if (fastHasAttribute(mutedAttr))
932 ASSERT(!m_mediaSource);
934 bool attemptLoad = true;
936 if (url.protocolIs(mediaSourceBlobProtocol)) {
937 if (isMediaStreamURL(url.string())) {
938 m_userGestureRequiredForPlay = false;
940 m_mediaSource = HTMLMediaSource::lookup(url.string());
943 if (!m_mediaSource->attachToElement(this)) {
944 // Forget our reference to the MediaSource, so we leave it alone
945 // while processing remainder of load failure.
946 m_mediaSource = nullptr;
953 if (attemptLoad && canLoadURL(url, contentType, keySystem)) {
954 ASSERT(!webMediaPlayer());
956 if (!m_havePreparedToPlay && !autoplay() && m_preload == MediaPlayer::None) {
957 WTF_LOG(Media, "HTMLMediaElement::loadResource : Delaying load because preload == 'none'");
963 mediaLoadingFailed(MediaPlayer::FormatError);
966 // If there is no poster to display, allow the media engine to render video frames as soon as
967 // they are available.
968 updateDisplayState();
971 renderer()->updateFromElement();
974 void HTMLMediaElement::startPlayerLoad()
976 // Filter out user:pass as those two URL components aren't
977 // considered for media resource fetches (including for the CORS
978 // use-credentials mode.) That behavior aligns with Gecko, with IE
979 // being more restrictive and not allowing fetches to such URLs.
981 // Spec reference: http://whatwg.org/c/#concept-media-load-resource
983 // FIXME: when the HTML spec switches to specifying resource
984 // fetches in terms of Fetch (http://fetch.spec.whatwg.org), and
985 // along with that potentially also specifying a setting for its
986 // 'authentication flag' to control how user:pass embedded in a
987 // media resource URL should be treated, then update the handling
989 KURL requestURL = m_currentSrc;
990 if (!requestURL.user().isEmpty())
991 requestURL.setUser(String());
992 if (!requestURL.pass().isEmpty())
993 requestURL.setPass(String());
995 m_player->load(loadType(), requestURL, corsMode());
998 void HTMLMediaElement::setPlayerPreload()
1000 m_player->setPreload(m_preload);
1002 if (loadIsDeferred() && m_preload != MediaPlayer::None)
1003 startDeferredLoad();
1006 bool HTMLMediaElement::loadIsDeferred() const
1008 return m_deferredLoadState != NotDeferred;
1011 void HTMLMediaElement::deferLoad()
1013 // This implements the "optional" step 3 from the resource fetch algorithm.
1014 ASSERT(!m_deferredLoadTimer.isActive());
1015 ASSERT(m_deferredLoadState == NotDeferred);
1016 // 1. Set the networkState to NETWORK_IDLE.
1017 // 2. Queue a task to fire a simple event named suspend at the element.
1018 changeNetworkStateFromLoadingToIdle();
1019 // 3. Queue a task to set the element's delaying-the-load-event
1020 // flag to false. This stops delaying the load event.
1021 m_deferredLoadTimer.startOneShot(0, FROM_HERE);
1022 // 4. Wait for the task to be run.
1023 m_deferredLoadState = WaitingForStopDelayingLoadEventTask;
1024 // Continued in executeDeferredLoad().
1027 void HTMLMediaElement::cancelDeferredLoad()
1029 m_deferredLoadTimer.stop();
1030 m_deferredLoadState = NotDeferred;
1033 void HTMLMediaElement::executeDeferredLoad()
1035 ASSERT(m_deferredLoadState >= WaitingForTrigger);
1037 // resource fetch algorithm step 3 - continued from deferLoad().
1039 // 5. Wait for an implementation-defined event (e.g. the user requesting that the media element begin playback).
1040 // This is assumed to be whatever 'event' ended up calling this method.
1041 cancelDeferredLoad();
1042 // 6. Set the element's delaying-the-load-event flag back to true (this
1043 // delays the load event again, in case it hasn't been fired yet).
1044 setShouldDelayLoadEvent(true);
1045 // 7. Set the networkState to NETWORK_LOADING.
1046 m_networkState = NETWORK_LOADING;
1048 startProgressEventTimer();
1053 void HTMLMediaElement::startDeferredLoad()
1055 if (m_deferredLoadState == WaitingForTrigger) {
1056 executeDeferredLoad();
1059 ASSERT(m_deferredLoadState == WaitingForStopDelayingLoadEventTask);
1060 m_deferredLoadState = ExecuteOnStopDelayingLoadEventTask;
1063 void HTMLMediaElement::deferredLoadTimerFired(Timer<HTMLMediaElement>*)
1065 setShouldDelayLoadEvent(false);
1067 if (m_deferredLoadState == ExecuteOnStopDelayingLoadEventTask) {
1068 executeDeferredLoad();
1071 ASSERT(m_deferredLoadState == WaitingForStopDelayingLoadEventTask);
1072 m_deferredLoadState = WaitingForTrigger;
1075 WebMediaPlayer::LoadType HTMLMediaElement::loadType() const
1078 return WebMediaPlayer::LoadTypeMediaSource;
1080 if (isMediaStreamURL(m_currentSrc.string()))
1081 return WebMediaPlayer::LoadTypeMediaStream;
1083 return WebMediaPlayer::LoadTypeURL;
1086 static bool trackIndexCompare(TextTrack* a,
1089 return a->trackIndex() - b->trackIndex() < 0;
1092 static bool eventTimeCueCompare(const std::pair<double, TextTrackCue*>& a,
1093 const std::pair<double, TextTrackCue*>& b)
1095 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1097 if (a.first != b.first)
1098 return a.first - b.first < 0;
1100 // If the cues belong to different text tracks, it doesn't make sense to
1101 // compare the two tracks by the relative cue order, so return the relative
1103 if (a.second->track() != b.second->track())
1104 return trackIndexCompare(a.second->track(), b.second->track());
1106 // 12 - Further sort tasks in events that have the same time by the
1107 // relative text track cue order of the text track cues associated
1108 // with these tasks.
1109 return a.second->cueIndex() - b.second->cueIndex() < 0;
1113 void HTMLMediaElement::updateActiveTextTrackCues(double movieTime)
1115 // 4.8.10.8 Playing the media resource
1117 // If the current playback position changes while the steps are running,
1118 // then the user agent must wait for the steps to complete, and then must
1119 // immediately rerun the steps.
1120 if (ignoreTrackDisplayUpdateRequests())
1123 // 1 - Let current cues be a list of cues, initialized to contain all the
1124 // cues of all the hidden, showing, or showing by default text tracks of the
1125 // media element (not the disabled ones) whose start times are less than or
1126 // equal to the current playback position and whose end times are greater
1127 // than the current playback position.
1128 CueList currentCues;
1130 // The user agent must synchronously unset [the text track cue active] flag
1131 // whenever ... the media element's readyState is changed back to HAVE_NOTHING.
1132 if (m_readyState != HAVE_NOTHING && m_player)
1133 currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime));
1135 CueList previousCues;
1138 // 2 - Let other cues be a list of cues, initialized to contain all the cues
1139 // of hidden, showing, and showing by default text tracks of the media
1140 // element that are not present in current cues.
1141 previousCues = m_currentlyActiveCues;
1143 // 3 - Let last time be the current playback position at the time this
1144 // algorithm was last run for this media element, if this is not the first
1146 double lastTime = m_lastTextTrackUpdateTime;
1148 // 4 - If the current playback position has, since the last time this
1149 // algorithm was run, only changed through its usual monotonic increase
1150 // during normal playback, then let missed cues be the list of cues in other
1151 // cues whose start times are greater than or equal to last time and whose
1152 // end times are less than or equal to the current playback position.
1153 // Otherwise, let missed cues be an empty list.
1154 if (lastTime >= 0 && m_lastSeekTime < movieTime) {
1155 CueList potentiallySkippedCues =
1156 m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime));
1158 for (size_t i = 0; i < potentiallySkippedCues.size(); ++i) {
1159 double cueStartTime = potentiallySkippedCues[i].low();
1160 double cueEndTime = potentiallySkippedCues[i].high();
1162 // Consider cues that may have been missed since the last seek time.
1163 if (cueStartTime > std::max(m_lastSeekTime, lastTime) && cueEndTime < movieTime)
1164 missedCues.append(potentiallySkippedCues[i]);
1168 m_lastTextTrackUpdateTime = movieTime;
1170 // 5 - If the time was reached through the usual monotonic increase of the
1171 // current playback position during normal playback, and if the user agent
1172 // has not fired a timeupdate event at the element in the past 15 to 250ms
1173 // and is not still running event handlers for such an event, then the user
1174 // agent must queue a task to fire a simple event named timeupdate at the
1175 // element. (In the other cases, such as explicit seeks, relevant events get
1176 // fired as part of the overall process of changing the current playback
1178 if (!m_seeking && m_lastSeekTime <= lastTime)
1179 scheduleTimeupdateEvent(true);
1181 // Explicitly cache vector sizes, as their content is constant from here.
1182 size_t currentCuesSize = currentCues.size();
1183 size_t missedCuesSize = missedCues.size();
1184 size_t previousCuesSize = previousCues.size();
1186 // 6 - If all of the cues in current cues have their text track cue active
1187 // flag set, none of the cues in other cues have their text track cue active
1188 // flag set, and missed cues is empty, then abort these steps.
1189 bool activeSetChanged = missedCuesSize;
1191 for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i)
1192 if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive())
1193 activeSetChanged = true;
1195 for (size_t i = 0; i < currentCuesSize; ++i) {
1196 currentCues[i].data()->updateDisplayTree(movieTime);
1198 if (!currentCues[i].data()->isActive())
1199 activeSetChanged = true;
1202 if (!activeSetChanged)
1205 // 7 - If the time was reached through the usual monotonic increase of the
1206 // current playback position during normal playback, and there are cues in
1207 // other cues that have their text track cue pause-on-exi flag set and that
1208 // either have their text track cue active flag set or are also in missed
1209 // cues, then immediately pause the media element.
1210 for (size_t i = 0; !m_paused && i < previousCuesSize; ++i) {
1211 if (previousCues[i].data()->pauseOnExit()
1212 && previousCues[i].data()->isActive()
1213 && !currentCues.contains(previousCues[i]))
1217 for (size_t i = 0; !m_paused && i < missedCuesSize; ++i) {
1218 if (missedCues[i].data()->pauseOnExit())
1222 // 8 - Let events be a list of tasks, initially empty. Each task in this
1223 // list will be associated with a text track, a text track cue, and a time,
1224 // which are used to sort the list before the tasks are queued.
1225 Vector<std::pair<double, TextTrackCue*> > eventTasks;
1227 // 8 - Let affected tracks be a list of text tracks, initially empty.
1228 Vector<TextTrack*> affectedTracks;
1230 for (size_t i = 0; i < missedCuesSize; ++i) {
1231 // 9 - For each text track cue in missed cues, prepare an event named enter
1232 // for the TextTrackCue object with the text track cue start time.
1233 eventTasks.append(std::make_pair(missedCues[i].data()->startTime(),
1234 missedCues[i].data()));
1236 // 10 - For each text track [...] in missed cues, prepare an event
1237 // named exit for the TextTrackCue object with the with the later of
1238 // the text track cue end time and the text track cue start time.
1240 // Note: An explicit task is added only if the cue is NOT a zero or
1241 // negative length cue. Otherwise, the need for an exit event is
1242 // checked when these tasks are actually queued below. This doesn't
1243 // affect sorting events before dispatch either, because the exit
1244 // event has the same time as the enter event.
1245 if (missedCues[i].data()->startTime() < missedCues[i].data()->endTime())
1246 eventTasks.append(std::make_pair(missedCues[i].data()->endTime(),
1247 missedCues[i].data()));
1250 for (size_t i = 0; i < previousCuesSize; ++i) {
1251 // 10 - For each text track cue in other cues that has its text
1252 // track cue active flag set prepare an event named exit for the
1253 // TextTrackCue object with the text track cue end time.
1254 if (!currentCues.contains(previousCues[i]))
1255 eventTasks.append(std::make_pair(previousCues[i].data()->endTime(),
1256 previousCues[i].data()));
1259 for (size_t i = 0; i < currentCuesSize; ++i) {
1260 // 11 - For each text track cue in current cues that does not have its
1261 // text track cue active flag set, prepare an event named enter for the
1262 // TextTrackCue object with the text track cue start time.
1263 if (!previousCues.contains(currentCues[i]))
1264 eventTasks.append(std::make_pair(currentCues[i].data()->startTime(),
1265 currentCues[i].data()));
1268 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1270 nonCopyingSort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare);
1272 for (size_t i = 0; i < eventTasks.size(); ++i) {
1273 if (!affectedTracks.contains(eventTasks[i].second->track()))
1274 affectedTracks.append(eventTasks[i].second->track());
1276 // 13 - Queue each task in events, in list order.
1277 RefPtrWillBeRawPtr<Event> event = nullptr;
1279 // Each event in eventTasks may be either an enterEvent or an exitEvent,
1280 // depending on the time that is associated with the event. This
1281 // correctly identifies the type of the event, if the startTime is
1282 // less than the endTime in the cue.
1283 if (eventTasks[i].second->startTime() >= eventTasks[i].second->endTime()) {
1284 event = Event::create(EventTypeNames::enter);
1285 event->setTarget(eventTasks[i].second);
1286 m_asyncEventQueue->enqueueEvent(event.release());
1288 event = Event::create(EventTypeNames::exit);
1289 event->setTarget(eventTasks[i].second);
1290 m_asyncEventQueue->enqueueEvent(event.release());
1292 if (eventTasks[i].first == eventTasks[i].second->startTime())
1293 event = Event::create(EventTypeNames::enter);
1295 event = Event::create(EventTypeNames::exit);
1297 event->setTarget(eventTasks[i].second);
1298 m_asyncEventQueue->enqueueEvent(event.release());
1302 // 14 - Sort affected tracks in the same order as the text tracks appear in
1303 // the media element's list of text tracks, and remove duplicates.
1304 nonCopyingSort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare);
1306 // 15 - For each text track in affected tracks, in the list order, queue a
1307 // task to fire a simple event named cuechange at the TextTrack object, and, ...
1308 for (size_t i = 0; i < affectedTracks.size(); ++i) {
1309 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::cuechange);
1310 event->setTarget(affectedTracks[i]);
1312 m_asyncEventQueue->enqueueEvent(event.release());
1314 // ... if the text track has a corresponding track element, to then fire a
1315 // simple event named cuechange at the track element as well.
1316 if (affectedTracks[i]->trackType() == TextTrack::TrackElement) {
1317 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::cuechange);
1318 HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(affectedTracks[i])->trackElement();
1319 ASSERT(trackElement);
1320 event->setTarget(trackElement);
1322 m_asyncEventQueue->enqueueEvent(event.release());
1326 // 16 - Set the text track cue active flag of all the cues in the current
1327 // cues, and unset the text track cue active flag of all the cues in the
1329 for (size_t i = 0; i < currentCuesSize; ++i)
1330 currentCues[i].data()->setIsActive(true);
1332 for (size_t i = 0; i < previousCuesSize; ++i)
1333 if (!currentCues.contains(previousCues[i]))
1334 previousCues[i].data()->setIsActive(false);
1336 // Update the current active cues.
1337 m_currentlyActiveCues = currentCues;
1339 if (activeSetChanged)
1340 updateTextTrackDisplay();
1343 bool HTMLMediaElement::textTracksAreReady() const
1345 // 4.8.10.12.1 Text track model
1347 // The text tracks of a media element are ready if all the text tracks whose mode was not
1348 // in the disabled state when the element's resource selection algorithm last started now
1349 // have a text track readiness state of loaded or failed to load.
1350 for (unsigned i = 0; i < m_textTracksWhenResourceSelectionBegan.size(); ++i) {
1351 if (m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::Loading
1352 || m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::NotLoaded)
1359 void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track)
1361 if (webMediaPlayer()&& m_textTracksWhenResourceSelectionBegan.contains(track)) {
1362 if (track->readinessState() != TextTrack::Loading)
1363 setReadyState(static_cast<ReadyState>(webMediaPlayer()->readyState()));
1365 // The track readiness state might have changed as a result of the user
1366 // clicking the captions button. In this case, a check whether all the
1367 // resources have failed loading should be done in order to hide the CC button.
1368 if (hasMediaControls() && track->readinessState() == TextTrack::FailedToLoad)
1369 mediaControls()->refreshClosedCaptionsButtonVisibility();
1373 void HTMLMediaElement::textTrackModeChanged(TextTrack* track)
1375 if (track->trackType() == TextTrack::TrackElement) {
1376 // 4.8.10.12.3 Sourcing out-of-band text tracks
1377 // ... when a text track corresponding to a track element is created with text track
1378 // mode set to disabled and subsequently changes its text track mode to hidden, showing,
1379 // or showing by default for the first time, the user agent must immediately and synchronously
1380 // run the following algorithm ...
1382 for (HTMLTrackElement* trackElement = Traversal<HTMLTrackElement>::firstChild(*this); trackElement; trackElement = Traversal<HTMLTrackElement>::nextSibling(*trackElement)) {
1383 if (trackElement->track() != track)
1386 // Mark this track as "configured" so configureTextTracks won't change the mode again.
1387 track->setHasBeenConfigured(true);
1388 if (track->mode() != TextTrack::disabledKeyword()) {
1389 if (trackElement->readyState() == HTMLTrackElement::LOADED)
1390 textTrackAddCues(track, track->cues());
1392 // If this is the first added track, create the list of text tracks.
1394 m_textTracks = TextTrackList::create(this);
1398 } else if (track->trackType() == TextTrack::AddTrack && track->mode() != TextTrack::disabledKeyword()) {
1399 textTrackAddCues(track, track->cues());
1402 configureTextTrackDisplay(AssumeVisibleChange);
1404 ASSERT(textTracks()->contains(track));
1405 textTracks()->scheduleChangeEvent();
1408 void HTMLMediaElement::textTrackKindChanged(TextTrack* track)
1410 if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword() && track->mode() == TextTrack::showingKeyword())
1411 track->setMode(TextTrack::hiddenKeyword());
1414 void HTMLMediaElement::beginIgnoringTrackDisplayUpdateRequests()
1416 ++m_ignoreTrackDisplayUpdate;
1419 void HTMLMediaElement::endIgnoringTrackDisplayUpdateRequests()
1421 ASSERT(m_ignoreTrackDisplayUpdate);
1422 --m_ignoreTrackDisplayUpdate;
1423 if (!m_ignoreTrackDisplayUpdate && m_active)
1424 updateActiveTextTrackCues(currentTime());
1427 void HTMLMediaElement::textTrackAddCues(TextTrack* track, const TextTrackCueList* cues)
1429 WTF_LOG(Media, "HTMLMediaElement::textTrackAddCues");
1430 if (track->mode() == TextTrack::disabledKeyword())
1433 TrackDisplayUpdateScope scope(this);
1434 for (size_t i = 0; i < cues->length(); ++i)
1435 textTrackAddCue(cues->item(i)->track(), cues->item(i));
1438 void HTMLMediaElement::textTrackRemoveCues(TextTrack*, const TextTrackCueList* cues)
1440 WTF_LOG(Media, "HTMLMediaElement::textTrackRemoveCues");
1442 TrackDisplayUpdateScope scope(this);
1443 for (size_t i = 0; i < cues->length(); ++i)
1444 textTrackRemoveCue(cues->item(i)->track(), cues->item(i));
1447 void HTMLMediaElement::textTrackAddCue(TextTrack* track, PassRefPtrWillBeRawPtr<TextTrackCue> cue)
1449 if (track->mode() == TextTrack::disabledKeyword())
1452 // Negative duration cues need be treated in the interval tree as
1453 // zero-length cues.
1454 double endTime = std::max(cue->startTime(), cue->endTime());
1456 CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get());
1457 if (!m_cueTree.contains(interval))
1458 m_cueTree.add(interval);
1459 updateActiveTextTrackCues(currentTime());
1462 void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtrWillBeRawPtr<TextTrackCue> cue)
1464 // Negative duration cues need to be treated in the interval tree as
1465 // zero-length cues.
1466 double endTime = std::max(cue->startTime(), cue->endTime());
1468 CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get());
1469 m_cueTree.remove(interval);
1471 // Since the cue will be removed from the media element and likely the
1472 // TextTrack might also be destructed, notifying the region of the cue
1473 // removal shouldn't be done.
1474 cue->notifyRegionWhenRemovingDisplayTree(false);
1476 size_t index = m_currentlyActiveCues.find(interval);
1477 if (index != kNotFound) {
1478 m_currentlyActiveCues.remove(index);
1479 cue->setIsActive(false);
1481 cue->removeDisplayTree();
1482 updateActiveTextTrackCues(currentTime());
1484 cue->notifyRegionWhenRemovingDisplayTree(true);
1488 bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidURLAction actionIfInvalid)
1490 if (!url.isValid()) {
1491 WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE because url is invalid", urlForLoggingMedia(url).utf8().data());
1495 LocalFrame* frame = document().frame();
1496 if (!frame || !document().securityOrigin()->canDisplay(url)) {
1497 if (actionIfInvalid == Complain)
1498 FrameLoader::reportLocalLoadFailed(frame, url.elidedString());
1499 WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE rejected by SecurityOrigin", urlForLoggingMedia(url).utf8().data());
1503 if (!document().contentSecurityPolicy()->allowMediaFromSource(url)) {
1504 WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> rejected by Content Security Policy", urlForLoggingMedia(url).utf8().data());
1511 void HTMLMediaElement::startProgressEventTimer()
1513 if (m_progressEventTimer.isActive())
1516 m_previousProgressTime = WTF::currentTime();
1517 // 350ms is not magic, it is in the spec!
1518 m_progressEventTimer.startRepeating(0.350, FROM_HERE);
1521 void HTMLMediaElement::waitForSourceChange()
1523 WTF_LOG(Media, "HTMLMediaElement::waitForSourceChange");
1525 stopPeriodicTimers();
1526 m_loadState = WaitingForSource;
1528 // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value
1529 m_networkState = NETWORK_NO_SOURCE;
1531 // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1532 setShouldDelayLoadEvent(false);
1534 updateDisplayState();
1537 renderer()->updateFromElement();
1540 void HTMLMediaElement::noneSupported()
1542 WTF_LOG(Media, "HTMLMediaElement::noneSupported");
1544 stopPeriodicTimers();
1545 m_loadState = WaitingForSource;
1546 m_currentSourceNode = nullptr;
1549 // 6 - Reaching this step indicates that the media resource failed to load or that the given
1550 // URL could not be resolved. In one atomic operation, run the following steps:
1552 // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to
1553 // MEDIA_ERR_SRC_NOT_SUPPORTED.
1554 m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
1556 // 6.2 - Forget the media element's media-resource-specific text tracks.
1557 forgetResourceSpecificTracks();
1559 // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
1560 m_networkState = NETWORK_NO_SOURCE;
1562 // 7 - Queue a task to fire a simple event named error at the media element.
1563 scheduleEvent(EventTypeNames::error);
1567 // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1568 setShouldDelayLoadEvent(false);
1570 // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed,
1571 // the element won't attempt to load another resource.
1573 updateDisplayState();
1576 renderer()->updateFromElement();
1579 void HTMLMediaElement::mediaEngineError(PassRefPtrWillBeRawPtr<MediaError> err)
1581 WTF_LOG(Media, "HTMLMediaElement::mediaEngineError(%d)", static_cast<int>(err->code()));
1583 // 1 - The user agent should cancel the fetching process.
1584 stopPeriodicTimers();
1585 m_loadState = WaitingForSource;
1587 // 2 - Set the error attribute to a new MediaError object whose code attribute is
1588 // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
1591 // 3 - Queue a task to fire a simple event named error at the media element.
1592 scheduleEvent(EventTypeNames::error);
1596 // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a
1597 // task to fire a simple event called emptied at the element.
1598 m_networkState = NETWORK_EMPTY;
1599 scheduleEvent(EventTypeNames::emptied);
1601 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1602 setShouldDelayLoadEvent(false);
1604 // 6 - Abort the overall resource selection algorithm.
1605 m_currentSourceNode = nullptr;
1608 void HTMLMediaElement::cancelPendingEventsAndCallbacks()
1610 WTF_LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks");
1611 m_asyncEventQueue->cancelAllEvents();
1613 for (HTMLSourceElement* source = Traversal<HTMLSourceElement>::firstChild(*this); source; source = Traversal<HTMLSourceElement>::nextSibling(*source))
1614 source->cancelPendingErrorEvent();
1617 void HTMLMediaElement::mediaPlayerNetworkStateChanged()
1619 setNetworkState(m_player->networkState());
1622 void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error)
1624 stopPeriodicTimers();
1626 // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
1627 // <source> children, schedule the next one
1628 if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
1630 // resource selection algorithm
1631 // 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.
1632 if (m_currentSourceNode)
1633 m_currentSourceNode->scheduleErrorEvent();
1635 WTF_LOG(Media, "HTMLMediaElement::setNetworkState - error event not sent, <source> was removed");
1637 // 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.
1639 // 9.Otherwise.11 - Forget the media element's media-resource-specific tracks.
1640 forgetResourceSpecificTracks();
1642 if (havePotentialSourceChild()) {
1643 WTF_LOG(Media, "HTMLMediaElement::setNetworkState - scheduling next <source>");
1644 scheduleNextSourceChild();
1646 WTF_LOG(Media, "HTMLMediaElement::setNetworkState - no more <source> elements, waiting");
1647 waitForSourceChange();
1653 if (error == MediaPlayer::NetworkError && m_readyState >= HAVE_METADATA)
1654 mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK));
1655 else if (error == MediaPlayer::DecodeError)
1656 mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE));
1657 else if ((error == MediaPlayer::FormatError || error == MediaPlayer::NetworkError) && m_loadState == LoadingFromSrcAttr)
1660 updateDisplayState();
1661 if (hasMediaControls())
1662 mediaControls()->reset();
1665 void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
1667 WTF_LOG(Media, "HTMLMediaElement::setNetworkState(%d) - current state is %d", static_cast<int>(state), static_cast<int>(m_networkState));
1669 if (state == MediaPlayer::Empty) {
1670 // Just update the cached state and leave, we can't do anything.
1671 m_networkState = NETWORK_EMPTY;
1675 if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) {
1676 mediaLoadingFailed(state);
1680 if (state == MediaPlayer::Idle) {
1681 if (m_networkState > NETWORK_IDLE) {
1682 changeNetworkStateFromLoadingToIdle();
1683 setShouldDelayLoadEvent(false);
1685 m_networkState = NETWORK_IDLE;
1689 if (state == MediaPlayer::Loading) {
1690 if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE)
1691 startProgressEventTimer();
1692 m_networkState = NETWORK_LOADING;
1695 if (state == MediaPlayer::Loaded) {
1696 if (m_networkState != NETWORK_IDLE)
1697 changeNetworkStateFromLoadingToIdle();
1698 m_completelyLoaded = true;
1702 void HTMLMediaElement::changeNetworkStateFromLoadingToIdle()
1705 m_progressEventTimer.stop();
1707 // Schedule one last progress event so we guarantee that at least one is fired
1708 // for files that load very quickly.
1709 if (m_player->didLoadingProgress())
1710 scheduleEvent(EventTypeNames::progress);
1711 scheduleEvent(EventTypeNames::suspend);
1712 m_networkState = NETWORK_IDLE;
1715 void HTMLMediaElement::mediaPlayerReadyStateChanged()
1717 setReadyState(static_cast<ReadyState>(webMediaPlayer()->readyState()));
1720 void HTMLMediaElement::setReadyState(ReadyState state)
1722 WTF_LOG(Media, "HTMLMediaElement::setReadyState(%d) - current state is %d,", static_cast<int>(state), static_cast<int>(m_readyState));
1724 // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it
1725 bool wasPotentiallyPlaying = potentiallyPlaying();
1727 ReadyState oldState = m_readyState;
1728 ReadyState newState = state;
1730 bool tracksAreReady = textTracksAreReady();
1732 if (newState == oldState && m_tracksAreReady == tracksAreReady)
1735 m_tracksAreReady = tracksAreReady;
1738 m_readyState = newState;
1740 // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until
1741 // the text tracks are ready, regardless of the state of the media file.
1742 if (newState <= HAVE_METADATA)
1743 m_readyState = newState;
1745 m_readyState = HAVE_CURRENT_DATA;
1748 if (oldState > m_readyStateMaximum)
1749 m_readyStateMaximum = oldState;
1751 if (m_networkState == NETWORK_EMPTY)
1755 // 4.8.10.9, step 9 note: If the media element was potentially playing immediately before
1756 // it started seeking, but seeking caused its readyState attribute to change to a value
1757 // lower than HAVE_FUTURE_DATA, then a waiting will be fired at the element.
1758 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA)
1759 scheduleEvent(EventTypeNames::waiting);
1761 // 4.8.10.9 steps 12-14
1762 if (m_readyState >= HAVE_CURRENT_DATA)
1765 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
1767 scheduleTimeupdateEvent(false);
1768 scheduleEvent(EventTypeNames::waiting);
1772 if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
1773 createPlaceholderTracksIfNecessary();
1775 prepareMediaFragmentURI();
1777 selectInitialTracksIfNecessary();
1779 m_duration = duration();
1780 scheduleEvent(EventTypeNames::durationchange);
1782 if (isHTMLVideoElement(*this))
1783 scheduleEvent(EventTypeNames::resize);
1784 scheduleEvent(EventTypeNames::loadedmetadata);
1785 if (hasMediaControls())
1786 mediaControls()->reset();
1788 renderer()->updateFromElement();
1791 bool shouldUpdateDisplayState = false;
1793 if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) {
1794 m_haveFiredLoadedData = true;
1795 shouldUpdateDisplayState = true;
1796 scheduleEvent(EventTypeNames::loadeddata);
1797 setShouldDelayLoadEvent(false);
1798 applyMediaFragmentURI();
1801 bool isPotentiallyPlaying = potentiallyPlaying();
1802 if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
1803 scheduleEvent(EventTypeNames::canplay);
1804 if (isPotentiallyPlaying)
1805 scheduleEvent(EventTypeNames::playing);
1806 shouldUpdateDisplayState = true;
1809 if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA && tracksAreReady) {
1810 if (oldState <= HAVE_CURRENT_DATA) {
1811 scheduleEvent(EventTypeNames::canplay);
1812 if (isPotentiallyPlaying)
1813 scheduleEvent(EventTypeNames::playing);
1816 if (m_autoplaying && m_paused && autoplay() && !document().isSandboxed(SandboxAutomaticFeatures) && !m_userGestureRequiredForPlay) {
1818 invalidateCachedTime();
1819 scheduleEvent(EventTypeNames::play);
1820 scheduleEvent(EventTypeNames::playing);
1823 scheduleEvent(EventTypeNames::canplaythrough);
1825 shouldUpdateDisplayState = true;
1828 if (shouldUpdateDisplayState) {
1829 updateDisplayState();
1830 if (hasMediaControls())
1831 mediaControls()->refreshClosedCaptionsButtonVisibility();
1835 updateMediaController();
1836 updateActiveTextTrackCues(currentTime());
1839 void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*)
1842 if (m_networkState != NETWORK_LOADING)
1845 double time = WTF::currentTime();
1846 double timedelta = time - m_previousProgressTime;
1848 if (m_player->didLoadingProgress()) {
1849 scheduleEvent(EventTypeNames::progress);
1850 m_previousProgressTime = time;
1851 m_sentStalledEvent = false;
1853 renderer()->updateFromElement();
1854 } else if (timedelta > 3.0 && !m_sentStalledEvent) {
1855 scheduleEvent(EventTypeNames::stalled);
1856 m_sentStalledEvent = true;
1857 setShouldDelayLoadEvent(false);
1861 void HTMLMediaElement::addPlayedRange(double start, double end)
1863 WTF_LOG(Media, "HTMLMediaElement::addPlayedRange(%f, %f)", start, end);
1864 if (!m_playedTimeRanges)
1865 m_playedTimeRanges = TimeRanges::create();
1866 m_playedTimeRanges->add(start, end);
1869 bool HTMLMediaElement::supportsSave() const
1871 return m_player ? m_player->supportsSave() : false;
1874 void HTMLMediaElement::prepareToPlay()
1876 WTF_LOG(Media, "HTMLMediaElement::prepareToPlay(%p)", this);
1877 if (m_havePreparedToPlay)
1879 m_havePreparedToPlay = true;
1881 if (loadIsDeferred())
1882 startDeferredLoad();
1885 void HTMLMediaElement::seek(double time, ExceptionState& exceptionState)
1887 WTF_LOG(Media, "HTMLMediaElement::seek(%f)", time);
1891 // 1 - If the media element's readyState is HAVE_NOTHING, then raise an InvalidStateError exception.
1892 if (m_readyState == HAVE_NOTHING || !m_player) {
1893 exceptionState.throwDOMException(InvalidStateError, "The element's readyState is HAVE_NOTHING.");
1897 // If the media engine has been told to postpone loading data, let it go ahead now.
1898 if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA)
1901 // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set.
1902 refreshCachedTime();
1903 double now = currentTime();
1905 // 2 - If the element's seeking IDL attribute is true, then another instance of this algorithm is
1906 // already running. Abort that other instance of the algorithm without waiting for the step that
1907 // it is running to complete.
1908 // Nothing specific to be done here.
1910 // 3 - Set the seeking IDL attribute to true.
1911 // The flag will be cleared when the engine tells us the time has actually changed.
1912 bool previousSeekStillPending = m_seeking;
1915 // 5 - If the new playback position is later than the end of the media resource, then let it be the end
1916 // of the media resource instead.
1917 time = std::min(time, duration());
1919 // 6 - If the new playback position is less than the earliest possible position, let it be that position instead.
1920 time = std::max(time, 0.0);
1922 // Ask the media engine for the time value in the movie's time scale before comparing with current time. This
1923 // is necessary because if the seek time is not equal to currentTime but the delta is less than the movie's
1924 // time scale, we will ask the media engine to "seek" to the current movie time, which may be a noop and
1925 // not generate a timechanged callback. This means m_seeking will never be cleared and we will never
1926 // fire a 'seeked' event.
1928 double mediaTime = m_player->mediaTimeForTimeValue(time);
1929 if (time != mediaTime)
1930 WTF_LOG(Media, "HTMLMediaElement::seek(%f) - media timeline equivalent is %f", time, mediaTime);
1932 time = m_player->mediaTimeForTimeValue(time);
1934 // 7 - If the (possibly now changed) new playback position is not in one of the ranges given in the
1935 // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute
1936 // that is the nearest to the new playback position. ... If there are no ranges given in the seekable
1937 // attribute then set the seeking IDL attribute to false and abort these steps.
1938 RefPtr<TimeRanges> seekableRanges = seekable();
1940 // Short circuit seeking to the current time by just firing the events if no seek is required.
1941 // Don't skip calling the media engine if we are in poster mode because a seek should always
1942 // cancel poster display.
1943 bool noSeekRequired = !seekableRanges->length() || (time == now && displayMode() != Poster);
1945 if (noSeekRequired) {
1947 scheduleEvent(EventTypeNames::seeking);
1948 if (previousSeekStillPending)
1950 // FIXME: There must be a stable state before timeupdate+seeked are dispatched and seeking
1951 // is reset to false. See http://crbug.com/266631
1952 scheduleTimeupdateEvent(false);
1953 scheduleEvent(EventTypeNames::seeked);
1958 time = seekableRanges->nearest(time);
1961 if (m_lastSeekTime < now)
1962 addPlayedRange(m_lastSeekTime, now);
1964 m_lastSeekTime = time;
1965 m_sentEndEvent = false;
1967 // 8 - Queue a task to fire a simple event named seeking at the element.
1968 scheduleEvent(EventTypeNames::seeking);
1970 // 9 - Set the current playback position to the given new playback position
1971 m_player->seek(time);
1973 // 10-14 are handled, if necessary, when the engine signals a readystate change or otherwise
1974 // satisfies seek completion and signals a time change.
1977 void HTMLMediaElement::finishSeek()
1979 WTF_LOG(Media, "HTMLMediaElement::finishSeek");
1981 // 4.8.10.9 Seeking completion
1982 // 12 - Set the seeking IDL attribute to false.
1985 // 13 - Queue a task to fire a simple event named timeupdate at the element.
1986 scheduleTimeupdateEvent(false);
1988 // 14 - Queue a task to fire a simple event named seeked at the element.
1989 scheduleEvent(EventTypeNames::seeked);
1991 setDisplayMode(Video);
1994 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
1996 return m_readyState;
1999 bool HTMLMediaElement::hasAudio() const
2001 return webMediaPlayer() && webMediaPlayer()->hasAudio();
2004 bool HTMLMediaElement::seeking() const
2009 void HTMLMediaElement::refreshCachedTime() const
2011 m_cachedTime = m_player->currentTime();
2012 m_cachedTimeWallClockUpdateTime = WTF::currentTime();
2015 void HTMLMediaElement::invalidateCachedTime()
2017 WTF_LOG(Media, "HTMLMediaElement::invalidateCachedTime");
2019 // Don't try to cache movie time when playback first starts as the time reported by the engine
2020 // sometimes fluctuates for a short amount of time, so the cached time will be off if we take it
2022 static const double minimumTimePlayingBeforeCacheSnapshot = 0.5;
2024 m_minimumWallClockTimeToCacheMediaTime = WTF::currentTime() + minimumTimePlayingBeforeCacheSnapshot;
2025 m_cachedTime = MediaPlayer::invalidTime();
2029 double HTMLMediaElement::currentTime() const
2031 #if LOG_CACHED_TIME_WARNINGS
2032 static const double minCachedDeltaForWarning = 0.01;
2039 WTF_LOG(Media, "HTMLMediaElement::currentTime - seeking, returning %f", m_lastSeekTime);
2040 return m_lastSeekTime;
2043 if (m_cachedTime != MediaPlayer::invalidTime() && m_paused) {
2044 #if LOG_CACHED_TIME_WARNINGS
2045 double delta = m_cachedTime - m_player->currentTime();
2046 if (delta > minCachedDeltaForWarning)
2047 WTF_LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when paused", delta);
2049 return m_cachedTime;
2052 refreshCachedTime();
2054 return m_cachedTime;
2057 void HTMLMediaElement::setCurrentTime(double time, ExceptionState& exceptionState)
2059 if (m_mediaController) {
2060 exceptionState.throwDOMException(InvalidStateError, "The element is slaved to a MediaController.");
2063 seek(time, exceptionState);
2066 double HTMLMediaElement::duration() const
2068 if (!m_player || m_readyState < HAVE_METADATA)
2069 return std::numeric_limits<double>::quiet_NaN();
2071 // FIXME: Refactor so m_duration is kept current (in both MSE and
2072 // non-MSE cases) once we have transitioned from HAVE_NOTHING ->
2073 // HAVE_METADATA. Currently, m_duration may be out of date for at least MSE
2074 // case because MediaSource and SourceBuffer do not notify the element
2075 // directly upon duration changes caused by endOfStream, remove, or append
2076 // operations; rather the notification is triggered by the WebMediaPlayer
2077 // implementation observing that the underlying engine has updated duration
2078 // and notifying the element to consult its MediaSource for current
2079 // duration. See http://crbug.com/266644
2082 return m_mediaSource->duration();
2084 return m_player->duration();
2087 bool HTMLMediaElement::paused() const
2092 double HTMLMediaElement::defaultPlaybackRate() const
2094 return m_defaultPlaybackRate;
2097 void HTMLMediaElement::setDefaultPlaybackRate(double rate)
2099 if (m_defaultPlaybackRate == rate)
2102 m_defaultPlaybackRate = rate;
2103 scheduleEvent(EventTypeNames::ratechange);
2106 double HTMLMediaElement::playbackRate() const
2108 return m_playbackRate;
2111 void HTMLMediaElement::setPlaybackRate(double rate)
2113 WTF_LOG(Media, "HTMLMediaElement::setPlaybackRate(%f)", rate);
2115 if (m_playbackRate != rate) {
2116 m_playbackRate = rate;
2117 invalidateCachedTime();
2118 scheduleEvent(EventTypeNames::ratechange);
2121 updatePlaybackRate();
2124 double HTMLMediaElement::effectivePlaybackRate() const
2126 return m_mediaController ? m_mediaController->playbackRate() : m_playbackRate;
2129 HTMLMediaElement::DirectionOfPlayback HTMLMediaElement::directionOfPlayback() const
2131 return m_playbackRate >= 0 ? Forward : Backward;
2134 void HTMLMediaElement::updatePlaybackRate()
2136 double effectiveRate = effectivePlaybackRate();
2137 if (m_player && potentiallyPlaying() && m_player->rate() != effectiveRate)
2138 m_player->setRate(effectiveRate);
2141 bool HTMLMediaElement::ended() const
2143 // 4.8.10.8 Playing the media resource
2144 // The ended attribute must return true if the media element has ended
2145 // playback and the direction of playback is forwards, and false otherwise.
2146 return endedPlayback() && directionOfPlayback() == Forward;
2149 bool HTMLMediaElement::autoplay() const
2151 return fastHasAttribute(autoplayAttr);
2154 String HTMLMediaElement::preload() const
2156 switch (m_preload) {
2157 case MediaPlayer::None:
2160 case MediaPlayer::MetaData:
2163 case MediaPlayer::Auto:
2168 ASSERT_NOT_REACHED();
2172 void HTMLMediaElement::setPreload(const AtomicString& preload)
2174 WTF_LOG(Media, "HTMLMediaElement::setPreload(%s)", preload.utf8().data());
2175 setAttribute(preloadAttr, preload);
2178 void HTMLMediaElement::play()
2180 WTF_LOG(Media, "HTMLMediaElement::play()");
2182 if (m_userGestureRequiredForPlay && !UserGestureIndicator::processingUserGesture())
2184 if (UserGestureIndicator::processingUserGesture())
2185 m_userGestureRequiredForPlay = false;
2190 void HTMLMediaElement::playInternal()
2192 WTF_LOG(Media, "HTMLMediaElement::playInternal");
2194 // 4.8.10.9. Playing the media resource
2195 if (!m_player || m_networkState == NETWORK_EMPTY)
2196 scheduleDelayedAction(LoadMediaResource);
2198 if (endedPlayback())
2199 seek(0, IGNORE_EXCEPTION);
2201 if (m_mediaController)
2202 m_mediaController->bringElementUpToSpeed(this);
2206 invalidateCachedTime();
2207 scheduleEvent(EventTypeNames::play);
2209 if (m_readyState <= HAVE_CURRENT_DATA)
2210 scheduleEvent(EventTypeNames::waiting);
2211 else if (m_readyState >= HAVE_FUTURE_DATA)
2212 scheduleEvent(EventTypeNames::playing);
2214 m_autoplaying = false;
2217 updateMediaController();
2220 void HTMLMediaElement::pause()
2222 WTF_LOG(Media, "HTMLMediaElement::pause()");
2224 if (!m_player || m_networkState == NETWORK_EMPTY)
2225 scheduleDelayedAction(LoadMediaResource);
2227 m_autoplaying = false;
2231 scheduleTimeupdateEvent(false);
2232 scheduleEvent(EventTypeNames::pause);
2238 void HTMLMediaElement::closeMediaSource()
2243 m_mediaSource->close();
2244 m_mediaSource = nullptr;
2247 bool HTMLMediaElement::loop() const
2249 return fastHasAttribute(loopAttr);
2252 void HTMLMediaElement::setLoop(bool b)
2254 WTF_LOG(Media, "HTMLMediaElement::setLoop(%s)", boolString(b));
2255 setBooleanAttribute(loopAttr, b);
2258 bool HTMLMediaElement::controls() const
2260 LocalFrame* frame = document().frame();
2262 // always show controls when scripting is disabled
2263 if (frame && !frame->script().canExecuteScripts(NotAboutToExecuteScript))
2266 // Always show controls when in full screen mode.
2270 return fastHasAttribute(controlsAttr);
2273 void HTMLMediaElement::setControls(bool b)
2275 WTF_LOG(Media, "HTMLMediaElement::setControls(%s)", boolString(b));
2276 setBooleanAttribute(controlsAttr, b);
2279 double HTMLMediaElement::volume() const
2284 void HTMLMediaElement::setVolume(double vol, ExceptionState& exceptionState)
2286 WTF_LOG(Media, "HTMLMediaElement::setVolume(%f)", vol);
2288 if (m_volume == vol)
2291 if (vol < 0.0f || vol > 1.0f) {
2292 exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexOutsideRange("volume", vol, 0.0, ExceptionMessages::InclusiveBound, 1.0, ExceptionMessages::InclusiveBound));
2298 scheduleEvent(EventTypeNames::volumechange);
2301 bool HTMLMediaElement::muted() const
2306 void HTMLMediaElement::setMuted(bool muted)
2308 WTF_LOG(Media, "HTMLMediaElement::setMuted(%s)", boolString(muted));
2310 if (m_muted == muted)
2317 scheduleEvent(EventTypeNames::volumechange);
2320 // The spec says to fire periodic timeupdate events (those sent while playing) every
2321 // "15 to 250ms", we choose the slowest frequency
2322 static const double maxTimeupdateEventFrequency = 0.25;
2324 void HTMLMediaElement::startPlaybackProgressTimer()
2326 if (m_playbackProgressTimer.isActive())
2329 m_previousProgressTime = WTF::currentTime();
2330 m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency, FROM_HERE);
2333 void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*)
2337 if (m_fragmentEndTime != MediaPlayer::invalidTime() && currentTime() >= m_fragmentEndTime && directionOfPlayback() == Forward) {
2338 m_fragmentEndTime = MediaPlayer::invalidTime();
2339 if (!m_mediaController && !m_paused) {
2340 UseCounter::count(document(), UseCounter::HTMLMediaElementPauseAtFragmentEnd);
2341 // changes paused to true and fires a simple event named pause at the media element.
2347 scheduleTimeupdateEvent(true);
2349 if (!effectivePlaybackRate())
2352 if (!m_paused && hasMediaControls())
2353 mediaControls()->playbackProgressed();
2355 updateActiveTextTrackCues(currentTime());
2358 void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent)
2360 double now = WTF::currentTime();
2361 double timedelta = now - m_lastTimeUpdateEventWallTime;
2363 // throttle the periodic events
2364 if (periodicEvent && timedelta < maxTimeupdateEventFrequency)
2367 // Some media engines make multiple "time changed" callbacks at the same time, but we only want one
2368 // event at a given time so filter here
2369 double movieTime = currentTime();
2370 if (movieTime != m_lastTimeUpdateEventMovieTime) {
2371 scheduleEvent(EventTypeNames::timeupdate);
2372 m_lastTimeUpdateEventWallTime = now;
2373 m_lastTimeUpdateEventMovieTime = movieTime;
2377 bool HTMLMediaElement::togglePlayStateWillPlay() const
2379 if (m_mediaController)
2380 return m_mediaController->paused() || m_mediaController->isRestrained();
2384 void HTMLMediaElement::togglePlayState()
2386 if (m_mediaController) {
2387 if (m_mediaController->isRestrained())
2388 m_mediaController->play();
2389 else if (m_mediaController->paused())
2390 m_mediaController->unpause();
2392 m_mediaController->pause();
2401 AudioTrackList& HTMLMediaElement::audioTracks()
2403 ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
2404 return *m_audioTracks;
2407 void HTMLMediaElement::audioTrackChanged()
2409 WTF_LOG(Media, "HTMLMediaElement::audioTrackChanged()");
2410 ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
2412 audioTracks().scheduleChangeEvent();
2414 // FIXME: Add call on m_mediaSource to notify it of track changes once the SourceBuffer.audioTracks attribute is added.
2416 if (!m_audioTracksTimer.isActive())
2417 m_audioTracksTimer.startOneShot(0, FROM_HERE);
2420 void HTMLMediaElement::audioTracksTimerFired(Timer<HTMLMediaElement>*)
2422 Vector<WebMediaPlayer::TrackId> enabledTrackIds;
2423 for (unsigned i = 0; i < audioTracks().length(); ++i) {
2424 AudioTrack* track = audioTracks().anonymousIndexedGetter(i);
2425 if (track->enabled())
2426 enabledTrackIds.append(track->trackId());
2429 webMediaPlayer()->enabledAudioTracksChanged(enabledTrackIds);
2432 WebMediaPlayer::TrackId HTMLMediaElement::addAudioTrack(const String& id, blink::WebMediaPlayerClient::AudioTrackKind kind, const AtomicString& label, const AtomicString& language, bool enabled)
2434 AtomicString kindString = AudioKindToString(kind);
2435 WTF_LOG(Media, "HTMLMediaElement::addAudioTrack('%s', '%s', '%s', '%s', %d)",
2436 id.ascii().data(), kindString.ascii().data(), label.ascii().data(), language.ascii().data(), enabled);
2438 if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
2441 RefPtrWillBeRawPtr<AudioTrack> audioTrack = AudioTrack::create(id, kindString, label, language, enabled);
2442 audioTracks().add(audioTrack);
2444 return audioTrack->trackId();
2447 void HTMLMediaElement::removeAudioTrack(WebMediaPlayer::TrackId trackId)
2449 WTF_LOG(Media, "HTMLMediaElement::removeAudioTrack()");
2451 if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
2454 audioTracks().remove(trackId);
2457 VideoTrackList& HTMLMediaElement::videoTracks()
2459 ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
2460 return *m_videoTracks;
2463 void HTMLMediaElement::selectedVideoTrackChanged(WebMediaPlayer::TrackId* selectedTrackId)
2465 WTF_LOG(Media, "HTMLMediaElement::selectedVideoTrackChanged()");
2466 ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
2468 if (selectedTrackId)
2469 videoTracks().trackSelected(*selectedTrackId);
2471 // FIXME: Add call on m_mediaSource to notify it of track changes once the SourceBuffer.videoTracks attribute is added.
2473 webMediaPlayer()->selectedVideoTrackChanged(selectedTrackId);
2476 WebMediaPlayer::TrackId HTMLMediaElement::addVideoTrack(const String& id, blink::WebMediaPlayerClient::VideoTrackKind kind, const AtomicString& label, const AtomicString& language, bool selected)
2478 AtomicString kindString = VideoKindToString(kind);
2479 WTF_LOG(Media, "HTMLMediaElement::addVideoTrack('%s', '%s', '%s', '%s', %d)",
2480 id.ascii().data(), kindString.ascii().data(), label.ascii().data(), language.ascii().data(), selected);
2482 if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
2485 // If another track was selected (potentially by the user), leave it selected.
2486 if (selected && videoTracks().selectedIndex() != -1)
2489 RefPtrWillBeRawPtr<VideoTrack> videoTrack = VideoTrack::create(id, kindString, label, language, selected);
2490 videoTracks().add(videoTrack);
2492 return videoTrack->trackId();
2495 void HTMLMediaElement::removeVideoTrack(WebMediaPlayer::TrackId trackId)
2497 WTF_LOG(Media, "HTMLMediaElement::removeVideoTrack()");
2499 if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
2502 videoTracks().remove(trackId);
2505 void HTMLMediaElement::mediaPlayerDidAddTextTrack(WebInbandTextTrack* webTrack)
2507 // 4.8.10.12.2 Sourcing in-band text tracks
2508 // 1. Associate the relevant data with a new text track and its corresponding new TextTrack object.
2509 RefPtrWillBeRawPtr<InbandTextTrack> textTrack = InbandTextTrack::create(document(), webTrack);
2511 // 2. Set the new text track's kind, label, and language based on the semantics of the relevant data,
2512 // as defined by the relevant specification. If there is no label in that data, then the label must
2513 // be set to the empty string.
2514 // 3. Associate the text track list of cues with the rules for updating the text track rendering appropriate
2515 // for the format in question.
2516 // 4. If the new text track's kind is metadata, then set the text track in-band metadata track dispatch type
2517 // as follows, based on the type of the media resource:
2518 // 5. Populate the new text track's list of cues with the cues parsed so far, folllowing the guidelines for exposing
2519 // cues, and begin updating it dynamically as necessary.
2520 // - Thess are all done by the media engine.
2522 // 6. Set the new text track's readiness state to loaded.
2523 textTrack->setReadinessState(TextTrack::Loaded);
2525 // 7. Set the new text track's mode to the mode consistent with the user's preferences and the requirements of
2526 // the relevant specification for the data.
2527 // - This will happen in configureTextTracks()
2528 scheduleDelayedAction(LoadTextTrackResource);
2530 // 8. Add the new text track to the media element's list of text tracks.
2531 // 9. Fire an event with the name addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent
2532 // interface, with the track attribute initialized to the text track's TextTrack object, at the media element's
2533 // textTracks attribute's TextTrackList object.
2534 addTextTrack(textTrack.get());
2537 void HTMLMediaElement::mediaPlayerDidRemoveTextTrack(WebInbandTextTrack* webTrack)
2542 // This cast is safe because we created the InbandTextTrack with the WebInbandTextTrack
2543 // passed to mediaPlayerDidAddTextTrack.
2544 RefPtrWillBeRawPtr<InbandTextTrack> textTrack = static_cast<InbandTextTrack*>(webTrack->client());
2548 removeTextTrack(textTrack.get());
2551 void HTMLMediaElement::closeCaptionTracksChanged()
2553 if (hasMediaControls())
2554 mediaControls()->closedCaptionTracksChanged();
2557 void HTMLMediaElement::addTextTrack(TextTrack* track)
2559 textTracks()->append(track);
2561 closeCaptionTracksChanged();
2564 void HTMLMediaElement::removeTextTrack(TextTrack* track)
2566 TrackDisplayUpdateScope scope(this);
2567 m_textTracks->remove(track);
2569 closeCaptionTracksChanged();
2572 void HTMLMediaElement::forgetResourceSpecificTracks()
2574 // Implements the "forget the media element's media-resource-specific tracks" algorithm.
2575 // The order is explicitly specified as text, then audio, and finally video. Also
2576 // 'removetrack' events should not be fired.
2578 TrackDisplayUpdateScope scope(this);
2579 m_textTracks->removeAllInbandTracks();
2580 closeCaptionTracksChanged();
2583 m_audioTracks->removeAll();
2584 m_videoTracks->removeAll();
2586 m_audioTracksTimer.stop();
2589 PassRefPtrWillBeRawPtr<TextTrack> HTMLMediaElement::addTextTrack(const AtomicString& kind, const AtomicString& label, const AtomicString& language, ExceptionState& exceptionState)
2591 // 4.8.10.12.4 Text track API
2592 // The addTextTrack(kind, label, language) method of media elements, when invoked, must run the following steps:
2594 // 1. If kind is not one of the following strings, then throw a SyntaxError exception and abort these steps
2595 if (!TextTrack::isValidKindKeyword(kind)) {
2596 exceptionState.throwDOMException(SyntaxError, "The 'kind' provided ('" + kind + "') is invalid.");
2600 // 2. If the label argument was omitted, let label be the empty string.
2601 // 3. If the language argument was omitted, let language be the empty string.
2602 // 4. Create a new TextTrack object.
2604 // 5. Create a new text track corresponding to the new object, and set its text track kind to kind, its text
2605 // track label to label, its text track language to language...
2606 RefPtrWillBeRawPtr<TextTrack> textTrack = TextTrack::create(document(), kind, label, language);
2608 // Note, due to side effects when changing track parameters, we have to
2609 // first append the track to the text track list.
2611 // 6. Add the new text track to the media element's list of text tracks.
2612 addTextTrack(textTrack.get());
2614 // ... its text track readiness state to the text track loaded state ...
2615 textTrack->setReadinessState(TextTrack::Loaded);
2617 // ... its text track mode to the text track hidden mode, and its text track list of cues to an empty list ...
2618 textTrack->setMode(TextTrack::hiddenKeyword());
2620 return textTrack.release();
2623 TextTrackList* HTMLMediaElement::textTracks()
2626 m_textTracks = TextTrackList::create(this);
2628 return m_textTracks.get();
2631 void HTMLMediaElement::didAddTrackElement(HTMLTrackElement* trackElement)
2633 // 4.8.10.12.3 Sourcing out-of-band text tracks
2634 // When a track element's parent element changes and the new parent is a media element,
2635 // then the user agent must add the track element's corresponding text track to the
2636 // media element's list of text tracks ... [continues in TextTrackList::append]
2637 RefPtrWillBeRawPtr<TextTrack> textTrack = trackElement->track();
2641 addTextTrack(textTrack.get());
2643 // Do not schedule the track loading until parsing finishes so we don't start before all tracks
2644 // in the markup have been added.
2645 if (isFinishedParsingChildren())
2646 scheduleDelayedAction(LoadTextTrackResource);
2648 if (hasMediaControls())
2649 mediaControls()->closedCaptionTracksChanged();
2652 void HTMLMediaElement::didRemoveTrackElement(HTMLTrackElement* trackElement)
2655 KURL url = trackElement->getNonEmptyURLAttribute(srcAttr);
2656 WTF_LOG(Media, "HTMLMediaElement::didRemoveTrackElement - 'src' is %s", urlForLoggingMedia(url).utf8().data());
2659 RefPtrWillBeRawPtr<TextTrack> textTrack = trackElement->track();
2663 textTrack->setHasBeenConfigured(false);
2668 // 4.8.10.12.3 Sourcing out-of-band text tracks
2669 // When a track element's parent element changes and the old parent was a media element,
2670 // then the user agent must remove the track element's corresponding text track from the
2671 // media element's list of text tracks.
2672 removeTextTrack(textTrack.get());
2674 size_t index = m_textTracksWhenResourceSelectionBegan.find(textTrack.get());
2675 if (index != kNotFound)
2676 m_textTracksWhenResourceSelectionBegan.remove(index);
2679 static int textTrackLanguageSelectionScore(const TextTrack& track)
2681 if (track.language().isEmpty())
2684 Vector<AtomicString> languages = userPreferredLanguages();
2685 size_t languageMatchIndex = indexOfBestMatchingLanguageInList(track.language(), languages);
2686 if (languageMatchIndex >= languages.size())
2689 return languages.size() - languageMatchIndex;
2692 static int textTrackSelectionScore(const TextTrack& track)
2694 if (track.kind() != TextTrack::captionsKeyword() && track.kind() != TextTrack::subtitlesKeyword())
2697 return textTrackLanguageSelectionScore(track);
2700 void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group)
2702 ASSERT(group.tracks.size());
2704 WTF_LOG(Media, "HTMLMediaElement::configureTextTrackGroup(%d)", group.kind);
2706 // First, find the track in the group that should be enabled (if any).
2707 WillBeHeapVector<RefPtrWillBeMember<TextTrack> > currentlyEnabledTracks;
2708 RefPtrWillBeRawPtr<TextTrack> trackToEnable = nullptr;
2709 RefPtrWillBeRawPtr<TextTrack> defaultTrack = nullptr;
2710 RefPtrWillBeRawPtr<TextTrack> fallbackTrack = nullptr;
2711 int highestTrackScore = 0;
2712 for (size_t i = 0; i < group.tracks.size(); ++i) {
2713 RefPtrWillBeRawPtr<TextTrack> textTrack = group.tracks[i];
2715 if (m_processingPreferenceChange && textTrack->mode() == TextTrack::showingKeyword())
2716 currentlyEnabledTracks.append(textTrack);
2718 int trackScore = textTrackSelectionScore(*textTrack);
2720 // * If the text track kind is { [subtitles or captions] [descriptions] } and the user has indicated an interest in having a
2721 // track with this text track kind, text track language, and text track label enabled, and there is no
2722 // other text track in the media element's list of text tracks with a text track kind of either subtitles
2723 // or captions whose text track mode is showing
2725 // * If the text track kind is chapters and the text track language is one that the user agent has reason
2726 // to believe is appropriate for the user, and there is no other text track in the media element's list of
2727 // text tracks with a text track kind of chapters whose text track mode is showing
2728 // Let the text track mode be showing.
2729 if (trackScore > highestTrackScore) {
2730 highestTrackScore = trackScore;
2731 trackToEnable = textTrack;
2734 if (!defaultTrack && textTrack->isDefault())
2735 defaultTrack = textTrack;
2736 if (!defaultTrack && !fallbackTrack)
2737 fallbackTrack = textTrack;
2738 } else if (!group.visibleTrack && !defaultTrack && textTrack->isDefault()) {
2739 // * If the track element has a default attribute specified, and there is no other text track in the media
2740 // element's list of text tracks whose text track mode is showing or showing by default
2741 // Let the text track mode be showing by default.
2742 defaultTrack = textTrack;
2746 if (!trackToEnable && defaultTrack)
2747 trackToEnable = defaultTrack;
2749 // If no track matches the user's preferred language and non was marked 'default', enable the first track
2750 // because the user has explicitly stated a preference for this kind of track.
2751 if (!fallbackTrack && m_closedCaptionsVisible && group.kind == TrackGroup::CaptionsAndSubtitles)
2752 fallbackTrack = group.tracks[0];
2754 if (!trackToEnable && fallbackTrack)
2755 trackToEnable = fallbackTrack;
2757 if (currentlyEnabledTracks.size()) {
2758 for (size_t i = 0; i < currentlyEnabledTracks.size(); ++i) {
2759 RefPtrWillBeRawPtr<TextTrack> textTrack = currentlyEnabledTracks[i];
2760 if (textTrack != trackToEnable)
2761 textTrack->setMode(TextTrack::disabledKeyword());
2766 trackToEnable->setMode(TextTrack::showingKeyword());
2769 void HTMLMediaElement::configureTextTracks()
2771 TrackGroup captionAndSubtitleTracks(TrackGroup::CaptionsAndSubtitles);
2772 TrackGroup descriptionTracks(TrackGroup::Description);
2773 TrackGroup chapterTracks(TrackGroup::Chapter);
2774 TrackGroup metadataTracks(TrackGroup::Metadata);
2775 TrackGroup otherTracks(TrackGroup::Other);
2780 for (size_t i = 0; i < m_textTracks->length(); ++i) {
2781 RefPtrWillBeRawPtr<TextTrack> textTrack = m_textTracks->item(i);
2785 String kind = textTrack->kind();
2786 TrackGroup* currentGroup;
2787 if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword())
2788 currentGroup = &captionAndSubtitleTracks;
2789 else if (kind == TextTrack::descriptionsKeyword())
2790 currentGroup = &descriptionTracks;
2791 else if (kind == TextTrack::chaptersKeyword())
2792 currentGroup = &chapterTracks;
2793 else if (kind == TextTrack::metadataKeyword())
2794 currentGroup = &metadataTracks;
2796 currentGroup = &otherTracks;
2798 if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::showingKeyword())
2799 currentGroup->visibleTrack = textTrack;
2800 if (!currentGroup->defaultTrack && textTrack->isDefault())
2801 currentGroup->defaultTrack = textTrack;
2803 // Do not add this track to the group if it has already been automatically configured
2804 // as we only want to call configureTextTrack once per track so that adding another
2805 // track after the initial configuration doesn't reconfigure every track - only those
2806 // that should be changed by the new addition. For example all metadata tracks are
2807 // disabled by default, and we don't want a track that has been enabled by script
2808 // to be disabled automatically when a new metadata track is added later.
2809 if (textTrack->hasBeenConfigured())
2812 if (textTrack->language().length())
2813 currentGroup->hasSrcLang = true;
2814 currentGroup->tracks.append(textTrack);
2817 if (captionAndSubtitleTracks.tracks.size())
2818 configureTextTrackGroup(captionAndSubtitleTracks);
2819 if (descriptionTracks.tracks.size())
2820 configureTextTrackGroup(descriptionTracks);
2821 if (chapterTracks.tracks.size())
2822 configureTextTrackGroup(chapterTracks);
2823 if (metadataTracks.tracks.size())
2824 configureTextTrackGroup(metadataTracks);
2825 if (otherTracks.tracks.size())
2826 configureTextTrackGroup(otherTracks);
2828 if (hasMediaControls())
2829 mediaControls()->closedCaptionTracksChanged();
2832 bool HTMLMediaElement::havePotentialSourceChild()
2834 // Stash the current <source> node and next nodes so we can restore them after checking
2835 // to see there is another potential.
2836 RefPtrWillBeRawPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode;
2837 RefPtrWillBeRawPtr<Node> nextNode = m_nextChildNodeToConsider;
2839 KURL nextURL = selectNextSourceChild(0, 0, DoNothing);
2841 m_currentSourceNode = currentSourceNode;
2842 m_nextChildNodeToConsider = nextNode;
2844 return nextURL.isValid();
2847 KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid)
2850 // Don't log if this was just called to find out if there are any valid <source> elements.
2851 bool shouldLog = actionIfInvalid != DoNothing;
2853 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild");
2856 if (!m_nextChildNodeToConsider) {
2859 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild -> 0x0000, \"\"");
2866 HTMLSourceElement* source = 0;
2869 bool lookingForStartNode = m_nextChildNodeToConsider;
2870 bool canUseSourceElement = false;
2872 NodeVector potentialSourceNodes;
2873 getChildNodes(*this, potentialSourceNodes);
2875 for (unsigned i = 0; !canUseSourceElement && i < potentialSourceNodes.size(); ++i) {
2876 node = potentialSourceNodes[i].get();
2877 if (lookingForStartNode && m_nextChildNodeToConsider != node)
2879 lookingForStartNode = false;
2881 if (!isHTMLSourceElement(*node))
2883 if (node->parentNode() != this)
2886 source = toHTMLSourceElement(node);
2888 // 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
2889 mediaURL = source->getNonEmptyURLAttribute(srcAttr);
2892 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'src' is %s", urlForLoggingMedia(mediaURL).utf8().data());
2894 if (mediaURL.isEmpty())
2897 type = source->type();
2898 // FIXME(82965): Add support for keySystem in <source> and set system from source.
2899 if (type.isEmpty() && mediaURL.protocolIsData())
2900 type = mimeTypeFromDataURL(mediaURL);
2901 if (!type.isEmpty() || !system.isEmpty()) {
2904 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'type' is '%s' - key system is '%s'", type.utf8().data(), system.utf8().data());
2906 if (!supportsType(ContentType(type), system))
2910 // Is it safe to load this url?
2911 if (!isSafeToLoadURL(mediaURL, actionIfInvalid))
2914 // Making it this far means the <source> looks reasonable.
2915 canUseSourceElement = true;
2918 if (!canUseSourceElement && actionIfInvalid == Complain && source)
2919 source->scheduleErrorEvent();
2922 if (canUseSourceElement) {
2924 *contentType = ContentType(type);
2926 *keySystem = system;
2927 m_currentSourceNode = source;
2928 m_nextChildNodeToConsider = source->nextSibling();
2930 m_currentSourceNode = nullptr;
2931 m_nextChildNodeToConsider = nullptr;
2936 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild -> %p, %s", m_currentSourceNode.get(), canUseSourceElement ? urlForLoggingMedia(mediaURL).utf8().data() : "");
2938 return canUseSourceElement ? mediaURL : KURL();
2941 void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source)
2943 WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded(%p)", source);
2946 KURL url = source->getNonEmptyURLAttribute(srcAttr);
2947 WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded - 'src' is %s", urlForLoggingMedia(url).utf8().data());
2950 // We should only consider a <source> element when there is not src attribute at all.
2951 if (fastHasAttribute(srcAttr))
2954 // 4.8.8 - If a source element is inserted as a child of a media element that has no src
2955 // attribute and whose networkState has the value NETWORK_EMPTY, the user agent must invoke
2956 // the media element's resource selection algorithm.
2957 if (networkState() == HTMLMediaElement::NETWORK_EMPTY) {
2958 scheduleDelayedAction(LoadMediaResource);
2959 m_nextChildNodeToConsider = source;
2963 if (m_currentSourceNode && source == m_currentSourceNode->nextSibling()) {
2964 WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded - <source> inserted immediately after current source");
2965 m_nextChildNodeToConsider = source;
2969 if (m_nextChildNodeToConsider)
2972 // 4.8.9.5, resource selection algorithm, source elements section:
2973 // 21. Wait until the node after pointer is a node other than the end of the list. (This step might wait forever.)
2974 // 22. Asynchronously await a stable state...
2975 // 23. Set the element's delaying-the-load-event flag back to true (this delays the load event again, in case
2976 // it hasn't been fired yet).
2977 setShouldDelayLoadEvent(true);
2979 // 24. Set the networkState back to NETWORK_LOADING.
2980 m_networkState = NETWORK_LOADING;
2982 // 25. Jump back to the find next candidate step above.
2983 m_nextChildNodeToConsider = source;
2984 scheduleNextSourceChild();
2987 void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement* source)
2989 WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p)", source);
2992 KURL url = source->getNonEmptyURLAttribute(srcAttr);
2993 WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved - 'src' is %s", urlForLoggingMedia(url).utf8().data());
2996 if (source != m_currentSourceNode && source != m_nextChildNodeToConsider)
2999 if (source == m_nextChildNodeToConsider) {
3000 if (m_currentSourceNode)
3001 m_nextChildNodeToConsider = m_currentSourceNode->nextSibling();
3002 WTF_LOG(Media, "HTMLMediaElement::sourceRemoved - m_nextChildNodeToConsider set to %p", m_nextChildNodeToConsider.get());
3003 } else if (source == m_currentSourceNode) {
3004 // Clear the current source node pointer, but don't change the movie as the spec says:
3005 // 4.8.8 - Dynamically modifying a source element and its attribute when the element is already
3006 // inserted in a video or audio element will have no effect.
3007 m_currentSourceNode = nullptr;
3008 WTF_LOG(Media, "HTMLMediaElement::sourceRemoved - m_currentSourceNode set to 0");
3012 void HTMLMediaElement::mediaPlayerTimeChanged()
3014 WTF_LOG(Media, "HTMLMediaElement::mediaPlayerTimeChanged");
3016 updateActiveTextTrackCues(currentTime());
3018 invalidateCachedTime();
3020 // 4.8.10.9 steps 12-14. Needed if no ReadyState change is associated with the seek.
3021 if (m_seeking && m_readyState >= HAVE_CURRENT_DATA && !m_player->seeking())
3024 // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity,
3025 // it will only queue a 'timeupdate' event if we haven't already posted one at the current
3027 scheduleTimeupdateEvent(false);
3029 double now = currentTime();
3030 double dur = duration();
3032 // When the current playback position reaches the end of the media resource when the direction of
3033 // playback is forwards, then the user agent must follow these steps:
3034 if (!std::isnan(dur) && dur && now >= dur && directionOfPlayback() == Forward) {
3035 // If the media element has a loop attribute specified and does not have a current media controller,
3036 if (loop() && !m_mediaController) {
3037 m_sentEndEvent = false;
3038 // then seek to the earliest possible position of the media resource and abort these steps.
3039 seek(0, IGNORE_EXCEPTION);
3041 // If the media element does not have a current media controller, and the media element
3042 // has still ended playback, and the direction of playback is still forwards, and paused
3044 if (!m_mediaController && !m_paused) {
3045 // changes paused to true and fires a simple event named pause at the media element.
3047 scheduleEvent(EventTypeNames::pause);
3049 // Queue a task to fire a simple event named ended at the media element.
3050 if (!m_sentEndEvent) {
3051 m_sentEndEvent = true;
3052 scheduleEvent(EventTypeNames::ended);
3054 // If the media element has a current media controller, then report the controller state
3055 // for the media element's current media controller.
3056 updateMediaController();
3060 m_sentEndEvent = false;
3065 void HTMLMediaElement::mediaPlayerDurationChanged()
3067 WTF_LOG(Media, "HTMLMediaElement::mediaPlayerDurationChanged");
3068 // FIXME: Change MediaPlayerClient & WebMediaPlayer to convey
3069 // the currentTime when the duration change occured. The current
3070 // WebMediaPlayer implementations always clamp currentTime() to
3071 // duration() so the requestSeek condition here is always false.
3072 durationChanged(duration(), currentTime() > duration());
3075 void HTMLMediaElement::durationChanged(double duration, bool requestSeek)
3077 WTF_LOG(Media, "HTMLMediaElement::durationChanged(%f, %d)", duration, requestSeek);
3079 // Abort if duration unchanged.
3080 if (m_duration == duration)
3083 WTF_LOG(Media, "HTMLMediaElement::durationChanged : %f -> %f", m_duration, duration);
3084 m_duration = duration;
3085 scheduleEvent(EventTypeNames::durationchange);
3087 if (hasMediaControls())
3088 mediaControls()->reset();
3090 renderer()->updateFromElement();
3093 seek(duration, IGNORE_EXCEPTION);
3096 void HTMLMediaElement::mediaPlayerPlaybackStateChanged()
3098 WTF_LOG(Media, "HTMLMediaElement::mediaPlayerPlaybackStateChanged");
3100 if (!m_player || m_pausedInternal)
3103 if (m_player->paused())
3109 void HTMLMediaElement::mediaPlayerRequestFullscreen()
3111 WTF_LOG(Media, "HTMLMediaElement::mediaPlayerRequestFullscreen");
3113 // The player is responsible for only invoking this callback in response to
3114 // user interaction or when it is technically required to play the video.
3115 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
3120 void HTMLMediaElement::mediaPlayerRequestSeek(double time)
3122 // The player is the source of this seek request.
3123 if (m_mediaController) {
3124 m_mediaController->setCurrentTime(time, IGNORE_EXCEPTION);
3127 setCurrentTime(time, IGNORE_EXCEPTION);
3130 // MediaPlayerPresentation methods
3131 void HTMLMediaElement::mediaPlayerRepaint()
3134 m_webLayer->invalidate();
3136 updateDisplayState();
3138 renderer()->paintInvalidationForWholeRenderer();
3141 void HTMLMediaElement::mediaPlayerSizeChanged()
3143 WTF_LOG(Media, "HTMLMediaElement::mediaPlayerSizeChanged");
3145 ASSERT(hasVideo()); // "resize" makes no sense absent video.
3146 if (m_readyState > HAVE_NOTHING && isHTMLVideoElement(*this))
3147 scheduleEvent(EventTypeNames::resize);
3150 renderer()->updateFromElement();
3153 PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const
3156 return TimeRanges::create();
3159 return m_mediaSource->buffered();
3161 return m_player->buffered();
3164 PassRefPtr<TimeRanges> HTMLMediaElement::played()
3167 double time = currentTime();
3168 if (time > m_lastSeekTime)
3169 addPlayedRange(m_lastSeekTime, time);
3172 if (!m_playedTimeRanges)
3173 m_playedTimeRanges = TimeRanges::create();
3175 return m_playedTimeRanges->copy();
3178 PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const
3181 double maxTimeSeekable = m_player->maxTimeSeekable();
3182 if (maxTimeSeekable)
3183 return TimeRanges::create(0, maxTimeSeekable);
3185 return TimeRanges::create();
3188 bool HTMLMediaElement::potentiallyPlaying() const
3190 // "pausedToBuffer" means the media engine's rate is 0, but only because it had to stop playing
3191 // when it ran out of buffered data. A movie is this state is "potentially playing", modulo the
3192 // checks in couldPlayIfEnoughData().
3193 bool pausedToBuffer = m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA;
3194 return (pausedToBuffer || m_readyState >= HAVE_FUTURE_DATA) && couldPlayIfEnoughData() && !isBlockedOnMediaController();
3197 bool HTMLMediaElement::couldPlayIfEnoughData() const
3199 return !paused() && !endedPlayback() && !stoppedDueToErrors();
3202 bool HTMLMediaElement::endedPlayback() const
3204 double dur = duration();
3205 if (!m_player || std::isnan(dur))
3208 // 4.8.10.8 Playing the media resource
3210 // A media element is said to have ended playback when the element's
3211 // readyState attribute is HAVE_METADATA or greater,
3212 if (m_readyState < HAVE_METADATA)
3215 // and the current playback position is the end of the media resource and the direction
3216 // of playback is forwards, Either the media element does not have a loop attribute specified,
3217 // or the media element has a current media controller.
3218 double now = currentTime();
3219 if (directionOfPlayback() == Forward)
3220 return dur > 0 && now >= dur && (!loop() || m_mediaController);
3222 // or the current playback position is the earliest possible position and the direction
3223 // of playback is backwards
3224 ASSERT(directionOfPlayback() == Backward);
3228 bool HTMLMediaElement::stoppedDueToErrors() const
3230 if (m_readyState >= HAVE_METADATA && m_error) {
3231 RefPtr<TimeRanges> seekableRanges = seekable();
3232 if (!seekableRanges->contain(currentTime()))
3239 void HTMLMediaElement::updateVolume()
3241 if (webMediaPlayer())
3242 webMediaPlayer()->setVolume(playerVolume());
3244 if (hasMediaControls())
3245 mediaControls()->updateVolume();
3248 double HTMLMediaElement::playerVolume() const
3250 double volumeMultiplier = 1;
3251 bool shouldMute = m_muted;
3253 if (m_mediaController) {
3254 volumeMultiplier *= m_mediaController->volume();
3255 shouldMute = m_mediaController->muted();
3258 return shouldMute ? 0 : m_volume * volumeMultiplier;
3261 void HTMLMediaElement::updatePlayState()
3266 if (m_pausedInternal) {
3267 if (!m_player->paused())
3269 refreshCachedTime();
3270 m_playbackProgressTimer.stop();
3271 if (hasMediaControls())
3272 mediaControls()->playbackStopped();
3276 bool shouldBePlaying = potentiallyPlaying();
3277 bool playerPaused = m_player->paused();
3279 WTF_LOG(Media, "HTMLMediaElement::updatePlayState - shouldBePlaying = %s, playerPaused = %s",
3280 boolString(shouldBePlaying), boolString(playerPaused));
3282 if (shouldBePlaying) {
3283 setDisplayMode(Video);
3284 invalidateCachedTime();
3287 // Set rate, muted before calling play in case they were set before the media engine was setup.
3288 // The media engine should just stash the rate and muted values since it isn't already playing.
3289 m_player->setRate(effectivePlaybackRate());
3295 if (hasMediaControls())
3296 mediaControls()->playbackStarted();
3297 startPlaybackProgressTimer();
3300 } else { // Should not be playing right now
3303 refreshCachedTime();
3305 m_playbackProgressTimer.stop();
3307 double time = currentTime();
3308 if (time > m_lastSeekTime)
3309 addPlayedRange(m_lastSeekTime, time);
3311 if (couldPlayIfEnoughData())
3314 if (hasMediaControls())
3315 mediaControls()->playbackStopped();
3318 updateMediaController();
3321 renderer()->updateFromElement();
3324 void HTMLMediaElement::setPausedInternal(bool b)
3326 m_pausedInternal = b;
3330 void HTMLMediaElement::stopPeriodicTimers()
3332 m_progressEventTimer.stop();
3333 m_playbackProgressTimer.stop();
3336 void HTMLMediaElement::userCancelledLoad()
3338 WTF_LOG(Media, "HTMLMediaElement::userCancelledLoad");
3340 // If the media data fetching process is aborted by the user:
3342 // 1 - The user agent should cancel the fetching process.
3343 clearMediaPlayer(-1);
3345 if (m_networkState == NETWORK_EMPTY || m_completelyLoaded)
3348 // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED.
3349 m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED);
3351 // 3 - Queue a task to fire a simple event named error at the media element.
3352 scheduleEvent(EventTypeNames::abort);
3356 // 4 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the
3357 // element's networkState attribute to the NETWORK_EMPTY value and queue a task to fire a
3358 // simple event named emptied at the element. Otherwise, set the element's networkState
3359 // attribute to the NETWORK_IDLE value.
3360 if (m_readyState == HAVE_NOTHING) {
3361 m_networkState = NETWORK_EMPTY;
3362 scheduleEvent(EventTypeNames::emptied);
3365 m_networkState = NETWORK_IDLE;
3367 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
3368 setShouldDelayLoadEvent(false);
3370 // 6 - Abort the overall resource selection algorithm.
3371 m_currentSourceNode = nullptr;
3373 // Reset m_readyState since m_player is gone.
3374 m_readyState = HAVE_NOTHING;
3375 updateMediaController();
3376 updateActiveTextTrackCues(0);
3379 void HTMLMediaElement::clearMediaPlayerAndAudioSourceProviderClientWithoutLocking()
3381 #if ENABLE(WEB_AUDIO)
3382 if (audioSourceProvider())
3383 audioSourceProvider()->setClient(0);
3388 void HTMLMediaElement::clearMediaPlayer(int flags)
3390 forgetResourceSpecificTracks();
3394 cancelDeferredLoad();
3396 #if ENABLE(WEB_AUDIO)
3397 if (m_audioSourceNode)
3398 m_audioSourceNode->lock();
3401 clearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
3403 #if ENABLE(WEB_AUDIO)
3404 if (m_audioSourceNode)
3405 m_audioSourceNode->unlock();
3408 stopPeriodicTimers();
3411 m_pendingActionFlags &= ~flags;
3412 m_loadState = WaitingForSource;
3415 configureTextTrackDisplay(AssumeNoVisibleChange);
3418 void HTMLMediaElement::stop()
3420 WTF_LOG(Media, "HTMLMediaElement::stop");
3423 userCancelledLoad();
3425 // Stop the playback without generating events
3427 setPausedInternal(true);
3430 renderer()->updateFromElement();
3432 stopPeriodicTimers();
3433 cancelPendingEventsAndCallbacks();
3435 m_asyncEventQueue->close();
3438 bool HTMLMediaElement::hasPendingActivity() const
3440 return (hasAudio() && isPlaying()) || m_asyncEventQueue->hasPendingEvents();
3443 void HTMLMediaElement::contextDestroyed()
3445 // With Oilpan the ExecutionContext is weakly referenced from the media
3446 // controller and so it will clear itself on destruction.
3448 if (m_mediaController)
3449 m_mediaController->clearExecutionContext();
3451 ActiveDOMObject::contextDestroyed();
3454 bool HTMLMediaElement::isFullscreen() const
3456 return FullscreenElementStack::isActiveFullScreenElement(this);
3459 void HTMLMediaElement::enterFullscreen()
3461 WTF_LOG(Media, "HTMLMediaElement::enterFullscreen");
3463 FullscreenElementStack::from(document()).requestFullScreenForElement(this, 0, FullscreenElementStack::ExemptIFrameAllowFullScreenRequirement);
3466 void HTMLMediaElement::exitFullscreen()
3468 WTF_LOG(Media, "HTMLMediaElement::exitFullscreen");
3470 FullscreenElementStack::from(document()).webkitCancelFullScreen();
3473 void HTMLMediaElement::didBecomeFullscreenElement()
3475 if (hasMediaControls())
3476 mediaControls()->enteredFullscreen();
3477 if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isHTMLVideoElement(*this))
3478 document().renderView()->compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree);
3481 void HTMLMediaElement::willStopBeingFullscreenElement()
3483 if (hasMediaControls())
3484 mediaControls()->exitedFullscreen();
3485 if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isHTMLVideoElement(*this))
3486 document().renderView()->compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree);
3489 blink::WebLayer* HTMLMediaElement::platformLayer() const
3494 bool HTMLMediaElement::hasClosedCaptions() const
3497 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
3498 if (m_textTracks->item(i)->readinessState() == TextTrack::FailedToLoad)
3501 if (m_textTracks->item(i)->kind() == TextTrack::captionsKeyword()
3502 || m_textTracks->item(i)->kind() == TextTrack::subtitlesKeyword())
3509 bool HTMLMediaElement::closedCaptionsVisible() const
3511 return m_closedCaptionsVisible;
3514 void HTMLMediaElement::updateTextTrackDisplay()
3516 WTF_LOG(Media, "HTMLMediaElement::updateTextTrackDisplay");
3518 if (!hasMediaControls() && !createMediaControls())
3521 mediaControls()->updateTextTrackDisplay();
3524 void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible)
3526 WTF_LOG(Media, "HTMLMediaElement::setClosedCaptionsVisible(%s)", boolString(closedCaptionVisible));
3528 if (!m_player || !hasClosedCaptions())
3531 m_closedCaptionsVisible = closedCaptionVisible;
3533 m_processingPreferenceChange = true;
3534 markCaptionAndSubtitleTracksAsUnconfigured();
3535 m_processingPreferenceChange = false;
3537 updateTextTrackDisplay();
3540 unsigned HTMLMediaElement::webkitAudioDecodedByteCount() const
3542 if (!webMediaPlayer())
3544 return webMediaPlayer()->audioDecodedByteCount();
3547 unsigned HTMLMediaElement::webkitVideoDecodedByteCount() const
3549 if (!webMediaPlayer())
3551 return webMediaPlayer()->videoDecodedByteCount();
3554 bool HTMLMediaElement::isURLAttribute(const Attribute& attribute) const
3556 return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute);
3559 void HTMLMediaElement::setShouldDelayLoadEvent(bool shouldDelay)
3561 if (m_shouldDelayLoadEvent == shouldDelay)
3564 WTF_LOG(Media, "HTMLMediaElement::setShouldDelayLoadEvent(%s)", boolString(shouldDelay));
3566 m_shouldDelayLoadEvent = shouldDelay;
3568 document().incrementLoadEventDelayCount();
3570 document().decrementLoadEventDelayCount();
3574 MediaControls* HTMLMediaElement::mediaControls() const
3576 return toMediaControls(userAgentShadowRoot()->firstChild());
3579 bool HTMLMediaElement::hasMediaControls() const
3581 if (ShadowRoot* userAgent = userAgentShadowRoot()) {
3582 Node* node = userAgent->firstChild();
3583 ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isMediaControls());
3590 bool HTMLMediaElement::createMediaControls()
3592 if (hasMediaControls())
3595 RefPtrWillBeRawPtr<MediaControls> mediaControls = MediaControls::create(*this);
3599 mediaControls->reset();
3601 mediaControls->enteredFullscreen();
3603 ensureUserAgentShadowRoot().appendChild(mediaControls);
3605 if (!controls() || !inDocument())
3606 mediaControls->hide();
3611 void HTMLMediaElement::configureMediaControls()
3613 if (!controls() || !inDocument()) {
3614 if (hasMediaControls())
3615 mediaControls()->hide();
3619 if (!hasMediaControls() && !createMediaControls())
3622 mediaControls()->reset();
3623 mediaControls()->show();
3626 void HTMLMediaElement::configureTextTrackDisplay(VisibilityChangeAssumption assumption)
3628 ASSERT(m_textTracks);
3629 WTF_LOG(Media, "HTMLMediaElement::configureTextTrackDisplay");
3631 if (m_processingPreferenceChange)
3634 bool haveVisibleTextTrack = false;
3635 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
3636 if (m_textTracks->item(i)->mode() == TextTrack::showingKeyword()) {
3637 haveVisibleTextTrack = true;
3642 if (assumption == AssumeNoVisibleChange
3643 && m_haveVisibleTextTrack == haveVisibleTextTrack) {
3644 updateActiveTextTrackCues(currentTime());
3647 m_haveVisibleTextTrack = haveVisibleTextTrack;
3648 m_closedCaptionsVisible = m_haveVisibleTextTrack;
3650 if (!m_haveVisibleTextTrack && !hasMediaControls())
3652 if (!hasMediaControls() && !createMediaControls())
3655 mediaControls()->changedClosedCaptionsVisibility();
3657 updateActiveTextTrackCues(currentTime());
3658 updateTextTrackDisplay();
3661 void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured()
3666 // Mark all tracks as not "configured" so that configureTextTracks()
3667 // will reconsider which tracks to display in light of new user preferences
3668 // (e.g. default tracks should not be displayed if the user has turned off
3669 // captions and non-default tracks should be displayed based on language
3670 // preferences if the user has turned captions on).
3671 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
3672 RefPtrWillBeRawPtr<TextTrack> textTrack = m_textTracks->item(i);
3673 String kind = textTrack->kind();
3675 if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword())
3676 textTrack->setHasBeenConfigured(false);
3678 configureTextTracks();
3681 void* HTMLMediaElement::preDispatchEventHandler(Event* event)
3683 if (event && event->type() == EventTypeNames::webkitfullscreenchange)
3684 configureMediaControls();
3689 void HTMLMediaElement::createMediaPlayer()
3691 #if ENABLE(WEB_AUDIO)
3692 if (m_audioSourceNode)
3693 m_audioSourceNode->lock();
3698 m_player = MediaPlayer::create(this);
3700 #if ENABLE(WEB_AUDIO)
3701 if (m_audioSourceNode) {
3702 // When creating the player, make sure its AudioSourceProvider knows about the client.
3703 if (audioSourceProvider())
3704 audioSourceProvider()->setClient(m_audioSourceNode);
3706 m_audioSourceNode->unlock();
3711 #if ENABLE(WEB_AUDIO)
3712 void HTMLMediaElement::setAudioSourceNode(AudioSourceProviderClient* sourceNode)
3714 m_audioSourceNode = sourceNode;
3716 if (m_audioSourceNode)
3717 m_audioSourceNode->lock();
3719 if (audioSourceProvider())
3720 audioSourceProvider()->setClient(m_audioSourceNode);
3722 if (m_audioSourceNode)
3723 m_audioSourceNode->unlock();
3726 AudioSourceProvider* HTMLMediaElement::audioSourceProvider()
3729 return m_player->audioSourceProvider();
3735 const AtomicString& HTMLMediaElement::mediaGroup() const
3737 return fastGetAttribute(mediagroupAttr);
3740 void HTMLMediaElement::setMediaGroup(const AtomicString& group)
3742 // When a media element is created with a mediagroup attribute, and when a media element's mediagroup
3743 // attribute is set, changed, or removed, the user agent must run the following steps:
3744 // 1. Let m [this] be the media element in question.
3745 // 2. Let m have no current media controller, if it currently has one.
3746 setControllerInternal(nullptr);
3748 // 3. If m's mediagroup attribute is being removed, then abort these steps.
3749 if (group.isNull() || group.isEmpty())
3752 // 4. If there is another media element whose Document is the same as m's Document (even if one or both
3753 // of these elements are not actually in the Document),
3754 WeakMediaElementSet elements = documentToElementSetMap().get(&document());
3755 for (WeakMediaElementSet::iterator i = elements.begin(); i != elements.end(); ++i) {
3759 // and which also has a mediagroup attribute, and whose mediagroup attribute has the same value as
3760 // the new value of m's mediagroup attribute,
3761 if ((*i)->mediaGroup() == group) {
3762 // then let controller be that media element's current media controller.
3763 setControllerInternal((*i)->controller());
3768 // Otherwise, let controller be a newly created MediaController.
3769 setControllerInternal(MediaController::create(Node::executionContext()));
3772 MediaController* HTMLMediaElement::controller() const
3774 return m_mediaController.get();
3777 void HTMLMediaElement::setController(PassRefPtrWillBeRawPtr<MediaController> controller)
3779 // 4.8.10.11.2 Media controllers: controller attribute.
3780 // On setting, it must first remove the element's mediagroup attribute, if any,
3781 removeAttribute(mediagroupAttr);
3782 // and then set the current media controller to the given value.
3783 setControllerInternal(controller);
3786 void HTMLMediaElement::setControllerInternal(PassRefPtrWillBeRawPtr<MediaController> controller)
3788 if (m_mediaController)
3789 m_mediaController->removeMediaElement(this);
3791 m_mediaController = controller;
3793 if (m_mediaController)
3794 m_mediaController->addMediaElement(this);
3797 void HTMLMediaElement::updateMediaController()
3799 if (m_mediaController)
3800 m_mediaController->reportControllerState();
3803 bool HTMLMediaElement::isBlocked() const
3805 // A media element is a blocked media element if its readyState attribute is in the
3806 // HAVE_NOTHING state, the HAVE_METADATA state, or the HAVE_CURRENT_DATA state,
3807 // or if the element has paused for user interaction or paused for in-band content.
3808 if (m_readyState <= HAVE_CURRENT_DATA)
3814 bool HTMLMediaElement::isBlockedOnMediaController() const
3816 if (!m_mediaController)
3819 // A media element is blocked on its media controller if the MediaController is a blocked
3820 // media controller,
3821 if (m_mediaController->isBlocked())
3824 // or if its media controller position is either before the media resource's earliest possible
3825 // position relative to the MediaController's timeline or after the end of the media resource
3826 // relative to the MediaController's timeline.
3827 double mediaControllerPosition = m_mediaController->currentTime();
3828 if (mediaControllerPosition < 0 || mediaControllerPosition > duration())
3834 void HTMLMediaElement::prepareMediaFragmentURI()
3836 MediaFragmentURIParser fragmentParser(m_currentSrc);
3837 double dur = duration();
3839 double start = fragmentParser.startTime();
3840 if (start != MediaFragmentURIParser::invalidTimeValue() && start > 0) {
3841 m_fragmentStartTime = start;
3842 if (m_fragmentStartTime > dur)
3843 m_fragmentStartTime = dur;
3845 m_fragmentStartTime = MediaPlayer::invalidTime();
3847 double end = fragmentParser.endTime();
3848 if (end != MediaFragmentURIParser::invalidTimeValue() && end > 0 && end > m_fragmentStartTime) {
3849 m_fragmentEndTime = end;
3850 if (m_fragmentEndTime > dur)
3851 m_fragmentEndTime = dur;
3853 m_fragmentEndTime = MediaPlayer::invalidTime();
3855 // FIXME: Add support for selecting tracks by ID with the Media Fragments track dimension.
3857 if (m_fragmentStartTime != MediaPlayer::invalidTime() && m_readyState < HAVE_FUTURE_DATA)
3861 void HTMLMediaElement::applyMediaFragmentURI()
3863 if (m_fragmentStartTime != MediaPlayer::invalidTime()) {
3864 m_sentEndEvent = false;
3865 UseCounter::count(document(), UseCounter::HTMLMediaElementSeekToFragmentStart);
3866 seek(m_fragmentStartTime, IGNORE_EXCEPTION);
3870 WebMediaPlayer::CORSMode HTMLMediaElement::corsMode() const
3872 const AtomicString& crossOriginMode = fastGetAttribute(crossoriginAttr);
3873 if (crossOriginMode.isNull())
3874 return WebMediaPlayer::CORSModeUnspecified;
3875 if (equalIgnoringCase(crossOriginMode, "use-credentials"))
3876 return WebMediaPlayer::CORSModeUseCredentials;
3877 return WebMediaPlayer::CORSModeAnonymous;
3880 void HTMLMediaElement::mediaPlayerSetWebLayer(blink::WebLayer* webLayer)
3882 if (webLayer == m_webLayer)
3885 // If either of the layers is null we need to enable or disable compositing. This is done by triggering a style recalc.
3886 if (!m_webLayer || !webLayer)
3887 setNeedsCompositingUpdate();
3890 GraphicsLayer::unregisterContentsLayer(m_webLayer);
3891 m_webLayer = webLayer;
3893 GraphicsLayer::registerContentsLayer(m_webLayer);
3897 void HTMLMediaElement::mediaPlayerMediaSourceOpened(blink::WebMediaSource* webMediaSource)
3899 m_mediaSource->setWebMediaSourceAndOpen(adoptPtr(webMediaSource));
3902 bool HTMLMediaElement::isInteractiveContent() const
3904 return fastHasAttribute(controlsAttr);
3907 void HTMLMediaElement::defaultEventHandler(Event* event)
3909 if (event->type() == EventTypeNames::focusin) {
3910 if (hasMediaControls())
3911 mediaControls()->mediaElementFocused();
3913 HTMLElement::defaultEventHandler(event);
3916 void HTMLMediaElement::trace(Visitor* visitor)
3918 visitor->trace(m_asyncEventQueue);
3919 visitor->trace(m_error);
3920 visitor->trace(m_currentSourceNode);
3921 visitor->trace(m_nextChildNodeToConsider);
3922 visitor->trace(m_audioTracks);
3923 visitor->trace(m_videoTracks);
3924 visitor->trace(m_textTracks);
3925 visitor->trace(m_textTracksWhenResourceSelectionBegan);
3926 visitor->trace(m_mediaController);
3927 #if ENABLE(WEB_AUDIO)
3928 visitor->registerWeakMembers<HTMLMediaElement, &HTMLMediaElement::clearWeakMembers>(this);
3930 WillBeHeapSupplementable<HTMLMediaElement>::trace(visitor);
3931 HTMLElement::trace(visitor);
3934 void HTMLMediaElement::createPlaceholderTracksIfNecessary()
3936 if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
3939 // Create a placeholder audio track if the player says it has audio but it didn't explicitly announce the tracks.
3940 if (hasAudio() && !audioTracks().length())
3941 addAudioTrack("audio", WebMediaPlayerClient::AudioTrackKindMain, "Audio Track", "", true);
3943 // Create a placeholder video track if the player says it has video but it didn't explicitly announce the tracks.
3944 if (webMediaPlayer() && webMediaPlayer()->hasVideo() && !videoTracks().length())
3945 addVideoTrack("video", WebMediaPlayerClient::VideoTrackKindMain, "Video Track", "", true);
3948 void HTMLMediaElement::selectInitialTracksIfNecessary()
3950 if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
3953 // Enable the first audio track if an audio track hasn't been enabled yet.
3954 if (audioTracks().length() > 0 && !audioTracks().hasEnabledTrack())
3955 audioTracks().anonymousIndexedGetter(0)->setEnabled(true);
3957 // Select the first video track if a video track hasn't been selected yet.
3958 if (videoTracks().length() > 0 && videoTracks().selectedIndex() == -1)
3959 videoTracks().anonymousIndexedGetter(0)->setSelected(true);
3962 #if ENABLE(WEB_AUDIO)
3963 void HTMLMediaElement::clearWeakMembers(Visitor* visitor)
3965 if (!visitor->isAlive(m_audioSourceNode) && audioSourceProvider())
3966 audioSourceProvider()->setClient(0);