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"
122 #if ENABLE(TIZEN_DEVICE_ROTATION)
131 static String urlForLogging(const KURL& url)
133 static const unsigned maximumURLLengthForLogging = 128;
135 if (url.string().length() < maximumURLLengthForLogging)
137 return url.string().substring(0, maximumURLLengthForLogging) + "...";
140 static const char* boolString(bool val)
142 return val ? "true" : "false";
146 #ifndef LOG_MEDIA_EVENTS
147 // Default to not logging events because so many are generated they can overwhelm the rest of
149 #define LOG_MEDIA_EVENTS 0
152 #ifndef LOG_CACHED_TIME_WARNINGS
153 // Default to not logging warnings about excessive drift in the cached media time because it adds a
154 // fair amount of overhead and logging.
155 #define LOG_CACHED_TIME_WARNINGS 0
158 #if ENABLE(MEDIA_SOURCE)
159 // URL protocol used to signal that the media source API is being used.
160 static const char* mediaSourceURLProtocol = "x-media-source";
163 #if ENABLE(TIZEN_DEVICE_ROTATION)
164 #define RADIAN_VALUE (57.2957)
175 using namespace HTMLNames;
178 typedef HashMap<Document*, HashSet<HTMLMediaElement*> > DocumentElementSetMap;
179 static DocumentElementSetMap& documentToElementSetMap()
181 DEFINE_STATIC_LOCAL(DocumentElementSetMap, map, ());
185 static void addElementToDocumentMap(HTMLMediaElement* element, Document* document)
187 DocumentElementSetMap& map = documentToElementSetMap();
188 HashSet<HTMLMediaElement*> set = map.take(document);
190 map.add(document, set);
193 static void removeElementFromDocumentMap(HTMLMediaElement* element, Document* document)
195 DocumentElementSetMap& map = documentToElementSetMap();
196 HashSet<HTMLMediaElement*> set = map.take(document);
199 map.add(document, set);
202 #if ENABLE(ENCRYPTED_MEDIA)
203 static ExceptionCode exceptionCodeForMediaKeyException(MediaPlayer::MediaKeyException exception)
206 case MediaPlayer::NoError:
208 case MediaPlayer::InvalidPlayerState:
209 return INVALID_STATE_ERR;
210 case MediaPlayer::KeySystemNotSupported:
211 return NOT_SUPPORTED_ERR;
214 ASSERT_NOT_REACHED();
215 return INVALID_STATE_ERR;
219 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* document, bool createdByParser)
220 : HTMLElement(tagName, document)
221 , ActiveDOMObject(document, this)
222 #if ENABLE(TIZEN_DEVICE_ROTATION)
223 , m_rotation(ROTATE_0)
225 , m_loadTimer(this, &HTMLMediaElement::loadTimerFired)
226 , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired)
227 , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired)
228 , m_playedTimeRanges()
229 , m_asyncEventQueue(GenericEventQueue::create(this))
230 , m_playbackRate(1.0f)
231 , m_defaultPlaybackRate(1.0f)
232 , m_webkitPreservesPitch(true)
233 , m_networkState(NETWORK_EMPTY)
234 , m_readyState(HAVE_NOTHING)
235 , m_readyStateMaximum(HAVE_NOTHING)
238 , m_previousProgressTime(numeric_limits<double>::max())
239 , m_lastTimeUpdateEventWallTime(0)
240 , m_lastTimeUpdateEventMovieTime(numeric_limits<float>::max())
241 , m_loadState(WaitingForSource)
242 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
245 , m_restrictions(RequireUserGestureForFullscreenRestriction | RequirePageConsentToLoadMediaRestriction)
246 , m_preload(MediaPlayer::Auto)
247 , m_displayMode(Unknown)
248 , m_processingMediaPlayerCallback(0)
249 #if ENABLE(MEDIA_SOURCE)
250 , m_sourceState(SOURCE_CLOSED)
252 , m_cachedTime(MediaPlayer::invalidTime())
253 , m_cachedTimeWallClockUpdateTime(0)
254 , m_minimumWallClockTimeToCacheMediaTime(0)
255 , m_fragmentStartTime(MediaPlayer::invalidTime())
256 , m_fragmentEndTime(MediaPlayer::invalidTime())
257 , m_pendingLoadFlags(0)
259 , m_isWaitingUntilMediaCanStart(false)
260 , m_shouldDelayLoadEvent(false)
261 , m_haveFiredLoadedData(false)
262 , m_inActiveDocument(true)
263 , m_autoplaying(true)
267 , m_sentStalledEvent(false)
268 , m_sentEndEvent(false)
269 , m_pausedInternal(false)
270 , m_sendProgressEvents(true)
271 , m_isFullscreen(false)
272 , m_closedCaptionsVisible(false)
273 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
274 , m_needWidgetUpdate(false)
276 , m_dispatchingCanPlayEvent(false)
277 , m_loadInitiatedByUserGesture(false)
278 , m_completelyLoaded(false)
279 , m_havePreparedToPlay(false)
280 , m_parsingInProgress(createdByParser)
281 #if ENABLE(VIDEO_TRACK)
282 , m_tracksAreReady(true)
283 , m_haveVisibleTextTrack(false)
284 , m_lastTextTrackUpdateTime(-1)
286 , m_ignoreTrackDisplayUpdate(0)
287 , m_disableCaptions(false)
289 #if ENABLE(WEB_AUDIO)
290 , m_audioSourceNode(0)
292 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
296 LOG(Media, "HTMLMediaElement::HTMLMediaElement");
297 document->registerForMediaVolumeCallbacks(this);
298 document->registerForPrivateBrowsingStateChangedCallbacks(this);
300 if (document->settings() && document->settings()->mediaPlaybackRequiresUserGesture()) {
301 addBehaviorRestriction(RequireUserGestureForRateChangeRestriction);
302 addBehaviorRestriction(RequireUserGestureForLoadRestriction);
305 #if ENABLE(MEDIA_SOURCE)
306 m_mediaSourceURL.setProtocol(mediaSourceURLProtocol);
307 m_mediaSourceURL.setPath(createCanonicalUUIDString());
310 setHasCustomCallbacks();
311 addElementToDocumentMap(this, document);
312 #if ENABLE(TIZEN_DEVICE_ROTATION)
313 registerRotationCallback();
317 HTMLMediaElement::~HTMLMediaElement()
319 LOG(Media, "HTMLMediaElement::~HTMLMediaElement");
320 if (m_isWaitingUntilMediaCanStart)
321 document()->removeMediaCanStartListener(this);
322 setShouldDelayLoadEvent(false);
323 document()->unregisterForMediaVolumeCallbacks(this);
324 document()->unregisterForPrivateBrowsingStateChangedCallbacks(this);
325 #if ENABLE(VIDEO_TRACK)
327 m_textTracks->clearOwner();
329 for (unsigned i = 0; i < m_textTracks->length(); ++i)
330 m_textTracks->item(i)->clearClient();
334 if (m_mediaController)
335 m_mediaController->removeMediaElement(this);
337 removeElementFromDocumentMap(this, document());
338 #if ENABLE(TIZEN_DEVICE_ROTATION)
339 unregisterRotationCallback();
343 #if ENABLE(TIZEN_DEVICE_ROTATION)
344 bool HTMLMediaElement::isHorizontal()
346 return (m_rotation == ROTATE_90 || m_rotation == ROTATE_270) ? true : false;
349 bool HTMLMediaElement::isVertical()
351 return (m_rotation == ROTATE_0 || m_rotation == ROTATE_180) ? true : false;
354 void HTMLMediaElement::registerRotationCallback()
356 sensor_create(&m_handle);
357 sensor_accelerometer_set_cb(m_handle, 0, onRotationChanged, this);
358 sensor_start(m_handle, SENSOR_ACCELEROMETER);
363 void HTMLMediaElement::unregisterRotationCallback()
365 sensor_accelerometer_unset_cb(m_handle);
366 sensor_stop(m_handle, SENSOR_ACCELEROMETER);
367 sensor_destroy(m_handle);
372 int HTMLMediaElement::calcRotation(float x, float y, float z)
374 double atanV, normZ, rawZ;
375 int accTheta, accPitch;
379 accTheta = (int)(atanV * (RADIAN_VALUE) + 270) % 360;
380 rawZ = (double)(z / (0.004 * 9.81));
384 else if (rawZ < -250)
387 normZ = ((double)rawZ) / 250;
389 accPitch = (int)(acos(normZ) * (RADIAN_VALUE));
391 if ((accPitch > 35) && (accPitch < 145)) {
392 if ((accTheta >= 315 && accTheta <= 359) || (accTheta >= 0 && accTheta < 45))
394 else if (accTheta >= 45 && accTheta < 135)
395 rotation = ROTATE_90;
396 else if (accTheta >= 135 && accTheta < 225)
397 rotation = ROTATE_180;
398 else if (accTheta >= 225 && accTheta < 315)
399 rotation = ROTATE_270;
401 rotation = ROTATE_ERROR;
403 rotation = ROTATE_ERROR;
408 void HTMLMediaElement::onRotationChanged(uint64_t timeStamp, sensor_data_accuracy_e accuracy, float x, float y, float z, void* userData)
410 HTMLMediaElement* that = static_cast<HTMLMediaElement*>(userData);
412 int autoRotation = 0;
413 vconf_get_bool(VCONFKEY_SETAPPL_AUTO_ROTATE_SCREEN_BOOL, &autoRotation);
417 int rotation = that->calcRotation(x, y, z);
418 if (rotation == ROTATE_ERROR || rotation == that->m_rotation)
421 that->m_rotation = rotation;
425 void HTMLMediaElement::didMoveToNewDocument(Document* oldDocument)
427 if (m_isWaitingUntilMediaCanStart) {
429 oldDocument->removeMediaCanStartListener(this);
430 document()->addMediaCanStartListener(this);
433 if (m_shouldDelayLoadEvent) {
435 oldDocument->decrementLoadEventDelayCount();
436 document()->incrementLoadEventDelayCount();
440 oldDocument->unregisterForMediaVolumeCallbacks(this);
441 removeElementFromDocumentMap(this, oldDocument);
444 document()->registerForMediaVolumeCallbacks(this);
445 addElementToDocumentMap(this, document());
447 HTMLElement::didMoveToNewDocument(oldDocument);
450 bool HTMLMediaElement::supportsFocus() const
452 if (ownerDocument()->isMediaDocument())
455 // If no controls specified, we should still be able to focus the element if it has tabIndex.
456 return controls() || HTMLElement::supportsFocus();
459 bool HTMLMediaElement::isMouseFocusable() const
464 void HTMLMediaElement::parseAttribute(const Attribute& attribute)
466 if (attribute.name() == srcAttr) {
467 // Trigger a reload, as long as the 'src' attribute is present.
468 if (fastHasAttribute(srcAttr))
469 scheduleLoad(MediaResource);
470 } else if (attribute.name() == controlsAttr)
471 configureMediaControls();
473 else if (attribute.name() == loopAttr)
474 updateDisableSleep();
476 #if ENABLE(TIZEN_MEDIA_STREAM)
477 else if (attribute.name() == mutedAttr)
480 else if (attribute.name() == preloadAttr) {
481 if (equalIgnoringCase(attribute.value(), "none"))
482 m_preload = MediaPlayer::None;
483 else if (equalIgnoringCase(attribute.value(), "metadata"))
484 m_preload = MediaPlayer::MetaData;
486 // The spec does not define an "invalid value default" but "auto" is suggested as the
487 // "missing value default", so use it for everything except "none" and "metadata"
488 m_preload = MediaPlayer::Auto;
491 // The attribute must be ignored if the autoplay attribute is present
492 if (!autoplay() && m_player)
493 m_player->setPreload(m_preload);
495 } else if (attribute.name() == mediagroupAttr)
496 setMediaGroup(attribute.value());
497 else if (attribute.name() == onabortAttr)
498 setAttributeEventListener(eventNames().abortEvent, createAttributeEventListener(this, attribute));
499 else if (attribute.name() == onbeforeloadAttr)
500 setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attribute));
501 else if (attribute.name() == oncanplayAttr)
502 setAttributeEventListener(eventNames().canplayEvent, createAttributeEventListener(this, attribute));
503 else if (attribute.name() == oncanplaythroughAttr)
504 setAttributeEventListener(eventNames().canplaythroughEvent, createAttributeEventListener(this, attribute));
505 else if (attribute.name() == ondurationchangeAttr)
506 setAttributeEventListener(eventNames().durationchangeEvent, createAttributeEventListener(this, attribute));
507 else if (attribute.name() == onemptiedAttr)
508 setAttributeEventListener(eventNames().emptiedEvent, createAttributeEventListener(this, attribute));
509 else if (attribute.name() == onendedAttr)
510 setAttributeEventListener(eventNames().endedEvent, createAttributeEventListener(this, attribute));
511 else if (attribute.name() == onerrorAttr)
512 setAttributeEventListener(eventNames().errorEvent, createAttributeEventListener(this, attribute));
513 else if (attribute.name() == onloadeddataAttr)
514 setAttributeEventListener(eventNames().loadeddataEvent, createAttributeEventListener(this, attribute));
515 else if (attribute.name() == onloadedmetadataAttr)
516 setAttributeEventListener(eventNames().loadedmetadataEvent, createAttributeEventListener(this, attribute));
517 else if (attribute.name() == onloadstartAttr)
518 setAttributeEventListener(eventNames().loadstartEvent, createAttributeEventListener(this, attribute));
519 else if (attribute.name() == onpauseAttr)
520 setAttributeEventListener(eventNames().pauseEvent, createAttributeEventListener(this, attribute));
521 else if (attribute.name() == onplayAttr)
522 setAttributeEventListener(eventNames().playEvent, createAttributeEventListener(this, attribute));
523 else if (attribute.name() == onplayingAttr)
524 setAttributeEventListener(eventNames().playingEvent, createAttributeEventListener(this, attribute));
525 else if (attribute.name() == onprogressAttr)
526 setAttributeEventListener(eventNames().progressEvent, createAttributeEventListener(this, attribute));
527 else if (attribute.name() == onratechangeAttr)
528 setAttributeEventListener(eventNames().ratechangeEvent, createAttributeEventListener(this, attribute));
529 else if (attribute.name() == onseekedAttr)
530 setAttributeEventListener(eventNames().seekedEvent, createAttributeEventListener(this, attribute));
531 else if (attribute.name() == onseekingAttr)
532 setAttributeEventListener(eventNames().seekingEvent, createAttributeEventListener(this, attribute));
533 else if (attribute.name() == onstalledAttr)
534 setAttributeEventListener(eventNames().stalledEvent, createAttributeEventListener(this, attribute));
535 else if (attribute.name() == onsuspendAttr)
536 setAttributeEventListener(eventNames().suspendEvent, createAttributeEventListener(this, attribute));
537 else if (attribute.name() == ontimeupdateAttr)
538 setAttributeEventListener(eventNames().timeupdateEvent, createAttributeEventListener(this, attribute));
539 else if (attribute.name() == onvolumechangeAttr)
540 setAttributeEventListener(eventNames().volumechangeEvent, createAttributeEventListener(this, attribute));
541 else if (attribute.name() == onwaitingAttr)
542 setAttributeEventListener(eventNames().waitingEvent, createAttributeEventListener(this, attribute));
543 else if (attribute.name() == onwebkitbeginfullscreenAttr)
544 setAttributeEventListener(eventNames().webkitbeginfullscreenEvent, createAttributeEventListener(this, attribute));
545 else if (attribute.name() == onwebkitendfullscreenAttr)
546 setAttributeEventListener(eventNames().webkitendfullscreenEvent, createAttributeEventListener(this, attribute));
547 #if ENABLE(MEDIA_SOURCE)
548 else if (attribute.name() == onwebkitsourcecloseAttr)
549 setAttributeEventListener(eventNames().webkitsourcecloseEvent, createAttributeEventListener(this, attribute));
550 else if (attribute.name() == onwebkitsourceendedAttr)
551 setAttributeEventListener(eventNames().webkitsourceendedEvent, createAttributeEventListener(this, attribute));
552 else if (attribute.name() == onwebkitsourceopenAttr)
553 setAttributeEventListener(eventNames().webkitsourceopenEvent, createAttributeEventListener(this, attribute));
556 HTMLElement::parseAttribute(attribute);
559 void HTMLMediaElement::finishParsingChildren()
561 HTMLElement::finishParsingChildren();
562 m_parsingInProgress = false;
564 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
565 document()->updateStyleIfNeeded();
566 createMediaPlayerProxy();
569 #if ENABLE(VIDEO_TRACK)
570 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
573 for (Node* node = firstChild(); node; node = node->nextSibling()) {
574 if (node->hasTagName(trackTag)) {
575 scheduleLoad(TextTrackResource);
582 bool HTMLMediaElement::rendererIsNeeded(const NodeRenderingContext& context)
584 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
585 UNUSED_PARAM(context);
586 Frame* frame = document()->frame();
592 return controls() ? HTMLElement::rendererIsNeeded(context) : false;
596 RenderObject* HTMLMediaElement::createRenderer(RenderArena* arena, RenderStyle*)
598 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
599 // Setup the renderer if we already have a proxy widget.
600 RenderEmbeddedObject* mediaRenderer = new (arena) RenderEmbeddedObject(this);
602 mediaRenderer->setWidget(m_proxyWidget);
604 if (Frame* frame = document()->frame())
605 frame->loader()->client()->showMediaPlayerProxyPlugin(m_proxyWidget.get());
607 return mediaRenderer;
609 return new (arena) RenderMedia(this);
613 bool HTMLMediaElement::childShouldCreateRenderer(const NodeRenderingContext& childContext) const
615 if (!hasMediaControls())
617 // <media> doesn't allow its content, including shadow subtree, to
618 // be rendered. So this should return false for most of the children.
619 // One exception is a shadow tree built for rendering controls which should be visible.
620 // So we let them go here by comparing its subtree root with one of the controls.
621 return (mediaControls()->treeScope() == childContext.node()->treeScope()
622 && childContext.isOnUpperEncapsulationBoundary() && HTMLElement::childShouldCreateRenderer(childContext));
625 Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode* insertionPoint)
627 LOG(Media, "HTMLMediaElement::insertedInto");
628 HTMLElement::insertedInto(insertionPoint);
629 if (insertionPoint->inDocument() && !getAttribute(srcAttr).isEmpty() && m_networkState == NETWORK_EMPTY)
630 scheduleLoad(MediaResource);
631 configureMediaControls();
632 return InsertionDone;
635 void HTMLMediaElement::removedFrom(ContainerNode* insertionPoint)
637 if (insertionPoint->inDocument()) {
638 LOG(Media, "HTMLMediaElement::removedFromDocument");
639 configureMediaControls();
640 if (m_networkState > NETWORK_EMPTY)
646 HTMLElement::removedFrom(insertionPoint);
649 void HTMLMediaElement::attach()
653 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
654 m_needWidgetUpdate = true;
657 HTMLElement::attach();
660 renderer()->updateFromElement();
661 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
662 else if (m_proxyWidget) {
663 if (Frame* frame = document()->frame())
664 frame->loader()->client()->hideMediaPlayerProxyPlugin(m_proxyWidget.get());
669 void HTMLMediaElement::didRecalcStyle(StyleChange)
672 renderer()->updateFromElement();
675 void HTMLMediaElement::scheduleLoad(LoadType loadType)
677 LOG(Media, "HTMLMediaElement::scheduleLoad");
679 if ((loadType & MediaResource) && !(m_pendingLoadFlags & MediaResource)) {
680 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
681 createMediaPlayerProxy();
685 m_pendingLoadFlags |= MediaResource;
688 #if ENABLE(VIDEO_TRACK)
689 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled() && (loadType & TextTrackResource))
690 m_pendingLoadFlags |= TextTrackResource;
693 if (!m_loadTimer.isActive())
694 m_loadTimer.startOneShot(0);
697 void HTMLMediaElement::scheduleNextSourceChild()
699 // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
700 m_pendingLoadFlags |= MediaResource;
701 m_loadTimer.startOneShot(0);
704 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
707 LOG(Media, "HTMLMediaElement::scheduleEvent - scheduling '%s'", eventName.string().ascii().data());
709 RefPtr<Event> event = Event::create(eventName, false, true);
710 event->setTarget(this);
712 m_asyncEventQueue->enqueueEvent(event.release());
715 void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*)
717 RefPtr<HTMLMediaElement> protect(this); // loadNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations.
719 #if ENABLE(VIDEO_TRACK)
720 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled() && (m_pendingLoadFlags & TextTrackResource))
721 configureTextTracks();
724 if (m_pendingLoadFlags & MediaResource) {
725 if (m_loadState == LoadingFromSourceElement)
726 loadNextSourceChild();
731 m_pendingLoadFlags = 0;
734 PassRefPtr<MediaError> HTMLMediaElement::error() const
739 void HTMLMediaElement::setSrc(const String& url)
741 setAttribute(srcAttr, url);
744 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
746 return m_networkState;
749 String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySystem, const KURL& url) const
751 MediaPlayer::SupportsType support = MediaPlayer::supportsType(ContentType(mimeType), keySystem, url, this);
757 case MediaPlayer::IsNotSupported:
760 case MediaPlayer::MayBeSupported:
763 case MediaPlayer::IsSupported:
764 canPlay = "probably";
768 LOG(Media, "HTMLMediaElement::canPlayType(%s, %s, %s) -> %s", mimeType.utf8().data(), keySystem.utf8().data(), url.string().utf8().data(), canPlay.utf8().data());
773 void HTMLMediaElement::load()
775 RefPtr<HTMLMediaElement> protect(this); // loadInternal may result in a 'beforeload' event, which can make arbitrary DOM mutations.
777 LOG(Media, "HTMLMediaElement::load()");
779 if (userGestureRequiredForLoad() && !ScriptController::processingUserGesture())
782 m_loadInitiatedByUserGesture = ScriptController::processingUserGesture();
783 if (m_loadInitiatedByUserGesture)
784 removeBehaviorsRestrictionsAfterFirstUserGesture();
790 void HTMLMediaElement::prepareForLoad()
792 LOG(Media, "HTMLMediaElement::prepareForLoad");
794 // Perform the cleanup required for the resource load algorithm to run.
795 stopPeriodicTimers();
797 m_sentEndEvent = false;
798 m_sentStalledEvent = false;
799 m_haveFiredLoadedData = false;
800 m_completelyLoaded = false;
801 m_havePreparedToPlay = false;
802 m_displayMode = Unknown;
804 // 1 - Abort any already-running instance of the resource selection algorithm for this element.
805 m_loadState = WaitingForSource;
806 m_currentSourceNode = 0;
808 // 2 - If there are any tasks from the media element's media element event task source in
809 // one of the task queues, then remove those tasks.
810 cancelPendingEventsAndCallbacks();
812 // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
813 // a task to fire a simple event named abort at the media element.
814 if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
815 scheduleEvent(eventNames().abortEvent);
817 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
821 m_player->cancelLoad();
823 createMediaPlayerProxy();
826 #if ENABLE(MEDIA_SOURCE)
827 if (m_sourceState != SOURCE_CLOSED)
828 setSourceState(SOURCE_CLOSED);
831 // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
832 if (m_networkState != NETWORK_EMPTY) {
833 m_networkState = NETWORK_EMPTY;
834 m_readyState = HAVE_NOTHING;
835 m_readyStateMaximum = HAVE_NOTHING;
839 invalidateCachedTime();
840 scheduleEvent(eventNames().emptiedEvent);
841 updateMediaController();
842 #if ENABLE(VIDEO_TRACK)
843 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
844 updateActiveTextTrackCues(0);
848 // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
849 setPlaybackRate(defaultPlaybackRate());
851 // 6 - Set the error attribute to null and the autoplaying flag to true.
853 m_autoplaying = true;
855 // 7 - Invoke the media element's resource selection algorithm.
857 // 8 - Note: Playback of any previously playing media resource for this element stops.
859 // The resource selection algorithm
860 // 1 - Set the networkState to NETWORK_NO_SOURCE
861 m_networkState = NETWORK_NO_SOURCE;
863 // 2 - Asynchronously await a stable state.
865 m_playedTimeRanges = TimeRanges::create();
868 // The spec doesn't say to block the load event until we actually run the asynchronous section
869 // algorithm, but do it now because we won't start that until after the timer fires and the
870 // event may have already fired by then.
871 setShouldDelayLoadEvent(true);
873 configureMediaControls();
876 void HTMLMediaElement::loadInternal()
878 // Some of the code paths below this function dispatch the BeforeLoad event. This ASSERT helps
879 // us catch those bugs more quickly without needing all the branches to align to actually
880 // trigger the event.
881 ASSERT(!eventDispatchForbidden());
883 // If we can't start a load right away, start it later.
884 Page* page = document()->page();
885 if (pageConsentRequiredForLoad() && page && !page->canStartMedia()) {
886 setShouldDelayLoadEvent(false);
887 if (m_isWaitingUntilMediaCanStart)
889 document()->addMediaCanStartListener(this);
890 m_isWaitingUntilMediaCanStart = true;
894 // Once the page has allowed an element to load media, it is free to load at will. This allows a
895 // playlist that starts in a foreground tab to continue automatically if the tab is subsequently
896 // put in the the background.
897 removeBehaviorRestriction(RequirePageConsentToLoadMediaRestriction);
899 #if ENABLE(VIDEO_TRACK)
900 // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the
901 // disabled state when the element's resource selection algorithm last started".
902 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) {
903 m_textTracksWhenResourceSelectionBegan.clear();
905 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
906 TextTrack* track = m_textTracks->item(i);
907 if (track->mode() != TextTrack::disabledKeyword())
908 m_textTracksWhenResourceSelectionBegan.append(track);
914 selectMediaResource();
917 void HTMLMediaElement::selectMediaResource()
919 LOG(Media, "HTMLMediaElement::selectMediaResource");
921 enum Mode { attribute, children };
923 // 3 - If the media element has a src attribute, then let mode be attribute.
924 Mode mode = attribute;
925 if (!fastHasAttribute(srcAttr)) {
927 for (node = firstChild(); node; node = node->nextSibling()) {
928 if (node->hasTagName(sourceTag))
932 // Otherwise, if the media element does not have a src attribute but has a source
933 // element child, then let mode be children and let candidate be the first such
934 // source element child in tree order.
937 m_nextChildNodeToConsider = node;
938 m_currentSourceNode = 0;
940 // Otherwise the media element has neither a src attribute nor a source element
941 // child: set the networkState to NETWORK_EMPTY, and abort these steps; the
942 // synchronous section ends.
943 m_loadState = WaitingForSource;
944 setShouldDelayLoadEvent(false);
945 m_networkState = NETWORK_EMPTY;
947 LOG(Media, "HTMLMediaElement::selectMediaResource, nothing to load");
952 // 4 - Set the media element's delaying-the-load-event flag to true (this delays the load event),
953 // and set its networkState to NETWORK_LOADING.
954 setShouldDelayLoadEvent(true);
955 m_networkState = NETWORK_LOADING;
957 // 5 - Queue a task to fire a simple event named loadstart at the media element.
958 scheduleEvent(eventNames().loadstartEvent);
960 // 6 - If mode is attribute, then run these substeps
961 if (mode == attribute) {
962 m_loadState = LoadingFromSrcAttr;
964 // If the src attribute's value is the empty string ... jump down to the failed step below
965 KURL mediaURL = getNonEmptyURLAttribute(srcAttr);
966 if (mediaURL.isEmpty()) {
967 mediaLoadingFailed(MediaPlayer::FormatError);
968 LOG(Media, "HTMLMediaElement::selectMediaResource, empty 'src'");
972 if (!isSafeToLoadURL(mediaURL, Complain) || !dispatchBeforeLoadEvent(mediaURL.string())) {
973 mediaLoadingFailed(MediaPlayer::FormatError);
977 // No type or key system information is available when the url comes
978 // from the 'src' attribute so MediaPlayer
979 // will have to pick a media engine based on the file extension.
980 ContentType contentType((String()));
981 loadResource(mediaURL, contentType, String());
982 LOG(Media, "HTMLMediaElement::selectMediaResource, using 'src' attribute url");
986 // Otherwise, the source elements will be used
987 loadNextSourceChild();
990 void HTMLMediaElement::loadNextSourceChild()
992 ContentType contentType((String()));
994 KURL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain);
995 if (!mediaURL.isValid()) {
996 waitForSourceChange();
1000 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
1001 // Recreate the media player for the new url
1002 createMediaPlayer();
1005 m_loadState = LoadingFromSourceElement;
1006 loadResource(mediaURL, contentType, keySystem);
1009 #if !PLATFORM(CHROMIUM)
1010 static KURL createFileURLForApplicationCacheResource(const String& path)
1012 // KURL should have a function to create a url from a path, but it does not. This function
1013 // is not suitable because KURL::setPath uses encodeWithURLEscapeSequences, which it notes
1014 // does not correctly escape '#' and '?'. This function works for our purposes because
1015 // app cache media files are always created with encodeForFileName(createCanonicalUUIDString()).
1017 #if USE(CF) && PLATFORM(WIN)
1018 RetainPtr<CFStringRef> cfPath(AdoptCF, path.createCFString());
1019 RetainPtr<CFURLRef> cfURL(AdoptCF, CFURLCreateWithFileSystemPath(0, cfPath.get(), kCFURLWindowsPathStyle, false));
1020 KURL url(cfURL.get());
1024 url.setProtocol("file");
1031 void HTMLMediaElement::loadResource(const KURL& initialURL, ContentType& contentType, const String& keySystem)
1033 ASSERT(isSafeToLoadURL(initialURL, Complain));
1035 LOG(Media, "HTMLMediaElement::loadResource(%s, %s, %s)", urlForLogging(initialURL).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data());
1037 Frame* frame = document()->frame();
1039 mediaLoadingFailed(MediaPlayer::FormatError);
1043 KURL url = initialURL;
1044 if (!frame->loader()->willLoadMediaElementURL(url)) {
1045 mediaLoadingFailed(MediaPlayer::FormatError);
1049 #if ENABLE(MEDIA_SOURCE)
1050 // If this is a media source URL, make sure it is the one for this media element.
1051 if (url.protocolIs(mediaSourceURLProtocol) && url != m_mediaSourceURL) {
1052 mediaLoadingFailed(MediaPlayer::FormatError);
1057 // The resource fetch algorithm
1058 m_networkState = NETWORK_LOADING;
1060 #if !PLATFORM(CHROMIUM)
1061 // If the url should be loaded from the application cache, pass the url of the cached file
1062 // to the media engine.
1063 ApplicationCacheHost* cacheHost = frame->loader()->documentLoader()->applicationCacheHost();
1064 ApplicationCacheResource* resource = 0;
1065 if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource)) {
1066 // Resources that are not present in the manifest will always fail to load (at least, after the
1067 // cache has been primed the first time), making the testing of offline applications simpler.
1068 if (!resource || resource->path().isEmpty()) {
1069 mediaLoadingFailed(MediaPlayer::NetworkError);
1075 // Set m_currentSrc *before* changing to the cache url, the fact that we are loading from the app
1076 // cache is an internal detail not exposed through the media element API.
1079 #if !PLATFORM(CHROMIUM)
1081 url = createFileURLForApplicationCacheResource(resource->path());
1082 LOG(Media, "HTMLMediaElement::loadResource - will load from app cache -> %s", urlForLogging(url).utf8().data());
1086 LOG(Media, "HTMLMediaElement::loadResource - m_currentSrc -> %s", urlForLogging(m_currentSrc).utf8().data());
1088 #if ENABLE(MEDIA_STREAM)
1089 if (MediaStreamRegistry::registry().lookupMediaStreamDescriptor(url.string()))
1090 removeBehaviorRestriction(RequireUserGestureForRateChangeRestriction);
1093 if (m_sendProgressEvents)
1094 startProgressEventTimer();
1096 Settings* settings = document()->settings();
1097 bool privateMode = !settings || settings->privateBrowsingEnabled();
1098 m_player->setPrivateBrowsingMode(privateMode);
1100 // Reset display mode to force a recalculation of what to show because we are resetting the player.
1101 setDisplayMode(Unknown);
1104 m_player->setPreload(m_preload);
1105 m_player->setPreservesPitch(m_webkitPreservesPitch);
1107 #if !ENABLE(TIZEN_MEDIA_STREAM)
1108 if (fastHasAttribute(mutedAttr))
1113 if (!m_player->load(url, contentType, keySystem))
1114 mediaLoadingFailed(MediaPlayer::FormatError);
1116 #if ENABLE(TIZEN_MEDIA_STREAM)
1120 // If there is no poster to display, allow the media engine to render video frames as soon as
1121 // they are available.
1122 updateDisplayState();
1125 renderer()->updateFromElement();
1128 #if ENABLE(VIDEO_TRACK)
1129 static bool trackIndexCompare(TextTrack* a,
1132 return a->trackIndex() - b->trackIndex() < 0;
1135 static bool eventTimeCueCompare(const std::pair<double, TextTrackCue*>& a,
1136 const std::pair<double, TextTrackCue*>& b)
1138 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1140 if (a.first != b.first)
1141 return a.first - b.first < 0;
1143 // If the cues belong to different text tracks, it doesn't make sense to
1144 // compare the two tracks by the relative cue order, so return the relative
1146 if (a.second->track() != b.second->track())
1147 return trackIndexCompare(a.second->track(), b.second->track());
1149 // 12 - Further sort tasks in events that have the same time by the
1150 // relative text track cue order of the text track cues associated
1151 // with these tasks.
1152 return a.second->cueIndex() - b.second->cueIndex() < 0;
1156 void HTMLMediaElement::updateActiveTextTrackCues(float movieTime)
1158 LOG(Media, "HTMLMediaElement::updateActiveTextTracks");
1160 // 4.8.10.8 Playing the media resource
1162 // If the current playback position changes while the steps are running,
1163 // then the user agent must wait for the steps to complete, and then must
1164 // immediately rerun the steps.
1165 if (ignoreTrackDisplayUpdateRequests())
1168 // 1 - Let current cues be a list of cues, initialized to contain all the
1169 // cues of all the hidden, showing, or showing by default text tracks of the
1170 // media element (not the disabled ones) whose start times are less than or
1171 // equal to the current playback position and whose end times are greater
1172 // than the current playback position.
1173 Vector<CueIntervalTree::IntervalType> currentCues;
1175 // The user agent must synchronously unset [the text track cue active] flag
1176 // whenever ... the media element's readyState is changed back to HAVE_NOTHING.
1177 if (m_readyState != HAVE_NOTHING && m_player)
1178 currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime));
1180 Vector<CueIntervalTree::IntervalType> affectedCues;
1181 Vector<CueIntervalTree::IntervalType> previousCues;
1182 Vector<CueIntervalTree::IntervalType> missedCues;
1184 // 2 - Let other cues be a list of cues, initialized to contain all the cues
1185 // of hidden, showing, and showing by default text tracks of the media
1186 // element that are not present in current cues.
1187 previousCues = m_currentlyActiveCues;
1189 // 3 - Let last time be the current playback position at the time this
1190 // algorithm was last run for this media element, if this is not the first
1192 float lastTime = m_lastTextTrackUpdateTime;
1194 // 4 - If the current playback position has, since the last time this
1195 // algorithm was run, only changed through its usual monotonic increase
1196 // during normal playback, then let missed cues be the list of cues in other
1197 // cues whose start times are greater than or equal to last time and whose
1198 // end times are less than or equal to the current playback position.
1199 // Otherwise, let missed cues be an empty list.
1200 if (lastTime >= 0 && m_lastSeekTime < movieTime) {
1201 Vector<CueIntervalTree::IntervalType> potentiallySkippedCues =
1202 m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime));
1204 for (size_t i = 0; i < potentiallySkippedCues.size(); ++i) {
1205 float cueStartTime = potentiallySkippedCues[i].low();
1206 float cueEndTime = potentiallySkippedCues[i].high();
1208 // Consider cues that may have been missed since the last seek time.
1209 if (cueStartTime > max(m_lastSeekTime, lastTime) && cueEndTime < movieTime)
1210 missedCues.append(potentiallySkippedCues[i]);
1214 m_lastTextTrackUpdateTime = movieTime;
1216 // 5 - If the time was reached through the usual monotonic increase of the
1217 // current playback position during normal playback, and if the user agent
1218 // has not fired a timeupdate event at the element in the past 15 to 250ms
1219 // and is not still running event handlers for such an event, then the user
1220 // agent must queue a task to fire a simple event named timeupdate at the
1221 // element. (In the other cases, such as explicit seeks, relevant events get
1222 // fired as part of the overall process of changing the current playback
1224 if (m_lastSeekTime <= lastTime)
1225 scheduleTimeupdateEvent(false);
1227 // Explicitly cache vector sizes, as their content is constant from here.
1228 size_t currentCuesSize = currentCues.size();
1229 size_t missedCuesSize = missedCues.size();
1230 size_t previousCuesSize = previousCues.size();
1232 // 6 - If all of the cues in current cues have their text track cue active
1233 // flag set, none of the cues in other cues have their text track cue active
1234 // flag set, and missed cues is empty, then abort these steps.
1235 bool activeSetChanged = missedCuesSize;
1237 for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i)
1238 if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive())
1239 activeSetChanged = true;
1241 for (size_t i = 0; i < currentCuesSize; ++i) {
1242 currentCues[i].data()->updateDisplayTree(movieTime);
1244 if (!currentCues[i].data()->isActive())
1245 activeSetChanged = true;
1248 if (!activeSetChanged) {
1249 // Even though the active set has not changed, it is possible that the
1250 // the mode of a track has changed from 'hidden' to 'showing' and the
1251 // cues have not yet been rendered.
1252 if (hasMediaControls())
1253 mediaControls()->updateTextTrackDisplay();
1258 // 7 - If the time was reached through the usual monotonic increase of the
1259 // current playback position during normal playback, and there are cues in
1260 // other cues that have their text track cue pause-on-exi flag set and that
1261 // either have their text track cue active flag set or are also in missed
1262 // cues, then immediately pause the media element.
1263 for (size_t i = 0; !m_paused && i < previousCuesSize; ++i) {
1264 if (previousCues[i].data()->pauseOnExit()
1265 && previousCues[i].data()->isActive()
1266 && !currentCues.contains(previousCues[i]))
1270 for (size_t i = 0; !m_paused && i < missedCuesSize; ++i) {
1271 if (missedCues[i].data()->pauseOnExit())
1275 // 8 - Let events be a list of tasks, initially empty. Each task in this
1276 // list will be associated with a text track, a text track cue, and a time,
1277 // which are used to sort the list before the tasks are queued.
1278 Vector<std::pair<double, TextTrackCue*> > eventTasks;
1280 // 8 - Let affected tracks be a list of text tracks, initially empty.
1281 Vector<TextTrack*> affectedTracks;
1283 for (size_t i = 0; i < missedCuesSize; ++i) {
1284 // 9 - For each text track cue in missed cues, prepare an event named enter
1285 // for the TextTrackCue object with the text track cue start time.
1286 eventTasks.append(std::make_pair(missedCues[i].data()->startTime(),
1287 missedCues[i].data()));
1289 // 10 - For each text track [...] in missed cues, prepare an event
1290 // named exit for the TextTrackCue object with the with the later of
1291 // the text track cue end time and the text track cue start time.
1293 // Note: An explicit task is added only if the cue is NOT a zero or
1294 // negative length cue. Otherwise, the need for an exit event is
1295 // checked when these tasks are actually queued below. This doesn't
1296 // affect sorting events before dispatch either, because the exit
1297 // event has the same time as the enter event.
1298 if (missedCues[i].data()->startTime() < missedCues[i].data()->endTime())
1299 eventTasks.append(std::make_pair(missedCues[i].data()->endTime(),
1300 missedCues[i].data()));
1303 for (size_t i = 0; i < previousCuesSize; ++i) {
1304 // 10 - For each text track cue in other cues that has its text
1305 // track cue active flag set prepare an event named exit for the
1306 // TextTrackCue object with the text track cue end time.
1307 if (!currentCues.contains(previousCues[i]))
1308 eventTasks.append(std::make_pair(previousCues[i].data()->endTime(),
1309 previousCues[i].data()));
1312 for (size_t i = 0; i < currentCuesSize; ++i) {
1313 // 11 - For each text track cue in current cues that does not have its
1314 // text track cue active flag set, prepare an event named enter for the
1315 // TextTrackCue object with the text track cue start time.
1316 if (!previousCues.contains(currentCues[i]))
1317 eventTasks.append(std::make_pair(currentCues[i].data()->startTime(),
1318 currentCues[i].data()));
1321 // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1323 nonCopyingSort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare);
1325 for (size_t i = 0; i < eventTasks.size(); ++i) {
1326 if (!affectedTracks.contains(eventTasks[i].second->track()))
1327 affectedTracks.append(eventTasks[i].second->track());
1329 // 13 - Queue each task in events, in list order.
1330 RefPtr<Event> event;
1332 // Each event in eventTasks may be either an enterEvent or an exitEvent,
1333 // depending on the time that is associated with the event. This
1334 // correctly identifies the type of the event, if the startTime is
1335 // less than the endTime in the cue.
1336 if (eventTasks[i].second->startTime() >= eventTasks[i].second->endTime()) {
1337 event = Event::create(eventNames().enterEvent, false, false);
1338 event->setTarget(eventTasks[i].second);
1339 m_asyncEventQueue->enqueueEvent(event.release());
1341 event = Event::create(eventNames().exitEvent, false, false);
1342 event->setTarget(eventTasks[i].second);
1343 m_asyncEventQueue->enqueueEvent(event.release());
1345 if (eventTasks[i].first == eventTasks[i].second->startTime())
1346 event = Event::create(eventNames().enterEvent, false, false);
1348 event = Event::create(eventNames().exitEvent, false, false);
1350 event->setTarget(eventTasks[i].second);
1351 m_asyncEventQueue->enqueueEvent(event.release());
1355 // 14 - Sort affected tracks in the same order as the text tracks appear in
1356 // the media element's list of text tracks, and remove duplicates.
1357 nonCopyingSort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare);
1359 // 15 - For each text track in affected tracks, in the list order, queue a
1360 // task to fire a simple event named cuechange at the TextTrack object, and, ...
1361 for (size_t i = 0; i < affectedTracks.size(); ++i) {
1362 RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false);
1363 event->setTarget(affectedTracks[i]);
1365 m_asyncEventQueue->enqueueEvent(event.release());
1367 // ... if the text track has a corresponding track element, to then fire a
1368 // simple event named cuechange at the track element as well.
1369 if (affectedTracks[i]->trackType() == TextTrack::TrackElement) {
1370 RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false);
1371 HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(affectedTracks[i])->trackElement();
1372 ASSERT(trackElement);
1373 event->setTarget(trackElement);
1375 m_asyncEventQueue->enqueueEvent(event.release());
1379 // 16 - Set the text track cue active flag of all the cues in the current
1380 // cues, and unset the text track cue active flag of all the cues in the
1382 for (size_t i = 0; i < currentCuesSize; ++i)
1383 currentCues[i].data()->setIsActive(true);
1385 for (size_t i = 0; i < previousCuesSize; ++i)
1386 if (!currentCues.contains(previousCues[i]))
1387 previousCues[i].data()->setIsActive(false);
1389 // Update the current active cues.
1390 m_currentlyActiveCues = currentCues;
1392 if (activeSetChanged && hasMediaControls())
1393 mediaControls()->updateTextTrackDisplay();
1396 bool HTMLMediaElement::textTracksAreReady() const
1398 // 4.8.10.12.1 Text track model
1400 // The text tracks of a media element are ready if all the text tracks whose mode was not
1401 // in the disabled state when the element's resource selection algorithm last started now
1402 // have a text track readiness state of loaded or failed to load.
1403 for (unsigned i = 0; i < m_textTracksWhenResourceSelectionBegan.size(); ++i) {
1404 if (m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::Loading)
1411 void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track)
1413 if (m_player && m_textTracksWhenResourceSelectionBegan.contains(track)) {
1414 if (track->readinessState() != TextTrack::Loading)
1415 setReadyState(m_player->readyState());
1419 void HTMLMediaElement::textTrackModeChanged(TextTrack* track)
1421 if (track->trackType() == TextTrack::TrackElement) {
1422 // 4.8.10.12.3 Sourcing out-of-band text tracks
1423 // ... when a text track corresponding to a track element is created with text track
1424 // mode set to disabled and subsequently changes its text track mode to hidden, showing,
1425 // or showing by default for the first time, the user agent must immediately and synchronously
1426 // run the following algorithm ...
1428 for (Node* node = firstChild(); node; node = node->nextSibling()) {
1429 if (!node->hasTagName(trackTag))
1431 HTMLTrackElement* trackElement = static_cast<HTMLTrackElement*>(node);
1432 if (trackElement->track() != track)
1435 // Mark this track as "configured" so configureTextTracks won't change the mode again.
1436 trackElement->setHasBeenConfigured(true);
1437 if (track->mode() != TextTrack::disabledKeyword()) {
1438 if (trackElement->readyState() == HTMLTrackElement::LOADED)
1439 textTrackAddCues(track, track->cues());
1440 else if (trackElement->readyState() == HTMLTrackElement::NONE)
1441 trackElement->scheduleLoad();
1443 // If this is the first added track, create the list of text tracks.
1445 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
1451 configureTextTrackDisplay();
1452 updateActiveTextTrackCues(currentTime());
1455 void HTMLMediaElement::textTrackKindChanged(TextTrack* track)
1457 if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword() && track->mode() == TextTrack::showingKeyword())
1458 track->setMode(TextTrack::hiddenKeyword());
1461 void HTMLMediaElement::textTrackAddCues(TextTrack*, const TextTrackCueList* cues)
1463 beginIgnoringTrackDisplayUpdateRequests();
1464 for (size_t i = 0; i < cues->length(); ++i)
1465 textTrackAddCue(cues->item(i)->track(), cues->item(i));
1466 endIgnoringTrackDisplayUpdateRequests();
1467 updateActiveTextTrackCues(currentTime());
1470 void HTMLMediaElement::textTrackRemoveCues(TextTrack*, const TextTrackCueList* cues)
1472 beginIgnoringTrackDisplayUpdateRequests();
1473 for (size_t i = 0; i < cues->length(); ++i)
1474 textTrackRemoveCue(cues->item(i)->track(), cues->item(i));
1475 endIgnoringTrackDisplayUpdateRequests();
1476 updateActiveTextTrackCues(currentTime());
1479 void HTMLMediaElement::textTrackAddCue(TextTrack*, PassRefPtr<TextTrackCue> cue)
1481 // Negative duration cues need be treated in the interval tree as
1482 // zero-length cues.
1483 double endTime = max(cue->startTime(), cue->endTime());
1485 m_cueTree.add(m_cueTree.createInterval(cue->startTime(), endTime, cue.get()));
1486 updateActiveTextTrackCues(currentTime());
1489 void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtr<TextTrackCue> cue)
1491 // Negative duration cues need to be treated in the interval tree as
1492 // zero-length cues.
1493 double endTime = max(cue->startTime(), cue->endTime());
1495 m_cueTree.remove(m_cueTree.createInterval(cue->startTime(), endTime, cue.get()));
1496 updateActiveTextTrackCues(currentTime());
1501 bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidURLAction actionIfInvalid)
1503 if (!url.isValid()) {
1504 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE because url is invalid", urlForLogging(url).utf8().data());
1508 Frame* frame = document()->frame();
1509 if (!frame || !document()->securityOrigin()->canDisplay(url)) {
1510 if (actionIfInvalid == Complain)
1511 FrameLoader::reportLocalLoadFailed(frame, url.string());
1512 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE rejected by SecurityOrigin", urlForLogging(url).utf8().data());
1516 if (!document()->contentSecurityPolicy()->allowMediaFromSource(url)) {
1517 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> rejected by Content Security Policy", urlForLogging(url).utf8().data());
1524 void HTMLMediaElement::startProgressEventTimer()
1526 if (m_progressEventTimer.isActive())
1529 m_previousProgressTime = WTF::currentTime();
1530 // 350ms is not magic, it is in the spec!
1531 m_progressEventTimer.startRepeating(0.350);
1534 void HTMLMediaElement::waitForSourceChange()
1536 LOG(Media, "HTMLMediaElement::waitForSourceChange");
1538 stopPeriodicTimers();
1539 m_loadState = WaitingForSource;
1541 // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value
1542 m_networkState = NETWORK_NO_SOURCE;
1544 // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1545 setShouldDelayLoadEvent(false);
1547 updateDisplayState();
1550 renderer()->updateFromElement();
1553 void HTMLMediaElement::noneSupported()
1555 LOG(Media, "HTMLMediaElement::noneSupported");
1557 stopPeriodicTimers();
1558 m_loadState = WaitingForSource;
1559 m_currentSourceNode = 0;
1562 // 6 - Reaching this step indicates that the media resource failed to load or that the given
1563 // URL could not be resolved. In one atomic operation, run the following steps:
1565 // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to
1566 // MEDIA_ERR_SRC_NOT_SUPPORTED.
1567 m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
1569 // 6.2 - Forget the media element's media-resource-specific text tracks.
1571 // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
1572 m_networkState = NETWORK_NO_SOURCE;
1574 // 7 - Queue a task to fire a simple event named error at the media element.
1575 scheduleEvent(eventNames().errorEvent);
1577 #if ENABLE(MEDIA_SOURCE)
1578 if (m_sourceState != SOURCE_CLOSED)
1579 setSourceState(SOURCE_CLOSED);
1582 // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1583 setShouldDelayLoadEvent(false);
1585 // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed,
1586 // the element won't attempt to load another resource.
1588 updateDisplayState();
1591 renderer()->updateFromElement();
1594 void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err)
1596 LOG(Media, "HTMLMediaElement::mediaEngineError(%d)", static_cast<int>(err->code()));
1598 // 1 - The user agent should cancel the fetching process.
1599 stopPeriodicTimers();
1600 m_loadState = WaitingForSource;
1602 // 2 - Set the error attribute to a new MediaError object whose code attribute is
1603 // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
1606 // 3 - Queue a task to fire a simple event named error at the media element.
1607 scheduleEvent(eventNames().errorEvent);
1609 #if ENABLE(MEDIA_SOURCE)
1610 if (m_sourceState != SOURCE_CLOSED)
1611 setSourceState(SOURCE_CLOSED);
1614 // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a
1615 // task to fire a simple event called emptied at the element.
1616 m_networkState = NETWORK_EMPTY;
1617 scheduleEvent(eventNames().emptiedEvent);
1619 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1620 setShouldDelayLoadEvent(false);
1622 // 6 - Abort the overall resource selection algorithm.
1623 m_currentSourceNode = 0;
1626 void HTMLMediaElement::cancelPendingEventsAndCallbacks()
1628 LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks");
1629 m_asyncEventQueue->cancelAllEvents();
1631 for (Node* node = firstChild(); node; node = node->nextSibling()) {
1632 if (node->hasTagName(sourceTag))
1633 static_cast<HTMLSourceElement*>(node)->cancelPendingErrorEvent();
1637 Document* HTMLMediaElement::mediaPlayerOwningDocument()
1639 Document* d = document();
1642 d = ownerDocument();
1647 void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*)
1649 beginProcessingMediaPlayerCallback();
1650 setNetworkState(m_player->networkState());
1651 endProcessingMediaPlayerCallback();
1654 static String stringForNetworkState(MediaPlayer::NetworkState state)
1657 case MediaPlayer::Empty: return "Empty";
1658 case MediaPlayer::Idle: return "Idle";
1659 case MediaPlayer::Loading: return "Loading";
1660 case MediaPlayer::Loaded: return "Loaded";
1661 case MediaPlayer::FormatError: return "FormatError";
1662 case MediaPlayer::NetworkError: return "NetworkError";
1663 case MediaPlayer::DecodeError: return "DecodeError";
1664 default: return emptyString();
1668 void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error)
1670 stopPeriodicTimers();
1672 // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
1673 // <source> children, schedule the next one
1674 if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
1676 if (m_currentSourceNode)
1677 m_currentSourceNode->scheduleErrorEvent();
1679 LOG(Media, "HTMLMediaElement::setNetworkState - error event not sent, <source> was removed");
1681 if (havePotentialSourceChild()) {
1682 LOG(Media, "HTMLMediaElement::setNetworkState - scheduling next <source>");
1683 scheduleNextSourceChild();
1685 LOG(Media, "HTMLMediaElement::setNetworkState - no more <source> elements, waiting");
1686 waitForSourceChange();
1692 if (error == MediaPlayer::NetworkError && m_readyState >= HAVE_METADATA)
1693 mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK));
1694 else if (error == MediaPlayer::DecodeError)
1695 mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE));
1696 else if ((error == MediaPlayer::FormatError || error == MediaPlayer::NetworkError) && m_loadState == LoadingFromSrcAttr)
1699 updateDisplayState();
1700 if (hasMediaControls()) {
1701 mediaControls()->reset();
1702 mediaControls()->reportedError();
1705 if (document()->page() && document()->page()->settings()->diagnosticLoggingEnabled())
1706 document()->page()->chrome()->client()->logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadingFailedKey(), stringForNetworkState(error), DiagnosticLoggingKeys::failKey());
1709 void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
1711 LOG(Media, "HTMLMediaElement::setNetworkState(%d) - current state is %d", static_cast<int>(state), static_cast<int>(m_networkState));
1713 if (state == MediaPlayer::Empty) {
1714 // Just update the cached state and leave, we can't do anything.
1715 m_networkState = NETWORK_EMPTY;
1719 if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) {
1720 mediaLoadingFailed(state);
1724 if (state == MediaPlayer::Idle) {
1725 if (m_networkState > NETWORK_IDLE) {
1726 changeNetworkStateFromLoadingToIdle();
1727 setShouldDelayLoadEvent(false);
1729 m_networkState = NETWORK_IDLE;
1733 if (state == MediaPlayer::Loading) {
1734 if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE)
1735 startProgressEventTimer();
1736 m_networkState = NETWORK_LOADING;
1739 if (state == MediaPlayer::Loaded) {
1740 if (m_networkState != NETWORK_IDLE)
1741 changeNetworkStateFromLoadingToIdle();
1742 m_completelyLoaded = true;
1745 if (hasMediaControls())
1746 mediaControls()->updateStatusDisplay();
1749 void HTMLMediaElement::changeNetworkStateFromLoadingToIdle()
1751 m_progressEventTimer.stop();
1752 if (hasMediaControls() && m_player->didLoadingProgress())
1753 mediaControls()->bufferingProgressed();
1755 // Schedule one last progress event so we guarantee that at least one is fired
1756 // for files that load very quickly.
1757 scheduleEvent(eventNames().progressEvent);
1758 scheduleEvent(eventNames().suspendEvent);
1759 m_networkState = NETWORK_IDLE;
1762 void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*)
1764 beginProcessingMediaPlayerCallback();
1766 setReadyState(m_player->readyState());
1768 endProcessingMediaPlayerCallback();
1771 void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
1773 LOG(Media, "HTMLMediaElement::setReadyState(%d) - current state is %d,", static_cast<int>(state), static_cast<int>(m_readyState));
1775 // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it
1776 bool wasPotentiallyPlaying = potentiallyPlaying();
1778 ReadyState oldState = m_readyState;
1779 ReadyState newState = static_cast<ReadyState>(state);
1781 #if ENABLE(VIDEO_TRACK)
1782 bool tracksAreReady = !RuntimeEnabledFeatures::webkitVideoTrackEnabled() || textTracksAreReady();
1784 if (newState == oldState && m_tracksAreReady == tracksAreReady)
1787 m_tracksAreReady = tracksAreReady;
1789 if (newState == oldState)
1791 bool tracksAreReady = true;
1795 m_readyState = newState;
1797 // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until
1798 // the text tracks are ready, regardless of the state of the media file.
1799 if (newState <= HAVE_METADATA)
1800 m_readyState = newState;
1802 m_readyState = HAVE_CURRENT_DATA;
1805 if (oldState > m_readyStateMaximum)
1806 m_readyStateMaximum = oldState;
1808 if (m_networkState == NETWORK_EMPTY)
1812 // 4.8.10.9, step 11
1813 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA)
1814 scheduleEvent(eventNames().waitingEvent);
1816 // 4.8.10.10 step 14 & 15.
1817 if (m_readyState >= HAVE_CURRENT_DATA)
1820 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
1822 scheduleTimeupdateEvent(false);
1823 scheduleEvent(eventNames().waitingEvent);
1827 if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
1828 prepareMediaFragmentURI();
1829 scheduleEvent(eventNames().durationchangeEvent);
1830 scheduleEvent(eventNames().loadedmetadataEvent);
1831 if (hasMediaControls())
1832 mediaControls()->loadedMetadata();
1834 renderer()->updateFromElement();
1836 if (document()->page() && document()->page()->settings()->diagnosticLoggingEnabled())
1837 document()->page()->chrome()->client()->logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadedKey(), m_player->engineDescription(), DiagnosticLoggingKeys::noopKey());
1840 bool shouldUpdateDisplayState = false;
1842 if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) {
1843 m_haveFiredLoadedData = true;
1844 shouldUpdateDisplayState = true;
1845 scheduleEvent(eventNames().loadeddataEvent);
1846 setShouldDelayLoadEvent(false);
1847 applyMediaFragmentURI();
1850 bool isPotentiallyPlaying = potentiallyPlaying();
1851 if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
1852 scheduleEvent(eventNames().canplayEvent);
1853 if (isPotentiallyPlaying)
1854 scheduleEvent(eventNames().playingEvent);
1855 shouldUpdateDisplayState = true;
1858 if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA && tracksAreReady) {
1859 if (oldState <= HAVE_CURRENT_DATA)
1860 scheduleEvent(eventNames().canplayEvent);
1862 scheduleEvent(eventNames().canplaythroughEvent);
1864 if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA)
1865 scheduleEvent(eventNames().playingEvent);
1867 if (m_autoplaying && m_paused && autoplay() && !document()->isSandboxed(SandboxAutomaticFeatures) && !userGestureRequiredForRateChange()) {
1869 invalidateCachedTime();
1870 scheduleEvent(eventNames().playEvent);
1871 scheduleEvent(eventNames().playingEvent);
1874 shouldUpdateDisplayState = true;
1877 if (shouldUpdateDisplayState) {
1878 updateDisplayState();
1879 if (hasMediaControls())
1880 mediaControls()->updateStatusDisplay();
1884 updateMediaController();
1885 #if ENABLE(VIDEO_TRACK)
1886 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
1887 updateActiveTextTrackCues(currentTime());
1891 #if ENABLE(MEDIA_SOURCE)
1892 void HTMLMediaElement::mediaPlayerSourceOpened()
1894 beginProcessingMediaPlayerCallback();
1896 setSourceState(SOURCE_OPEN);
1898 endProcessingMediaPlayerCallback();
1901 String HTMLMediaElement::mediaPlayerSourceURL() const
1903 return m_mediaSourceURL.string();
1906 bool HTMLMediaElement::isValidSourceId(const String& id, ExceptionCode& ec) const
1908 if (id.isNull() || id.isEmpty()) {
1909 ec = INVALID_ACCESS_ERR;
1913 if (!m_sourceIDs.contains(id)) {
1923 #if ENABLE(ENCRYPTED_MEDIA)
1924 void HTMLMediaElement::mediaPlayerKeyAdded(MediaPlayer*, const String& keySystem, const String& sessionId)
1926 MediaKeyEventInit initializer;
1927 initializer.keySystem = keySystem;
1928 initializer.sessionId = sessionId;
1929 initializer.bubbles = false;
1930 initializer.cancelable = false;
1932 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeyaddedEvent, initializer);
1933 event->setTarget(this);
1934 m_asyncEventQueue->enqueueEvent(event.release());
1937 void HTMLMediaElement::mediaPlayerKeyError(MediaPlayer*, const String& keySystem, const String& sessionId, MediaPlayerClient::MediaKeyErrorCode errorCode, unsigned short systemCode)
1939 MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
1940 switch (errorCode) {
1941 case MediaPlayerClient::UnknownError:
1942 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
1944 case MediaPlayerClient::ClientError:
1945 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT;
1947 case MediaPlayerClient::ServiceError:
1948 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_SERVICE;
1950 case MediaPlayerClient::OutputError:
1951 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_OUTPUT;
1953 case MediaPlayerClient::HardwareChangeError:
1954 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_HARDWARECHANGE;
1956 case MediaPlayerClient::DomainError:
1957 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_DOMAIN;
1961 MediaKeyEventInit initializer;
1962 initializer.keySystem = keySystem;
1963 initializer.sessionId = sessionId;
1964 initializer.errorCode = MediaKeyError::create(mediaKeyErrorCode);
1965 initializer.systemCode = systemCode;
1966 initializer.bubbles = false;
1967 initializer.cancelable = false;
1969 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeyerrorEvent, initializer);
1970 event->setTarget(this);
1971 m_asyncEventQueue->enqueueEvent(event.release());
1974 void HTMLMediaElement::mediaPlayerKeyMessage(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* message, unsigned messageLength)
1976 MediaKeyEventInit initializer;
1977 initializer.keySystem = keySystem;
1978 initializer.sessionId = sessionId;
1979 initializer.message = Uint8Array::create(message, messageLength);
1980 initializer.bubbles = false;
1981 initializer.cancelable = false;
1983 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeymessageEvent, initializer);
1984 event->setTarget(this);
1985 m_asyncEventQueue->enqueueEvent(event.release());
1988 void HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* initData, unsigned initDataLength)
1990 MediaKeyEventInit initializer;
1991 initializer.keySystem = keySystem;
1992 initializer.sessionId = sessionId;
1993 initializer.initData = Uint8Array::create(initData, initDataLength);
1994 initializer.bubbles = false;
1995 initializer.cancelable = false;
1997 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitneedkeyEvent, initializer);
1998 event->setTarget(this);
1999 m_asyncEventQueue->enqueueEvent(event.release());
2003 void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*)
2006 if (m_networkState != NETWORK_LOADING)
2009 double time = WTF::currentTime();
2010 double timedelta = time - m_previousProgressTime;
2012 if (m_player->didLoadingProgress()) {
2013 scheduleEvent(eventNames().progressEvent);
2014 m_previousProgressTime = time;
2015 m_sentStalledEvent = false;
2017 renderer()->updateFromElement();
2018 if (hasMediaControls())
2019 mediaControls()->bufferingProgressed();
2020 } else if (timedelta > 3.0 && !m_sentStalledEvent) {
2021 scheduleEvent(eventNames().stalledEvent);
2022 m_sentStalledEvent = true;
2023 setShouldDelayLoadEvent(false);
2027 void HTMLMediaElement::createShadowSubtree()
2029 ASSERT(!userAgentShadowRoot());
2030 ShadowRoot::create(this, ShadowRoot::UserAgentShadowRoot);
2033 void HTMLMediaElement::willAddAuthorShadowRoot()
2035 if (!userAgentShadowRoot())
2036 createShadowSubtree();
2039 void HTMLMediaElement::rewind(float timeDelta)
2041 LOG(Media, "HTMLMediaElement::rewind(%f)", timeDelta);
2044 setCurrentTime(max(currentTime() - timeDelta, minTimeSeekable()), e);
2047 void HTMLMediaElement::returnToRealtime()
2049 LOG(Media, "HTMLMediaElement::returnToRealtime");
2051 setCurrentTime(maxTimeSeekable(), e);
2054 void HTMLMediaElement::addPlayedRange(float start, float end)
2056 LOG(Media, "HTMLMediaElement::addPlayedRange(%f, %f)", start, end);
2057 if (!m_playedTimeRanges)
2058 m_playedTimeRanges = TimeRanges::create();
2059 m_playedTimeRanges->add(start, end);
2062 bool HTMLMediaElement::supportsSave() const
2064 return m_player ? m_player->supportsSave() : false;
2067 bool HTMLMediaElement::supportsScanning() const
2069 return m_player ? m_player->supportsScanning() : false;
2072 void HTMLMediaElement::prepareToPlay()
2074 LOG(Media, "HTMLMediaElement::prepareToPlay(%p)", this);
2075 if (m_havePreparedToPlay)
2077 m_havePreparedToPlay = true;
2078 m_player->prepareToPlay();
2081 void HTMLMediaElement::seek(float time, ExceptionCode& ec)
2083 LOG(Media, "HTMLMediaElement::seek(%f)", time);
2087 // 1 - If the media element's readyState is HAVE_NOTHING, then raise an INVALID_STATE_ERR exception.
2088 if (m_readyState == HAVE_NOTHING || !m_player) {
2089 ec = INVALID_STATE_ERR;
2093 // If the media engine has been told to postpone loading data, let it go ahead now.
2094 if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA)
2097 // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set.
2098 refreshCachedTime();
2099 float now = currentTime();
2101 // 2 - If the element's seeking IDL attribute is true, then another instance of this algorithm is
2102 // already running. Abort that other instance of the algorithm without waiting for the step that
2103 // it is running to complete.
2104 // Nothing specific to be done here.
2106 // 3 - Set the seeking IDL attribute to true.
2107 // The flag will be cleared when the engine tells us the time has actually changed.
2110 // 5 - If the new playback position is later than the end of the media resource, then let it be the end
2111 // of the media resource instead.
2112 time = min(time, duration());
2114 // 6 - If the new playback position is less than the earliest possible position, let it be that position instead.
2115 float earliestTime = m_player->startTime();
2116 time = max(time, earliestTime);
2118 // Ask the media engine for the time value in the movie's time scale before comparing with current time. This
2119 // is necessary because if the seek time is not equal to currentTime but the delta is less than the movie's
2120 // time scale, we will ask the media engine to "seek" to the current movie time, which may be a noop and
2121 // not generate a timechanged callback. This means m_seeking will never be cleared and we will never
2122 // fire a 'seeked' event.
2124 float mediaTime = m_player->mediaTimeForTimeValue(time);
2125 if (time != mediaTime)
2126 LOG(Media, "HTMLMediaElement::seek(%f) - media timeline equivalent is %f", time, mediaTime);
2128 time = m_player->mediaTimeForTimeValue(time);
2130 // 7 - If the (possibly now changed) new playback position is not in one of the ranges given in the
2131 // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute
2132 // that is the nearest to the new playback position. ... If there are no ranges given in the seekable
2133 // attribute then set the seeking IDL attribute to false and abort these steps.
2134 RefPtr<TimeRanges> seekableRanges = seekable();
2136 // Short circuit seeking to the current time by just firing the events if no seek is required.
2137 // Don't skip calling the media engine if we are in poster mode because a seek should always
2138 // cancel poster display.
2139 bool noSeekRequired = !seekableRanges->length() || (time == now && displayMode() != Poster);
2141 #if ENABLE(MEDIA_SOURCE)
2142 // Always notify the media engine of a seek if the source is not closed. This ensures that the source is
2143 // always in a flushed state when the 'seeking' event fires.
2144 if (m_sourceState != SOURCE_CLOSED)
2145 noSeekRequired = false;
2148 if (noSeekRequired) {
2150 scheduleEvent(eventNames().seekingEvent);
2151 scheduleTimeupdateEvent(false);
2152 scheduleEvent(eventNames().seekedEvent);
2157 time = seekableRanges->nearest(time);
2160 if (m_lastSeekTime < now)
2161 addPlayedRange(m_lastSeekTime, now);
2163 m_lastSeekTime = time;
2164 m_sentEndEvent = false;
2166 #if ENABLE(MEDIA_SOURCE)
2167 if (m_sourceState == SOURCE_ENDED)
2168 setSourceState(SOURCE_OPEN);
2171 // 8 - Set the current playback position to the given new playback position
2172 m_player->seek(time);
2174 // 9 - Queue a task to fire a simple event named seeking at the element.
2175 scheduleEvent(eventNames().seekingEvent);
2177 // 10 - Queue a task to fire a simple event named timeupdate at the element.
2178 scheduleTimeupdateEvent(false);
2180 // 11-15 are handled, if necessary, when the engine signals a readystate change.
2183 void HTMLMediaElement::finishSeek()
2185 LOG(Media, "HTMLMediaElement::finishSeek");
2187 // 4.8.10.9 Seeking step 14
2190 // 4.8.10.9 Seeking step 15
2191 scheduleEvent(eventNames().seekedEvent);
2193 setDisplayMode(Video);
2196 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
2198 return m_readyState;
2201 MediaPlayer::MovieLoadType HTMLMediaElement::movieLoadType() const
2203 return m_player ? m_player->movieLoadType() : MediaPlayer::Unknown;
2206 bool HTMLMediaElement::hasAudio() const
2208 return m_player ? m_player->hasAudio() : false;
2211 bool HTMLMediaElement::seeking() const
2216 void HTMLMediaElement::refreshCachedTime() const
2218 m_cachedTime = m_player->currentTime();
2219 m_cachedTimeWallClockUpdateTime = WTF::currentTime();
2222 void HTMLMediaElement::invalidateCachedTime()
2224 LOG(Media, "HTMLMediaElement::invalidateCachedTime");
2226 // Don't try to cache movie time when playback first starts as the time reported by the engine
2227 // sometimes fluctuates for a short amount of time, so the cached time will be off if we take it
2229 static const double minimumTimePlayingBeforeCacheSnapshot = 0.5;
2231 m_minimumWallClockTimeToCacheMediaTime = WTF::currentTime() + minimumTimePlayingBeforeCacheSnapshot;
2232 m_cachedTime = MediaPlayer::invalidTime();
2236 float HTMLMediaElement::currentTime() const
2238 #if LOG_CACHED_TIME_WARNINGS
2239 static const double minCachedDeltaForWarning = 0.01;
2246 LOG(Media, "HTMLMediaElement::currentTime - seeking, returning %f", m_lastSeekTime);
2247 return m_lastSeekTime;
2250 if (m_cachedTime != MediaPlayer::invalidTime() && m_paused) {
2251 #if LOG_CACHED_TIME_WARNINGS
2252 float delta = m_cachedTime - m_player->currentTime();
2253 if (delta > minCachedDeltaForWarning)
2254 LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when paused", delta);
2256 return m_cachedTime;
2259 // Is it too soon use a cached time?
2260 double now = WTF::currentTime();
2261 double maximumDurationToCacheMediaTime = m_player->maximumDurationToCacheMediaTime();
2263 if (maximumDurationToCacheMediaTime && m_cachedTime != MediaPlayer::invalidTime() && !m_paused && now > m_minimumWallClockTimeToCacheMediaTime) {
2264 double wallClockDelta = now - m_cachedTimeWallClockUpdateTime;
2266 // Not too soon, use the cached time only if it hasn't expired.
2267 if (wallClockDelta < maximumDurationToCacheMediaTime) {
2268 float adjustedCacheTime = static_cast<float>(m_cachedTime + (m_playbackRate * wallClockDelta));
2270 #if LOG_CACHED_TIME_WARNINGS
2271 float delta = adjustedCacheTime - m_player->currentTime();
2272 if (delta > minCachedDeltaForWarning)
2273 LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when playing", delta);
2275 return adjustedCacheTime;
2279 #if LOG_CACHED_TIME_WARNINGS
2280 if (maximumDurationToCacheMediaTime && now > m_minimumWallClockTimeToCacheMediaTime && m_cachedTime != MediaPlayer::invalidTime()) {
2281 double wallClockDelta = now - m_cachedTimeWallClockUpdateTime;
2282 float delta = m_cachedTime + (m_playbackRate * wallClockDelta) - m_player->currentTime();
2283 LOG(Media, "HTMLMediaElement::currentTime - cached time was %f seconds off of media time when it expired", delta);
2287 refreshCachedTime();
2289 return m_cachedTime;
2292 void HTMLMediaElement::setCurrentTime(float time, ExceptionCode& ec)
2294 if (m_mediaController) {
2295 ec = INVALID_STATE_ERR;
2301 float HTMLMediaElement::startTime() const
2305 return m_player->startTime();
2308 double HTMLMediaElement::initialTime() const
2310 if (m_fragmentStartTime != MediaPlayer::invalidTime())
2311 return m_fragmentStartTime;
2316 return m_player->initialTime();
2319 float HTMLMediaElement::duration() const
2321 if (m_player && m_readyState >= HAVE_METADATA)
2322 return m_player->duration();
2324 return numeric_limits<float>::quiet_NaN();
2327 bool HTMLMediaElement::paused() const
2332 float HTMLMediaElement::defaultPlaybackRate() const
2334 return m_defaultPlaybackRate;
2337 void HTMLMediaElement::setDefaultPlaybackRate(float rate)
2339 if (m_defaultPlaybackRate != rate) {
2340 m_defaultPlaybackRate = rate;
2341 scheduleEvent(eventNames().ratechangeEvent);
2345 float HTMLMediaElement::playbackRate() const
2347 return m_playbackRate;
2350 void HTMLMediaElement::setPlaybackRate(float rate)
2352 LOG(Media, "HTMLMediaElement::setPlaybackRate(%f)", rate);
2354 if (m_playbackRate != rate) {
2355 m_playbackRate = rate;
2356 invalidateCachedTime();
2357 scheduleEvent(eventNames().ratechangeEvent);
2360 if (m_player && potentiallyPlaying() && m_player->rate() != rate && !m_mediaController)
2361 m_player->setRate(rate);
2364 void HTMLMediaElement::updatePlaybackRate()
2366 float effectiveRate = m_mediaController ? m_mediaController->playbackRate() : m_playbackRate;
2367 if (m_player && potentiallyPlaying() && m_player->rate() != effectiveRate)
2368 m_player->setRate(effectiveRate);
2371 bool HTMLMediaElement::webkitPreservesPitch() const
2373 return m_webkitPreservesPitch;
2376 void HTMLMediaElement::setWebkitPreservesPitch(bool preservesPitch)
2378 LOG(Media, "HTMLMediaElement::setWebkitPreservesPitch(%s)", boolString(preservesPitch));
2380 m_webkitPreservesPitch = preservesPitch;
2385 m_player->setPreservesPitch(preservesPitch);
2388 bool HTMLMediaElement::ended() const
2390 // 4.8.10.8 Playing the media resource
2391 // The ended attribute must return true if the media element has ended
2392 // playback and the direction of playback is forwards, and false otherwise.
2393 return endedPlayback() && m_playbackRate > 0;
2396 bool HTMLMediaElement::autoplay() const
2398 return fastHasAttribute(autoplayAttr);
2401 void HTMLMediaElement::setAutoplay(bool b)
2403 LOG(Media, "HTMLMediaElement::setAutoplay(%s)", boolString(b));
2404 setBooleanAttribute(autoplayAttr, b);
2407 String HTMLMediaElement::preload() const
2409 switch (m_preload) {
2410 case MediaPlayer::None:
2413 case MediaPlayer::MetaData:
2416 case MediaPlayer::Auto:
2421 ASSERT_NOT_REACHED();
2425 void HTMLMediaElement::setPreload(const String& preload)
2427 LOG(Media, "HTMLMediaElement::setPreload(%s)", preload.utf8().data());
2428 setAttribute(preloadAttr, preload);
2431 void HTMLMediaElement::play()
2433 LOG(Media, "HTMLMediaElement::play()");
2435 if (userGestureRequiredForRateChange() && !ScriptController::processingUserGesture())
2437 if (ScriptController::processingUserGesture())
2438 removeBehaviorsRestrictionsAfterFirstUserGesture();
2440 Settings* settings = document()->settings();
2441 if (settings && settings->needsSiteSpecificQuirks() && m_dispatchingCanPlayEvent && !m_loadInitiatedByUserGesture) {
2442 // It should be impossible to be processing the canplay event while handling a user gesture
2443 // since it is dispatched asynchronously.
2444 ASSERT(!ScriptController::processingUserGesture());
2445 String host = document()->baseURL().host();
2446 if (host.endsWith(".npr.org", false) || equalIgnoringCase(host, "npr.org"))
2453 void HTMLMediaElement::playInternal()
2455 LOG(Media, "HTMLMediaElement::playInternal");
2457 // 4.8.10.9. Playing the media resource
2458 if (!m_player || m_networkState == NETWORK_EMPTY)
2459 scheduleLoad(MediaResource);
2461 if (endedPlayback()) {
2462 ExceptionCode unused;
2466 if (m_mediaController)
2467 m_mediaController->bringElementUpToSpeed(this);
2471 invalidateCachedTime();
2472 scheduleEvent(eventNames().playEvent);
2474 if (m_readyState <= HAVE_CURRENT_DATA)
2475 scheduleEvent(eventNames().waitingEvent);
2476 else if (m_readyState >= HAVE_FUTURE_DATA)
2477 scheduleEvent(eventNames().playingEvent);
2479 m_autoplaying = false;
2482 updateMediaController();
2485 void HTMLMediaElement::pause()
2487 LOG(Media, "HTMLMediaElement::pause()");
2489 if (userGestureRequiredForRateChange() && !ScriptController::processingUserGesture())
2496 void HTMLMediaElement::pauseInternal()
2498 LOG(Media, "HTMLMediaElement::pauseInternal");
2500 // 4.8.10.9. Playing the media resource
2501 if (!m_player || m_networkState == NETWORK_EMPTY)
2502 scheduleLoad(MediaResource);
2504 m_autoplaying = false;
2508 scheduleTimeupdateEvent(false);
2509 scheduleEvent(eventNames().pauseEvent);
2515 #if ENABLE(MEDIA_SOURCE)
2517 void HTMLMediaElement::webkitSourceAddId(const String& id, const String& type, ExceptionCode& ec)
2519 if (id.isNull() || id.isEmpty()) {
2520 ec = INVALID_ACCESS_ERR;
2524 if (m_sourceIDs.contains(id)) {
2525 ec = INVALID_STATE_ERR;
2529 if (type.isNull() || type.isEmpty()) {
2530 ec = INVALID_ACCESS_ERR;
2534 if (!m_player || m_currentSrc != m_mediaSourceURL || m_sourceState != SOURCE_OPEN) {
2535 ec = INVALID_STATE_ERR;
2539 ContentType contentType(type);
2540 Vector<String> codecs = contentType.codecs();
2542 if (!codecs.size()) {
2543 ec = NOT_SUPPORTED_ERR;
2547 switch (m_player->sourceAddId(id, contentType.type(), codecs)) {
2548 case MediaPlayer::Ok:
2549 m_sourceIDs.add(id);
2551 case MediaPlayer::NotSupported:
2552 ec = NOT_SUPPORTED_ERR;
2554 case MediaPlayer::ReachedIdLimit:
2555 ec = QUOTA_EXCEEDED_ERR;
2559 ASSERT_NOT_REACHED();
2562 void HTMLMediaElement::webkitSourceRemoveId(const String& id, ExceptionCode& ec)
2564 if (!isValidSourceId(id, ec))
2567 if (!m_player || m_currentSrc != m_mediaSourceURL || m_sourceState == SOURCE_CLOSED) {
2568 ec = INVALID_STATE_ERR;
2572 if (!m_player->sourceRemoveId(id))
2573 ASSERT_NOT_REACHED();
2575 m_sourceIDs.remove(id);
2578 PassRefPtr<TimeRanges> HTMLMediaElement::webkitSourceBuffered(const String& id, ExceptionCode& ec)
2580 if (!isValidSourceId(id, ec))
2583 if (!m_player || m_currentSrc != m_mediaSourceURL || m_sourceState == SOURCE_CLOSED) {
2584 ec = INVALID_STATE_ERR;
2588 return m_player->sourceBuffered(id);
2591 void HTMLMediaElement::webkitSourceAppend(const String& id, PassRefPtr<Uint8Array> data, ExceptionCode& ec)
2593 if (!isValidSourceId(id, ec))
2596 if (!m_player || m_currentSrc != m_mediaSourceURL || m_sourceState != SOURCE_OPEN) {
2597 ec = INVALID_STATE_ERR;
2602 ec = INVALID_ACCESS_ERR;
2606 if (!data->length())
2609 if (!m_player->sourceAppend(id, data->data(), data->length())) {
2615 void HTMLMediaElement::webkitSourceAbort(const String& id, ExceptionCode& ec)
2617 if (!isValidSourceId(id, ec))
2620 if (!m_player || m_currentSrc != m_mediaSourceURL || m_sourceState != SOURCE_OPEN) {
2621 ec = INVALID_STATE_ERR;
2625 if (!m_player->sourceAbort(id))
2626 ASSERT_NOT_REACHED();
2629 void HTMLMediaElement::webkitSourceEndOfStream(unsigned short status, ExceptionCode& ec)
2631 if (!m_player || m_currentSrc != m_mediaSourceURL || m_sourceState != SOURCE_OPEN) {
2632 ec = INVALID_STATE_ERR;
2636 MediaPlayer::EndOfStreamStatus eosStatus = MediaPlayer::EosNoError;
2640 eosStatus = MediaPlayer::EosNoError;
2642 case EOS_NETWORK_ERR:
2643 eosStatus = MediaPlayer::EosNetworkError;
2645 case EOS_DECODE_ERR:
2646 eosStatus = MediaPlayer::EosDecodeError;
2653 setSourceState(SOURCE_ENDED);
2654 m_player->sourceEndOfStream(eosStatus);
2657 HTMLMediaElement::SourceState HTMLMediaElement::webkitSourceState() const
2659 return m_sourceState;
2662 void HTMLMediaElement::setSourceState(SourceState state)
2664 SourceState oldState = m_sourceState;
2665 m_sourceState = static_cast<SourceState>(state);
2667 if (m_sourceState == oldState)
2670 if (m_sourceState == SOURCE_CLOSED) {
2671 m_sourceIDs.clear();
2672 scheduleEvent(eventNames().webkitsourcecloseEvent);
2676 if (oldState == SOURCE_OPEN && m_sourceState == SOURCE_ENDED) {
2677 scheduleEvent(eventNames().webkitsourceendedEvent);
2681 if (m_sourceState == SOURCE_OPEN) {
2682 scheduleEvent(eventNames().webkitsourceopenEvent);
2688 #if ENABLE(ENCRYPTED_MEDIA)
2689 void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, PassRefPtr<Uint8Array> initData, ExceptionCode& ec)
2691 if (keySystem.isEmpty()) {
2697 ec = INVALID_STATE_ERR;
2701 const unsigned char* initDataPointer = 0;
2702 unsigned initDataLength = 0;
2704 initDataPointer = initData->data();
2705 initDataLength = initData->length();
2708 MediaPlayer::MediaKeyException result = m_player->generateKeyRequest(keySystem, initDataPointer, initDataLength);
2709 ec = exceptionCodeForMediaKeyException(result);
2712 void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, ExceptionCode& ec)
2714 webkitGenerateKeyRequest(keySystem, Uint8Array::create(0), ec);
2717 void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, PassRefPtr<Uint8Array> initData, const String& sessionId, ExceptionCode& ec)
2719 if (keySystem.isEmpty()) {
2729 if (!key->length()) {
2730 ec = TYPE_MISMATCH_ERR;
2735 ec = INVALID_STATE_ERR;
2739 const unsigned char* initDataPointer = 0;
2740 unsigned initDataLength = 0;
2742 initDataPointer = initData->data();
2743 initDataLength = initData->length();
2746 MediaPlayer::MediaKeyException result = m_player->addKey(keySystem, key->data(), key->length(), initDataPointer, initDataLength, sessionId);
2747 ec = exceptionCodeForMediaKeyException(result);
2750 void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, ExceptionCode& ec)
2752 webkitAddKey(keySystem, key, Uint8Array::create(0), String(), ec);
2755 void HTMLMediaElement::webkitCancelKeyRequest(const String& keySystem, const String& sessionId, ExceptionCode& ec)
2757 if (keySystem.isEmpty()) {
2763 ec = INVALID_STATE_ERR;
2767 MediaPlayer::MediaKeyException result = m_player->cancelKeyRequest(keySystem, sessionId);
2768 ec = exceptionCodeForMediaKeyException(result);
2773 bool HTMLMediaElement::loop() const
2775 return fastHasAttribute(loopAttr);
2778 void HTMLMediaElement::setLoop(bool b)
2780 LOG(Media, "HTMLMediaElement::setLoop(%s)", boolString(b));
2781 setBooleanAttribute(loopAttr, b);
2783 updateDisableSleep();
2787 bool HTMLMediaElement::controls() const
2789 Frame* frame = document()->frame();
2791 // always show controls when scripting is disabled
2792 if (frame && !frame->script()->canExecuteScripts(NotAboutToExecuteScript))
2795 // always show controls for video when fullscreen playback is required.
2796 if (isVideo() && document()->page() && document()->page()->chrome()->requiresFullscreenForVideoPlayback())
2799 // Always show controls when in full screen mode.
2803 return fastHasAttribute(controlsAttr);
2806 void HTMLMediaElement::setControls(bool b)
2808 LOG(Media, "HTMLMediaElement::setControls(%s)", boolString(b));
2809 setBooleanAttribute(controlsAttr, b);
2812 float HTMLMediaElement::volume() const
2817 void HTMLMediaElement::setVolume(float vol, ExceptionCode& ec)
2819 LOG(Media, "HTMLMediaElement::setVolume(%f)", vol);
2821 if (vol < 0.0f || vol > 1.0f) {
2822 ec = INDEX_SIZE_ERR;
2826 if (m_volume != vol) {
2829 scheduleEvent(eventNames().volumechangeEvent);
2833 bool HTMLMediaElement::muted() const
2838 void HTMLMediaElement::setMuted(bool muted)
2840 LOG(Media, "HTMLMediaElement::setMuted(%s)", boolString(muted));
2842 if (m_muted != muted) {
2844 // Avoid recursion when the player reports volume changes.
2845 if (!processingMediaPlayerCallback()) {
2847 m_player->setMuted(m_muted);
2848 if (hasMediaControls())
2849 mediaControls()->changedMute();
2852 scheduleEvent(eventNames().volumechangeEvent);
2856 void HTMLMediaElement::togglePlayState()
2858 LOG(Media, "HTMLMediaElement::togglePlayState - canPlay() is %s", boolString(canPlay()));
2860 // We can safely call the internal play/pause methods, which don't check restrictions, because
2861 // this method is only called from the built-in media controller
2863 updatePlaybackRate();
2869 void HTMLMediaElement::beginScrubbing()
2871 LOG(Media, "HTMLMediaElement::beginScrubbing - paused() is %s", boolString(paused()));
2875 // Because a media element stays in non-paused state when it reaches end, playback resumes
2876 // when the slider is dragged from the end to another position unless we pause first. Do
2877 // a "hard pause" so an event is generated, since we want to stay paused after scrubbing finishes.
2880 // Not at the end but we still want to pause playback so the media engine doesn't try to
2881 // continue playing during scrubbing. Pause without generating an event as we will
2882 // unpause after scrubbing finishes.
2883 setPausedInternal(true);
2888 void HTMLMediaElement::endScrubbing()
2890 LOG(Media, "HTMLMediaElement::endScrubbing - m_pausedInternal is %s", boolString(m_pausedInternal));
2892 if (m_pausedInternal)
2893 setPausedInternal(false);
2896 // The spec says to fire periodic timeupdate events (those sent while playing) every
2897 // "15 to 250ms", we choose the slowest frequency
2898 static const double maxTimeupdateEventFrequency = 0.25;
2900 void HTMLMediaElement::startPlaybackProgressTimer()
2902 if (m_playbackProgressTimer.isActive())
2905 m_previousProgressTime = WTF::currentTime();
2906 m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency);
2909 void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*)
2913 if (m_fragmentEndTime != MediaPlayer::invalidTime() && currentTime() >= m_fragmentEndTime && m_playbackRate > 0) {
2914 m_fragmentEndTime = MediaPlayer::invalidTime();
2915 if (!m_mediaController && !m_paused) {
2916 // changes paused to true and fires a simple event named pause at the media element.
2921 scheduleTimeupdateEvent(true);
2923 if (!m_playbackRate)
2926 if (!m_paused && hasMediaControls())
2927 mediaControls()->playbackProgressed();
2929 #if ENABLE(VIDEO_TRACK)
2930 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
2931 updateActiveTextTrackCues(currentTime());
2935 void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent)
2937 double now = WTF::currentTime();
2938 double timedelta = now - m_lastTimeUpdateEventWallTime;
2940 // throttle the periodic events
2941 if (periodicEvent && timedelta < maxTimeupdateEventFrequency)
2944 // Some media engines make multiple "time changed" callbacks at the same time, but we only want one
2945 // event at a given time so filter here
2946 float movieTime = currentTime();
2947 if (movieTime != m_lastTimeUpdateEventMovieTime) {
2948 scheduleEvent(eventNames().timeupdateEvent);
2949 m_lastTimeUpdateEventWallTime = now;
2950 m_lastTimeUpdateEventMovieTime = movieTime;
2954 bool HTMLMediaElement::canPlay() const
2956 return paused() || ended() || m_readyState < HAVE_METADATA;
2959 float HTMLMediaElement::percentLoaded() const
2963 float duration = m_player->duration();
2965 if (!duration || isinf(duration))
2969 RefPtr<TimeRanges> timeRanges = m_player->buffered();
2970 for (unsigned i = 0; i < timeRanges->length(); ++i) {
2971 ExceptionCode ignoredException;
2972 float start = timeRanges->start(i, ignoredException);
2973 float end = timeRanges->end(i, ignoredException);
2974 buffered += end - start;
2976 return buffered / duration;
2979 #if ENABLE(VIDEO_TRACK)
2980 PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const String& kind, const String& label, const String& language, ExceptionCode& ec)
2982 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
2985 // 4.8.10.12.4 Text track API
2986 // The addTextTrack(kind, label, language) method of media elements, when invoked, must run the following steps:
2988 // 1. If kind is not one of the following strings, then throw a SyntaxError exception and abort these steps
2989 if (!TextTrack::isValidKindKeyword(kind)) {
2994 // 2. If the label argument was omitted, let label be the empty string.
2995 // 3. If the language argument was omitted, let language be the empty string.
2996 // 4. Create a new TextTrack object.
2998 // 5. Create a new text track corresponding to the new object, and set its text track kind to kind, its text
2999 // track label to label, its text track language to language...
3000 RefPtr<TextTrack> textTrack = TextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, kind, label, language);
3002 // Note, due to side effects when changing track parameters, we have to
3003 // first append the track to the text track list.
3005 // 6. Add the new text track to the media element's list of text tracks.
3006 textTracks()->append(textTrack);
3008 // ... its text track readiness state to the text track loaded state ...
3009 textTrack->setReadinessState(TextTrack::Loaded);
3011 // ... its text track mode to the text track hidden mode, and its text track list of cues to an empty list ...
3012 textTrack->setMode(TextTrack::hiddenKeyword());
3014 return textTrack.release();
3017 TextTrackList* HTMLMediaElement::textTracks()
3019 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3023 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
3025 return m_textTracks.get();
3028 HTMLTrackElement* HTMLMediaElement::showingTrackWithSameKind(HTMLTrackElement* trackElement) const
3030 for (Node* node = firstChild(); node; node = node->nextSibling()) {
3031 if (trackElement == node)
3033 if (!node->hasTagName(trackTag))
3036 HTMLTrackElement* showingTrack = static_cast<HTMLTrackElement*>(node);
3037 if (showingTrack->kind() == trackElement->kind() && showingTrack->track()->mode() == TextTrack::showingKeyword())
3038 return showingTrack;
3044 void HTMLMediaElement::didAddTrack(HTMLTrackElement* trackElement)
3046 ASSERT(trackElement->hasTagName(trackTag));
3048 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3051 // 4.8.10.12.3 Sourcing out-of-band text tracks
3052 // When a track element's parent element changes and the new parent is a media element,
3053 // then the user agent must add the track element's corresponding text track to the
3054 // media element's list of text tracks ... [continues in TextTrackList::append]
3055 RefPtr<TextTrack> textTrack = trackElement->track();
3059 textTracks()->append(textTrack);
3061 // Do not schedule the track loading until parsing finishes so we don't start before all tracks
3062 // in the markup have been added.
3063 if (!m_parsingInProgress)
3064 scheduleLoad(TextTrackResource);
3067 void HTMLMediaElement::willRemoveTrack(HTMLTrackElement* trackElement)
3069 ASSERT(trackElement->hasTagName(trackTag));
3071 if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3075 if (trackElement->hasTagName(trackTag)) {
3076 KURL url = trackElement->getNonEmptyURLAttribute(srcAttr);
3077 LOG(Media, "HTMLMediaElement::willRemoveTrack - 'src' is %s", urlForLogging(url).utf8().data());
3081 trackElement->setHasBeenConfigured(false);
3086 RefPtr<TextTrack> textTrack = trackElement->track();
3090 // 4.8.10.12.3 Sourcing out-of-band text tracks
3091 // When a track element's parent element changes and the old parent was a media element,
3092 // then the user agent must remove the track element's corresponding text track from the
3093 // media element's list of text tracks.
3094 m_textTracks->remove(textTrack.get());
3095 size_t index = m_textTracksWhenResourceSelectionBegan.find(textTrack.get());
3096 if (index != notFound)
3097 m_textTracksWhenResourceSelectionBegan.remove(index);
3100 bool HTMLMediaElement::userIsInterestedInThisLanguage(const String&) const
3102 // FIXME: check the user's language preference - bugs.webkit.org/show_bug.cgi?id=74121
3106 bool HTMLMediaElement::userIsInterestedInThisTrackKind(String kind) const
3108 // If ... the user has indicated an interest in having a track with this text track kind, text track language, ...
3109 if (m_disableCaptions)
3112 Settings* settings = document()->settings();
3116 if (kind == TextTrack::subtitlesKeyword())
3117 return settings->shouldDisplaySubtitles() || m_closedCaptionsVisible;
3118 if (kind == TextTrack::captionsKeyword())
3119 return settings->shouldDisplayCaptions() || m_closedCaptionsVisible;
3120 if (kind == TextTrack::descriptionsKeyword())
3121 return settings->shouldDisplayTextDescriptions() || m_closedCaptionsVisible;
3126 void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group) const
3128 ASSERT(group.tracks.size());
3130 String bestMatchingLanguage;
3131 if (group.hasSrcLang) {
3132 Vector<String> languages;
3133 languages.reserveInitialCapacity(group.tracks.size());
3134 for (size_t i = 0; i < group.tracks.size(); ++i) {
3135 String srcLanguage = group.tracks[i]->track()->language();
3136 if (srcLanguage.length())
3137 languages.append(srcLanguage);
3139 bestMatchingLanguage = preferredLanguageFromList(languages);
3142 // First, find the track in the group that should be enabled (if any).
3143 HTMLTrackElement* trackElementToEnable = 0;
3144 HTMLTrackElement* defaultTrack = 0;
3145 HTMLTrackElement* fallbackTrack = 0;
3146 for (size_t i = 0; !trackElementToEnable && i < group.tracks.size(); ++i) {
3147 HTMLTrackElement* trackElement = group.tracks[i];
3148 RefPtr<TextTrack> textTrack = trackElement->track();
3150 if (userIsInterestedInThisTrackKind(textTrack->kind())) {
3151 // * If the text track kind is { [subtitles or captions] [descriptions] } and the user has indicated an interest in having a
3152 // track with this text track kind, text track language, and text track label enabled, and there is no
3153 // other text track in the media element's list of text tracks with a text track kind of either subtitles
3154 // or captions whose text track mode is showing
3156 // * If the text track kind is chapters and the text track language is one that the user agent has reason
3157 // to believe is appropriate for the user, and there is no other text track in the media element's list of
3158 // text tracks with a text track kind of chapters whose text track mode is showing
3159 // Let the text track mode be showing.
3160 if (bestMatchingLanguage.length()) {
3161 if (textTrack->language() == bestMatchingLanguage)
3162 trackElementToEnable = trackElement;
3163 } else if (trackElement->isDefault()) {
3164 // The user is interested in this type of track, but their language preference doesn't match any track so we will
3165 // enable the 'default' track.
3166 defaultTrack = trackElement;
3169 // Remember the first track that doesn't match language or have 'default' to potentially use as fallback.
3171 fallbackTrack = trackElement;
3172 } else if (!group.visibleTrack && !defaultTrack && trackElement->isDefault()) {
3173 // * If the track element has a default attribute specified, and there is no other text track in the media
3174 // element's list of text tracks whose text track mode is showing or showing by default
3175 // Let the text track mode be showing by default.
3176 defaultTrack = trackElement;
3180 if (!trackElementToEnable && defaultTrack)
3181 trackElementToEnable = defaultTrack;
3183 // If no track matches the user's preferred language and non was marked 'default', enable the first track
3184 // because the user has explicitly stated a preference for this kind of track.
3185 if (!trackElementToEnable && fallbackTrack)
3186 trackElementToEnable = fallbackTrack;
3188 for (size_t i = 0; i < group.tracks.size(); ++i) {
3189 HTMLTrackElement* trackElement = group.tracks[i];
3190 RefPtr<TextTrack> textTrack = trackElement->track();
3192 if (trackElementToEnable == trackElement) {
3193 textTrack->setMode(TextTrack::showingKeyword());
3194 if (defaultTrack == trackElement)
3195 textTrack->setShowingByDefault(true);
3197 if (textTrack->showingByDefault()) {
3198 // If there is a text track in the media element's list of text tracks whose text track
3199 // mode is showing by default, the user agent must furthermore change that text track's
3200 // text track mode to hidden.
3201 textTrack->setShowingByDefault(false);
3202 textTrack->setMode(TextTrack::hiddenKeyword());
3204 textTrack->setMode(TextTrack::disabledKeyword());
3208 if (trackElementToEnable && group.defaultTrack && group.defaultTrack != trackElementToEnable) {
3209 RefPtr<TextTrack> textTrack = group.defaultTrack->track();
3210 if (textTrack && textTrack->showingByDefault()) {
3211 textTrack->setShowingByDefault(false);
3212 textTrack->setMode(TextTrack::hiddenKeyword());
3217 void HTMLMediaElement::configureTextTracks()
3219 TrackGroup captionAndSubtitleTracks(TrackGroup::CaptionsAndSubtitles);
3220 TrackGroup descriptionTracks(TrackGroup::Description);
3221 TrackGroup chapterTracks(TrackGroup::Chapter);
3222 TrackGroup metadataTracks(TrackGroup::Metadata);
3223 TrackGroup otherTracks(TrackGroup::Other);
3225 for (Node* node = firstChild(); node; node = node->nextSibling()) {
3226 if (!node->hasTagName(trackTag))
3229 HTMLTrackElement* trackElement = static_cast<HTMLTrackElement*>(node);
3230 RefPtr<TextTrack> textTrack = trackElement->track();
3234 String kind = textTrack->kind();
3235 TrackGroup* currentGroup;
3236 if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword())
3237 currentGroup = &captionAndSubtitleTracks;
3238 else if (kind == TextTrack::descriptionsKeyword())
3239 currentGroup = &descriptionTracks;
3240 else if (kind == TextTrack::chaptersKeyword())
3241 currentGroup = &chapterTracks;
3242 else if (kind == TextTrack::metadataKeyword())
3243 currentGroup = &metadataTracks;
3245 currentGroup = &otherTracks;
3247 if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::showingKeyword())
3248 currentGroup->visibleTrack = trackElement;
3249 if (!currentGroup->defaultTrack && trackElement->isDefault())
3250 currentGroup->defaultTrack = trackElement;
3252 // Do not add this track to the group if it has already been automatically configured
3253 // as we only want to call configureTextTrack once per track so that adding another
3254 // track after the initial configuration doesn't reconfigure every track - only those
3255 // that should be changed by the new addition. For example all metadata tracks are
3256 // disabled by default, and we don't want a track that has been enabled by script
3257 // to be disabled automatically when a new metadata track is added later.
3258 if (trackElement->hasBeenConfigured())
3261 if (textTrack->language().length())
3262 currentGroup->hasSrcLang = true;
3263 currentGroup->tracks.append(trackElement);
3266 if (captionAndSubtitleTracks.tracks.size())
3267 configureTextTrackGroup(captionAndSubtitleTracks);
3268 if (descriptionTracks.tracks.size())
3269 configureTextTrackGroup(descriptionTracks);
3270 if (chapterTracks.tracks.size())
3271 configureTextTrackGroup(chapterTracks);
3272 if (metadataTracks.tracks.size())
3273 configureTextTrackGroup(metadataTracks);
3274 if (otherTracks.tracks.size())
3275 configureTextTrackGroup(otherTracks);
3279 bool HTMLMediaElement::havePotentialSourceChild()
3281 // Stash the current <source> node and next nodes so we can restore them after checking
3282 // to see there is another potential.
3283 RefPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode;
3284 RefPtr<Node> nextNode = m_nextChildNodeToConsider;
3286 KURL nextURL = selectNextSourceChild(0, 0, DoNothing);
3288 m_currentSourceNode = currentSourceNode;
3289 m_nextChildNodeToConsider = nextNode;
3291 return nextURL.isValid();
3294 KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid)
3297 // Don't log if this was just called to find out if there are any valid <source> elements.
3298 bool shouldLog = actionIfInvalid != DoNothing;
3300 LOG(Media, "HTMLMediaElement::selectNextSourceChild");
3303 if (!m_nextChildNodeToConsider) {
3306 LOG(Media, "HTMLMediaElement::selectNextSourceChild -> 0x0000, \"\"");
3313 HTMLSourceElement* source = 0;
3316 bool lookingForStartNode = m_nextChildNodeToConsider;
3317 bool canUseSourceElement = false;
3318 bool okToLoadSourceURL;
3320 NodeVector potentialSourceNodes;
3321 getChildNodes(this, potentialSourceNodes);
3323 for (unsigned i = 0; !canUseSourceElement && i < potentialSourceNodes.size(); ++i) {
3324 node = potentialSourceNodes[i].get();
3325 if (lookingForStartNode && m_nextChildNodeToConsider != node)
3327 lookingForStartNode = false;
3329 if (!node->hasTagName(sourceTag))
3331 if (node->parentNode() != this)
3334 source = static_cast<HTMLSourceElement*>(node);
3336 // 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
3337 mediaURL = source->getNonEmptyURLAttribute(srcAttr);
3340 LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'src' is %s", urlForLogging(mediaURL).utf8().data());
3342 if (mediaURL.isEmpty())
3345 if (source->fastHasAttribute(mediaAttr)) {
3346 MediaQueryEvaluator screenEval("screen", document()->frame(), renderer() ? renderer()->style() : 0);
3347 RefPtr<MediaQuerySet> media = MediaQuerySet::createAllowingDescriptionSyntax(source->media());
3350 LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'media' is %s", source->media().utf8().data());
3352 if (!screenEval.eval(media.get()))
3356 type = source->type();
3357 // FIXME(82965): Add support for keySystem in <source> and set system from source.
3358 if (type.isEmpty() && mediaURL.protocolIsData())
3359 type = mimeTypeFromDataURL(mediaURL);
3360 if (!type.isEmpty() || !system.isEmpty()) {
3363 LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'type' is '%s' - key system is '%s'", type.utf8().data(), system.utf8().data());
3365 if (!MediaPlayer::supportsType(ContentType(type), system, mediaURL, this))
3369 // Is it safe to load this url?
3370 okToLoadSourceURL = isSafeToLoadURL(mediaURL, actionIfInvalid) && dispatchBeforeLoadEvent(mediaURL.string());
3372 // A 'beforeload' event handler can mutate the DOM, so check to see if the source element is still a child node.
3373 if (node->parentNode() != this) {
3374 LOG(Media, "HTMLMediaElement::selectNextSourceChild : 'beforeload' removed current element");
3379 if (!okToLoadSourceURL)
3382 // Making it this far means the <source> looks reasonable.
3383 canUseSourceElement = true;
3386 if (!canUseSourceElement && actionIfInvalid == Complain && source)
3387 source->scheduleErrorEvent();
3390 if (canUseSourceElement) {
3392 *contentType = ContentType(type);
3394 *keySystem = system;
3395 m_currentSourceNode = source;
3396 m_nextChildNodeToConsider = source->nextSibling();
3398 m_currentSourceNode = 0;
3399 m_nextChildNodeToConsider = 0;
3404 LOG(Media, "HTMLMediaElement::selectNextSourceChild -> %p, %s", m_currentSourceNode.get(), canUseSourceElement ? urlForLogging(mediaURL).utf8().data() : "");
3406 return canUseSourceElement ? mediaURL : KURL();
3409 void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source)
3411 LOG(Media, "HTMLMediaElement::sourceWasAdded(%p)", source);
3414 if (source->hasTagName(sourceTag)) {
3415 KURL url = source->getNonEmptyURLAttribute(srcAttr);
3416 LOG(Media, "HTMLMediaElement::sourceWasAdded - 'src' is %s", urlForLogging(url).utf8().data());
3420 // We should only consider a <source> element when there is not src attribute at all.
3421 if (fastHasAttribute(srcAttr))
3424 // 4.8.8 - If a source element is inserted as a child of a media element that has no src
3425 // attribute and whose networkState has the value NETWORK_EMPTY, the user agent must invoke
3426 // the media element's resource selection algorithm.
3427 if (networkState() == HTMLMediaElement::NETWORK_EMPTY) {
3428 scheduleLoad(MediaResource);
3429 m_nextChildNodeToConsider = source;
3433 if (m_currentSourceNode && source == m_currentSourceNode->nextSibling()) {
3434 LOG(Media, "HTMLMediaElement::sourceWasAdded - <source> inserted immediately after current source");
3435 m_nextChildNodeToConsider = source;
3439 if (m_nextChildNodeToConsider)
3442 // 4.8.9.5, resource selection algorithm, source elements section:
3443 // 21. Wait until the node after pointer is a node other than the end of the list. (This step might wait forever.)
3444 // 22. Asynchronously await a stable state...
3445 // 23. Set the element's delaying-the-load-event flag back to true (this delays the load event again, in case
3446 // it hasn't been fired yet).
3447 setShouldDelayLoadEvent(true);
3449 // 24. Set the networkState back to NETWORK_LOADING.
3450 m_networkState = NETWORK_LOADING;
3452 // 25. Jump back to the find next candidate step above.
3453 m_nextChildNodeToConsider = source;
3454 scheduleNextSourceChild();
3457 void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement* source)
3459 LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p)", source);
3462 if (source->hasTagName(sourceTag)) {
3463 KURL url = source->getNonEmptyURLAttribute(srcAttr);
3464 LOG(Media, "HTMLMediaElement::sourceWasRemoved - 'src' is %s", urlForLogging(url).utf8().data());
3468 if (source != m_currentSourceNode && source != m_nextChildNodeToConsider)
3471 if (source == m_nextChildNodeToConsider) {
3472 if (m_currentSourceNode)
3473 m_nextChildNodeToConsider = m_currentSourceNode->nextSibling();
3474 LOG(Media, "HTMLMediaElement::sourceRemoved - m_nextChildNodeToConsider set to %p", m_nextChildNodeToConsider.get());
3475 } else if (source == m_currentSourceNode) {
3476 // Clear the current source node pointer, but don't change the movie as the spec says:
3477 // 4.8.8 - Dynamically modifying a source element and its attribute when the element is already
3478 // inserted in a video or audio element will have no effect.
3479 m_currentSourceNode = 0;
3480 LOG(Media, "HTMLMediaElement::sourceRemoved - m_currentSourceNode set to 0");
3484 void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*)
3486 LOG(Media, "HTMLMediaElement::mediaPlayerTimeChanged");
3488 #if ENABLE(VIDEO_TRACK)
3489 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3490 updateActiveTextTrackCues(currentTime());
3493 beginProcessingMediaPlayerCallback();
3495 invalidateCachedTime();
3497 // 4.8.10.9 step 14 & 15. Needed if no ReadyState change is associated with the seek.
3498 if (m_seeking && m_readyState >= HAVE_CURRENT_DATA)
3501 // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity,
3502 // it will only queue a 'timeupdate' event if we haven't already posted one at the current
3504 scheduleTimeupdateEvent(false);
3506 float now = currentTime();
3507 float dur = duration();
3509 // When the current playback position reaches the end of the media resource when the direction of
3510 // playback is forwards, then the user agent must follow these steps:
3511 if (!isnan(dur) && dur && now >= dur && m_playbackRate > 0) {
3512 // If the media element has a loop attribute specified and does not have a current media controller,
3513 if (loop() && !m_mediaController) {
3514 ExceptionCode ignoredException;
3515 m_sentEndEvent = false;
3516 // then seek to the earliest possible position of the media resource and abort these steps.
3517 seek(startTime(), ignoredException);
3519 // If the media element does not have a current media controller, and the media element
3520 // has still ended playback, and the direction of playback is still forwards, and paused
3522 if (!m_mediaController && !m_paused) {
3523 // changes paused to true and fires a simple event named pause at the media element.
3525 scheduleEvent(eventNames().pauseEvent);
3527 // Queue a task to fire a simple event named ended at the media element.
3528 if (!m_sentEndEvent) {
3529 m_sentEndEvent = true;
3530 scheduleEvent(eventNames().endedEvent);
3532 // If the media element has a current media controller, then report the controller state
3533 // for the media element's current media controller.
3534 updateMediaController();
3538 m_sentEndEvent = false;
3541 endProcessingMediaPlayerCallback();
3544 void HTMLMediaElement::mediaPlayerVolumeChanged(MediaPlayer*)
3546 LOG(Media, "HTMLMediaElement::mediaPlayerVolumeChanged");
3548 beginProcessingMediaPlayerCallback();
3550 float vol = m_player->volume();
3551 if (vol != m_volume) {
3554 scheduleEvent(eventNames().volumechangeEvent);
3557 endProcessingMediaPlayerCallback();
3560 void HTMLMediaElement::mediaPlayerMuteChanged(MediaPlayer*)
3562 LOG(Media, "HTMLMediaElement::mediaPlayerMuteChanged");
3564 beginProcessingMediaPlayerCallback();
3566 setMuted(m_player->muted());
3567 endProcessingMediaPlayerCallback();
3570 void HTMLMediaElement::mediaPlayerDurationChanged(MediaPlayer* player)
3572 LOG(Media, "HTMLMediaElement::mediaPlayerDurationChanged");
3574 beginProcessingMediaPlayerCallback();
3575 scheduleEvent(eventNames().durationchangeEvent);
3576 mediaPlayerCharacteristicChanged(player);
3577 endProcessingMediaPlayerCallback();
3580 void HTMLMediaElement::mediaPlayerRateChanged(MediaPlayer*)
3582 LOG(Media, "HTMLMediaElement::mediaPlayerRateChanged");
3584 beginProcessingMediaPlayerCallback();
3586 // Stash the rate in case the one we tried to set isn't what the engine is
3587 // using (eg. it can't handle the rate we set)
3588 m_playbackRate = m_player->rate();
3590 invalidateCachedTime();
3593 updateDisableSleep();
3596 endProcessingMediaPlayerCallback();
3599 void HTMLMediaElement::mediaPlayerPlaybackStateChanged(MediaPlayer*)
3601 LOG(Media, "HTMLMediaElement::mediaPlayerPlaybackStateChanged");
3603 if (!m_player || m_pausedInternal)
3606 beginProcessingMediaPlayerCallback();
3607 if (m_player->paused())
3611 endProcessingMediaPlayerCallback();
3614 void HTMLMediaElement::mediaPlayerSawUnsupportedTracks(MediaPlayer*)
3616 LOG(Media, "HTMLMediaElement::mediaPlayerSawUnsupportedTracks");
3618 // The MediaPlayer came across content it cannot completely handle.
3619 // This is normally acceptable except when we are in a standalone
3620 // MediaDocument. If so, tell the document what has happened.
3621 if (ownerDocument()->isMediaDocument()) {
3622 MediaDocument* mediaDocument = static_cast<MediaDocument*>(ownerDocument());
3623 mediaDocument->mediaElementSawUnsupportedTracks();
3627 void HTMLMediaElement::mediaPlayerResourceNotSupported(MediaPlayer*)
3629 LOG(Media, "HTMLMediaElement::mediaPlayerResourceNotSupported");
3631 // The MediaPlayer came across content which no installed engine supports.
3632 mediaLoadingFailed(MediaPlayer::FormatError);
3635 // MediaPlayerPresentation methods
3636 void HTMLMediaElement::mediaPlayerRepaint(MediaPlayer*)
3638 beginProcessingMediaPlayerCallback();
3639 updateDisplayState();
3641 renderer()->repaint();
3642 endProcessingMediaPlayerCallback();
3645 void HTMLMediaElement::mediaPlayerSizeChanged(MediaPlayer*)
3647 LOG(Media, "HTMLMediaElement::mediaPlayerSizeChanged");
3649 beginProcessingMediaPlayerCallback();
3651 renderer()->updateFromElement();
3652 endProcessingMediaPlayerCallback();
3655 #if USE(ACCELERATED_COMPOSITING)
3656 bool HTMLMediaElement::mediaPlayerRenderingCanBeAccelerated(MediaPlayer*)
3658 if (renderer() && renderer()->isVideo()) {
3659 ASSERT(renderer()->view());
3660 return renderer()->view()->compositor()->canAccelerateVideoRendering(toRenderVideo(renderer()));
3665 void HTMLMediaElement::mediaPlayerRenderingModeChanged(MediaPlayer*)
3667 LOG(Media, "HTMLMediaElement::mediaPlayerRenderingModeChanged");
3669 // Kick off a fake recalcStyle that will update the compositing tree.
3670 setNeedsStyleRecalc(SyntheticStyleChange);
3674 #if PLATFORM(WIN) && USE(AVFOUNDATION)
3675 GraphicsDeviceAdapter* HTMLMediaElement::mediaPlayerGraphicsDeviceAdapter(const MediaPlayer*) const
3677 if (!document() || !document()->page())
3680 return document()->page()->chrome()->client()->graphicsDeviceAdapter();
3684 void HTMLMediaElement::mediaPlayerEngineUpdated(MediaPlayer*)
3686 LOG(Media, "HTMLMediaElement::mediaPlayerEngineUpdated");
3687 beginProcessingMediaPlayerCallback();
3689 renderer()->updateFromElement();
3690 endProcessingMediaPlayerCallback();
3693 void HTMLMediaElement::mediaPlayerFirstVideoFrameAvailable(MediaPlayer*)
3695 LOG(Media, "HTMLMediaElement::mediaPlayerFirstVideoFrameAvailable");
3696 beginProcessingMediaPlayerCallback();
3697 if (displayMode() == PosterWaitingForVideo) {
3698 setDisplayMode(Video);
3699 #if USE(ACCELERATED_COMPOSITING)
3700 mediaPlayerRenderingModeChanged(m_player.get());
3703 endProcessingMediaPlayerCallback();
3706 void HTMLMediaElement::mediaPlayerCharacteristicChanged(MediaPlayer*)
3708 LOG(Media, "HTMLMediaElement::mediaPlayerCharacteristicChanged");
3710 beginProcessingMediaPlayerCallback();
3711 if (hasMediaControls())
3712 mediaControls()->reset();
3714 renderer()->updateFromElement();
3715 endProcessingMediaPlayerCallback();
3718 PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const
3721 return TimeRanges::create();
3722 return m_player->buffered();
3725 PassRefPtr<TimeRanges> HTMLMediaElement::played()
3728 float time = currentTime();
3729 if (time > m_lastSeekTime)
3730 addPlayedRange(m_lastSeekTime, time);
3733 if (!m_playedTimeRanges)
3734 m_playedTimeRanges = TimeRanges::create();
3736 return m_playedTimeRanges->copy();
3739 PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const
3741 return m_player ? m_player->seekable() : TimeRanges::create();
3744 bool HTMLMediaElement::potentiallyPlaying() const
3746 // "pausedToBuffer" means the media engine's rate is 0, but only because it had to stop playing
3747 // when it ran out of buffered data. A movie is this state is "potentially playing", modulo the
3748 // checks in couldPlayIfEnoughData().
3749 bool pausedToBuffer = m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA;
3750 return (pausedToBuffer || m_readyState >= HAVE_FUTURE_DATA) && couldPlayIfEnoughData() && !isBlockedOnMediaController();
3753 bool HTMLMediaElement::couldPlayIfEnoughData() const
3755 return !paused() && !endedPlayback() && !stoppedDueToErrors() && !pausedForUserInteraction();
3758 bool HTMLMediaElement::endedPlayback() const
3760 float dur = duration();
3761 if (!m_player || isnan(dur))
3764 // 4.8.10.8 Playing the media resource
3766 // A media element is said to have ended playback when the element's
3767 // readyState attribute is HAVE_METADATA or greater,
3768 if (m_readyState < HAVE_METADATA)
3771 // and the current playback position is the end of the media resource and the direction
3772 // of playback is forwards, Either the media element does not have a loop attribute specified,
3773 // or the media element has a current media controller.
3774 float now = currentTime();
3775 if (m_playbackRate > 0)
3776 return dur > 0 && now >= dur && (!loop() || m_mediaController);
3778 // or the current playback position is the earliest possible position and the direction
3779 // of playback is backwards
3780 if (m_playbackRate < 0)
3786 bool HTMLMediaElement::stoppedDueToErrors() const
3788 if (m_readyState >= HAVE_METADATA && m_error) {
3789 RefPtr<TimeRanges> seekableRanges = seekable();
3790 if (!seekableRanges->contain(currentTime()))
3797 bool HTMLMediaElement::pausedForUserInteraction() const
3799 // return !paused() && m_readyState >= HAVE_FUTURE_DATA && [UA requires a decitions from the user]
3803 float HTMLMediaElement::minTimeSeekable() const
3808 float HTMLMediaElement::maxTimeSeekable() const
3810 return m_player ? m_player->maxTimeSeekable() : 0;
3813 void HTMLMediaElement::updateVolume()
3818 // Avoid recursion when the player reports volume changes.
3819 if (!processingMediaPlayerCallback()) {
3820 Page* page = document()->page();
3821 float volumeMultiplier = page ? page->mediaVolume() : 1;
3822 bool shouldMute = m_muted;
3824 if (m_mediaController) {
3825 volumeMultiplier *= m_mediaController->volume();
3826 shouldMute = m_mediaController->muted();
3829 m_player->setMuted(shouldMute);
3830 m_player->setVolume(m_volume * volumeMultiplier);
3833 if (hasMediaControls())
3834 mediaControls()->changedVolume();
3837 void HTMLMediaElement::updatePlayState()
3842 if (m_pausedInternal) {
3843 if (!m_player->paused())
3845 refreshCachedTime();
3846 m_playbackProgressTimer.stop();
3847 if (hasMediaControls())
3848 mediaControls()->playbackStopped();
3852 bool shouldBePlaying = potentiallyPlaying();
3853 bool playerPaused = m_player->paused();
3855 LOG(Media, "HTMLMediaElement::updatePlayState - shouldBePlaying = %s, playerPaused = %s",
3856 boolString(shouldBePlaying), boolString(playerPaused));
3858 if (shouldBePlaying) {
3859 setDisplayMode(Video);
3860 invalidateCachedTime();
3863 if (!m_isFullscreen && isVideo() && document() && document()->page() && document()->page()->chrome()->requiresFullscreenForVideoPlayback())
3866 // Set rate, muted before calling play in case they were set before the media engine was setup.
3867 // The media engine should just stash the rate and muted values since it isn't already playing.
3868 m_player->setRate(m_playbackRate);
3869 m_player->setMuted(m_muted);
3874 if (hasMediaControls())
3875 mediaControls()->playbackStarted();
3876 startPlaybackProgressTimer();
3878 } else { // Should not be playing right now
3881 refreshCachedTime();
3883 m_playbackProgressTimer.stop();
3885 float time = currentTime();
3886 if (time > m_lastSeekTime)
3887 addPlayedRange(m_lastSeekTime, time);
3889 if (couldPlayIfEnoughData())
3892 if (hasMediaControls())
3893 mediaControls()->playbackStopped();
3896 updateMediaController();
3899 renderer()->updateFromElement();
3902 void HTMLMediaElement::setPausedInternal(bool b)
3904 m_pausedInternal = b;
3908 void HTMLMediaElement::stopPeriodicTimers()
3910 m_progressEventTimer.stop();
3911 m_playbackProgressTimer.stop();
3914 void HTMLMediaElement::userCancelledLoad()
3916 LOG(Media, "HTMLMediaElement::userCancelledLoad");
3918 if (m_networkState == NETWORK_EMPTY || m_completelyLoaded)
3921 // If the media data fetching process is aborted by the user:
3923 // 1 - The user agent should cancel the fetching process.
3924 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
3927 stopPeriodicTimers();
3929 m_loadState = WaitingForSource;
3930 m_pendingLoadFlags = 0;
3932 // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED.
3933 m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED);
3935 // 3 - Queue a task to fire a simple event named error at the media element.
3936 scheduleEvent(eventNames().abortEvent);
3938 #if ENABLE(MEDIA_SOURCE)
3939 if (m_sourceState != SOURCE_CLOSED)
3940 setSourceState(SOURCE_CLOSED);
3943 // 4 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the
3944 // element's networkState attribute to the NETWORK_EMPTY value and queue a task to fire a
3945 // simple event named emptied at the element. Otherwise, set the element's networkState
3946 // attribute to the NETWORK_IDLE value.
3947 if (m_readyState == HAVE_NOTHING) {
3948 m_networkState = NETWORK_EMPTY;
3949 scheduleEvent(eventNames().emptiedEvent);
3952 m_networkState = NETWORK_IDLE;
3954 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
3955 setShouldDelayLoadEvent(false);
3957 // 6 - Abort the overall resource selection algorithm.
3958 m_currentSourceNode = 0;
3960 // Reset m_readyState since m_player is gone.
3961 m_readyState = HAVE_NOTHING;
3962 updateMediaController();
3963 #if ENABLE(VIDEO_TRACK)
3964 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
3965 updateActiveTextTrackCues(0);
3969 bool HTMLMediaElement::canSuspend() const
3974 void HTMLMediaElement::stop()
3976 LOG(Media, "HTMLMediaElement::stop");
3980 m_inActiveDocument = false;
3981 userCancelledLoad();
3983 // Stop the playback without generating events
3984 setPausedInternal(true);
3987 renderer()->updateFromElement();
3989 stopPeriodicTimers();
3990 cancelPendingEventsAndCallbacks();
3993 void HTMLMediaElement::suspend(ReasonForSuspension why)
3995 LOG(Media, "HTMLMediaElement::suspend");
3996 #if ENABLE(TIZEN_DLOG_SUPPORT)
3997 TIZEN_LOGI("Why: %d", why);
4002 case DocumentWillBecomeInactive:
4005 case PageWillBeSuspended:
4006 case JavaScriptDebuggerPaused:
4007 case WillDeferLoading:
4008 // Do nothing, we don't pause media playback in these cases.
4009 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
4010 if (shouldSuspendMedia()) {
4012 m_player->suspend();
4020 void HTMLMediaElement::resume()
4022 LOG(Media, "HTMLMediaElement::resume");
4024 m_inActiveDocument = true;
4025 setPausedInternal(false);
4027 if (m_error && m_error->code() == MediaError::MEDIA_ERR_ABORTED) {
4028 // Restart the load if it was aborted in the middle by moving the document to the page cache.
4029 // m_error is only left at MEDIA_ERR_ABORTED when the document becomes inactive (it is set to
4030 // MEDIA_ERR_ABORTED while the abortEvent is being sent, but cleared immediately afterwards).
4031 // This behavior is not specified but it seems like a sensible thing to do.
4032 // As it is not safe to immedately start loading now, let's schedule a load.
4033 scheduleLoad(MediaResource);
4037 renderer()->updateFromElement();
4039 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
4040 // Player will resume asynchronously.
4041 // Set m_suspended to false in setSuspended().
4047 bool HTMLMediaElement::hasPendingActivity() const
4049 return m_asyncEventQueue->hasPendingEvents();
4052 void HTMLMediaElement::mediaVolumeDidChange()
4054 LOG(Media, "HTMLMediaElement::mediaVolumeDidChange");
4058 void HTMLMediaElement::defaultEventHandler(Event* event)
4060 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
4061 RenderObject* r = renderer();
4062 if (!r || !r->isWidget())
4065 Widget* widget = toRenderWidget(r)->widget();
4067 widget->handleEvent(event);
4069 HTMLElement::defaultEventHandler(event);
4073 bool HTMLMediaElement::willRespondToMouseClickEvents()
4075 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
4078 return HTMLElement::willRespondToMouseClickEvents();
4082 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
4084 void HTMLMediaElement::ensureMediaPlayer()
4087 createMediaPlayer();
4090 void HTMLMediaElement::deliverNotification(MediaPlayerProxyNotificationType notification)
4092 if (notification == MediaPlayerNotificationPlayPauseButtonPressed) {
4098 m_player->deliverNotification(notification);
4101 void HTMLMediaElement::setMediaPlayerProxy(WebMediaPlayerProxy* proxy)
4103 ensureMediaPlayer();
4104 m_player->setMediaPlayerProxy(proxy);
4107 void HTMLMediaElement::getPluginProxyParams(KURL& url, Vector<String>& names, Vector<String>& values)
4109 RefPtr<HTMLMediaElement> protect(this); // selectNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations.
4111 Frame* frame = document()->frame();
4114 KURL posterURL = getNonEmptyURLAttribute(posterAttr);
4115 if (!posterURL.isEmpty() && frame && frame->loader()->willLoadMediaElementURL(posterURL)) {
4116 names.append("_media_element_poster_");
4117 values.append(posterURL.string());
4122 names.append("_media_element_controls_");
4123 values.append("true");
4127 if (!isSafeToLoadURL(url, Complain))
4128 url = selectNextSourceChild(0, 0, DoNothing);
4131 if (url.isValid() && frame && frame->loader()->willLoadMediaElementURL(url)) {
4132 names.append("_media_element_src_");
4133 values.append(m_currentSrc.string());
4137 void HTMLMediaElement::createMediaPlayerProxy()
4139 ensureMediaPlayer();
4141 if (m_proxyWidget || (inDocument() && !m_needWidgetUpdate))
4144 Frame* frame = document()->frame();
4148 LOG(Media, "HTMLMediaElement::createMediaPlayerProxy");
4151 Vector<String> paramNames;
4152 Vector<String> paramValues;
4154 getPluginProxyParams(url, paramNames, paramValues);
4156 // Hang onto the proxy widget so it won't be destroyed if the plug-in is set to
4158 m_proxyWidget = frame->loader()->subframeLoader()->loadMediaPlayerProxyPlugin(this, url, paramNames, paramValues);
4160 m_needWidgetUpdate = false;
4163 void HTMLMediaElement::updateWidget(PluginCreationOption)
4165 mediaElement->setNeedWidgetUpdate(false);
4167 Vector<String> paramNames;
4168 Vector<String> paramValues;
4169 // FIXME: Rename kurl to something more sensible.
4172 mediaElement->getPluginProxyParams(kurl, paramNames, paramValues);
4173 // FIXME: What if document()->frame() is 0?
4174 SubframeLoader* loader = document()->frame()->loader()->subframeLoader();
4175 loader->loadMediaPlayerProxyPlugin(mediaElement, kurl, paramNames, paramValues);
4178 #endif // ENABLE(PLUGIN_PROXY_FOR_VIDEO)
4180 bool HTMLMediaElement::isFullscreen() const
4185 #if ENABLE(FULLSCREEN_API)
4186 if (document()->webkitIsFullScreen() && document()->webkitCurrentFullScreenElement() == this)
4193 void HTMLMediaElement::enterFullscreen()
4195 LOG(Media, "HTMLMediaElement::enterFullscreen");
4197 #if ENABLE(FULLSCREEN_API)
4198 if (document() && document()->settings() && document()->settings()->fullScreenEnabled()) {
4199 document()->requestFullScreenForElement(this, 0, Document::ExemptIFrameAllowFullScreenRequirement);
4203 ASSERT(!m_isFullscreen);
4204 m_isFullscreen = true;
4205 if (hasMediaControls())
4206 mediaControls()->enteredFullscreen();
4207 if (document() && document()->page()) {
4208 document()->page()->chrome()->client()->enterFullscreenForNode(this);
4209 scheduleEvent(eventNames().webkitbeginfullscreenEvent);
4214 void HTMLMediaElement::exitFullscreen()
4216 LOG(Media, "HTMLMediaElement::exitFullscreen");
4218 #if ENABLE(FULLSCREEN_API)
4219 if (document() && document()->settings() && document()->settings()->fullScreenEnabled()) {
4220 if (document()->webkitIsFullScreen() && document()->webkitCurrentFullScreenElement() == this)
4221 document()->webkitCancelFullScreen();
4225 ASSERT(m_isFullscreen);
4226 m_isFullscreen = false;
4227 if (hasMediaControls())
4228 mediaControls()->exitedFullscreen();
4229 if (document() && document()->page()) {
4230 if (document()->page()->chrome()->requiresFullscreenForVideoPlayback())
4232 document()->page()->chrome()->client()->exitFullscreenForNode(this);
4233 scheduleEvent(eventNames().webkitendfullscreenEvent);
4237 void HTMLMediaElement::didBecomeFullscreenElement()
4239 if (hasMediaControls())
4240 mediaControls()->enteredFullscreen();
4243 void HTMLMediaElement::willStopBeingFullscreenElement()
4245 if (hasMediaControls())
4246 mediaControls()->exitedFullscreen();
4249 PlatformMedia HTMLMediaElement::platformMedia() const
4251 return m_player ? m_player->platformMedia() : NoPlatformMedia;
4254 #if USE(ACCELERATED_COMPOSITING)
4255 PlatformLayer* HTMLMediaElement::platformLayer() const
4257 return m_player ? m_player->platformLayer() : 0;
4261 bool HTMLMediaElement::hasClosedCaptions() const
4263 if (m_player && m_player->hasClosedCaptions())
4266 #if ENABLE(VIDEO_TRACK)
4267 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled() && m_textTracks)
4268 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
4269 if (m_textTracks->item(i)->kind() == TextTrack::captionsKeyword()
4270 || m_textTracks->item(i)->kind() == TextTrack::subtitlesKeyword())
4277 bool HTMLMediaElement::closedCaptionsVisible() const
4279 return m_closedCaptionsVisible;
4282 void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible)
4284 LOG(Media, "HTMLMediaElement::setClosedCaptionsVisible(%s)", boolString(closedCaptionVisible));
4286 if (!m_player || !hasClosedCaptions())
4289 m_closedCaptionsVisible = closedCaptionVisible;
4290 m_player->setClosedCaptionsVisible(closedCaptionVisible);
4292 #if ENABLE(VIDEO_TRACK)
4293 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) {
4294 m_disableCaptions = !m_closedCaptionsVisible;
4296 // Mark all track elements as not "configured" so that configureTextTracks()
4297 // will reconsider which tracks to display in light of new user preferences
4298 // (e.g. default tracks should not be displayed if the user has turned off
4299 // captions and non-default tracks should be displayed based on language
4300 // preferences if the user has turned captions on).
4301 for (Node* node = firstChild(); node; node = node->nextSibling()) {
4302 if (!node->hasTagName(trackTag))
4304 HTMLTrackElement* trackElement = static_cast<HTMLTrackElement*>(node);
4305 if (trackElement->kind() == TextTrack::captionsKeyword()
4306 || trackElement->kind() == TextTrack::subtitlesKeyword())
4307 trackElement->setHasBeenConfigured(false);
4310 configureTextTracks();
4313 if (hasMediaControls())
4314 mediaControls()->changedClosedCaptionsVisibility();
4318 void HTMLMediaElement::setWebkitClosedCaptionsVisible(bool visible)
4320 setClosedCaptionsVisible(visible);
4323 bool HTMLMediaElement::webkitClosedCaptionsVisible() const
4325 return closedCaptionsVisible();
4329 bool HTMLMediaElement::webkitHasClosedCaptions() const
4331 return hasClosedCaptions();
4334 #if ENABLE(MEDIA_STATISTICS)
4335 unsigned HTMLMediaElement::webkitAudioDecodedByteCount() const
4339 return m_player->audioDecodedByteCount();
4342 unsigned HTMLMediaElement::webkitVideoDecodedByteCount() const
4346 return m_player->videoDecodedByteCount();
4350 void HTMLMediaElement::mediaCanStart()
4352 LOG(Media, "HTMLMediaElement::mediaCanStart");
4354 ASSERT(m_isWaitingUntilMediaCanStart);
4355 m_isWaitingUntilMediaCanStart = false;
4359 bool HTMLMediaElement::isURLAttribute(const Attribute& attribute) const
4361 return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute);
4364 void HTMLMediaElement::setShouldDelayLoadEvent(bool shouldDelay)
4366 if (m_shouldDelayLoadEvent == shouldDelay)
4369 LOG(Media, "HTMLMediaElement::setShouldDelayLoadEvent(%s)", boolString(shouldDelay));
4371 m_shouldDelayLoadEvent = shouldDelay;
4373 document()->incrementLoadEventDelayCount();
4375 document()->decrementLoadEventDelayCount();
4379 void HTMLMediaElement::getSitesInMediaCache(Vector<String>& sites)
4381 MediaPlayer::getSitesInMediaCache(sites);
4384 void HTMLMediaElement::clearMediaCache()
4386 MediaPlayer::clearMediaCache();
4389 void HTMLMediaElement::clearMediaCacheForSite(const String& site)
4391 MediaPlayer::clearMediaCacheForSite(site);
4394 void HTMLMediaElement::privateBrowsingStateDidChange()
4399 Settings* settings = document()->settings();
4400 bool privateMode = !settings || settings->privateBrowsingEnabled();
4401 LOG(Media, "HTMLMediaElement::privateBrowsingStateDidChange(%s)", boolString(privateMode));
4402 m_player->setPrivateBrowsingMode(privateMode);
4405 MediaControls* HTMLMediaElement::mediaControls() const
4407 return toMediaControls(userAgentShadowRoot()->firstChild());
4410 bool HTMLMediaElement::hasMediaControls() const
4412 if (ShadowRoot* userAgent = userAgentShadowRoot()) {
4413 Node* node = userAgent->firstChild();
4414 ASSERT(!node || node->isMediaControls());
4421 bool HTMLMediaElement::createMediaControls()
4423 if (hasMediaControls())
4427 RefPtr<MediaControls> controls = MediaControls::create(document());
4431 controls->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this));
4434 controls->enteredFullscreen();
4437 createShadowSubtree();
4439 ASSERT(userAgentShadowRoot());
4440 userAgentShadowRoot()->appendChild(controls, ec);
4444 void HTMLMediaElement::configureMediaControls()
4446 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO)
4447 if (!controls() || !inDocument()) {
4448 if (hasMediaControls())
4449 mediaControls()->hide();
4453 if (!hasMediaControls() && !createMediaControls())
4456 mediaControls()->show();
4459 m_player->setControls(controls());
4463 #if ENABLE(VIDEO_TRACK)
4464 void HTMLMediaElement::configureTextTrackDisplay()
4466 ASSERT(m_textTracks);
4468 bool haveVisibleTextTrack = false;
4469 for (unsigned i = 0; i < m_textTracks->length(); ++i) {
4470 if (m_textTracks->item(i)->mode() == TextTrack::showingKeyword()) {
4471 haveVisibleTextTrack = true;
4476 if (m_haveVisibleTextTrack == haveVisibleTextTrack)
4478 m_haveVisibleTextTrack = haveVisibleTextTrack;
4479 m_closedCaptionsVisible = m_haveVisibleTextTrack;
4481 if (!m_haveVisibleTextTrack && !hasMediaControls())
4483 if (!hasMediaControls() && !createMediaControls())
4486 updateClosedCaptionsControls();
4489 void HTMLMediaElement::updateClosedCaptionsControls()
4491 if (hasMediaControls()) {
4492 mediaControls()->changedClosedCaptionsVisibility();
4494 if (RuntimeEnabledFeatures::webkitVideoTrackEnabled())
4495 mediaControls()->updateTextTrackDisplay();
4500 void* HTMLMediaElement::preDispatchEventHandler(Event* event)
4502 if (event && event->type() == eventNames().webkitfullscreenchangeEvent) {
4503 configureMediaControls();
4504 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
4505 if (!isFullscreen())
4506 scheduleEvent(eventNames().webkitendfullscreenEvent);
4513 void HTMLMediaElement::createMediaPlayer()
4515 #if ENABLE(WEB_AUDIO)
4516 if (m_audioSourceNode)
4517 m_audioSourceNode->lock();
4520 m_player = MediaPlayer::create(this);
4522 #if ENABLE(WEB_AUDIO)
4523 if (m_audioSourceNode) {
4524 // When creating the player, make sure its AudioSourceProvider knows about the MediaElementAudioSourceNode.
4525 if (audioSourceProvider())
4526 audioSourceProvider()->setClient(m_audioSourceNode);
4528 m_audioSourceNode->unlock();
4533 #if ENABLE(WEB_AUDIO)
4534 void HTMLMediaElement::setAudioSourceNode(MediaElementAudioSourceNode* sourceNode)
4536 m_audioSourceNode = sourceNode;
4538 if (audioSourceProvider())
4539 audioSourceProvider()->setClient(m_audioSourceNode);
4542 AudioSourceProvider* HTMLMediaElement::audioSourceProvider()
4545 return m_player->audioSourceProvider();
4551 #if ENABLE(MICRODATA)
4552 String HTMLMediaElement::itemValueText() const
4554 return getURLAttribute(srcAttr);
4557 void HTMLMediaElement::setItemValueText(const String& value, ExceptionCode&)
4559 setAttribute(srcAttr, value);
4563 const String& HTMLMediaElement::mediaGroup() const
4565 return m_mediaGroup;
4568 void HTMLMediaElement::setMediaGroup(const String& group)
4570 if (m_mediaGroup == group)
4572 m_mediaGroup = group;
4574 // When a media element is created with a mediagroup attribute, and when a media element's mediagroup
4575 // attribute is set, changed, or removed, the user agent must run the following steps:
4576 // 1. Let m [this] be the media element in question.
4577 // 2. Let m have no current media controller, if it currently has one.
4580 // 3. If m's mediagroup attribute is being removed, then abort these steps.
4581 if (group.isNull() || group.isEmpty())
4584 // 4. If there is another media element whose Document is the same as m's Document (even if one or both
4585 // of these elements are not actually in the Document),
4586 HashSet<HTMLMediaElement*> elements = documentToElementSetMap().get(document());
4587 for (HashSet<HTMLMediaElement*>::iterator i = elements.begin(); i != elements.end(); ++i) {
4591 // and which also has a mediagroup attribute, and whose mediagroup attribute has the same value as
4592 // the new value of m's mediagroup attribute,
4593 if ((*i)->mediaGroup() == group) {
4594 // then let controller be that media element's current media controller.
4595 setController((*i)->controller());
4600 // Otherwise, let controller be a newly created MediaController.
4601 setController(MediaController::create(Node::scriptExecutionContext()));
4604 MediaController* HTMLMediaElement::controller() const
4606 return m_mediaController.get();
4609 void HTMLMediaElement::setController(PassRefPtr<MediaController> controller)
4611 if (m_mediaController)
4612 m_mediaController->removeMediaElement(this);
4614 m_mediaController = controller;
4616 if (m_mediaController)
4617 m_mediaController->addMediaElement(this);
4619 if (hasMediaControls())
4620 mediaControls()->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this));
4623 void HTMLMediaElement::updateMediaController()
4625 if (m_mediaController)
4626 m_mediaController->reportControllerState();
4629 bool HTMLMediaElement::dispatchEvent(PassRefPtr<Event> event)
4631 bool dispatchResult;
4632 bool isCanPlayEvent;
4634 isCanPlayEvent = (event->type() == eventNames().canplayEvent);
4637 m_dispatchingCanPlayEvent = true;
4639 dispatchResult = HTMLElement::dispatchEvent(event);
4642 m_dispatchingCanPlayEvent = false;
4644 return dispatchResult;
4647 bool HTMLMediaElement::isBlocked() const
4649 // A media element is a blocked media element if its readyState attribute is in the
4650 // HAVE_NOTHING state, the HAVE_METADATA state, or the HAVE_CURRENT_DATA state,
4651 if (m_readyState <= HAVE_CURRENT_DATA)
4654 // or if the element has paused for user interaction.
4655 return pausedForUserInteraction();
4658 bool HTMLMediaElement::isBlockedOnMediaController() const
4660 if (!m_mediaController)
4663 // A media element is blocked on its media controller if the MediaController is a blocked
4664 // media controller,
4665 if (m_mediaController->isBlocked())
4668 // or if its media controller position is either before the media resource's earliest possible
4669 // position relative to the MediaController's timeline or after the end of the media resource
4670 // relative to the MediaController's timeline.
4671 float mediaControllerPosition = m_mediaController->currentTime();
4672 if (mediaControllerPosition < startTime() || mediaControllerPosition > startTime() + duration())
4678 void HTMLMediaElement::prepareMediaFragmentURI()
4680 MediaFragmentURIParser fragmentParser(m_currentSrc);
4681 float dur = duration();
4683 double start = fragmentParser.startTime();
4684 if (start != MediaFragmentURIParser::invalidTimeValue() && start > 0) {
4685 m_fragmentStartTime = start;
4686 if (m_fragmentStartTime > dur)
4687 m_fragmentStartTime = dur;
4689 m_fragmentStartTime = MediaPlayer::invalidTime();
4691 double end = fragmentParser.endTime();
4692 if (end != MediaFragmentURIParser::invalidTimeValue() && end > 0 && end > m_fragmentStartTime) {
4693 m_fragmentEndTime = end;
4694 if (m_fragmentEndTime > dur)
4695 m_fragmentEndTime = dur;
4697 m_fragmentEndTime = MediaPlayer::invalidTime();
4699 if (m_fragmentStartTime != MediaPlayer::invalidTime() && m_readyState < HAVE_FUTURE_DATA)
4703 void HTMLMediaElement::applyMediaFragmentURI()
4705 if (m_fragmentStartTime != MediaPlayer::invalidTime()) {
4706 ExceptionCode ignoredException;
4707 m_sentEndEvent = false;
4708 seek(m_fragmentStartTime, ignoredException);
4713 void HTMLMediaElement::updateDisableSleep()
4715 if (!shouldDisableSleep() && m_sleepDisabler)
4716 m_sleepDisabler = nullptr;
4717 else if (shouldDisableSleep() && !m_sleepDisabler)
4718 m_sleepDisabler = DisplaySleepDisabler::create("com.apple.WebCore: HTMLMediaElement playback");
4721 bool HTMLMediaElement::shouldDisableSleep() const
4723 return m_player && !m_player->paused() && hasVideo() && hasAudio() && !loop();
4727 String HTMLMediaElement::mediaPlayerReferrer() const
4729 Frame* frame = document()->frame();
4733 return SecurityPolicy::generateReferrerHeader(document()->referrerPolicy(), m_currentSrc, frame->loader()->outgoingReferrer());
4736 String HTMLMediaElement::mediaPlayerUserAgent() const
4738 Frame* frame = document()->frame();
4742 return frame->loader()->userAgent(m_currentSrc);
4746 MediaPlayerClient::CORSMode HTMLMediaElement::mediaPlayerCORSMode() const
4748 if (!fastHasAttribute(HTMLNames::crossoriginAttr))
4750 if (equalIgnoringCase(fastGetAttribute(HTMLNames::crossoriginAttr), "use-credentials"))
4751 return UseCredentials;
4755 bool HTMLMediaElement::mediaPlayerNeedsSiteSpecificHacks() const
4757 Settings* settings = document()->settings();
4758 return settings && settings->needsSiteSpecificQuirks();
4761 String HTMLMediaElement::mediaPlayerDocumentHost() const
4763 return document()->url().host();
4766 void HTMLMediaElement::mediaPlayerExitFullscreen()
4771 bool HTMLMediaElement::mediaPlayerIsVideo() const
4776 LayoutRect HTMLMediaElement::mediaPlayerContentBoxRect() const
4779 return renderer()->enclosingBox()->contentBoxRect();
4780 return LayoutRect();
4783 void HTMLMediaElement::mediaPlayerSetSize(const IntSize& size)
4785 setAttribute(widthAttr, String::number(size.width()));
4786 setAttribute(heightAttr, String::number(size.height()));
4789 void HTMLMediaElement::mediaPlayerPause()
4794 void HTMLMediaElement::mediaPlayerPlay()
4799 bool HTMLMediaElement::mediaPlayerIsPaused() const
4804 bool HTMLMediaElement::mediaPlayerIsLooping() const
4809 HostWindow* HTMLMediaElement::mediaPlayerHostWindow()
4811 return mediaPlayerOwningDocument()->view()->hostWindow();
4814 IntRect HTMLMediaElement::mediaPlayerWindowClipRect()
4816 return mediaPlayerOwningDocument()->view()->windowClipRect();
4819 void HTMLMediaElement::removeBehaviorsRestrictionsAfterFirstUserGesture()
4821 m_restrictions = NoRestrictions;
4824 #if ENABLE(TIZEN_GSTREAMER_VIDEO)
4825 bool HTMLMediaElement::shouldSuspendMedia()
4833 #if ENABLE(TIZEN_EXTENSIBLE_API)
4834 if (!TizenExtensibleAPI::extensibleAPI().backgroundMusic())
4841 void HTMLMediaElement::setSuspended(bool suspended)
4843 m_suspended = suspended;