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