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