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