• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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.
24  */
25 
26 #include "config.h"
27 #include "core/html/HTMLMediaElement.h"
28 
29 #include "bindings/core/v8/ExceptionState.h"
30 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
31 #include "bindings/core/v8/ScriptController.h"
32 #include "bindings/core/v8/ScriptEventListener.h"
33 #include "core/HTMLNames.h"
34 #include "core/css/MediaList.h"
35 #include "core/dom/Attribute.h"
36 #include "core/dom/ElementTraversal.h"
37 #include "core/dom/ExceptionCode.h"
38 #include "core/dom/Fullscreen.h"
39 #include "core/dom/shadow/ShadowRoot.h"
40 #include "core/events/Event.h"
41 #include "core/frame/LocalFrame.h"
42 #include "core/frame/Settings.h"
43 #include "core/frame/UseCounter.h"
44 #include "core/frame/csp/ContentSecurityPolicy.h"
45 #include "core/html/HTMLMediaSource.h"
46 #include "core/html/HTMLSourceElement.h"
47 #include "core/html/HTMLTrackElement.h"
48 #include "core/html/MediaController.h"
49 #include "core/html/MediaError.h"
50 #include "core/html/MediaFragmentURIParser.h"
51 #include "core/html/TimeRanges.h"
52 #include "core/html/shadow/MediaControls.h"
53 #include "core/html/track/AudioTrack.h"
54 #include "core/html/track/AudioTrackList.h"
55 #include "core/html/track/InbandTextTrack.h"
56 #include "core/html/track/TextTrackCueList.h"
57 #include "core/html/track/TextTrackList.h"
58 #include "core/html/track/VideoTrack.h"
59 #include "core/html/track/VideoTrackList.h"
60 #include "core/loader/FrameLoader.h"
61 #include "core/rendering/RenderVideo.h"
62 #include "core/rendering/RenderView.h"
63 #include "core/rendering/compositing/RenderLayerCompositor.h"
64 #include "platform/ContentType.h"
65 #include "platform/Language.h"
66 #include "platform/Logging.h"
67 #include "platform/MIMETypeFromURL.h"
68 #include "platform/MIMETypeRegistry.h"
69 #include "platform/NotImplemented.h"
70 #include "platform/RuntimeEnabledFeatures.h"
71 #include "platform/UserGestureIndicator.h"
72 #include "platform/graphics/GraphicsLayer.h"
73 #include "platform/weborigin/SecurityOrigin.h"
74 #include "public/platform/Platform.h"
75 #include "public/platform/WebContentDecryptionModule.h"
76 #include "public/platform/WebInbandTextTrack.h"
77 #include "wtf/CurrentTime.h"
78 #include "wtf/MathExtras.h"
79 #include "wtf/NonCopyingSort.h"
80 #include "wtf/Uint8Array.h"
81 #include "wtf/text/CString.h"
82 #include <limits>
83 
84 #if ENABLE(WEB_AUDIO)
85 #include "platform/audio/AudioSourceProvider.h"
86 #include "platform/audio/AudioSourceProviderClient.h"
87 #endif
88 
89 using blink::WebInbandTextTrack;
90 using blink::WebMediaPlayer;
91 using blink::WebMimeRegistry;
92 using blink::WebMediaPlayerClient;
93 
94 namespace blink {
95 
96 #if !LOG_DISABLED
urlForLoggingMedia(const KURL & url)97 static String urlForLoggingMedia(const KURL& url)
98 {
99     static const unsigned maximumURLLengthForLogging = 128;
100 
101     if (url.string().length() < maximumURLLengthForLogging)
102         return url.string();
103     return url.string().substring(0, maximumURLLengthForLogging) + "...";
104 }
105 
boolString(bool val)106 static const char* boolString(bool val)
107 {
108     return val ? "true" : "false";
109 }
110 #endif
111 
112 #ifndef LOG_MEDIA_EVENTS
113 // Default to not logging events because so many are generated they can overwhelm the rest of
114 // the logging.
115 #define LOG_MEDIA_EVENTS 0
116 #endif
117 
118 #ifndef LOG_CACHED_TIME_WARNINGS
119 // Default to not logging warnings about excessive drift in the cached media time because it adds a
120 // fair amount of overhead and logging.
121 #define LOG_CACHED_TIME_WARNINGS 0
122 #endif
123 
124 // URL protocol used to signal that the media source API is being used.
125 static const char mediaSourceBlobProtocol[] = "blob";
126 
127 using namespace HTMLNames;
128 
129 typedef WillBeHeapHashSet<RawPtrWillBeWeakMember<HTMLMediaElement> > WeakMediaElementSet;
130 typedef WillBeHeapHashMap<RawPtrWillBeWeakMember<Document>, WeakMediaElementSet> DocumentElementSetMap;
documentToElementSetMap()131 static DocumentElementSetMap& documentToElementSetMap()
132 {
133     DEFINE_STATIC_LOCAL(OwnPtrWillBePersistent<DocumentElementSetMap>, map, (adoptPtrWillBeNoop(new DocumentElementSetMap())));
134     return *map;
135 }
136 
addElementToDocumentMap(HTMLMediaElement * element,Document * document)137 static void addElementToDocumentMap(HTMLMediaElement* element, Document* document)
138 {
139     DocumentElementSetMap& map = documentToElementSetMap();
140     WeakMediaElementSet set = map.take(document);
141     set.add(element);
142     map.add(document, set);
143 }
144 
removeElementFromDocumentMap(HTMLMediaElement * element,Document * document)145 static void removeElementFromDocumentMap(HTMLMediaElement* element, Document* document)
146 {
147     DocumentElementSetMap& map = documentToElementSetMap();
148     WeakMediaElementSet set = map.take(document);
149     set.remove(element);
150     if (!set.isEmpty())
151         map.add(document, set);
152 }
153 
154 class TrackDisplayUpdateScope {
155     STACK_ALLOCATED();
156 public:
TrackDisplayUpdateScope(HTMLMediaElement * mediaElement)157     TrackDisplayUpdateScope(HTMLMediaElement* mediaElement)
158     {
159         m_mediaElement = mediaElement;
160         m_mediaElement->beginIgnoringTrackDisplayUpdateRequests();
161     }
~TrackDisplayUpdateScope()162     ~TrackDisplayUpdateScope()
163     {
164         ASSERT(m_mediaElement);
165         m_mediaElement->endIgnoringTrackDisplayUpdateRequests();
166     }
167 
168 private:
169     RawPtrWillBeMember<HTMLMediaElement> m_mediaElement;
170 };
171 
172 class AudioSourceProviderClientLockScope {
173     STACK_ALLOCATED();
174 public:
175 #if ENABLE(WEB_AUDIO)
AudioSourceProviderClientLockScope(HTMLMediaElement & element)176     AudioSourceProviderClientLockScope(HTMLMediaElement& element)
177         : m_client(element.audioSourceNode())
178     {
179         if (m_client)
180             m_client->lock();
181     }
~AudioSourceProviderClientLockScope()182     ~AudioSourceProviderClientLockScope()
183     {
184         if (m_client)
185             m_client->unlock();
186     }
187 
188 private:
189     Member<AudioSourceProviderClient> m_client;
190 #else
191     explicit AudioSourceProviderClientLockScope(HTMLMediaElement&) { }
192     ~AudioSourceProviderClientLockScope() { }
193 #endif
194 };
195 
AudioKindToString(WebMediaPlayerClient::AudioTrackKind kind)196 static const AtomicString& AudioKindToString(WebMediaPlayerClient::AudioTrackKind kind)
197 {
198     switch (kind) {
199     case WebMediaPlayerClient::AudioTrackKindNone:
200         return emptyAtom;
201     case WebMediaPlayerClient::AudioTrackKindAlternative:
202         return AudioTrack::alternativeKeyword();
203     case WebMediaPlayerClient::AudioTrackKindDescriptions:
204         return AudioTrack::descriptionsKeyword();
205     case WebMediaPlayerClient::AudioTrackKindMain:
206         return AudioTrack::mainKeyword();
207     case WebMediaPlayerClient::AudioTrackKindMainDescriptions:
208         return AudioTrack::mainDescriptionsKeyword();
209     case WebMediaPlayerClient::AudioTrackKindTranslation:
210         return AudioTrack::translationKeyword();
211     case WebMediaPlayerClient::AudioTrackKindCommentary:
212         return AudioTrack::commentaryKeyword();
213     }
214 
215     ASSERT_NOT_REACHED();
216     return emptyAtom;
217 }
218 
VideoKindToString(WebMediaPlayerClient::VideoTrackKind kind)219 static const AtomicString& VideoKindToString(WebMediaPlayerClient::VideoTrackKind kind)
220 {
221     switch (kind) {
222     case WebMediaPlayerClient::VideoTrackKindNone:
223         return emptyAtom;
224     case WebMediaPlayerClient::VideoTrackKindAlternative:
225         return VideoTrack::alternativeKeyword();
226     case WebMediaPlayerClient::VideoTrackKindCaptions:
227         return VideoTrack::captionsKeyword();
228     case WebMediaPlayerClient::VideoTrackKindMain:
229         return VideoTrack::mainKeyword();
230     case WebMediaPlayerClient::VideoTrackKindSign:
231         return VideoTrack::signKeyword();
232     case WebMediaPlayerClient::VideoTrackKindSubtitles:
233         return VideoTrack::subtitlesKeyword();
234     case WebMediaPlayerClient::VideoTrackKindCommentary:
235         return VideoTrack::commentaryKeyword();
236     }
237 
238     ASSERT_NOT_REACHED();
239     return emptyAtom;
240 }
241 
canLoadURL(const KURL & url,const ContentType & contentType,const String & keySystem)242 static bool canLoadURL(const KURL& url, const ContentType& contentType, const String& keySystem)
243 {
244     DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
245 
246     String contentMIMEType = contentType.type().lower();
247     String contentTypeCodecs = contentType.parameter(codecs);
248 
249     // If the MIME type is missing or is not meaningful, try to figure it out from the URL.
250     if (contentMIMEType.isEmpty() || contentMIMEType == "application/octet-stream" || contentMIMEType == "text/plain") {
251         if (url.protocolIsData())
252             contentMIMEType = mimeTypeFromDataURL(url.string());
253     }
254 
255     // If no MIME type is specified, always attempt to load.
256     if (contentMIMEType.isEmpty())
257         return true;
258 
259     // 4.8.10.3 MIME types - In the absence of a specification to the contrary, the MIME type "application/octet-stream"
260     // when used with parameters, e.g. "application/octet-stream;codecs=theora", is a type that the user agent knows
261     // it cannot render.
262     if (contentMIMEType != "application/octet-stream" || contentTypeCodecs.isEmpty()) {
263         WebMimeRegistry::SupportsType supported = blink::Platform::current()->mimeRegistry()->supportsMediaMIMEType(contentMIMEType, contentTypeCodecs, keySystem.lower());
264         return supported > WebMimeRegistry::IsNotSupported;
265     }
266 
267     return false;
268 }
269 
supportsType(const ContentType & contentType,const String & keySystem)270 WebMimeRegistry::SupportsType HTMLMediaElement::supportsType(const ContentType& contentType, const String& keySystem)
271 {
272     DEFINE_STATIC_LOCAL(const String, codecs, ("codecs"));
273 
274     if (!RuntimeEnabledFeatures::mediaEnabled())
275         return WebMimeRegistry::IsNotSupported;
276 
277     String type = contentType.type().lower();
278     // The codecs string is not lower-cased because MP4 values are case sensitive
279     // per http://tools.ietf.org/html/rfc4281#page-7.
280     String typeCodecs = contentType.parameter(codecs);
281     String system = keySystem.lower();
282 
283     if (type.isEmpty())
284         return WebMimeRegistry::IsNotSupported;
285 
286     // 4.8.10.3 MIME types - The canPlayType(type) method must return the empty string if type is a type that the
287     // user agent knows it cannot render or is the type "application/octet-stream"
288     if (type == "application/octet-stream")
289         return WebMimeRegistry::IsNotSupported;
290 
291     return blink::Platform::current()->mimeRegistry()->supportsMediaMIMEType(type, typeCodecs, system);
292 }
293 
294 URLRegistry* HTMLMediaElement::s_mediaStreamRegistry = 0;
295 
setMediaStreamRegistry(URLRegistry * registry)296 void HTMLMediaElement::setMediaStreamRegistry(URLRegistry* registry)
297 {
298     ASSERT(!s_mediaStreamRegistry);
299     s_mediaStreamRegistry = registry;
300 }
301 
isMediaStreamURL(const String & url)302 bool HTMLMediaElement::isMediaStreamURL(const String& url)
303 {
304     return s_mediaStreamRegistry ? s_mediaStreamRegistry->contains(url) : false;
305 }
306 
HTMLMediaElement(const QualifiedName & tagName,Document & document)307 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document)
308     : HTMLElement(tagName, document)
309     , ActiveDOMObject(&document)
310     , m_loadTimer(this, &HTMLMediaElement::loadTimerFired)
311     , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired)
312     , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired)
313     , m_audioTracksTimer(this, &HTMLMediaElement::audioTracksTimerFired)
314     , m_playedTimeRanges()
315     , m_asyncEventQueue(GenericEventQueue::create(this))
316     , m_playbackRate(1.0f)
317     , m_defaultPlaybackRate(1.0f)
318     , m_networkState(NETWORK_EMPTY)
319     , m_readyState(HAVE_NOTHING)
320     , m_readyStateMaximum(HAVE_NOTHING)
321     , m_volume(1.0f)
322     , m_lastSeekTime(0)
323     , m_previousProgressTime(std::numeric_limits<double>::max())
324     , m_duration(std::numeric_limits<double>::quiet_NaN())
325     , m_lastTimeUpdateEventWallTime(0)
326     , m_lastTimeUpdateEventMovieTime(0)
327     , m_defaultPlaybackStartPosition(0)
328     , m_loadState(WaitingForSource)
329     , m_deferredLoadState(NotDeferred)
330     , m_deferredLoadTimer(this, &HTMLMediaElement::deferredLoadTimerFired)
331     , m_webLayer(0)
332     , m_preload(MediaPlayer::Auto)
333     , m_displayMode(Unknown)
334     , m_cachedTime(MediaPlayer::invalidTime())
335     , m_fragmentEndTime(MediaPlayer::invalidTime())
336     , m_pendingActionFlags(0)
337     , m_userGestureRequiredForPlay(false)
338     , m_playing(false)
339     , m_shouldDelayLoadEvent(false)
340     , m_haveFiredLoadedData(false)
341     , m_active(true)
342     , m_autoplaying(true)
343     , m_muted(false)
344     , m_paused(true)
345     , m_seeking(false)
346     , m_sentStalledEvent(false)
347     , m_sentEndEvent(false)
348     , m_closedCaptionsVisible(false)
349     , m_completelyLoaded(false)
350     , m_havePreparedToPlay(false)
351     , m_tracksAreReady(true)
352     , m_haveVisibleTextTrack(false)
353     , m_processingPreferenceChange(false)
354     , m_remoteRoutesAvailable(false)
355     , m_playingRemotely(false)
356 #if ENABLE(OILPAN)
357     , m_isFinalizing(false)
358     , m_closeMediaSourceWhenFinalizing(false)
359 #endif
360     , m_lastTextTrackUpdateTime(-1)
361     , m_audioTracks(AudioTrackList::create(*this))
362     , m_videoTracks(VideoTrackList::create(*this))
363     , m_textTracks(nullptr)
364     , m_ignoreTrackDisplayUpdate(0)
365 #if ENABLE(WEB_AUDIO)
366     , m_audioSourceNode(nullptr)
367 #endif
368 {
369     ASSERT(RuntimeEnabledFeatures::mediaEnabled());
370 
371     WTF_LOG(Media, "HTMLMediaElement::HTMLMediaElement(%p)", this);
372 
373     if (document.settings() && document.settings()->mediaPlaybackRequiresUserGesture())
374         m_userGestureRequiredForPlay = true;
375 
376     setHasCustomStyleCallbacks();
377     addElementToDocumentMap(this, &document);
378 }
379 
~HTMLMediaElement()380 HTMLMediaElement::~HTMLMediaElement()
381 {
382     WTF_LOG(Media, "HTMLMediaElement::~HTMLMediaElement(%p)", this);
383 
384 #if ENABLE(OILPAN)
385     // If the HTMLMediaElement dies with the document we are not
386     // allowed to touch the document to adjust delay load event counts
387     // because the document could have been already
388     // destructed. However, if the HTMLMediaElement dies with the
389     // document there is no need to change the delayed load counts
390     // because no load event will fire anyway. If the document is
391     // still alive we do have to decrement the load delay counts. We
392     // determine if the document is alive via the ActiveDOMObject
393     // which is a context lifecycle observer. If the Document has been
394     // destructed ActiveDOMObject::executionContext() returns 0.
395     if (ActiveDOMObject::executionContext())
396         setShouldDelayLoadEvent(false);
397 #else
398     // HTMLMediaElement and m_asyncEventQueue always become unreachable
399     // together. So HTMLMediaElemenet and m_asyncEventQueue are destructed in
400     // the same GC. We don't need to close it explicitly in Oilpan.
401     m_asyncEventQueue->close();
402 
403     setShouldDelayLoadEvent(false);
404 
405     if (m_textTracks)
406         m_textTracks->clearOwner();
407     m_audioTracks->shutdown();
408     m_videoTracks->shutdown();
409 
410     if (m_mediaController) {
411         m_mediaController->removeMediaElement(this);
412         m_mediaController = nullptr;
413     }
414 #endif
415 
416 #if ENABLE(OILPAN)
417     if (m_closeMediaSourceWhenFinalizing)
418         closeMediaSource();
419 #else
420     closeMediaSource();
421 
422     removeElementFromDocumentMap(this, &document());
423 #endif
424 
425     // Destroying the player may cause a resource load to be canceled,
426     // which could result in userCancelledLoad() being called back.
427     // Setting m_completelyLoaded ensures that such a call will not cause
428     // us to dispatch an abort event, which would result in a crash.
429     // See http://crbug.com/233654 for more details.
430     m_completelyLoaded = true;
431 
432     // With Oilpan load events on the Document are always delayed during
433     // sweeping so we don't need to explicitly increment and decrement
434     // load event delay counts.
435 #if !ENABLE(OILPAN)
436     // Destroying the player may cause a resource load to be canceled,
437     // which could result in Document::dispatchWindowLoadEvent() being
438     // called via ResourceFetch::didLoadResource() then
439     // FrameLoader::loadDone(). To prevent load event dispatching during
440     // object destruction, we use Document::incrementLoadEventDelayCount().
441     // See http://crbug.com/275223 for more details.
442     document().incrementLoadEventDelayCount();
443 #endif
444 
445 #if ENABLE(OILPAN)
446     // Oilpan: the player must be released, but the player object
447     // cannot safely access this player client any longer as parts of
448     // it may have been finalized already (like the media element's
449     // supplementable table.)  Handled for now by entering an
450     // is-finalizing state, which is explicitly checked for if the
451     // player tries to access the media element during shutdown.
452     //
453     // FIXME: Oilpan: move the media player to the heap instead and
454     // avoid having to finalize it from here; this whole #if block
455     // could then be removed (along with the state bit it depends on.)
456     // crbug.com/378229
457     m_isFinalizing = true;
458 #endif
459 
460     // m_audioSourceNode is explicitly cleared by AudioNode::dispose().
461     // Since AudioNode::dispose() is guaranteed to be always called before
462     // the AudioNode is destructed, m_audioSourceNode is explicitly cleared
463     // even if the AudioNode and the HTMLMediaElement die together.
464 #if ENABLE(WEB_AUDIO)
465     ASSERT(!m_audioSourceNode);
466 #endif
467     clearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
468 
469 #if !ENABLE(OILPAN)
470     document().decrementLoadEventDelayCount();
471 #endif
472 }
473 
474 #if ENABLE(OILPAN)
setCloseMediaSourceWhenFinalizing()475 void HTMLMediaElement::setCloseMediaSourceWhenFinalizing()
476 {
477     ASSERT(!m_closeMediaSourceWhenFinalizing);
478     m_closeMediaSourceWhenFinalizing = true;
479 }
480 #endif
481 
didMoveToNewDocument(Document & oldDocument)482 void HTMLMediaElement::didMoveToNewDocument(Document& oldDocument)
483 {
484     WTF_LOG(Media, "HTMLMediaElement::didMoveToNewDocument(%p)", this);
485 
486     if (m_shouldDelayLoadEvent) {
487         document().incrementLoadEventDelayCount();
488         // Note: Keeping the load event delay count increment on oldDocument that was added
489         // when m_shouldDelayLoadEvent was set so that destruction of m_player can not
490         // cause load event dispatching in oldDocument.
491     } else {
492         // Incrementing the load event delay count so that destruction of m_player can not
493         // cause load event dispatching in oldDocument.
494         oldDocument.incrementLoadEventDelayCount();
495     }
496 
497     removeElementFromDocumentMap(this, &oldDocument);
498     addElementToDocumentMap(this, &document());
499 
500     // FIXME: This is a temporary fix to prevent this object from causing the
501     // MediaPlayer to dereference LocalFrame and FrameLoader pointers from the
502     // previous document. A proper fix would provide a mechanism to allow this
503     // object to refresh the MediaPlayer's LocalFrame and FrameLoader references on
504     // document changes so that playback can be resumed properly.
505     userCancelledLoad();
506 
507     // Decrement the load event delay count on oldDocument now that m_player has been destroyed
508     // and there is no risk of dispatching a load event from within the destructor.
509     oldDocument.decrementLoadEventDelayCount();
510 
511     ActiveDOMObject::didMoveToNewExecutionContext(&document());
512     HTMLElement::didMoveToNewDocument(oldDocument);
513 }
514 
supportsFocus() const515 bool HTMLMediaElement::supportsFocus() const
516 {
517     if (ownerDocument()->isMediaDocument())
518         return false;
519 
520     // If no controls specified, we should still be able to focus the element if it has tabIndex.
521     return shouldShowControls() || HTMLElement::supportsFocus();
522 }
523 
isMouseFocusable() const524 bool HTMLMediaElement::isMouseFocusable() const
525 {
526     return false;
527 }
528 
parseAttribute(const QualifiedName & name,const AtomicString & value)529 void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
530 {
531     if (name == srcAttr) {
532         // Trigger a reload, as long as the 'src' attribute is present.
533         if (!value.isNull()) {
534             clearMediaPlayer(LoadMediaResource);
535             scheduleDelayedAction(LoadMediaResource);
536         }
537     } else if (name == controlsAttr) {
538         configureMediaControls();
539     } else if (name == preloadAttr) {
540         if (equalIgnoringCase(value, "none")) {
541             m_preload = MediaPlayer::None;
542         } else if (equalIgnoringCase(value, "metadata")) {
543             m_preload = MediaPlayer::MetaData;
544         } else {
545             // The spec does not define an "invalid value default" but "auto" is suggested as the
546             // "missing value default", so use it for everything except "none" and "metadata"
547             m_preload = MediaPlayer::Auto;
548         }
549 
550         // The attribute must be ignored if the autoplay attribute is present
551         if (!autoplay() && m_player)
552             setPlayerPreload();
553 
554     } else if (name == mediagroupAttr && RuntimeEnabledFeatures::mediaControllerEnabled()) {
555         setMediaGroup(value);
556     } else {
557         HTMLElement::parseAttribute(name, value);
558     }
559 }
560 
finishParsingChildren()561 void HTMLMediaElement::finishParsingChildren()
562 {
563     HTMLElement::finishParsingChildren();
564 
565     if (Traversal<HTMLTrackElement>::firstChild(*this))
566         scheduleDelayedAction(LoadTextTrackResource);
567 }
568 
rendererIsNeeded(const RenderStyle & style)569 bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style)
570 {
571     return shouldShowControls() && HTMLElement::rendererIsNeeded(style);
572 }
573 
createRenderer(RenderStyle *)574 RenderObject* HTMLMediaElement::createRenderer(RenderStyle*)
575 {
576     return new RenderMedia(this);
577 }
578 
insertedInto(ContainerNode * insertionPoint)579 Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode* insertionPoint)
580 {
581     WTF_LOG(Media, "HTMLMediaElement::insertedInto(%p, %p)", this, insertionPoint);
582 
583     HTMLElement::insertedInto(insertionPoint);
584     if (insertionPoint->inDocument()) {
585         m_active = true;
586 
587         if (!getAttribute(srcAttr).isEmpty() && m_networkState == NETWORK_EMPTY)
588             scheduleDelayedAction(LoadMediaResource);
589     }
590 
591     return InsertionShouldCallDidNotifySubtreeInsertions;
592 }
593 
didNotifySubtreeInsertionsToDocument()594 void HTMLMediaElement::didNotifySubtreeInsertionsToDocument()
595 {
596     configureMediaControls();
597 }
598 
removedFrom(ContainerNode * insertionPoint)599 void HTMLMediaElement::removedFrom(ContainerNode* insertionPoint)
600 {
601     WTF_LOG(Media, "HTMLMediaElement::removedFrom(%p, %p)", this, insertionPoint);
602 
603     m_active = false;
604     if (insertionPoint->inDocument() && insertionPoint->document().isActive()) {
605         configureMediaControls();
606         if (m_networkState > NETWORK_EMPTY)
607             pause();
608     }
609 
610     HTMLElement::removedFrom(insertionPoint);
611 }
612 
attach(const AttachContext & context)613 void HTMLMediaElement::attach(const AttachContext& context)
614 {
615     HTMLElement::attach(context);
616 
617     if (renderer())
618         renderer()->updateFromElement();
619 }
620 
didRecalcStyle(StyleRecalcChange)621 void HTMLMediaElement::didRecalcStyle(StyleRecalcChange)
622 {
623     if (renderer())
624         renderer()->updateFromElement();
625 }
626 
scheduleDelayedAction(DelayedActionType actionType)627 void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType)
628 {
629     WTF_LOG(Media, "HTMLMediaElement::scheduleDelayedAction(%p)", this);
630 
631     if ((actionType & LoadMediaResource) && !(m_pendingActionFlags & LoadMediaResource)) {
632         prepareForLoad();
633         m_pendingActionFlags |= LoadMediaResource;
634     }
635 
636     if (actionType & LoadTextTrackResource)
637         m_pendingActionFlags |= LoadTextTrackResource;
638 
639     if (!m_loadTimer.isActive())
640         m_loadTimer.startOneShot(0, FROM_HERE);
641 }
642 
scheduleNextSourceChild()643 void HTMLMediaElement::scheduleNextSourceChild()
644 {
645     // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
646     m_pendingActionFlags |= LoadMediaResource;
647     m_loadTimer.startOneShot(0, FROM_HERE);
648 }
649 
scheduleEvent(const AtomicString & eventName)650 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
651 {
652     scheduleEvent(Event::createCancelable(eventName));
653 }
654 
scheduleEvent(PassRefPtrWillBeRawPtr<Event> event)655 void HTMLMediaElement::scheduleEvent(PassRefPtrWillBeRawPtr<Event> event)
656 {
657 #if LOG_MEDIA_EVENTS
658     WTF_LOG(Media, "HTMLMediaElement::scheduleEvent(%p) - scheduling '%s'", this, event->type().ascii().data());
659 #endif
660     m_asyncEventQueue->enqueueEvent(event);
661 }
662 
loadTimerFired(Timer<HTMLMediaElement> *)663 void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*)
664 {
665     if (m_pendingActionFlags & LoadTextTrackResource)
666         configureTextTracks();
667 
668     if (m_pendingActionFlags & LoadMediaResource) {
669         if (m_loadState == LoadingFromSourceElement)
670             loadNextSourceChild();
671         else
672             loadInternal();
673     }
674 
675     m_pendingActionFlags = 0;
676 }
677 
error() const678 PassRefPtrWillBeRawPtr<MediaError> HTMLMediaElement::error() const
679 {
680     return m_error;
681 }
682 
setSrc(const AtomicString & url)683 void HTMLMediaElement::setSrc(const AtomicString& url)
684 {
685     setAttribute(srcAttr, url);
686 }
687 
networkState() const688 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
689 {
690     return m_networkState;
691 }
692 
canPlayType(const String & mimeType,const String & keySystem) const693 String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySystem) const
694 {
695     if (!keySystem.isNull())
696         UseCounter::count(document(), UseCounter::CanPlayTypeKeySystem);
697 
698     WebMimeRegistry::SupportsType support = supportsType(ContentType(mimeType), keySystem);
699     String canPlay;
700 
701     // 4.8.10.3
702     switch (support) {
703     case WebMimeRegistry::IsNotSupported:
704         canPlay = emptyString();
705         break;
706     case WebMimeRegistry::MayBeSupported:
707         canPlay = "maybe";
708         break;
709     case WebMimeRegistry::IsSupported:
710         canPlay = "probably";
711         break;
712     }
713 
714     WTF_LOG(Media, "HTMLMediaElement::canPlayType(%p, %s, %s) -> %s", this, mimeType.utf8().data(), keySystem.utf8().data(), canPlay.utf8().data());
715 
716     return canPlay;
717 }
718 
load()719 void HTMLMediaElement::load()
720 {
721     WTF_LOG(Media, "HTMLMediaElement::load(%p)", this);
722 
723     if (UserGestureIndicator::processingUserGesture())
724         m_userGestureRequiredForPlay = false;
725 
726     prepareForLoad();
727     loadInternal();
728     prepareToPlay();
729 }
730 
prepareForLoad()731 void HTMLMediaElement::prepareForLoad()
732 {
733     WTF_LOG(Media, "HTMLMediaElement::prepareForLoad(%p)", this);
734 
735     // Perform the cleanup required for the resource load algorithm to run.
736     stopPeriodicTimers();
737     m_loadTimer.stop();
738     cancelDeferredLoad();
739     // FIXME: Figure out appropriate place to reset LoadTextTrackResource if necessary and set m_pendingActionFlags to 0 here.
740     m_pendingActionFlags &= ~LoadMediaResource;
741     m_sentEndEvent = false;
742     m_sentStalledEvent = false;
743     m_haveFiredLoadedData = false;
744     m_completelyLoaded = false;
745     m_havePreparedToPlay = false;
746     m_displayMode = Unknown;
747 
748     // 1 - Abort any already-running instance of the resource selection algorithm for this element.
749     m_loadState = WaitingForSource;
750     m_currentSourceNode = nullptr;
751 
752     // 2 - If there are any tasks from the media element's media element event task source in
753     // one of the task queues, then remove those tasks.
754     cancelPendingEventsAndCallbacks();
755 
756     // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
757     // a task to fire a simple event named abort at the media element.
758     if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
759         scheduleEvent(EventTypeNames::abort);
760 
761     createMediaPlayer();
762 
763     // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
764     if (m_networkState != NETWORK_EMPTY) {
765         // 4.1 - Queue a task to fire a simple event named emptied at the media element.
766         scheduleEvent(EventTypeNames::emptied);
767 
768         // 4.2 - If a fetching process is in progress for the media element, the user agent should stop it.
769         m_networkState = NETWORK_EMPTY;
770 
771         // 4.3 - Forget the media element's media-resource-specific tracks.
772         forgetResourceSpecificTracks();
773 
774         // 4.4 - If readyState is not set to HAVE_NOTHING, then set it to that state.
775         m_readyState = HAVE_NOTHING;
776         m_readyStateMaximum = HAVE_NOTHING;
777 
778         // 4.5 - If the paused attribute is false, then set it to true.
779         m_paused = true;
780 
781         // 4.6 - If seeking is true, set it to false.
782         m_seeking = false;
783 
784         // 4.7 - Set the current playback position to 0.
785         //       Set the official playback position to 0.
786         //       If this changed the official playback position, then queue a task to fire a simple event named timeupdate at the media element.
787         // FIXME: Add support for firing this event.
788 
789         // 4.8 - Set the initial playback position to 0.
790         // FIXME: Make this less subtle. The position only becomes 0 because the ready state is HAVE_NOTHING.
791         invalidateCachedTime();
792 
793         // 4.9 - Set the timeline offset to Not-a-Number (NaN).
794         // 4.10 - Update the duration attribute to Not-a-Number (NaN).
795 
796 
797         updateMediaController();
798         updateActiveTextTrackCues(0);
799     }
800 
801     // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
802     setPlaybackRate(defaultPlaybackRate());
803 
804     // 6 - Set the error attribute to null and the autoplaying flag to true.
805     m_error = nullptr;
806     m_autoplaying = true;
807 
808     // 7 - Invoke the media element's resource selection algorithm.
809 
810     // 8 - Note: Playback of any previously playing media resource for this element stops.
811 
812     // The resource selection algorithm
813     // 1 - Set the networkState to NETWORK_NO_SOURCE
814     m_networkState = NETWORK_NO_SOURCE;
815 
816     // 2 - Asynchronously await a stable state.
817 
818     m_playedTimeRanges = TimeRanges::create();
819 
820     // FIXME: Investigate whether these can be moved into m_networkState != NETWORK_EMPTY block above
821     // so they are closer to the relevant spec steps.
822     m_lastSeekTime = 0;
823     m_duration = std::numeric_limits<double>::quiet_NaN();
824 
825     // The spec doesn't say to block the load event until we actually run the asynchronous section
826     // algorithm, but do it now because we won't start that until after the timer fires and the
827     // event may have already fired by then.
828     setShouldDelayLoadEvent(true);
829     if (hasMediaControls())
830         mediaControls()->reset();
831 }
832 
loadInternal()833 void HTMLMediaElement::loadInternal()
834 {
835     // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the
836     // disabled state when the element's resource selection algorithm last started".
837     m_textTracksWhenResourceSelectionBegan.clear();
838     if (m_textTracks) {
839         for (unsigned i = 0; i < m_textTracks->length(); ++i) {
840             TextTrack* track = m_textTracks->item(i);
841             if (track->mode() != TextTrack::disabledKeyword())
842                 m_textTracksWhenResourceSelectionBegan.append(track);
843         }
844     }
845 
846     selectMediaResource();
847 }
848 
selectMediaResource()849 void HTMLMediaElement::selectMediaResource()
850 {
851     WTF_LOG(Media, "HTMLMediaElement::selectMediaResource(%p)", this);
852 
853     enum Mode { attribute, children };
854 
855     // 3 - If the media element has a src attribute, then let mode be attribute.
856     Mode mode = attribute;
857     if (!fastHasAttribute(srcAttr)) {
858         // Otherwise, if the media element does not have a src attribute but has a source
859         // element child, then let mode be children and let candidate be the first such
860         // source element child in tree order.
861         if (HTMLSourceElement* element = Traversal<HTMLSourceElement>::firstChild(*this)) {
862             mode = children;
863             m_nextChildNodeToConsider = element;
864             m_currentSourceNode = nullptr;
865         } else {
866             // Otherwise the media element has neither a src attribute nor a source element
867             // child: set the networkState to NETWORK_EMPTY, and abort these steps; the
868             // synchronous section ends.
869             m_loadState = WaitingForSource;
870             setShouldDelayLoadEvent(false);
871             m_networkState = NETWORK_EMPTY;
872 
873             WTF_LOG(Media, "HTMLMediaElement::selectMediaResource(%p), nothing to load", this);
874             return;
875         }
876     }
877 
878     // 4 - Set the media element's delaying-the-load-event flag to true (this delays the load event),
879     // and set its networkState to NETWORK_LOADING.
880     setShouldDelayLoadEvent(true);
881     m_networkState = NETWORK_LOADING;
882 
883     // 5 - Queue a task to fire a simple event named loadstart at the media element.
884     scheduleEvent(EventTypeNames::loadstart);
885 
886     // 6 - If mode is attribute, then run these substeps
887     if (mode == attribute) {
888         m_loadState = LoadingFromSrcAttr;
889 
890         // If the src attribute's value is the empty string ... jump down to the failed step below
891         KURL mediaURL = getNonEmptyURLAttribute(srcAttr);
892         if (mediaURL.isEmpty()) {
893             mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError);
894             WTF_LOG(Media, "HTMLMediaElement::selectMediaResource(%p), empty 'src'", this);
895             return;
896         }
897 
898         if (!isSafeToLoadURL(mediaURL, Complain)) {
899             mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError);
900             return;
901         }
902 
903         // No type or key system information is available when the url comes
904         // from the 'src' attribute so MediaPlayer
905         // will have to pick a media engine based on the file extension.
906         ContentType contentType((String()));
907         loadResource(mediaURL, contentType, String());
908         WTF_LOG(Media, "HTMLMediaElement::selectMediaResource(%p), using 'src' attribute url", this);
909         return;
910     }
911 
912     // Otherwise, the source elements will be used
913     loadNextSourceChild();
914 }
915 
loadNextSourceChild()916 void HTMLMediaElement::loadNextSourceChild()
917 {
918     ContentType contentType((String()));
919     String keySystem;
920     KURL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain);
921     if (!mediaURL.isValid()) {
922         waitForSourceChange();
923         return;
924     }
925 
926     // Recreate the media player for the new url
927     createMediaPlayer();
928 
929     m_loadState = LoadingFromSourceElement;
930     loadResource(mediaURL, contentType, keySystem);
931 }
932 
loadResource(const KURL & url,ContentType & contentType,const String & keySystem)933 void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType, const String& keySystem)
934 {
935     ASSERT(isSafeToLoadURL(url, Complain));
936 
937     WTF_LOG(Media, "HTMLMediaElement::loadResource(%p, %s, %s, %s)", this, urlForLoggingMedia(url).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data());
938 
939     LocalFrame* frame = document().frame();
940     if (!frame) {
941         mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError);
942         return;
943     }
944 
945     // The resource fetch algorithm
946     m_networkState = NETWORK_LOADING;
947 
948     // Set m_currentSrc *before* changing to the cache url, the fact that we are loading from the app
949     // cache is an internal detail not exposed through the media element API.
950     m_currentSrc = url;
951 
952     WTF_LOG(Media, "HTMLMediaElement::loadResource(%p) - m_currentSrc -> %s", this, urlForLoggingMedia(m_currentSrc).utf8().data());
953 
954     startProgressEventTimer();
955 
956     // Reset display mode to force a recalculation of what to show because we are resetting the player.
957     setDisplayMode(Unknown);
958 
959     if (!autoplay())
960         setPlayerPreload();
961 
962     if (fastHasAttribute(mutedAttr))
963         m_muted = true;
964     updateVolume();
965 
966     ASSERT(!m_mediaSource);
967 
968     bool attemptLoad = true;
969 
970     if (url.protocolIs(mediaSourceBlobProtocol)) {
971         if (isMediaStreamURL(url.string())) {
972             m_userGestureRequiredForPlay = false;
973         } else {
974             m_mediaSource = HTMLMediaSource::lookup(url.string());
975 
976             if (m_mediaSource) {
977                 if (!m_mediaSource->attachToElement(this)) {
978                     // Forget our reference to the MediaSource, so we leave it alone
979                     // while processing remainder of load failure.
980                     m_mediaSource = nullptr;
981                     attemptLoad = false;
982                 }
983             }
984         }
985     }
986 
987     if (attemptLoad && canLoadURL(url, contentType, keySystem)) {
988         ASSERT(!webMediaPlayer());
989 
990         if (!m_havePreparedToPlay && !autoplay() && m_preload == MediaPlayer::None) {
991             WTF_LOG(Media, "HTMLMediaElement::loadResource(%p) : Delaying load because preload == 'none'", this);
992             deferLoad();
993         } else {
994             startPlayerLoad();
995         }
996     } else {
997         mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError);
998     }
999 
1000     // If there is no poster to display, allow the media engine to render video frames as soon as
1001     // they are available.
1002     updateDisplayState();
1003 
1004     if (renderer())
1005         renderer()->updateFromElement();
1006 }
1007 
startPlayerLoad()1008 void HTMLMediaElement::startPlayerLoad()
1009 {
1010     // Filter out user:pass as those two URL components aren't
1011     // considered for media resource fetches (including for the CORS
1012     // use-credentials mode.) That behavior aligns with Gecko, with IE
1013     // being more restrictive and not allowing fetches to such URLs.
1014     //
1015     // Spec reference: http://whatwg.org/c/#concept-media-load-resource
1016     //
1017     // FIXME: when the HTML spec switches to specifying resource
1018     // fetches in terms of Fetch (http://fetch.spec.whatwg.org), and
1019     // along with that potentially also specifying a setting for its
1020     // 'authentication flag' to control how user:pass embedded in a
1021     // media resource URL should be treated, then update the handling
1022     // here to match.
1023     KURL requestURL = m_currentSrc;
1024     if (!requestURL.user().isEmpty())
1025         requestURL.setUser(String());
1026     if (!requestURL.pass().isEmpty())
1027         requestURL.setPass(String());
1028 
1029     m_player->load(loadType(), requestURL, corsMode());
1030 }
1031 
setPlayerPreload()1032 void HTMLMediaElement::setPlayerPreload()
1033 {
1034     m_player->setPreload(m_preload);
1035 
1036     if (loadIsDeferred() && m_preload != MediaPlayer::None)
1037         startDeferredLoad();
1038 }
1039 
loadIsDeferred() const1040 bool HTMLMediaElement::loadIsDeferred() const
1041 {
1042     return m_deferredLoadState != NotDeferred;
1043 }
1044 
deferLoad()1045 void HTMLMediaElement::deferLoad()
1046 {
1047     // This implements the "optional" step 3 from the resource fetch algorithm.
1048     ASSERT(!m_deferredLoadTimer.isActive());
1049     ASSERT(m_deferredLoadState == NotDeferred);
1050     // 1. Set the networkState to NETWORK_IDLE.
1051     // 2. Queue a task to fire a simple event named suspend at the element.
1052     changeNetworkStateFromLoadingToIdle();
1053     // 3. Queue a task to set the element's delaying-the-load-event
1054     // flag to false. This stops delaying the load event.
1055     m_deferredLoadTimer.startOneShot(0, FROM_HERE);
1056     // 4. Wait for the task to be run.
1057     m_deferredLoadState = WaitingForStopDelayingLoadEventTask;
1058     // Continued in executeDeferredLoad().
1059 }
1060 
cancelDeferredLoad()1061 void HTMLMediaElement::cancelDeferredLoad()
1062 {
1063     m_deferredLoadTimer.stop();
1064     m_deferredLoadState = NotDeferred;
1065 }
1066 
executeDeferredLoad()1067 void HTMLMediaElement::executeDeferredLoad()
1068 {
1069     ASSERT(m_deferredLoadState >= WaitingForTrigger);
1070 
1071     // resource fetch algorithm step 3 - continued from deferLoad().
1072 
1073     // 5. Wait for an implementation-defined event (e.g. the user requesting that the media element begin playback).
1074     // This is assumed to be whatever 'event' ended up calling this method.
1075     cancelDeferredLoad();
1076     // 6. Set the element's delaying-the-load-event flag back to true (this
1077     // delays the load event again, in case it hasn't been fired yet).
1078     setShouldDelayLoadEvent(true);
1079     // 7. Set the networkState to NETWORK_LOADING.
1080     m_networkState = NETWORK_LOADING;
1081 
1082     startProgressEventTimer();
1083 
1084     startPlayerLoad();
1085 }
1086 
startDeferredLoad()1087 void HTMLMediaElement::startDeferredLoad()
1088 {
1089     if (m_deferredLoadState == WaitingForTrigger) {
1090         executeDeferredLoad();
1091         return;
1092     }
1093     ASSERT(m_deferredLoadState == WaitingForStopDelayingLoadEventTask);
1094     m_deferredLoadState = ExecuteOnStopDelayingLoadEventTask;
1095 }
1096 
deferredLoadTimerFired(Timer<HTMLMediaElement> *)1097 void HTMLMediaElement::deferredLoadTimerFired(Timer<HTMLMediaElement>*)
1098 {
1099     setShouldDelayLoadEvent(false);
1100 
1101     if (m_deferredLoadState == ExecuteOnStopDelayingLoadEventTask) {
1102         executeDeferredLoad();
1103         return;
1104     }
1105     ASSERT(m_deferredLoadState == WaitingForStopDelayingLoadEventTask);
1106     m_deferredLoadState = WaitingForTrigger;
1107 }
1108 
loadType() const1109 WebMediaPlayer::LoadType HTMLMediaElement::loadType() const
1110 {
1111     if (m_mediaSource)
1112         return WebMediaPlayer::LoadTypeMediaSource;
1113 
1114     if (isMediaStreamURL(m_currentSrc.string()))
1115         return WebMediaPlayer::LoadTypeMediaStream;
1116 
1117     return WebMediaPlayer::LoadTypeURL;
1118 }
1119 
trackIndexCompare(TextTrack * a,TextTrack * b)1120 static bool trackIndexCompare(TextTrack* a,
1121                               TextTrack* b)
1122 {
1123     return a->trackIndex() - b->trackIndex() < 0;
1124 }
1125 
eventTimeCueCompare(const std::pair<double,TextTrackCue * > & a,const std::pair<double,TextTrackCue * > & b)1126 static bool eventTimeCueCompare(const std::pair<double, TextTrackCue*>& a,
1127                                 const std::pair<double, TextTrackCue*>& b)
1128 {
1129     // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1130     // times first).
1131     if (a.first != b.first)
1132         return a.first - b.first < 0;
1133 
1134     // If the cues belong to different text tracks, it doesn't make sense to
1135     // compare the two tracks by the relative cue order, so return the relative
1136     // track order.
1137     if (a.second->track() != b.second->track())
1138         return trackIndexCompare(a.second->track(), b.second->track());
1139 
1140     // 12 - Further sort tasks in events that have the same time by the
1141     // relative text track cue order of the text track cues associated
1142     // with these tasks.
1143     return a.second->cueIndex() - b.second->cueIndex() < 0;
1144 }
1145 
1146 
updateActiveTextTrackCues(double movieTime)1147 void HTMLMediaElement::updateActiveTextTrackCues(double movieTime)
1148 {
1149     // 4.8.10.8 Playing the media resource
1150 
1151     //  If the current playback position changes while the steps are running,
1152     //  then the user agent must wait for the steps to complete, and then must
1153     //  immediately rerun the steps.
1154     if (ignoreTrackDisplayUpdateRequests())
1155         return;
1156 
1157     // 1 - Let current cues be a list of cues, initialized to contain all the
1158     // cues of all the hidden, showing, or showing by default text tracks of the
1159     // media element (not the disabled ones) whose start times are less than or
1160     // equal to the current playback position and whose end times are greater
1161     // than the current playback position.
1162     CueList currentCues;
1163 
1164     // The user agent must synchronously unset [the text track cue active] flag
1165     // whenever ... the media element's readyState is changed back to HAVE_NOTHING.
1166     if (m_readyState != HAVE_NOTHING && m_player)
1167         currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime));
1168 
1169     CueList previousCues;
1170     CueList missedCues;
1171 
1172     // 2 - Let other cues be a list of cues, initialized to contain all the cues
1173     // of hidden, showing, and showing by default text tracks of the media
1174     // element that are not present in current cues.
1175     previousCues = m_currentlyActiveCues;
1176 
1177     // 3 - Let last time be the current playback position at the time this
1178     // algorithm was last run for this media element, if this is not the first
1179     // time it has run.
1180     double lastTime = m_lastTextTrackUpdateTime;
1181 
1182     // 4 - If the current playback position has, since the last time this
1183     // algorithm was run, only changed through its usual monotonic increase
1184     // during normal playback, then let missed cues be the list of cues in other
1185     // cues whose start times are greater than or equal to last time and whose
1186     // end times are less than or equal to the current playback position.
1187     // Otherwise, let missed cues be an empty list.
1188     if (lastTime >= 0 && m_lastSeekTime < movieTime) {
1189         CueList potentiallySkippedCues =
1190             m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime));
1191 
1192         for (size_t i = 0; i < potentiallySkippedCues.size(); ++i) {
1193             double cueStartTime = potentiallySkippedCues[i].low();
1194             double cueEndTime = potentiallySkippedCues[i].high();
1195 
1196             // Consider cues that may have been missed since the last seek time.
1197             if (cueStartTime > std::max(m_lastSeekTime, lastTime) && cueEndTime < movieTime)
1198                 missedCues.append(potentiallySkippedCues[i]);
1199         }
1200     }
1201 
1202     m_lastTextTrackUpdateTime = movieTime;
1203 
1204     // 5 - If the time was reached through the usual monotonic increase of the
1205     // current playback position during normal playback, and if the user agent
1206     // has not fired a timeupdate event at the element in the past 15 to 250ms
1207     // and is not still running event handlers for such an event, then the user
1208     // agent must queue a task to fire a simple event named timeupdate at the
1209     // element. (In the other cases, such as explicit seeks, relevant events get
1210     // fired as part of the overall process of changing the current playback
1211     // position.)
1212     if (!m_seeking && m_lastSeekTime < lastTime)
1213         scheduleTimeupdateEvent(true);
1214 
1215     // Explicitly cache vector sizes, as their content is constant from here.
1216     size_t currentCuesSize = currentCues.size();
1217     size_t missedCuesSize = missedCues.size();
1218     size_t previousCuesSize = previousCues.size();
1219 
1220     // 6 - If all of the cues in current cues have their text track cue active
1221     // flag set, none of the cues in other cues have their text track cue active
1222     // flag set, and missed cues is empty, then abort these steps.
1223     bool activeSetChanged = missedCuesSize;
1224 
1225     for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i) {
1226         if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive())
1227             activeSetChanged = true;
1228     }
1229 
1230     for (size_t i = 0; i < currentCuesSize; ++i) {
1231         currentCues[i].data()->updateDisplayTree(movieTime);
1232 
1233         if (!currentCues[i].data()->isActive())
1234             activeSetChanged = true;
1235     }
1236 
1237     if (!activeSetChanged)
1238         return;
1239 
1240     // 7 - If the time was reached through the usual monotonic increase of the
1241     // current playback position during normal playback, and there are cues in
1242     // other cues that have their text track cue pause-on-exi flag set and that
1243     // either have their text track cue active flag set or are also in missed
1244     // cues, then immediately pause the media element.
1245     for (size_t i = 0; !m_paused && i < previousCuesSize; ++i) {
1246         if (previousCues[i].data()->pauseOnExit()
1247             && previousCues[i].data()->isActive()
1248             && !currentCues.contains(previousCues[i]))
1249             pause();
1250     }
1251 
1252     for (size_t i = 0; !m_paused && i < missedCuesSize; ++i) {
1253         if (missedCues[i].data()->pauseOnExit())
1254             pause();
1255     }
1256 
1257     // 8 - Let events be a list of tasks, initially empty. Each task in this
1258     // list will be associated with a text track, a text track cue, and a time,
1259     // which are used to sort the list before the tasks are queued.
1260     WillBeHeapVector<std::pair<double, RawPtrWillBeMember<TextTrackCue> > > eventTasks;
1261 
1262     // 8 - Let affected tracks be a list of text tracks, initially empty.
1263     WillBeHeapVector<RawPtrWillBeMember<TextTrack> > affectedTracks;
1264 
1265     for (size_t i = 0; i < missedCuesSize; ++i) {
1266         // 9 - For each text track cue in missed cues, prepare an event named enter
1267         // for the TextTrackCue object with the text track cue start time.
1268         eventTasks.append(std::make_pair(missedCues[i].data()->startTime(),
1269                                          missedCues[i].data()));
1270 
1271         // 10 - For each text track [...] in missed cues, prepare an event
1272         // named exit for the TextTrackCue object with the  with the later of
1273         // the text track cue end time and the text track cue start time.
1274 
1275         // Note: An explicit task is added only if the cue is NOT a zero or
1276         // negative length cue. Otherwise, the need for an exit event is
1277         // checked when these tasks are actually queued below. This doesn't
1278         // affect sorting events before dispatch either, because the exit
1279         // event has the same time as the enter event.
1280         if (missedCues[i].data()->startTime() < missedCues[i].data()->endTime())
1281             eventTasks.append(std::make_pair(missedCues[i].data()->endTime(),
1282                                              missedCues[i].data()));
1283     }
1284 
1285     for (size_t i = 0; i < previousCuesSize; ++i) {
1286         // 10 - For each text track cue in other cues that has its text
1287         // track cue active flag set prepare an event named exit for the
1288         // TextTrackCue object with the text track cue end time.
1289         if (!currentCues.contains(previousCues[i]))
1290             eventTasks.append(std::make_pair(previousCues[i].data()->endTime(),
1291                                              previousCues[i].data()));
1292     }
1293 
1294     for (size_t i = 0; i < currentCuesSize; ++i) {
1295         // 11 - For each text track cue in current cues that does not have its
1296         // text track cue active flag set, prepare an event named enter for the
1297         // TextTrackCue object with the text track cue start time.
1298         if (!previousCues.contains(currentCues[i]))
1299             eventTasks.append(std::make_pair(currentCues[i].data()->startTime(),
1300                                              currentCues[i].data()));
1301     }
1302 
1303     // 12 - Sort the tasks in events in ascending time order (tasks with earlier
1304     // times first).
1305     nonCopyingSort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare);
1306 
1307     for (size_t i = 0; i < eventTasks.size(); ++i) {
1308         if (!affectedTracks.contains(eventTasks[i].second->track()))
1309             affectedTracks.append(eventTasks[i].second->track());
1310 
1311         // 13 - Queue each task in events, in list order.
1312         RefPtrWillBeRawPtr<Event> event = nullptr;
1313 
1314         // Each event in eventTasks may be either an enterEvent or an exitEvent,
1315         // depending on the time that is associated with the event. This
1316         // correctly identifies the type of the event, if the startTime is
1317         // less than the endTime in the cue.
1318         if (eventTasks[i].second->startTime() >= eventTasks[i].second->endTime()) {
1319             event = Event::create(EventTypeNames::enter);
1320             event->setTarget(eventTasks[i].second);
1321             m_asyncEventQueue->enqueueEvent(event.release());
1322 
1323             event = Event::create(EventTypeNames::exit);
1324             event->setTarget(eventTasks[i].second);
1325             m_asyncEventQueue->enqueueEvent(event.release());
1326         } else {
1327             if (eventTasks[i].first == eventTasks[i].second->startTime())
1328                 event = Event::create(EventTypeNames::enter);
1329             else
1330                 event = Event::create(EventTypeNames::exit);
1331 
1332             event->setTarget(eventTasks[i].second);
1333             m_asyncEventQueue->enqueueEvent(event.release());
1334         }
1335     }
1336 
1337     // 14 - Sort affected tracks in the same order as the text tracks appear in
1338     // the media element's list of text tracks, and remove duplicates.
1339     nonCopyingSort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare);
1340 
1341     // 15 - For each text track in affected tracks, in the list order, queue a
1342     // task to fire a simple event named cuechange at the TextTrack object, and, ...
1343     for (size_t i = 0; i < affectedTracks.size(); ++i) {
1344         RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::cuechange);
1345         event->setTarget(affectedTracks[i]);
1346 
1347         m_asyncEventQueue->enqueueEvent(event.release());
1348 
1349         // ... if the text track has a corresponding track element, to then fire a
1350         // simple event named cuechange at the track element as well.
1351         if (affectedTracks[i]->trackType() == TextTrack::TrackElement) {
1352             RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::cuechange);
1353             HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(affectedTracks[i].get())->trackElement();
1354             ASSERT(trackElement);
1355             event->setTarget(trackElement);
1356 
1357             m_asyncEventQueue->enqueueEvent(event.release());
1358         }
1359     }
1360 
1361     // 16 - Set the text track cue active flag of all the cues in the current
1362     // cues, and unset the text track cue active flag of all the cues in the
1363     // other cues.
1364     for (size_t i = 0; i < currentCuesSize; ++i)
1365         currentCues[i].data()->setIsActive(true);
1366 
1367     for (size_t i = 0; i < previousCuesSize; ++i) {
1368         if (!currentCues.contains(previousCues[i]))
1369             previousCues[i].data()->setIsActive(false);
1370     }
1371 
1372     // Update the current active cues.
1373     m_currentlyActiveCues = currentCues;
1374 
1375     if (activeSetChanged)
1376         updateTextTrackDisplay();
1377 }
1378 
textTracksAreReady() const1379 bool HTMLMediaElement::textTracksAreReady() const
1380 {
1381     // 4.8.10.12.1 Text track model
1382     // ...
1383     // The text tracks of a media element are ready if all the text tracks whose mode was not
1384     // in the disabled state when the element's resource selection algorithm last started now
1385     // have a text track readiness state of loaded or failed to load.
1386     for (unsigned i = 0; i < m_textTracksWhenResourceSelectionBegan.size(); ++i) {
1387         if (m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::Loading
1388             || m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::NotLoaded)
1389             return false;
1390     }
1391 
1392     return true;
1393 }
1394 
textTrackReadyStateChanged(TextTrack * track)1395 void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track)
1396 {
1397     if (webMediaPlayer()&& m_textTracksWhenResourceSelectionBegan.contains(track)) {
1398         if (track->readinessState() != TextTrack::Loading)
1399             setReadyState(static_cast<ReadyState>(webMediaPlayer()->readyState()));
1400     } else {
1401         // The track readiness state might have changed as a result of the user
1402         // clicking the captions button. In this case, a check whether all the
1403         // resources have failed loading should be done in order to hide the CC button.
1404         if (hasMediaControls() && track->readinessState() == TextTrack::FailedToLoad)
1405             mediaControls()->refreshClosedCaptionsButtonVisibility();
1406     }
1407 }
1408 
textTrackModeChanged(TextTrack * track)1409 void HTMLMediaElement::textTrackModeChanged(TextTrack* track)
1410 {
1411     if (track->trackType() == TextTrack::TrackElement) {
1412         // 4.8.10.12.3 Sourcing out-of-band text tracks
1413         // ... when a text track corresponding to a track element is created with text track
1414         // mode set to disabled and subsequently changes its text track mode to hidden, showing,
1415         // or showing by default for the first time, the user agent must immediately and synchronously
1416         // run the following algorithm ...
1417 
1418         for (HTMLTrackElement* trackElement = Traversal<HTMLTrackElement>::firstChild(*this); trackElement; trackElement = Traversal<HTMLTrackElement>::nextSibling(*trackElement)) {
1419             if (trackElement->track() != track)
1420                 continue;
1421 
1422             // Mark this track as "configured" so configureTextTracks won't change the mode again.
1423             track->setHasBeenConfigured(true);
1424             if (track->mode() != TextTrack::disabledKeyword()) {
1425                 if (trackElement->readyState() == HTMLTrackElement::LOADED)
1426                     textTrackAddCues(track, track->cues());
1427 
1428                 // If this is the first added track, create the list of text tracks.
1429                 if (!m_textTracks)
1430                     m_textTracks = TextTrackList::create(this);
1431             }
1432             break;
1433         }
1434     } else if (track->trackType() == TextTrack::AddTrack && track->mode() != TextTrack::disabledKeyword()) {
1435         textTrackAddCues(track, track->cues());
1436     }
1437 
1438     configureTextTrackDisplay(AssumeVisibleChange);
1439 
1440     ASSERT(textTracks()->contains(track));
1441     textTracks()->scheduleChangeEvent();
1442 }
1443 
textTrackKindChanged(TextTrack * track)1444 void HTMLMediaElement::textTrackKindChanged(TextTrack* track)
1445 {
1446     if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword() && track->mode() == TextTrack::showingKeyword())
1447         track->setMode(TextTrack::hiddenKeyword());
1448 }
1449 
beginIgnoringTrackDisplayUpdateRequests()1450 void HTMLMediaElement::beginIgnoringTrackDisplayUpdateRequests()
1451 {
1452     ++m_ignoreTrackDisplayUpdate;
1453 }
1454 
endIgnoringTrackDisplayUpdateRequests()1455 void HTMLMediaElement::endIgnoringTrackDisplayUpdateRequests()
1456 {
1457     ASSERT(m_ignoreTrackDisplayUpdate);
1458     --m_ignoreTrackDisplayUpdate;
1459     if (!m_ignoreTrackDisplayUpdate && m_active)
1460         updateActiveTextTrackCues(currentTime());
1461 }
1462 
textTrackAddCues(TextTrack * track,const TextTrackCueList * cues)1463 void HTMLMediaElement::textTrackAddCues(TextTrack* track, const TextTrackCueList* cues)
1464 {
1465     WTF_LOG(Media, "HTMLMediaElement::textTrackAddCues(%p)", this);
1466     if (track->mode() == TextTrack::disabledKeyword())
1467         return;
1468 
1469     TrackDisplayUpdateScope scope(this);
1470     for (size_t i = 0; i < cues->length(); ++i)
1471         textTrackAddCue(cues->item(i)->track(), cues->item(i));
1472 }
1473 
textTrackRemoveCues(TextTrack *,const TextTrackCueList * cues)1474 void HTMLMediaElement::textTrackRemoveCues(TextTrack*, const TextTrackCueList* cues)
1475 {
1476     WTF_LOG(Media, "HTMLMediaElement::textTrackRemoveCues(%p)", this);
1477 
1478     TrackDisplayUpdateScope scope(this);
1479     for (size_t i = 0; i < cues->length(); ++i)
1480         textTrackRemoveCue(cues->item(i)->track(), cues->item(i));
1481 }
1482 
textTrackAddCue(TextTrack * track,PassRefPtrWillBeRawPtr<TextTrackCue> cue)1483 void HTMLMediaElement::textTrackAddCue(TextTrack* track, PassRefPtrWillBeRawPtr<TextTrackCue> cue)
1484 {
1485     if (track->mode() == TextTrack::disabledKeyword())
1486         return;
1487 
1488     // Negative duration cues need be treated in the interval tree as
1489     // zero-length cues.
1490     double endTime = std::max(cue->startTime(), cue->endTime());
1491 
1492     CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get());
1493     if (!m_cueTree.contains(interval))
1494         m_cueTree.add(interval);
1495     updateActiveTextTrackCues(currentTime());
1496 }
1497 
textTrackRemoveCue(TextTrack *,PassRefPtrWillBeRawPtr<TextTrackCue> cue)1498 void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtrWillBeRawPtr<TextTrackCue> cue)
1499 {
1500     // Negative duration cues need to be treated in the interval tree as
1501     // zero-length cues.
1502     double endTime = std::max(cue->startTime(), cue->endTime());
1503 
1504     CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get());
1505     m_cueTree.remove(interval);
1506 
1507     // Since the cue will be removed from the media element and likely the
1508     // TextTrack might also be destructed, notifying the region of the cue
1509     // removal shouldn't be done.
1510     cue->notifyRegionWhenRemovingDisplayTree(false);
1511 
1512     size_t index = m_currentlyActiveCues.find(interval);
1513     if (index != kNotFound) {
1514         m_currentlyActiveCues.remove(index);
1515         cue->setIsActive(false);
1516     }
1517     cue->removeDisplayTree();
1518     updateActiveTextTrackCues(currentTime());
1519 
1520     cue->notifyRegionWhenRemovingDisplayTree(true);
1521 }
1522 
1523 
isSafeToLoadURL(const KURL & url,InvalidURLAction actionIfInvalid)1524 bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidURLAction actionIfInvalid)
1525 {
1526     if (!url.isValid()) {
1527         WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p, %s) -> FALSE because url is invalid", this, urlForLoggingMedia(url).utf8().data());
1528         return false;
1529     }
1530 
1531     LocalFrame* frame = document().frame();
1532     if (!frame || !document().securityOrigin()->canDisplay(url)) {
1533         if (actionIfInvalid == Complain)
1534             FrameLoader::reportLocalLoadFailed(frame, url.elidedString());
1535         WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p, %s) -> FALSE rejected by SecurityOrigin", this, urlForLoggingMedia(url).utf8().data());
1536         return false;
1537     }
1538 
1539     if (!document().contentSecurityPolicy()->allowMediaFromSource(url)) {
1540         WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p, %s) -> rejected by Content Security Policy", this, urlForLoggingMedia(url).utf8().data());
1541         return false;
1542     }
1543 
1544     return true;
1545 }
1546 
startProgressEventTimer()1547 void HTMLMediaElement::startProgressEventTimer()
1548 {
1549     if (m_progressEventTimer.isActive())
1550         return;
1551 
1552     m_previousProgressTime = WTF::currentTime();
1553     // 350ms is not magic, it is in the spec!
1554     m_progressEventTimer.startRepeating(0.350, FROM_HERE);
1555 }
1556 
waitForSourceChange()1557 void HTMLMediaElement::waitForSourceChange()
1558 {
1559     WTF_LOG(Media, "HTMLMediaElement::waitForSourceChange(%p)", this);
1560 
1561     stopPeriodicTimers();
1562     m_loadState = WaitingForSource;
1563 
1564     // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value
1565     m_networkState = NETWORK_NO_SOURCE;
1566 
1567     // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1568     setShouldDelayLoadEvent(false);
1569 
1570     updateDisplayState();
1571 
1572     if (renderer())
1573         renderer()->updateFromElement();
1574 }
1575 
noneSupported()1576 void HTMLMediaElement::noneSupported()
1577 {
1578     WTF_LOG(Media, "HTMLMediaElement::noneSupported(%p)", this);
1579 
1580     stopPeriodicTimers();
1581     m_loadState = WaitingForSource;
1582     m_currentSourceNode = nullptr;
1583 
1584     // 4.8.10.5
1585     // 6 - Reaching this step indicates that the media resource failed to load or that the given
1586     // URL could not be resolved. In one atomic operation, run the following steps:
1587 
1588     // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to
1589     // MEDIA_ERR_SRC_NOT_SUPPORTED.
1590     m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
1591 
1592     // 6.2 - Forget the media element's media-resource-specific text tracks.
1593     forgetResourceSpecificTracks();
1594 
1595     // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
1596     m_networkState = NETWORK_NO_SOURCE;
1597 
1598     // 7 - Queue a task to fire a simple event named error at the media element.
1599     scheduleEvent(EventTypeNames::error);
1600 
1601     closeMediaSource();
1602 
1603     // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1604     setShouldDelayLoadEvent(false);
1605 
1606     // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed,
1607     // the element won't attempt to load another resource.
1608 
1609     updateDisplayState();
1610 
1611     if (renderer())
1612         renderer()->updateFromElement();
1613 }
1614 
mediaEngineError(PassRefPtrWillBeRawPtr<MediaError> err)1615 void HTMLMediaElement::mediaEngineError(PassRefPtrWillBeRawPtr<MediaError> err)
1616 {
1617     ASSERT(m_readyState >= HAVE_METADATA);
1618     WTF_LOG(Media, "HTMLMediaElement::mediaEngineError(%p, %d)", this, static_cast<int>(err->code()));
1619 
1620     // 1 - The user agent should cancel the fetching process.
1621     stopPeriodicTimers();
1622     m_loadState = WaitingForSource;
1623 
1624     // 2 - Set the error attribute to a new MediaError object whose code attribute is
1625     // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
1626     m_error = err;
1627 
1628     // 3 - Queue a task to fire a simple event named error at the media element.
1629     scheduleEvent(EventTypeNames::error);
1630 
1631     // 4 - Set the element's networkState attribute to the NETWORK_IDLE value.
1632     m_networkState = NETWORK_IDLE;
1633 
1634     // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
1635     setShouldDelayLoadEvent(false);
1636 
1637     // 6 - Abort the overall resource selection algorithm.
1638     m_currentSourceNode = nullptr;
1639 }
1640 
cancelPendingEventsAndCallbacks()1641 void HTMLMediaElement::cancelPendingEventsAndCallbacks()
1642 {
1643     WTF_LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks(%p)", this);
1644     m_asyncEventQueue->cancelAllEvents();
1645 
1646     for (HTMLSourceElement* source = Traversal<HTMLSourceElement>::firstChild(*this); source; source = Traversal<HTMLSourceElement>::nextSibling(*source))
1647         source->cancelPendingErrorEvent();
1648 }
1649 
mediaPlayerNetworkStateChanged()1650 void HTMLMediaElement::mediaPlayerNetworkStateChanged()
1651 {
1652     setNetworkState(webMediaPlayer()->networkState());
1653 }
1654 
mediaLoadingFailed(WebMediaPlayer::NetworkState error)1655 void HTMLMediaElement::mediaLoadingFailed(WebMediaPlayer::NetworkState error)
1656 {
1657     stopPeriodicTimers();
1658 
1659     // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
1660     // <source> children, schedule the next one
1661     if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
1662 
1663         // resource selection algorithm
1664         // Step 9.Otherwise.9 - Failed with elements: Queue a task, using the DOM manipulation task source, to fire a simple event named error at the candidate element.
1665         if (m_currentSourceNode)
1666             m_currentSourceNode->scheduleErrorEvent();
1667         else
1668             WTF_LOG(Media, "HTMLMediaElement::setNetworkState(%p) - error event not sent, <source> was removed", this);
1669 
1670         // 9.Otherwise.10 - Asynchronously await a stable state. The synchronous section consists of all the remaining steps of this algorithm until the algorithm says the synchronous section has ended.
1671 
1672         // 9.Otherwise.11 - Forget the media element's media-resource-specific tracks.
1673         forgetResourceSpecificTracks();
1674 
1675         if (havePotentialSourceChild()) {
1676             WTF_LOG(Media, "HTMLMediaElement::setNetworkState(%p) - scheduling next <source>", this);
1677             scheduleNextSourceChild();
1678         } else {
1679             WTF_LOG(Media, "HTMLMediaElement::setNetworkState(%p) - no more <source> elements, waiting", this);
1680             waitForSourceChange();
1681         }
1682 
1683         return;
1684     }
1685 
1686     if (error == WebMediaPlayer::NetworkStateNetworkError && m_readyState >= HAVE_METADATA)
1687         mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK));
1688     else if (error == WebMediaPlayer::NetworkStateDecodeError)
1689         mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE));
1690     else if ((error == WebMediaPlayer::NetworkStateFormatError
1691         || error == WebMediaPlayer::NetworkStateNetworkError)
1692         && m_loadState == LoadingFromSrcAttr)
1693         noneSupported();
1694 
1695     updateDisplayState();
1696     if (hasMediaControls())
1697         mediaControls()->reset();
1698 }
1699 
setNetworkState(WebMediaPlayer::NetworkState state)1700 void HTMLMediaElement::setNetworkState(WebMediaPlayer::NetworkState state)
1701 {
1702     WTF_LOG(Media, "HTMLMediaElement::setNetworkState(%p, %d) - current state is %d", this, static_cast<int>(state), static_cast<int>(m_networkState));
1703 
1704     if (state == WebMediaPlayer::NetworkStateEmpty) {
1705         // Just update the cached state and leave, we can't do anything.
1706         m_networkState = NETWORK_EMPTY;
1707         return;
1708     }
1709 
1710     if (state == WebMediaPlayer::NetworkStateFormatError
1711         || state == WebMediaPlayer::NetworkStateNetworkError
1712         || state == WebMediaPlayer::NetworkStateDecodeError) {
1713         mediaLoadingFailed(state);
1714         return;
1715     }
1716 
1717     if (state == WebMediaPlayer::NetworkStateIdle) {
1718         if (m_networkState > NETWORK_IDLE) {
1719             changeNetworkStateFromLoadingToIdle();
1720             setShouldDelayLoadEvent(false);
1721         } else {
1722             m_networkState = NETWORK_IDLE;
1723         }
1724     }
1725 
1726     if (state == WebMediaPlayer::NetworkStateLoading) {
1727         if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE)
1728             startProgressEventTimer();
1729         m_networkState = NETWORK_LOADING;
1730     }
1731 
1732     if (state == WebMediaPlayer::NetworkStateLoaded) {
1733         if (m_networkState != NETWORK_IDLE)
1734             changeNetworkStateFromLoadingToIdle();
1735         m_completelyLoaded = true;
1736     }
1737 }
1738 
changeNetworkStateFromLoadingToIdle()1739 void HTMLMediaElement::changeNetworkStateFromLoadingToIdle()
1740 {
1741     ASSERT(m_player);
1742     m_progressEventTimer.stop();
1743 
1744     // Schedule one last progress event so we guarantee that at least one is fired
1745     // for files that load very quickly.
1746     if (webMediaPlayer() && webMediaPlayer()->didLoadingProgress())
1747         scheduleEvent(EventTypeNames::progress);
1748     scheduleEvent(EventTypeNames::suspend);
1749     m_networkState = NETWORK_IDLE;
1750 }
1751 
mediaPlayerReadyStateChanged()1752 void HTMLMediaElement::mediaPlayerReadyStateChanged()
1753 {
1754     setReadyState(static_cast<ReadyState>(webMediaPlayer()->readyState()));
1755 }
1756 
setReadyState(ReadyState state)1757 void HTMLMediaElement::setReadyState(ReadyState state)
1758 {
1759     WTF_LOG(Media, "HTMLMediaElement::setReadyState(%p, %d) - current state is %d,", this, static_cast<int>(state), static_cast<int>(m_readyState));
1760 
1761     // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it
1762     bool wasPotentiallyPlaying = potentiallyPlaying();
1763 
1764     ReadyState oldState = m_readyState;
1765     ReadyState newState = state;
1766 
1767     bool tracksAreReady = textTracksAreReady();
1768 
1769     if (newState == oldState && m_tracksAreReady == tracksAreReady)
1770         return;
1771 
1772     m_tracksAreReady = tracksAreReady;
1773 
1774     if (tracksAreReady) {
1775         m_readyState = newState;
1776     } else {
1777         // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until
1778         // the text tracks are ready, regardless of the state of the media file.
1779         if (newState <= HAVE_METADATA)
1780             m_readyState = newState;
1781         else
1782             m_readyState = HAVE_CURRENT_DATA;
1783     }
1784 
1785     if (oldState > m_readyStateMaximum)
1786         m_readyStateMaximum = oldState;
1787 
1788     if (m_networkState == NETWORK_EMPTY)
1789         return;
1790 
1791     if (m_seeking) {
1792         // 4.8.10.9, step 9 note: If the media element was potentially playing immediately before
1793         // it started seeking, but seeking caused its readyState attribute to change to a value
1794         // lower than HAVE_FUTURE_DATA, then a waiting will be fired at the element.
1795         if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA)
1796             scheduleEvent(EventTypeNames::waiting);
1797 
1798         // 4.8.10.9 steps 12-14
1799         if (m_readyState >= HAVE_CURRENT_DATA)
1800             finishSeek();
1801     } else {
1802         if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) {
1803             // 4.8.10.8
1804             scheduleTimeupdateEvent(false);
1805             scheduleEvent(EventTypeNames::waiting);
1806         }
1807     }
1808 
1809     if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
1810         createPlaceholderTracksIfNecessary();
1811 
1812         selectInitialTracksIfNecessary();
1813 
1814         MediaFragmentURIParser fragmentParser(m_currentSrc);
1815         m_fragmentEndTime = fragmentParser.endTime();
1816 
1817         m_duration = duration();
1818         scheduleEvent(EventTypeNames::durationchange);
1819 
1820         if (isHTMLVideoElement())
1821             scheduleEvent(EventTypeNames::resize);
1822         scheduleEvent(EventTypeNames::loadedmetadata);
1823 
1824         bool jumped = false;
1825         if (m_defaultPlaybackStartPosition > 0) {
1826             seek(m_defaultPlaybackStartPosition);
1827             jumped = true;
1828         }
1829         m_defaultPlaybackStartPosition = 0;
1830 
1831         double initialPlaybackPosition = fragmentParser.startTime();
1832         if (initialPlaybackPosition == MediaPlayer::invalidTime())
1833             initialPlaybackPosition = 0;
1834 
1835         if (!jumped && initialPlaybackPosition > 0) {
1836             m_sentEndEvent = false;
1837             UseCounter::count(document(), UseCounter::HTMLMediaElementSeekToFragmentStart);
1838             seek(initialPlaybackPosition);
1839             jumped = true;
1840         }
1841 
1842         if (m_mediaController) {
1843             if (jumped && initialPlaybackPosition > m_mediaController->currentTime())
1844                 m_mediaController->setCurrentTime(initialPlaybackPosition);
1845             else
1846                 seek(m_mediaController->currentTime());
1847         }
1848 
1849         if (hasMediaControls())
1850             mediaControls()->reset();
1851         if (renderer())
1852             renderer()->updateFromElement();
1853     }
1854 
1855     bool shouldUpdateDisplayState = false;
1856 
1857     if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) {
1858         m_haveFiredLoadedData = true;
1859         shouldUpdateDisplayState = true;
1860         scheduleEvent(EventTypeNames::loadeddata);
1861         setShouldDelayLoadEvent(false);
1862     }
1863 
1864     bool isPotentiallyPlaying = potentiallyPlaying();
1865     if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
1866         scheduleEvent(EventTypeNames::canplay);
1867         if (isPotentiallyPlaying)
1868             scheduleEvent(EventTypeNames::playing);
1869         shouldUpdateDisplayState = true;
1870     }
1871 
1872     if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA && tracksAreReady) {
1873         if (oldState <= HAVE_CURRENT_DATA) {
1874             scheduleEvent(EventTypeNames::canplay);
1875             if (isPotentiallyPlaying)
1876                 scheduleEvent(EventTypeNames::playing);
1877         }
1878 
1879         if (m_autoplaying && m_paused && autoplay() && !document().isSandboxed(SandboxAutomaticFeatures) && !m_userGestureRequiredForPlay) {
1880             m_paused = false;
1881             invalidateCachedTime();
1882             scheduleEvent(EventTypeNames::play);
1883             scheduleEvent(EventTypeNames::playing);
1884         }
1885 
1886         scheduleEvent(EventTypeNames::canplaythrough);
1887 
1888         shouldUpdateDisplayState = true;
1889     }
1890 
1891     if (shouldUpdateDisplayState) {
1892         updateDisplayState();
1893         if (hasMediaControls())
1894             mediaControls()->refreshClosedCaptionsButtonVisibility();
1895     }
1896 
1897     updatePlayState();
1898     updateMediaController();
1899     updateActiveTextTrackCues(currentTime());
1900 }
1901 
progressEventTimerFired(Timer<HTMLMediaElement> *)1902 void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*)
1903 {
1904     ASSERT(m_player);
1905     if (m_networkState != NETWORK_LOADING)
1906         return;
1907 
1908     double time = WTF::currentTime();
1909     double timedelta = time - m_previousProgressTime;
1910 
1911     if (webMediaPlayer() && webMediaPlayer()->didLoadingProgress()) {
1912         scheduleEvent(EventTypeNames::progress);
1913         m_previousProgressTime = time;
1914         m_sentStalledEvent = false;
1915         if (renderer())
1916             renderer()->updateFromElement();
1917     } else if (timedelta > 3.0 && !m_sentStalledEvent) {
1918         scheduleEvent(EventTypeNames::stalled);
1919         m_sentStalledEvent = true;
1920         setShouldDelayLoadEvent(false);
1921     }
1922 }
1923 
addPlayedRange(double start,double end)1924 void HTMLMediaElement::addPlayedRange(double start, double end)
1925 {
1926     WTF_LOG(Media, "HTMLMediaElement::addPlayedRange(%p, %f, %f)", this, start, end);
1927     if (!m_playedTimeRanges)
1928         m_playedTimeRanges = TimeRanges::create();
1929     m_playedTimeRanges->add(start, end);
1930 }
1931 
supportsSave() const1932 bool HTMLMediaElement::supportsSave() const
1933 {
1934     return webMediaPlayer() && webMediaPlayer()->supportsSave();
1935 }
1936 
prepareToPlay()1937 void HTMLMediaElement::prepareToPlay()
1938 {
1939     WTF_LOG(Media, "HTMLMediaElement::prepareToPlay(%p)", this);
1940     if (m_havePreparedToPlay)
1941         return;
1942     m_havePreparedToPlay = true;
1943 
1944     if (loadIsDeferred())
1945         startDeferredLoad();
1946 }
1947 
seek(double time)1948 void HTMLMediaElement::seek(double time)
1949 {
1950     WTF_LOG(Media, "HTMLMediaElement::seek(%p, %f)", this, time);
1951 
1952     // 2 - If the media element's readyState is HAVE_NOTHING, abort these steps.
1953     if (m_readyState == HAVE_NOTHING)
1954         return;
1955 
1956     // If the media engine has been told to postpone loading data, let it go ahead now.
1957     if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA)
1958         prepareToPlay();
1959 
1960     // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set.
1961     refreshCachedTime();
1962     // This is needed to avoid getting default playback start position from currentTime().
1963     double now = m_cachedTime;
1964 
1965     // 3 - If the element's seeking IDL attribute is true, then another instance of this algorithm is
1966     // already running. Abort that other instance of the algorithm without waiting for the step that
1967     // it is running to complete.
1968     // Nothing specific to be done here.
1969 
1970     // 4 - Set the seeking IDL attribute to true.
1971     // The flag will be cleared when the engine tells us the time has actually changed.
1972     bool previousSeekStillPending = m_seeking;
1973     m_seeking = true;
1974 
1975     // 6 - If the new playback position is later than the end of the media resource, then let it be the end
1976     // of the media resource instead.
1977     time = std::min(time, duration());
1978 
1979     // 7 - If the new playback position is less than the earliest possible position, let it be that position instead.
1980     time = std::max(time, 0.0);
1981 
1982     // Ask the media engine for the time value in the movie's time scale before comparing with current time. This
1983     // is necessary because if the seek time is not equal to currentTime but the delta is less than the movie's
1984     // time scale, we will ask the media engine to "seek" to the current movie time, which may be a noop and
1985     // not generate a timechanged callback. This means m_seeking will never be cleared and we will never
1986     // fire a 'seeked' event.
1987     double mediaTime = webMediaPlayer()->mediaTimeForTimeValue(time);
1988     if (time != mediaTime) {
1989         WTF_LOG(Media, "HTMLMediaElement::seek(%p, %f) - media timeline equivalent is %f", this, time, mediaTime);
1990         time = mediaTime;
1991     }
1992 
1993     // 8 - If the (possibly now changed) new playback position is not in one of the ranges given in the
1994     // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute
1995     // that is the nearest to the new playback position. ... If there are no ranges given in the seekable
1996     // attribute then set the seeking IDL attribute to false and abort these steps.
1997     RefPtrWillBeRawPtr<TimeRanges> seekableRanges = seekable();
1998 
1999     // Short circuit seeking to the current time by just firing the events if no seek is required.
2000     // Don't skip calling the media engine if we are in poster mode because a seek should always
2001     // cancel poster display.
2002     bool noSeekRequired = !seekableRanges->length() || (time == now && displayMode() != Poster);
2003 
2004     if (noSeekRequired) {
2005         if (time == now) {
2006             scheduleEvent(EventTypeNames::seeking);
2007             if (previousSeekStillPending)
2008                 return;
2009             // FIXME: There must be a stable state before timeupdate+seeked are dispatched and seeking
2010             // is reset to false. See http://crbug.com/266631
2011             scheduleTimeupdateEvent(false);
2012             scheduleEvent(EventTypeNames::seeked);
2013         }
2014         m_seeking = false;
2015         return;
2016     }
2017     time = seekableRanges->nearest(time, now);
2018 
2019     if (m_playing) {
2020         if (m_lastSeekTime < now)
2021             addPlayedRange(m_lastSeekTime, now);
2022     }
2023     m_lastSeekTime = time;
2024     m_sentEndEvent = false;
2025 
2026     // 10 - Queue a task to fire a simple event named seeking at the element.
2027     scheduleEvent(EventTypeNames::seeking);
2028 
2029     // 11 - Set the current playback position to the given new playback position.
2030     webMediaPlayer()->seek(time);
2031 
2032     // 14-17 are handled, if necessary, when the engine signals a readystate change or otherwise
2033     // satisfies seek completion and signals a time change.
2034 }
2035 
finishSeek()2036 void HTMLMediaElement::finishSeek()
2037 {
2038     WTF_LOG(Media, "HTMLMediaElement::finishSeek(%p)", this);
2039 
2040     // 14 - Set the seeking IDL attribute to false.
2041     m_seeking = false;
2042 
2043     // 16 - Queue a task to fire a simple event named timeupdate at the element.
2044     scheduleTimeupdateEvent(false);
2045 
2046     // 17 - Queue a task to fire a simple event named seeked at the element.
2047     scheduleEvent(EventTypeNames::seeked);
2048 
2049     setDisplayMode(Video);
2050 }
2051 
readyState() const2052 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
2053 {
2054     return m_readyState;
2055 }
2056 
hasAudio() const2057 bool HTMLMediaElement::hasAudio() const
2058 {
2059     return webMediaPlayer() && webMediaPlayer()->hasAudio();
2060 }
2061 
seeking() const2062 bool HTMLMediaElement::seeking() const
2063 {
2064     return m_seeking;
2065 }
2066 
refreshCachedTime() const2067 void HTMLMediaElement::refreshCachedTime() const
2068 {
2069     if (!webMediaPlayer() || m_readyState < HAVE_METADATA)
2070         return;
2071 
2072     m_cachedTime = webMediaPlayer()->currentTime();
2073 }
2074 
invalidateCachedTime()2075 void HTMLMediaElement::invalidateCachedTime()
2076 {
2077     WTF_LOG(Media, "HTMLMediaElement::invalidateCachedTime(%p)", this);
2078     m_cachedTime = MediaPlayer::invalidTime();
2079 }
2080 
2081 // playback state
currentTime() const2082 double HTMLMediaElement::currentTime() const
2083 {
2084     if (m_defaultPlaybackStartPosition)
2085         return m_defaultPlaybackStartPosition;
2086 
2087     if (m_readyState == HAVE_NOTHING)
2088         return 0;
2089 
2090     if (m_seeking) {
2091         WTF_LOG(Media, "HTMLMediaElement::currentTime(%p) - seeking, returning %f", this, m_lastSeekTime);
2092         return m_lastSeekTime;
2093     }
2094 
2095     if (m_cachedTime != MediaPlayer::invalidTime() && m_paused) {
2096 #if LOG_CACHED_TIME_WARNINGS
2097         static const double minCachedDeltaForWarning = 0.01;
2098         double delta = m_cachedTime - webMediaPlayer()->currentTime();
2099         if (delta > minCachedDeltaForWarning)
2100             WTF_LOG(Media, "HTMLMediaElement::currentTime(%p) - WARNING, cached time is %f seconds off of media time when paused", this, delta);
2101 #endif
2102         return m_cachedTime;
2103     }
2104 
2105     refreshCachedTime();
2106 
2107     return m_cachedTime;
2108 }
2109 
setCurrentTime(double time,ExceptionState & exceptionState)2110 void HTMLMediaElement::setCurrentTime(double time, ExceptionState& exceptionState)
2111 {
2112     if (m_mediaController) {
2113         exceptionState.throwDOMException(InvalidStateError, "The element is slaved to a MediaController.");
2114         return;
2115     }
2116 
2117     // If the media element's readyState is HAVE_NOTHING, then set the default
2118     // playback start position to that time.
2119     if (m_readyState == HAVE_NOTHING) {
2120         m_defaultPlaybackStartPosition = time;
2121         return;
2122     }
2123 
2124     seek(time);
2125 }
2126 
duration() const2127 double HTMLMediaElement::duration() const
2128 {
2129     // FIXME: remove m_player check once we figure out how m_player is going
2130     // out of sync with readystate. m_player is cleared but readystate is not set
2131     // to HAVE_NOTHING
2132     if (!m_player || m_readyState < HAVE_METADATA)
2133         return std::numeric_limits<double>::quiet_NaN();
2134 
2135     // FIXME: Refactor so m_duration is kept current (in both MSE and
2136     // non-MSE cases) once we have transitioned from HAVE_NOTHING ->
2137     // HAVE_METADATA. Currently, m_duration may be out of date for at least MSE
2138     // case because MediaSource and SourceBuffer do not notify the element
2139     // directly upon duration changes caused by endOfStream, remove, or append
2140     // operations; rather the notification is triggered by the WebMediaPlayer
2141     // implementation observing that the underlying engine has updated duration
2142     // and notifying the element to consult its MediaSource for current
2143     // duration. See http://crbug.com/266644
2144 
2145     if (m_mediaSource)
2146         return m_mediaSource->duration();
2147 
2148     return webMediaPlayer()->duration();
2149 }
2150 
paused() const2151 bool HTMLMediaElement::paused() const
2152 {
2153     return m_paused;
2154 }
2155 
defaultPlaybackRate() const2156 double HTMLMediaElement::defaultPlaybackRate() const
2157 {
2158     return m_defaultPlaybackRate;
2159 }
2160 
setDefaultPlaybackRate(double rate)2161 void HTMLMediaElement::setDefaultPlaybackRate(double rate)
2162 {
2163     if (m_defaultPlaybackRate == rate)
2164         return;
2165 
2166     m_defaultPlaybackRate = rate;
2167     scheduleEvent(EventTypeNames::ratechange);
2168 }
2169 
playbackRate() const2170 double HTMLMediaElement::playbackRate() const
2171 {
2172     return m_playbackRate;
2173 }
2174 
setPlaybackRate(double rate)2175 void HTMLMediaElement::setPlaybackRate(double rate)
2176 {
2177     WTF_LOG(Media, "HTMLMediaElement::setPlaybackRate(%p, %f)", this, rate);
2178 
2179     if (m_playbackRate != rate) {
2180         m_playbackRate = rate;
2181         invalidateCachedTime();
2182         scheduleEvent(EventTypeNames::ratechange);
2183     }
2184 
2185     updatePlaybackRate();
2186 }
2187 
effectivePlaybackRate() const2188 double HTMLMediaElement::effectivePlaybackRate() const
2189 {
2190     return m_mediaController ? m_mediaController->playbackRate() : m_playbackRate;
2191 }
2192 
directionOfPlayback() const2193 HTMLMediaElement::DirectionOfPlayback HTMLMediaElement::directionOfPlayback() const
2194 {
2195     return m_playbackRate >= 0 ? Forward : Backward;
2196 }
2197 
updatePlaybackRate()2198 void HTMLMediaElement::updatePlaybackRate()
2199 {
2200     double effectiveRate = effectivePlaybackRate();
2201     if (m_player && potentiallyPlaying())
2202         webMediaPlayer()->setRate(effectiveRate);
2203 }
2204 
ended() const2205 bool HTMLMediaElement::ended() const
2206 {
2207     // 4.8.10.8 Playing the media resource
2208     // The ended attribute must return true if the media element has ended
2209     // playback and the direction of playback is forwards, and false otherwise.
2210     return endedPlayback() && directionOfPlayback() == Forward;
2211 }
2212 
autoplay() const2213 bool HTMLMediaElement::autoplay() const
2214 {
2215     return fastHasAttribute(autoplayAttr);
2216 }
2217 
preload() const2218 String HTMLMediaElement::preload() const
2219 {
2220     switch (m_preload) {
2221     case MediaPlayer::None:
2222         return "none";
2223         break;
2224     case MediaPlayer::MetaData:
2225         return "metadata";
2226         break;
2227     case MediaPlayer::Auto:
2228         return "auto";
2229         break;
2230     }
2231 
2232     ASSERT_NOT_REACHED();
2233     return String();
2234 }
2235 
setPreload(const AtomicString & preload)2236 void HTMLMediaElement::setPreload(const AtomicString& preload)
2237 {
2238     WTF_LOG(Media, "HTMLMediaElement::setPreload(%p, %s)", this, preload.utf8().data());
2239     setAttribute(preloadAttr, preload);
2240 }
2241 
play()2242 void HTMLMediaElement::play()
2243 {
2244     WTF_LOG(Media, "HTMLMediaElement::play(%p)", this);
2245 
2246     if (m_userGestureRequiredForPlay && !UserGestureIndicator::processingUserGesture())
2247         return;
2248     if (UserGestureIndicator::processingUserGesture())
2249         m_userGestureRequiredForPlay = false;
2250 
2251     playInternal();
2252 }
2253 
playInternal()2254 void HTMLMediaElement::playInternal()
2255 {
2256     WTF_LOG(Media, "HTMLMediaElement::playInternal(%p)", this);
2257 
2258     // 4.8.10.9. Playing the media resource
2259     if (!m_player || m_networkState == NETWORK_EMPTY)
2260         scheduleDelayedAction(LoadMediaResource);
2261 
2262     if (endedPlayback())
2263         seek(0);
2264 
2265     if (m_mediaController)
2266         m_mediaController->bringElementUpToSpeed(this);
2267 
2268     if (m_paused) {
2269         m_paused = false;
2270         invalidateCachedTime();
2271         scheduleEvent(EventTypeNames::play);
2272 
2273         if (m_readyState <= HAVE_CURRENT_DATA)
2274             scheduleEvent(EventTypeNames::waiting);
2275         else if (m_readyState >= HAVE_FUTURE_DATA)
2276             scheduleEvent(EventTypeNames::playing);
2277     }
2278     m_autoplaying = false;
2279 
2280     updatePlayState();
2281     updateMediaController();
2282 }
2283 
pause()2284 void HTMLMediaElement::pause()
2285 {
2286     WTF_LOG(Media, "HTMLMediaElement::pause(%p)", this);
2287 
2288     if (!m_player || m_networkState == NETWORK_EMPTY)
2289         scheduleDelayedAction(LoadMediaResource);
2290 
2291     m_autoplaying = false;
2292 
2293     if (!m_paused) {
2294         m_paused = true;
2295         scheduleTimeupdateEvent(false);
2296         scheduleEvent(EventTypeNames::pause);
2297     }
2298 
2299     updatePlayState();
2300 }
2301 
requestRemotePlayback()2302 void HTMLMediaElement::requestRemotePlayback()
2303 {
2304     ASSERT(m_remoteRoutesAvailable);
2305     webMediaPlayer()->requestRemotePlayback();
2306 }
2307 
requestRemotePlaybackControl()2308 void HTMLMediaElement::requestRemotePlaybackControl()
2309 {
2310     ASSERT(m_remoteRoutesAvailable);
2311     webMediaPlayer()->requestRemotePlaybackControl();
2312 }
2313 
closeMediaSource()2314 void HTMLMediaElement::closeMediaSource()
2315 {
2316     if (!m_mediaSource)
2317         return;
2318 
2319     m_mediaSource->close();
2320     m_mediaSource = nullptr;
2321 }
2322 
loop() const2323 bool HTMLMediaElement::loop() const
2324 {
2325     return fastHasAttribute(loopAttr);
2326 }
2327 
setLoop(bool b)2328 void HTMLMediaElement::setLoop(bool b)
2329 {
2330     WTF_LOG(Media, "HTMLMediaElement::setLoop(%p, %s)", this, boolString(b));
2331     setBooleanAttribute(loopAttr, b);
2332 }
2333 
shouldShowControls() const2334 bool HTMLMediaElement::shouldShowControls() const
2335 {
2336     LocalFrame* frame = document().frame();
2337 
2338     // always show controls when scripting is disabled
2339     if (frame && !frame->script().canExecuteScripts(NotAboutToExecuteScript))
2340         return true;
2341 
2342     // Always show controls when in full screen mode.
2343     if (isFullscreen())
2344         return true;
2345 
2346     return fastHasAttribute(controlsAttr);
2347 }
2348 
volume() const2349 double HTMLMediaElement::volume() const
2350 {
2351     return m_volume;
2352 }
2353 
setVolume(double vol,ExceptionState & exceptionState)2354 void HTMLMediaElement::setVolume(double vol, ExceptionState& exceptionState)
2355 {
2356     WTF_LOG(Media, "HTMLMediaElement::setVolume(%p, %f)", this, vol);
2357 
2358     if (m_volume == vol)
2359         return;
2360 
2361     if (vol < 0.0f || vol > 1.0f) {
2362         exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexOutsideRange("volume", vol, 0.0, ExceptionMessages::InclusiveBound, 1.0, ExceptionMessages::InclusiveBound));
2363         return;
2364     }
2365 
2366     m_volume = vol;
2367     updateVolume();
2368     scheduleEvent(EventTypeNames::volumechange);
2369 }
2370 
muted() const2371 bool HTMLMediaElement::muted() const
2372 {
2373     return m_muted;
2374 }
2375 
setMuted(bool muted)2376 void HTMLMediaElement::setMuted(bool muted)
2377 {
2378     WTF_LOG(Media, "HTMLMediaElement::setMuted(%p, %s)", this, boolString(muted));
2379 
2380     if (m_muted == muted)
2381         return;
2382 
2383     m_muted = muted;
2384 
2385     updateVolume();
2386 
2387     scheduleEvent(EventTypeNames::volumechange);
2388 }
2389 
updateVolume()2390 void HTMLMediaElement::updateVolume()
2391 {
2392     if (webMediaPlayer())
2393         webMediaPlayer()->setVolume(effectiveMediaVolume());
2394 
2395     if (hasMediaControls())
2396         mediaControls()->updateVolume();
2397 }
2398 
effectiveMediaVolume() const2399 double HTMLMediaElement::effectiveMediaVolume() const
2400 {
2401     if (m_muted)
2402         return 0;
2403 
2404     if (m_mediaController && m_mediaController->muted())
2405         return 0;
2406 
2407     double volume = m_volume;
2408 
2409     if (m_mediaController)
2410         volume *= m_mediaController->volume();
2411 
2412     return volume;
2413 }
2414 
2415 // The spec says to fire periodic timeupdate events (those sent while playing) every
2416 // "15 to 250ms", we choose the slowest frequency
2417 static const double maxTimeupdateEventFrequency = 0.25;
2418 
startPlaybackProgressTimer()2419 void HTMLMediaElement::startPlaybackProgressTimer()
2420 {
2421     if (m_playbackProgressTimer.isActive())
2422         return;
2423 
2424     m_previousProgressTime = WTF::currentTime();
2425     m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency, FROM_HERE);
2426 }
2427 
playbackProgressTimerFired(Timer<HTMLMediaElement> *)2428 void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*)
2429 {
2430     ASSERT(m_player);
2431 
2432     if (m_fragmentEndTime != MediaPlayer::invalidTime() && currentTime() >= m_fragmentEndTime && directionOfPlayback() == Forward) {
2433         m_fragmentEndTime = MediaPlayer::invalidTime();
2434         if (!m_mediaController && !m_paused) {
2435             UseCounter::count(document(), UseCounter::HTMLMediaElementPauseAtFragmentEnd);
2436             // changes paused to true and fires a simple event named pause at the media element.
2437             pause();
2438         }
2439     }
2440 
2441     if (!m_seeking)
2442         scheduleTimeupdateEvent(true);
2443 
2444     if (!effectivePlaybackRate())
2445         return;
2446 
2447     if (!m_paused && hasMediaControls())
2448         mediaControls()->playbackProgressed();
2449 
2450     updateActiveTextTrackCues(currentTime());
2451 }
2452 
scheduleTimeupdateEvent(bool periodicEvent)2453 void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent)
2454 {
2455     double now = WTF::currentTime();
2456     double movieTime = currentTime();
2457 
2458     bool haveNotRecentlyFiredTimeupdate = (now - m_lastTimeUpdateEventWallTime) >= maxTimeupdateEventFrequency;
2459     bool movieTimeHasProgressed = movieTime != m_lastTimeUpdateEventMovieTime;
2460 
2461     // Non-periodic timeupdate events must always fire as mandated by the spec,
2462     // otherwise we shouldn't fire duplicate periodic timeupdate events when the
2463     // movie time hasn't changed.
2464     if (!periodicEvent || (haveNotRecentlyFiredTimeupdate && movieTimeHasProgressed)) {
2465         scheduleEvent(EventTypeNames::timeupdate);
2466         m_lastTimeUpdateEventWallTime = now;
2467         m_lastTimeUpdateEventMovieTime = movieTime;
2468     }
2469 }
2470 
togglePlayStateWillPlay() const2471 bool HTMLMediaElement::togglePlayStateWillPlay() const
2472 {
2473     if (m_mediaController)
2474         return m_mediaController->paused() || m_mediaController->isRestrained();
2475     return paused();
2476 }
2477 
togglePlayState()2478 void HTMLMediaElement::togglePlayState()
2479 {
2480     if (m_mediaController) {
2481         if (m_mediaController->isRestrained())
2482             m_mediaController->play();
2483         else if (m_mediaController->paused())
2484             m_mediaController->unpause();
2485         else
2486             m_mediaController->pause();
2487     } else {
2488         if (paused())
2489             play();
2490         else
2491             pause();
2492     }
2493 }
2494 
audioTracks()2495 AudioTrackList& HTMLMediaElement::audioTracks()
2496 {
2497     ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
2498     return *m_audioTracks;
2499 }
2500 
audioTrackChanged()2501 void HTMLMediaElement::audioTrackChanged()
2502 {
2503     WTF_LOG(Media, "HTMLMediaElement::audioTrackChanged(%p)", this);
2504     ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
2505 
2506     audioTracks().scheduleChangeEvent();
2507 
2508     // FIXME: Add call on m_mediaSource to notify it of track changes once the SourceBuffer.audioTracks attribute is added.
2509 
2510     if (!m_audioTracksTimer.isActive())
2511         m_audioTracksTimer.startOneShot(0, FROM_HERE);
2512 }
2513 
audioTracksTimerFired(Timer<HTMLMediaElement> *)2514 void HTMLMediaElement::audioTracksTimerFired(Timer<HTMLMediaElement>*)
2515 {
2516     Vector<WebMediaPlayer::TrackId> enabledTrackIds;
2517     for (unsigned i = 0; i < audioTracks().length(); ++i) {
2518         AudioTrack* track = audioTracks().anonymousIndexedGetter(i);
2519         if (track->enabled())
2520             enabledTrackIds.append(track->trackId());
2521     }
2522 
2523     webMediaPlayer()->enabledAudioTracksChanged(enabledTrackIds);
2524 }
2525 
addAudioTrack(const String & id,blink::WebMediaPlayerClient::AudioTrackKind kind,const AtomicString & label,const AtomicString & language,bool enabled)2526 WebMediaPlayer::TrackId HTMLMediaElement::addAudioTrack(const String& id, blink::WebMediaPlayerClient::AudioTrackKind kind, const AtomicString& label, const AtomicString& language, bool enabled)
2527 {
2528     AtomicString kindString = AudioKindToString(kind);
2529     WTF_LOG(Media, "HTMLMediaElement::addAudioTrack(%p, '%s', '%s', '%s', '%s', %d)",
2530         this, id.ascii().data(), kindString.ascii().data(), label.ascii().data(), language.ascii().data(), enabled);
2531 
2532     if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
2533         return 0;
2534 
2535     RefPtrWillBeRawPtr<AudioTrack> audioTrack = AudioTrack::create(id, kindString, label, language, enabled);
2536     audioTracks().add(audioTrack);
2537 
2538     return audioTrack->trackId();
2539 }
2540 
removeAudioTrack(WebMediaPlayer::TrackId trackId)2541 void HTMLMediaElement::removeAudioTrack(WebMediaPlayer::TrackId trackId)
2542 {
2543     WTF_LOG(Media, "HTMLMediaElement::removeAudioTrack(%p)", this);
2544 
2545     if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
2546         return;
2547 
2548     audioTracks().remove(trackId);
2549 }
2550 
videoTracks()2551 VideoTrackList& HTMLMediaElement::videoTracks()
2552 {
2553     ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
2554     return *m_videoTracks;
2555 }
2556 
selectedVideoTrackChanged(WebMediaPlayer::TrackId * selectedTrackId)2557 void HTMLMediaElement::selectedVideoTrackChanged(WebMediaPlayer::TrackId* selectedTrackId)
2558 {
2559     WTF_LOG(Media, "HTMLMediaElement::selectedVideoTrackChanged(%p)", this);
2560     ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
2561 
2562     if (selectedTrackId)
2563         videoTracks().trackSelected(*selectedTrackId);
2564 
2565     // FIXME: Add call on m_mediaSource to notify it of track changes once the SourceBuffer.videoTracks attribute is added.
2566 
2567     webMediaPlayer()->selectedVideoTrackChanged(selectedTrackId);
2568 }
2569 
addVideoTrack(const String & id,blink::WebMediaPlayerClient::VideoTrackKind kind,const AtomicString & label,const AtomicString & language,bool selected)2570 WebMediaPlayer::TrackId HTMLMediaElement::addVideoTrack(const String& id, blink::WebMediaPlayerClient::VideoTrackKind kind, const AtomicString& label, const AtomicString& language, bool selected)
2571 {
2572     AtomicString kindString = VideoKindToString(kind);
2573     WTF_LOG(Media, "HTMLMediaElement::addVideoTrack(%p, '%s', '%s', '%s', '%s', %d)",
2574         this, id.ascii().data(), kindString.ascii().data(), label.ascii().data(), language.ascii().data(), selected);
2575 
2576     if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
2577         return 0;
2578 
2579     // If another track was selected (potentially by the user), leave it selected.
2580     if (selected && videoTracks().selectedIndex() != -1)
2581         selected = false;
2582 
2583     RefPtrWillBeRawPtr<VideoTrack> videoTrack = VideoTrack::create(id, kindString, label, language, selected);
2584     videoTracks().add(videoTrack);
2585 
2586     return videoTrack->trackId();
2587 }
2588 
removeVideoTrack(WebMediaPlayer::TrackId trackId)2589 void HTMLMediaElement::removeVideoTrack(WebMediaPlayer::TrackId trackId)
2590 {
2591     WTF_LOG(Media, "HTMLMediaElement::removeVideoTrack(%p)", this);
2592 
2593     if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
2594         return;
2595 
2596     videoTracks().remove(trackId);
2597 }
2598 
mediaPlayerDidAddTextTrack(WebInbandTextTrack * webTrack)2599 void HTMLMediaElement::mediaPlayerDidAddTextTrack(WebInbandTextTrack* webTrack)
2600 {
2601     // 4.8.10.12.2 Sourcing in-band text tracks
2602     // 1. Associate the relevant data with a new text track and its corresponding new TextTrack object.
2603     RefPtrWillBeRawPtr<InbandTextTrack> textTrack = InbandTextTrack::create(webTrack);
2604 
2605     // 2. Set the new text track's kind, label, and language based on the semantics of the relevant data,
2606     // as defined by the relevant specification. If there is no label in that data, then the label must
2607     // be set to the empty string.
2608     // 3. Associate the text track list of cues with the rules for updating the text track rendering appropriate
2609     // for the format in question.
2610     // 4. If the new text track's kind is metadata, then set the text track in-band metadata track dispatch type
2611     // as follows, based on the type of the media resource:
2612     // 5. Populate the new text track's list of cues with the cues parsed so far, folllowing the guidelines for exposing
2613     // cues, and begin updating it dynamically as necessary.
2614     //   - Thess are all done by the media engine.
2615 
2616     // 6. Set the new text track's readiness state to loaded.
2617     textTrack->setReadinessState(TextTrack::Loaded);
2618 
2619     // 7. Set the new text track's mode to the mode consistent with the user's preferences and the requirements of
2620     // the relevant specification for the data.
2621     //  - This will happen in configureTextTracks()
2622     scheduleDelayedAction(LoadTextTrackResource);
2623 
2624     // 8. Add the new text track to the media element's list of text tracks.
2625     // 9. Fire an event with the name addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent
2626     // interface, with the track attribute initialized to the text track's TextTrack object, at the media element's
2627     // textTracks attribute's TextTrackList object.
2628     addTextTrack(textTrack.get());
2629 }
2630 
mediaPlayerDidRemoveTextTrack(WebInbandTextTrack * webTrack)2631 void HTMLMediaElement::mediaPlayerDidRemoveTextTrack(WebInbandTextTrack* webTrack)
2632 {
2633     if (!m_textTracks)
2634         return;
2635 
2636     // This cast is safe because we created the InbandTextTrack with the WebInbandTextTrack
2637     // passed to mediaPlayerDidAddTextTrack.
2638     RefPtrWillBeRawPtr<InbandTextTrack> textTrack = static_cast<InbandTextTrack*>(webTrack->client());
2639     if (!textTrack)
2640         return;
2641 
2642     removeTextTrack(textTrack.get());
2643 }
2644 
textTracksChanged()2645 void HTMLMediaElement::textTracksChanged()
2646 {
2647     if (hasMediaControls())
2648         mediaControls()->textTracksChanged();
2649 }
2650 
addTextTrack(TextTrack * track)2651 void HTMLMediaElement::addTextTrack(TextTrack* track)
2652 {
2653     textTracks()->append(track);
2654 
2655     textTracksChanged();
2656 }
2657 
removeTextTrack(TextTrack * track)2658 void HTMLMediaElement::removeTextTrack(TextTrack* track)
2659 {
2660     TrackDisplayUpdateScope scope(this);
2661     m_textTracks->remove(track);
2662 
2663     textTracksChanged();
2664 }
2665 
forgetResourceSpecificTracks()2666 void HTMLMediaElement::forgetResourceSpecificTracks()
2667 {
2668     // Implements the "forget the media element's media-resource-specific tracks" algorithm.
2669     // The order is explicitly specified as text, then audio, and finally video. Also
2670     // 'removetrack' events should not be fired.
2671     if (m_textTracks) {
2672         TrackDisplayUpdateScope scope(this);
2673         m_textTracks->removeAllInbandTracks();
2674         textTracksChanged();
2675     }
2676 
2677     m_audioTracks->removeAll();
2678     m_videoTracks->removeAll();
2679 
2680     m_audioTracksTimer.stop();
2681 }
2682 
addTextTrack(const AtomicString & kind,const AtomicString & label,const AtomicString & language,ExceptionState & exceptionState)2683 PassRefPtrWillBeRawPtr<TextTrack> HTMLMediaElement::addTextTrack(const AtomicString& kind, const AtomicString& label, const AtomicString& language, ExceptionState& exceptionState)
2684 {
2685     // 4.8.10.12.4 Text track API
2686     // The addTextTrack(kind, label, language) method of media elements, when invoked, must run the following steps:
2687 
2688     // 1. If kind is not one of the following strings, then throw a SyntaxError exception and abort these steps
2689     if (!TextTrack::isValidKindKeyword(kind)) {
2690         exceptionState.throwDOMException(SyntaxError, "The 'kind' provided ('" + kind + "') is invalid.");
2691         return nullptr;
2692     }
2693 
2694     // 2. If the label argument was omitted, let label be the empty string.
2695     // 3. If the language argument was omitted, let language be the empty string.
2696     // 4. Create a new TextTrack object.
2697 
2698     // 5. Create a new text track corresponding to the new object, and set its text track kind to kind, its text
2699     // track label to label, its text track language to language...
2700     RefPtrWillBeRawPtr<TextTrack> textTrack = TextTrack::create(kind, label, language);
2701 
2702     // Note, due to side effects when changing track parameters, we have to
2703     // first append the track to the text track list.
2704 
2705     // 6. Add the new text track to the media element's list of text tracks.
2706     addTextTrack(textTrack.get());
2707 
2708     // ... its text track readiness state to the text track loaded state ...
2709     textTrack->setReadinessState(TextTrack::Loaded);
2710 
2711     // ... its text track mode to the text track hidden mode, and its text track list of cues to an empty list ...
2712     textTrack->setMode(TextTrack::hiddenKeyword());
2713 
2714     return textTrack.release();
2715 }
2716 
textTracks()2717 TextTrackList* HTMLMediaElement::textTracks()
2718 {
2719     if (!m_textTracks)
2720         m_textTracks = TextTrackList::create(this);
2721 
2722     return m_textTracks.get();
2723 }
2724 
didAddTrackElement(HTMLTrackElement * trackElement)2725 void HTMLMediaElement::didAddTrackElement(HTMLTrackElement* trackElement)
2726 {
2727     // 4.8.10.12.3 Sourcing out-of-band text tracks
2728     // When a track element's parent element changes and the new parent is a media element,
2729     // then the user agent must add the track element's corresponding text track to the
2730     // media element's list of text tracks ... [continues in TextTrackList::append]
2731     RefPtrWillBeRawPtr<TextTrack> textTrack = trackElement->track();
2732     if (!textTrack)
2733         return;
2734 
2735     addTextTrack(textTrack.get());
2736 
2737     // Do not schedule the track loading until parsing finishes so we don't start before all tracks
2738     // in the markup have been added.
2739     if (isFinishedParsingChildren())
2740         scheduleDelayedAction(LoadTextTrackResource);
2741 }
2742 
didRemoveTrackElement(HTMLTrackElement * trackElement)2743 void HTMLMediaElement::didRemoveTrackElement(HTMLTrackElement* trackElement)
2744 {
2745 #if !LOG_DISABLED
2746     KURL url = trackElement->getNonEmptyURLAttribute(srcAttr);
2747     WTF_LOG(Media, "HTMLMediaElement::didRemoveTrackElement(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data());
2748 #endif
2749 
2750     RefPtrWillBeRawPtr<TextTrack> textTrack = trackElement->track();
2751     if (!textTrack)
2752         return;
2753 
2754     textTrack->setHasBeenConfigured(false);
2755 
2756     if (!m_textTracks)
2757         return;
2758 
2759     // 4.8.10.12.3 Sourcing out-of-band text tracks
2760     // When a track element's parent element changes and the old parent was a media element,
2761     // then the user agent must remove the track element's corresponding text track from the
2762     // media element's list of text tracks.
2763     removeTextTrack(textTrack.get());
2764 
2765     size_t index = m_textTracksWhenResourceSelectionBegan.find(textTrack.get());
2766     if (index != kNotFound)
2767         m_textTracksWhenResourceSelectionBegan.remove(index);
2768 }
2769 
textTrackLanguageSelectionScore(const TextTrack & track)2770 static int textTrackLanguageSelectionScore(const TextTrack& track)
2771 {
2772     if (track.language().isEmpty())
2773         return 0;
2774 
2775     Vector<AtomicString> languages = userPreferredLanguages();
2776     size_t languageMatchIndex = indexOfBestMatchingLanguageInList(track.language(), languages);
2777     if (languageMatchIndex >= languages.size())
2778         return 0;
2779 
2780     return languages.size() - languageMatchIndex;
2781 }
2782 
textTrackSelectionScore(const TextTrack & track)2783 static int textTrackSelectionScore(const TextTrack& track)
2784 {
2785     if (track.kind() != TextTrack::captionsKeyword() && track.kind() != TextTrack::subtitlesKeyword())
2786         return 0;
2787 
2788     return textTrackLanguageSelectionScore(track);
2789 }
2790 
configureTextTrackGroup(const TrackGroup & group)2791 void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group)
2792 {
2793     ASSERT(group.tracks.size());
2794 
2795     WTF_LOG(Media, "HTMLMediaElement::configureTextTrackGroup(%p, %d)", this, group.kind);
2796 
2797     // First, find the track in the group that should be enabled (if any).
2798     WillBeHeapVector<RefPtrWillBeMember<TextTrack> > currentlyEnabledTracks;
2799     RefPtrWillBeRawPtr<TextTrack> trackToEnable = nullptr;
2800     RefPtrWillBeRawPtr<TextTrack> defaultTrack = nullptr;
2801     RefPtrWillBeRawPtr<TextTrack> fallbackTrack = nullptr;
2802     int highestTrackScore = 0;
2803     for (size_t i = 0; i < group.tracks.size(); ++i) {
2804         RefPtrWillBeRawPtr<TextTrack> textTrack = group.tracks[i];
2805 
2806         if (m_processingPreferenceChange && textTrack->mode() == TextTrack::showingKeyword())
2807             currentlyEnabledTracks.append(textTrack);
2808 
2809         int trackScore = textTrackSelectionScore(*textTrack);
2810         if (trackScore) {
2811             // * If the text track kind is { [subtitles or captions] [descriptions] } and the user has indicated an interest in having a
2812             // track with this text track kind, text track language, and text track label enabled, and there is no
2813             // other text track in the media element's list of text tracks with a text track kind of either subtitles
2814             // or captions whose text track mode is showing
2815             // ...
2816             // * If the text track kind is chapters and the text track language is one that the user agent has reason
2817             // to believe is appropriate for the user, and there is no other text track in the media element's list of
2818             // text tracks with a text track kind of chapters whose text track mode is showing
2819             //    Let the text track mode be showing.
2820             if (trackScore > highestTrackScore) {
2821                 highestTrackScore = trackScore;
2822                 trackToEnable = textTrack;
2823             }
2824 
2825             if (!defaultTrack && textTrack->isDefault())
2826                 defaultTrack = textTrack;
2827             if (!defaultTrack && !fallbackTrack)
2828                 fallbackTrack = textTrack;
2829         } else if (!group.visibleTrack && !defaultTrack && textTrack->isDefault()) {
2830             // * If the track element has a default attribute specified, and there is no other text track in the media
2831             // element's list of text tracks whose text track mode is showing or showing by default
2832             //    Let the text track mode be showing by default.
2833             defaultTrack = textTrack;
2834         }
2835     }
2836 
2837     if (!trackToEnable && defaultTrack)
2838         trackToEnable = defaultTrack;
2839 
2840     // If no track matches the user's preferred language and non was marked 'default', enable the first track
2841     // because the user has explicitly stated a preference for this kind of track.
2842     if (!fallbackTrack && m_closedCaptionsVisible && group.kind == TrackGroup::CaptionsAndSubtitles)
2843         fallbackTrack = group.tracks[0];
2844 
2845     if (!trackToEnable && fallbackTrack)
2846         trackToEnable = fallbackTrack;
2847 
2848     if (currentlyEnabledTracks.size()) {
2849         for (size_t i = 0; i < currentlyEnabledTracks.size(); ++i) {
2850             RefPtrWillBeRawPtr<TextTrack> textTrack = currentlyEnabledTracks[i];
2851             if (textTrack != trackToEnable)
2852                 textTrack->setMode(TextTrack::disabledKeyword());
2853         }
2854     }
2855 
2856     if (trackToEnable)
2857         trackToEnable->setMode(TextTrack::showingKeyword());
2858 }
2859 
configureTextTracks()2860 void HTMLMediaElement::configureTextTracks()
2861 {
2862     TrackGroup captionAndSubtitleTracks(TrackGroup::CaptionsAndSubtitles);
2863     TrackGroup descriptionTracks(TrackGroup::Description);
2864     TrackGroup chapterTracks(TrackGroup::Chapter);
2865     TrackGroup metadataTracks(TrackGroup::Metadata);
2866     TrackGroup otherTracks(TrackGroup::Other);
2867 
2868     if (!m_textTracks)
2869         return;
2870 
2871     for (size_t i = 0; i < m_textTracks->length(); ++i) {
2872         RefPtrWillBeRawPtr<TextTrack> textTrack = m_textTracks->item(i);
2873         if (!textTrack)
2874             continue;
2875 
2876         String kind = textTrack->kind();
2877         TrackGroup* currentGroup;
2878         if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword())
2879             currentGroup = &captionAndSubtitleTracks;
2880         else if (kind == TextTrack::descriptionsKeyword())
2881             currentGroup = &descriptionTracks;
2882         else if (kind == TextTrack::chaptersKeyword())
2883             currentGroup = &chapterTracks;
2884         else if (kind == TextTrack::metadataKeyword())
2885             currentGroup = &metadataTracks;
2886         else
2887             currentGroup = &otherTracks;
2888 
2889         if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::showingKeyword())
2890             currentGroup->visibleTrack = textTrack;
2891         if (!currentGroup->defaultTrack && textTrack->isDefault())
2892             currentGroup->defaultTrack = textTrack;
2893 
2894         // Do not add this track to the group if it has already been automatically configured
2895         // as we only want to call configureTextTrack once per track so that adding another
2896         // track after the initial configuration doesn't reconfigure every track - only those
2897         // that should be changed by the new addition. For example all metadata tracks are
2898         // disabled by default, and we don't want a track that has been enabled by script
2899         // to be disabled automatically when a new metadata track is added later.
2900         if (textTrack->hasBeenConfigured())
2901             continue;
2902 
2903         if (textTrack->language().length())
2904             currentGroup->hasSrcLang = true;
2905         currentGroup->tracks.append(textTrack);
2906     }
2907 
2908     if (captionAndSubtitleTracks.tracks.size())
2909         configureTextTrackGroup(captionAndSubtitleTracks);
2910     if (descriptionTracks.tracks.size())
2911         configureTextTrackGroup(descriptionTracks);
2912     if (chapterTracks.tracks.size())
2913         configureTextTrackGroup(chapterTracks);
2914     if (metadataTracks.tracks.size())
2915         configureTextTrackGroup(metadataTracks);
2916     if (otherTracks.tracks.size())
2917         configureTextTrackGroup(otherTracks);
2918 
2919     textTracksChanged();
2920 }
2921 
havePotentialSourceChild()2922 bool HTMLMediaElement::havePotentialSourceChild()
2923 {
2924     // Stash the current <source> node and next nodes so we can restore them after checking
2925     // to see there is another potential.
2926     RefPtrWillBeRawPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode;
2927     RefPtrWillBeRawPtr<Node> nextNode = m_nextChildNodeToConsider;
2928 
2929     KURL nextURL = selectNextSourceChild(0, 0, DoNothing);
2930 
2931     m_currentSourceNode = currentSourceNode;
2932     m_nextChildNodeToConsider = nextNode;
2933 
2934     return nextURL.isValid();
2935 }
2936 
selectNextSourceChild(ContentType * contentType,String * keySystem,InvalidURLAction actionIfInvalid)2937 KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid)
2938 {
2939 #if !LOG_DISABLED
2940     // Don't log if this was just called to find out if there are any valid <source> elements.
2941     bool shouldLog = actionIfInvalid != DoNothing;
2942     if (shouldLog)
2943         WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p)", this);
2944 #endif
2945 
2946     if (!m_nextChildNodeToConsider) {
2947 #if !LOG_DISABLED
2948         if (shouldLog)
2949             WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) -> 0x0000, \"\"", this);
2950 #endif
2951         return KURL();
2952     }
2953 
2954     KURL mediaURL;
2955     Node* node;
2956     HTMLSourceElement* source = 0;
2957     String type;
2958     String system;
2959     bool lookingForStartNode = m_nextChildNodeToConsider;
2960     bool canUseSourceElement = false;
2961 
2962     NodeVector potentialSourceNodes;
2963     getChildNodes(*this, potentialSourceNodes);
2964 
2965     for (unsigned i = 0; !canUseSourceElement && i < potentialSourceNodes.size(); ++i) {
2966         node = potentialSourceNodes[i].get();
2967         if (lookingForStartNode && m_nextChildNodeToConsider != node)
2968             continue;
2969         lookingForStartNode = false;
2970 
2971         if (!isHTMLSourceElement(*node))
2972             continue;
2973         if (node->parentNode() != this)
2974             continue;
2975 
2976         source = toHTMLSourceElement(node);
2977 
2978         // 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
2979         mediaURL = source->getNonEmptyURLAttribute(srcAttr);
2980 #if !LOG_DISABLED
2981         if (shouldLog)
2982             WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) - 'src' is %s", this, urlForLoggingMedia(mediaURL).utf8().data());
2983 #endif
2984         if (mediaURL.isEmpty())
2985             goto checkAgain;
2986 
2987         type = source->type();
2988         // FIXME(82965): Add support for keySystem in <source> and set system from source.
2989         if (type.isEmpty() && mediaURL.protocolIsData())
2990             type = mimeTypeFromDataURL(mediaURL);
2991         if (!type.isEmpty() || !system.isEmpty()) {
2992 #if !LOG_DISABLED
2993             if (shouldLog)
2994                 WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) - 'type' is '%s' - key system is '%s'", this, type.utf8().data(), system.utf8().data());
2995 #endif
2996             if (!supportsType(ContentType(type), system))
2997                 goto checkAgain;
2998         }
2999 
3000         // Is it safe to load this url?
3001         if (!isSafeToLoadURL(mediaURL, actionIfInvalid))
3002             goto checkAgain;
3003 
3004         // Making it this far means the <source> looks reasonable.
3005         canUseSourceElement = true;
3006 
3007 checkAgain:
3008         if (!canUseSourceElement && actionIfInvalid == Complain && source)
3009             source->scheduleErrorEvent();
3010     }
3011 
3012     if (canUseSourceElement) {
3013         if (contentType)
3014             *contentType = ContentType(type);
3015         if (keySystem)
3016             *keySystem = system;
3017         m_currentSourceNode = source;
3018         m_nextChildNodeToConsider = source->nextSibling();
3019     } else {
3020         m_currentSourceNode = nullptr;
3021         m_nextChildNodeToConsider = nullptr;
3022     }
3023 
3024 #if !LOG_DISABLED
3025     if (shouldLog)
3026         WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) -> %p, %s", this, m_currentSourceNode.get(), canUseSourceElement ? urlForLoggingMedia(mediaURL).utf8().data() : "");
3027 #endif
3028     return canUseSourceElement ? mediaURL : KURL();
3029 }
3030 
sourceWasAdded(HTMLSourceElement * source)3031 void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source)
3032 {
3033     WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded(%p, %p)", this, source);
3034 
3035 #if !LOG_DISABLED
3036     KURL url = source->getNonEmptyURLAttribute(srcAttr);
3037     WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data());
3038 #endif
3039 
3040     // We should only consider a <source> element when there is not src attribute at all.
3041     if (fastHasAttribute(srcAttr))
3042         return;
3043 
3044     // 4.8.8 - If a source element is inserted as a child of a media element that has no src
3045     // attribute and whose networkState has the value NETWORK_EMPTY, the user agent must invoke
3046     // the media element's resource selection algorithm.
3047     if (networkState() == HTMLMediaElement::NETWORK_EMPTY) {
3048         scheduleDelayedAction(LoadMediaResource);
3049         m_nextChildNodeToConsider = source;
3050         return;
3051     }
3052 
3053     if (m_currentSourceNode && source == m_currentSourceNode->nextSibling()) {
3054         WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded(%p) - <source> inserted immediately after current source", this);
3055         m_nextChildNodeToConsider = source;
3056         return;
3057     }
3058 
3059     if (m_nextChildNodeToConsider)
3060         return;
3061 
3062     if (m_loadState != WaitingForSource)
3063         return;
3064 
3065     // 4.8.9.5, resource selection algorithm, source elements section:
3066     // 21. Wait until the node after pointer is a node other than the end of the list. (This step might wait forever.)
3067     // 22. Asynchronously await a stable state...
3068     // 23. Set the element's delaying-the-load-event flag back to true (this delays the load event again, in case
3069     // it hasn't been fired yet).
3070     setShouldDelayLoadEvent(true);
3071 
3072     // 24. Set the networkState back to NETWORK_LOADING.
3073     m_networkState = NETWORK_LOADING;
3074 
3075     // 25. Jump back to the find next candidate step above.
3076     m_nextChildNodeToConsider = source;
3077     scheduleNextSourceChild();
3078 }
3079 
sourceWasRemoved(HTMLSourceElement * source)3080 void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement* source)
3081 {
3082     WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p, %p)", this, source);
3083 
3084 #if !LOG_DISABLED
3085     KURL url = source->getNonEmptyURLAttribute(srcAttr);
3086     WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data());
3087 #endif
3088 
3089     if (source != m_currentSourceNode && source != m_nextChildNodeToConsider)
3090         return;
3091 
3092     if (source == m_nextChildNodeToConsider) {
3093         if (m_currentSourceNode)
3094             m_nextChildNodeToConsider = m_currentSourceNode->nextSibling();
3095         WTF_LOG(Media, "HTMLMediaElement::sourceRemoved(%p) - m_nextChildNodeToConsider set to %p", this, m_nextChildNodeToConsider.get());
3096     } else if (source == m_currentSourceNode) {
3097         // Clear the current source node pointer, but don't change the movie as the spec says:
3098         // 4.8.8 - Dynamically modifying a source element and its attribute when the element is already
3099         // inserted in a video or audio element will have no effect.
3100         m_currentSourceNode = nullptr;
3101         WTF_LOG(Media, "HTMLMediaElement::sourceRemoved(%p) - m_currentSourceNode set to 0", this);
3102     }
3103 }
3104 
mediaPlayerTimeChanged()3105 void HTMLMediaElement::mediaPlayerTimeChanged()
3106 {
3107     WTF_LOG(Media, "HTMLMediaElement::mediaPlayerTimeChanged(%p)", this);
3108 
3109     updateActiveTextTrackCues(currentTime());
3110 
3111     invalidateCachedTime();
3112 
3113     // 4.8.10.9 steps 12-14. Needed if no ReadyState change is associated with the seek.
3114     if (m_seeking && m_readyState >= HAVE_CURRENT_DATA && !webMediaPlayer()->seeking())
3115         finishSeek();
3116 
3117     // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity,
3118     // it will only queue a 'timeupdate' event if we haven't already posted one at the current
3119     // movie time.
3120     scheduleTimeupdateEvent(false);
3121 
3122     double now = currentTime();
3123     double dur = duration();
3124 
3125     // When the current playback position reaches the end of the media resource when the direction of
3126     // playback is forwards, then the user agent must follow these steps:
3127     if (!std::isnan(dur) && dur && now >= dur && directionOfPlayback() == Forward) {
3128         // If the media element has a loop attribute specified and does not have a current media controller,
3129         if (loop() && !m_mediaController) {
3130             m_sentEndEvent = false;
3131             //  then seek to the earliest possible position of the media resource and abort these steps.
3132             seek(0);
3133         } else {
3134             // If the media element does not have a current media controller, and the media element
3135             // has still ended playback, and the direction of playback is still forwards, and paused
3136             // is false,
3137             if (!m_mediaController && !m_paused) {
3138                 // changes paused to true and fires a simple event named pause at the media element.
3139                 m_paused = true;
3140                 scheduleEvent(EventTypeNames::pause);
3141             }
3142             // Queue a task to fire a simple event named ended at the media element.
3143             if (!m_sentEndEvent) {
3144                 m_sentEndEvent = true;
3145                 scheduleEvent(EventTypeNames::ended);
3146             }
3147             // If the media element has a current media controller, then report the controller state
3148             // for the media element's current media controller.
3149             updateMediaController();
3150         }
3151     } else {
3152         m_sentEndEvent = false;
3153     }
3154 
3155     updatePlayState();
3156 }
3157 
mediaPlayerDurationChanged()3158 void HTMLMediaElement::mediaPlayerDurationChanged()
3159 {
3160     WTF_LOG(Media, "HTMLMediaElement::mediaPlayerDurationChanged(%p)", this);
3161     // FIXME: Change MediaPlayerClient & WebMediaPlayer to convey
3162     // the currentTime when the duration change occured. The current
3163     // WebMediaPlayer implementations always clamp currentTime() to
3164     // duration() so the requestSeek condition here is always false.
3165     durationChanged(duration(), currentTime() > duration());
3166 }
3167 
durationChanged(double duration,bool requestSeek)3168 void HTMLMediaElement::durationChanged(double duration, bool requestSeek)
3169 {
3170     WTF_LOG(Media, "HTMLMediaElement::durationChanged(%p, %f, %d)", this, duration, requestSeek);
3171 
3172     // Abort if duration unchanged.
3173     if (m_duration == duration)
3174         return;
3175 
3176     WTF_LOG(Media, "HTMLMediaElement::durationChanged(%p) : %f -> %f", this, m_duration, duration);
3177     m_duration = duration;
3178     scheduleEvent(EventTypeNames::durationchange);
3179 
3180     if (hasMediaControls())
3181         mediaControls()->reset();
3182     if (renderer())
3183         renderer()->updateFromElement();
3184 
3185     if (requestSeek)
3186         seek(duration);
3187 }
3188 
mediaPlayerPlaybackStateChanged()3189 void HTMLMediaElement::mediaPlayerPlaybackStateChanged()
3190 {
3191     WTF_LOG(Media, "HTMLMediaElement::mediaPlayerPlaybackStateChanged(%p)", this);
3192 
3193     if (!webMediaPlayer())
3194         return;
3195 
3196     if (webMediaPlayer()->paused())
3197         pause();
3198     else
3199         playInternal();
3200 }
3201 
mediaPlayerRequestFullscreen()3202 void HTMLMediaElement::mediaPlayerRequestFullscreen()
3203 {
3204     WTF_LOG(Media, "HTMLMediaElement::mediaPlayerRequestFullscreen(%p)", this);
3205 
3206     // The player is responsible for only invoking this callback in response to
3207     // user interaction or when it is technically required to play the video.
3208     UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
3209 
3210     enterFullscreen();
3211 }
3212 
mediaPlayerRequestSeek(double time)3213 void HTMLMediaElement::mediaPlayerRequestSeek(double time)
3214 {
3215     // The player is the source of this seek request.
3216     if (m_mediaController) {
3217         m_mediaController->setCurrentTime(time);
3218         return;
3219     }
3220     setCurrentTime(time, ASSERT_NO_EXCEPTION);
3221 }
3222 
remoteRouteAvailabilityChanged(bool routesAvailable)3223 void HTMLMediaElement::remoteRouteAvailabilityChanged(bool routesAvailable)
3224 {
3225     m_remoteRoutesAvailable = routesAvailable;
3226     if (hasMediaControls())
3227         mediaControls()->refreshCastButtonVisibility();
3228 }
3229 
connectedToRemoteDevice()3230 void HTMLMediaElement::connectedToRemoteDevice()
3231 {
3232     m_playingRemotely = true;
3233     if (hasMediaControls())
3234         mediaControls()->startedCasting();
3235 }
3236 
disconnectedFromRemoteDevice()3237 void HTMLMediaElement::disconnectedFromRemoteDevice()
3238 {
3239     m_playingRemotely = false;
3240     if (hasMediaControls())
3241         mediaControls()->stoppedCasting();
3242 }
3243 
3244 // MediaPlayerPresentation methods
mediaPlayerRepaint()3245 void HTMLMediaElement::mediaPlayerRepaint()
3246 {
3247     if (m_webLayer)
3248         m_webLayer->invalidate();
3249 
3250     updateDisplayState();
3251     if (renderer())
3252         renderer()->setShouldDoFullPaintInvalidation(true);
3253 }
3254 
mediaPlayerSizeChanged()3255 void HTMLMediaElement::mediaPlayerSizeChanged()
3256 {
3257     WTF_LOG(Media, "HTMLMediaElement::mediaPlayerSizeChanged(%p)", this);
3258 
3259     ASSERT(hasVideo()); // "resize" makes no sense absent video.
3260     if (m_readyState > HAVE_NOTHING && isHTMLVideoElement())
3261         scheduleEvent(EventTypeNames::resize);
3262 
3263     if (renderer())
3264         renderer()->updateFromElement();
3265 }
3266 
buffered() const3267 PassRefPtrWillBeRawPtr<TimeRanges> HTMLMediaElement::buffered() const
3268 {
3269     if (m_mediaSource)
3270         return m_mediaSource->buffered();
3271 
3272     if (!webMediaPlayer())
3273         return TimeRanges::create();
3274 
3275     return TimeRanges::create(webMediaPlayer()->buffered());
3276 }
3277 
played()3278 PassRefPtrWillBeRawPtr<TimeRanges> HTMLMediaElement::played()
3279 {
3280     if (m_playing) {
3281         double time = currentTime();
3282         if (time > m_lastSeekTime)
3283             addPlayedRange(m_lastSeekTime, time);
3284     }
3285 
3286     if (!m_playedTimeRanges)
3287         m_playedTimeRanges = TimeRanges::create();
3288 
3289     return m_playedTimeRanges->copy();
3290 }
3291 
seekable() const3292 PassRefPtrWillBeRawPtr<TimeRanges> HTMLMediaElement::seekable() const
3293 {
3294     if (webMediaPlayer()) {
3295         double maxTimeSeekable = webMediaPlayer()->maxTimeSeekable();
3296         if (maxTimeSeekable)
3297             return TimeRanges::create(0, maxTimeSeekable);
3298     }
3299     return TimeRanges::create();
3300 }
3301 
potentiallyPlaying() const3302 bool HTMLMediaElement::potentiallyPlaying() const
3303 {
3304     // "pausedToBuffer" means the media engine's rate is 0, but only because it had to stop playing
3305     // when it ran out of buffered data. A movie is this state is "potentially playing", modulo the
3306     // checks in couldPlayIfEnoughData().
3307     bool pausedToBuffer = m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA;
3308     return (pausedToBuffer || m_readyState >= HAVE_FUTURE_DATA) && couldPlayIfEnoughData() && !isBlockedOnMediaController();
3309 }
3310 
couldPlayIfEnoughData() const3311 bool HTMLMediaElement::couldPlayIfEnoughData() const
3312 {
3313     return !paused() && !endedPlayback() && !stoppedDueToErrors();
3314 }
3315 
endedPlayback() const3316 bool HTMLMediaElement::endedPlayback() const
3317 {
3318     double dur = duration();
3319     if (!m_player || std::isnan(dur))
3320         return false;
3321 
3322     // 4.8.10.8 Playing the media resource
3323 
3324     // A media element is said to have ended playback when the element's
3325     // readyState attribute is HAVE_METADATA or greater,
3326     if (m_readyState < HAVE_METADATA)
3327         return false;
3328 
3329     // and the current playback position is the end of the media resource and the direction
3330     // of playback is forwards, Either the media element does not have a loop attribute specified,
3331     // or the media element has a current media controller.
3332     double now = currentTime();
3333     if (directionOfPlayback() == Forward)
3334         return dur > 0 && now >= dur && (!loop() || m_mediaController);
3335 
3336     // or the current playback position is the earliest possible position and the direction
3337     // of playback is backwards
3338     ASSERT(directionOfPlayback() == Backward);
3339     return now <= 0;
3340 }
3341 
stoppedDueToErrors() const3342 bool HTMLMediaElement::stoppedDueToErrors() const
3343 {
3344     if (m_readyState >= HAVE_METADATA && m_error) {
3345         RefPtrWillBeRawPtr<TimeRanges> seekableRanges = seekable();
3346         if (!seekableRanges->contain(currentTime()))
3347             return true;
3348     }
3349 
3350     return false;
3351 }
3352 
updatePlayState()3353 void HTMLMediaElement::updatePlayState()
3354 {
3355     if (!m_player)
3356         return;
3357 
3358     bool isPlaying = webMediaPlayer() && !webMediaPlayer()->paused();
3359     bool shouldBePlaying = potentiallyPlaying();
3360 
3361     WTF_LOG(Media, "HTMLMediaElement::updatePlayState(%p) - shouldBePlaying = %s, isPlaying = %s",
3362         this, boolString(shouldBePlaying), boolString(isPlaying));
3363 
3364     if (shouldBePlaying) {
3365         setDisplayMode(Video);
3366         invalidateCachedTime();
3367 
3368         if (!isPlaying) {
3369             // Set rate, muted before calling play in case they were set before the media engine was setup.
3370             // The media engine should just stash the rate and muted values since it isn't already playing.
3371             webMediaPlayer()->setRate(effectivePlaybackRate());
3372             updateVolume();
3373             webMediaPlayer()->play();
3374         }
3375 
3376         if (hasMediaControls())
3377             mediaControls()->playbackStarted();
3378         startPlaybackProgressTimer();
3379         m_playing = true;
3380 
3381     } else { // Should not be playing right now
3382         if (isPlaying)
3383             webMediaPlayer()->pause();
3384         refreshCachedTime();
3385 
3386         m_playbackProgressTimer.stop();
3387         m_playing = false;
3388         double time = currentTime();
3389         if (time > m_lastSeekTime)
3390             addPlayedRange(m_lastSeekTime, time);
3391 
3392         if (couldPlayIfEnoughData())
3393             prepareToPlay();
3394 
3395         if (hasMediaControls())
3396             mediaControls()->playbackStopped();
3397     }
3398 
3399     updateMediaController();
3400 
3401     if (renderer())
3402         renderer()->updateFromElement();
3403 }
3404 
stopPeriodicTimers()3405 void HTMLMediaElement::stopPeriodicTimers()
3406 {
3407     m_progressEventTimer.stop();
3408     m_playbackProgressTimer.stop();
3409 }
3410 
userCancelledLoad()3411 void HTMLMediaElement::userCancelledLoad()
3412 {
3413     WTF_LOG(Media, "HTMLMediaElement::userCancelledLoad(%p)", this);
3414 
3415     // If the media data fetching process is aborted by the user:
3416 
3417     // 1 - The user agent should cancel the fetching process.
3418     clearMediaPlayer(-1);
3419 
3420     if (m_networkState == NETWORK_EMPTY || m_completelyLoaded)
3421         return;
3422 
3423     // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED.
3424     m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED);
3425 
3426     // 3 - Queue a task to fire a simple event named error at the media element.
3427     scheduleEvent(EventTypeNames::abort);
3428 
3429     closeMediaSource();
3430 
3431     // 4 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the
3432     // element's networkState attribute to the NETWORK_EMPTY value and queue a task to fire a
3433     // simple event named emptied at the element. Otherwise, set the element's networkState
3434     // attribute to the NETWORK_IDLE value.
3435     if (m_readyState == HAVE_NOTHING) {
3436         m_networkState = NETWORK_EMPTY;
3437         scheduleEvent(EventTypeNames::emptied);
3438     } else {
3439         m_networkState = NETWORK_IDLE;
3440     }
3441 
3442     // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
3443     setShouldDelayLoadEvent(false);
3444 
3445     // 6 - Abort the overall resource selection algorithm.
3446     m_currentSourceNode = nullptr;
3447 
3448     // Reset m_readyState since m_player is gone.
3449     m_readyState = HAVE_NOTHING;
3450     invalidateCachedTime();
3451     updateMediaController();
3452     updateActiveTextTrackCues(0);
3453 }
3454 
clearMediaPlayerAndAudioSourceProviderClientWithoutLocking()3455 void HTMLMediaElement::clearMediaPlayerAndAudioSourceProviderClientWithoutLocking()
3456 {
3457 #if ENABLE(WEB_AUDIO)
3458     if (audioSourceProvider())
3459         audioSourceProvider()->setClient(0);
3460 #endif
3461     m_player.clear();
3462 }
3463 
clearMediaPlayer(int flags)3464 void HTMLMediaElement::clearMediaPlayer(int flags)
3465 {
3466     forgetResourceSpecificTracks();
3467 
3468     closeMediaSource();
3469 
3470     cancelDeferredLoad();
3471 
3472     {
3473         AudioSourceProviderClientLockScope scope(*this);
3474         clearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
3475     }
3476 
3477     stopPeriodicTimers();
3478     m_loadTimer.stop();
3479 
3480     m_pendingActionFlags &= ~flags;
3481     m_loadState = WaitingForSource;
3482 
3483     // We can't cast if we don't have a media player.
3484     m_remoteRoutesAvailable = false;
3485     m_playingRemotely = false;
3486     if (hasMediaControls()) {
3487         mediaControls()->refreshCastButtonVisibility();
3488     }
3489 
3490     if (m_textTracks)
3491         configureTextTrackDisplay(AssumeNoVisibleChange);
3492 }
3493 
stop()3494 void HTMLMediaElement::stop()
3495 {
3496     WTF_LOG(Media, "HTMLMediaElement::stop(%p)", this);
3497 
3498     m_active = false;
3499     userCancelledLoad();
3500 
3501     // Stop the playback without generating events
3502     m_playing = false;
3503     m_paused = true;
3504     m_seeking = false;
3505 
3506     if (renderer())
3507         renderer()->updateFromElement();
3508 
3509     stopPeriodicTimers();
3510     cancelPendingEventsAndCallbacks();
3511 
3512     m_asyncEventQueue->close();
3513 
3514     // Ensure that hasPendingActivity() is not preventing garbage collection, since otherwise this
3515     // media element will simply leak.
3516     ASSERT(!hasPendingActivity());
3517 }
3518 
hasPendingActivity() const3519 bool HTMLMediaElement::hasPendingActivity() const
3520 {
3521     // The delaying-the-load-event flag is set by resource selection algorithm when looking for a
3522     // resource to load, before networkState has reached to NETWORK_LOADING.
3523     if (m_shouldDelayLoadEvent)
3524         return true;
3525 
3526     // When networkState is NETWORK_LOADING, progress and stalled events may be fired.
3527     if (m_networkState == NETWORK_LOADING)
3528         return true;
3529 
3530     // When playing or if playback may continue, timeupdate events may be fired.
3531     if (couldPlayIfEnoughData())
3532         return true;
3533 
3534     // When the seek finishes timeupdate and seeked events will be fired.
3535     if (m_seeking)
3536         return true;
3537 
3538     // When connected to a MediaSource, e.g. setting MediaSource.duration will cause a
3539     // durationchange event to be fired.
3540     if (m_mediaSource)
3541         return true;
3542 
3543     // Wait for any pending events to be fired.
3544     if (m_asyncEventQueue->hasPendingEvents())
3545         return true;
3546 
3547     return false;
3548 }
3549 
contextDestroyed()3550 void HTMLMediaElement::contextDestroyed()
3551 {
3552     // With Oilpan the ExecutionContext is weakly referenced from the media
3553     // controller and so it will clear itself on destruction.
3554 #if !ENABLE(OILPAN)
3555     if (m_mediaController)
3556         m_mediaController->clearExecutionContext();
3557 #endif
3558     ActiveDOMObject::contextDestroyed();
3559 }
3560 
isFullscreen() const3561 bool HTMLMediaElement::isFullscreen() const
3562 {
3563     return Fullscreen::isActiveFullScreenElement(*this);
3564 }
3565 
enterFullscreen()3566 void HTMLMediaElement::enterFullscreen()
3567 {
3568     WTF_LOG(Media, "HTMLMediaElement::enterFullscreen(%p)", this);
3569 
3570     Fullscreen::from(document()).requestFullscreen(*this, Fullscreen::PrefixedVideoRequest);
3571 }
3572 
exitFullscreen()3573 void HTMLMediaElement::exitFullscreen()
3574 {
3575     WTF_LOG(Media, "HTMLMediaElement::exitFullscreen(%p)", this);
3576 
3577     Fullscreen::from(document()).exitFullscreen();
3578 }
3579 
didBecomeFullscreenElement()3580 void HTMLMediaElement::didBecomeFullscreenElement()
3581 {
3582     if (hasMediaControls())
3583         mediaControls()->enteredFullscreen();
3584     if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isHTMLVideoElement())
3585         document().renderView()->compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree);
3586 }
3587 
willStopBeingFullscreenElement()3588 void HTMLMediaElement::willStopBeingFullscreenElement()
3589 {
3590     if (hasMediaControls())
3591         mediaControls()->exitedFullscreen();
3592     if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isHTMLVideoElement())
3593         document().renderView()->compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree);
3594 }
3595 
platformLayer() const3596 blink::WebLayer* HTMLMediaElement::platformLayer() const
3597 {
3598     return m_webLayer;
3599 }
3600 
hasClosedCaptions() const3601 bool HTMLMediaElement::hasClosedCaptions() const
3602 {
3603     if (m_textTracks) {
3604         for (unsigned i = 0; i < m_textTracks->length(); ++i) {
3605             if (m_textTracks->item(i)->readinessState() == TextTrack::FailedToLoad)
3606                 continue;
3607 
3608             if (m_textTracks->item(i)->kind() == TextTrack::captionsKeyword()
3609                 || m_textTracks->item(i)->kind() == TextTrack::subtitlesKeyword())
3610                 return true;
3611         }
3612     }
3613     return false;
3614 }
3615 
closedCaptionsVisible() const3616 bool HTMLMediaElement::closedCaptionsVisible() const
3617 {
3618     return m_closedCaptionsVisible;
3619 }
3620 
updateTextTrackDisplay()3621 void HTMLMediaElement::updateTextTrackDisplay()
3622 {
3623     WTF_LOG(Media, "HTMLMediaElement::updateTextTrackDisplay(%p)", this);
3624 
3625     if (!createMediaControls())
3626         return;
3627 
3628     mediaControls()->updateTextTrackDisplay();
3629 }
3630 
setClosedCaptionsVisible(bool closedCaptionVisible)3631 void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible)
3632 {
3633     WTF_LOG(Media, "HTMLMediaElement::setClosedCaptionsVisible(%p, %s)", this, boolString(closedCaptionVisible));
3634 
3635     if (!m_player || !hasClosedCaptions())
3636         return;
3637 
3638     m_closedCaptionsVisible = closedCaptionVisible;
3639 
3640     markCaptionAndSubtitleTracksAsUnconfigured();
3641     m_processingPreferenceChange = true;
3642     configureTextTracks();
3643     m_processingPreferenceChange = false;
3644 
3645     updateTextTrackDisplay();
3646 }
3647 
webkitAudioDecodedByteCount() const3648 unsigned HTMLMediaElement::webkitAudioDecodedByteCount() const
3649 {
3650     if (!webMediaPlayer())
3651         return 0;
3652     return webMediaPlayer()->audioDecodedByteCount();
3653 }
3654 
webkitVideoDecodedByteCount() const3655 unsigned HTMLMediaElement::webkitVideoDecodedByteCount() const
3656 {
3657     if (!webMediaPlayer())
3658         return 0;
3659     return webMediaPlayer()->videoDecodedByteCount();
3660 }
3661 
isURLAttribute(const Attribute & attribute) const3662 bool HTMLMediaElement::isURLAttribute(const Attribute& attribute) const
3663 {
3664     return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute);
3665 }
3666 
setShouldDelayLoadEvent(bool shouldDelay)3667 void HTMLMediaElement::setShouldDelayLoadEvent(bool shouldDelay)
3668 {
3669     if (m_shouldDelayLoadEvent == shouldDelay)
3670         return;
3671 
3672     WTF_LOG(Media, "HTMLMediaElement::setShouldDelayLoadEvent(%p, %s)", this, boolString(shouldDelay));
3673 
3674     m_shouldDelayLoadEvent = shouldDelay;
3675     if (shouldDelay)
3676         document().incrementLoadEventDelayCount();
3677     else
3678         document().decrementLoadEventDelayCount();
3679 }
3680 
3681 
mediaControls() const3682 MediaControls* HTMLMediaElement::mediaControls() const
3683 {
3684     return toMediaControls(userAgentShadowRoot()->firstChild());
3685 }
3686 
hasMediaControls() const3687 bool HTMLMediaElement::hasMediaControls() const
3688 {
3689     if (ShadowRoot* userAgent = userAgentShadowRoot()) {
3690         Node* node = userAgent->firstChild();
3691         ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isMediaControls());
3692         return node;
3693     }
3694 
3695     return false;
3696 }
3697 
createMediaControls()3698 bool HTMLMediaElement::createMediaControls()
3699 {
3700     if (hasMediaControls())
3701         return true;
3702 
3703     RefPtrWillBeRawPtr<MediaControls> mediaControls = MediaControls::create(*this);
3704     if (!mediaControls)
3705         return false;
3706 
3707     mediaControls->reset();
3708     if (isFullscreen())
3709         mediaControls->enteredFullscreen();
3710 
3711     ensureUserAgentShadowRoot().appendChild(mediaControls);
3712 
3713     if (!shouldShowControls() || !inDocument())
3714         mediaControls->hide();
3715 
3716     return true;
3717 }
3718 
configureMediaControls()3719 void HTMLMediaElement::configureMediaControls()
3720 {
3721     if (!inDocument()) {
3722         if (hasMediaControls())
3723             mediaControls()->hide();
3724         return;
3725     }
3726 
3727     if (!createMediaControls())
3728         return;
3729 
3730     mediaControls()->reset();
3731     if (shouldShowControls())
3732         mediaControls()->show();
3733     else
3734         mediaControls()->hide();
3735 }
3736 
configureTextTrackDisplay(VisibilityChangeAssumption assumption)3737 void HTMLMediaElement::configureTextTrackDisplay(VisibilityChangeAssumption assumption)
3738 {
3739     ASSERT(m_textTracks);
3740     WTF_LOG(Media, "HTMLMediaElement::configureTextTrackDisplay(%p)", this);
3741 
3742     if (m_processingPreferenceChange)
3743         return;
3744 
3745     bool haveVisibleTextTrack = false;
3746     for (unsigned i = 0; i < m_textTracks->length(); ++i) {
3747         if (m_textTracks->item(i)->mode() == TextTrack::showingKeyword()) {
3748             haveVisibleTextTrack = true;
3749             break;
3750         }
3751     }
3752 
3753     if (assumption == AssumeNoVisibleChange
3754         && m_haveVisibleTextTrack == haveVisibleTextTrack) {
3755         updateActiveTextTrackCues(currentTime());
3756         return;
3757     }
3758     m_haveVisibleTextTrack = haveVisibleTextTrack;
3759     m_closedCaptionsVisible = m_haveVisibleTextTrack;
3760 
3761     if (!m_haveVisibleTextTrack && !hasMediaControls())
3762         return;
3763     if (!createMediaControls())
3764         return;
3765 
3766     mediaControls()->changedClosedCaptionsVisibility();
3767 
3768     updateActiveTextTrackCues(currentTime());
3769     updateTextTrackDisplay();
3770 }
3771 
markCaptionAndSubtitleTracksAsUnconfigured()3772 void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured()
3773 {
3774     if (!m_textTracks)
3775         return;
3776 
3777     // Mark all tracks as not "configured" so that configureTextTracks()
3778     // will reconsider which tracks to display in light of new user preferences
3779     // (e.g. default tracks should not be displayed if the user has turned off
3780     // captions and non-default tracks should be displayed based on language
3781     // preferences if the user has turned captions on).
3782     for (unsigned i = 0; i < m_textTracks->length(); ++i) {
3783         RefPtrWillBeRawPtr<TextTrack> textTrack = m_textTracks->item(i);
3784         String kind = textTrack->kind();
3785 
3786         if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword())
3787             textTrack->setHasBeenConfigured(false);
3788     }
3789 }
3790 
preDispatchEventHandler(Event * event)3791 void* HTMLMediaElement::preDispatchEventHandler(Event* event)
3792 {
3793     if (event && event->type() == EventTypeNames::webkitfullscreenchange)
3794         configureMediaControls();
3795 
3796     return 0;
3797 }
3798 
createMediaPlayer()3799 void HTMLMediaElement::createMediaPlayer()
3800 {
3801     AudioSourceProviderClientLockScope scope(*this);
3802 
3803     closeMediaSource();
3804 
3805     m_player = MediaPlayer::create(this);
3806 
3807     // We haven't yet found out if any remote routes are available.
3808     m_remoteRoutesAvailable = false;
3809     m_playingRemotely = false;
3810 
3811 #if ENABLE(WEB_AUDIO)
3812     if (m_audioSourceNode && audioSourceProvider()) {
3813         // When creating the player, make sure its AudioSourceProvider knows about the client.
3814         audioSourceProvider()->setClient(m_audioSourceNode);
3815     }
3816 #endif
3817 }
3818 
3819 #if ENABLE(WEB_AUDIO)
setAudioSourceNode(AudioSourceProviderClient * sourceNode)3820 void HTMLMediaElement::setAudioSourceNode(AudioSourceProviderClient* sourceNode)
3821 {
3822     m_audioSourceNode = sourceNode;
3823 
3824     AudioSourceProviderClientLockScope scope(*this);
3825     if (audioSourceProvider())
3826         audioSourceProvider()->setClient(m_audioSourceNode);
3827 }
3828 
audioSourceProvider()3829 AudioSourceProvider* HTMLMediaElement::audioSourceProvider()
3830 {
3831     if (m_player)
3832         return m_player->audioSourceProvider();
3833 
3834     return 0;
3835 }
3836 #endif
3837 
mediaGroup() const3838 const AtomicString& HTMLMediaElement::mediaGroup() const
3839 {
3840     return fastGetAttribute(mediagroupAttr);
3841 }
3842 
setMediaGroup(const AtomicString & group)3843 void HTMLMediaElement::setMediaGroup(const AtomicString& group)
3844 {
3845     // When a media element is created with a mediagroup attribute, and when a media element's mediagroup
3846     // attribute is set, changed, or removed, the user agent must run the following steps:
3847     // 1. Let _R [this] be the media element in question.
3848     // 2. Let m have no current media controller, if it currently has one.
3849     setControllerInternal(nullptr);
3850 
3851     // 3. If m's mediagroup attribute is being removed, then abort these steps.
3852     if (group.isNull() || group.isEmpty())
3853         return;
3854 
3855     // 4. If there is another media element whose Document is the same as m's Document (even if one or both
3856     // of these elements are not actually in the Document),
3857     WeakMediaElementSet elements = documentToElementSetMap().get(&document());
3858     for (WeakMediaElementSet::iterator i = elements.begin(); i != elements.end(); ++i) {
3859         if (*i == this)
3860             continue;
3861 
3862         // and which also has a mediagroup attribute, and whose mediagroup attribute has the same value as
3863         // the new value of m's mediagroup attribute,
3864         if ((*i)->mediaGroup() == group) {
3865             //  then let controller be that media element's current media controller.
3866             setControllerInternal((*i)->controller());
3867             return;
3868         }
3869     }
3870 
3871     // Otherwise, let controller be a newly created MediaController.
3872     setControllerInternal(MediaController::create(Node::executionContext()));
3873 }
3874 
controller() const3875 MediaController* HTMLMediaElement::controller() const
3876 {
3877     return m_mediaController.get();
3878 }
3879 
setController(PassRefPtrWillBeRawPtr<MediaController> controller)3880 void HTMLMediaElement::setController(PassRefPtrWillBeRawPtr<MediaController> controller)
3881 {
3882     // 4.8.10.11.2 Media controllers: controller attribute.
3883     // On setting, it must first remove the element's mediagroup attribute, if any,
3884     removeAttribute(mediagroupAttr);
3885     // and then set the current media controller to the given value.
3886     setControllerInternal(controller);
3887 }
3888 
setControllerInternal(PassRefPtrWillBeRawPtr<MediaController> controller)3889 void HTMLMediaElement::setControllerInternal(PassRefPtrWillBeRawPtr<MediaController> controller)
3890 {
3891     if (m_mediaController)
3892         m_mediaController->removeMediaElement(this);
3893 
3894     m_mediaController = controller;
3895 
3896     if (m_mediaController)
3897         m_mediaController->addMediaElement(this);
3898 }
3899 
updateMediaController()3900 void HTMLMediaElement::updateMediaController()
3901 {
3902     if (m_mediaController)
3903         m_mediaController->reportControllerState();
3904 }
3905 
isBlocked() const3906 bool HTMLMediaElement::isBlocked() const
3907 {
3908     // A media element is a blocked media element if its readyState attribute is in the
3909     // HAVE_NOTHING state, the HAVE_METADATA state, or the HAVE_CURRENT_DATA state,
3910     // or if the element has paused for user interaction or paused for in-band content.
3911     if (m_readyState <= HAVE_CURRENT_DATA)
3912         return true;
3913 
3914     return false;
3915 }
3916 
isBlockedOnMediaController() const3917 bool HTMLMediaElement::isBlockedOnMediaController() const
3918 {
3919     if (!m_mediaController)
3920         return false;
3921 
3922     // A media element is blocked on its media controller if the MediaController is a blocked
3923     // media controller,
3924     if (m_mediaController->isBlocked())
3925         return true;
3926 
3927     // or if its media controller position is either before the media resource's earliest possible
3928     // position relative to the MediaController's timeline or after the end of the media resource
3929     // relative to the MediaController's timeline.
3930     double mediaControllerPosition = m_mediaController->currentTime();
3931     if (mediaControllerPosition < 0 || mediaControllerPosition > duration())
3932         return true;
3933 
3934     return false;
3935 }
3936 
corsMode() const3937 WebMediaPlayer::CORSMode HTMLMediaElement::corsMode() const
3938 {
3939     const AtomicString& crossOriginMode = fastGetAttribute(crossoriginAttr);
3940     if (crossOriginMode.isNull())
3941         return WebMediaPlayer::CORSModeUnspecified;
3942     if (equalIgnoringCase(crossOriginMode, "use-credentials"))
3943         return WebMediaPlayer::CORSModeUseCredentials;
3944     return WebMediaPlayer::CORSModeAnonymous;
3945 }
3946 
mediaPlayerSetWebLayer(blink::WebLayer * webLayer)3947 void HTMLMediaElement::mediaPlayerSetWebLayer(blink::WebLayer* webLayer)
3948 {
3949     if (webLayer == m_webLayer)
3950         return;
3951 
3952     // If either of the layers is null we need to enable or disable compositing. This is done by triggering a style recalc.
3953     if (!m_webLayer || !webLayer)
3954         setNeedsCompositingUpdate();
3955 
3956     if (m_webLayer)
3957         GraphicsLayer::unregisterContentsLayer(m_webLayer);
3958     m_webLayer = webLayer;
3959     if (m_webLayer) {
3960         GraphicsLayer::registerContentsLayer(m_webLayer);
3961     }
3962 }
3963 
mediaPlayerMediaSourceOpened(blink::WebMediaSource * webMediaSource)3964 void HTMLMediaElement::mediaPlayerMediaSourceOpened(blink::WebMediaSource* webMediaSource)
3965 {
3966     m_mediaSource->setWebMediaSourceAndOpen(adoptPtr(webMediaSource));
3967 }
3968 
isInteractiveContent() const3969 bool HTMLMediaElement::isInteractiveContent() const
3970 {
3971     return fastHasAttribute(controlsAttr);
3972 }
3973 
defaultEventHandler(Event * event)3974 void HTMLMediaElement::defaultEventHandler(Event* event)
3975 {
3976     if (event->type() == EventTypeNames::focusin) {
3977         if (hasMediaControls())
3978             mediaControls()->mediaElementFocused();
3979     }
3980     HTMLElement::defaultEventHandler(event);
3981 }
3982 
trace(Visitor * visitor)3983 void HTMLMediaElement::trace(Visitor* visitor)
3984 {
3985 #if ENABLE(OILPAN)
3986     visitor->trace(m_playedTimeRanges);
3987     visitor->trace(m_asyncEventQueue);
3988     visitor->trace(m_error);
3989     visitor->trace(m_currentSourceNode);
3990     visitor->trace(m_nextChildNodeToConsider);
3991     visitor->trace(m_mediaSource);
3992     visitor->trace(m_audioTracks);
3993     visitor->trace(m_videoTracks);
3994     visitor->trace(m_textTracks);
3995     visitor->trace(m_textTracksWhenResourceSelectionBegan);
3996     visitor->trace(m_mediaController);
3997 #if ENABLE(WEB_AUDIO)
3998     visitor->registerWeakMembers<HTMLMediaElement, &HTMLMediaElement::clearWeakMembers>(this);
3999 #endif
4000     HeapSupplementable<HTMLMediaElement>::trace(visitor);
4001 #endif
4002     HTMLElement::trace(visitor);
4003 }
4004 
createPlaceholderTracksIfNecessary()4005 void HTMLMediaElement::createPlaceholderTracksIfNecessary()
4006 {
4007     if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
4008         return;
4009 
4010     // Create a placeholder audio track if the player says it has audio but it didn't explicitly announce the tracks.
4011     if (hasAudio() && !audioTracks().length())
4012         addAudioTrack("audio", WebMediaPlayerClient::AudioTrackKindMain, "Audio Track", "", true);
4013 
4014     // Create a placeholder video track if the player says it has video but it didn't explicitly announce the tracks.
4015     if (webMediaPlayer() && webMediaPlayer()->hasVideo() && !videoTracks().length())
4016         addVideoTrack("video", WebMediaPlayerClient::VideoTrackKindMain, "Video Track", "", true);
4017 }
4018 
selectInitialTracksIfNecessary()4019 void HTMLMediaElement::selectInitialTracksIfNecessary()
4020 {
4021     if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
4022         return;
4023 
4024     // Enable the first audio track if an audio track hasn't been enabled yet.
4025     if (audioTracks().length() > 0 && !audioTracks().hasEnabledTrack())
4026         audioTracks().anonymousIndexedGetter(0)->setEnabled(true);
4027 
4028     // Select the first video track if a video track hasn't been selected yet.
4029     if (videoTracks().length() > 0 && videoTracks().selectedIndex() == -1)
4030         videoTracks().anonymousIndexedGetter(0)->setSelected(true);
4031 }
4032 
4033 #if ENABLE(WEB_AUDIO)
clearWeakMembers(Visitor * visitor)4034 void HTMLMediaElement::clearWeakMembers(Visitor* visitor)
4035 {
4036     if (!visitor->isAlive(m_audioSourceNode) && audioSourceProvider())
4037         audioSourceProvider()->setClient(0);
4038 }
4039 #endif
4040 
4041 }
4042