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.
28 #include "HTMLMediaElement.h"
30 #include "ApplicationCacheHost.h"
31 #include "ApplicationCacheResource.h"
32 #include "Attribute.h"
34 #include "ChromeClient.h"
35 #include "ClientRect.h"
36 #include "ClientRectList.h"
37 #include "ContentSecurityPolicy.h"
38 #include "ContentType.h"
39 #include "CSSPropertyNames.h"
40 #include "CSSValueKeywords.h"
41 #include "DiagnosticLoggingKeys.h"
42 #include "DocumentLoader.h"
43 #include "ElementIterator.h"
45 #include "EventNames.h"
46 #include "ExceptionCode.h"
47 #include "ExceptionCodePlaceholder.h"
49 #include "FrameLoader.h"
50 #include "FrameLoaderClient.h"
51 #include "FrameView.h"
52 #include "HTMLDocument.h"
53 #include "HTMLNames.h"
54 #include "HTMLSourceElement.h"
55 #include "HTMLVideoElement.h"
58 #include "MediaController.h"
59 #include "MediaControls.h"
60 #include "MediaDocument.h"
61 #include "MediaError.h"
62 #include "MediaFragmentURIParser.h"
63 #include "MediaKeyError.h"
64 #include "MediaKeyEvent.h"
65 #include "MediaList.h"
66 #include "MediaPlayer.h"
67 #include "MediaQueryEvaluator.h"
68 #include "MouseEvent.h"
69 #include "MIMETypeRegistry.h"
71 #include "PageActivityAssertionToken.h"
72 #include "PageGroup.h"
73 #include "RenderVideo.h"
74 #include "RenderView.h"
75 #include "ScriptController.h"
76 #include "SecurityPolicy.h"
78 #include "ShadowRoot.h"
79 #include "TimeRanges.h"
81 #include <runtime/Uint8Array.h>
82 #include <wtf/CurrentTime.h>
83 #include <wtf/MathExtras.h>
85 #include <wtf/text/CString.h>
87 #if USE(ACCELERATED_COMPOSITING)
88 #include "RenderLayerCompositor.h"
91 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
92 #include "RenderEmbeddedObject.h"
96 #if ENABLE(VIDEO_TRACK)
97 #include "AudioTrackList.h"
98 #include "AudioTrackPrivate.h"
99 #include "CaptionUserPreferences.h"
100 #include "HTMLTrackElement.h"
101 #include "InbandGenericTextTrack.h"
102 #include "InbandTextTrack.h"
103 #include "InbandTextTrackPrivate.h"
104 #include "InbandWebVTTTextTrack.h"
105 #include "RuntimeEnabledFeatures.h"
106 #include "TextTrackCueList.h"
107 #include "TextTrackList.h"
108 #include "VideoTrackList.h"
109 #include "VideoTrackPrivate.h"
112 #if ENABLE(WEB_AUDIO)
113 #include "AudioSourceProvider.h"
114 #include "MediaElementAudioSourceNode.h"
118 #include "DisplaySleepDisabler.h"
121 #if ENABLE(MEDIA_SOURCE)
122 #include "HTMLMediaSource.h"
125 #if ENABLE(MEDIA_STREAM)
126 #include "MediaStreamRegistry.h"
129 #if ENABLE(ENCRYPTED_MEDIA_V2)
130 #include "MediaKeyNeededEvent.h"
131 #include "MediaKeys.h"
134 #if USE(PLATFORM_TEXT_TRACK_MENU)
135 #include "PlatformTextTrack.h"
138 #if USE(AUDIO_SESSION)
139 #include "AudioSessionManager.h"
146 static void setFlags(unsigned& value, unsigned flags)
151 static void clearFlags(unsigned& value, unsigned flags)
157 static String urlForLoggingMedia(const KURL& url)
159 static const unsigned maximumURLLengthForLogging = 128;
161 if (url.string().length() < maximumURLLengthForLogging)
163 return url.string().substring(0, maximumURLLengthForLogging) + "...";
166 static const char* boolString(bool val)
168 return val ? "true" : "false";
172 #ifndef LOG_MEDIA_EVENTS
173 // Default to not logging events because so many are generated they can overwhelm the rest of
175 #define LOG_MEDIA_EVENTS 0
178 #ifndef LOG_CACHED_TIME_WARNINGS
179 // Default to not logging warnings about excessive drift in the cached media time because it adds a
180 // fair amount of overhead and logging.
181 #define LOG_CACHED_TIME_WARNINGS 0
184 #if ENABLE(MEDIA_SOURCE)
185 // URL protocol used to signal that the media source API is being used.
186 static const char* mediaSourceBlobProtocol = "blob";
189 using namespace HTMLNames;
192 typedef HashMap<Document*, HashSet<HTMLMediaElement*> > DocumentElementSetMap;
193 static DocumentElementSetMap& documentToElementSetMap()
195 DEFINE_STATIC_LOCAL(DocumentElementSetMap, map, ());
199 static void addElementToDocumentMap(HTMLMediaElement& element, Document& document)
201 DocumentElementSetMap& map = documentToElementSetMap();
202 HashSet<HTMLMediaElement*> set = map.take(&document);
204 map.add(&document, set);
207 static void removeElementFromDocumentMap(HTMLMediaElement& element, Document& document)
209 DocumentElementSetMap& map = documentToElementSetMap();
210 HashSet<HTMLMediaElement*> set = map.take(&document);
211 set.remove(&element);
213 map.add(&document, set);
216 #if ENABLE(ENCRYPTED_MEDIA)
217 static ExceptionCode exceptionCodeForMediaKeyException(MediaPlayer::MediaKeyException exception)
220 case MediaPlayer::NoError:
222 case MediaPlayer::InvalidPlayerState:
223 return INVALID_STATE_ERR;
224 case MediaPlayer::KeySystemNotSupported:
225 return NOT_SUPPORTED_ERR;
228 ASSERT_NOT_REACHED();
229 return INVALID_STATE_ERR;
233 #if ENABLE(VIDEO_TRACK)
234 class TrackDisplayUpdateScope {
236 TrackDisplayUpdateScope(HTMLMediaElement* mediaElement)
238 m_mediaElement = mediaElement;
239 m_mediaElement->beginIgnoringTrackDisplayUpdateRequests();
241 ~TrackDisplayUpdateScope()
243 ASSERT(m_mediaElement);
244 m_mediaElement->endIgnoringTrackDisplayUpdateRequests();
248 HTMLMediaElement* m_mediaElement;
252 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document, bool createdByParser)
253 : HTMLElement(tagName, document)
254 , ActiveDOMObject(&document)
255 , m_loadTimer(this, &HTMLMediaElement::loadTimerFired)
256 , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired)
257 , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired)
258 , m_playedTimeRanges()
259 , m_asyncEventQueue(*this)
260 , m_playbackRate(1.0f)
261 , m_defaultPlaybackRate(1.0f)
262 , m_webkitPreservesPitch(true)
263 , m_networkState(NETWORK_EMPTY)
264 , m_readyState(HAVE_NOTHING)
265 , m_readyStateMaximum(HAVE_NOTHING)
267 , m_volumeInitialized(false)
269 , m_previousProgressTime(numeric_limits<double>::max())
270 , m_clockTimeAtLastUpdateEvent(0)
271 , m_lastTimeUpdateEventMovieTime(numeric_limits<double>::max())
272 , m_loadState(WaitingForSource)
273 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
276 , m_restrictions(RequireUserGestureForFullscreenRestriction | RequirePageConsentToLoadMediaRestriction)
277 , m_preload(MediaPlayer::Auto)
278 , m_displayMode(Unknown)
279 , m_processingMediaPlayerCallback(0)
280 , m_cachedTime(MediaPlayer::invalidTime())
281 , m_clockTimeAtLastCachedTimeUpdate(0)
282 , m_minimumClockTimeToUpdateCachedTime(0)
283 , m_fragmentStartTime(MediaPlayer::invalidTime())
284 , m_fragmentEndTime(MediaPlayer::invalidTime())
285 , m_pendingActionFlags(0)
287 , m_isWaitingUntilMediaCanStart(false)
288 , m_shouldDelayLoadEvent(false)
289 , m_haveFiredLoadedData(false)
290 , m_inActiveDocument(true)
291 , m_autoplaying(true)
295 , m_sentStalledEvent(false)
296 , m_sentEndEvent(false)
297 , m_pausedInternal(false)
298 , m_sendProgressEvents(true)
299 , m_isFullscreen(false)
300 , m_closedCaptionsVisible(false)
301 , m_webkitLegacyClosedCaptionOverride(false)
302 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
303 , m_needWidgetUpdate(false)
305 , m_dispatchingCanPlayEvent(false)
306 , m_loadInitiatedByUserGesture(false)
307 , m_completelyLoaded(false)
308 , m_havePreparedToPlay(false)
309 , m_parsingInProgress(createdByParser)
310 #if ENABLE(VIDEO_TRACK)
311 , m_tracksAreReady(true)
312 , m_haveVisibleTextTrack(false)
313 , m_processingPreferenceChange(false)
314 , m_lastTextTrackUpdateTime(-1)
315 , m_captionDisplayMode(CaptionUserPreferences::Automatic)
319 , m_ignoreTrackDisplayUpdate(0)
321 #if ENABLE(WEB_AUDIO)
322 , m_audioSourceNode(0)
324 #if USE(AUDIO_SESSION)
325 , m_audioSessionManagerToken(AudioSessionManagerToken::create(tagName == videoTag ? AudioSessionManager::Video : AudioSessionManager::Audio))
327 , m_reportedExtraMemoryCost(0)
329 LOG(Media, "HTMLMediaElement::HTMLMediaElement");
330 setHasCustomStyleResolveCallbacks();
332 document.registerForMediaVolumeCallbacks(this);
333 document.registerForPrivateBrowsingStateChangedCallbacks(this);
335 if (document.settings() && document.settings()->mediaPlaybackRequiresUserGesture()) {
336 addBehaviorRestriction(RequireUserGestureForRateChangeRestriction);
337 addBehaviorRestriction(RequireUserGestureForLoadRestriction);
340 addElementToDocumentMap(*this, document);
342 #if ENABLE(VIDEO_TRACK)
343 document.registerForCaptionPreferencesChangedCallbacks(this);
345 m_captionDisplayMode = document.page()->group().captionPreferences()->captionDisplayMode();
349 HTMLMediaElement::~HTMLMediaElement()
351 LOG(Media, "HTMLMediaElement::~HTMLMediaElement");
353 m_asyncEventQueue.close();
355 if (m_isWaitingUntilMediaCanStart)
356 document().removeMediaCanStartListener(this);
357 setShouldDelayLoadEvent(false);
358 document().unregisterForMediaVolumeCallbacks(this);
359 document().unregisterForPrivateBrowsingStateChangedCallbacks(this);
360 #if ENABLE(VIDEO_TRACK)
361 document().unregisterForCaptionPreferencesChangedCallbacks(this);
363 m_audioTracks->clearElement();
364 for (unsigned i = 0; i < m_audioTracks->length(); ++i)
365 m_audioTracks->item(i)->clearClient();
368 m_textTracks->clearElement();
370 for (unsigned i = 0; i < m_textTracks->length(); ++i)
371 m_textTracks->item(i)->clearClient();
374 m_videoTracks->clearElement();
375 for (unsigned i = 0; i < m_videoTracks->length(); ++i)
376 m_videoTracks->item(i)->clearClient();
380 if (m_mediaController)
381 m_mediaController->removeMediaElement(this);
383 #if ENABLE(MEDIA_SOURCE)
387 #if ENABLE(ENCRYPTED_MEDIA_V2)
391 removeElementFromDocumentMap(*this, document());
393 m_completelyLoaded = true;
395 m_player->clearMediaPlayerClient();
398 void HTMLMediaElement::didMoveToNewDocument(Document* oldDocument)
400 if (m_isWaitingUntilMediaCanStart) {
402 oldDocument->removeMediaCanStartListener(this);
403 document().addMediaCanStartListener(this);
406 if (m_shouldDelayLoadEvent) {
408 oldDocument->decrementLoadEventDelayCount();
409 document().incrementLoadEventDelayCount();
413 oldDocument->unregisterForMediaVolumeCallbacks(this);
414 removeElementFromDocumentMap(*this, *oldDocument);
417 document().registerForMediaVolumeCallbacks(this);
418 addElementToDocumentMap(*this, document());
420 HTMLElement::didMoveToNewDocument(oldDocument);
423 bool HTMLMediaElement::hasCustomFocusLogic() const
428 bool HTMLMediaElement::supportsFocus() const
430 if (document().isMediaDocument())
433 // If no controls specified, we should still be able to focus the element if it has tabIndex.
434 return controls() || HTMLElement::supportsFocus();
437 bool HTMLMediaElement::isMouseFocusable() const
442 void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
444 if (name == srcAttr) {
445 // Trigger a reload, as long as the 'src' attribute is present.
446 if (!value.isNull()) {
447 clearMediaPlayer(LoadMediaResource);
448 scheduleDelayedAction(LoadMediaResource);
450 } else if (name == controlsAttr)
451 configureMediaControls();
453 else if (name == loopAttr)
454 updateDisableSleep();
456 else if (name == preloadAttr) {
457 if (equalIgnoringCase(value, "none"))
458 m_preload = MediaPlayer::None;
459 else if (equalIgnoringCase(value, "metadata"))
460 m_preload = MediaPlayer::MetaData;
462 // The spec does not define an "invalid value default" but "auto" is suggested as the
463 // "missing value default", so use it for everything except "none" and "metadata"
464 m_preload = MediaPlayer::Auto;
467 // The attribute must be ignored if the autoplay attribute is present
468 if (!autoplay() && m_player)
469 m_player->setPreload(m_preload);
471 } else if (name == mediagroupAttr)
472 setMediaGroup(value);
473 else if (name == onabortAttr)
474 setAttributeEventListener(eventNames().abortEvent, name, value);
475 else if (name == onbeforeloadAttr)
476 setAttributeEventListener(eventNames().beforeloadEvent, name, value);
477 else if (name == oncanplayAttr)
478 setAttributeEventListener(eventNames().canplayEvent, name, value);
479 else if (name == oncanplaythroughAttr)
480 setAttributeEventListener(eventNames().canplaythroughEvent, name, value);
481 else if (name == ondurationchangeAttr)
482 setAttributeEventListener(eventNames().durationchangeEvent, name, value);
483 else if (name == onemptiedAttr)
484 setAttributeEventListener(eventNames().emptiedEvent, name, value);
485 else if (name == onendedAttr)
486 setAttributeEventListener(eventNames().endedEvent, name, value);
487 else if (name == onerrorAttr)
488 setAttributeEventListener(eventNames().errorEvent, name, value);
489 else if (name == onloadeddataAttr)
490 setAttributeEventListener(eventNames().loadeddataEvent, name, value);
491 else if (name == onloadedmetadataAttr)
492 setAttributeEventListener(eventNames().loadedmetadataEvent, name, value);
493 else if (name == onloadstartAttr)
494 setAttributeEventListener(eventNames().loadstartEvent, name, value);
495 else if (name == onpauseAttr)
496 setAttributeEventListener(eventNames().pauseEvent, name, value);
497 else if (name == onplayAttr)
498 setAttributeEventListener(eventNames().playEvent, name, value);
499 else if (name == onplayingAttr)
500 setAttributeEventListener(eventNames().playingEvent, name, value);
501 else if (name == onprogressAttr)
502 setAttributeEventListener(eventNames().progressEvent, name, value);
503 else if (name == onratechangeAttr)
504 setAttributeEventListener(eventNames().ratechangeEvent, name, value);
505 else if (name == onseekedAttr)
506 setAttributeEventListener(eventNames().seekedEvent, name, value);
507 else if (name == onseekingAttr)
508 setAttributeEventListener(eventNames().seekingEvent, name, value);
509 else if (name == onstalledAttr)
510 setAttributeEventListener(eventNames().stalledEvent, name, value);
511 else if (name == onsuspendAttr)
512 setAttributeEventListener(eventNames().suspendEvent, name, value);
513 else if (name == ontimeupdateAttr)
514 setAttributeEventListener(eventNames().timeupdateEvent, name, value);
515 else if (name == onvolumechangeAttr)
516 setAttributeEventListener(eventNames().volumechangeEvent, name, value);
517 else if (name == onwaitingAttr)
518 setAttributeEventListener(eventNames().waitingEvent, name, value);
519 else if (name == onwebkitbeginfullscreenAttr)
520 setAttributeEventListener(eventNames().webkitbeginfullscreenEvent, name, value);
521 else if (name == onwebkitendfullscreenAttr)
522 setAttributeEventListener(eventNames().webkitendfullscreenEvent, name, value);
524 HTMLElement::parseAttribute(name, value);
527 void HTMLMediaElement::finishParsingChildren()
529 HTMLElement::finishParsingChildren();
530 m_parsingInProgress = false;
532 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
533 document().updateStyleIfNeeded();
534 createMediaPlayerProxy();
537 #if ENABLE(VIDEO_TRACK)
538 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
541 auto trackDescendants = descendantsOfType<HTMLTrackElement>(this);
542 if (trackDescendants.begin() != trackDescendants.end())
543 scheduleDelayedAction(ConfigureTextTracks);
547 bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style)
549 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
553 return controls() && HTMLElement::rendererIsNeeded(style);
557 RenderElement* HTMLMediaElement::createRenderer(RenderArena& arena, RenderStyle&)
559 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
560 // Setup the renderer if we already have a proxy widget.
561 RenderEmbeddedObject* mediaRenderer = new (arena) RenderEmbeddedObject(*this);
563 mediaRenderer->setWidget(m_proxyWidget);
565 if (Frame* frame = document().frame())
566 frame->loader().client().showMediaPlayerProxyPlugin(m_proxyWidget.get());
568 return mediaRenderer;
570 return new (arena) RenderMedia(*this);
574 bool HTMLMediaElement::childShouldCreateRenderer(const Node* child) const
576 if (!hasMediaControls())
578 // <media> doesn't allow its content, including shadow subtree, to
579 // be rendered. So this should return false for most of the children.
580 // One exception is a shadow tree built for rendering controls which should be visible.
581 // So we let them go here by comparing its subtree root with one of the controls.
582 return mediaControls()->treeScope() == child->treeScope()
583 && hasShadowRootParent(child)
584 && HTMLElement::childShouldCreateRenderer(child);
587 Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode* insertionPoint)
589 LOG(Media, "HTMLMediaElement::insertedInto");
591 HTMLElement::insertedInto(insertionPoint);
592 if (insertionPoint->inDocument()) {
593 m_inActiveDocument = true;
595 if (m_networkState == NETWORK_EMPTY && !getAttribute(srcAttr).isEmpty())
596 scheduleDelayedAction(LoadMediaResource);
599 configureMediaControls();
600 return InsertionDone;
603 void HTMLMediaElement::removedFrom(ContainerNode* insertionPoint)
605 LOG(Media, "HTMLMediaElement::removedFrom");
607 m_inActiveDocument = false;
608 if (insertionPoint->inDocument()) {
609 configureMediaControls();
610 if (m_networkState > NETWORK_EMPTY)
616 JSC::VM* vm = JSDOMWindowBase::commonVM();
617 JSC::JSLockHolder lock(vm);
619 size_t extraMemoryCost = m_player->extraMemoryCost();
620 size_t extraMemoryCostDelta = extraMemoryCost - m_reportedExtraMemoryCost;
621 m_reportedExtraMemoryCost = extraMemoryCost;
623 if (extraMemoryCostDelta > 0)
624 vm->heap.reportExtraMemoryCost(extraMemoryCostDelta);
628 HTMLElement::removedFrom(insertionPoint);
631 void HTMLMediaElement::willAttachRenderers()
635 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
636 m_needWidgetUpdate = true;
640 void HTMLMediaElement::didAttachRenderers()
643 renderer()->updateFromElement();
644 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
645 else if (m_proxyWidget) {
646 if (Frame* frame = document().frame())
647 frame->loader().client().hideMediaPlayerProxyPlugin(m_proxyWidget.get());
652 void HTMLMediaElement::didRecalcStyle(Style::Change)
655 renderer()->updateFromElement();
658 void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType)
660 LOG(Media, "HTMLMediaElement::scheduleLoad");
662 if ((actionType & LoadMediaResource) && !(m_pendingActionFlags & LoadMediaResource)) {
663 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
664 createMediaPlayerProxy();
668 setFlags(m_pendingActionFlags, LoadMediaResource);
671 #if ENABLE(VIDEO_TRACK)
672 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled() && (actionType & ConfigureTextTracks))
673 setFlags(m_pendingActionFlags, ConfigureTextTracks);
676 #if USE(PLATFORM_TEXT_TRACK_MENU)
677 if (actionType & TextTrackChangesNotification)
678 setFlags(m_pendingActionFlags, TextTrackChangesNotification);
681 m_loadTimer.startOneShot(0);
684 void HTMLMediaElement::scheduleNextSourceChild()
686 // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
687 setFlags(m_pendingActionFlags, LoadMediaResource);
688 m_loadTimer.startOneShot(0);
691 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
694 LOG(Media, "HTMLMediaElement::scheduleEvent - scheduling '%s'", eventName.string().ascii().data());
696 RefPtr<Event> event = Event::create(eventName, false, true);
698 // Don't set the event target, the event queue will set it in GenericEventQueue::timerFired and setting it here
699 // will trigger an ASSERT if this element has been marked for deletion.
701 m_asyncEventQueue.enqueueEvent(event.release());
704 void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*)
706 Ref<HTMLMediaElement> protect(*this); // loadNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations.
708 #if ENABLE(VIDEO_TRACK)
709 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled() && (m_pendingActionFlags & ConfigureTextTracks))
710 configureTextTracks();
713 if (m_pendingActionFlags & LoadMediaResource) {
714 if (m_loadState == LoadingFromSourceElement)
715 loadNextSourceChild();
720 #if USE(PLATFORM_TEXT_TRACK_MENU)
721 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled() && (m_pendingActionFlags & TextTrackChangesNotification))
722 notifyMediaPlayerOfTextTrackChanges();
725 m_pendingActionFlags = 0;
728 PassRefPtr<MediaError> HTMLMediaElement::error() const
733 void HTMLMediaElement::setSrc(const String& url)
735 setAttribute(srcAttr, url);
738 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
740 return m_networkState;
743 String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySystem, const KURL& url) const
745 MediaPlayer::SupportsType support = MediaPlayer::supportsType(ContentType(mimeType), keySystem, url, this);
751 case MediaPlayer::IsNotSupported:
752 canPlay = emptyString();
754 case MediaPlayer::MayBeSupported:
755 canPlay = ASCIILiteral("maybe");
757 case MediaPlayer::IsSupported:
758 canPlay = ASCIILiteral("probably");
762 LOG(Media, "HTMLMediaElement::canPlayType(%s, %s, %s) -> %s", mimeType.utf8().data(), keySystem.utf8().data(), url.stringCenterEllipsizedToLength().utf8().data(), canPlay.utf8().data());
767 void HTMLMediaElement::load()
769 Ref<HTMLMediaElement> protect(*this); // loadInternal may result in a 'beforeload' event, which can make arbitrary DOM mutations.
771 LOG(Media, "HTMLMediaElement::load()");
773 if (userGestureRequiredForLoad() && !ScriptController::processingUserGesture())
776 m_loadInitiatedByUserGesture = ScriptController::processingUserGesture();
777 if (m_loadInitiatedByUserGesture)
778 removeBehaviorsRestrictionsAfterFirstUserGesture();
784 void HTMLMediaElement::prepareForLoad()
786 LOG(Media, "HTMLMediaElement::prepareForLoad");
788 // Perform the cleanup required for the resource load algorithm to run.
789 stopPeriodicTimers();
791 m_sentEndEvent = false;
792 m_sentStalledEvent = false;
793 m_haveFiredLoadedData = false;
794 m_completelyLoaded = false;
795 m_havePreparedToPlay = false;
796 m_displayMode = Unknown;
798 // 1 - Abort any already-running instance of the resource selection algorithm for this element.
799 m_loadState = WaitingForSource;
800 m_currentSourceNode = 0;
802 // 2 - If there are any tasks from the media element's media element event task source in
803 // one of the task queues, then remove those tasks.
804 cancelPendingEventsAndCallbacks();
806 // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
807 // a task to fire a simple event named abort at the media element.
808 if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
809 scheduleEvent(eventNames().abortEvent);
811 #if ENABLE(MEDIA_SOURCE)
815 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
819 m_player->cancelLoad();
821 createMediaPlayerProxy();
824 // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
825 if (m_networkState != NETWORK_EMPTY) {
826 m_networkState = NETWORK_EMPTY;
827 m_readyState = HAVE_NOTHING;
828 m_readyStateMaximum = HAVE_NOTHING;
832 invalidateCachedTime();
833 scheduleEvent(eventNames().emptiedEvent);
834 updateMediaController();
835 #if ENABLE(VIDEO_TRACK)
836 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
837 updateActiveTextTrackCues(0);
841 // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
842 setPlaybackRate(defaultPlaybackRate());
844 // 6 - Set the error attribute to null and the autoplaying flag to true.
846 m_autoplaying = true;
848 // 7 - Invoke the media element's resource selection algorithm.
850 // 8 - Note: Playback of any previously playing media resource for this element stops.
852 // The resource selection algorithm
853 // 1 - Set the networkState to NETWORK_NO_SOURCE
854 m_networkState = NETWORK_NO_SOURCE;
856 // 2 - Asynchronously await a stable state.
858 m_playedTimeRanges = TimeRanges::create();
861 // The spec doesn't say to block the load event until we actually run the asynchronous section
862 // algorithm, but do it now because we won't start that until after the timer fires and the
863 // event may have already fired by then.
864 if (m_preload != MediaPlayer::None)
865 setShouldDelayLoadEvent(true);
867 configureMediaControls();
870 void HTMLMediaElement::loadInternal()
872 // Some of the code paths below this function dispatch the BeforeLoad event. This ASSERT helps
873 // us catch those bugs more quickly without needing all the branches to align to actually
874 // trigger the event.
875 ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden());
877 // If we can't start a load right away, start it later.
878 Page* page = document().page();
879 if (pageConsentRequiredForLoad() && page && !page->canStartMedia()) {
880 setShouldDelayLoadEvent(false);
881 if (m_isWaitingUntilMediaCanStart)
883 document().addMediaCanStartListener(this);
884 m_isWaitingUntilMediaCanStart = true;
888 clearFlags(m_pendingActionFlags, LoadMediaResource);
890 // Once the page has allowed an element to load media, it is free to load at will. This allows a
891 // playlist that starts in a foreground tab to continue automatically if the tab is subsequently
892 // put in the the background.
893 removeBehaviorRestriction(RequirePageConsentToLoadMediaRestriction);
895 #if ENABLE(VIDEO_TRACK)
896 if (hasMediaControls())
897 mediaControls()->changedClosedCaptionsVisibility();
899 // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the
900 // disabled state when the element's resource selection algorithm last started".
901 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) {
902 m_textTracksWhenResourceSelectionBegan.clear();
904 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
905 TextTrack* track = m_textTracks->item(i);
906 if (track->mode() != TextTrack::disabledKeyword())
907 m_textTracksWhenResourceSelectionBegan.append(track);
913 selectMediaResource();
916 void HTMLMediaElement::selectMediaResource()
918 LOG(Media, "HTMLMediaElement::selectMediaResource");
920 enum Mode { attribute, children };
922 // 3 - If the media element has a src attribute, then let mode be attribute.
923 Mode mode = attribute;
924 if (!fastHasAttribute(srcAttr)) {
925 // Otherwise, if the media element does not have a src attribute but has a source
926 // element child, then let mode be children and let candidate be the first such
927 // source element child in tree order.
928 auto source = childrenOfType<HTMLSourceElement>(this).begin();
929 if (source != childrenOfType<HTMLSourceElement>(this).end()) {
931 m_nextChildNodeToConsider = &*source;
932 m_currentSourceNode = 0;
934 // Otherwise the media element has neither a src attribute nor a source element
935 // child: set the networkState to NETWORK_EMPTY, and abort these steps; the
936 // synchronous section ends.
937 m_loadState = WaitingForSource;
938 setShouldDelayLoadEvent(false);
939 m_networkState = NETWORK_EMPTY;
941 LOG(Media, "HTMLMediaElement::selectMediaResource, nothing to load");
946 // 4 - Set the media element's delaying-the-load-event flag to true (this delays the load event),
947 // and set its networkState to NETWORK_LOADING.
948 setShouldDelayLoadEvent(true);
949 m_networkState = NETWORK_LOADING;
951 // 5 - Queue a task to fire a simple event named loadstart at the media element.
952 scheduleEvent(eventNames().loadstartEvent);
954 // 6 - If mode is attribute, then run these substeps
955 if (mode == attribute) {
956 m_loadState = LoadingFromSrcAttr;
958 // If the src attribute's value is the empty string ... jump down to the failed step below
959 KURL mediaURL = getNonEmptyURLAttribute(srcAttr);
960 if (mediaURL.isEmpty()) {
961 mediaLoadingFailed(MediaPlayer::FormatError);
962 LOG(Media, "HTMLMediaElement::selectMediaResource, empty 'src'");
966 if (!isSafeToLoadURL(mediaURL, Complain) || !dispatchBeforeLoadEvent(mediaURL.string())) {
967 mediaLoadingFailed(MediaPlayer::FormatError);
971 // No type or key system information is available when the url comes
972 // from the 'src' attribute so MediaPlayer
973 // will have to pick a media engine based on the file extension.
974 ContentType contentType((String()));
975 loadResource(mediaURL, contentType, String());
976 LOG(Media, "HTMLMediaElement::selectMediaResource, using 'src' attribute url");
980 // Otherwise, the source elements will be used
981 loadNextSourceChild();
984 void HTMLMediaElement::loadNextSourceChild()
986 ContentType contentType((String()));
988 KURL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain);
989 if (!mediaURL.isValid()) {
990 waitForSourceChange();
994 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
995 // Recreate the media player for the new url
999 m_loadState = LoadingFromSourceElement;
1000 loadResource(mediaURL, contentType, keySystem);
1003 static KURL createFileURLForApplicationCacheResource(const String& path)
1005 // KURL should have a function to create a url from a path, but it does not. This function
1006 // is not suitable because KURL::setPath uses encodeWithURLEscapeSequences, which it notes
1007 // does not correctly escape '#' and '?'. This function works for our purposes because
1008 // app cache media files are always created with encodeForFileName(createCanonicalUUIDString()).
1010 #if USE(CF) && PLATFORM(WIN)
1011 RetainPtr<CFURLRef> cfURL = adoptCF(CFURLCreateWithFileSystemPath(0, path.createCFString().get(), kCFURLWindowsPathStyle, false));
1012 KURL url(cfURL.get());
1016 url.setProtocol(ASCIILiteral("file"));
1022 void HTMLMediaElement::loadResource(const KURL& initialURL, ContentType& contentType, const String& keySystem)
1024 ASSERT(isSafeToLoadURL(initialURL, Complain));
1026 LOG(Media, "HTMLMediaElement::loadResource(%s, %s, %s)", urlForLoggingMedia(initialURL).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data());
1028 Frame* frame = document().frame();
1030 mediaLoadingFailed(MediaPlayer::FormatError);
1034 KURL url = initialURL;
1035 if (!frame->loader().willLoadMediaElementURL(url)) {
1036 mediaLoadingFailed(MediaPlayer::FormatError);
1040 // The resource fetch algorithm
1041 m_networkState = NETWORK_LOADING;
1043 // If the url should be loaded from the application cache, pass the url of the cached file
1044 // to the media engine.
1045 ApplicationCacheHost* cacheHost = frame->loader().documentLoader()->applicationCacheHost();
1046 ApplicationCacheResource* resource = 0;
1047 if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource)) {
1048 // Resources that are not present in the manifest will always fail to load (at least, after the
1049 // cache has been primed the first time), making the testing of offline applications simpler.
1050 if (!resource || resource->path().isEmpty()) {
1051 mediaLoadingFailed(MediaPlayer::NetworkError);
1056 // Set m_currentSrc *before* changing to the cache url, the fact that we are loading from the app
1057 // cache is an internal detail not exposed through the media element API.
1061 url = createFileURLForApplicationCacheResource(resource->path());
1062 LOG(Media, "HTMLMediaElement::loadResource - will load from app cache -> %s", urlForLoggingMedia(url).utf8().data());
1065 LOG(Media, "HTMLMediaElement::loadResource - m_currentSrc -> %s", urlForLoggingMedia(m_currentSrc).utf8().data());
1067 #if ENABLE(MEDIA_STREAM)
1068 if (MediaStreamRegistry::registry().lookupMediaStreamDescriptor(url.string()))
1069 removeBehaviorRestriction(RequireUserGestureForRateChangeRestriction);
1072 if (m_sendProgressEvents)
1073 startProgressEventTimer();
1075 Settings* settings = document().settings();
1076 bool privateMode = !settings || settings->privateBrowsingEnabled();
1077 m_player->setPrivateBrowsingMode(privateMode);
1079 // Reset display mode to force a recalculation of what to show because we are resetting the player.
1080 setDisplayMode(Unknown);
1083 m_player->setPreload(m_preload);
1084 m_player->setPreservesPitch(m_webkitPreservesPitch);
1086 if (fastHasAttribute(mutedAttr))
1090 #if ENABLE(MEDIA_SOURCE)
1091 ASSERT(!m_mediaSource);
1093 if (url.protocolIs(mediaSourceBlobProtocol))
1094 m_mediaSource = HTMLMediaSource::lookup(url.string());
1096 if (m_mediaSource) {
1097 if (m_mediaSource->attachToElement())
1098 m_player->load(url, m_mediaSource);
1100 // Forget our reference to the MediaSource, so we leave it alone
1101 // while processing remainder of load failure.
1103 mediaLoadingFailed(MediaPlayer::FormatError);
1107 if (!m_player->load(url, contentType, keySystem))
1108 mediaLoadingFailed(MediaPlayer::FormatError);
1110 // If there is no poster to display, allow the media engine to render video frames as soon as
1111 // they are available.
1112 updateDisplayState();
1115 renderer()->updateFromElement();
1118 #if ENABLE(VIDEO_TRACK)
1119 static bool trackIndexCompare(TextTrack* a,
1122 return a->trackIndex() - b->trackIndex() < 0;
1125 static bool eventTimeCueCompare(const std::pair<double, TextTrackCue*>& a,
1126 const std::pair<double, TextTrackCue*>& b)
1128 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1130 if (a.first != b.first)
1131 return a.first - b.first < 0;
1133 // If the cues belong to different text tracks, it doesn't make sense to
1134 // compare the two tracks by the relative cue order, so return the relative
1136 if (a.second->track() != b.second->track())
1137 return trackIndexCompare(a.second->track(), b.second->track());
1139 // 12 - Further sort tasks in events that have the same time by the
1140 // relative text track cue order of the text track cues associated
1141 // with these tasks.
1142 return a.second->cueIndex() - b.second->cueIndex() < 0;
1145 static bool compareCueInterval(const CueInterval& one, const CueInterval& two)
1147 return one.data()->isOrderedBefore(two.data());
1151 void HTMLMediaElement::updateActiveTextTrackCues(double movieTime)
1153 // 4.8.10.8 Playing the media resource
1155 // If the current playback position changes while the steps are running,
1156 // then the user agent must wait for the steps to complete, and then must
1157 // immediately rerun the steps.
1158 if (ignoreTrackDisplayUpdateRequests())
1161 LOG(Media, "HTMLMediaElement::updateActiveTextTracks");
1163 // 1 - Let current cues be a list of cues, initialized to contain all the
1164 // cues of all the hidden, showing, or showing by default text tracks of the
1165 // media element (not the disabled ones) whose start times are less than or
1166 // equal to the current playback position and whose end times are greater
1167 // than the current playback position.
1168 CueList currentCues;
1170 // The user agent must synchronously unset [the text track cue active] flag
1171 // whenever ... the media element's readyState is changed back to HAVE_NOTHING.
1172 if (m_readyState != HAVE_NOTHING && m_player) {
1173 currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime));
1174 std::sort(currentCues.begin(), currentCues.end(), &compareCueInterval);
1177 CueList previousCues;
1180 // 2 - Let other cues be a list of cues, initialized to contain all the cues
1181 // of hidden, showing, and showing by default text tracks of the media
1182 // element that are not present in current cues.
1183 previousCues = m_currentlyActiveCues;
1185 // 3 - Let last time be the current playback position at the time this
1186 // algorithm was last run for this media element, if this is not the first
1188 double lastTime = m_lastTextTrackUpdateTime;
1190 // 4 - If the current playback position has, since the last time this
1191 // algorithm was run, only changed through its usual monotonic increase
1192 // during normal playback, then let missed cues be the list of cues in other
1193 // cues whose start times are greater than or equal to last time and whose
1194 // end times are less than or equal to the current playback position.
1195 // Otherwise, let missed cues be an empty list.
1196 if (lastTime >= 0 && m_lastSeekTime < movieTime) {
1197 CueList potentiallySkippedCues =
1198 m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime));
1200 for (size_t i = 0; i < potentiallySkippedCues.size(); ++i) {
1201 double cueStartTime = potentiallySkippedCues[i].low();
1202 double cueEndTime = potentiallySkippedCues[i].high();
1204 // Consider cues that may have been missed since the last seek time.
1205 if (cueStartTime > max(m_lastSeekTime, lastTime) && cueEndTime < movieTime)
1206 missedCues.append(potentiallySkippedCues[i]);
1210 m_lastTextTrackUpdateTime = movieTime;
1212 // 5 - If the time was reached through the usual monotonic increase of the
1213 // current playback position during normal playback, and if the user agent
1214 // has not fired a timeupdate event at the element in the past 15 to 250ms
1215 // and is not still running event handlers for such an event, then the user
1216 // agent must queue a task to fire a simple event named timeupdate at the
1217 // element. (In the other cases, such as explicit seeks, relevant events get
1218 // fired as part of the overall process of changing the current playback
1220 if (m_lastSeekTime <= lastTime)
1221 scheduleTimeupdateEvent(false);
1223 // Explicitly cache vector sizes, as their content is constant from here.
1224 size_t currentCuesSize = currentCues.size();
1225 size_t missedCuesSize = missedCues.size();
1226 size_t previousCuesSize = previousCues.size();
1228 // 6 - If all of the cues in current cues have their text track cue active
1229 // flag set, none of the cues in other cues have their text track cue active
1230 // flag set, and missed cues is empty, then abort these steps.
1231 bool activeSetChanged = missedCuesSize;
1233 for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i)
1234 if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive())
1235 activeSetChanged = true;
1237 for (size_t i = 0; i < currentCuesSize; ++i) {
1238 currentCues[i].data()->updateDisplayTree(movieTime);
1240 if (!currentCues[i].data()->isActive())
1241 activeSetChanged = true;
1244 if (!activeSetChanged)
1247 // 7 - If the time was reached through the usual monotonic increase of the
1248 // current playback position during normal playback, and there are cues in
1249 // other cues that have their text track cue pause-on-exi flag set and that
1250 // either have their text track cue active flag set or are also in missed
1251 // cues, then immediately pause the media element.
1252 for (size_t i = 0; !m_paused && i < previousCuesSize; ++i) {
1253 if (previousCues[i].data()->pauseOnExit()
1254 && previousCues[i].data()->isActive()
1255 && !currentCues.contains(previousCues[i]))
1259 for (size_t i = 0; !m_paused && i < missedCuesSize; ++i) {
1260 if (missedCues[i].data()->pauseOnExit())
1264 // 8 - Let events be a list of tasks, initially empty. Each task in this
1265 // list will be associated with a text track, a text track cue, and a time,
1266 // which are used to sort the list before the tasks are queued.
1267 Vector<std::pair<double, TextTrackCue*> > eventTasks;
1269 // 8 - Let affected tracks be a list of text tracks, initially empty.
1270 Vector<TextTrack*> affectedTracks;
1272 for (size_t i = 0; i < missedCuesSize; ++i) {
1273 // 9 - For each text track cue in missed cues, prepare an event named enter
1274 // for the TextTrackCue object with the text track cue start time.
1275 eventTasks.append(std::make_pair(missedCues[i].data()->startTime(),
1276 missedCues[i].data()));
1278 // 10 - For each text track [...] in missed cues, prepare an event
1279 // named exit for the TextTrackCue object with the with the later of
1280 // the text track cue end time and the text track cue start time.
1282 // Note: An explicit task is added only if the cue is NOT a zero or
1283 // negative length cue. Otherwise, the need for an exit event is
1284 // checked when these tasks are actually queued below. This doesn't
1285 // affect sorting events before dispatch either, because the exit
1286 // event has the same time as the enter event.
1287 if (missedCues[i].data()->startTime() < missedCues[i].data()->endTime())
1288 eventTasks.append(std::make_pair(missedCues[i].data()->endTime(),
1289 missedCues[i].data()));
1292 for (size_t i = 0; i < previousCuesSize; ++i) {
1293 // 10 - For each text track cue in other cues that has its text
1294 // track cue active flag set prepare an event named exit for the
1295 // TextTrackCue object with the text track cue end time.
1296 if (!currentCues.contains(previousCues[i]))
1297 eventTasks.append(std::make_pair(previousCues[i].data()->endTime(),
1298 previousCues[i].data()));
1301 for (size_t i = 0; i < currentCuesSize; ++i) {
1302 // 11 - For each text track cue in current cues that does not have its
1303 // text track cue active flag set, prepare an event named enter for the
1304 // TextTrackCue object with the text track cue start time.
1305 if (!previousCues.contains(currentCues[i]))
1306 eventTasks.append(std::make_pair(currentCues[i].data()->startTime(),
1307 currentCues[i].data()));
1310 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1312 std::sort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare);
1314 for (size_t i = 0; i < eventTasks.size(); ++i) {
1315 if (!affectedTracks.contains(eventTasks[i].second->track()))
1316 affectedTracks.append(eventTasks[i].second->track());
1318 // 13 - Queue each task in events, in list order.
1319 RefPtr<Event> event;
1321 // Each event in eventTasks may be either an enterEvent or an exitEvent,
1322 // depending on the time that is associated with the event. This
1323 // correctly identifies the type of the event, if the startTime is
1324 // less than the endTime in the cue.
1325 if (eventTasks[i].second->startTime() >= eventTasks[i].second->endTime()) {
1326 event = Event::create(eventNames().enterEvent, false, false);
1327 event->setTarget(eventTasks[i].second);
1328 m_asyncEventQueue.enqueueEvent(event.release());
1330 event = Event::create(eventNames().exitEvent, false, false);
1331 event->setTarget(eventTasks[i].second);
1332 m_asyncEventQueue.enqueueEvent(event.release());
1334 if (eventTasks[i].first == eventTasks[i].second->startTime())
1335 event = Event::create(eventNames().enterEvent, false, false);
1337 event = Event::create(eventNames().exitEvent, false, false);
1339 event->setTarget(eventTasks[i].second);
1340 m_asyncEventQueue.enqueueEvent(event.release());
1344 // 14 - Sort affected tracks in the same order as the text tracks appear in
1345 // the media element's list of text tracks, and remove duplicates.
1346 std::sort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare);
1348 // 15 - For each text track in affected tracks, in the list order, queue a
1349 // task to fire a simple event named cuechange at the TextTrack object, and, ...
1350 for (size_t i = 0; i < affectedTracks.size(); ++i) {
1351 RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false);
1352 event->setTarget(affectedTracks[i]);
1354 m_asyncEventQueue.enqueueEvent(event.release());
1356 // ... if the text track has a corresponding track element, to then fire a
1357 // simple event named cuechange at the track element as well.
1358 if (affectedTracks[i]->trackType() == TextTrack::TrackElement) {
1359 RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false);
1360 HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(affectedTracks[i])->trackElement();
1361 ASSERT(trackElement);
1362 event->setTarget(trackElement);
1364 m_asyncEventQueue.enqueueEvent(event.release());
1368 // 16 - Set the text track cue active flag of all the cues in the current
1369 // cues, and unset the text track cue active flag of all the cues in the
1371 for (size_t i = 0; i < currentCuesSize; ++i)
1372 currentCues[i].data()->setIsActive(true);
1374 for (size_t i = 0; i < previousCuesSize; ++i)
1375 if (!currentCues.contains(previousCues[i]))
1376 previousCues[i].data()->setIsActive(false);
1378 // Update the current active cues.
1379 m_currentlyActiveCues = currentCues;
1381 if (activeSetChanged)
1382 updateTextTrackDisplay();
1385 bool HTMLMediaElement::textTracksAreReady() const
1387 // 4.8.10.12.1 Text track model
1389 // The text tracks of a media element are ready if all the text tracks whose mode was not
1390 // in the disabled state when the element's resource selection algorithm last started now
1391 // have a text track readiness state of loaded or failed to load.
1392 for (unsigned i = 0; i < m_textTracksWhenResourceSelectionBegan.size(); ++i) {
1393 if (m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::Loading
1394 || m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::NotLoaded)
1401 void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track)
1403 if (m_player && m_textTracksWhenResourceSelectionBegan.contains(track)) {
1404 if (track->readinessState() != TextTrack::Loading)
1405 setReadyState(m_player->readyState());
1407 // The track readiness state might have changed as a result of the user
1408 // clicking the captions button. In this case, a check whether all the
1409 // resources have failed loading should be done in order to hide the CC button.
1410 if (hasMediaControls() && track->readinessState() == TextTrack::FailedToLoad)
1411 mediaControls()->refreshClosedCaptionsButtonVisibility();
1415 void HTMLMediaElement::audioTrackEnabledChanged(AudioTrack*)
1417 // We will want to change the media controls here once they exist
1420 void HTMLMediaElement::textTrackModeChanged(TextTrack* track)
1422 bool trackIsLoaded = true;
1423 if (track->trackType() == TextTrack::TrackElement) {
1424 trackIsLoaded = false;
1425 auto end = childrenOfType<HTMLTrackElement>(this).end();
1426 for (auto trackElement = childrenOfType<HTMLTrackElement>(this).begin(); trackElement != end; ++trackElement) {
1427 if (trackElement->track() == track) {
1428 if (trackElement->readyState() == HTMLTrackElement::LOADING || trackElement->readyState() == HTMLTrackElement::LOADED)
1429 trackIsLoaded = true;
1435 // If this is the first added track, create the list of text tracks.
1437 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
1439 // Mark this track as "configured" so configureTextTracks won't change the mode again.
1440 track->setHasBeenConfigured(true);
1442 if (track->mode() != TextTrack::disabledKeyword() && trackIsLoaded)
1443 textTrackAddCues(track, track->cues());
1445 #if USE(PLATFORM_TEXT_TRACK_MENU)
1446 if (platformTextTrackMenu())
1447 platformTextTrackMenu()->trackWasSelected(track->platformTextTrack());
1450 configureTextTrackDisplay(AssumeTextTrackVisibilityChanged);
1453 void HTMLMediaElement::videoTrackSelectedChanged(VideoTrack*)
1455 // We will want to change the media controls here once they exist
1458 void HTMLMediaElement::textTrackKindChanged(TextTrack* track)
1460 if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword() && track->mode() == TextTrack::showingKeyword())
1461 track->setMode(TextTrack::hiddenKeyword());
1464 void HTMLMediaElement::beginIgnoringTrackDisplayUpdateRequests()
1466 ++m_ignoreTrackDisplayUpdate;
1469 void HTMLMediaElement::endIgnoringTrackDisplayUpdateRequests()
1471 ASSERT(m_ignoreTrackDisplayUpdate);
1472 --m_ignoreTrackDisplayUpdate;
1473 if (!m_ignoreTrackDisplayUpdate && m_inActiveDocument)
1474 updateActiveTextTrackCues(currentTime());
1477 void HTMLMediaElement::textTrackAddCues(TextTrack* track, const TextTrackCueList* cues)
1479 if (track->mode() == TextTrack::disabledKeyword())
1482 TrackDisplayUpdateScope scope(this);
1483 for (size_t i = 0; i < cues->length(); ++i)
1484 textTrackAddCue(track, cues->item(i));
1487 void HTMLMediaElement::textTrackRemoveCues(TextTrack*, const TextTrackCueList* cues)
1489 TrackDisplayUpdateScope scope(this);
1490 for (size_t i = 0; i < cues->length(); ++i)
1491 textTrackRemoveCue(cues->item(i)->track(), cues->item(i));
1494 void HTMLMediaElement::textTrackAddCue(TextTrack* track, PassRefPtr<TextTrackCue> cue)
1496 if (track->mode() == TextTrack::disabledKeyword())
1499 // Negative duration cues need be treated in the interval tree as
1500 // zero-length cues.
1501 double endTime = max(cue->startTime(), cue->endTime());
1503 CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get());
1504 if (!m_cueTree.contains(interval))
1505 m_cueTree.add(interval);
1506 updateActiveTextTrackCues(currentTime());
1509 void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtr<TextTrackCue> cue)
1511 // Negative duration cues need to be treated in the interval tree as
1512 // zero-length cues.
1513 double endTime = max(cue->startTime(), cue->endTime());
1515 CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get());
1516 m_cueTree.remove(interval);
1518 size_t index = m_currentlyActiveCues.find(interval);
1519 if (index != notFound) {
1520 cue->setIsActive(false);
1521 m_currentlyActiveCues.remove(index);
1524 cue->removeDisplayTree();
1525 updateActiveTextTrackCues(currentTime());
1530 bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidURLAction actionIfInvalid)
1532 if (!url.isValid()) {
1533 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE because url is invalid", urlForLoggingMedia(url).utf8().data());
1537 Frame* frame = document().frame();
1538 if (!frame || !document().securityOrigin()->canDisplay(url)) {
1539 if (actionIfInvalid == Complain)
1540 FrameLoader::reportLocalLoadFailed(frame, url.stringCenterEllipsizedToLength());
1541 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE rejected by SecurityOrigin", urlForLoggingMedia(url).utf8().data());
1545 if (!document().contentSecurityPolicy()->allowMediaFromSource(url)) {
1546 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> rejected by Content Security Policy", urlForLoggingMedia(url).utf8().data());
1553 void HTMLMediaElement::startProgressEventTimer()
1555 if (m_progressEventTimer.isActive())
1558 m_previousProgressTime = monotonicallyIncreasingTime();
1559 // 350ms is not magic, it is in the spec!
1560 m_progressEventTimer.startRepeating(0.350);
1563 void HTMLMediaElement::waitForSourceChange()
1565 LOG(Media, "HTMLMediaElement::waitForSourceChange");
1567 stopPeriodicTimers();
1568 m_loadState = WaitingForSource;
1570 // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value
1571 m_networkState = NETWORK_NO_SOURCE;
1573 // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1574 setShouldDelayLoadEvent(false);
1576 updateDisplayState();
1579 renderer()->updateFromElement();
1582 void HTMLMediaElement::noneSupported()
1584 LOG(Media, "HTMLMediaElement::noneSupported");
1586 stopPeriodicTimers();
1587 m_loadState = WaitingForSource;
1588 m_currentSourceNode = 0;
1591 // 6 - Reaching this step indicates that the media resource failed to load or that the given
1592 // URL could not be resolved. In one atomic operation, run the following steps:
1594 // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to
1595 // MEDIA_ERR_SRC_NOT_SUPPORTED.
1596 m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
1598 // 6.2 - Forget the media element's media-resource-specific text tracks.
1600 // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
1601 m_networkState = NETWORK_NO_SOURCE;
1603 // 7 - Queue a task to fire a simple event named error at the media element.
1604 scheduleEvent(eventNames().errorEvent);
1606 #if ENABLE(MEDIA_SOURCE)
1610 // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1611 setShouldDelayLoadEvent(false);
1613 // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed,
1614 // the element won't attempt to load another resource.
1616 updateDisplayState();
1619 renderer()->updateFromElement();
1622 void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err)
1624 LOG(Media, "HTMLMediaElement::mediaEngineError(%d)", static_cast<int>(err->code()));
1626 // 1 - The user agent should cancel the fetching process.
1627 stopPeriodicTimers();
1628 m_loadState = WaitingForSource;
1630 // 2 - Set the error attribute to a new MediaError object whose code attribute is
1631 // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
1634 // 3 - Queue a task to fire a simple event named error at the media element.
1635 scheduleEvent(eventNames().errorEvent);
1637 #if ENABLE(MEDIA_SOURCE)
1641 // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a
1642 // task to fire a simple event called emptied at the element.
1643 m_networkState = NETWORK_EMPTY;
1644 scheduleEvent(eventNames().emptiedEvent);
1646 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1647 setShouldDelayLoadEvent(false);
1649 // 6 - Abort the overall resource selection algorithm.
1650 m_currentSourceNode = 0;
1653 void HTMLMediaElement::cancelPendingEventsAndCallbacks()
1655 LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks");
1656 m_asyncEventQueue.cancelAllEvents();
1658 auto sourceChildren = childrenOfType<HTMLSourceElement>(this);
1659 for (auto source = sourceChildren.begin(), end = sourceChildren.end(); source != end; ++source)
1660 source->cancelPendingErrorEvent();
1663 Document* HTMLMediaElement::mediaPlayerOwningDocument()
1668 void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*)
1670 beginProcessingMediaPlayerCallback();
1671 setNetworkState(m_player->networkState());
1672 endProcessingMediaPlayerCallback();
1675 static void logMediaLoadRequest(Page* page, const String& mediaEngine, const String& errorMessage, bool succeeded)
1677 if (!page || !page->settings().diagnosticLoggingEnabled())
1680 ChromeClient& chromeClient = page->chrome().client();
1683 chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadingFailedKey(), errorMessage, DiagnosticLoggingKeys::failKey());
1687 chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadedKey(), mediaEngine, DiagnosticLoggingKeys::noopKey());
1689 if (!page->hasSeenAnyMediaEngine())
1690 chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOneMediaEngineKey(), emptyString(), DiagnosticLoggingKeys::noopKey());
1692 if (!page->hasSeenMediaEngine(mediaEngine))
1693 chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsMediaEngineKey(), mediaEngine, DiagnosticLoggingKeys::noopKey());
1695 page->sawMediaEngine(mediaEngine);
1698 static String stringForNetworkState(MediaPlayer::NetworkState state)
1701 case MediaPlayer::Empty: return ASCIILiteral("Empty");
1702 case MediaPlayer::Idle: return ASCIILiteral("Idle");
1703 case MediaPlayer::Loading: return ASCIILiteral("Loading");
1704 case MediaPlayer::Loaded: return ASCIILiteral("Loaded");
1705 case MediaPlayer::FormatError: return ASCIILiteral("FormatError");
1706 case MediaPlayer::NetworkError: return ASCIILiteral("NetworkError");
1707 case MediaPlayer::DecodeError: return ASCIILiteral("DecodeError");
1708 default: return emptyString();
1712 void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error)
1714 stopPeriodicTimers();
1716 // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
1717 // <source> children, schedule the next one
1718 if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
1720 if (m_currentSourceNode)
1721 m_currentSourceNode->scheduleErrorEvent();
1723 LOG(Media, "HTMLMediaElement::setNetworkState - error event not sent, <source> was removed");
1725 if (havePotentialSourceChild()) {
1726 LOG(Media, "HTMLMediaElement::setNetworkState - scheduling next <source>");
1727 scheduleNextSourceChild();
1729 LOG(Media, "HTMLMediaElement::setNetworkState - no more <source> elements, waiting");
1730 waitForSourceChange();
1736 if (error == MediaPlayer::NetworkError && m_readyState >= HAVE_METADATA)
1737 mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK));
1738 else if (error == MediaPlayer::DecodeError)
1739 mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE));
1740 else if ((error == MediaPlayer::FormatError || error == MediaPlayer::NetworkError) && m_loadState == LoadingFromSrcAttr)
1743 updateDisplayState();
1744 if (hasMediaControls()) {
1745 mediaControls()->reset();
1746 mediaControls()->reportedError();
1749 logMediaLoadRequest(document().page(), String(), stringForNetworkState(error), false);
1752 void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
1754 LOG(Media, "HTMLMediaElement::setNetworkState(%d) - current state is %d", static_cast<int>(state), static_cast<int>(m_networkState));
1756 if (state == MediaPlayer::Empty) {
1757 // Just update the cached state and leave, we can't do anything.
1758 m_networkState = NETWORK_EMPTY;
1762 if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) {
1763 mediaLoadingFailed(state);
1767 if (state == MediaPlayer::Idle) {
1768 if (m_networkState > NETWORK_IDLE) {
1769 changeNetworkStateFromLoadingToIdle();
1770 setShouldDelayLoadEvent(false);
1772 m_networkState = NETWORK_IDLE;
1776 if (state == MediaPlayer::Loading) {
1777 if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE)
1778 startProgressEventTimer();
1779 m_networkState = NETWORK_LOADING;
1782 if (state == MediaPlayer::Loaded) {
1783 if (m_networkState != NETWORK_IDLE)
1784 changeNetworkStateFromLoadingToIdle();
1785 m_completelyLoaded = true;
1788 if (hasMediaControls())
1789 mediaControls()->updateStatusDisplay();
1792 void HTMLMediaElement::changeNetworkStateFromLoadingToIdle()
1794 m_progressEventTimer.stop();
1795 if (hasMediaControls() && m_player->didLoadingProgress())
1796 mediaControls()->bufferingProgressed();
1798 // Schedule one last progress event so we guarantee that at least one is fired
1799 // for files that load very quickly.
1800 scheduleEvent(eventNames().progressEvent);
1801 scheduleEvent(eventNames().suspendEvent);
1802 m_networkState = NETWORK_IDLE;
1805 void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*)
1807 beginProcessingMediaPlayerCallback();
1809 setReadyState(m_player->readyState());
1811 endProcessingMediaPlayerCallback();
1814 void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
1816 LOG(Media, "HTMLMediaElement::setReadyState(%d) - current state is %d,", static_cast<int>(state), static_cast<int>(m_readyState));
1818 // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it
1819 bool wasPotentiallyPlaying = potentiallyPlaying();
1821 ReadyState oldState = m_readyState;
1822 ReadyState newState = static_cast<ReadyState>(state);
1824 #if ENABLE(VIDEO_TRACK)
1825 bool tracksAreReady = !RuntimeEnabledFeatures::webkitVideoTrackEnabled() || textTracksAreReady();
1827 if (newState == oldState && m_tracksAreReady == tracksAreReady)
1830 m_tracksAreReady = tracksAreReady;
1832 if (newState == oldState)
1834 bool tracksAreReady = true;
1838 m_readyState = newState;
1840 // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until
1841 // the text tracks are ready, regardless of the state of the media file.
1842 if (newState <= HAVE_METADATA)
1843 m_readyState = newState;
1845 m_readyState = HAVE_CURRENT_DATA;
1848 if (oldState > m_readyStateMaximum)
1849 m_readyStateMaximum = oldState;
1851 if (m_networkState == NETWORK_EMPTY)
1855 // 4.8.10.9, step 11
1856 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA)
1857 scheduleEvent(eventNames().waitingEvent);
1859 // 4.8.10.10 step 14 & 15.
1860 if (m_readyState >= HAVE_CURRENT_DATA)
1863 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
1865 scheduleTimeupdateEvent(false);
1866 scheduleEvent(eventNames().waitingEvent);
1870 if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
1871 prepareMediaFragmentURI();
1872 scheduleEvent(eventNames().durationchangeEvent);
1873 scheduleEvent(eventNames().loadedmetadataEvent);
1874 if (hasMediaControls())
1875 mediaControls()->loadedMetadata();
1877 renderer()->updateFromElement();
1879 logMediaLoadRequest(document().page(), m_player->engineDescription(), String(), true);
1882 bool shouldUpdateDisplayState = false;
1884 if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) {
1885 m_haveFiredLoadedData = true;
1886 shouldUpdateDisplayState = true;
1887 scheduleEvent(eventNames().loadeddataEvent);
1888 setShouldDelayLoadEvent(false);
1889 applyMediaFragmentURI();
1892 bool isPotentiallyPlaying = potentiallyPlaying();
1893 if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
1894 scheduleEvent(eventNames().canplayEvent);
1895 if (isPotentiallyPlaying)
1896 scheduleEvent(eventNames().playingEvent);
1897 shouldUpdateDisplayState = true;
1900 if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA && tracksAreReady) {
1901 if (oldState <= HAVE_CURRENT_DATA)
1902 scheduleEvent(eventNames().canplayEvent);
1904 scheduleEvent(eventNames().canplaythroughEvent);
1906 if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA)
1907 scheduleEvent(eventNames().playingEvent);
1909 if (m_autoplaying && m_paused && autoplay() && !document().isSandboxed(SandboxAutomaticFeatures) && !userGestureRequiredForRateChange()) {
1911 invalidateCachedTime();
1912 scheduleEvent(eventNames().playEvent);
1913 scheduleEvent(eventNames().playingEvent);
1916 shouldUpdateDisplayState = true;
1919 if (shouldUpdateDisplayState) {
1920 updateDisplayState();
1921 if (hasMediaControls()) {
1922 mediaControls()->refreshClosedCaptionsButtonVisibility();
1923 mediaControls()->updateStatusDisplay();
1928 updateMediaController();
1929 #if ENABLE(VIDEO_TRACK)
1930 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
1931 updateActiveTextTrackCues(currentTime());
1935 #if ENABLE(ENCRYPTED_MEDIA)
1936 void HTMLMediaElement::mediaPlayerKeyAdded(MediaPlayer*, const String& keySystem, const String& sessionId)
1938 MediaKeyEventInit initializer;
1939 initializer.keySystem = keySystem;
1940 initializer.sessionId = sessionId;
1941 initializer.bubbles = false;
1942 initializer.cancelable = false;
1944 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeyaddedEvent, initializer);
1945 event->setTarget(this);
1946 m_asyncEventQueue.enqueueEvent(event.release());
1949 void HTMLMediaElement::mediaPlayerKeyError(MediaPlayer*, const String& keySystem, const String& sessionId, MediaPlayerClient::MediaKeyErrorCode errorCode, unsigned short systemCode)
1951 MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
1952 switch (errorCode) {
1953 case MediaPlayerClient::UnknownError:
1954 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
1956 case MediaPlayerClient::ClientError:
1957 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT;
1959 case MediaPlayerClient::ServiceError:
1960 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_SERVICE;
1962 case MediaPlayerClient::OutputError:
1963 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_OUTPUT;
1965 case MediaPlayerClient::HardwareChangeError:
1966 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_HARDWARECHANGE;
1968 case MediaPlayerClient::DomainError:
1969 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_DOMAIN;
1973 MediaKeyEventInit initializer;
1974 initializer.keySystem = keySystem;
1975 initializer.sessionId = sessionId;
1976 initializer.errorCode = MediaKeyError::create(mediaKeyErrorCode);
1977 initializer.systemCode = systemCode;
1978 initializer.bubbles = false;
1979 initializer.cancelable = false;
1981 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeyerrorEvent, initializer);
1982 event->setTarget(this);
1983 m_asyncEventQueue.enqueueEvent(event.release());
1986 void HTMLMediaElement::mediaPlayerKeyMessage(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* message, unsigned messageLength, const KURL& defaultURL)
1988 MediaKeyEventInit initializer;
1989 initializer.keySystem = keySystem;
1990 initializer.sessionId = sessionId;
1991 initializer.message = Uint8Array::create(message, messageLength);
1992 initializer.defaultURL = defaultURL;
1993 initializer.bubbles = false;
1994 initializer.cancelable = false;
1996 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeymessageEvent, initializer);
1997 event->setTarget(this);
1998 m_asyncEventQueue.enqueueEvent(event.release());
2001 bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* initData, unsigned initDataLength)
2003 if (!hasEventListeners(eventNames().webkitneedkeyEvent)) {
2004 m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED);
2005 scheduleEvent(eventNames().errorEvent);
2009 MediaKeyEventInit initializer;
2010 initializer.keySystem = keySystem;
2011 initializer.sessionId = sessionId;
2012 initializer.initData = Uint8Array::create(initData, initDataLength);
2013 initializer.bubbles = false;
2014 initializer.cancelable = false;
2016 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitneedkeyEvent, initializer);
2017 event->setTarget(this);
2018 m_asyncEventQueue.enqueueEvent(event.release());
2023 #if ENABLE(ENCRYPTED_MEDIA_V2)
2024 bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, Uint8Array* initData)
2026 if (!hasEventListeners("webkitneedkey")) {
2027 m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED);
2028 scheduleEvent(eventNames().errorEvent);
2032 MediaKeyNeededEventInit initializer;
2033 initializer.initData = initData;
2034 initializer.bubbles = false;
2035 initializer.cancelable = false;
2037 RefPtr<Event> event = MediaKeyNeededEvent::create(eventNames().webkitneedkeyEvent, initializer);
2038 event->setTarget(this);
2039 m_asyncEventQueue.enqueueEvent(event.release());
2044 void HTMLMediaElement::setMediaKeys(MediaKeys* mediaKeys)
2046 if (m_mediaKeys == mediaKeys)
2050 m_mediaKeys->setMediaElement(0);
2051 m_mediaKeys = mediaKeys;
2053 m_mediaKeys->setMediaElement(this);
2057 void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*)
2060 if (m_networkState != NETWORK_LOADING)
2063 double time = monotonicallyIncreasingTime();
2064 double timedelta = time - m_previousProgressTime;
2066 if (m_player->didLoadingProgress()) {
2067 scheduleEvent(eventNames().progressEvent);
2068 m_previousProgressTime = time;
2069 m_sentStalledEvent = false;
2071 renderer()->updateFromElement();
2072 if (hasMediaControls())
2073 mediaControls()->bufferingProgressed();
2074 } else if (timedelta > 3.0 && !m_sentStalledEvent) {
2075 scheduleEvent(eventNames().stalledEvent);
2076 m_sentStalledEvent = true;
2077 setShouldDelayLoadEvent(false);
2081 void HTMLMediaElement::rewind(double timeDelta)
2083 LOG(Media, "HTMLMediaElement::rewind(%f)", timeDelta);
2084 setCurrentTime(max(currentTime() - timeDelta, minTimeSeekable()), IGNORE_EXCEPTION);
2087 void HTMLMediaElement::returnToRealtime()
2089 LOG(Media, "HTMLMediaElement::returnToRealtime");
2090 setCurrentTime(maxTimeSeekable(), IGNORE_EXCEPTION);
2093 void HTMLMediaElement::addPlayedRange(double start, double end)
2095 LOG(Media, "HTMLMediaElement::addPlayedRange(%f, %f)", start, end);
2096 if (!m_playedTimeRanges)
2097 m_playedTimeRanges = TimeRanges::create();
2098 m_playedTimeRanges->add(start, end);
2101 bool HTMLMediaElement::supportsSave() const
2103 return m_player ? m_player->supportsSave() : false;
2106 bool HTMLMediaElement::supportsScanning() const
2108 return m_player ? m_player->supportsScanning() : false;
2111 void HTMLMediaElement::prepareToPlay()
2113 LOG(Media, "HTMLMediaElement::prepareToPlay(%p)", this);
2114 if (m_havePreparedToPlay)
2116 m_havePreparedToPlay = true;
2117 m_player->prepareToPlay();
2120 void HTMLMediaElement::seek(double time, ExceptionCode& ec)
2122 LOG(Media, "HTMLMediaElement::seek(%f)", time);
2126 // 1 - If the media element's readyState is HAVE_NOTHING, then raise an INVALID_STATE_ERR exception.
2127 if (m_readyState == HAVE_NOTHING || !m_player) {
2128 ec = INVALID_STATE_ERR;
2132 // If the media engine has been told to postpone loading data, let it go ahead now.
2133 if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA)
2136 // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set.
2137 refreshCachedTime();
2138 double now = currentTime();
2140 // 2 - If the element's seeking IDL attribute is true, then another instance of this algorithm is
2141 // already running. Abort that other instance of the algorithm without waiting for the step that
2142 // it is running to complete.
2143 // Nothing specific to be done here.
2145 // 3 - Set the seeking IDL attribute to true.
2146 // The flag will be cleared when the engine tells us the time has actually changed.
2149 // 5 - If the new playback position is later than the end of the media resource, then let it be the end
2150 // of the media resource instead.
2151 time = min(time, duration());
2153 // 6 - If the new playback position is less than the earliest possible position, let it be that position instead.
2154 double earliestTime = m_player->startTime();
2155 time = max(time, earliestTime);
2157 // Ask the media engine for the time value in the movie's time scale before comparing with current time. This
2158 // is necessary because if the seek time is not equal to currentTime but the delta is less than the movie's
2159 // time scale, we will ask the media engine to "seek" to the current movie time, which may be a noop and
2160 // not generate a timechanged callback. This means m_seeking will never be cleared and we will never
2161 // fire a 'seeked' event.
2163 double mediaTime = m_player->mediaTimeForTimeValue(time);
2164 if (time != mediaTime)
2165 LOG(Media, "HTMLMediaElement::seek(%f) - media timeline equivalent is %f", time, mediaTime);
2167 time = m_player->mediaTimeForTimeValue(time);
2169 // 7 - If the (possibly now changed) new playback position is not in one of the ranges given in the
2170 // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute
2171 // that is the nearest to the new playback position. ... If there are no ranges given in the seekable
2172 // attribute then set the seeking IDL attribute to false and abort these steps.
2173 RefPtr<TimeRanges> seekableRanges = seekable();
2175 // Short circuit seeking to the current time by just firing the events if no seek is required.
2176 // Don't skip calling the media engine if we are in poster mode because a seek should always
2177 // cancel poster display.
2178 bool noSeekRequired = !seekableRanges->length() || (time == now && displayMode() != Poster);
2180 #if ENABLE(MEDIA_SOURCE)
2181 // Always notify the media engine of a seek if the source is not closed. This ensures that the source is
2182 // always in a flushed state when the 'seeking' event fires.
2183 if (m_mediaSource && m_mediaSource->isClosed())
2184 noSeekRequired = false;
2187 if (noSeekRequired) {
2189 scheduleEvent(eventNames().seekingEvent);
2190 scheduleTimeupdateEvent(false);
2191 scheduleEvent(eventNames().seekedEvent);
2196 time = seekableRanges->nearest(time);
2199 if (m_lastSeekTime < now)
2200 addPlayedRange(m_lastSeekTime, now);
2202 m_lastSeekTime = time;
2203 m_sentEndEvent = false;
2205 // 8 - Set the current playback position to the given new playback position
2206 m_player->seek(time);
2208 // 9 - Queue a task to fire a simple event named seeking at the element.
2209 scheduleEvent(eventNames().seekingEvent);
2211 // 10 - Queue a task to fire a simple event named timeupdate at the element.
2212 scheduleTimeupdateEvent(false);
2214 // 11-15 are handled, if necessary, when the engine signals a readystate change.
2217 void HTMLMediaElement::finishSeek()
2219 LOG(Media, "HTMLMediaElement::finishSeek");
2221 // 4.8.10.9 Seeking step 14
2224 // 4.8.10.9 Seeking step 15
2225 scheduleEvent(eventNames().seekedEvent);
2227 setDisplayMode(Video);
2230 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
2232 return m_readyState;
2235 MediaPlayer::MovieLoadType HTMLMediaElement::movieLoadType() const
2237 return m_player ? m_player->movieLoadType() : MediaPlayer::Unknown;
2240 bool HTMLMediaElement::hasAudio() const
2242 return m_player ? m_player->hasAudio() : false;
2245 bool HTMLMediaElement::seeking() const
2250 void HTMLMediaElement::refreshCachedTime() const
2252 m_cachedTime = m_player->currentTime();
2253 m_clockTimeAtLastCachedTimeUpdate = monotonicallyIncreasingTime();
2256 void HTMLMediaElement::invalidateCachedTime()
2258 LOG(Media, "HTMLMediaElement::invalidateCachedTime");
2260 // Don't try to cache movie time when playback first starts as the time reported by the engine
2261 // sometimes fluctuates for a short amount of time, so the cached time will be off if we take it
2263 static const double minimumTimePlayingBeforeCacheSnapshot = 0.5;
2265 m_minimumClockTimeToUpdateCachedTime = monotonicallyIncreasingTime() + minimumTimePlayingBeforeCacheSnapshot;
2266 m_cachedTime = MediaPlayer::invalidTime();
2270 double HTMLMediaElement::currentTime() const
2272 #if LOG_CACHED_TIME_WARNINGS
2273 static const double minCachedDeltaForWarning = 0.01;
2280 LOG(Media, "HTMLMediaElement::currentTime - seeking, returning %f", m_lastSeekTime);
2281 return m_lastSeekTime;
2284 if (m_cachedTime != MediaPlayer::invalidTime() && m_paused) {
2285 #if LOG_CACHED_TIME_WARNINGS
2286 double delta = m_cachedTime - m_player->currentTime();
2287 if (delta > minCachedDeltaForWarning)
2288 LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when paused", delta);
2290 return m_cachedTime;
2293 // Is it too soon use a cached time?
2294 double now = monotonicallyIncreasingTime();
2295 double maximumDurationToCacheMediaTime = m_player->maximumDurationToCacheMediaTime();
2297 if (maximumDurationToCacheMediaTime && m_cachedTime != MediaPlayer::invalidTime() && !m_paused && now > m_minimumClockTimeToUpdateCachedTime) {
2298 double clockDelta = now - m_clockTimeAtLastCachedTimeUpdate;
2300 // Not too soon, use the cached time only if it hasn't expired.
2301 if (clockDelta < maximumDurationToCacheMediaTime) {
2302 double adjustedCacheTime = m_cachedTime + (m_playbackRate * clockDelta);
2304 #if LOG_CACHED_TIME_WARNINGS
2305 double delta = adjustedCacheTime - m_player->currentTime();
2306 if (delta > minCachedDeltaForWarning)
2307 LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when playing", delta);
2309 return adjustedCacheTime;
2313 #if LOG_CACHED_TIME_WARNINGS
2314 if (maximumDurationToCacheMediaTime && now > m_minimumClockTimeToUpdateCachedTime && m_cachedTime != MediaPlayer::invalidTime()) {
2315 double clockDelta = now - m_clockTimeAtLastCachedTimeUpdate;
2316 double delta = m_cachedTime + (m_playbackRate * clockDelta) - m_player->currentTime();
2317 LOG(Media, "HTMLMediaElement::currentTime - cached time was %f seconds off of media time when it expired", delta);
2321 refreshCachedTime();
2323 return m_cachedTime;
2326 void HTMLMediaElement::setCurrentTime(double time, ExceptionCode& ec)
2328 if (m_mediaController) {
2329 ec = INVALID_STATE_ERR;
2335 double HTMLMediaElement::startTime() const
2339 return m_player->startTime();
2342 double HTMLMediaElement::initialTime() const
2344 if (m_fragmentStartTime != MediaPlayer::invalidTime())
2345 return m_fragmentStartTime;
2350 return m_player->initialTime();
2353 double HTMLMediaElement::duration() const
2355 if (m_player && m_readyState >= HAVE_METADATA)
2356 return m_player->duration();
2358 return numeric_limits<double>::quiet_NaN();
2361 bool HTMLMediaElement::paused() const
2366 double HTMLMediaElement::defaultPlaybackRate() const
2368 return m_defaultPlaybackRate;
2371 void HTMLMediaElement::setDefaultPlaybackRate(double rate)
2373 if (m_defaultPlaybackRate != rate) {
2374 m_defaultPlaybackRate = rate;
2375 scheduleEvent(eventNames().ratechangeEvent);
2379 double HTMLMediaElement::playbackRate() const
2381 return m_playbackRate;
2384 void HTMLMediaElement::setPlaybackRate(double rate)
2386 LOG(Media, "HTMLMediaElement::setPlaybackRate(%f)", rate);
2388 if (m_playbackRate != rate) {
2389 m_playbackRate = rate;
2390 invalidateCachedTime();
2391 scheduleEvent(eventNames().ratechangeEvent);
2394 if (m_player && potentiallyPlaying() && m_player->rate() != rate && !m_mediaController)
2395 m_player->setRate(rate);
2398 void HTMLMediaElement::updatePlaybackRate()
2400 double effectiveRate = m_mediaController ? m_mediaController->playbackRate() : m_playbackRate;
2401 if (m_player && potentiallyPlaying() && m_player->rate() != effectiveRate)
2402 m_player->setRate(effectiveRate);
2405 bool HTMLMediaElement::webkitPreservesPitch() const
2407 return m_webkitPreservesPitch;
2410 void HTMLMediaElement::setWebkitPreservesPitch(bool preservesPitch)
2412 LOG(Media, "HTMLMediaElement::setWebkitPreservesPitch(%s)", boolString(preservesPitch));
2414 m_webkitPreservesPitch = preservesPitch;
2419 m_player->setPreservesPitch(preservesPitch);
2422 bool HTMLMediaElement::ended() const
2424 // 4.8.10.8 Playing the media resource
2425 // The ended attribute must return true if the media element has ended
2426 // playback and the direction of playback is forwards, and false otherwise.
2427 return endedPlayback() && m_playbackRate > 0;
2430 bool HTMLMediaElement::autoplay() const
2432 return fastHasAttribute(autoplayAttr);
2435 void HTMLMediaElement::setAutoplay(bool b)
2437 LOG(Media, "HTMLMediaElement::setAutoplay(%s)", boolString(b));
2438 setBooleanAttribute(autoplayAttr, b);
2441 String HTMLMediaElement::preload() const
2443 switch (m_preload) {
2444 case MediaPlayer::None:
2445 return ASCIILiteral("none");
2447 case MediaPlayer::MetaData:
2448 return ASCIILiteral("metadata");
2450 case MediaPlayer::Auto:
2451 return ASCIILiteral("auto");
2455 ASSERT_NOT_REACHED();
2459 void HTMLMediaElement::setPreload(const String& preload)
2461 LOG(Media, "HTMLMediaElement::setPreload(%s)", preload.utf8().data());
2462 setAttribute(preloadAttr, preload);
2465 void HTMLMediaElement::play()
2467 LOG(Media, "HTMLMediaElement::play()");
2469 if (userGestureRequiredForRateChange() && !ScriptController::processingUserGesture())
2471 if (ScriptController::processingUserGesture())
2472 removeBehaviorsRestrictionsAfterFirstUserGesture();
2474 Settings* settings = document().settings();
2475 if (settings && settings->needsSiteSpecificQuirks() && m_dispatchingCanPlayEvent && !m_loadInitiatedByUserGesture) {
2476 // It should be impossible to be processing the canplay event while handling a user gesture
2477 // since it is dispatched asynchronously.
2478 ASSERT(!ScriptController::processingUserGesture());
2479 String host = document().baseURL().host();
2480 if (host.endsWith(".npr.org", false) || equalIgnoringCase(host, "npr.org"))
2487 void HTMLMediaElement::playInternal()
2489 LOG(Media, "HTMLMediaElement::playInternal");
2491 // 4.8.10.9. Playing the media resource
2492 if (!m_player || m_networkState == NETWORK_EMPTY)
2493 scheduleDelayedAction(LoadMediaResource);
2495 if (endedPlayback())
2496 seek(0, IGNORE_EXCEPTION);
2498 if (m_mediaController)
2499 m_mediaController->bringElementUpToSpeed(this);
2503 invalidateCachedTime();
2504 scheduleEvent(eventNames().playEvent);
2506 if (m_readyState <= HAVE_CURRENT_DATA)
2507 scheduleEvent(eventNames().waitingEvent);
2508 else if (m_readyState >= HAVE_FUTURE_DATA)
2509 scheduleEvent(eventNames().playingEvent);
2511 m_autoplaying = false;
2513 updateMediaController();
2516 void HTMLMediaElement::pause()
2518 LOG(Media, "HTMLMediaElement::pause()");
2520 if (userGestureRequiredForRateChange() && !ScriptController::processingUserGesture())
2527 void HTMLMediaElement::pauseInternal()
2529 LOG(Media, "HTMLMediaElement::pauseInternal");
2531 // 4.8.10.9. Playing the media resource
2532 if (!m_player || m_networkState == NETWORK_EMPTY)
2533 scheduleDelayedAction(LoadMediaResource);
2535 m_autoplaying = false;
2539 scheduleTimeupdateEvent(false);
2540 scheduleEvent(eventNames().pauseEvent);
2546 #if ENABLE(MEDIA_SOURCE)
2547 void HTMLMediaElement::closeMediaSource()
2552 m_mediaSource->close();
2557 #if ENABLE(ENCRYPTED_MEDIA)
2558 void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, PassRefPtr<Uint8Array> initData, ExceptionCode& ec)
2560 #if ENABLE(ENCRYPTED_MEDIA_V2)
2561 static bool firstTime = true;
2562 if (firstTime && scriptExecutionContext()) {
2563 scriptExecutionContext()->addConsoleMessage(JSMessageSource, WarningMessageLevel, "'HTMLMediaElement.webkitGenerateKeyRequest()' is deprecated. Use 'MediaKeys.createSession()' instead.");
2568 if (keySystem.isEmpty()) {
2574 ec = INVALID_STATE_ERR;
2578 const unsigned char* initDataPointer = 0;
2579 unsigned initDataLength = 0;
2581 initDataPointer = initData->data();
2582 initDataLength = initData->length();
2585 MediaPlayer::MediaKeyException result = m_player->generateKeyRequest(keySystem, initDataPointer, initDataLength);
2586 ec = exceptionCodeForMediaKeyException(result);
2589 void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, ExceptionCode& ec)
2591 webkitGenerateKeyRequest(keySystem, Uint8Array::create(0), ec);
2594 void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, PassRefPtr<Uint8Array> initData, const String& sessionId, ExceptionCode& ec)
2596 #if ENABLE(ENCRYPTED_MEDIA_V2)
2597 static bool firstTime = true;
2598 if (firstTime && scriptExecutionContext()) {
2599 scriptExecutionContext()->addConsoleMessage(JSMessageSource, WarningMessageLevel, "'HTMLMediaElement.webkitAddKey()' is deprecated. Use 'MediaKeySession.update()' instead.");
2604 if (keySystem.isEmpty()) {
2614 if (!key->length()) {
2615 ec = TYPE_MISMATCH_ERR;
2620 ec = INVALID_STATE_ERR;
2624 const unsigned char* initDataPointer = 0;
2625 unsigned initDataLength = 0;
2627 initDataPointer = initData->data();
2628 initDataLength = initData->length();
2631 MediaPlayer::MediaKeyException result = m_player->addKey(keySystem, key->data(), key->length(), initDataPointer, initDataLength, sessionId);
2632 ec = exceptionCodeForMediaKeyException(result);
2635 void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, ExceptionCode& ec)
2637 webkitAddKey(keySystem, key, Uint8Array::create(0), String(), ec);
2640 void HTMLMediaElement::webkitCancelKeyRequest(const String& keySystem, const String& sessionId, ExceptionCode& ec)
2642 if (keySystem.isEmpty()) {
2648 ec = INVALID_STATE_ERR;
2652 MediaPlayer::MediaKeyException result = m_player->cancelKeyRequest(keySystem, sessionId);
2653 ec = exceptionCodeForMediaKeyException(result);
2658 bool HTMLMediaElement::loop() const
2660 return fastHasAttribute(loopAttr);
2663 void HTMLMediaElement::setLoop(bool b)
2665 LOG(Media, "HTMLMediaElement::setLoop(%s)", boolString(b));
2666 setBooleanAttribute(loopAttr, b);
2669 bool HTMLMediaElement::controls() const
2671 Frame* frame = document().frame();
2673 // always show controls when scripting is disabled
2674 if (frame && !frame->script().canExecuteScripts(NotAboutToExecuteScript))
2677 // always show controls for video when fullscreen playback is required.
2678 if (isVideo() && document().page() && document().page()->chrome().requiresFullscreenForVideoPlayback())
2681 // Always show controls when in full screen mode.
2685 return fastHasAttribute(controlsAttr);
2688 void HTMLMediaElement::setControls(bool b)
2690 LOG(Media, "HTMLMediaElement::setControls(%s)", boolString(b));
2691 setBooleanAttribute(controlsAttr, b);
2694 double HTMLMediaElement::volume() const
2699 void HTMLMediaElement::setVolume(double vol, ExceptionCode& ec)
2701 LOG(Media, "HTMLMediaElement::setVolume(%f)", vol);
2703 if (vol < 0.0f || vol > 1.0f) {
2704 ec = INDEX_SIZE_ERR;
2708 if (m_volume != vol) {
2710 m_volumeInitialized = true;
2712 scheduleEvent(eventNames().volumechangeEvent);
2716 bool HTMLMediaElement::muted() const
2721 void HTMLMediaElement::setMuted(bool muted)
2723 LOG(Media, "HTMLMediaElement::setMuted(%s)", boolString(muted));
2725 if (m_muted != muted) {
2727 // Avoid recursion when the player reports volume changes.
2728 if (!processingMediaPlayerCallback()) {
2730 m_player->setMuted(m_muted);
2731 if (hasMediaControls())
2732 mediaControls()->changedMute();
2735 scheduleEvent(eventNames().volumechangeEvent);
2739 void HTMLMediaElement::togglePlayState()
2741 LOG(Media, "HTMLMediaElement::togglePlayState - canPlay() is %s", boolString(canPlay()));
2743 // We can safely call the internal play/pause methods, which don't check restrictions, because
2744 // this method is only called from the built-in media controller
2746 updatePlaybackRate();
2752 void HTMLMediaElement::beginScrubbing()
2754 LOG(Media, "HTMLMediaElement::beginScrubbing - paused() is %s", boolString(paused()));
2758 // Because a media element stays in non-paused state when it reaches end, playback resumes
2759 // when the slider is dragged from the end to another position unless we pause first. Do
2760 // a "hard pause" so an event is generated, since we want to stay paused after scrubbing finishes.
2763 // Not at the end but we still want to pause playback so the media engine doesn't try to
2764 // continue playing during scrubbing. Pause without generating an event as we will
2765 // unpause after scrubbing finishes.
2766 setPausedInternal(true);
2771 void HTMLMediaElement::endScrubbing()
2773 LOG(Media, "HTMLMediaElement::endScrubbing - m_pausedInternal is %s", boolString(m_pausedInternal));
2775 if (m_pausedInternal)
2776 setPausedInternal(false);
2779 // The spec says to fire periodic timeupdate events (those sent while playing) every
2780 // "15 to 250ms", we choose the slowest frequency
2781 static const double maxTimeupdateEventFrequency = 0.25;
2783 void HTMLMediaElement::startPlaybackProgressTimer()
2785 if (m_playbackProgressTimer.isActive())
2788 m_previousProgressTime = monotonicallyIncreasingTime();
2789 m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency);
2792 void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*)
2796 if (m_fragmentEndTime != MediaPlayer::invalidTime() && currentTime() >= m_fragmentEndTime && m_playbackRate > 0) {
2797 m_fragmentEndTime = MediaPlayer::invalidTime();
2798 if (!m_mediaController && !m_paused) {
2799 // changes paused to true and fires a simple event named pause at the media element.
2804 scheduleTimeupdateEvent(true);
2806 if (!m_playbackRate)
2809 if (!m_paused && hasMediaControls())
2810 mediaControls()->playbackProgressed();
2812 #if ENABLE(VIDEO_TRACK)
2813 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
2814 updateActiveTextTrackCues(currentTime());
2818 void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent)
2820 double now = monotonicallyIncreasingTime();
2821 double timedelta = now - m_clockTimeAtLastUpdateEvent;
2823 // throttle the periodic events
2824 if (periodicEvent && timedelta < maxTimeupdateEventFrequency)
2827 // Some media engines make multiple "time changed" callbacks at the same time, but we only want one
2828 // event at a given time so filter here
2829 double movieTime = currentTime();
2830 if (movieTime != m_lastTimeUpdateEventMovieTime) {
2831 scheduleEvent(eventNames().timeupdateEvent);
2832 m_clockTimeAtLastUpdateEvent = now;
2833 m_lastTimeUpdateEventMovieTime = movieTime;
2837 bool HTMLMediaElement::canPlay() const
2839 return paused() || ended() || m_readyState < HAVE_METADATA;
2842 double HTMLMediaElement::percentLoaded() const
2846 double duration = m_player->duration();
2848 if (!duration || std::isinf(duration))
2851 double buffered = 0;
2852 RefPtr<TimeRanges> timeRanges = m_player->buffered();
2853 for (unsigned i = 0; i < timeRanges->length(); ++i) {
2854 double start = timeRanges->start(i, IGNORE_EXCEPTION);
2855 double end = timeRanges->end(i, IGNORE_EXCEPTION);
2856 buffered += end - start;
2858 return buffered / duration;
2861 #if ENABLE(VIDEO_TRACK)
2863 void HTMLMediaElement::mediaPlayerDidAddAudioTrack(PassRefPtr<AudioTrackPrivate> prpTrack)
2865 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
2868 addAudioTrack(AudioTrack::create(this, prpTrack));
2871 void HTMLMediaElement::mediaPlayerDidAddTextTrack(PassRefPtr<InbandTextTrackPrivate> prpTrack)
2873 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
2876 // 4.8.10.12.2 Sourcing in-band text tracks
2877 // 1. Associate the relevant data with a new text track and its corresponding new TextTrack object.
2878 RefPtr<InbandTextTrack> textTrack = InbandTextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, prpTrack);
2880 // 2. Set the new text track's kind, label, and language based on the semantics of the relevant data,
2881 // as defined by the relevant specification. If there is no label in that data, then the label must
2882 // be set to the empty string.
2883 // 3. Associate the text track list of cues with the rules for updating the text track rendering appropriate
2884 // for the format in question.
2885 // 4. If the new text track's kind is metadata, then set the text track in-band metadata track dispatch type
2886 // as follows, based on the type of the media resource:
2887 // 5. Populate the new text track's list of cues with the cues parsed so far, folllowing the guidelines for exposing
2888 // cues, and begin updating it dynamically as necessary.
2889 // - Thess are all done by the media engine.
2891 // 6. Set the new text track's readiness state to loaded.
2892 textTrack->setReadinessState(TextTrack::Loaded);
2894 // 7. Set the new text track's mode to the mode consistent with the user's preferences and the requirements of
2895 // the relevant specification for the data.
2896 // - This will happen in configureTextTracks()
2897 scheduleDelayedAction(ConfigureTextTracks);
2899 // 8. Add the new text track to the media element's list of text tracks.
2900 // 9. Fire an event with the name addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent
2901 // interface, with the track attribute initialized to the text track's TextTrack object, at the media element's
2902 // textTracks attribute's TextTrackList object.
2903 addTextTrack(textTrack.release());
2906 void HTMLMediaElement::mediaPlayerDidAddVideoTrack(PassRefPtr<VideoTrackPrivate> prpTrack)
2908 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
2911 addVideoTrack(VideoTrack::create(this, prpTrack));
2914 void HTMLMediaElement::mediaPlayerDidRemoveAudioTrack(PassRefPtr<AudioTrackPrivate> prpTrack)
2916 prpTrack->willBeRemoved();
2919 void HTMLMediaElement::mediaPlayerDidRemoveTextTrack(PassRefPtr<InbandTextTrackPrivate> prpTrack)
2921 prpTrack->willBeRemoved();
2924 void HTMLMediaElement::mediaPlayerDidRemoveVideoTrack(PassRefPtr<VideoTrackPrivate> prpTrack)
2926 prpTrack->willBeRemoved();
2929 #if USE(PLATFORM_TEXT_TRACK_MENU)
2930 void HTMLMediaElement::setSelectedTextTrack(PassRefPtr<PlatformTextTrack> platformTrack)
2935 TrackDisplayUpdateScope scope(this);
2937 if (!platformTrack) {
2938 setSelectedTextTrack(TextTrack::captionMenuOffItem());
2942 TextTrack* textTrack;
2943 if (platformTrack == PlatformTextTrack::captionMenuOffItem())
2944 textTrack = TextTrack::captionMenuOffItem();
2945 else if (platformTrack == PlatformTextTrack::captionMenuAutomaticItem())
2946 textTrack = TextTrack::captionMenuAutomaticItem();
2949 for (i = 0; i < m_textTracks->length(); ++i) {
2950 textTrack = m_textTracks->item(i);
2952 if (textTrack->platformTextTrack() == platformTrack)
2955 if (i == m_textTracks->length())
2959 setSelectedTextTrack(textTrack);
2962 Vector<RefPtr<PlatformTextTrack> > HTMLMediaElement::platformTextTracks()
2964 if (!m_textTracks || !m_textTracks->length())
2965 return Vector<RefPtr<PlatformTextTrack> >();
2967 Vector<RefPtr<PlatformTextTrack> > platformTracks;
2968 for (size_t i = 0; i < m_textTracks->length(); ++i)
2969 platformTracks.append(m_textTracks->item(i)->platformTextTrack());
2971 return platformTracks;
2974 void HTMLMediaElement::notifyMediaPlayerOfTextTrackChanges()
2976 if (!m_textTracks || !m_textTracks->length() || !platformTextTrackMenu())
2979 m_platformMenu->tracksDidChange();
2982 PlatformTextTrackMenuInterface* HTMLMediaElement::platformTextTrackMenu()
2985 return m_platformMenu.get();
2987 if (!m_player || !m_player->implementsTextTrackControls())
2990 m_platformMenu = m_player->textTrackMenu();
2991 if (!m_platformMenu)
2994 m_platformMenu->setClient(this);
2996 return m_platformMenu.get();
2998 #endif // #if USE(PLATFORM_TEXT_TRACK_MENU)
3000 void HTMLMediaElement::closeCaptionTracksChanged()
3002 if (hasMediaControls())
3003 mediaControls()->closedCaptionTracksChanged();
3005 #if USE(PLATFORM_TEXT_TRACK_MENU)
3006 if (m_player && m_player->implementsTextTrackControls())
3007 scheduleDelayedAction(TextTrackChangesNotification);
3011 void HTMLMediaElement::addAudioTrack(PassRefPtr<AudioTrack> track)
3013 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3016 audioTracks()->append(track);
3019 void HTMLMediaElement::addTextTrack(PassRefPtr<TextTrack> track)
3021 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3024 textTracks()->append(track);
3026 closeCaptionTracksChanged();
3029 void HTMLMediaElement::addVideoTrack(PassRefPtr<VideoTrack> track)
3031 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3034 videoTracks()->append(track);
3037 void HTMLMediaElement::removeAudioTrack(AudioTrack* track)
3039 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3042 m_audioTracks->remove(track);
3045 void HTMLMediaElement::removeTextTrack(TextTrack* track)
3047 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3050 TrackDisplayUpdateScope scope(this);
3051 TextTrackCueList* cues = track->cues();
3053 textTrackRemoveCues(track, cues);
3054 track->clearClient();
3055 m_textTracks->remove(track);
3057 closeCaptionTracksChanged();
3060 void HTMLMediaElement::removeVideoTrack(VideoTrack* track)
3062 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3065 m_videoTracks->remove(track);
3068 void HTMLMediaElement::removeAllInbandTracks()
3070 while (m_audioTracks && m_audioTracks->length())
3071 removeAudioTrack(m_audioTracks->lastItem());
3074 TrackDisplayUpdateScope scope(this);
3075 for (int i = m_textTracks->length() - 1; i >= 0; --i) {
3076 TextTrack* track = m_textTracks->item(i);
3078 if (track->trackType() == TextTrack::InBand)
3079 removeTextTrack(track);
3083 while (m_videoTracks && m_videoTracks->length())
3084 removeVideoTrack(m_videoTracks->lastItem());
3087 PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const String& kind, const String& label, const String& language, ExceptionCode& ec)
3089 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3092 // 4.8.10.12.4 Text track API
3093 // The addTextTrack(kind, label, language) method of media elements, when invoked, must run the following steps:
3095 // 1. If kind is not one of the following strings, then throw a SyntaxError exception and abort these steps
3096 if (!TextTrack::isValidKindKeyword(kind)) {
3101 // 2. If the label argument was omitted, let label be the empty string.
3102 // 3. If the language argument was omitted, let language be the empty string.
3103 // 4. Create a new TextTrack object.
3105 // 5. Create a new text track corresponding to the new object, and set its text track kind to kind, its text
3106 // track label to label, its text track language to language...
3107 RefPtr<TextTrack> textTrack = TextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, kind, label, language);
3109 // Note, due to side effects when changing track parameters, we have to
3110 // first append the track to the text track list.
3112 // 6. Add the new text track to the media element's list of text tracks.
3113 addTextTrack(textTrack);
3115 // ... its text track readiness state to the text track loaded state ...
3116 textTrack->setReadinessState(TextTrack::Loaded);
3118 // ... its text track mode to the text track hidden mode, and its text track list of cues to an empty list ...
3119 textTrack->setMode(TextTrack::hiddenKeyword());
3121 return textTrack.release();
3124 AudioTrackList* HTMLMediaElement::audioTracks()
3126 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3130 m_audioTracks = AudioTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
3132 return m_audioTracks.get();
3135 TextTrackList* HTMLMediaElement::textTracks()
3137 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3141 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
3143 return m_textTracks.get();
3146 VideoTrackList* HTMLMediaElement::videoTracks()
3148 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3152 m_videoTracks = VideoTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
3154 return m_videoTracks.get();
3157 void HTMLMediaElement::didAddTextTrack(HTMLTrackElement* trackElement)
3159 ASSERT(trackElement->hasTagName(trackTag));
3161 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3164 // 4.8.10.12.3 Sourcing out-of-band text tracks
3165 // When a track element's parent element changes and the new parent is a media element,
3166 // then the user agent must add the track element's corresponding text track to the
3167 // media element's list of text tracks ... [continues in TextTrackList::append]
3168 RefPtr<TextTrack> textTrack = trackElement->track();
3172 addTextTrack(textTrack.release());
3174 // Do not schedule the track loading until parsing finishes so we don't start before all tracks
3175 // in the markup have been added.
3176 if (!m_parsingInProgress)
3177 scheduleDelayedAction(ConfigureTextTracks);
3179 if (hasMediaControls())
3180 mediaControls()->closedCaptionTracksChanged();
3183 void HTMLMediaElement::didRemoveTextTrack(HTMLTrackElement* trackElement)
3185 ASSERT(trackElement->hasTagName(trackTag));
3187 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3191 if (trackElement->hasTagName(trackTag)) {
3192 KURL url = trackElement->getNonEmptyURLAttribute(srcAttr);
3193 LOG(Media, "HTMLMediaElement::didRemoveTrack - 'src' is %s", urlForLoggingMedia(url).utf8().data());
3197 RefPtr<TextTrack> textTrack = trackElement->track();
3201 textTrack->setHasBeenConfigured(false);
3206 // 4.8.10.12.3 Sourcing out-of-band text tracks
3207 // When a track element's parent element changes and the old parent was a media element,
3208 // then the user agent must remove the track element's corresponding text track from the
3209 // media element's list of text tracks.
3210 removeTextTrack(textTrack.get());
3212 size_t index = m_textTracksWhenResourceSelectionBegan.find(textTrack.get());
3213 if (index != notFound)
3214 m_textTracksWhenResourceSelectionBegan.remove(index);
3217 void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group)
3219 ASSERT(group.tracks.size());
3221 LOG(Media, "HTMLMediaElement::configureTextTrackGroup");
3223 Page* page = document().page();
3224 CaptionUserPreferences* captionPreferences = page? page->group().captionPreferences() : 0;
3225 CaptionUserPreferences::CaptionDisplayMode displayMode = captionPreferences ? captionPreferences->captionDisplayMode() : CaptionUserPreferences::Automatic;
3227 // First, find the track in the group that should be enabled (if any).
3228 Vector<RefPtr<TextTrack> > currentlyEnabledTracks;
3229 RefPtr<TextTrack> trackToEnable;
3230 RefPtr<TextTrack> defaultTrack;
3231 RefPtr<TextTrack> fallbackTrack;
3232 RefPtr<TextTrack> forcedSubitleTrack;
3233 int highestTrackScore = 0;
3234 int highestForcedScore = 0;
3235 for (size_t i = 0; i < group.tracks.size(); ++i) {
3236 RefPtr<TextTrack> textTrack = group.tracks[i];
3238 if (m_processingPreferenceChange && textTrack->mode() == TextTrack::showingKeyword())
3239 currentlyEnabledTracks.append(textTrack);
3241 int trackScore = captionPreferences ? captionPreferences->textTrackSelectionScore(textTrack.get(), this) : 0;
3242 LOG(Media, "HTMLMediaElement::configureTextTrackGroup - '%s' track with language '%s' has score %i", textTrack->kind().string().utf8().data(), textTrack->language().string().utf8().data(), trackScore);
3246 // * If the text track kind is { [subtitles or captions] [descriptions] } and the user has indicated an interest in having a
3247 // track with this text track kind, text track language, and text track label enabled, and there is no
3248 // other text track in the media element's list of text tracks with a text track kind of either subtitles
3249 // or captions whose text track mode is showing
3251 // * If the text track kind is chapters and the text track language is one that the user agent has reason
3252 // to believe is appropriate for the user, and there is no other text track in the media element's list of
3253 // text tracks with a text track kind of chapters whose text track mode is showing
3254 // Let the text track mode be showing.
3255 if (trackScore > highestTrackScore) {
3256 highestTrackScore = trackScore;
3257 trackToEnable = textTrack;
3260 if (!defaultTrack && textTrack->isDefault())
3261 defaultTrack = textTrack;
3262 if (!defaultTrack && !fallbackTrack)
3263 fallbackTrack = textTrack;
3264 if (textTrack->containsOnlyForcedSubtitles() && trackScore > highestForcedScore) {
3265 forcedSubitleTrack = textTrack;
3266 highestForcedScore = trackScore;
3268 } else if (!group.visibleTrack && !defaultTrack && textTrack->isDefault()) {
3269 // * If the track element has a default attribute specified, and there is no other text track in the media
3270 // element's list of text tracks whose text track mode is showing or showing by default
3271 // Let the text track mode be showing by default.
3272 if (group.kind != TrackGroup::CaptionsAndSubtitles || displayMode != CaptionUserPreferences::ForcedOnly)
3273 defaultTrack = textTrack;
3277 if (!trackToEnable && defaultTrack)
3278 trackToEnable = defaultTrack;
3280 // If no track matches the user's preferred language, none was marked as 'default', and there is a forced subtitle track
3281 // in the same language as the language of the primary audio track, enable it.
3282 if (!trackToEnable && forcedSubitleTrack)
3283 trackToEnable = forcedSubitleTrack;
3285 // If no track matches, don't disable an already visible track unless preferences say they all should be off.
3286 if (group.kind != TrackGroup::CaptionsAndSubtitles || displayMode != CaptionUserPreferences::ForcedOnly) {
3287 if (!trackToEnable && !defaultTrack && group.visibleTrack)
3288 trackToEnable = group.visibleTrack;
3291 // If no track matches the user's preferred language and non was marked 'default', enable the first track
3292 // because the user has explicitly stated a preference for this kind of track.
3293 if (!trackToEnable && fallbackTrack)
3294 trackToEnable = fallbackTrack;
3297 m_subtitleTrackLanguage = trackToEnable->language();
3299 m_subtitleTrackLanguage = emptyString();
3301 if (currentlyEnabledTracks.size()) {
3302 for (size_t i = 0; i < currentlyEnabledTracks.size(); ++i) {
3303 RefPtr<TextTrack> textTrack = currentlyEnabledTracks[i];
3304 if (textTrack != trackToEnable)
3305 textTrack->setMode(TextTrack::disabledKeyword());
3310 trackToEnable->setMode(TextTrack::showingKeyword());
3312 m_processingPreferenceChange = false;
3315 void HTMLMediaElement::setSelectedTextTrack(TextTrack* trackToSelect)
3317 TextTrackList* trackList = textTracks();
3318 if (!trackList || !trackList->length())
3321 if (trackToSelect != TextTrack::captionMenuOffItem() && trackToSelect != TextTrack::captionMenuAutomaticItem()) {
3322 if (!trackList->contains(trackToSelect))
3325 for (int i = 0, length = trackList->length(); i < length; ++i) {
3326 TextTrack* track = trackList->item(i);
3327 if (!trackToSelect || track != trackToSelect)
3328 track->setMode(TextTrack::disabledKeyword());
3330 track->setMode(TextTrack::showingKeyword());
3334 CaptionUserPreferences* captionPreferences = document().page() ? document().page()->group().captionPreferences() : 0;
3335 if (!captionPreferences)
3338 CaptionUserPreferences::CaptionDisplayMode displayMode = captionPreferences->captionDisplayMode();
3339 if (trackToSelect == TextTrack::captionMenuOffItem())
3340 displayMode = CaptionUserPreferences::ForcedOnly;
3341 else if (trackToSelect == TextTrack::captionMenuAutomaticItem())
3342 displayMode = CaptionUserPreferences::Automatic;
3344 displayMode = CaptionUserPreferences::AlwaysOn;
3345 if (trackToSelect->language().length())
3346 captionPreferences->setPreferredLanguage(trackToSelect->language());
3348 // Set m_captionDisplayMode here so we don't reconfigure again when the preference changed notification comes through.
3349 m_captionDisplayMode = displayMode;
3352 captionPreferences->setCaptionDisplayMode(displayMode);
3355 void HTMLMediaElement::configureTextTracks()
3357 TrackGroup captionAndSubtitleTracks(TrackGroup::CaptionsAndSubtitles);
3358 TrackGroup descriptionTracks(TrackGroup::Description);
3359 TrackGroup chapterTracks(TrackGroup::Chapter);
3360 TrackGroup metadataTracks(TrackGroup::Metadata);
3361 TrackGroup otherTracks(TrackGroup::Other);
3366 for (size_t i = 0; i < m_textTracks->length(); ++i) {
3367 RefPtr<TextTrack> textTrack = m_textTracks->item(i);
3371 String kind = textTrack->kind();
3372 TrackGroup* currentGroup;
3373 if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword() || kind == TextTrack::forcedKeyword())
3374 currentGroup = &captionAndSubtitleTracks;
3375 else if (kind == TextTrack::descriptionsKeyword())
3376 currentGroup = &descriptionTracks;
3377 else if (kind == TextTrack::chaptersKeyword())
3378 currentGroup = &chapterTracks;
3379 else if (kind == TextTrack::metadataKeyword())
3380 currentGroup = &metadataTracks;
3382 currentGroup = &otherTracks;
3384 if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::showingKeyword())
3385 currentGroup->visibleTrack = textTrack;
3386 if (!currentGroup->defaultTrack && textTrack->isDefault())
3387 currentGroup->defaultTrack = textTrack;
3389 // Do not add this track to the group if it has already been automatically configured
3390 // as we only want to call configureTextTrack once per track so that adding another
3391 // track after the initial configuration doesn't reconfigure every track - only those
3392 // that should be changed by the new addition. For example all metadata tracks are
3393 // disabled by default, and we don't want a track that has been enabled by script
3394 // to be disabled automatically when a new metadata track is added later.
3395 if (textTrack->hasBeenConfigured())
3398 if (textTrack->language().length())
3399 currentGroup->hasSrcLang = true;
3400 currentGroup->tracks.append(textTrack);
3403 if (captionAndSubtitleTracks.tracks.size())
3404 configureTextTrackGroup(captionAndSubtitleTracks);
3405 if (descriptionTracks.tracks.size())
3406 configureTextTrackGroup(descriptionTracks);
3407 if (chapterTracks.tracks.size())
3408 configureTextTrackGroup(chapterTracks);
3409 if (metadataTracks.tracks.size())
3410 configureTextTrackGroup(metadataTracks);
3411 if (otherTracks.tracks.size())
3412 configureTextTrackGroup(otherTracks);
3414 configureTextTrackDisplay();
3415 if (hasMediaControls())
3416 mediaControls()->closedCaptionTracksChanged();
3420 bool HTMLMediaElement::havePotentialSourceChild()
3422 // Stash the current <source> node and next nodes so we can restore them after checking
3423 // to see there is another potential.
3424 RefPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode;
3425 RefPtr<Node> nextNode = m_nextChildNodeToConsider;
3427 KURL nextURL = selectNextSourceChild(0, 0, DoNothing);
3429 m_currentSourceNode = currentSourceNode;
3430 m_nextChildNodeToConsider = nextNode;
3432 return nextURL.isValid();
3435 KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid)
3438 // Don't log if this was just called to find out if there are any valid <source> elements.
3439 bool shouldLog = actionIfInvalid != DoNothing;
3441 LOG(Media, "HTMLMediaElement::selectNextSourceChild");
3444 if (!m_nextChildNodeToConsider) {
3447 LOG(Media, "HTMLMediaElement::selectNextSourceChild -> 0x0000, \"\"");
3453 HTMLSourceElement* source = 0;
3456 bool lookingForStartNode = m_nextChildNodeToConsider;
3457 bool canUseSourceElement = false;
3458 bool okToLoadSourceURL;
3460 NodeVector potentialSourceNodes;
3461 getChildNodes(*this, potentialSourceNodes);
3463 for (unsigned i = 0; !canUseSourceElement && i < potentialSourceNodes.size(); ++i) {
3464 Node& node = potentialSourceNodes[i].get();
3465 if (lookingForStartNode && m_nextChildNodeToConsider != &node)
3467 lookingForStartNode = false;
3469 if (!node.hasTagName(sourceTag))
3471 if (node.parentNode() != this)
3474 source = static_cast<HTMLSourceElement*>(&node);
3476 // 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
3477 mediaURL = source->getNonEmptyURLAttribute(srcAttr);
3480 LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'src' is %s", urlForLoggingMedia(mediaURL).utf8().data());
3482 if (mediaURL.isEmpty())
3485 if (source->fastHasAttribute(mediaAttr)) {
3486 MediaQueryEvaluator screenEval("screen", document().frame(), renderer() ? renderer()->style() : 0);
3487 RefPtr<MediaQuerySet> media = MediaQuerySet::createAllowingDescriptionSyntax(source->media());
3490 LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'media' is %s", source->media().utf8().data());
3492 if (!screenEval.eval(media.get()))
3496 type = source->type();
3497 // FIXME(82965): Add support for keySystem in <source> and set system from source.