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