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