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