2 * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 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.
29 #include "HTMLMediaElement.h"
31 #include "ApplicationCacheHost.h"
32 #include "ApplicationCacheResource.h"
33 #include "Attribute.h"
35 #include "ChromeClient.h"
36 #include "ClientRect.h"
37 #include "ClientRectList.h"
38 #include "ContentSecurityPolicy.h"
39 #include "ContentType.h"
40 #include "CSSPropertyNames.h"
41 #include "CSSValueKeywords.h"
42 #include "DiagnosticLoggingKeys.h"
43 #include "DocumentLoader.h"
44 #include "ElementShadow.h"
46 #include "EventNames.h"
47 #include "ExceptionCode.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"
70 #include "NodeRenderingContext.h"
72 #include "RenderVideo.h"
73 #include "RenderView.h"
74 #include "ScriptController.h"
75 #include "ScriptEventListener.h"
76 #include "SecurityOrigin.h"
77 #include "SecurityPolicy.h"
79 #include "ShadowRoot.h"
80 #include "TimeRanges.h"
83 #include <wtf/CurrentTime.h>
84 #include <wtf/MathExtras.h>
85 #include <wtf/NonCopyingSort.h>
86 #include <wtf/Uint8Array.h>
87 #include <wtf/text/CString.h>
89 #if USE(ACCELERATED_COMPOSITING)
90 #include "RenderLayerCompositor.h"
93 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
94 #include "RenderEmbeddedObject.h"
98 #if ENABLE(VIDEO_TRACK)
99 #include "HTMLTrackElement.h"
100 #include "RuntimeEnabledFeatures.h"
101 #include "TextTrackCueList.h"
102 #include "TextTrackList.h"
105 #if ENABLE(WEB_AUDIO)
106 #include "AudioSourceProvider.h"
107 #include "MediaElementAudioSourceNode.h"
111 #include "DisplaySleepDisabler.h"
114 #if ENABLE(MEDIA_STREAM)
115 #include "MediaStreamRegistry.h"
118 #if ENABLE(TIZEN_EXTENSIBLE_API)
119 #include "TizenExtensibleAPI.h"
127 static String urlForLogging(const KURL& url)
129 static const unsigned maximumURLLengthForLogging = 128;
131 if (url.string().length() < maximumURLLengthForLogging)
133 return url.string().substring(0, maximumURLLengthForLogging) + "...";
136 static const char* boolString(bool val)
138 return val ? "true" : "false";
142 #ifndef LOG_MEDIA_EVENTS
143 // Default to not logging events because so many are generated they can overwhelm the rest of
145 #define LOG_MEDIA_EVENTS 0
148 #ifndef LOG_CACHED_TIME_WARNINGS
149 // Default to not logging warnings about excessive drift in the cached media time because it adds a
150 // fair amount of overhead and logging.
151 #define LOG_CACHED_TIME_WARNINGS 0
154 #if ENABLE(MEDIA_SOURCE)
155 // URL protocol used to signal that the media source API is being used.
156 static const char* mediaSourceURLProtocol = "x-media-source";
159 using namespace HTMLNames;
162 typedef HashMap<Document*, HashSet<HTMLMediaElement*> > DocumentElementSetMap;
163 static DocumentElementSetMap& documentToElementSetMap()
165 DEFINE_STATIC_LOCAL(DocumentElementSetMap, map, ());
169 static void addElementToDocumentMap(HTMLMediaElement* element, Document* document)
171 DocumentElementSetMap& map = documentToElementSetMap();
172 HashSet<HTMLMediaElement*> set = map.take(document);
174 map.add(document, set);
177 static void removeElementFromDocumentMap(HTMLMediaElement* element, Document* document)
179 DocumentElementSetMap& map = documentToElementSetMap();
180 HashSet<HTMLMediaElement*> set = map.take(document);
183 map.add(document, set);
186 #if ENABLE(ENCRYPTED_MEDIA)
187 static ExceptionCode exceptionCodeForMediaKeyException(MediaPlayer::MediaKeyException exception)
190 case MediaPlayer::NoError:
192 case MediaPlayer::InvalidPlayerState:
193 return INVALID_STATE_ERR;
194 case MediaPlayer::KeySystemNotSupported:
195 return NOT_SUPPORTED_ERR;
198 ASSERT_NOT_REACHED();
199 return INVALID_STATE_ERR;
203 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* document, bool createdByParser)
204 : HTMLElement(tagName, document)
205 , ActiveDOMObject(document, this)
206 , m_loadTimer(this, &HTMLMediaElement::loadTimerFired)
207 , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired)
208 , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired)
209 , m_playedTimeRanges()
210 , m_asyncEventQueue(GenericEventQueue::create(this))
211 , m_playbackRate(1.0f)
212 , m_defaultPlaybackRate(1.0f)
213 , m_webkitPreservesPitch(true)
214 , m_networkState(NETWORK_EMPTY)
215 , m_readyState(HAVE_NOTHING)
216 , m_readyStateMaximum(HAVE_NOTHING)
219 , m_previousProgressTime(numeric_limits<double>::max())
220 , m_lastTimeUpdateEventWallTime(0)
221 , m_lastTimeUpdateEventMovieTime(numeric_limits<float>::max())
222 , m_loadState(WaitingForSource)
223 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
226 , m_restrictions(RequireUserGestureForFullscreenRestriction | RequirePageConsentToLoadMediaRestriction)
227 , m_preload(MediaPlayer::Auto)
228 , m_displayMode(Unknown)
229 , m_processingMediaPlayerCallback(0)
230 #if ENABLE(MEDIA_SOURCE)
231 , m_sourceState(SOURCE_CLOSED)
233 , m_cachedTime(MediaPlayer::invalidTime())
234 , m_cachedTimeWallClockUpdateTime(0)
235 , m_minimumWallClockTimeToCacheMediaTime(0)
236 , m_fragmentStartTime(MediaPlayer::invalidTime())
237 , m_fragmentEndTime(MediaPlayer::invalidTime())
238 , m_pendingLoadFlags(0)
240 , m_isWaitingUntilMediaCanStart(false)
241 , m_shouldDelayLoadEvent(false)
242 , m_haveFiredLoadedData(false)
243 , m_inActiveDocument(true)
244 , m_autoplaying(true)
248 , m_sentStalledEvent(false)
249 , m_sentEndEvent(false)
250 , m_pausedInternal(false)
251 , m_sendProgressEvents(true)
252 , m_isFullscreen(false)
253 , m_closedCaptionsVisible(false)
254 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
255 , m_needWidgetUpdate(false)
257 , m_dispatchingCanPlayEvent(false)
258 , m_loadInitiatedByUserGesture(false)
259 , m_completelyLoaded(false)
260 , m_havePreparedToPlay(false)
261 , m_parsingInProgress(createdByParser)
262 #if ENABLE(VIDEO_TRACK)
263 , m_tracksAreReady(true)
264 , m_haveVisibleTextTrack(false)
265 , m_lastTextTrackUpdateTime(-1)
267 , m_ignoreTrackDisplayUpdate(0)
268 , m_disableCaptions(false)
270 #if ENABLE(WEB_AUDIO)
271 , m_audioSourceNode(0)
273 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
277 LOG(Media, "HTMLMediaElement::HTMLMediaElement");
278 document->registerForMediaVolumeCallbacks(this);
279 document->registerForPrivateBrowsingStateChangedCallbacks(this);
281 if (document->settings() && document->settings()->mediaPlaybackRequiresUserGesture()) {
282 addBehaviorRestriction(RequireUserGestureForRateChangeRestriction);
283 addBehaviorRestriction(RequireUserGestureForLoadRestriction);
286 #if ENABLE(MEDIA_SOURCE)
287 m_mediaSourceURL.setProtocol(mediaSourceURLProtocol);
288 m_mediaSourceURL.setPath(createCanonicalUUIDString());
291 setHasCustomCallbacks();
292 addElementToDocumentMap(this, document);
294 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
295 document->incrementActiveMediaObjectCount();
299 HTMLMediaElement::~HTMLMediaElement()
301 LOG(Media, "HTMLMediaElement::~HTMLMediaElement");
302 if (m_isWaitingUntilMediaCanStart)
303 document()->removeMediaCanStartListener(this);
304 setShouldDelayLoadEvent(false);
305 document()->unregisterForMediaVolumeCallbacks(this);
306 document()->unregisterForPrivateBrowsingStateChangedCallbacks(this);
307 #if ENABLE(VIDEO_TRACK)
309 m_textTracks->clearOwner();
311 for (unsigned i = 0; i < m_textTracks->length(); ++i)
312 m_textTracks->item(i)->clearClient();
316 if (m_mediaController)
317 m_mediaController->removeMediaElement(this);
319 removeElementFromDocumentMap(this, document());
321 m_completelyLoaded = true;
323 m_player->clearMediaPlayerClient();
325 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
326 document()->decrementActiveMediaObjectCount();
330 void HTMLMediaElement::didMoveToNewDocument(Document* oldDocument)
332 if (m_isWaitingUntilMediaCanStart) {
334 oldDocument->removeMediaCanStartListener(this);
335 document()->addMediaCanStartListener(this);
338 if (m_shouldDelayLoadEvent) {
340 oldDocument->decrementLoadEventDelayCount();
341 document()->incrementLoadEventDelayCount();
345 oldDocument->unregisterForMediaVolumeCallbacks(this);
346 removeElementFromDocumentMap(this, oldDocument);
349 document()->registerForMediaVolumeCallbacks(this);
350 addElementToDocumentMap(this, document());
352 HTMLElement::didMoveToNewDocument(oldDocument);
355 bool HTMLMediaElement::supportsFocus() const
357 if (ownerDocument()->isMediaDocument())
360 // If no controls specified, we should still be able to focus the element if it has tabIndex.
361 return controls() || HTMLElement::supportsFocus();
364 bool HTMLMediaElement::isMouseFocusable() const
369 void HTMLMediaElement::parseAttribute(const Attribute& attribute)
371 if (attribute.name() == srcAttr) {
372 // Trigger a reload, as long as the 'src' attribute is present.
373 if (fastHasAttribute(srcAttr))
374 scheduleLoad(MediaResource);
375 } else if (attribute.name() == controlsAttr)
376 configureMediaControls();
378 else if (attribute.name() == loopAttr)
379 updateDisableSleep();
381 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
382 else if (attribute.name() == mutedAttr)
385 else if (attribute.name() == preloadAttr) {
386 if (equalIgnoringCase(attribute.value(), "none"))
387 m_preload = MediaPlayer::None;
388 else if (equalIgnoringCase(attribute.value(), "metadata"))
389 m_preload = MediaPlayer::MetaData;
391 // The spec does not define an "invalid value default" but "auto" is suggested as the
392 // "missing value default", so use it for everything except "none" and "metadata"
393 m_preload = MediaPlayer::Auto;
396 // The attribute must be ignored if the autoplay attribute is present
397 if (!autoplay() && m_player)
398 m_player->setPreload(m_preload);
400 } else if (attribute.name() == mediagroupAttr)
401 setMediaGroup(attribute.value());
402 else if (attribute.name() == onabortAttr)
403 setAttributeEventListener(eventNames().abortEvent, createAttributeEventListener(this, attribute));
404 else if (attribute.name() == onbeforeloadAttr)
405 setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attribute));
406 else if (attribute.name() == oncanplayAttr)
407 setAttributeEventListener(eventNames().canplayEvent, createAttributeEventListener(this, attribute));
408 else if (attribute.name() == oncanplaythroughAttr)
409 setAttributeEventListener(eventNames().canplaythroughEvent, createAttributeEventListener(this, attribute));
410 else if (attribute.name() == ondurationchangeAttr)
411 setAttributeEventListener(eventNames().durationchangeEvent, createAttributeEventListener(this, attribute));
412 else if (attribute.name() == onemptiedAttr)
413 setAttributeEventListener(eventNames().emptiedEvent, createAttributeEventListener(this, attribute));
414 else if (attribute.name() == onendedAttr)
415 setAttributeEventListener(eventNames().endedEvent, createAttributeEventListener(this, attribute));
416 else if (attribute.name() == onerrorAttr)
417 setAttributeEventListener(eventNames().errorEvent, createAttributeEventListener(this, attribute));
418 else if (attribute.name() == onloadeddataAttr)
419 setAttributeEventListener(eventNames().loadeddataEvent, createAttributeEventListener(this, attribute));
420 else if (attribute.name() == onloadedmetadataAttr)
421 setAttributeEventListener(eventNames().loadedmetadataEvent, createAttributeEventListener(this, attribute));
422 else if (attribute.name() == onloadstartAttr)
423 setAttributeEventListener(eventNames().loadstartEvent, createAttributeEventListener(this, attribute));
424 else if (attribute.name() == onpauseAttr)
425 setAttributeEventListener(eventNames().pauseEvent, createAttributeEventListener(this, attribute));
426 else if (attribute.name() == onplayAttr)
427 setAttributeEventListener(eventNames().playEvent, createAttributeEventListener(this, attribute));
428 else if (attribute.name() == onplayingAttr)
429 setAttributeEventListener(eventNames().playingEvent, createAttributeEventListener(this, attribute));
430 else if (attribute.name() == onprogressAttr)
431 setAttributeEventListener(eventNames().progressEvent, createAttributeEventListener(this, attribute));
432 else if (attribute.name() == onratechangeAttr)
433 setAttributeEventListener(eventNames().ratechangeEvent, createAttributeEventListener(this, attribute));
434 else if (attribute.name() == onseekedAttr)
435 setAttributeEventListener(eventNames().seekedEvent, createAttributeEventListener(this, attribute));
436 else if (attribute.name() == onseekingAttr)
437 setAttributeEventListener(eventNames().seekingEvent, createAttributeEventListener(this, attribute));
438 else if (attribute.name() == onstalledAttr)
439 setAttributeEventListener(eventNames().stalledEvent, createAttributeEventListener(this, attribute));
440 else if (attribute.name() == onsuspendAttr)
441 setAttributeEventListener(eventNames().suspendEvent, createAttributeEventListener(this, attribute));
442 else if (attribute.name() == ontimeupdateAttr)
443 setAttributeEventListener(eventNames().timeupdateEvent, createAttributeEventListener(this, attribute));
444 else if (attribute.name() == onvolumechangeAttr)
445 setAttributeEventListener(eventNames().volumechangeEvent, createAttributeEventListener(this, attribute));
446 else if (attribute.name() == onwaitingAttr)
447 setAttributeEventListener(eventNames().waitingEvent, createAttributeEventListener(this, attribute));
448 else if (attribute.name() == onwebkitbeginfullscreenAttr)
449 setAttributeEventListener(eventNames().webkitbeginfullscreenEvent, createAttributeEventListener(this, attribute));
450 else if (attribute.name() == onwebkitendfullscreenAttr)
451 setAttributeEventListener(eventNames().webkitendfullscreenEvent, createAttributeEventListener(this, attribute));
452 #if ENABLE(MEDIA_SOURCE)
453 else if (attribute.name() == onwebkitsourcecloseAttr)
454 setAttributeEventListener(eventNames().webkitsourcecloseEvent, createAttributeEventListener(this, attribute));
455 else if (attribute.name() == onwebkitsourceendedAttr)
456 setAttributeEventListener(eventNames().webkitsourceendedEvent, createAttributeEventListener(this, attribute));
457 else if (attribute.name() == onwebkitsourceopenAttr)
458 setAttributeEventListener(eventNames().webkitsourceopenEvent, createAttributeEventListener(this, attribute));
461 HTMLElement::parseAttribute(attribute);
464 void HTMLMediaElement::finishParsingChildren()
466 HTMLElement::finishParsingChildren();
467 m_parsingInProgress = false;
469 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
470 document()->updateStyleIfNeeded();
471 createMediaPlayerProxy();
474 #if ENABLE(VIDEO_TRACK)
475 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
478 for (Node* node = firstChild(); node; node = node->nextSibling()) {
479 if (node->hasTagName(trackTag)) {
480 scheduleLoad(TextTrackResource);
487 bool HTMLMediaElement::rendererIsNeeded(const NodeRenderingContext& context)
489 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
490 UNUSED_PARAM(context);
491 Frame* frame = document()->frame();
497 return controls() ? HTMLElement::rendererIsNeeded(context) : false;
501 RenderObject* HTMLMediaElement::createRenderer(RenderArena* arena, RenderStyle*)
503 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
504 // Setup the renderer if we already have a proxy widget.
505 RenderEmbeddedObject* mediaRenderer = new (arena) RenderEmbeddedObject(this);
507 mediaRenderer->setWidget(m_proxyWidget);
509 if (Frame* frame = document()->frame())
510 frame->loader()->client()->showMediaPlayerProxyPlugin(m_proxyWidget.get());
512 return mediaRenderer;
514 return new (arena) RenderMedia(this);
518 bool HTMLMediaElement::childShouldCreateRenderer(const NodeRenderingContext& childContext) const
520 if (!hasMediaControls())
522 // <media> doesn't allow its content, including shadow subtree, to
523 // be rendered. So this should return false for most of the children.
524 // One exception is a shadow tree built for rendering controls which should be visible.
525 // So we let them go here by comparing its subtree root with one of the controls.
526 return (mediaControls()->treeScope() == childContext.node()->treeScope()
527 && childContext.isOnUpperEncapsulationBoundary() && HTMLElement::childShouldCreateRenderer(childContext));
530 Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode* insertionPoint)
532 LOG(Media, "HTMLMediaElement::insertedInto");
533 HTMLElement::insertedInto(insertionPoint);
534 if (insertionPoint->inDocument() && !getAttribute(srcAttr).isEmpty() && m_networkState == NETWORK_EMPTY)
535 scheduleLoad(MediaResource);
536 configureMediaControls();
537 return InsertionDone;
540 void HTMLMediaElement::removedFrom(ContainerNode* insertionPoint)
542 if (insertionPoint->inDocument()) {
543 LOG(Media, "HTMLMediaElement::removedFromDocument");
544 configureMediaControls();
545 if (m_networkState > NETWORK_EMPTY)
551 HTMLElement::removedFrom(insertionPoint);
554 void HTMLMediaElement::attach()
558 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
559 m_needWidgetUpdate = true;
562 HTMLElement::attach();
565 renderer()->updateFromElement();
566 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
567 else if (m_proxyWidget) {
568 if (Frame* frame = document()->frame())
569 frame->loader()->client()->hideMediaPlayerProxyPlugin(m_proxyWidget.get());
574 void HTMLMediaElement::didRecalcStyle(StyleChange)
577 renderer()->updateFromElement();
580 void HTMLMediaElement::scheduleLoad(LoadType loadType)
582 LOG(Media, "HTMLMediaElement::scheduleLoad");
584 if ((loadType & MediaResource) && !(m_pendingLoadFlags & MediaResource)) {
585 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
586 createMediaPlayerProxy();
590 m_pendingLoadFlags |= MediaResource;
593 #if ENABLE(VIDEO_TRACK)
594 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled() && (loadType & TextTrackResource))
595 m_pendingLoadFlags |= TextTrackResource;
598 if (!m_loadTimer.isActive())
599 m_loadTimer.startOneShot(0);
602 void HTMLMediaElement::scheduleNextSourceChild()
604 // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
605 m_pendingLoadFlags |= MediaResource;
606 m_loadTimer.startOneShot(0);
609 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
612 LOG(Media, "HTMLMediaElement::scheduleEvent - scheduling '%s'", eventName.string().ascii().data());
614 RefPtr<Event> event = Event::create(eventName, false, true);
615 event->setTarget(this);
617 m_asyncEventQueue->enqueueEvent(event.release());
620 void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*)
622 RefPtr<HTMLMediaElement> protect(this); // loadNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations.
624 #if ENABLE(VIDEO_TRACK)
625 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled() && (m_pendingLoadFlags & TextTrackResource))
626 configureTextTracks();
629 if (m_pendingLoadFlags & MediaResource) {
630 if (m_loadState == LoadingFromSourceElement)
631 loadNextSourceChild();
636 m_pendingLoadFlags = 0;
639 PassRefPtr<MediaError> HTMLMediaElement::error() const
644 void HTMLMediaElement::setSrc(const String& url)
646 setAttribute(srcAttr, url);
649 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
651 return m_networkState;
654 String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySystem, const KURL& url) const
656 MediaPlayer::SupportsType support = MediaPlayer::supportsType(ContentType(mimeType), keySystem, url, this);
662 case MediaPlayer::IsNotSupported:
665 case MediaPlayer::MayBeSupported:
668 case MediaPlayer::IsSupported:
669 canPlay = "probably";
673 LOG(Media, "HTMLMediaElement::canPlayType(%s, %s, %s) -> %s", mimeType.utf8().data(), keySystem.utf8().data(), url.string().utf8().data(), canPlay.utf8().data());
678 void HTMLMediaElement::load()
680 RefPtr<HTMLMediaElement> protect(this); // loadInternal may result in a 'beforeload' event, which can make arbitrary DOM mutations.
682 LOG(Media, "HTMLMediaElement::load()");
684 if (userGestureRequiredForLoad() && !ScriptController::processingUserGesture())
687 m_loadInitiatedByUserGesture = ScriptController::processingUserGesture();
688 if (m_loadInitiatedByUserGesture)
689 removeBehaviorsRestrictionsAfterFirstUserGesture();
695 void HTMLMediaElement::prepareForLoad()
697 LOG(Media, "HTMLMediaElement::prepareForLoad");
699 // Perform the cleanup required for the resource load algorithm to run.
700 stopPeriodicTimers();
702 m_sentEndEvent = false;
703 m_sentStalledEvent = false;
704 m_haveFiredLoadedData = false;
705 m_completelyLoaded = false;
706 m_havePreparedToPlay = false;
707 m_displayMode = Unknown;
709 // 1 - Abort any already-running instance of the resource selection algorithm for this element.
710 m_loadState = WaitingForSource;
711 m_currentSourceNode = 0;
713 // 2 - If there are any tasks from the media element's media element event task source in
714 // one of the task queues, then remove those tasks.
715 cancelPendingEventsAndCallbacks();
717 // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
718 // a task to fire a simple event named abort at the media element.
719 if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
720 scheduleEvent(eventNames().abortEvent);
722 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
726 m_player->cancelLoad();
728 createMediaPlayerProxy();
731 #if ENABLE(MEDIA_SOURCE)
732 if (m_sourceState != SOURCE_CLOSED)
733 setSourceState(SOURCE_CLOSED);
736 // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
737 if (m_networkState != NETWORK_EMPTY) {
738 m_networkState = NETWORK_EMPTY;
739 m_readyState = HAVE_NOTHING;
740 m_readyStateMaximum = HAVE_NOTHING;
744 invalidateCachedTime();
745 scheduleEvent(eventNames().emptiedEvent);
746 updateMediaController();
747 #if ENABLE(VIDEO_TRACK)
748 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
749 updateActiveTextTrackCues(0);
753 // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
754 setPlaybackRate(defaultPlaybackRate());
756 // 6 - Set the error attribute to null and the autoplaying flag to true.
758 m_autoplaying = true;
760 // 7 - Invoke the media element's resource selection algorithm.
762 // 8 - Note: Playback of any previously playing media resource for this element stops.
764 // The resource selection algorithm
765 // 1 - Set the networkState to NETWORK_NO_SOURCE
766 m_networkState = NETWORK_NO_SOURCE;
768 // 2 - Asynchronously await a stable state.
770 m_playedTimeRanges = TimeRanges::create();
773 // The spec doesn't say to block the load event until we actually run the asynchronous section
774 // algorithm, but do it now because we won't start that until after the timer fires and the
775 // event may have already fired by then.
776 setShouldDelayLoadEvent(true);
778 configureMediaControls();
781 void HTMLMediaElement::loadInternal()
783 // Some of the code paths below this function dispatch the BeforeLoad event. This ASSERT helps
784 // us catch those bugs more quickly without needing all the branches to align to actually
785 // trigger the event.
786 ASSERT(!eventDispatchForbidden());
788 // If we can't start a load right away, start it later.
789 Page* page = document()->page();
790 if (pageConsentRequiredForLoad() && page && !page->canStartMedia()) {
791 setShouldDelayLoadEvent(false);
792 if (m_isWaitingUntilMediaCanStart)
794 document()->addMediaCanStartListener(this);
795 m_isWaitingUntilMediaCanStart = true;
799 // Once the page has allowed an element to load media, it is free to load at will. This allows a
800 // playlist that starts in a foreground tab to continue automatically if the tab is subsequently
801 // put in the the background.
802 removeBehaviorRestriction(RequirePageConsentToLoadMediaRestriction);
804 #if ENABLE(VIDEO_TRACK)
805 // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the
806 // disabled state when the element's resource selection algorithm last started".
807 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) {
808 m_textTracksWhenResourceSelectionBegan.clear();
810 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
811 TextTrack* track = m_textTracks->item(i);
812 if (track->mode() != TextTrack::disabledKeyword())
813 m_textTracksWhenResourceSelectionBegan.append(track);
819 selectMediaResource();
822 void HTMLMediaElement::selectMediaResource()
824 LOG(Media, "HTMLMediaElement::selectMediaResource");
826 enum Mode { attribute, children };
828 // 3 - If the media element has a src attribute, then let mode be attribute.
829 Mode mode = attribute;
830 if (!fastHasAttribute(srcAttr)) {
832 for (node = firstChild(); node; node = node->nextSibling()) {
833 if (node->hasTagName(sourceTag))
837 // Otherwise, if the media element does not have a src attribute but has a source
838 // element child, then let mode be children and let candidate be the first such
839 // source element child in tree order.
842 m_nextChildNodeToConsider = node;
843 m_currentSourceNode = 0;
845 // Otherwise the media element has neither a src attribute nor a source element
846 // child: set the networkState to NETWORK_EMPTY, and abort these steps; the
847 // synchronous section ends.
848 m_loadState = WaitingForSource;
849 setShouldDelayLoadEvent(false);
850 m_networkState = NETWORK_EMPTY;
852 LOG(Media, "HTMLMediaElement::selectMediaResource, nothing to load");
857 // 4 - Set the media element's delaying-the-load-event flag to true (this delays the load event),
858 // and set its networkState to NETWORK_LOADING.
859 setShouldDelayLoadEvent(true);
860 m_networkState = NETWORK_LOADING;
862 // 5 - Queue a task to fire a simple event named loadstart at the media element.
863 scheduleEvent(eventNames().loadstartEvent);
865 // 6 - If mode is attribute, then run these substeps
866 if (mode == attribute) {
867 m_loadState = LoadingFromSrcAttr;
869 // If the src attribute's value is the empty string ... jump down to the failed step below
870 KURL mediaURL = getNonEmptyURLAttribute(srcAttr);
871 if (mediaURL.isEmpty()) {
872 mediaLoadingFailed(MediaPlayer::FormatError);
873 LOG(Media, "HTMLMediaElement::selectMediaResource, empty 'src'");
877 if (!isSafeToLoadURL(mediaURL, Complain) || !dispatchBeforeLoadEvent(mediaURL.string())) {
878 mediaLoadingFailed(MediaPlayer::FormatError);
882 // No type or key system information is available when the url comes
883 // from the 'src' attribute so MediaPlayer
884 // will have to pick a media engine based on the file extension.
885 ContentType contentType((String()));
886 loadResource(mediaURL, contentType, String());
887 LOG(Media, "HTMLMediaElement::selectMediaResource, using 'src' attribute url");
891 // Otherwise, the source elements will be used
892 loadNextSourceChild();
895 void HTMLMediaElement::loadNextSourceChild()
897 ContentType contentType((String()));
899 KURL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain);
900 if (!mediaURL.isValid()) {
901 waitForSourceChange();
905 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
906 // Recreate the media player for the new url
910 m_loadState = LoadingFromSourceElement;
911 loadResource(mediaURL, contentType, keySystem);
914 #if !PLATFORM(CHROMIUM)
915 static KURL createFileURLForApplicationCacheResource(const String& path)
917 // KURL should have a function to create a url from a path, but it does not. This function
918 // is not suitable because KURL::setPath uses encodeWithURLEscapeSequences, which it notes
919 // does not correctly escape '#' and '?'. This function works for our purposes because
920 // app cache media files are always created with encodeForFileName(createCanonicalUUIDString()).
922 #if USE(CF) && PLATFORM(WIN)
923 RetainPtr<CFStringRef> cfPath(AdoptCF, path.createCFString());
924 RetainPtr<CFURLRef> cfURL(AdoptCF, CFURLCreateWithFileSystemPath(0, cfPath.get(), kCFURLWindowsPathStyle, false));
925 KURL url(cfURL.get());
929 url.setProtocol("file");
936 void HTMLMediaElement::loadResource(const KURL& initialURL, ContentType& contentType, const String& keySystem)
938 ASSERT(isSafeToLoadURL(initialURL, Complain));
940 LOG(Media, "HTMLMediaElement::loadResource(%s, %s, %s)", urlForLogging(initialURL).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data());
942 Frame* frame = document()->frame();
944 mediaLoadingFailed(MediaPlayer::FormatError);
948 KURL url = initialURL;
949 if (!frame->loader()->willLoadMediaElementURL(url)) {
950 mediaLoadingFailed(MediaPlayer::FormatError);
954 #if ENABLE(MEDIA_SOURCE)
955 // If this is a media source URL, make sure it is the one for this media element.
956 if (url.protocolIs(mediaSourceURLProtocol) && url != m_mediaSourceURL) {
957 mediaLoadingFailed(MediaPlayer::FormatError);
962 // The resource fetch algorithm
963 m_networkState = NETWORK_LOADING;
965 #if !PLATFORM(CHROMIUM)
966 // If the url should be loaded from the application cache, pass the url of the cached file
967 // to the media engine.
968 ApplicationCacheHost* cacheHost = frame->loader()->documentLoader()->applicationCacheHost();
969 ApplicationCacheResource* resource = 0;
970 if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource)) {
971 // Resources that are not present in the manifest will always fail to load (at least, after the
972 // cache has been primed the first time), making the testing of offline applications simpler.
973 if (!resource || resource->path().isEmpty()) {
974 mediaLoadingFailed(MediaPlayer::NetworkError);
980 // Set m_currentSrc *before* changing to the cache url, the fact that we are loading from the app
981 // cache is an internal detail not exposed through the media element API.
984 #if !PLATFORM(CHROMIUM)
986 url = createFileURLForApplicationCacheResource(resource->path());
987 LOG(Media, "HTMLMediaElement::loadResource - will load from app cache -> %s", urlForLogging(url).utf8().data());
991 LOG(Media, "HTMLMediaElement::loadResource - m_currentSrc -> %s", urlForLogging(m_currentSrc).utf8().data());
993 #if ENABLE(MEDIA_STREAM)
994 if (MediaStreamRegistry::registry().lookupMediaStreamDescriptor(url.string()))
995 removeBehaviorRestriction(RequireUserGestureForRateChangeRestriction);
998 if (m_sendProgressEvents)
999 startProgressEventTimer();
1001 Settings* settings = document()->settings();
1002 bool privateMode = !settings || settings->privateBrowsingEnabled();
1003 m_player->setPrivateBrowsingMode(privateMode);
1005 // Reset display mode to force a recalculation of what to show because we are resetting the player.
1006 setDisplayMode(Unknown);
1009 m_player->setPreload(m_preload);
1010 m_player->setPreservesPitch(m_webkitPreservesPitch);
1012 #if !ENABLE(TIZEN_GSTREAMER_AUDIO)
1013 if (fastHasAttribute(mutedAttr))
1018 if (!m_player->load(url, contentType, keySystem))
1019 mediaLoadingFailed(MediaPlayer::FormatError);
1021 #if ENABLE(TIZEN_GSTREAMER_AUDIO)
1025 // If there is no poster to display, allow the media engine to render video frames as soon as
1026 // they are available.
1027 updateDisplayState();
1030 renderer()->updateFromElement();
1033 #if ENABLE(VIDEO_TRACK)
1034 static bool trackIndexCompare(TextTrack* a,
1037 return a->trackIndex() - b->trackIndex() < 0;
1040 static bool eventTimeCueCompare(const std::pair<double, TextTrackCue*>& a,
1041 const std::pair<double, TextTrackCue*>& b)
1043 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1045 if (a.first != b.first)
1046 return a.first - b.first < 0;
1048 // If the cues belong to different text tracks, it doesn't make sense to
1049 // compare the two tracks by the relative cue order, so return the relative
1051 if (a.second->track() != b.second->track())
1052 return trackIndexCompare(a.second->track(), b.second->track());
1054 // 12 - Further sort tasks in events that have the same time by the
1055 // relative text track cue order of the text track cues associated
1056 // with these tasks.
1057 return a.second->cueIndex() - b.second->cueIndex() < 0;
1061 void HTMLMediaElement::updateActiveTextTrackCues(float movieTime)
1063 LOG(Media, "HTMLMediaElement::updateActiveTextTracks");
1065 // 4.8.10.8 Playing the media resource
1067 // If the current playback position changes while the steps are running,
1068 // then the user agent must wait for the steps to complete, and then must
1069 // immediately rerun the steps.
1070 if (ignoreTrackDisplayUpdateRequests())
1073 // 1 - Let current cues be a list of cues, initialized to contain all the
1074 // cues of all the hidden, showing, or showing by default text tracks of the
1075 // media element (not the disabled ones) whose start times are less than or
1076 // equal to the current playback position and whose end times are greater
1077 // than the current playback position.
1078 Vector<CueIntervalTree::IntervalType> currentCues;
1080 // The user agent must synchronously unset [the text track cue active] flag
1081 // whenever ... the media element's readyState is changed back to HAVE_NOTHING.
1082 if (m_readyState != HAVE_NOTHING && m_player)
1083 currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime));
1085 Vector<CueIntervalTree::IntervalType> affectedCues;
1086 Vector<CueIntervalTree::IntervalType> previousCues;
1087 Vector<CueIntervalTree::IntervalType> missedCues;
1089 // 2 - Let other cues be a list of cues, initialized to contain all the cues
1090 // of hidden, showing, and showing by default text tracks of the media
1091 // element that are not present in current cues.
1092 previousCues = m_currentlyActiveCues;
1094 // 3 - Let last time be the current playback position at the time this
1095 // algorithm was last run for this media element, if this is not the first
1097 float lastTime = m_lastTextTrackUpdateTime;
1099 // 4 - If the current playback position has, since the last time this
1100 // algorithm was run, only changed through its usual monotonic increase
1101 // during normal playback, then let missed cues be the list of cues in other
1102 // cues whose start times are greater than or equal to last time and whose
1103 // end times are less than or equal to the current playback position.
1104 // Otherwise, let missed cues be an empty list.
1105 if (lastTime >= 0 && m_lastSeekTime < movieTime) {
1106 Vector<CueIntervalTree::IntervalType> potentiallySkippedCues =
1107 m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime));
1109 for (size_t i = 0; i < potentiallySkippedCues.size(); ++i) {
1110 float cueStartTime = potentiallySkippedCues[i].low();
1111 float cueEndTime = potentiallySkippedCues[i].high();
1113 // Consider cues that may have been missed since the last seek time.
1114 if (cueStartTime > max(m_lastSeekTime, lastTime) && cueEndTime < movieTime)
1115 missedCues.append(potentiallySkippedCues[i]);
1119 m_lastTextTrackUpdateTime = movieTime;
1121 // 5 - If the time was reached through the usual monotonic increase of the
1122 // current playback position during normal playback, and if the user agent
1123 // has not fired a timeupdate event at the element in the past 15 to 250ms
1124 // and is not still running event handlers for such an event, then the user
1125 // agent must queue a task to fire a simple event named timeupdate at the
1126 // element. (In the other cases, such as explicit seeks, relevant events get
1127 // fired as part of the overall process of changing the current playback
1129 if (m_lastSeekTime <= lastTime)
1130 scheduleTimeupdateEvent(false);
1132 // Explicitly cache vector sizes, as their content is constant from here.
1133 size_t currentCuesSize = currentCues.size();
1134 size_t missedCuesSize = missedCues.size();
1135 size_t previousCuesSize = previousCues.size();
1137 // 6 - If all of the cues in current cues have their text track cue active
1138 // flag set, none of the cues in other cues have their text track cue active
1139 // flag set, and missed cues is empty, then abort these steps.
1140 bool activeSetChanged = missedCuesSize;
1142 for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i)
1143 if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive())
1144 activeSetChanged = true;
1146 for (size_t i = 0; i < currentCuesSize; ++i) {
1147 currentCues[i].data()->updateDisplayTree(movieTime);
1149 if (!currentCues[i].data()->isActive())
1150 activeSetChanged = true;
1153 if (!activeSetChanged) {
1154 // Even though the active set has not changed, it is possible that the
1155 // the mode of a track has changed from 'hidden' to 'showing' and the
1156 // cues have not yet been rendered.
1157 if (hasMediaControls())
1158 mediaControls()->updateTextTrackDisplay();
1163 // 7 - If the time was reached through the usual monotonic increase of the
1164 // current playback position during normal playback, and there are cues in
1165 // other cues that have their text track cue pause-on-exi flag set and that
1166 // either have their text track cue active flag set or are also in missed
1167 // cues, then immediately pause the media element.
1168 for (size_t i = 0; !m_paused && i < previousCuesSize; ++i) {
1169 if (previousCues[i].data()->pauseOnExit()
1170 && previousCues[i].data()->isActive()
1171 && !currentCues.contains(previousCues[i]))
1175 for (size_t i = 0; !m_paused && i < missedCuesSize; ++i) {
1176 if (missedCues[i].data()->pauseOnExit())
1180 // 8 - Let events be a list of tasks, initially empty. Each task in this
1181 // list will be associated with a text track, a text track cue, and a time,
1182 // which are used to sort the list before the tasks are queued.
1183 Vector<std::pair<double, TextTrackCue*> > eventTasks;
1185 // 8 - Let affected tracks be a list of text tracks, initially empty.
1186 Vector<TextTrack*> affectedTracks;
1188 for (size_t i = 0; i < missedCuesSize; ++i) {
1189 // 9 - For each text track cue in missed cues, prepare an event named enter
1190 // for the TextTrackCue object with the text track cue start time.
1191 eventTasks.append(std::make_pair(missedCues[i].data()->startTime(),
1192 missedCues[i].data()));
1194 // 10 - For each text track [...] in missed cues, prepare an event
1195 // named exit for the TextTrackCue object with the with the later of
1196 // the text track cue end time and the text track cue start time.
1198 // Note: An explicit task is added only if the cue is NOT a zero or
1199 // negative length cue. Otherwise, the need for an exit event is
1200 // checked when these tasks are actually queued below. This doesn't
1201 // affect sorting events before dispatch either, because the exit
1202 // event has the same time as the enter event.
1203 if (missedCues[i].data()->startTime() < missedCues[i].data()->endTime())
1204 eventTasks.append(std::make_pair(missedCues[i].data()->endTime(),
1205 missedCues[i].data()));
1208 for (size_t i = 0; i < previousCuesSize; ++i) {
1209 // 10 - For each text track cue in other cues that has its text
1210 // track cue active flag set prepare an event named exit for the
1211 // TextTrackCue object with the text track cue end time.
1212 if (!currentCues.contains(previousCues[i]))
1213 eventTasks.append(std::make_pair(previousCues[i].data()->endTime(),
1214 previousCues[i].data()));
1217 for (size_t i = 0; i < currentCuesSize; ++i) {
1218 // 11 - For each text track cue in current cues that does not have its
1219 // text track cue active flag set, prepare an event named enter for the
1220 // TextTrackCue object with the text track cue start time.
1221 if (!previousCues.contains(currentCues[i]))
1222 eventTasks.append(std::make_pair(currentCues[i].data()->startTime(),
1223 currentCues[i].data()));
1226 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1228 nonCopyingSort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare);
1230 for (size_t i = 0; i < eventTasks.size(); ++i) {
1231 if (!affectedTracks.contains(eventTasks[i].second->track()))
1232 affectedTracks.append(eventTasks[i].second->track());
1234 // 13 - Queue each task in events, in list order.
1235 RefPtr<Event> event;
1237 // Each event in eventTasks may be either an enterEvent or an exitEvent,
1238 // depending on the time that is associated with the event. This
1239 // correctly identifies the type of the event, if the startTime is
1240 // less than the endTime in the cue.
1241 if (eventTasks[i].second->startTime() >= eventTasks[i].second->endTime()) {
1242 event = Event::create(eventNames().enterEvent, false, false);
1243 event->setTarget(eventTasks[i].second);
1244 m_asyncEventQueue->enqueueEvent(event.release());
1246 event = Event::create(eventNames().exitEvent, false, false);
1247 event->setTarget(eventTasks[i].second);
1248 m_asyncEventQueue->enqueueEvent(event.release());
1250 if (eventTasks[i].first == eventTasks[i].second->startTime())
1251 event = Event::create(eventNames().enterEvent, false, false);
1253 event = Event::create(eventNames().exitEvent, false, false);
1255 event->setTarget(eventTasks[i].second);
1256 m_asyncEventQueue->enqueueEvent(event.release());
1260 // 14 - Sort affected tracks in the same order as the text tracks appear in
1261 // the media element's list of text tracks, and remove duplicates.
1262 nonCopyingSort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare);
1264 // 15 - For each text track in affected tracks, in the list order, queue a
1265 // task to fire a simple event named cuechange at the TextTrack object, and, ...
1266 for (size_t i = 0; i < affectedTracks.size(); ++i) {
1267 RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false);
1268 event->setTarget(affectedTracks[i]);
1270 m_asyncEventQueue->enqueueEvent(event.release());
1272 // ... if the text track has a corresponding track element, to then fire a
1273 // simple event named cuechange at the track element as well.
1274 if (affectedTracks[i]->trackType() == TextTrack::TrackElement) {
1275 RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false);
1276 HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(affectedTracks[i])->trackElement();
1277 ASSERT(trackElement);
1278 event->setTarget(trackElement);
1280 m_asyncEventQueue->enqueueEvent(event.release());
1284 // 16 - Set the text track cue active flag of all the cues in the current
1285 // cues, and unset the text track cue active flag of all the cues in the
1287 for (size_t i = 0; i < currentCuesSize; ++i)
1288 currentCues[i].data()->setIsActive(true);
1290 for (size_t i = 0; i < previousCuesSize; ++i)
1291 if (!currentCues.contains(previousCues[i]))
1292 previousCues[i].data()->setIsActive(false);
1294 // Update the current active cues.
1295 m_currentlyActiveCues = currentCues;
1297 if (activeSetChanged && hasMediaControls())
1298 mediaControls()->updateTextTrackDisplay();
1301 bool HTMLMediaElement::textTracksAreReady() const
1303 // 4.8.10.12.1 Text track model
1305 // The text tracks of a media element are ready if all the text tracks whose mode was not
1306 // in the disabled state when the element's resource selection algorithm last started now
1307 // have a text track readiness state of loaded or failed to load.
1308 for (unsigned i = 0; i < m_textTracksWhenResourceSelectionBegan.size(); ++i) {
1309 if (m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::Loading)
1316 void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track)
1318 if (m_player && m_textTracksWhenResourceSelectionBegan.contains(track)) {
1319 if (track->readinessState() != TextTrack::Loading)
1320 setReadyState(m_player->readyState());
1324 void HTMLMediaElement::textTrackModeChanged(TextTrack* track)
1326 if (track->trackType() == TextTrack::TrackElement) {
1327 // 4.8.10.12.3 Sourcing out-of-band text tracks
1328 // ... when a text track corresponding to a track element is created with text track
1329 // mode set to disabled and subsequently changes its text track mode to hidden, showing,
1330 // or showing by default for the first time, the user agent must immediately and synchronously
1331 // run the following algorithm ...
1333 for (Node* node = firstChild(); node; node = node->nextSibling()) {
1334 if (!node->hasTagName(trackTag))
1336 HTMLTrackElement* trackElement = static_cast<HTMLTrackElement*>(node);
1337 if (trackElement->track() != track)
1340 // Mark this track as "configured" so configureTextTracks won't change the mode again.
1341 trackElement->setHasBeenConfigured(true);
1342 if (track->mode() != TextTrack::disabledKeyword()) {
1343 if (trackElement->readyState() == HTMLTrackElement::LOADED)
1344 textTrackAddCues(track, track->cues());
1345 else if (trackElement->readyState() == HTMLTrackElement::NONE)
1346 trackElement->scheduleLoad();
1348 // If this is the first added track, create the list of text tracks.
1350 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
1356 configureTextTrackDisplay();
1357 updateActiveTextTrackCues(currentTime());
1360 void HTMLMediaElement::textTrackKindChanged(TextTrack* track)
1362 if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword() && track->mode() == TextTrack::showingKeyword())
1363 track->setMode(TextTrack::hiddenKeyword());
1366 void HTMLMediaElement::textTrackAddCues(TextTrack*, const TextTrackCueList* cues)
1368 beginIgnoringTrackDisplayUpdateRequests();
1369 for (size_t i = 0; i < cues->length(); ++i)
1370 textTrackAddCue(cues->item(i)->track(), cues->item(i));
1371 endIgnoringTrackDisplayUpdateRequests();
1372 updateActiveTextTrackCues(currentTime());
1375 void HTMLMediaElement::textTrackRemoveCues(TextTrack*, const TextTrackCueList* cues)
1377 beginIgnoringTrackDisplayUpdateRequests();
1378 for (size_t i = 0; i < cues->length(); ++i)
1379 textTrackRemoveCue(cues->item(i)->track(), cues->item(i));
1380 endIgnoringTrackDisplayUpdateRequests();
1381 updateActiveTextTrackCues(currentTime());
1384 void HTMLMediaElement::textTrackAddCue(TextTrack*, PassRefPtr<TextTrackCue> cue)
1386 // Negative duration cues need be treated in the interval tree as
1387 // zero-length cues.
1388 double endTime = max(cue->startTime(), cue->endTime());
1390 m_cueTree.add(m_cueTree.createInterval(cue->startTime(), endTime, cue.get()));
1391 updateActiveTextTrackCues(currentTime());
1394 void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtr<TextTrackCue> cue)
1396 // Negative duration cues need to be treated in the interval tree as
1397 // zero-length cues.
1398 double endTime = max(cue->startTime(), cue->endTime());
1400 m_cueTree.remove(m_cueTree.createInterval(cue->startTime(), endTime, cue.get()));
1401 updateActiveTextTrackCues(currentTime());
1406 bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidURLAction actionIfInvalid)
1408 if (!url.isValid()) {
1409 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE because url is invalid", urlForLogging(url).utf8().data());
1413 Frame* frame = document()->frame();
1414 if (!frame || !document()->securityOrigin()->canDisplay(url)) {
1415 if (actionIfInvalid == Complain)
1416 FrameLoader::reportLocalLoadFailed(frame, url.string());
1417 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE rejected by SecurityOrigin", urlForLogging(url).utf8().data());
1421 if (!document()->contentSecurityPolicy()->allowMediaFromSource(url)) {
1422 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> rejected by Content Security Policy", urlForLogging(url).utf8().data());
1429 void HTMLMediaElement::startProgressEventTimer()
1431 if (m_progressEventTimer.isActive())
1434 m_previousProgressTime = WTF::currentTime();
1435 // 350ms is not magic, it is in the spec!
1436 m_progressEventTimer.startRepeating(0.350);
1439 void HTMLMediaElement::waitForSourceChange()
1441 LOG(Media, "HTMLMediaElement::waitForSourceChange");
1443 stopPeriodicTimers();
1444 m_loadState = WaitingForSource;
1446 // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value
1447 m_networkState = NETWORK_NO_SOURCE;
1449 // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1450 setShouldDelayLoadEvent(false);
1452 updateDisplayState();
1455 renderer()->updateFromElement();
1458 void HTMLMediaElement::noneSupported()
1460 LOG(Media, "HTMLMediaElement::noneSupported");
1462 stopPeriodicTimers();
1463 m_loadState = WaitingForSource;
1464 m_currentSourceNode = 0;
1467 // 6 - Reaching this step indicates that the media resource failed to load or that the given
1468 // URL could not be resolved. In one atomic operation, run the following steps:
1470 // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to
1471 // MEDIA_ERR_SRC_NOT_SUPPORTED.
1472 m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
1474 // 6.2 - Forget the media element's media-resource-specific text tracks.
1476 // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
1477 m_networkState = NETWORK_NO_SOURCE;
1479 // 7 - Queue a task to fire a simple event named error at the media element.
1480 scheduleEvent(eventNames().errorEvent);
1482 #if ENABLE(MEDIA_SOURCE)
1483 if (m_sourceState != SOURCE_CLOSED)
1484 setSourceState(SOURCE_CLOSED);
1487 // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1488 setShouldDelayLoadEvent(false);
1490 // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed,
1491 // the element won't attempt to load another resource.
1493 updateDisplayState();
1496 renderer()->updateFromElement();
1499 void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err)
1501 LOG(Media, "HTMLMediaElement::mediaEngineError(%d)", static_cast<int>(err->code()));
1503 // 1 - The user agent should cancel the fetching process.
1504 stopPeriodicTimers();
1505 m_loadState = WaitingForSource;
1507 // 2 - Set the error attribute to a new MediaError object whose code attribute is
1508 // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
1511 // 3 - Queue a task to fire a simple event named error at the media element.
1512 scheduleEvent(eventNames().errorEvent);
1514 #if ENABLE(MEDIA_SOURCE)
1515 if (m_sourceState != SOURCE_CLOSED)
1516 setSourceState(SOURCE_CLOSED);
1519 // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a
1520 // task to fire a simple event called emptied at the element.
1521 m_networkState = NETWORK_EMPTY;
1522 scheduleEvent(eventNames().emptiedEvent);
1524 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1525 setShouldDelayLoadEvent(false);
1527 // 6 - Abort the overall resource selection algorithm.
1528 m_currentSourceNode = 0;
1531 void HTMLMediaElement::cancelPendingEventsAndCallbacks()
1533 LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks");
1534 m_asyncEventQueue->cancelAllEvents();
1536 for (Node* node = firstChild(); node; node = node->nextSibling()) {
1537 if (node->hasTagName(sourceTag))
1538 static_cast<HTMLSourceElement*>(node)->cancelPendingErrorEvent();
1542 Document* HTMLMediaElement::mediaPlayerOwningDocument()
1544 Document* d = document();
1547 d = ownerDocument();
1552 void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*)
1554 beginProcessingMediaPlayerCallback();
1555 setNetworkState(m_player->networkState());
1556 endProcessingMediaPlayerCallback();
1559 static String stringForNetworkState(MediaPlayer::NetworkState state)
1562 case MediaPlayer::Empty: return "Empty";
1563 case MediaPlayer::Idle: return "Idle";
1564 case MediaPlayer::Loading: return "Loading";
1565 case MediaPlayer::Loaded: return "Loaded";
1566 case MediaPlayer::FormatError: return "FormatError";
1567 case MediaPlayer::NetworkError: return "NetworkError";
1568 case MediaPlayer::DecodeError: return "DecodeError";
1569 default: return emptyString();
1573 void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error)
1575 stopPeriodicTimers();
1577 // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
1578 // <source> children, schedule the next one
1579 if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
1581 if (m_currentSourceNode)
1582 m_currentSourceNode->scheduleErrorEvent();
1584 LOG(Media, "HTMLMediaElement::setNetworkState - error event not sent, <source> was removed");
1586 if (havePotentialSourceChild()) {
1587 LOG(Media, "HTMLMediaElement::setNetworkState - scheduling next <source>");
1588 scheduleNextSourceChild();
1590 LOG(Media, "HTMLMediaElement::setNetworkState - no more <source> elements, waiting");
1591 waitForSourceChange();
1597 if (error == MediaPlayer::NetworkError && m_readyState >= HAVE_METADATA)
1598 mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK));
1599 else if (error == MediaPlayer::DecodeError)
1600 mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE));
1601 else if ((error == MediaPlayer::FormatError || error == MediaPlayer::NetworkError) && m_loadState == LoadingFromSrcAttr)
1604 updateDisplayState();
1605 if (hasMediaControls()) {
1606 mediaControls()->reset();
1607 mediaControls()->reportedError();
1610 if (document()->page() && document()->page()->settings()->diagnosticLoggingEnabled())
1611 document()->page()->chrome()->client()->logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadingFailedKey(), stringForNetworkState(error), DiagnosticLoggingKeys::failKey());
1614 void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
1616 LOG(Media, "HTMLMediaElement::setNetworkState(%d) - current state is %d", static_cast<int>(state), static_cast<int>(m_networkState));
1618 if (state == MediaPlayer::Empty) {
1619 // Just update the cached state and leave, we can't do anything.
1620 m_networkState = NETWORK_EMPTY;
1624 if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) {
1625 mediaLoadingFailed(state);
1629 if (state == MediaPlayer::Idle) {
1630 if (m_networkState > NETWORK_IDLE) {
1631 changeNetworkStateFromLoadingToIdle();
1632 setShouldDelayLoadEvent(false);
1634 m_networkState = NETWORK_IDLE;
1638 if (state == MediaPlayer::Loading) {
1639 if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE)
1640 startProgressEventTimer();
1641 m_networkState = NETWORK_LOADING;
1644 if (state == MediaPlayer::Loaded) {
1645 if (m_networkState != NETWORK_IDLE)
1646 changeNetworkStateFromLoadingToIdle();
1647 m_completelyLoaded = true;
1650 if (hasMediaControls())
1651 mediaControls()->updateStatusDisplay();
1654 void HTMLMediaElement::changeNetworkStateFromLoadingToIdle()
1656 m_progressEventTimer.stop();
1657 if (hasMediaControls() && m_player->didLoadingProgress())
1658 mediaControls()->bufferingProgressed();
1660 // Schedule one last progress event so we guarantee that at least one is fired
1661 // for files that load very quickly.
1662 scheduleEvent(eventNames().progressEvent);
1663 scheduleEvent(eventNames().suspendEvent);
1664 m_networkState = NETWORK_IDLE;
1667 void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*)
1669 beginProcessingMediaPlayerCallback();
1671 setReadyState(m_player->readyState());
1673 endProcessingMediaPlayerCallback();
1676 void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
1678 LOG(Media, "HTMLMediaElement::setReadyState(%d) - current state is %d,", static_cast<int>(state), static_cast<int>(m_readyState));
1680 // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it
1681 bool wasPotentiallyPlaying = potentiallyPlaying();
1683 ReadyState oldState = m_readyState;
1684 ReadyState newState = static_cast<ReadyState>(state);
1686 #if ENABLE(VIDEO_TRACK)
1687 bool tracksAreReady = !RuntimeEnabledFeatures::webkitVideoTrackEnabled() || textTracksAreReady();
1689 if (newState == oldState && m_tracksAreReady == tracksAreReady)
1692 m_tracksAreReady = tracksAreReady;
1694 if (newState == oldState)
1696 bool tracksAreReady = true;
1700 m_readyState = newState;
1702 // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until
1703 // the text tracks are ready, regardless of the state of the media file.
1704 if (newState <= HAVE_METADATA)
1705 m_readyState = newState;
1707 m_readyState = HAVE_CURRENT_DATA;
1710 if (oldState > m_readyStateMaximum)
1711 m_readyStateMaximum = oldState;
1713 if (m_networkState == NETWORK_EMPTY)
1717 // 4.8.10.9, step 11
1718 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA)
1719 scheduleEvent(eventNames().waitingEvent);
1721 // 4.8.10.10 step 14 & 15.
1722 if (m_readyState >= HAVE_CURRENT_DATA)
1725 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
1727 scheduleTimeupdateEvent(false);
1728 scheduleEvent(eventNames().waitingEvent);
1732 if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
1733 prepareMediaFragmentURI();
1734 scheduleEvent(eventNames().durationchangeEvent);
1735 scheduleEvent(eventNames().loadedmetadataEvent);
1736 if (hasMediaControls())
1737 mediaControls()->loadedMetadata();
1739 renderer()->updateFromElement();
1741 if (document()->page() && document()->page()->settings()->diagnosticLoggingEnabled())
1742 document()->page()->chrome()->client()->logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadedKey(), m_player->engineDescription(), DiagnosticLoggingKeys::noopKey());
1745 bool shouldUpdateDisplayState = false;
1747 if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) {
1748 m_haveFiredLoadedData = true;
1749 shouldUpdateDisplayState = true;
1750 scheduleEvent(eventNames().loadeddataEvent);
1751 setShouldDelayLoadEvent(false);
1752 applyMediaFragmentURI();
1755 bool isPotentiallyPlaying = potentiallyPlaying();
1756 if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
1757 scheduleEvent(eventNames().canplayEvent);
1758 if (isPotentiallyPlaying)
1759 scheduleEvent(eventNames().playingEvent);
1760 shouldUpdateDisplayState = true;
1763 if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA && tracksAreReady) {
1764 if (oldState <= HAVE_CURRENT_DATA)
1765 scheduleEvent(eventNames().canplayEvent);
1767 scheduleEvent(eventNames().canplaythroughEvent);
1769 if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA)
1770 scheduleEvent(eventNames().playingEvent);
1772 if (m_autoplaying && m_paused && autoplay() && !document()->isSandboxed(SandboxAutomaticFeatures) && !userGestureRequiredForRateChange()) {
1774 invalidateCachedTime();
1775 scheduleEvent(eventNames().playEvent);
1776 scheduleEvent(eventNames().playingEvent);
1779 shouldUpdateDisplayState = true;
1782 if (shouldUpdateDisplayState) {
1783 updateDisplayState();
1784 if (hasMediaControls())
1785 mediaControls()->updateStatusDisplay();
1789 updateMediaController();
1790 #if ENABLE(VIDEO_TRACK)
1791 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
1792 updateActiveTextTrackCues(currentTime());
1796 #if ENABLE(MEDIA_SOURCE)
1797 void HTMLMediaElement::mediaPlayerSourceOpened()
1799 beginProcessingMediaPlayerCallback();
1801 setSourceState(SOURCE_OPEN);
1803 endProcessingMediaPlayerCallback();
1806 String HTMLMediaElement::mediaPlayerSourceURL() const
1808 return m_mediaSourceURL.string();
1811 bool HTMLMediaElement::isValidSourceId(const String& id, ExceptionCode& ec) const
1813 if (id.isNull() || id.isEmpty()) {
1814 ec = INVALID_ACCESS_ERR;
1818 if (!m_sourceIDs.contains(id)) {
1828 #if ENABLE(ENCRYPTED_MEDIA)
1829 void HTMLMediaElement::mediaPlayerKeyAdded(MediaPlayer*, const String& keySystem, const String& sessionId)
1831 MediaKeyEventInit initializer;
1832 initializer.keySystem = keySystem;
1833 initializer.sessionId = sessionId;
1834 initializer.bubbles = false;
1835 initializer.cancelable = false;
1837 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeyaddedEvent, initializer);
1838 event->setTarget(this);
1839 m_asyncEventQueue->enqueueEvent(event.release());
1842 void HTMLMediaElement::mediaPlayerKeyError(MediaPlayer*, const String& keySystem, const String& sessionId, MediaPlayerClient::MediaKeyErrorCode errorCode, unsigned short systemCode)
1844 MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
1845 switch (errorCode) {
1846 case MediaPlayerClient::UnknownError:
1847 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
1849 case MediaPlayerClient::ClientError:
1850 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT;
1852 case MediaPlayerClient::ServiceError:
1853 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_SERVICE;
1855 case MediaPlayerClient::OutputError:
1856 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_OUTPUT;
1858 case MediaPlayerClient::HardwareChangeError:
1859 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_HARDWARECHANGE;
1861 case MediaPlayerClient::DomainError:
1862 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_DOMAIN;
1866 MediaKeyEventInit initializer;
1867 initializer.keySystem = keySystem;
1868 initializer.sessionId = sessionId;
1869 initializer.errorCode = MediaKeyError::create(mediaKeyErrorCode);
1870 initializer.systemCode = systemCode;
1871 initializer.bubbles = false;
1872 initializer.cancelable = false;
1874 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeyerrorEvent, initializer);
1875 event->setTarget(this);
1876 m_asyncEventQueue->enqueueEvent(event.release());
1879 void HTMLMediaElement::mediaPlayerKeyMessage(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* message, unsigned messageLength)
1881 MediaKeyEventInit initializer;
1882 initializer.keySystem = keySystem;
1883 initializer.sessionId = sessionId;
1884 initializer.message = Uint8Array::create(message, messageLength);
1885 initializer.bubbles = false;
1886 initializer.cancelable = false;
1888 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeymessageEvent, initializer);
1889 event->setTarget(this);
1890 m_asyncEventQueue->enqueueEvent(event.release());
1893 void HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* initData, unsigned initDataLength)
1895 MediaKeyEventInit initializer;
1896 initializer.keySystem = keySystem;
1897 initializer.sessionId = sessionId;
1898 initializer.initData = Uint8Array::create(initData, initDataLength);
1899 initializer.bubbles = false;
1900 initializer.cancelable = false;
1902 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitneedkeyEvent, initializer);
1903 event->setTarget(this);
1904 m_asyncEventQueue->enqueueEvent(event.release());
1908 void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*)
1911 if (m_networkState != NETWORK_LOADING)
1914 double time = WTF::currentTime();
1915 double timedelta = time - m_previousProgressTime;
1917 if (m_player->didLoadingProgress()) {
1918 scheduleEvent(eventNames().progressEvent);
1919 m_previousProgressTime = time;
1920 m_sentStalledEvent = false;
1922 renderer()->updateFromElement();
1923 if (hasMediaControls())
1924 mediaControls()->bufferingProgressed();
1925 } else if (timedelta > 3.0 && !m_sentStalledEvent) {
1926 scheduleEvent(eventNames().stalledEvent);
1927 m_sentStalledEvent = true;
1928 setShouldDelayLoadEvent(false);
1932 void HTMLMediaElement::createShadowSubtree()
1934 ASSERT(!userAgentShadowRoot());
1935 ShadowRoot::create(this, ShadowRoot::UserAgentShadowRoot);
1938 void HTMLMediaElement::willAddAuthorShadowRoot()
1940 if (!userAgentShadowRoot())
1941 createShadowSubtree();
1944 void HTMLMediaElement::rewind(float timeDelta)
1946 LOG(Media, "HTMLMediaElement::rewind(%f)", timeDelta);
1949 setCurrentTime(max(currentTime() - timeDelta, minTimeSeekable()), e);
1952 void HTMLMediaElement::returnToRealtime()
1954 LOG(Media, "HTMLMediaElement::returnToRealtime");
1956 setCurrentTime(maxTimeSeekable(), e);
1959 void HTMLMediaElement::addPlayedRange(float start, float end)
1961 LOG(Media, "HTMLMediaElement::addPlayedRange(%f, %f)", start, end);
1962 if (!m_playedTimeRanges)
1963 m_playedTimeRanges = TimeRanges::create();
1964 m_playedTimeRanges->add(start, end);
1967 bool HTMLMediaElement::supportsSave() const
1969 return m_player ? m_player->supportsSave() : false;
1972 bool HTMLMediaElement::supportsScanning() const
1974 return m_player ? m_player->supportsScanning() : false;
1977 void HTMLMediaElement::prepareToPlay()
1979 LOG(Media, "HTMLMediaElement::prepareToPlay(%p)", this);
1980 if (m_havePreparedToPlay)
1982 m_havePreparedToPlay = true;
1983 m_player->prepareToPlay();
1986 void HTMLMediaElement::seek(float time, ExceptionCode& ec)
1988 LOG(Media, "HTMLMediaElement::seek(%f)", time);
1992 // 1 - If the media element's readyState is HAVE_NOTHING, then raise an INVALID_STATE_ERR exception.
1993 if (m_readyState == HAVE_NOTHING || !m_player) {
1994 ec = INVALID_STATE_ERR;
1998 // If the media engine has been told to postpone loading data, let it go ahead now.
1999 if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA)
2002 // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set.
2003 refreshCachedTime();
2004 float now = currentTime();
2006 // 2 - If the element's seeking IDL attribute is true, then another instance of this algorithm is
2007 // already running. Abort that other instance of the algorithm without waiting for the step that
2008 // it is running to complete.
2009 // Nothing specific to be done here.
2011 // 3 - Set the seeking IDL attribute to true.
2012 // The flag will be cleared when the engine tells us the time has actually changed.
2015 // 5 - If the new playback position is later than the end of the media resource, then let it be the end
2016 // of the media resource instead.
2017 time = min(time, duration());
2019 // 6 - If the new playback position is less than the earliest possible position, let it be that position instead.
2020 float earliestTime = m_player->startTime();
2021 time = max(time, earliestTime);
2023 // Ask the media engine for the time value in the movie's time scale before comparing with current time. This
2024 // is necessary because if the seek time is not equal to currentTime but the delta is less than the movie's
2025 // time scale, we will ask the media engine to "seek" to the current movie time, which may be a noop and
2026 // not generate a timechanged callback. This means m_seeking will never be cleared and we will never
2027 // fire a 'seeked' event.
2029 float mediaTime = m_player->mediaTimeForTimeValue(time);
2030 if (time != mediaTime)
2031 LOG(Media, "HTMLMediaElement::seek(%f) - media timeline equivalent is %f", time, mediaTime);
2033 time = m_player->mediaTimeForTimeValue(time);
2035 // 7 - If the (possibly now changed) new playback position is not in one of the ranges given in the
2036 // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute
2037 // that is the nearest to the new playback position. ... If there are no ranges given in the seekable
2038 // attribute then set the seeking IDL attribute to false and abort these steps.
2039 RefPtr<TimeRanges> seekableRanges = seekable();
2041 // Short circuit seeking to the current time by just firing the events if no seek is required.
2042 // Don't skip calling the media engine if we are in poster mode because a seek should always
2043 // cancel poster display.
2044 bool noSeekRequired = !seekableRanges->length() || (time == now && displayMode() != Poster);
2046 #if ENABLE(MEDIA_SOURCE)
2047 // Always notify the media engine of a seek if the source is not closed. This ensures that the source is
2048 // always in a flushed state when the 'seeking' event fires.
2049 if (m_sourceState != SOURCE_CLOSED)
2050 noSeekRequired = false;
2053 if (noSeekRequired) {
2055 scheduleEvent(eventNames().seekingEvent);
2056 scheduleTimeupdateEvent(false);
2057 scheduleEvent(eventNames().seekedEvent);
2062 time = seekableRanges->nearest(time);
2065 if (m_lastSeekTime < now)
2066 addPlayedRange(m_lastSeekTime, now);
2068 m_lastSeekTime = time;
2069 m_sentEndEvent = false;
2071 #if ENABLE(MEDIA_SOURCE)
2072 if (m_sourceState == SOURCE_ENDED)
2073 setSourceState(SOURCE_OPEN);
2076 // 8 - Set the current playback position to the given new playback position
2077 m_player->seek(time);
2079 // 9 - Queue a task to fire a simple event named seeking at the element.
2080 scheduleEvent(eventNames().seekingEvent);
2082 // 10 - Queue a task to fire a simple event named timeupdate at the element.
2083 scheduleTimeupdateEvent(false);
2085 // 11-15 are handled, if necessary, when the engine signals a readystate change.
2088 void HTMLMediaElement::finishSeek()
2090 LOG(Media, "HTMLMediaElement::finishSeek");
2092 // 4.8.10.9 Seeking step 14
2095 // 4.8.10.9 Seeking step 15
2096 scheduleEvent(eventNames().seekedEvent);
2098 setDisplayMode(Video);
2101 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
2103 return m_readyState;
2106 MediaPlayer::MovieLoadType HTMLMediaElement::movieLoadType() const
2108 return m_player ? m_player->movieLoadType() : MediaPlayer::Unknown;
2111 bool HTMLMediaElement::hasAudio() const
2113 return m_player ? m_player->hasAudio() : false;
2116 bool HTMLMediaElement::seeking() const
2121 void HTMLMediaElement::refreshCachedTime() const
2123 m_cachedTime = m_player->currentTime();
2124 m_cachedTimeWallClockUpdateTime = WTF::currentTime();
2127 void HTMLMediaElement::invalidateCachedTime()
2129 LOG(Media, "HTMLMediaElement::invalidateCachedTime");
2131 // Don't try to cache movie time when playback first starts as the time reported by the engine
2132 // sometimes fluctuates for a short amount of time, so the cached time will be off if we take it
2134 static const double minimumTimePlayingBeforeCacheSnapshot = 0.5;
2136 m_minimumWallClockTimeToCacheMediaTime = WTF::currentTime() + minimumTimePlayingBeforeCacheSnapshot;
2137 m_cachedTime = MediaPlayer::invalidTime();
2141 float HTMLMediaElement::currentTime() const
2143 #if LOG_CACHED_TIME_WARNINGS
2144 static const double minCachedDeltaForWarning = 0.01;
2151 LOG(Media, "HTMLMediaElement::currentTime - seeking, returning %f", m_lastSeekTime);
2152 return m_lastSeekTime;
2155 if (m_cachedTime != MediaPlayer::invalidTime() && m_paused) {
2156 #if LOG_CACHED_TIME_WARNINGS
2157 float delta = m_cachedTime - m_player->currentTime();
2158 if (delta > minCachedDeltaForWarning)
2159 LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when paused", delta);
2161 return m_cachedTime;
2164 // Is it too soon use a cached time?
2165 double now = WTF::currentTime();
2166 double maximumDurationToCacheMediaTime = m_player->maximumDurationToCacheMediaTime();
2168 if (maximumDurationToCacheMediaTime && m_cachedTime != MediaPlayer::invalidTime() && !m_paused && now > m_minimumWallClockTimeToCacheMediaTime) {
2169 double wallClockDelta = now - m_cachedTimeWallClockUpdateTime;
2171 // Not too soon, use the cached time only if it hasn't expired.
2172 if (wallClockDelta < maximumDurationToCacheMediaTime) {
2173 float adjustedCacheTime = static_cast<float>(m_cachedTime + (m_playbackRate * wallClockDelta));
2175 #if LOG_CACHED_TIME_WARNINGS
2176 float delta = adjustedCacheTime - m_player->currentTime();
2177 if (delta > minCachedDeltaForWarning)
2178 LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when playing", delta);
2180 return adjustedCacheTime;
2184 #if LOG_CACHED_TIME_WARNINGS
2185 if (maximumDurationToCacheMediaTime && now > m_minimumWallClockTimeToCacheMediaTime && m_cachedTime != MediaPlayer::invalidTime()) {
2186 double wallClockDelta = now - m_cachedTimeWallClockUpdateTime;
2187 float delta = m_cachedTime + (m_playbackRate * wallClockDelta) - m_player->currentTime();
2188 LOG(Media, "HTMLMediaElement::currentTime - cached time was %f seconds off of media time when it expired", delta);
2192 refreshCachedTime();
2194 return m_cachedTime;
2197 void HTMLMediaElement::setCurrentTime(float time, ExceptionCode& ec)
2199 if (m_mediaController) {
2200 ec = INVALID_STATE_ERR;
2206 float HTMLMediaElement::startTime() const
2210 return m_player->startTime();
2213 double HTMLMediaElement::initialTime() const
2215 if (m_fragmentStartTime != MediaPlayer::invalidTime())
2216 return m_fragmentStartTime;
2221 return m_player->initialTime();
2224 float HTMLMediaElement::duration() const
2226 if (m_player && m_readyState >= HAVE_METADATA)
2227 return m_player->duration();
2229 return numeric_limits<float>::quiet_NaN();
2232 bool HTMLMediaElement::paused() const
2237 float HTMLMediaElement::defaultPlaybackRate() const
2239 return m_defaultPlaybackRate;
2242 void HTMLMediaElement::setDefaultPlaybackRate(float rate)
2244 if (m_defaultPlaybackRate != rate) {
2245 m_defaultPlaybackRate = rate;
2246 scheduleEvent(eventNames().ratechangeEvent);
2250 float HTMLMediaElement::playbackRate() const
2252 return m_playbackRate;
2255 void HTMLMediaElement::setPlaybackRate(float rate)
2257 LOG(Media, "HTMLMediaElement::setPlaybackRate(%f)", rate);
2259 if (m_playbackRate != rate) {
2260 m_playbackRate = rate;
2261 invalidateCachedTime();
2262 scheduleEvent(eventNames().ratechangeEvent);
2265 if (m_player && potentiallyPlaying() && m_player->rate() != rate && !m_mediaController)
2266 m_player->setRate(rate);
2269 void HTMLMediaElement::updatePlaybackRate()
2271 float effectiveRate = m_mediaController ? m_mediaController->playbackRate() : m_playbackRate;
2272 if (m_player && potentiallyPlaying() && m_player->rate() != effectiveRate)
2273 m_player->setRate(effectiveRate);
2276 bool HTMLMediaElement::webkitPreservesPitch() const
2278 return m_webkitPreservesPitch;
2281 void HTMLMediaElement::setWebkitPreservesPitch(bool preservesPitch)
2283 LOG(Media, "HTMLMediaElement::setWebkitPreservesPitch(%s)", boolString(preservesPitch));
2285 m_webkitPreservesPitch = preservesPitch;
2290 m_player->setPreservesPitch(preservesPitch);
2293 bool HTMLMediaElement::ended() const
2295 // 4.8.10.8 Playing the media resource
2296 // The ended attribute must return true if the media element has ended
2297 // playback and the direction of playback is forwards, and false otherwise.
2298 return endedPlayback() && m_playbackRate > 0;
2301 bool HTMLMediaElement::autoplay() const
2303 return fastHasAttribute(autoplayAttr);
2306 void HTMLMediaElement::setAutoplay(bool b)
2308 LOG(Media, "HTMLMediaElement::setAutoplay(%s)", boolString(b));
2309 setBooleanAttribute(autoplayAttr, b);
2312 String HTMLMediaElement::preload() const
2314 switch (m_preload) {
2315 case MediaPlayer::None:
2318 case MediaPlayer::MetaData:
2321 case MediaPlayer::Auto:
2326 ASSERT_NOT_REACHED();
2330 void HTMLMediaElement::setPreload(const String& preload)
2332 LOG(Media, "HTMLMediaElement::setPreload(%s)", preload.utf8().data());
2333 setAttribute(preloadAttr, preload);
2336 void HTMLMediaElement::play()
2338 LOG(Media, "HTMLMediaElement::play()");
2340 if (userGestureRequiredForRateChange() && !ScriptController::processingUserGesture())
2342 if (ScriptController::processingUserGesture())
2343 removeBehaviorsRestrictionsAfterFirstUserGesture();
2345 Settings* settings = document()->settings();
2346 if (settings && settings->needsSiteSpecificQuirks() && m_dispatchingCanPlayEvent && !m_loadInitiatedByUserGesture) {
2347 // It should be impossible to be processing the canplay event while handling a user gesture
2348 // since it is dispatched asynchronously.
2349 ASSERT(!ScriptController::processingUserGesture());
2350 String host = document()->baseURL().host();
2351 if (host.endsWith(".npr.org", false) || equalIgnoringCase(host, "npr.org"))
2358 void HTMLMediaElement::playInternal()
2360 LOG(Media, "HTMLMediaElement::playInternal");
2362 // 4.8.10.9. Playing the media resource
2363 if (!m_player || m_networkState == NETWORK_EMPTY)
2364 scheduleLoad(MediaResource);
2366 if (endedPlayback()) {
2367 ExceptionCode unused;
2371 if (m_mediaController)
2372 m_mediaController->bringElementUpToSpeed(this);
2376 invalidateCachedTime();
2377 scheduleEvent(eventNames().playEvent);
2379 if (m_readyState <= HAVE_CURRENT_DATA)
2380 scheduleEvent(eventNames().waitingEvent);
2381 else if (m_readyState >= HAVE_FUTURE_DATA)
2382 scheduleEvent(eventNames().playingEvent);
2384 m_autoplaying = false;
2387 updateMediaController();
2390 void HTMLMediaElement::pause()
2392 LOG(Media, "HTMLMediaElement::pause()");
2394 if (userGestureRequiredForRateChange() && !ScriptController::processingUserGesture())
2401 void HTMLMediaElement::pauseInternal()
2403 LOG(Media, "HTMLMediaElement::pauseInternal");
2405 // 4.8.10.9. Playing the media resource
2406 if (!m_player || m_networkState == NETWORK_EMPTY)
2407 scheduleLoad(MediaResource);
2409 m_autoplaying = false;
2413 scheduleTimeupdateEvent(false);
2414 scheduleEvent(eventNames().pauseEvent);
2420 #if ENABLE(MEDIA_SOURCE)
2422 void HTMLMediaElement::webkitSourceAddId(const String& id, const String& type, ExceptionCode& ec)
2424 if (id.isNull() || id.isEmpty()) {
2425 ec = INVALID_ACCESS_ERR;
2429 if (m_sourceIDs.contains(id)) {
2430 ec = INVALID_STATE_ERR;
2434 if (type.isNull() || type.isEmpty()) {
2435 ec = INVALID_ACCESS_ERR;
2439 if (!m_player || m_currentSrc != m_mediaSourceURL || m_sourceState != SOURCE_OPEN) {
2440 ec = INVALID_STATE_ERR;
2444 ContentType contentType(type);
2445 Vector<String> codecs = contentType.codecs();
2447 if (!codecs.size()) {
2448 ec = NOT_SUPPORTED_ERR;
2452 switch (m_player->sourceAddId(id, contentType.type(), codecs)) {
2453 case MediaPlayer::Ok:
2454 m_sourceIDs.add(id);
2456 case MediaPlayer::NotSupported:
2457 ec = NOT_SUPPORTED_ERR;
2459 case MediaPlayer::ReachedIdLimit:
2460 ec = QUOTA_EXCEEDED_ERR;
2464 ASSERT_NOT_REACHED();
2467 void HTMLMediaElement::webkitSourceRemoveId(const String& id, ExceptionCode& ec)
2469 if (!isValidSourceId(id, ec))
2472 if (!m_player || m_currentSrc != m_mediaSourceURL || m_sourceState == SOURCE_CLOSED) {
2473 ec = INVALID_STATE_ERR;
2477 if (!m_player->sourceRemoveId(id))
2478 ASSERT_NOT_REACHED();
2480 m_sourceIDs.remove(id);
2483 PassRefPtr<TimeRanges> HTMLMediaElement::webkitSourceBuffered(const String& id, ExceptionCode& ec)
2485 if (!isValidSourceId(id, ec))
2488 if (!m_player || m_currentSrc != m_mediaSourceURL || m_sourceState == SOURCE_CLOSED) {
2489 ec = INVALID_STATE_ERR;
2493 return m_player->sourceBuffered(id);
2496 void HTMLMediaElement::webkitSourceAppend(const String& id, PassRefPtr<Uint8Array> data, ExceptionCode& ec)
2498 if (!isValidSourceId(id, ec))
2501 if (!m_player || m_currentSrc != m_mediaSourceURL || m_sourceState != SOURCE_OPEN) {
2502 ec = INVALID_STATE_ERR;
2507 ec = INVALID_ACCESS_ERR;
2511 if (!data->length())
2514 if (!m_player->sourceAppend(id, data->data(), data->length())) {
2520 void HTMLMediaElement::webkitSourceAbort(const String& id, ExceptionCode& ec)
2522 if (!isValidSourceId(id, ec))
2525 if (!m_player || m_currentSrc != m_mediaSourceURL || m_sourceState != SOURCE_OPEN) {
2526 ec = INVALID_STATE_ERR;
2530 if (!m_player->sourceAbort(id))
2531 ASSERT_NOT_REACHED();
2534 void HTMLMediaElement::webkitSourceEndOfStream(unsigned short status, ExceptionCode& ec)
2536 if (!m_player || m_currentSrc != m_mediaSourceURL || m_sourceState != SOURCE_OPEN) {
2537 ec = INVALID_STATE_ERR;
2541 MediaPlayer::EndOfStreamStatus eosStatus = MediaPlayer::EosNoError;
2545 eosStatus = MediaPlayer::EosNoError;
2547 case EOS_NETWORK_ERR:
2548 eosStatus = MediaPlayer::EosNetworkError;
2550 case EOS_DECODE_ERR:
2551 eosStatus = MediaPlayer::EosDecodeError;
2558 setSourceState(SOURCE_ENDED);
2559 m_player->sourceEndOfStream(eosStatus);
2562 HTMLMediaElement::SourceState HTMLMediaElement::webkitSourceState() const
2564 return m_sourceState;
2567 void HTMLMediaElement::setSourceState(SourceState state)
2569 SourceState oldState = m_sourceState;
2570 m_sourceState = static_cast<SourceState>(state);
2572 if (m_sourceState == oldState)
2575 if (m_sourceState == SOURCE_CLOSED) {
2576 m_sourceIDs.clear();
2577 scheduleEvent(eventNames().webkitsourcecloseEvent);
2581 if (oldState == SOURCE_OPEN && m_sourceState == SOURCE_ENDED) {
2582 scheduleEvent(eventNames().webkitsourceendedEvent);
2586 if (m_sourceState == SOURCE_OPEN) {
2587 scheduleEvent(eventNames().webkitsourceopenEvent);
2593 #if ENABLE(ENCRYPTED_MEDIA)
2594 void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, PassRefPtr<Uint8Array> initData, ExceptionCode& ec)
2596 if (keySystem.isEmpty()) {
2602 ec = INVALID_STATE_ERR;
2606 const unsigned char* initDataPointer = 0;
2607 unsigned initDataLength = 0;
2609 initDataPointer = initData->data();
2610 initDataLength = initData->length();
2613 MediaPlayer::MediaKeyException result = m_player->generateKeyRequest(keySystem, initDataPointer, initDataLength);
2614 ec = exceptionCodeForMediaKeyException(result);
2617 void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, ExceptionCode& ec)
2619 webkitGenerateKeyRequest(keySystem, Uint8Array::create(0), ec);
2622 void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, PassRefPtr<Uint8Array> initData, const String& sessionId, ExceptionCode& ec)
2624 if (keySystem.isEmpty()) {
2634 if (!key->length()) {
2635 ec = TYPE_MISMATCH_ERR;
2640 ec = INVALID_STATE_ERR;
2644 const unsigned char* initDataPointer = 0;
2645 unsigned initDataLength = 0;
2647 initDataPointer = initData->data();
2648 initDataLength = initData->length();
2651 MediaPlayer::MediaKeyException result = m_player->addKey(keySystem, key->data(), key->length(), initDataPointer, initDataLength, sessionId);
2652 ec = exceptionCodeForMediaKeyException(result);
2655 void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, ExceptionCode& ec)
2657 webkitAddKey(keySystem, key, Uint8Array::create(0), String(), ec);
2660 void HTMLMediaElement::webkitCancelKeyRequest(const String& keySystem, const String& sessionId, ExceptionCode& ec)
2662 if (keySystem.isEmpty()) {
2668 ec = INVALID_STATE_ERR;
2672 MediaPlayer::MediaKeyException result = m_player->cancelKeyRequest(keySystem, sessionId);
2673 ec = exceptionCodeForMediaKeyException(result);
2678 bool HTMLMediaElement::loop() const
2680 return fastHasAttribute(loopAttr);
2683 void HTMLMediaElement::setLoop(bool b)
2685 LOG(Media, "HTMLMediaElement::setLoop(%s)", boolString(b));
2686 setBooleanAttribute(loopAttr, b);
2688 updateDisableSleep();
2692 bool HTMLMediaElement::controls() const
2694 Frame* frame = document()->frame();
2696 // always show controls when scripting is disabled
2697 if (frame && !frame->script()->canExecuteScripts(NotAboutToExecuteScript))
2700 // always show controls for video when fullscreen playback is required.
2701 if (isVideo() && document()->page() && document()->page()->chrome()->requiresFullscreenForVideoPlayback())
2704 // Always show controls when in full screen mode.
2708 return fastHasAttribute(controlsAttr);
2711 void HTMLMediaElement::setControls(bool b)
2713 LOG(Media, "HTMLMediaElement::setControls(%s)", boolString(b));
2714 setBooleanAttribute(controlsAttr, b);
2717 float HTMLMediaElement::volume() const
2722 void HTMLMediaElement::setVolume(float vol, ExceptionCode& ec)
2724 LOG(Media, "HTMLMediaElement::setVolume(%f)", vol);
2726 if (vol < 0.0f || vol > 1.0f) {
2727 ec = INDEX_SIZE_ERR;
2731 if (m_volume != vol) {
2734 scheduleEvent(eventNames().volumechangeEvent);
2738 bool HTMLMediaElement::muted() const
2743 void HTMLMediaElement::setMuted(bool muted)
2745 LOG(Media, "HTMLMediaElement::setMuted(%s)", boolString(muted));
2747 if (m_muted != muted) {
2749 // Avoid recursion when the player reports volume changes.
2750 if (!processingMediaPlayerCallback()) {
2752 m_player->setMuted(m_muted);
2753 if (hasMediaControls())
2754 mediaControls()->changedMute();
2757 scheduleEvent(eventNames().volumechangeEvent);
2761 void HTMLMediaElement::togglePlayState()
2763 LOG(Media, "HTMLMediaElement::togglePlayState - canPlay() is %s", boolString(canPlay()));
2765 // We can safely call the internal play/pause methods, which don't check restrictions, because
2766 // this method is only called from the built-in media controller
2768 updatePlaybackRate();
2774 void HTMLMediaElement::beginScrubbing()
2776 LOG(Media, "HTMLMediaElement::beginScrubbing - paused() is %s", boolString(paused()));
2780 // Because a media element stays in non-paused state when it reaches end, playback resumes
2781 // when the slider is dragged from the end to another position unless we pause first. Do
2782 // a "hard pause" so an event is generated, since we want to stay paused after scrubbing finishes.
2785 // Not at the end but we still want to pause playback so the media engine doesn't try to
2786 // continue playing during scrubbing. Pause without generating an event as we will
2787 // unpause after scrubbing finishes.
2788 setPausedInternal(true);
2793 void HTMLMediaElement::endScrubbing()
2795 LOG(Media, "HTMLMediaElement::endScrubbing - m_pausedInternal is %s", boolString(m_pausedInternal));
2797 if (m_pausedInternal)
2798 setPausedInternal(false);
2801 // The spec says to fire periodic timeupdate events (those sent while playing) every
2802 // "15 to 250ms", we choose the slowest frequency
2803 static const double maxTimeupdateEventFrequency = 0.25;
2805 void HTMLMediaElement::startPlaybackProgressTimer()
2807 if (m_playbackProgressTimer.isActive())
2810 m_previousProgressTime = WTF::currentTime();
2811 m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency);
2814 void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*)
2818 if (m_fragmentEndTime != MediaPlayer::invalidTime() && currentTime() >= m_fragmentEndTime && m_playbackRate > 0) {
2819 m_fragmentEndTime = MediaPlayer::invalidTime();
2820 if (!m_mediaController && !m_paused) {
2821 // changes paused to true and fires a simple event named pause at the media element.
2826 scheduleTimeupdateEvent(true);
2828 if (!m_playbackRate)
2831 if (!m_paused && hasMediaControls())
2832 mediaControls()->playbackProgressed();
2834 #if ENABLE(VIDEO_TRACK)
2835 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
2836 updateActiveTextTrackCues(currentTime());
2840 void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent)
2842 double now = WTF::currentTime();
2843 double timedelta = now - m_lastTimeUpdateEventWallTime;
2845 // throttle the periodic events
2846 if (periodicEvent && timedelta < maxTimeupdateEventFrequency)
2849 // Some media engines make multiple "time changed" callbacks at the same time, but we only want one
2850 // event at a given time so filter here
2851 float movieTime = currentTime();
2852 if (movieTime != m_lastTimeUpdateEventMovieTime) {
2853 scheduleEvent(eventNames().timeupdateEvent);
2854 m_lastTimeUpdateEventWallTime = now;
2855 m_lastTimeUpdateEventMovieTime = movieTime;
2859 bool HTMLMediaElement::canPlay() const
2861 return paused() || ended() || m_readyState < HAVE_METADATA;
2864 float HTMLMediaElement::percentLoaded() const
2868 float duration = m_player->duration();
2870 if (!duration || isinf(duration))
2874 RefPtr<TimeRanges> timeRanges = m_player->buffered();
2875 for (unsigned i = 0; i < timeRanges->length(); ++i) {
2876 ExceptionCode ignoredException;
2877 float start = timeRanges->start(i, ignoredException);
2878 float end = timeRanges->end(i, ignoredException);
2879 buffered += end - start;
2881 return buffered / duration;
2884 #if ENABLE(VIDEO_TRACK)
2885 PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const String& kind, const String& label, const String& language, ExceptionCode& ec)
2887 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
2890 // 4.8.10.12.4 Text track API
2891 // The addTextTrack(kind, label, language) method of media elements, when invoked, must run the following steps:
2893 // 1. If kind is not one of the following strings, then throw a SyntaxError exception and abort these steps
2894 if (!TextTrack::isValidKindKeyword(kind)) {
2899 // 2. If the label argument was omitted, let label be the empty string.
2900 // 3. If the language argument was omitted, let language be the empty string.
2901 // 4. Create a new TextTrack object.
2903 // 5. Create a new text track corresponding to the new object, and set its text track kind to kind, its text
2904 // track label to label, its text track language to language...
2905 RefPtr<TextTrack> textTrack = TextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, kind, label, language);
2907 // Note, due to side effects when changing track parameters, we have to
2908 // first append the track to the text track list.
2910 // 6. Add the new text track to the media element's list of text tracks.
2911 textTracks()->append(textTrack);
2913 // ... its text track readiness state to the text track loaded state ...
2914 textTrack->setReadinessState(TextTrack::Loaded);
2916 // ... its text track mode to the text track hidden mode, and its text track list of cues to an empty list ...
2917 textTrack->setMode(TextTrack::hiddenKeyword());
2919 return textTrack.release();
2922 TextTrackList* HTMLMediaElement::textTracks()
2924 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
2928 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
2930 return m_textTracks.get();
2933 HTMLTrackElement* HTMLMediaElement::showingTrackWithSameKind(HTMLTrackElement* trackElement) const
2935 for (Node* node = firstChild(); node; node = node->nextSibling()) {
2936 if (trackElement == node)
2938 if (!node->hasTagName(trackTag))
2941 HTMLTrackElement* showingTrack = static_cast<HTMLTrackElement*>(node);
2942 if (showingTrack->kind() == trackElement->kind() && showingTrack->track()->mode() == TextTrack::showingKeyword())
2943 return showingTrack;
2949 void HTMLMediaElement::didAddTrack(HTMLTrackElement* trackElement)
2951 ASSERT(trackElement->hasTagName(trackTag));
2953 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
2956 // 4.8.10.12.3 Sourcing out-of-band text tracks
2957 // When a track element's parent element changes and the new parent is a media element,
2958 // then the user agent must add the track element's corresponding text track to the
2959 // media element's list of text tracks ... [continues in TextTrackList::append]
2960 RefPtr<TextTrack> textTrack = trackElement->track();
2964 textTracks()->append(textTrack);
2966 // Do not schedule the track loading until parsing finishes so we don't start before all tracks
2967 // in the markup have been added.
2968 if (!m_parsingInProgress)
2969 scheduleLoad(TextTrackResource);
2972 void HTMLMediaElement::willRemoveTrack(HTMLTrackElement* trackElement)
2974 ASSERT(trackElement->hasTagName(trackTag));
2976 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
2980 if (trackElement->hasTagName(trackTag)) {
2981 KURL url = trackElement->getNonEmptyURLAttribute(srcAttr);
2982 LOG(Media, "HTMLMediaElement::willRemoveTrack - 'src' is %s", urlForLogging(url).utf8().data());
2986 trackElement->setHasBeenConfigured(false);
2991 RefPtr<TextTrack> textTrack = trackElement->track();
2995 // 4.8.10.12.3 Sourcing out-of-band text tracks
2996 // When a track element's parent element changes and the old parent was a media element,
2997 // then the user agent must remove the track element's corresponding text track from the
2998 // media element's list of text tracks.
2999 m_textTracks->remove(textTrack.get());
3000 size_t index = m_textTracksWhenResourceSelectionBegan.find(textTrack.get());
3001 if (index != notFound)
3002 m_textTracksWhenResourceSelectionBegan.remove(index);
3005 bool HTMLMediaElement::userIsInterestedInThisLanguage(const String&) const
3007 // FIXME: check the user's language preference - bugs.webkit.org/show_bug.cgi?id=74121
3011 bool HTMLMediaElement::userIsInterestedInThisTrackKind(String kind) const
3013 // If ... the user has indicated an interest in having a track with this text track kind, text track language, ...
3014 if (m_disableCaptions)
3017 Settings* settings = document()->settings();
3021 if (kind == TextTrack::subtitlesKeyword())
3022 return settings->shouldDisplaySubtitles() || m_closedCaptionsVisible;
3023 if (kind == TextTrack::captionsKeyword())
3024 return settings->shouldDisplayCaptions() || m_closedCaptionsVisible;
3025 if (kind == TextTrack::descriptionsKeyword())
3026 return settings->shouldDisplayTextDescriptions() || m_closedCaptionsVisible;
3031 void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group) const
3033 ASSERT(group.tracks.size());
3035 String bestMatchingLanguage;
3036 if (group.hasSrcLang) {
3037 Vector<String> languages;
3038 languages.reserveInitialCapacity(group.tracks.size());
3039 for (size_t i = 0; i < group.tracks.size(); ++i) {
3040 String srcLanguage = group.tracks[i]->track()->language();
3041 if (srcLanguage.length())
3042 languages.append(srcLanguage);
3044 bestMatchingLanguage = preferredLanguageFromList(languages);
3047 // First, find the track in the group that should be enabled (if any).
3048 HTMLTrackElement* trackElementToEnable = 0;
3049 HTMLTrackElement* defaultTrack = 0;
3050 HTMLTrackElement* fallbackTrack = 0;
3051 for (size_t i = 0; !trackElementToEnable && i < group.tracks.size(); ++i) {
3052 HTMLTrackElement* trackElement = group.tracks[i];
3053 RefPtr<TextTrack> textTrack = trackElement->track();
3055 if (userIsInterestedInThisTrackKind(textTrack->kind())) {
3056 // * If the text track kind is { [subtitles or captions] [descriptions] } and the user has indicated an interest in having a
3057 // track with this text track kind, text track language, and text track label enabled, and there is no
3058 // other text track in the media element's list of text tracks with a text track kind of either subtitles
3059 // or captions whose text track mode is showing
3061 // * If the text track kind is chapters and the text track language is one that the user agent has reason
3062 // to believe is appropriate for the user, and there is no other text track in the media element's list of
3063 // text tracks with a text track kind of chapters whose text track mode is showing
3064 // Let the text track mode be showing.
3065 if (bestMatchingLanguage.length()) {
3066 if (textTrack->language() == bestMatchingLanguage)
3067 trackElementToEnable = trackElement;
3068 } else if (trackElement->isDefault()) {
3069 // The user is interested in this type of track, but their language preference doesn't match any track so we will
3070 // enable the 'default' track.
3071 defaultTrack = trackElement;
3074 // Remember the first track that doesn't match language or have 'default' to potentially use as fallback.
3076 fallbackTrack = trackElement;
3077 } else if (!group.visibleTrack && !defaultTrack && trackElement->isDefault()) {
3078 // * If the track element has a default attribute specified, and there is no other text track in the media
3079 // element's list of text tracks whose text track mode is showing or showing by default
3080 // Let the text track mode be showing by default.
3081 defaultTrack = trackElement;
3085 if (!trackElementToEnable && defaultTrack)
3086 trackElementToEnable = defaultTrack;
3088 // If no track matches the user's preferred language and non was marked 'default', enable the first track
3089 // because the user has explicitly stated a preference for this kind of track.
3090 if (!trackElementToEnable && fallbackTrack)
3091 trackElementToEnable = fallbackTrack;
3093 for (size_t i = 0; i < group.tracks.size(); ++i) {
3094 HTMLTrackElement* trackElement = group.tracks[i];
3095 RefPtr<TextTrack> textTrack = trackElement->track();
3097 if (trackElementToEnable == trackElement) {
3098 textTrack->setMode(TextTrack::showingKeyword());
3099 if (defaultTrack == trackElement)
3100 textTrack->setShowingByDefault(true);
3102 if (textTrack->showingByDefault()) {
3103 // If there is a text track in the media element's list of text tracks whose text track
3104 // mode is showing by default, the user agent must furthermore change that text track's
3105 // text track mode to hidden.
3106 textTrack->setShowingByDefault(false);
3107 textTrack->setMode(TextTrack::hiddenKeyword());
3109 textTrack->setMode(TextTrack::disabledKeyword());
3113 if (trackElementToEnable && group.defaultTrack && group.defaultTrack != trackElementToEnable) {
3114 RefPtr<TextTrack> textTrack = group.defaultTrack->track();
3115 if (textTrack && textTrack->showingByDefault()) {
3116 textTrack->setShowingByDefault(false);
3117 textTrack->setMode(TextTrack::hiddenKeyword());
3122 void HTMLMediaElement::configureTextTracks()
3124 TrackGroup captionAndSubtitleTracks(TrackGroup::CaptionsAndSubtitles);
3125 TrackGroup descriptionTracks(TrackGroup::Description);
3126 TrackGroup chapterTracks(TrackGroup::Chapter);
3127 TrackGroup metadataTracks(TrackGroup::Metadata);
3128 TrackGroup otherTracks(TrackGroup::Other);
3130 for (Node* node = firstChild(); node; node = node->nextSibling()) {
3131 if (!node->hasTagName(trackTag))
3134 HTMLTrackElement* trackElement = static_cast<HTMLTrackElement*>(node);
3135 RefPtr<TextTrack> textTrack = trackElement->track();
3139 String kind = textTrack->kind();
3140 TrackGroup* currentGroup;
3141 if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword())
3142 currentGroup = &captionAndSubtitleTracks;
3143 else if (kind == TextTrack::descriptionsKeyword())
3144 currentGroup = &descriptionTracks;
3145 else if (kind == TextTrack::chaptersKeyword())
3146 currentGroup = &chapterTracks;
3147 else if (kind == TextTrack::metadataKeyword())
3148 currentGroup = &metadataTracks;
3150 currentGroup = &otherTracks;
3152 if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::showingKeyword())
3153 currentGroup->visibleTrack = trackElement;
3154 if (!currentGroup->defaultTrack && trackElement->isDefault())
3155 currentGroup->defaultTrack = trackElement;
3157 // Do not add this track to the group if it has already been automatically configured
3158 // as we only want to call configureTextTrack once per track so that adding another
3159 // track after the initial configuration doesn't reconfigure every track - only those
3160 // that should be changed by the new addition. For example all metadata tracks are
3161 // disabled by default, and we don't want a track that has been enabled by script
3162 // to be disabled automatically when a new metadata track is added later.
3163 if (trackElement->hasBeenConfigured())
3166 if (textTrack->language().length())
3167 currentGroup->hasSrcLang = true;
3168 currentGroup->tracks.append(trackElement);
3171 if (captionAndSubtitleTracks.tracks.size())
3172 configureTextTrackGroup(captionAndSubtitleTracks);
3173 if (descriptionTracks.tracks.size())
3174 configureTextTrackGroup(descriptionTracks);
3175 if (chapterTracks.tracks.size())
3176 configureTextTrackGroup(chapterTracks);
3177 if (metadataTracks.tracks.size())
3178 configureTextTrackGroup(metadataTracks);
3179 if (otherTracks.tracks.size())
3180 configureTextTrackGroup(otherTracks);
3184 bool HTMLMediaElement::havePotentialSourceChild()
3186 // Stash the current <source> node and next nodes so we can restore them after checking
3187 // to see there is another potential.
3188 RefPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode;
3189 RefPtr<Node> nextNode = m_nextChildNodeToConsider;
3191 KURL nextURL = selectNextSourceChild(0, 0, DoNothing);
3193 m_currentSourceNode = currentSourceNode;
3194 m_nextChildNodeToConsider = nextNode;
3196 return nextURL.isValid();
3199 KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid)
3202 // Don't log if this was just called to find out if there are any valid <source> elements.
3203 bool shouldLog = actionIfInvalid != DoNothing;
3205 LOG(Media, "HTMLMediaElement::selectNextSourceChild");
3208 if (!m_nextChildNodeToConsider) {
3211 LOG(Media, "HTMLMediaElement::selectNextSourceChild -> 0x0000, \"\"");
3218 HTMLSourceElement* source = 0;
3221 bool lookingForStartNode = m_nextChildNodeToConsider;
3222 bool canUseSourceElement = false;
3223 bool okToLoadSourceURL;
3225 NodeVector potentialSourceNodes;
3226 getChildNodes(this, potentialSourceNodes);
3228 for (unsigned i = 0; !canUseSourceElement && i < potentialSourceNodes.size(); ++i) {
3229 node = potentialSourceNodes[i].get();
3230 if (lookingForStartNode && m_nextChildNodeToConsider != node)
3232 lookingForStartNode = false;
3234 if (!node->hasTagName(sourceTag))
3236 if (node->parentNode() != this)
3239 source = static_cast<HTMLSourceElement*>(node);
3241 // 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
3242 mediaURL = source->getNonEmptyURLAttribute(srcAttr);
3245 LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'src' is %s", urlForLogging(mediaURL).utf8().data());
3247 if (mediaURL.isEmpty())
3250 if (source->fastHasAttribute(mediaAttr)) {
3251 MediaQueryEvaluator screenEval("screen", document()->frame(), renderer() ? renderer()->style() : 0);
3252 RefPtr<MediaQuerySet> media = MediaQuerySet::createAllowingDescriptionSyntax(source->media());
3255 LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'media' is %s", source->media().utf8().data());
3257 if (!screenEval.eval(media.get()))
3261 type = source->type();
3262 // FIXME(82965): Add support for keySystem in <source> and set system from source.
3263 if (type.isEmpty() && mediaURL.protocolIsData())
3264 type = mimeTypeFromDataURL(mediaURL);
3265 if (!type.isEmpty() || !system.isEmpty()) {
3268 LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'type' is '%s' - key system is '%s'", type.utf8().data(), system.utf8().data());
3270 if (!MediaPlayer::supportsType(ContentType(type), system, mediaURL, this))
3274 // Is it safe to load this url?
3275 okToLoadSourceURL = isSafeToLoadURL(mediaURL, actionIfInvalid) && dispatchBeforeLoadEvent(mediaURL.string());
3277 // A 'beforeload' event handler can mutate the DOM, so check to see if the source element is still a child node.
3278 if (node->parentNode() != this) {
3279 LOG(Media, "HTMLMediaElement::selectNextSourceChild : 'beforeload' removed current element");
3284 if (!okToLoadSourceURL)
3287 // Making it this far means the <source> looks reasonable.
3288 canUseSourceElement = true;
3291 if (!canUseSourceElement && actionIfInvalid == Complain && source)
3292 source->scheduleErrorEvent();
3295 if (canUseSourceElement) {
3297 *contentType = ContentType(type);
3299 *keySystem = system;
3300 m_currentSourceNode = source;
3301 m_nextChildNodeToConsider = source->nextSibling();
3303 m_currentSourceNode = 0;
3304 m_nextChildNodeToConsider = 0;
3309 LOG(Media, "HTMLMediaElement::selectNextSourceChild -> %p, %s", m_currentSourceNode.get(), canUseSourceElement ? urlForLogging(mediaURL).utf8().data() : "");
3311 return canUseSourceElement ? mediaURL : KURL();
3314 void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source)
3316 LOG(Media, "HTMLMediaElement::sourceWasAdded(%p)", source);
3319 if (source->hasTagName(sourceTag)) {
3320 KURL url = source->getNonEmptyURLAttribute(srcAttr);
3321 LOG(Media, "HTMLMediaElement::sourceWasAdded - 'src' is %s", urlForLogging(url).utf8().data());
3325 // We should only consider a <source> element when there is not src attribute at all.
3326 if (fastHasAttribute(srcAttr))
3329 // 4.8.8 - If a source element is inserted as a child of a media element that has no src
3330 // attribute and whose networkState has the value NETWORK_EMPTY, the user agent must invoke
3331 // the media element's resource selection algorithm.
3332 if (networkState() == HTMLMediaElement::NETWORK_EMPTY) {
3333 scheduleLoad(MediaResource);
3334 m_nextChildNodeToConsider = source;
3338 if (m_currentSourceNode && source == m_currentSourceNode->nextSibling()) {
3339 LOG(Media, "HTMLMediaElement::sourceWasAdded - <source> inserted immediately after current source");
3340 m_nextChildNodeToConsider = source;
3344 if (m_nextChildNodeToConsider)
3347 // 4.8.9.5, resource selection algorithm, source elements section:
3348 // 21. Wait until the node after pointer is a node other than the end of the list. (This step might wait forever.)
3349 // 22. Asynchronously await a stable state...
3350 // 23. Set the element's delaying-the-load-event flag back to true (this delays the load event again, in case
3351 // it hasn't been fired yet).
3352 setShouldDelayLoadEvent(true);
3354 // 24. Set the networkState back to NETWORK_LOADING.
3355 m_networkState = NETWORK_LOADING;
3357 // 25. Jump back to the find next candidate step above.
3358 m_nextChildNodeToConsider = source;
3359 scheduleNextSourceChild();
3362 void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement* source)
3364 LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p)", source);
3367 if (source->hasTagName(sourceTag)) {
3368 KURL url = source->getNonEmptyURLAttribute(srcAttr);
3369 LOG(Media, "HTMLMediaElement::sourceWasRemoved - 'src' is %s", urlForLogging(url).utf8().data());
3373 if (source != m_currentSourceNode && source != m_nextChildNodeToConsider)
3376 if (source == m_nextChildNodeToConsider) {
3377 if (m_currentSourceNode)
3378 m_nextChildNodeToConsider = m_currentSourceNode->nextSibling();
3379 LOG(Media, "HTMLMediaElement::sourceRemoved - m_nextChildNodeToConsider set to %p", m_nextChildNodeToConsider.get());
3380 } else if (source == m_currentSourceNode) {
3381 // Clear the current source node pointer, but don't change the movie as the spec says:
3382 // 4.8.8 - Dynamically modifying a source element and its attribute when the element is already
3383 // inserted in a video or audio element will have no effect.
3384 m_currentSourceNode = 0;
3385 LOG(Media, "HTMLMediaElement::sourceRemoved - m_currentSourceNode set to 0");
3389 void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*)
3391 LOG(Media, "HTMLMediaElement::mediaPlayerTimeChanged");
3393 #if ENABLE(VIDEO_TRACK)
3394 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3395 updateActiveTextTrackCues(currentTime());
3398 beginProcessingMediaPlayerCallback();
3400 invalidateCachedTime();
3402 // 4.8.10.9 step 14 & 15. Needed if no ReadyState change is associated with the seek.
3403 if (m_seeking && m_readyState >= HAVE_CURRENT_DATA)
3406 // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity,
3407 // it will only queue a 'timeupdate' event if we haven't already posted one at the current
3409 scheduleTimeupdateEvent(false);
3411 float now = currentTime();
3412 float dur = duration();
3414 // When the current playback position reaches the end of the media resource when the direction of
3415 // playback is forwards, then the user agent must follow these steps:
3416 if (!isnan(dur) && dur && now >= dur && m_playbackRate > 0) {
3417 // If the media element has a loop attribute specified and does not have a current media controller,
3418 if (loop() && !m_mediaController) {
3419 ExceptionCode ignoredException;
3420 m_sentEndEvent = false;
3421 // then seek to the earliest possible position of the media resource and abort these steps.
3422 seek(startTime(), ignoredException);
3424 // If the media element does not have a current media controller, and the media element
3425 // has still ended playback, and the direction of playback is still forwards, and paused
3427 if (!m_mediaController && !m_paused) {
3428 // changes paused to true and fires a simple event named pause at the media element.
3430 scheduleEvent(eventNames().pauseEvent);
3432 // Queue a task to fire a simple event named ended at the media element.
3433 if (!m_sentEndEvent) {
3434 m_sentEndEvent = true;
3435 scheduleEvent(eventNames().endedEvent);
3437 // If the media element has a current media controller, then report the controller state
3438 // for the media element's current media controller.
3439 updateMediaController();
3443 m_sentEndEvent = false;
3446 endProcessingMediaPlayerCallback();
3449 void HTMLMediaElement::mediaPlayerVolumeChanged(MediaPlayer*)
3451 LOG(Media, "HTMLMediaElement::mediaPlayerVolumeChanged");
3453 beginProcessingMediaPlayerCallback();
3455 float vol = m_player->volume();
3456 if (vol != m_volume) {
3459 scheduleEvent(eventNames().volumechangeEvent);
3462 endProcessingMediaPlayerCallback();
3465 void HTMLMediaElement::mediaPlayerMuteChanged(MediaPlayer*)
3467 LOG(Media, "HTMLMediaElement::mediaPlayerMuteChanged");
3469 beginProcessingMediaPlayerCallback();
3471 setMuted(m_player->muted());
3472 endProcessingMediaPlayerCallback();
3475 void HTMLMediaElement::mediaPlayerDurationChanged(MediaPlayer* player)
3477 LOG(Media, "HTMLMediaElement::mediaPlayerDurationChanged");
3479 beginProcessingMediaPlayerCallback();
3480 scheduleEvent(eventNames().durationchangeEvent);
3481 mediaPlayerCharacteristicChanged(player);
3482 endProcessingMediaPlayerCallback();
3485 void HTMLMediaElement::mediaPlayerRateChanged(MediaPlayer*)
3487 LOG(Media, "HTMLMediaElement::mediaPlayerRateChanged");
3489 beginProcessingMediaPlayerCallback();
3491 // Stash the rate in case the one we tried to set isn't what the engine is
3492 // using (eg. it can't handle the rate we set)
3493 m_playbackRate = m_player->rate();
3495 invalidateCachedTime();
3498 updateDisableSleep();
3501 endProcessingMediaPlayerCallback();
3504 void HTMLMediaElement::mediaPlayerPlaybackStateChanged(MediaPlayer*)
3506 LOG(Media, "HTMLMediaElement::mediaPlayerPlaybackStateChanged");
3508 if (!m_player || m_pausedInternal)
3511 beginProcessingMediaPlayerCallback();
3512 if (m_player->paused())
3516 endProcessingMediaPlayerCallback();
3519 void HTMLMediaElement::mediaPlayerSawUnsupportedTracks(MediaPlayer*)
3521 LOG(Media, "HTMLMediaElement::mediaPlayerSawUnsupportedTracks");
3523 // The MediaPlayer came across content it cannot completely handle.
3524 // This is normally acceptable except when we are in a standalone
3525 // MediaDocument. If so, tell the document what has happened.
3526 if (ownerDocument()->isMediaDocument()) {
3527 MediaDocument* mediaDocument = static_cast<MediaDocument*>(ownerDocument());
3528 mediaDocument->mediaElementSawUnsupportedTracks();
3532 void HTMLMediaElement::mediaPlayerResourceNotSupported(MediaPlayer*)
3534 LOG(Media, "HTMLMediaElement::mediaPlayerResourceNotSupported");
3536 // The MediaPlayer came across content which no installed engine supports.
3537 mediaLoadingFailed(MediaPlayer::FormatError);
3540 // MediaPlayerPresentation methods
3541 void HTMLMediaElement::mediaPlayerRepaint(MediaPlayer*)
3543 beginProcessingMediaPlayerCallback();
3544 updateDisplayState();
3546 renderer()->repaint();
3547 endProcessingMediaPlayerCallback();
3550 void HTMLMediaElement::mediaPlayerSizeChanged(MediaPlayer*)
3552 LOG(Media, "HTMLMediaElement::mediaPlayerSizeChanged");
3554 beginProcessingMediaPlayerCallback();
3556 renderer()->updateFromElement();
3557 endProcessingMediaPlayerCallback();
3560 #if USE(ACCELERATED_COMPOSITING)
3561 bool HTMLMediaElement::mediaPlayerRenderingCanBeAccelerated(MediaPlayer*)
3563 if (renderer() && renderer()->isVideo()) {
3564 ASSERT(renderer()->view());
3565 return renderer()->view()->compositor()->canAccelerateVideoRendering(toRenderVideo(renderer()));
3570 void HTMLMediaElement::mediaPlayerRenderingModeChanged(MediaPlayer*)
3572 LOG(Media, "HTMLMediaElement::mediaPlayerRenderingModeChanged");
3574 // Kick off a fake recalcStyle that will update the compositing tree.
3575 setNeedsStyleRecalc(SyntheticStyleChange);
3579 #if PLATFORM(WIN) && USE(AVFOUNDATION)
3580 GraphicsDeviceAdapter* HTMLMediaElement::mediaPlayerGraphicsDeviceAdapter(const MediaPlayer*) const
3582 if (!document() || !document()->page())
3585 return document()->page()->chrome()->client()->graphicsDeviceAdapter();
3589 void HTMLMediaElement::mediaPlayerEngineUpdated(MediaPlayer*)
3591 LOG(Media, "HTMLMediaElement::mediaPlayerEngineUpdated");
3592 beginProcessingMediaPlayerCallback();
3594 renderer()->updateFromElement();
3595 endProcessingMediaPlayerCallback();
3598 void HTMLMediaElement::mediaPlayerFirstVideoFrameAvailable(MediaPlayer*)
3600 LOG(Media, "HTMLMediaElement::mediaPlayerFirstVideoFrameAvailable");
3601 beginProcessingMediaPlayerCallback();
3602 if (displayMode() == PosterWaitingForVideo) {
3603 setDisplayMode(Video);
3604 #if USE(ACCELERATED_COMPOSITING)
3605 mediaPlayerRenderingModeChanged(m_player.get());
3608 endProcessingMediaPlayerCallback();
3611 void HTMLMediaElement::mediaPlayerCharacteristicChanged(MediaPlayer*)
3613 LOG(Media, "HTMLMediaElement::mediaPlayerCharacteristicChanged");
3615 beginProcessingMediaPlayerCallback();
3616 if (hasMediaControls())
3617 mediaControls()->reset();
3619 renderer()->updateFromElement();
3620 endProcessingMediaPlayerCallback();
3623 PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const
3626 return TimeRanges::create();
3627 return m_player->buffered();
3630 PassRefPtr<TimeRanges> HTMLMediaElement::played()
3633 float time = currentTime();
3634 if (time > m_lastSeekTime)
3635 addPlayedRange(m_lastSeekTime, time);
3638 if (!m_playedTimeRanges)
3639 m_playedTimeRanges = TimeRanges::create();
3641 return m_playedTimeRanges->copy();
3644 PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const
3646 return m_player ? m_player->seekable() : TimeRanges::create();
3649 bool HTMLMediaElement::potentiallyPlaying() const
3651 // "pausedToBuffer" means the media engine's rate is 0, but only because it had to stop playing
3652 // when it ran out of buffered data. A movie is this state is "potentially playing", modulo the
3653 // checks in couldPlayIfEnoughData().
3654 bool pausedToBuffer = m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA;
3655 return (pausedToBuffer || m_readyState >= HAVE_FUTURE_DATA) && couldPlayIfEnoughData() && !isBlockedOnMediaController();
3658 bool HTMLMediaElement::couldPlayIfEnoughData() const
3660 return !paused() && !endedPlayback() && !stoppedDueToErrors() && !pausedForUserInteraction();
3663 bool HTMLMediaElement::endedPlayback() const
3665 float dur = duration();
3666 if (!m_player || isnan(dur))
3669 // 4.8.10.8 Playing the media resource
3671 // A media element is said to have ended playback when the element's
3672 // readyState attribute is HAVE_METADATA or greater,
3673 if (m_readyState < HAVE_METADATA)
3676 // and the current playback position is the end of the media resource and the direction
3677 // of playback is forwards, Either the media element does not have a loop attribute specified,
3678 // or the media element has a current media controller.
3679 float now = currentTime();
3680 if (m_playbackRate > 0)
3681 return dur > 0 && now >= dur && (!loop() || m_mediaController);
3683 // or the current playback position is the earliest possible position and the direction
3684 // of playback is backwards
3685 if (m_playbackRate < 0)
3691 bool HTMLMediaElement::stoppedDueToErrors() const
3693 if (m_readyState >= HAVE_METADATA && m_error) {
3694 RefPtr<TimeRanges> seekableRanges = seekable();
3695 if (!seekableRanges->contain(currentTime()))
3702 bool HTMLMediaElement::pausedForUserInteraction() const
3704 // return !paused() && m_readyState >= HAVE_FUTURE_DATA && [UA requires a decitions from the user]
3708 float HTMLMediaElement::minTimeSeekable() const
3713 float HTMLMediaElement::maxTimeSeekable() const
3715 return m_player ? m_player->maxTimeSeekable() : 0;
3718 void HTMLMediaElement::updateVolume()
3723 // Avoid recursion when the player reports volume changes.
3724 if (!processingMediaPlayerCallback()) {
3725 Page* page = document()->page();
3726 float volumeMultiplier = page ? page->mediaVolume() : 1;
3727 bool shouldMute = m_muted;
3729 if (m_mediaController) {
3730 volumeMultiplier *= m_mediaController->volume();
3731 shouldMute = m_mediaController->muted();
3734 m_player->setMuted(shouldMute);
3735 m_player->setVolume(m_volume * volumeMultiplier);
3738 if (hasMediaControls())
3739 mediaControls()->changedVolume();
3742 void HTMLMediaElement::updatePlayState()
3747 if (m_pausedInternal) {
3748 if (!m_player->paused())
3750 refreshCachedTime();
3751 m_playbackProgressTimer.stop();
3752 if (hasMediaControls())
3753 mediaControls()->playbackStopped();
3757 bool shouldBePlaying = potentiallyPlaying();
3758 bool playerPaused = m_player->paused();
3760 LOG(Media, "HTMLMediaElement::updatePlayState - shouldBePlaying = %s, playerPaused = %s",
3761 boolString(shouldBePlaying), boolString(playerPaused));
3763 if (shouldBePlaying) {
3764 setDisplayMode(Video);
3765 invalidateCachedTime();
3768 if (!m_isFullscreen && isVideo() && document() && document()->page() && document()->page()->chrome()->requiresFullscreenForVideoPlayback())
3771 // Set rate, muted before calling play in case they were set before the media engine was setup.
3772 // The media engine should just stash the rate and muted values since it isn't already playing.
3773 m_player->setRate(m_playbackRate);
3774 m_player->setMuted(m_muted);
3779 if (hasMediaControls())
3780 mediaControls()->playbackStarted();
3781 startPlaybackProgressTimer();
3783 } else { // Should not be playing right now
3786 refreshCachedTime();
3788 m_playbackProgressTimer.stop();
3790 float time = currentTime();
3791 if (time > m_lastSeekTime)
3792 addPlayedRange(m_lastSeekTime, time);
3794 if (couldPlayIfEnoughData())
3797 if (hasMediaControls())
3798 mediaControls()->playbackStopped();
3801 updateMediaController();
3804 renderer()->updateFromElement();
3807 void HTMLMediaElement::setPausedInternal(bool b)
3809 m_pausedInternal = b;
3813 void HTMLMediaElement::stopPeriodicTimers()
3815 m_progressEventTimer.stop();
3816 m_playbackProgressTimer.stop();
3819 void HTMLMediaElement::userCancelledLoad()
3821 LOG(Media, "HTMLMediaElement::userCancelledLoad");
3823 if (m_networkState == NETWORK_EMPTY || m_completelyLoaded)
3826 // If the media data fetching process is aborted by the user:
3828 // 1 - The user agent should cancel the fetching process.
3829 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
3832 stopPeriodicTimers();
3834 m_loadState = WaitingForSource;
3835 m_pendingLoadFlags = 0;
3837 // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED.
3838 m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED);
3840 // 3 - Queue a task to fire a simple event named error at the media element.
3841 scheduleEvent(eventNames().abortEvent);
3843 #if ENABLE(MEDIA_SOURCE)
3844 if (m_sourceState != SOURCE_CLOSED)
3845 setSourceState(SOURCE_CLOSED);
3848 // 4 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the
3849 // element's networkState attribute to the NETWORK_EMPTY value and queue a task to fire a
3850 // simple event named emptied at the element. Otherwise, set the element's networkState
3851 // attribute to the NETWORK_IDLE value.
3852 if (m_readyState == HAVE_NOTHING) {
3853 m_networkState = NETWORK_EMPTY;
3854 scheduleEvent(eventNames().emptiedEvent);
3857 m_networkState = NETWORK_IDLE;
3859 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
3860 setShouldDelayLoadEvent(false);
3862 // 6 - Abort the overall resource selection algorithm.
3863 m_currentSourceNode = 0;
3865 // Reset m_readyState since m_player is gone.
3866 m_readyState = HAVE_NOTHING;
3867 updateMediaController();
3868 #if ENABLE(VIDEO_TRACK)
3869 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3870 updateActiveTextTrackCues(0);
3874 bool HTMLMediaElement::canSuspend() const
3879 void HTMLMediaElement::stop()
3881 LOG(Media, "HTMLMediaElement::stop");
3885 m_inActiveDocument = false;
3886 userCancelledLoad();
3888 // Stop the playback without generating events
3889 setPausedInternal(true);
3892 renderer()->updateFromElement();
3894 stopPeriodicTimers();
3895 cancelPendingEventsAndCallbacks();
3898 void HTMLMediaElement::suspend(ReasonForSuspension why)
3900 LOG(Media, "HTMLMediaElement::suspend");
3901 #if ENABLE(TIZEN_DLOG_SUPPORT)
3902 TIZEN_LOGI("Why: %d", why);
3907 case DocumentWillBecomeInactive:
3910 case PageWillBeSuspended:
3911 case JavaScriptDebuggerPaused:
3912 case WillDeferLoading:
3913 // Do nothing, we don't pause media playback in these cases.
3914 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
3915 if (shouldSuspendMedia()) {
3917 m_player->suspend();
3925 void HTMLMediaElement::resume()
3927 LOG(Media, "HTMLMediaElement::resume");
3929 m_inActiveDocument = true;
3930 setPausedInternal(false);
3932 if (m_error && m_error->code() == MediaError::MEDIA_ERR_ABORTED) {
3933 // Restart the load if it was aborted in the middle by moving the document to the page cache.
3934 // m_error is only left at MEDIA_ERR_ABORTED when the document becomes inactive (it is set to
3935 // MEDIA_ERR_ABORTED while the abortEvent is being sent, but cleared immediately afterwards).
3936 // This behavior is not specified but it seems like a sensible thing to do.
3937 // As it is not safe to immedately start loading now, let's schedule a load.
3938 scheduleLoad(MediaResource);
3942 renderer()->updateFromElement();
3944 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
3945 // Player will resume asynchronously.
3946 // Set m_suspended to false in setSuspended().
3952 bool HTMLMediaElement::hasPendingActivity() const
3954 return m_asyncEventQueue->hasPendingEvents();
3957 void HTMLMediaElement::mediaVolumeDidChange()
3959 LOG(Media, "HTMLMediaElement::mediaVolumeDidChange");
3963 void HTMLMediaElement::defaultEventHandler(Event* event)
3965 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
3966 RenderObject* r = renderer();
3967 if (!r || !r->isWidget())
3970 Widget* widget = toRenderWidget(r)->widget();
3972 widget->handleEvent(event);
3974 HTMLElement::defaultEventHandler(event);
3978 bool HTMLMediaElement::willRespondToMouseClickEvents()
3980 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
3983 return HTMLElement::willRespondToMouseClickEvents();
3987 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
3989 void HTMLMediaElement::ensureMediaPlayer()
3992 createMediaPlayer();
3995 void HTMLMediaElement::deliverNotification(MediaPlayerProxyNotificationType notification)
3997 if (notification == MediaPlayerNotificationPlayPauseButtonPressed) {
4003 m_player->deliverNotification(notification);
4006 void HTMLMediaElement::setMediaPlayerProxy(WebMediaPlayerProxy* proxy)
4008 ensureMediaPlayer();
4009 m_player->setMediaPlayerProxy(proxy);
4012 void HTMLMediaElement::getPluginProxyParams(KURL& url, Vector<String>& names, Vector<String>& values)
4014 RefPtr<HTMLMediaElement> protect(this); // selectNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations.
4016 Frame* frame = document()->frame();
4019 KURL posterURL = getNonEmptyURLAttribute(posterAttr);
4020 if (!posterURL.isEmpty() && frame && frame->loader()->willLoadMediaElementURL(posterURL)) {
4021 names.append("_media_element_poster_");
4022 values.append(posterURL.string());
4027 names.append("_media_element_controls_");
4028 values.append("true");
4032 if (!isSafeToLoadURL(url, Complain))
4033 url = selectNextSourceChild(0, 0, DoNothing);
4036 if (url.isValid() && frame && frame->loader()->willLoadMediaElementURL(url)) {
4037 names.append("_media_element_src_");
4038 values.append(m_currentSrc.string());
4042 void HTMLMediaElement::createMediaPlayerProxy()
4044 ensureMediaPlayer();
4046 if (m_proxyWidget || (inDocument() && !m_needWidgetUpdate))
4049 Frame* frame = document()->frame();
4053 LOG(Media, "HTMLMediaElement::createMediaPlayerProxy");
4056 Vector<String> paramNames;
4057 Vector<String> paramValues;
4059 getPluginProxyParams(url, paramNames, paramValues);
4061 // Hang onto the proxy widget so it won't be destroyed if the plug-in is set to
4063 m_proxyWidget = frame->loader()->subframeLoader()->loadMediaPlayerProxyPlugin(this, url, paramNames, paramValues);
4065 m_needWidgetUpdate = false;
4068 void HTMLMediaElement::updateWidget(PluginCreationOption)
4070 mediaElement->setNeedWidgetUpdate(false);
4072 Vector<String> paramNames;
4073 Vector<String> paramValues;
4074 // FIXME: Rename kurl to something more sensible.
4077 mediaElement->getPluginProxyParams(kurl, paramNames, paramValues);
4078 // FIXME: What if document()->frame() is 0?
4079 SubframeLoader* loader = document()->frame()->loader()->subframeLoader();
4080 loader->loadMediaPlayerProxyPlugin(mediaElement, kurl, paramNames, paramValues);
4083 #endif // ENABLE(PLUGIN_PROXY_FOR_VIDEO)
4085 bool HTMLMediaElement::isFullscreen() const
4090 #if ENABLE(FULLSCREEN_API)
4091 if (document()->webkitIsFullScreen() && document()->webkitCurrentFullScreenElement() == this)
4098 void HTMLMediaElement::enterFullscreen()
4100 LOG(Media, "HTMLMediaElement::enterFullscreen");
4102 #if ENABLE(FULLSCREEN_API)
4103 if (document() && document()->settings() && document()->settings()->fullScreenEnabled()) {
4104 document()->requestFullScreenForElement(this, 0, Document::ExemptIFrameAllowFullScreenRequirement);
4108 ASSERT(!m_isFullscreen);
4109 m_isFullscreen = true;
4110 if (hasMediaControls())
4111 mediaControls()->enteredFullscreen();
4112 if (document() && document()->page()) {
4113 document()->page()->chrome()->client()->enterFullscreenForNode(this);
4114 scheduleEvent(eventNames().webkitbeginfullscreenEvent);
4119 void HTMLMediaElement::exitFullscreen()
4121 LOG(Media, "HTMLMediaElement::exitFullscreen");
4123 #if ENABLE(FULLSCREEN_API)
4124 if (document() && document()->settings() && document()->settings()->fullScreenEnabled()) {
4125 if (document()->webkitIsFullScreen() && document()->webkitCurrentFullScreenElement() == this)
4126 document()->webkitCancelFullScreen();
4130 ASSERT(m_isFullscreen);
4131 m_isFullscreen = false;
4132 if (hasMediaControls())
4133 mediaControls()->exitedFullscreen();
4134 if (document() && document()->page()) {
4135 if (document()->page()->chrome()->requiresFullscreenForVideoPlayback())
4137 document()->page()->chrome()->client()->exitFullscreenForNode(this);
4138 scheduleEvent(eventNames().webkitendfullscreenEvent);
4142 #if ENABLE(TIZEN_FULLSCREEN_API)
4143 void HTMLMediaElement::updateMediaControlsStyle(bool needsRecalc)
4145 if (hasMediaControls())
4146 mediaControls()->updateMediaControlScale();
4149 recalcStyle(Node::Force);
4153 void HTMLMediaElement::didBecomeFullscreenElement()
4155 if (hasMediaControls())
4156 mediaControls()->enteredFullscreen();
4159 void HTMLMediaElement::willStopBeingFullscreenElement()
4161 if (hasMediaControls())
4162 mediaControls()->exitedFullscreen();
4165 PlatformMedia HTMLMediaElement::platformMedia() const
4167 return m_player ? m_player->platformMedia() : NoPlatformMedia;
4170 #if USE(ACCELERATED_COMPOSITING)
4171 PlatformLayer* HTMLMediaElement::platformLayer() const
4173 return m_player ? m_player->platformLayer() : 0;
4177 bool HTMLMediaElement::hasClosedCaptions() const
4179 if (m_player && m_player->hasClosedCaptions())
4182 #if ENABLE(VIDEO_TRACK)
4183 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled() && m_textTracks)
4184 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
4185 if (m_textTracks->item(i)->kind() == TextTrack::captionsKeyword()
4186 || m_textTracks->item(i)->kind() == TextTrack::subtitlesKeyword())
4193 bool HTMLMediaElement::closedCaptionsVisible() const
4195 return m_closedCaptionsVisible;
4198 void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible)
4200 LOG(Media, "HTMLMediaElement::setClosedCaptionsVisible(%s)", boolString(closedCaptionVisible));
4202 if (!m_player || !hasClosedCaptions())
4205 m_closedCaptionsVisible = closedCaptionVisible;
4206 m_player->setClosedCaptionsVisible(closedCaptionVisible);
4208 #if ENABLE(VIDEO_TRACK)
4209 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) {
4210 m_disableCaptions = !m_closedCaptionsVisible;
4212 // Mark all track elements as not "configured" so that configureTextTracks()
4213 // will reconsider which tracks to display in light of new user preferences
4214 // (e.g. default tracks should not be displayed if the user has turned off
4215 // captions and non-default tracks should be displayed based on language
4216 // preferences if the user has turned captions on).
4217 for (Node* node = firstChild(); node; node = node->nextSibling()) {
4218 if (!node->hasTagName(trackTag))
4220 HTMLTrackElement* trackElement = static_cast<HTMLTrackElement*>(node);
4221 if (trackElement->kind() == TextTrack::captionsKeyword()
4222 || trackElement->kind() == TextTrack::subtitlesKeyword())
4223 trackElement->setHasBeenConfigured(false);
4226 configureTextTracks();
4229 if (hasMediaControls())
4230 mediaControls()->changedClosedCaptionsVisibility();
4234 void HTMLMediaElement::setWebkitClosedCaptionsVisible(bool visible)
4236 setClosedCaptionsVisible(visible);
4239 bool HTMLMediaElement::webkitClosedCaptionsVisible() const
4241 return closedCaptionsVisible();
4245 bool HTMLMediaElement::webkitHasClosedCaptions() const
4247 return hasClosedCaptions();
4250 #if ENABLE(MEDIA_STATISTICS)
4251 unsigned HTMLMediaElement::webkitAudioDecodedByteCount() const
4255 return m_player->audioDecodedByteCount();
4258 unsigned HTMLMediaElement::webkitVideoDecodedByteCount() const
4262 return m_player->videoDecodedByteCount();
4266 void HTMLMediaElement::mediaCanStart()
4268 LOG(Media, "HTMLMediaElement::mediaCanStart");
4270 ASSERT(m_isWaitingUntilMediaCanStart);
4271 m_isWaitingUntilMediaCanStart = false;
4275 bool HTMLMediaElement::isURLAttribute(const Attribute& attribute) const
4277 return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute);
4280 void HTMLMediaElement::setShouldDelayLoadEvent(bool shouldDelay)
4282 if (m_shouldDelayLoadEvent == shouldDelay)
4285 LOG(Media, "HTMLMediaElement::setShouldDelayLoadEvent(%s)", boolString(shouldDelay));
4287 m_shouldDelayLoadEvent = shouldDelay;
4289 document()->incrementLoadEventDelayCount();
4291 document()->decrementLoadEventDelayCount();
4295 void HTMLMediaElement::getSitesInMediaCache(Vector<String>& sites)
4297 MediaPlayer::getSitesInMediaCache(sites);
4300 void HTMLMediaElement::clearMediaCache()
4302 MediaPlayer::clearMediaCache();
4305 void HTMLMediaElement::clearMediaCacheForSite(const String& site)
4307 MediaPlayer::clearMediaCacheForSite(site);
4310 void HTMLMediaElement::privateBrowsingStateDidChange()
4315 Settings* settings = document()->settings();
4316 bool privateMode = !settings || settings->privateBrowsingEnabled();
4317 LOG(Media, "HTMLMediaElement::privateBrowsingStateDidChange(%s)", boolString(privateMode));
4318 m_player->setPrivateBrowsingMode(privateMode);
4321 MediaControls* HTMLMediaElement::mediaControls() const
4323 return toMediaControls(userAgentShadowRoot()->firstChild());
4326 bool HTMLMediaElement::hasMediaControls() const
4328 if (ShadowRoot* userAgent = userAgentShadowRoot()) {
4329 Node* node = userAgent->firstChild();
4330 ASSERT(!node || node->isMediaControls());
4337 bool HTMLMediaElement::createMediaControls()
4339 if (hasMediaControls())
4343 RefPtr<MediaControls> controls = MediaControls::create(document());
4347 controls->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this));
4349 #if ENABLE(TIZEN_FULLSCREEN_API)
4351 controls->updateMediaControlScale();
4354 controls->enteredFullscreen();
4357 createShadowSubtree();
4359 ASSERT(userAgentShadowRoot());
4360 userAgentShadowRoot()->appendChild(controls, ec);
4364 void HTMLMediaElement::configureMediaControls()
4366 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
4367 if (!controls() || !inDocument()) {
4368 if (hasMediaControls())
4369 mediaControls()->hide();
4373 if (!hasMediaControls() && !createMediaControls())
4376 mediaControls()->show();
4379 m_player->setControls(controls());
4383 #if ENABLE(VIDEO_TRACK)
4384 void HTMLMediaElement::configureTextTrackDisplay()
4386 ASSERT(m_textTracks);
4388 bool haveVisibleTextTrack = false;
4389 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
4390 if (m_textTracks->item(i)->mode() == TextTrack::showingKeyword()) {
4391 haveVisibleTextTrack = true;
4396 if (m_haveVisibleTextTrack == haveVisibleTextTrack)
4398 m_haveVisibleTextTrack = haveVisibleTextTrack;
4399 m_closedCaptionsVisible = m_haveVisibleTextTrack;
4401 if (!m_haveVisibleTextTrack && !hasMediaControls())
4403 if (!hasMediaControls() && !createMediaControls())
4406 updateClosedCaptionsControls();
4409 void HTMLMediaElement::updateClosedCaptionsControls()
4411 if (hasMediaControls()) {
4412 mediaControls()->changedClosedCaptionsVisibility();
4414 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
4415 mediaControls()->updateTextTrackDisplay();
4420 void* HTMLMediaElement::preDispatchEventHandler(Event* event)
4422 if (event && event->type() == eventNames().webkitfullscreenchangeEvent) {
4423 configureMediaControls();
4424 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
4425 if (!isFullscreen())
4426 scheduleEvent(eventNames().webkitendfullscreenEvent);
4433 void HTMLMediaElement::createMediaPlayer()
4435 #if ENABLE(WEB_AUDIO)
4436 if (m_audioSourceNode)
4437 m_audioSourceNode->lock();
4440 m_player = MediaPlayer::create(this);
4442 #if ENABLE(WEB_AUDIO)
4443 if (m_audioSourceNode) {
4444 // When creating the player, make sure its AudioSourceProvider knows about the MediaElementAudioSourceNode.
4445 if (audioSourceProvider())
4446 audioSourceProvider()->setClient(m_audioSourceNode);
4448 m_audioSourceNode->unlock();
4453 #if ENABLE(WEB_AUDIO)
4454 void HTMLMediaElement::setAudioSourceNode(MediaElementAudioSourceNode* sourceNode)
4456 m_audioSourceNode = sourceNode;
4458 if (audioSourceProvider())
4459 audioSourceProvider()->setClient(m_audioSourceNode);
4462 AudioSourceProvider* HTMLMediaElement::audioSourceProvider()
4465 return m_player->audioSourceProvider();
4471 #if ENABLE(MICRODATA)
4472 String HTMLMediaElement::itemValueText() const
4474 return getURLAttribute(srcAttr);
4477 void HTMLMediaElement::setItemValueText(const String& value, ExceptionCode&)
4479 setAttribute(srcAttr, value);
4483 const String& HTMLMediaElement::mediaGroup() const
4485 return m_mediaGroup;
4488 void HTMLMediaElement::setMediaGroup(const String& group)
4490 if (m_mediaGroup == group)
4492 m_mediaGroup = group;
4494 // When a media element is created with a mediagroup attribute, and when a media element's mediagroup
4495 // attribute is set, changed, or removed, the user agent must run the following steps:
4496 // 1. Let m [this] be the media element in question.
4497 // 2. Let m have no current media controller, if it currently has one.
4500 // 3. If m's mediagroup attribute is being removed, then abort these steps.
4501 if (group.isNull() || group.isEmpty())
4504 // 4. If there is another media element whose Document is the same as m's Document (even if one or both
4505 // of these elements are not actually in the Document),
4506 HashSet<HTMLMediaElement*> elements = documentToElementSetMap().get(document());
4507 for (HashSet<HTMLMediaElement*>::iterator i = elements.begin(); i != elements.end(); ++i) {
4511 // and which also has a mediagroup attribute, and whose mediagroup attribute has the same value as
4512 // the new value of m's mediagroup attribute,
4513 if ((*i)->mediaGroup() == group) {
4514 // then let controller be that media element's current media controller.
4515 setController((*i)->controller());
4520 // Otherwise, let controller be a newly created MediaController.
4521 setController(MediaController::create(Node::scriptExecutionContext()));
4524 MediaController* HTMLMediaElement::controller() const
4526 return m_mediaController.get();
4529 void HTMLMediaElement::setController(PassRefPtr<MediaController> controller)
4531 if (m_mediaController)
4532 m_mediaController->removeMediaElement(this);
4534 m_mediaController = controller;
4536 if (m_mediaController)
4537 m_mediaController->addMediaElement(this);
4539 if (hasMediaControls())
4540 mediaControls()->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this));
4543 void HTMLMediaElement::updateMediaController()
4545 if (m_mediaController)
4546 m_mediaController->reportControllerState();
4549 bool HTMLMediaElement::dispatchEvent(PassRefPtr<Event> event)
4551 bool dispatchResult;
4552 bool isCanPlayEvent;
4554 isCanPlayEvent = (event->type() == eventNames().canplayEvent);
4557 m_dispatchingCanPlayEvent = true;
4559 dispatchResult = HTMLElement::dispatchEvent(event);
4562 m_dispatchingCanPlayEvent = false;
4564 return dispatchResult;
4567 bool HTMLMediaElement::isBlocked() const
4569 // A media element is a blocked media element if its readyState attribute is in the
4570 // HAVE_NOTHING state, the HAVE_METADATA state, or the HAVE_CURRENT_DATA state,
4571 if (m_readyState <= HAVE_CURRENT_DATA)
4574 // or if the element has paused for user interaction.
4575 return pausedForUserInteraction();
4578 bool HTMLMediaElement::isBlockedOnMediaController() const
4580 if (!m_mediaController)
4583 // A media element is blocked on its media controller if the MediaController is a blocked
4584 // media controller,
4585 if (m_mediaController->isBlocked())
4588 // or if its media controller position is either before the media resource's earliest possible
4589 // position relative to the MediaController's timeline or after the end of the media resource
4590 // relative to the MediaController's timeline.
4591 float mediaControllerPosition = m_mediaController->currentTime();
4592 if (mediaControllerPosition < startTime() || mediaControllerPosition > startTime() + duration())
4598 void HTMLMediaElement::prepareMediaFragmentURI()
4600 MediaFragmentURIParser fragmentParser(m_currentSrc);
4601 float dur = duration();
4603 double start = fragmentParser.startTime();
4604 if (start != MediaFragmentURIParser::invalidTimeValue() && start > 0) {
4605 m_fragmentStartTime = start;
4606 if (m_fragmentStartTime > dur)
4607 m_fragmentStartTime = dur;
4609 m_fragmentStartTime = MediaPlayer::invalidTime();
4611 double end = fragmentParser.endTime();
4612 if (end != MediaFragmentURIParser::invalidTimeValue() && end > 0 && end > m_fragmentStartTime) {
4613 m_fragmentEndTime = end;
4614 if (m_fragmentEndTime > dur)
4615 m_fragmentEndTime = dur;
4617 m_fragmentEndTime = MediaPlayer::invalidTime();
4619 if (m_fragmentStartTime != MediaPlayer::invalidTime() && m_readyState < HAVE_FUTURE_DATA)
4623 void HTMLMediaElement::applyMediaFragmentURI()
4625 if (m_fragmentStartTime != MediaPlayer::invalidTime()) {
4626 ExceptionCode ignoredException;
4627 m_sentEndEvent = false;
4628 seek(m_fragmentStartTime, ignoredException);
4633 void HTMLMediaElement::updateDisableSleep()
4635 if (!shouldDisableSleep() && m_sleepDisabler)
4636 m_sleepDisabler = nullptr;
4637 else if (shouldDisableSleep() && !m_sleepDisabler)
4638 m_sleepDisabler = DisplaySleepDisabler::create("com.apple.WebCore: HTMLMediaElement playback");
4641 bool HTMLMediaElement::shouldDisableSleep() const
4643 return m_player && !m_player->paused() && hasVideo() && hasAudio() && !loop();
4647 String HTMLMediaElement::mediaPlayerReferrer() const
4649 Frame* frame = document()->frame();
4653 return SecurityPolicy::generateReferrerHeader(document()->referrerPolicy(), m_currentSrc, frame->loader()->outgoingReferrer());
4656 String HTMLMediaElement::mediaPlayerUserAgent() const
4658 Frame* frame = document()->frame();
4662 return frame->loader()->userAgent(m_currentSrc);
4666 MediaPlayerClient::CORSMode HTMLMediaElement::mediaPlayerCORSMode() const
4668 if (!fastHasAttribute(HTMLNames::crossoriginAttr))
4670 if (equalIgnoringCase(fastGetAttribute(HTMLNames::crossoriginAttr), "use-credentials"))
4671 return UseCredentials;
4675 bool HTMLMediaElement::mediaPlayerNeedsSiteSpecificHacks() const
4677 Settings* settings = document()->settings();
4678 return settings && settings->needsSiteSpecificQuirks();
4681 String HTMLMediaElement::mediaPlayerDocumentHost() const
4683 return document()->url().host();
4686 void HTMLMediaElement::mediaPlayerExitFullscreen()
4691 bool HTMLMediaElement::mediaPlayerIsVideo() const
4696 LayoutRect HTMLMediaElement::mediaPlayerContentBoxRect() const
4699 return renderer()->enclosingBox()->contentBoxRect();
4700 return LayoutRect();
4703 void HTMLMediaElement::mediaPlayerSetSize(const IntSize& size)
4705 setAttribute(widthAttr, String::number(size.width()));
4706 setAttribute(heightAttr, String::number(size.height()));
4709 void HTMLMediaElement::mediaPlayerPause()
4714 void HTMLMediaElement::mediaPlayerPlay()
4719 bool HTMLMediaElement::mediaPlayerIsPaused() const
4724 bool HTMLMediaElement::mediaPlayerIsLooping() const
4729 HostWindow* HTMLMediaElement::mediaPlayerHostWindow()
4731 return mediaPlayerOwningDocument()->view()->hostWindow();
4734 IntRect HTMLMediaElement::mediaPlayerWindowClipRect()
4736 return mediaPlayerOwningDocument()->view()->windowClipRect();
4739 void HTMLMediaElement::removeBehaviorsRestrictionsAfterFirstUserGesture()
4741 m_restrictions = NoRestrictions;
4744 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
4745 bool HTMLMediaElement::shouldSuspendMedia()
4753 #if ENABLE(TIZEN_EXTENSIBLE_API)
4754 if (!TizenExtensibleAPI::extensibleAPI().backgroundMusic())
4761 void HTMLMediaElement::setSuspended(bool suspended)
4763 m_suspended = suspended;