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 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 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; 131 static DocumentElementSetMap& documentToElementSetMap() 132 { 133 DEFINE_STATIC_LOCAL(OwnPtrWillBePersistent<DocumentElementSetMap>, map, (adoptPtrWillBeNoop(new DocumentElementSetMap()))); 134 return *map; 135 } 136 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 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: 157 TrackDisplayUpdateScope(HTMLMediaElement* mediaElement) 158 { 159 m_mediaElement = mediaElement; 160 m_mediaElement->beginIgnoringTrackDisplayUpdateRequests(); 161 } 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) 176 AudioSourceProviderClientLockScope(HTMLMediaElement& element) 177 : m_client(element.audioSourceNode()) 178 { 179 if (m_client) 180 m_client->lock(); 181 } 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 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 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 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 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 296 void HTMLMediaElement::setMediaStreamRegistry(URLRegistry* registry) 297 { 298 ASSERT(!s_mediaStreamRegistry); 299 s_mediaStreamRegistry = registry; 300 } 301 302 bool HTMLMediaElement::isMediaStreamURL(const String& url) 303 { 304 return s_mediaStreamRegistry ? s_mediaStreamRegistry->contains(url) : false; 305 } 306 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 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) 475 void HTMLMediaElement::setCloseMediaSourceWhenFinalizing() 476 { 477 ASSERT(!m_closeMediaSourceWhenFinalizing); 478 m_closeMediaSourceWhenFinalizing = true; 479 } 480 #endif 481 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 515 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 524 bool HTMLMediaElement::isMouseFocusable() const 525 { 526 return false; 527 } 528 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 561 void HTMLMediaElement::finishParsingChildren() 562 { 563 HTMLElement::finishParsingChildren(); 564 565 if (Traversal<HTMLTrackElement>::firstChild(*this)) 566 scheduleDelayedAction(LoadTextTrackResource); 567 } 568 569 bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style) 570 { 571 return shouldShowControls() && HTMLElement::rendererIsNeeded(style); 572 } 573 574 RenderObject* HTMLMediaElement::createRenderer(RenderStyle*) 575 { 576 return new RenderMedia(this); 577 } 578 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 594 void HTMLMediaElement::didNotifySubtreeInsertionsToDocument() 595 { 596 configureMediaControls(); 597 } 598 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 613 void HTMLMediaElement::attach(const AttachContext& context) 614 { 615 HTMLElement::attach(context); 616 617 if (renderer()) 618 renderer()->updateFromElement(); 619 } 620 621 void HTMLMediaElement::didRecalcStyle(StyleRecalcChange) 622 { 623 if (renderer()) 624 renderer()->updateFromElement(); 625 } 626 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 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 650 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName) 651 { 652 scheduleEvent(Event::createCancelable(eventName)); 653 } 654 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 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 678 PassRefPtrWillBeRawPtr<MediaError> HTMLMediaElement::error() const 679 { 680 return m_error; 681 } 682 683 void HTMLMediaElement::setSrc(const AtomicString& url) 684 { 685 setAttribute(srcAttr, url); 686 } 687 688 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const 689 { 690 return m_networkState; 691 } 692 693 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 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 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 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 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 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 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 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 1032 void HTMLMediaElement::setPlayerPreload() 1033 { 1034 m_player->setPreload(m_preload); 1035 1036 if (loadIsDeferred() && m_preload != MediaPlayer::None) 1037 startDeferredLoad(); 1038 } 1039 1040 bool HTMLMediaElement::loadIsDeferred() const 1041 { 1042 return m_deferredLoadState != NotDeferred; 1043 } 1044 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 1061 void HTMLMediaElement::cancelDeferredLoad() 1062 { 1063 m_deferredLoadTimer.stop(); 1064 m_deferredLoadState = NotDeferred; 1065 } 1066 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 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 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 1109 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 1120 static bool trackIndexCompare(TextTrack* a, 1121 TextTrack* b) 1122 { 1123 return a->trackIndex() - b->trackIndex() < 0; 1124 } 1125 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 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 1379 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 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 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 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 1450 void HTMLMediaElement::beginIgnoringTrackDisplayUpdateRequests() 1451 { 1452 ++m_ignoreTrackDisplayUpdate; 1453 } 1454 1455 void HTMLMediaElement::endIgnoringTrackDisplayUpdateRequests() 1456 { 1457 ASSERT(m_ignoreTrackDisplayUpdate); 1458 --m_ignoreTrackDisplayUpdate; 1459 if (!m_ignoreTrackDisplayUpdate && m_active) 1460 updateActiveTextTrackCues(currentTime()); 1461 } 1462 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 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 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 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 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 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 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 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 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 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 1650 void HTMLMediaElement::mediaPlayerNetworkStateChanged() 1651 { 1652 setNetworkState(webMediaPlayer()->networkState()); 1653 } 1654 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 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 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 1752 void HTMLMediaElement::mediaPlayerReadyStateChanged() 1753 { 1754 setReadyState(static_cast<ReadyState>(webMediaPlayer()->readyState())); 1755 } 1756 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 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 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 1932 bool HTMLMediaElement::supportsSave() const 1933 { 1934 return webMediaPlayer() && webMediaPlayer()->supportsSave(); 1935 } 1936 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 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 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 2052 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const 2053 { 2054 return m_readyState; 2055 } 2056 2057 bool HTMLMediaElement::hasAudio() const 2058 { 2059 return webMediaPlayer() && webMediaPlayer()->hasAudio(); 2060 } 2061 2062 bool HTMLMediaElement::seeking() const 2063 { 2064 return m_seeking; 2065 } 2066 2067 void HTMLMediaElement::refreshCachedTime() const 2068 { 2069 if (!webMediaPlayer() || m_readyState < HAVE_METADATA) 2070 return; 2071 2072 m_cachedTime = webMediaPlayer()->currentTime(); 2073 } 2074 2075 void HTMLMediaElement::invalidateCachedTime() 2076 { 2077 WTF_LOG(Media, "HTMLMediaElement::invalidateCachedTime(%p)", this); 2078 m_cachedTime = MediaPlayer::invalidTime(); 2079 } 2080 2081 // playback state 2082 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 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 2127 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 2151 bool HTMLMediaElement::paused() const 2152 { 2153 return m_paused; 2154 } 2155 2156 double HTMLMediaElement::defaultPlaybackRate() const 2157 { 2158 return m_defaultPlaybackRate; 2159 } 2160 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 2170 double HTMLMediaElement::playbackRate() const 2171 { 2172 return m_playbackRate; 2173 } 2174 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 2188 double HTMLMediaElement::effectivePlaybackRate() const 2189 { 2190 return m_mediaController ? m_mediaController->playbackRate() : m_playbackRate; 2191 } 2192 2193 HTMLMediaElement::DirectionOfPlayback HTMLMediaElement::directionOfPlayback() const 2194 { 2195 return m_playbackRate >= 0 ? Forward : Backward; 2196 } 2197 2198 void HTMLMediaElement::updatePlaybackRate() 2199 { 2200 double effectiveRate = effectivePlaybackRate(); 2201 if (m_player && potentiallyPlaying()) 2202 webMediaPlayer()->setRate(effectiveRate); 2203 } 2204 2205 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 2213 bool HTMLMediaElement::autoplay() const 2214 { 2215 return fastHasAttribute(autoplayAttr); 2216 } 2217 2218 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 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 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 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 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 2302 void HTMLMediaElement::requestRemotePlayback() 2303 { 2304 ASSERT(m_remoteRoutesAvailable); 2305 webMediaPlayer()->requestRemotePlayback(); 2306 } 2307 2308 void HTMLMediaElement::requestRemotePlaybackControl() 2309 { 2310 ASSERT(m_remoteRoutesAvailable); 2311 webMediaPlayer()->requestRemotePlaybackControl(); 2312 } 2313 2314 void HTMLMediaElement::closeMediaSource() 2315 { 2316 if (!m_mediaSource) 2317 return; 2318 2319 m_mediaSource->close(); 2320 m_mediaSource = nullptr; 2321 } 2322 2323 bool HTMLMediaElement::loop() const 2324 { 2325 return fastHasAttribute(loopAttr); 2326 } 2327 2328 void HTMLMediaElement::setLoop(bool b) 2329 { 2330 WTF_LOG(Media, "HTMLMediaElement::setLoop(%p, %s)", this, boolString(b)); 2331 setBooleanAttribute(loopAttr, b); 2332 } 2333 2334 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 2349 double HTMLMediaElement::volume() const 2350 { 2351 return m_volume; 2352 } 2353 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 2371 bool HTMLMediaElement::muted() const 2372 { 2373 return m_muted; 2374 } 2375 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 2390 void HTMLMediaElement::updateVolume() 2391 { 2392 if (webMediaPlayer()) 2393 webMediaPlayer()->setVolume(effectiveMediaVolume()); 2394 2395 if (hasMediaControls()) 2396 mediaControls()->updateVolume(); 2397 } 2398 2399 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 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 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 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 2471 bool HTMLMediaElement::togglePlayStateWillPlay() const 2472 { 2473 if (m_mediaController) 2474 return m_mediaController->paused() || m_mediaController->isRestrained(); 2475 return paused(); 2476 } 2477 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 2495 AudioTrackList& HTMLMediaElement::audioTracks() 2496 { 2497 ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled()); 2498 return *m_audioTracks; 2499 } 2500 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 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 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 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 2551 VideoTrackList& HTMLMediaElement::videoTracks() 2552 { 2553 ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled()); 2554 return *m_videoTracks; 2555 } 2556 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 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 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 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 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 2645 void HTMLMediaElement::textTracksChanged() 2646 { 2647 if (hasMediaControls()) 2648 mediaControls()->textTracksChanged(); 2649 } 2650 2651 void HTMLMediaElement::addTextTrack(TextTrack* track) 2652 { 2653 textTracks()->append(track); 2654 2655 textTracksChanged(); 2656 } 2657 2658 void HTMLMediaElement::removeTextTrack(TextTrack* track) 2659 { 2660 TrackDisplayUpdateScope scope(this); 2661 m_textTracks->remove(track); 2662 2663 textTracksChanged(); 2664 } 2665 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 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 2717 TextTrackList* HTMLMediaElement::textTracks() 2718 { 2719 if (!m_textTracks) 2720 m_textTracks = TextTrackList::create(this); 2721 2722 return m_textTracks.get(); 2723 } 2724 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 3223 void HTMLMediaElement::remoteRouteAvailabilityChanged(bool routesAvailable) 3224 { 3225 m_remoteRoutesAvailable = routesAvailable; 3226 if (hasMediaControls()) 3227 mediaControls()->refreshCastButtonVisibility(); 3228 } 3229 3230 void HTMLMediaElement::connectedToRemoteDevice() 3231 { 3232 m_playingRemotely = true; 3233 if (hasMediaControls()) 3234 mediaControls()->startedCasting(); 3235 } 3236 3237 void HTMLMediaElement::disconnectedFromRemoteDevice() 3238 { 3239 m_playingRemotely = false; 3240 if (hasMediaControls()) 3241 mediaControls()->stoppedCasting(); 3242 } 3243 3244 // MediaPlayerPresentation methods 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 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 3267 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 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 3292 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 3302 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 3311 bool HTMLMediaElement::couldPlayIfEnoughData() const 3312 { 3313 return !paused() && !endedPlayback() && !stoppedDueToErrors(); 3314 } 3315 3316 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 3342 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 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 3405 void HTMLMediaElement::stopPeriodicTimers() 3406 { 3407 m_progressEventTimer.stop(); 3408 m_playbackProgressTimer.stop(); 3409 } 3410 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 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 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 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 3519 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 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 3561 bool HTMLMediaElement::isFullscreen() const 3562 { 3563 return Fullscreen::isActiveFullScreenElement(*this); 3564 } 3565 3566 void HTMLMediaElement::enterFullscreen() 3567 { 3568 WTF_LOG(Media, "HTMLMediaElement::enterFullscreen(%p)", this); 3569 3570 Fullscreen::from(document()).requestFullscreen(*this, Fullscreen::PrefixedVideoRequest); 3571 } 3572 3573 void HTMLMediaElement::exitFullscreen() 3574 { 3575 WTF_LOG(Media, "HTMLMediaElement::exitFullscreen(%p)", this); 3576 3577 Fullscreen::from(document()).exitFullscreen(); 3578 } 3579 3580 void HTMLMediaElement::didBecomeFullscreenElement() 3581 { 3582 if (hasMediaControls()) 3583 mediaControls()->enteredFullscreen(); 3584 if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isHTMLVideoElement()) 3585 document().renderView()->compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree); 3586 } 3587 3588 void HTMLMediaElement::willStopBeingFullscreenElement() 3589 { 3590 if (hasMediaControls()) 3591 mediaControls()->exitedFullscreen(); 3592 if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isHTMLVideoElement()) 3593 document().renderView()->compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree); 3594 } 3595 3596 blink::WebLayer* HTMLMediaElement::platformLayer() const 3597 { 3598 return m_webLayer; 3599 } 3600 3601 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 3616 bool HTMLMediaElement::closedCaptionsVisible() const 3617 { 3618 return m_closedCaptionsVisible; 3619 } 3620 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 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 3648 unsigned HTMLMediaElement::webkitAudioDecodedByteCount() const 3649 { 3650 if (!webMediaPlayer()) 3651 return 0; 3652 return webMediaPlayer()->audioDecodedByteCount(); 3653 } 3654 3655 unsigned HTMLMediaElement::webkitVideoDecodedByteCount() const 3656 { 3657 if (!webMediaPlayer()) 3658 return 0; 3659 return webMediaPlayer()->videoDecodedByteCount(); 3660 } 3661 3662 bool HTMLMediaElement::isURLAttribute(const Attribute& attribute) const 3663 { 3664 return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute); 3665 } 3666 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 3682 MediaControls* HTMLMediaElement::mediaControls() const 3683 { 3684 return toMediaControls(userAgentShadowRoot()->firstChild()); 3685 } 3686 3687 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 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 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 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 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 3791 void* HTMLMediaElement::preDispatchEventHandler(Event* event) 3792 { 3793 if (event && event->type() == EventTypeNames::webkitfullscreenchange) 3794 configureMediaControls(); 3795 3796 return 0; 3797 } 3798 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) 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 3829 AudioSourceProvider* HTMLMediaElement::audioSourceProvider() 3830 { 3831 if (m_player) 3832 return m_player->audioSourceProvider(); 3833 3834 return 0; 3835 } 3836 #endif 3837 3838 const AtomicString& HTMLMediaElement::mediaGroup() const 3839 { 3840 return fastGetAttribute(mediagroupAttr); 3841 } 3842 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 3875 MediaController* HTMLMediaElement::controller() const 3876 { 3877 return m_mediaController.get(); 3878 } 3879 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 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 3900 void HTMLMediaElement::updateMediaController() 3901 { 3902 if (m_mediaController) 3903 m_mediaController->reportControllerState(); 3904 } 3905 3906 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 3917 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 3937 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 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 3964 void HTMLMediaElement::mediaPlayerMediaSourceOpened(blink::WebMediaSource* webMediaSource) 3965 { 3966 m_mediaSource->setWebMediaSourceAndOpen(adoptPtr(webMediaSource)); 3967 } 3968 3969 bool HTMLMediaElement::isInteractiveContent() const 3970 { 3971 return fastHasAttribute(controlsAttr); 3972 } 3973 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 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 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 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) 4034 void HTMLMediaElement::clearWeakMembers(Visitor* visitor) 4035 { 4036 if (!visitor->isAlive(m_audioSourceNode) && audioSourceProvider()) 4037 audioSourceProvider()->setClient(0); 4038 } 4039 #endif 4040 4041 } 4042