1/* 2 * Copyright (C) 2007-2014 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 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 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#if ENABLE(VIDEO) 28#include "HTMLMediaElement.h" 29 30#include "ApplicationCacheHost.h" 31#include "ApplicationCacheResource.h" 32#include "Attribute.h" 33#include "CSSPropertyNames.h" 34#include "CSSValueKeywords.h" 35#include "ChromeClient.h" 36#include "ClientRect.h" 37#include "ClientRectList.h" 38#include "ContentSecurityPolicy.h" 39#include "ContentType.h" 40#include "CookieJar.h" 41#include "DiagnosticLoggingKeys.h" 42#include "DisplaySleepDisabler.h" 43#include "DocumentLoader.h" 44#include "ElementIterator.h" 45#include "EventNames.h" 46#include "ExceptionCodePlaceholder.h" 47#include "FrameLoader.h" 48#include "FrameLoaderClient.h" 49#include "FrameView.h" 50#include "HTMLSourceElement.h" 51#include "HTMLVideoElement.h" 52#include "JSHTMLMediaElement.h" 53#include "Language.h" 54#include "Logging.h" 55#include "MIMETypeRegistry.h" 56#include "MainFrame.h" 57#include "MediaController.h" 58#include "MediaControls.h" 59#include "MediaDocument.h" 60#include "MediaError.h" 61#include "MediaFragmentURIParser.h" 62#include "MediaKeyEvent.h" 63#include "MediaList.h" 64#include "MediaQueryEvaluator.h" 65#include "MediaSessionManager.h" 66#include "NetworkingContext.h" 67#include "PageActivityAssertionToken.h" 68#include "PageGroup.h" 69#include "PageThrottler.h" 70#include "ProgressTracker.h" 71#include "RenderLayerCompositor.h" 72#include "RenderVideo.h" 73#include "RenderView.h" 74#include "ScriptController.h" 75#include "ScriptSourceCode.h" 76#include "SecurityPolicy.h" 77#include "SessionID.h" 78#include "Settings.h" 79#include "ShadowRoot.h" 80#include "TimeRanges.h" 81#include <limits> 82#include <runtime/Uint8Array.h> 83#include <wtf/CurrentTime.h> 84#include <wtf/MathExtras.h> 85#include <wtf/Ref.h> 86#include <wtf/text/CString.h> 87 88#if ENABLE(VIDEO_TRACK) 89#include "AudioTrackList.h" 90#include "HTMLTrackElement.h" 91#include "InbandGenericTextTrack.h" 92#include "InbandTextTrackPrivate.h" 93#include "InbandWebVTTTextTrack.h" 94#include "RuntimeEnabledFeatures.h" 95#include "TextTrackCueList.h" 96#include "TextTrackList.h" 97#include "VideoTrackList.h" 98#endif 99 100#if ENABLE(WEB_AUDIO) 101#include "AudioSourceProvider.h" 102#include "MediaElementAudioSourceNode.h" 103#endif 104 105#if PLATFORM(IOS) 106#include "RuntimeApplicationChecksIOS.h" 107#endif 108 109#if ENABLE(IOS_AIRPLAY) 110#include "WebKitPlaybackTargetAvailabilityEvent.h" 111#endif 112 113#if ENABLE(MEDIA_SOURCE) 114#include "DOMWindow.h" 115#include "MediaSource.h" 116#include "Performance.h" 117#include "VideoPlaybackQuality.h" 118#endif 119 120#if ENABLE(MEDIA_STREAM) 121#include "MediaStream.h" 122#include "MediaStreamRegistry.h" 123#endif 124 125#if ENABLE(ENCRYPTED_MEDIA_V2) 126#include "MediaKeyNeededEvent.h" 127#include "MediaKeys.h" 128#endif 129 130#if USE(PLATFORM_TEXT_TRACK_MENU) 131#include "PlatformTextTrack.h" 132#endif 133 134#if ENABLE(MEDIA_CONTROLS_SCRIPT) 135#include "JSMediaControlsHost.h" 136#include "MediaControlsHost.h" 137#include "ScriptGlobalObject.h" 138#include <bindings/ScriptObject.h> 139#endif 140 141namespace WebCore { 142 143static const double SeekRepeatDelay = 0.1; 144static const double SeekTime = 0.2; 145static const double ScanRepeatDelay = 1.5; 146static const double ScanMaximumRate = 8; 147 148static void setFlags(unsigned& value, unsigned flags) 149{ 150 value |= flags; 151} 152 153static void clearFlags(unsigned& value, unsigned flags) 154{ 155 value &= ~flags; 156} 157 158#if !LOG_DISABLED 159static String urlForLoggingMedia(const URL& url) 160{ 161 static const unsigned maximumURLLengthForLogging = 128; 162 163 if (url.string().length() < maximumURLLengthForLogging) 164 return url.string(); 165 return url.string().substring(0, maximumURLLengthForLogging) + "..."; 166} 167 168static const char* boolString(bool val) 169{ 170 return val ? "true" : "false"; 171} 172#endif 173 174#ifndef LOG_MEDIA_EVENTS 175// Default to not logging events because so many are generated they can overwhelm the rest of 176// the logging. 177#define LOG_MEDIA_EVENTS 0 178#endif 179 180#ifndef LOG_CACHED_TIME_WARNINGS 181// Default to not logging warnings about excessive drift in the cached media time because it adds a 182// fair amount of overhead and logging. 183#define LOG_CACHED_TIME_WARNINGS 0 184#endif 185 186#if ENABLE(MEDIA_SOURCE) 187// URL protocol used to signal that the media source API is being used. 188static const char* mediaSourceBlobProtocol = "blob"; 189#endif 190 191using namespace HTMLNames; 192 193typedef HashMap<Document*, HashSet<HTMLMediaElement*>> DocumentElementSetMap; 194static DocumentElementSetMap& documentToElementSetMap() 195{ 196 DEPRECATED_DEFINE_STATIC_LOCAL(DocumentElementSetMap, map, ()); 197 return map; 198} 199 200static void addElementToDocumentMap(HTMLMediaElement& element, Document& document) 201{ 202 DocumentElementSetMap& map = documentToElementSetMap(); 203 HashSet<HTMLMediaElement*> set = map.take(&document); 204 set.add(&element); 205 map.add(&document, set); 206} 207 208static void removeElementFromDocumentMap(HTMLMediaElement& element, Document& document) 209{ 210 DocumentElementSetMap& map = documentToElementSetMap(); 211 HashSet<HTMLMediaElement*> set = map.take(&document); 212 set.remove(&element); 213 if (!set.isEmpty()) 214 map.add(&document, set); 215} 216 217#if ENABLE(ENCRYPTED_MEDIA) 218static ExceptionCode exceptionCodeForMediaKeyException(MediaPlayer::MediaKeyException exception) 219{ 220 switch (exception) { 221 case MediaPlayer::NoError: 222 return 0; 223 case MediaPlayer::InvalidPlayerState: 224 return INVALID_STATE_ERR; 225 case MediaPlayer::KeySystemNotSupported: 226 return NOT_SUPPORTED_ERR; 227 } 228 229 ASSERT_NOT_REACHED(); 230 return INVALID_STATE_ERR; 231} 232#endif 233 234#if ENABLE(VIDEO_TRACK) 235class TrackDisplayUpdateScope { 236public: 237 TrackDisplayUpdateScope(HTMLMediaElement* mediaElement) 238 { 239 m_mediaElement = mediaElement; 240 m_mediaElement->beginIgnoringTrackDisplayUpdateRequests(); 241 } 242 ~TrackDisplayUpdateScope() 243 { 244 ASSERT(m_mediaElement); 245 m_mediaElement->endIgnoringTrackDisplayUpdateRequests(); 246 } 247 248private: 249 HTMLMediaElement* m_mediaElement; 250}; 251#endif 252 253HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document, bool createdByParser) 254 : HTMLElement(tagName, document) 255 , ActiveDOMObject(&document) 256 , m_loadTimer(this, &HTMLMediaElement::loadTimerFired) 257 , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired) 258 , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired) 259 , m_scanTimer(this, &HTMLMediaElement::scanTimerFired) 260 , m_seekTimer(this, &HTMLMediaElement::seekTimerFired) 261 , m_playedTimeRanges() 262 , m_asyncEventQueue(*this) 263 , m_playbackRate(1.0f) 264 , m_defaultPlaybackRate(1.0f) 265 , m_webkitPreservesPitch(true) 266 , m_networkState(NETWORK_EMPTY) 267 , m_readyState(HAVE_NOTHING) 268 , m_readyStateMaximum(HAVE_NOTHING) 269 , m_volume(1.0f) 270 , m_volumeInitialized(false) 271 , m_lastSeekTime(0) 272 , m_previousProgressTime(std::numeric_limits<double>::max()) 273 , m_clockTimeAtLastUpdateEvent(0) 274 , m_lastTimeUpdateEventMovieTime(std::numeric_limits<double>::max()) 275 , m_loadState(WaitingForSource) 276#if PLATFORM(IOS) 277 , m_videoFullscreenGravity(MediaPlayer::VideoGravityResizeAspect) 278#endif 279 , m_preload(MediaPlayer::Auto) 280 , m_displayMode(Unknown) 281 , m_processingMediaPlayerCallback(0) 282#if ENABLE(MEDIA_SOURCE) 283 , m_droppedVideoFrames(0) 284#endif 285 , m_cachedTime(MediaPlayer::invalidTime()) 286 , m_clockTimeAtLastCachedTimeUpdate(0) 287 , m_minimumClockTimeToUpdateCachedTime(0) 288 , m_fragmentStartTime(MediaPlayer::invalidTime()) 289 , m_fragmentEndTime(MediaPlayer::invalidTime()) 290 , m_pendingActionFlags(0) 291 , m_actionAfterScan(Nothing) 292 , m_scanType(Scan) 293 , m_scanDirection(Forward) 294 , m_playing(false) 295 , m_isWaitingUntilMediaCanStart(false) 296 , m_shouldDelayLoadEvent(false) 297 , m_haveFiredLoadedData(false) 298 , m_inActiveDocument(true) 299 , m_autoplaying(true) 300 , m_muted(false) 301 , m_explicitlyMuted(false) 302 , m_paused(true) 303 , m_seeking(false) 304 , m_sentStalledEvent(false) 305 , m_sentEndEvent(false) 306 , m_pausedInternal(false) 307 , m_sendProgressEvents(true) 308 , m_isFullscreen(false) 309 , m_closedCaptionsVisible(false) 310 , m_webkitLegacyClosedCaptionOverride(false) 311 , m_completelyLoaded(false) 312 , m_havePreparedToPlay(false) 313 , m_parsingInProgress(createdByParser) 314 , m_elementIsHidden(document.hidden()) 315#if PLATFORM(IOS) 316 , m_requestingPlay(false) 317#endif 318#if ENABLE(MEDIA_CONTROLS_SCRIPT) 319 , m_mediaControlsDependOnPageScaleFactor(false) 320#endif 321#if ENABLE(VIDEO_TRACK) 322 , m_tracksAreReady(true) 323 , m_haveVisibleTextTrack(false) 324 , m_processingPreferenceChange(false) 325 , m_lastTextTrackUpdateTime(-1) 326 , m_captionDisplayMode(CaptionUserPreferences::Automatic) 327 , m_audioTracks(0) 328 , m_textTracks(0) 329 , m_videoTracks(0) 330 , m_ignoreTrackDisplayUpdate(0) 331#endif 332#if ENABLE(WEB_AUDIO) 333 , m_audioSourceNode(0) 334#endif 335 , m_mediaSession(HTMLMediaSession::create(*this)) 336 , m_reportedExtraMemoryCost(0) 337#if ENABLE(MEDIA_STREAM) 338 , m_mediaStreamSrcObject(nullptr) 339#endif 340{ 341 LOG(Media, "HTMLMediaElement::HTMLMediaElement"); 342 setHasCustomStyleResolveCallbacks(); 343 344 m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequireUserGestureForFullscreen); 345 m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequirePageConsentToLoadMedia); 346 347 // FIXME: We should clean up and look to better merge the iOS and non-iOS code below. 348 Settings* settings = document.settings(); 349#if !PLATFORM(IOS) 350 if (settings && settings->mediaPlaybackRequiresUserGesture()) { 351 m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequireUserGestureForRateChange); 352 m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequireUserGestureForLoad); 353 } 354#else 355 m_sendProgressEvents = false; 356 if (!settings || settings->mediaPlaybackRequiresUserGesture()) { 357 // Allow autoplay in a MediaDocument that is not in an iframe. 358 if (document.ownerElement() || !document.isMediaDocument()) 359 m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequireUserGestureForRateChange); 360#if ENABLE(IOS_AIRPLAY) 361 m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequireUserGestureToShowPlaybackTargetPicker); 362#endif 363 } else { 364 // Relax RequireUserGestureForFullscreen when mediaPlaybackRequiresUserGesture is not set: 365 m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequireUserGestureForFullscreen); 366 } 367#endif // !PLATFORM(IOS) 368 369#if ENABLE(VIDEO_TRACK) 370 if (document.page()) 371 m_captionDisplayMode = document.page()->group().captionPreferences()->captionDisplayMode(); 372#endif 373 374 registerWithDocument(document); 375} 376 377HTMLMediaElement::~HTMLMediaElement() 378{ 379 LOG(Media, "HTMLMediaElement::~HTMLMediaElement"); 380 381 m_asyncEventQueue.close(); 382 383 setShouldDelayLoadEvent(false); 384 unregisterWithDocument(document()); 385 386#if ENABLE(VIDEO_TRACK) 387 if (m_audioTracks) { 388 m_audioTracks->clearElement(); 389 for (unsigned i = 0; i < m_audioTracks->length(); ++i) 390 m_audioTracks->item(i)->clearClient(); 391 } 392 if (m_textTracks) 393 m_textTracks->clearElement(); 394 if (m_textTracks) { 395 for (unsigned i = 0; i < m_textTracks->length(); ++i) 396 m_textTracks->item(i)->clearClient(); 397 } 398 if (m_videoTracks) { 399 m_videoTracks->clearElement(); 400 for (unsigned i = 0; i < m_videoTracks->length(); ++i) 401 m_videoTracks->item(i)->clearClient(); 402 } 403#endif 404 405#if ENABLE(IOS_AIRPLAY) 406 if (!hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) 407 m_mediaSession->setHasPlaybackTargetAvailabilityListeners(*this, false); 408#endif 409 410 if (m_mediaController) { 411 m_mediaController->removeMediaElement(this); 412 m_mediaController = 0; 413 } 414 415#if ENABLE(MEDIA_SOURCE) 416 closeMediaSource(); 417#endif 418 419#if ENABLE(ENCRYPTED_MEDIA_V2) 420 setMediaKeys(0); 421#endif 422 423#if ENABLE(MEDIA_CONTROLS_SCRIPT) 424 if (m_isolatedWorld) 425 m_isolatedWorld->clearWrappers(); 426#endif 427 428 m_completelyLoaded = true; 429 if (m_player) 430 m_player->clearMediaPlayerClient(); 431} 432 433void HTMLMediaElement::registerWithDocument(Document& document) 434{ 435 if (m_isWaitingUntilMediaCanStart) 436 document.addMediaCanStartListener(this); 437 438#if !PLATFORM(IOS) 439 document.registerForMediaVolumeCallbacks(this); 440 document.registerForPrivateBrowsingStateChangedCallbacks(this); 441#endif 442 443 document.registerForVisibilityStateChangedCallbacks(this); 444 445#if ENABLE(VIDEO_TRACK) 446 document.registerForCaptionPreferencesChangedCallbacks(this); 447#endif 448 449#if ENABLE(MEDIA_CONTROLS_SCRIPT) 450 if (m_mediaControlsDependOnPageScaleFactor) 451 document.registerForPageScaleFactorChangedCallbacks(this); 452#endif 453 454 addElementToDocumentMap(*this, document); 455} 456 457void HTMLMediaElement::unregisterWithDocument(Document& document) 458{ 459 if (m_isWaitingUntilMediaCanStart) 460 document.removeMediaCanStartListener(this); 461 462#if !PLATFORM(IOS) 463 document.unregisterForMediaVolumeCallbacks(this); 464 document.unregisterForPrivateBrowsingStateChangedCallbacks(this); 465#endif 466 467 document.unregisterForVisibilityStateChangedCallbacks(this); 468 469#if ENABLE(VIDEO_TRACK) 470 document.unregisterForCaptionPreferencesChangedCallbacks(this); 471#endif 472 473#if ENABLE(MEDIA_CONTROLS_SCRIPT) 474 if (m_mediaControlsDependOnPageScaleFactor) 475 document.unregisterForPageScaleFactorChangedCallbacks(this); 476#endif 477 478 removeElementFromDocumentMap(*this, document); 479} 480 481void HTMLMediaElement::didMoveToNewDocument(Document* oldDocument) 482{ 483 if (m_shouldDelayLoadEvent) { 484 if (oldDocument) 485 oldDocument->decrementLoadEventDelayCount(); 486 document().incrementLoadEventDelayCount(); 487 } 488 489 if (oldDocument) { 490 unregisterWithDocument(*oldDocument); 491 } 492 493 registerWithDocument(document()); 494 495 HTMLElement::didMoveToNewDocument(oldDocument); 496} 497 498bool HTMLMediaElement::hasCustomFocusLogic() const 499{ 500 return true; 501} 502 503bool HTMLMediaElement::supportsFocus() const 504{ 505 if (document().isMediaDocument()) 506 return false; 507 508 // If no controls specified, we should still be able to focus the element if it has tabIndex. 509 return controls() || HTMLElement::supportsFocus(); 510} 511 512bool HTMLMediaElement::isMouseFocusable() const 513{ 514 return false; 515} 516 517#if PLATFORM(IOS) 518bool HTMLMediaElement::parseMediaPlayerAttribute(const QualifiedName& name, const AtomicString& value) 519{ 520 ASSERT(m_player); 521 if (name == data_youtube_idAttr) { 522 m_player->attributeChanged(name.toString(), value); 523 return true; 524 } 525 if (name == titleAttr) { 526 m_player->attributeChanged(name.toString(), value); 527 return true; 528 } 529 530 if (Settings* settings = document().settings()) { 531#if ENABLE(IOS_AIRPLAY) 532 if (name == webkitairplayAttr && settings->mediaPlaybackAllowsAirPlay()) { 533 m_player->attributeChanged(name.toString(), value); 534 return true; 535 } 536#endif 537 if (name == webkit_playsinlineAttr && settings->mediaPlaybackAllowsInline()) { 538 m_player->attributeChanged(name.toString(), ASCIILiteral(value.isNull() ? "false" : "true")); 539 return true; 540 } 541 } 542 return false; 543} 544#endif 545 546void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 547{ 548 if (name == srcAttr) { 549#if PLATFORM(IOS) 550 // Note, unless the restriction on requiring user action has been removed, 551 // do not begin downloading data on iOS. 552 if (!value.isNull() && m_mediaSession->dataLoadingPermitted(*this)) { 553#else 554 // Trigger a reload, as long as the 'src' attribute is present. 555 if (!value.isNull()) { 556#endif 557 clearMediaPlayer(LoadMediaResource); 558 scheduleDelayedAction(LoadMediaResource); 559 } 560 } else if (name == controlsAttr) 561 configureMediaControls(); 562 else if (name == loopAttr) 563 updateSleepDisabling(); 564 else if (name == preloadAttr) { 565 if (equalIgnoringCase(value, "none")) 566 m_preload = MediaPlayer::None; 567 else if (equalIgnoringCase(value, "metadata")) 568 m_preload = MediaPlayer::MetaData; 569 else { 570 // The spec does not define an "invalid value default" but "auto" is suggested as the 571 // "missing value default", so use it for everything except "none" and "metadata" 572 m_preload = MediaPlayer::Auto; 573 } 574 575 // The attribute must be ignored if the autoplay attribute is present 576 if (!autoplay() && m_player) 577 m_player->setPreload(m_mediaSession->effectivePreloadForElement(*this)); 578 579 } else if (name == mediagroupAttr) 580 setMediaGroup(value); 581 else if (name == onabortAttr) 582 setAttributeEventListener(eventNames().abortEvent, name, value); 583 else if (name == onbeforeloadAttr) 584 setAttributeEventListener(eventNames().beforeloadEvent, name, value); 585 else if (name == oncanplayAttr) 586 setAttributeEventListener(eventNames().canplayEvent, name, value); 587 else if (name == oncanplaythroughAttr) 588 setAttributeEventListener(eventNames().canplaythroughEvent, name, value); 589 else if (name == ondurationchangeAttr) 590 setAttributeEventListener(eventNames().durationchangeEvent, name, value); 591 else if (name == onemptiedAttr) 592 setAttributeEventListener(eventNames().emptiedEvent, name, value); 593 else if (name == onendedAttr) 594 setAttributeEventListener(eventNames().endedEvent, name, value); 595 else if (name == onerrorAttr) 596 setAttributeEventListener(eventNames().errorEvent, name, value); 597 else if (name == onloadeddataAttr) 598 setAttributeEventListener(eventNames().loadeddataEvent, name, value); 599 else if (name == onloadedmetadataAttr) 600 setAttributeEventListener(eventNames().loadedmetadataEvent, name, value); 601 else if (name == onloadstartAttr) 602 setAttributeEventListener(eventNames().loadstartEvent, name, value); 603 else if (name == onpauseAttr) 604 setAttributeEventListener(eventNames().pauseEvent, name, value); 605 else if (name == onplayAttr) 606 setAttributeEventListener(eventNames().playEvent, name, value); 607 else if (name == onplayingAttr) 608 setAttributeEventListener(eventNames().playingEvent, name, value); 609 else if (name == onprogressAttr) 610 setAttributeEventListener(eventNames().progressEvent, name, value); 611 else if (name == onratechangeAttr) 612 setAttributeEventListener(eventNames().ratechangeEvent, name, value); 613 else if (name == onseekedAttr) 614 setAttributeEventListener(eventNames().seekedEvent, name, value); 615 else if (name == onseekingAttr) 616 setAttributeEventListener(eventNames().seekingEvent, name, value); 617 else if (name == onstalledAttr) 618 setAttributeEventListener(eventNames().stalledEvent, name, value); 619 else if (name == onsuspendAttr) 620 setAttributeEventListener(eventNames().suspendEvent, name, value); 621 else if (name == ontimeupdateAttr) 622 setAttributeEventListener(eventNames().timeupdateEvent, name, value); 623 else if (name == onvolumechangeAttr) 624 setAttributeEventListener(eventNames().volumechangeEvent, name, value); 625 else if (name == onwaitingAttr) 626 setAttributeEventListener(eventNames().waitingEvent, name, value); 627 else if (name == onwebkitbeginfullscreenAttr) 628 setAttributeEventListener(eventNames().webkitbeginfullscreenEvent, name, value); 629 else if (name == onwebkitendfullscreenAttr) 630 setAttributeEventListener(eventNames().webkitendfullscreenEvent, name, value); 631#if ENABLE(IOS_AIRPLAY) 632 else if (name == onwebkitcurrentplaybacktargetiswirelesschangedAttr) 633 setAttributeEventListener(eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent, name, value); 634 else if (name == onwebkitplaybacktargetavailabilitychangedAttr) 635 setAttributeEventListener(eventNames().webkitplaybacktargetavailabilitychangedEvent, name, value); 636#endif 637#if PLATFORM(IOS) 638 else if (m_player && parseMediaPlayerAttribute(name, value)) 639 return; 640#endif 641 else 642 HTMLElement::parseAttribute(name, value); 643} 644 645void HTMLMediaElement::finishParsingChildren() 646{ 647 HTMLElement::finishParsingChildren(); 648 m_parsingInProgress = false; 649 650#if ENABLE(VIDEO_TRACK) 651 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 652 return; 653 654 if (descendantsOfType<HTMLTrackElement>(*this).first()) 655 scheduleDelayedAction(ConfigureTextTracks); 656#endif 657} 658 659bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style) 660{ 661 return controls() && HTMLElement::rendererIsNeeded(style); 662} 663 664RenderPtr<RenderElement> HTMLMediaElement::createElementRenderer(PassRef<RenderStyle> style) 665{ 666 return createRenderer<RenderMedia>(*this, WTF::move(style)); 667} 668 669bool HTMLMediaElement::childShouldCreateRenderer(const Node& child) const 670{ 671#if ENABLE(MEDIA_CONTROLS_SCRIPT) 672 return hasShadowRootParent(child) && HTMLElement::childShouldCreateRenderer(child); 673#else 674 if (!hasMediaControls()) 675 return false; 676 // <media> doesn't allow its content, including shadow subtree, to 677 // be rendered. So this should return false for most of the children. 678 // One exception is a shadow tree built for rendering controls which should be visible. 679 // So we let them go here by comparing its subtree root with one of the controls. 680 return &mediaControls()->treeScope() == &child.treeScope() 681 && hasShadowRootParent(child) 682 && HTMLElement::childShouldCreateRenderer(child); 683#endif 684} 685 686Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode& insertionPoint) 687{ 688 LOG(Media, "HTMLMediaElement::insertedInto"); 689 690 HTMLElement::insertedInto(insertionPoint); 691 if (insertionPoint.inDocument()) { 692 m_inActiveDocument = true; 693 694#if PLATFORM(IOS) 695 if (m_networkState == NETWORK_EMPTY && !fastGetAttribute(srcAttr).isEmpty() && m_mediaSession->dataLoadingPermitted(*this)) 696#else 697 if (m_networkState == NETWORK_EMPTY && !fastGetAttribute(srcAttr).isEmpty()) 698#endif 699 scheduleDelayedAction(LoadMediaResource); 700 } 701 702 if (!m_explicitlyMuted) { 703 m_explicitlyMuted = true; 704 m_muted = fastHasAttribute(mutedAttr); 705 } 706 707 configureMediaControls(); 708 return InsertionDone; 709} 710 711void HTMLMediaElement::removedFrom(ContainerNode& insertionPoint) 712{ 713 LOG(Media, "HTMLMediaElement::removedFrom"); 714 715 m_inActiveDocument = false; 716 if (insertionPoint.inDocument()) { 717 if (hasMediaControls()) 718 mediaControls()->hide(); 719 if (m_networkState > NETWORK_EMPTY) 720 pause(); 721 if (m_isFullscreen) 722 exitFullscreen(); 723 724 if (m_player) { 725 JSC::VM& vm = JSDOMWindowBase::commonVM(); 726 JSC::JSLockHolder lock(vm); 727 728 size_t extraMemoryCost = m_player->extraMemoryCost(); 729 size_t extraMemoryCostDelta = extraMemoryCost - m_reportedExtraMemoryCost; 730 m_reportedExtraMemoryCost = extraMemoryCost; 731 732 if (extraMemoryCostDelta > 0) 733 vm.heap.reportExtraMemoryCost(extraMemoryCostDelta); 734 } 735 } 736 737 HTMLElement::removedFrom(insertionPoint); 738} 739 740void HTMLMediaElement::willAttachRenderers() 741{ 742 ASSERT(!renderer()); 743} 744 745void HTMLMediaElement::didAttachRenderers() 746{ 747 if (renderer()) 748 renderer()->updateFromElement(); 749} 750 751void HTMLMediaElement::didRecalcStyle(Style::Change) 752{ 753 if (renderer()) 754 renderer()->updateFromElement(); 755} 756 757void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType) 758{ 759 LOG(Media, "HTMLMediaElement::scheduleLoad"); 760 761 if ((actionType & LoadMediaResource) && !(m_pendingActionFlags & LoadMediaResource)) { 762 prepareForLoad(); 763 setFlags(m_pendingActionFlags, LoadMediaResource); 764 } 765 766#if ENABLE(VIDEO_TRACK) 767 if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() && (actionType & ConfigureTextTracks)) 768 setFlags(m_pendingActionFlags, ConfigureTextTracks); 769#endif 770 771#if USE(PLATFORM_TEXT_TRACK_MENU) 772 if (actionType & TextTrackChangesNotification) 773 setFlags(m_pendingActionFlags, TextTrackChangesNotification); 774#endif 775 776 m_loadTimer.startOneShot(0); 777} 778 779void HTMLMediaElement::scheduleNextSourceChild() 780{ 781 // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad. 782 setFlags(m_pendingActionFlags, LoadMediaResource); 783 m_loadTimer.startOneShot(0); 784} 785 786void HTMLMediaElement::scheduleEvent(const AtomicString& eventName) 787{ 788#if LOG_MEDIA_EVENTS 789 LOG(Media, "HTMLMediaElement::scheduleEvent - scheduling '%s'", eventName.string().ascii().data()); 790#endif 791 RefPtr<Event> event = Event::create(eventName, false, true); 792 793 // Don't set the event target, the event queue will set it in GenericEventQueue::timerFired and setting it here 794 // will trigger an ASSERT if this element has been marked for deletion. 795 796 m_asyncEventQueue.enqueueEvent(event.release()); 797} 798 799void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>&) 800{ 801 Ref<HTMLMediaElement> protect(*this); // loadNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations. 802 803#if ENABLE(VIDEO_TRACK) 804 if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() && (m_pendingActionFlags & ConfigureTextTracks)) 805 configureTextTracks(); 806#endif 807 808 if (m_pendingActionFlags & LoadMediaResource) { 809 if (m_loadState == LoadingFromSourceElement) 810 loadNextSourceChild(); 811 else 812 loadInternal(); 813 } 814 815#if USE(PLATFORM_TEXT_TRACK_MENU) 816 if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() && (m_pendingActionFlags & TextTrackChangesNotification)) 817 notifyMediaPlayerOfTextTrackChanges(); 818#endif 819 820 m_pendingActionFlags = 0; 821} 822 823PassRefPtr<MediaError> HTMLMediaElement::error() const 824{ 825 return m_error; 826} 827 828void HTMLMediaElement::setSrc(const String& url) 829{ 830 setAttribute(srcAttr, url); 831} 832 833#if ENABLE(MEDIA_STREAM) 834void HTMLMediaElement::setSrcObject(MediaStream* mediaStream) 835{ 836 // FIXME: Setting the srcObject attribute may cause other changes to the media element's internal state: 837 // Specifically, if srcObject is specified, the UA must use it as the source of media, even if the src 838 // attribute is also set or children are present. If the value of srcObject is replaced or set to null 839 // the UA must re-run the media element load algorithm. 840 // 841 // https://bugs.webkit.org/show_bug.cgi?id=124896 842 843 m_mediaStreamSrcObject = mediaStream; 844} 845#endif 846 847HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const 848{ 849 return m_networkState; 850} 851 852String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySystem, const URL& url) const 853{ 854 MediaEngineSupportParameters parameters; 855 ContentType contentType(mimeType); 856 parameters.type = contentType.type().lower(); 857 parameters.codecs = contentType.parameter(ASCIILiteral("codecs")); 858 parameters.url = url; 859#if ENABLE(ENCRYPTED_MEDIA) 860 parameters.keySystem = keySystem; 861#else 862 UNUSED_PARAM(keySystem); 863#endif 864 MediaPlayer::SupportsType support = MediaPlayer::supportsType(parameters, this); 865 String canPlay; 866 867 // 4.8.10.3 868 switch (support) 869 { 870 case MediaPlayer::IsNotSupported: 871 canPlay = emptyString(); 872 break; 873 case MediaPlayer::MayBeSupported: 874 canPlay = ASCIILiteral("maybe"); 875 break; 876 case MediaPlayer::IsSupported: 877 canPlay = ASCIILiteral("probably"); 878 break; 879 } 880 881 LOG(Media, "HTMLMediaElement::canPlayType(%s, %s, %s) -> %s", mimeType.utf8().data(), keySystem.utf8().data(), url.stringCenterEllipsizedToLength().utf8().data(), canPlay.utf8().data()); 882 883 return canPlay; 884} 885 886void HTMLMediaElement::load() 887{ 888 Ref<HTMLMediaElement> protect(*this); // loadInternal may result in a 'beforeload' event, which can make arbitrary DOM mutations. 889 890 LOG(Media, "HTMLMediaElement::load()"); 891 892 if (!m_mediaSession->dataLoadingPermitted(*this)) 893 return; 894 if (ScriptController::processingUserGesture()) 895 removeBehaviorsRestrictionsAfterFirstUserGesture(); 896 897 prepareForLoad(); 898 loadInternal(); 899 prepareToPlay(); 900} 901 902void HTMLMediaElement::prepareForLoad() 903{ 904 LOG(Media, "HTMLMediaElement::prepareForLoad"); 905 906 // Perform the cleanup required for the resource load algorithm to run. 907 stopPeriodicTimers(); 908 m_loadTimer.stop(); 909 // FIXME: Figure out appropriate place to reset LoadTextTrackResource if necessary and set m_pendingActionFlags to 0 here. 910 m_pendingActionFlags &= ~LoadMediaResource; 911 m_sentEndEvent = false; 912 m_sentStalledEvent = false; 913 m_haveFiredLoadedData = false; 914 m_completelyLoaded = false; 915 m_havePreparedToPlay = false; 916 m_displayMode = Unknown; 917 m_currentSrc = URL(); 918 919 // 1 - Abort any already-running instance of the resource selection algorithm for this element. 920 m_loadState = WaitingForSource; 921 m_currentSourceNode = 0; 922 923 // 2 - If there are any tasks from the media element's media element event task source in 924 // one of the task queues, then remove those tasks. 925 cancelPendingEventsAndCallbacks(); 926 927 // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue 928 // a task to fire a simple event named abort at the media element. 929 if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE) 930 scheduleEvent(eventNames().abortEvent); 931 932#if ENABLE(MEDIA_SOURCE) 933 closeMediaSource(); 934#endif 935 936 createMediaPlayer(); 937 938 // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps 939 if (m_networkState != NETWORK_EMPTY) { 940 // 4.1 - Queue a task to fire a simple event named emptied at the media element. 941 scheduleEvent(eventNames().emptiedEvent); 942 943 // 4.2 - If a fetching process is in progress for the media element, the user agent should stop it. 944 m_networkState = NETWORK_EMPTY; 945 946 // 4.3 - Forget the media element's media-resource-specific tracks. 947 forgetResourceSpecificTracks(); 948 949 // 4.4 - If readyState is not set to HAVE_NOTHING, then set it to that state. 950 m_readyState = HAVE_NOTHING; 951 m_readyStateMaximum = HAVE_NOTHING; 952 953 // 4.5 - If the paused attribute is false, then set it to true. 954 m_paused = true; 955 956 // 4.6 - If seeking is true, set it to false. 957 m_seeking = false; 958 959 // 4.7 - Set the current playback position to 0. 960 // Set the official playback position to 0. 961 // If this changed the official playback position, then queue a task to fire a simple event named timeupdate at the media element. 962 // FIXME: Add support for firing this event. e.g., scheduleEvent(eventNames().timeUpdateEvent); 963 964 // 4.8 - Set the initial playback position to 0. 965 // FIXME: Make this less subtle. The position only becomes 0 because of the createMediaPlayer() call 966 // above. 967 refreshCachedTime(); 968 969 invalidateCachedTime(); 970 971 // 4.9 - Set the timeline offset to Not-a-Number (NaN). 972 // 4.10 - Update the duration attribute to Not-a-Number (NaN). 973 974 updateMediaController(); 975#if ENABLE(VIDEO_TRACK) 976 if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 977 updateActiveTextTrackCues(0); 978#endif 979 } 980 981 // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute. 982 setPlaybackRate(defaultPlaybackRate()); 983 984 // 6 - Set the error attribute to null and the autoplaying flag to true. 985 m_error = 0; 986 m_autoplaying = true; 987 988 // 7 - Invoke the media element's resource selection algorithm. 989 990 // 8 - Note: Playback of any previously playing media resource for this element stops. 991 992 // The resource selection algorithm 993 // 1 - Set the networkState to NETWORK_NO_SOURCE 994 m_networkState = NETWORK_NO_SOURCE; 995 996 // 2 - Asynchronously await a stable state. 997 998 m_playedTimeRanges = TimeRanges::create(); 999 1000 // FIXME: Investigate whether these can be moved into m_networkState != NETWORK_EMPTY block above 1001 // so they are closer to the relevant spec steps. 1002 m_lastSeekTime = 0; 1003 1004 // The spec doesn't say to block the load event until we actually run the asynchronous section 1005 // algorithm, but do it now because we won't start that until after the timer fires and the 1006 // event may have already fired by then. 1007 MediaPlayer::Preload effectivePreload = m_mediaSession->effectivePreloadForElement(*this); 1008 if (effectivePreload != MediaPlayer::None) 1009 setShouldDelayLoadEvent(true); 1010 1011#if PLATFORM(IOS) 1012 Settings* settings = document().settings(); 1013 if (effectivePreload != MediaPlayer::None && settings && settings->mediaDataLoadsAutomatically()) 1014 prepareToPlay(); 1015#endif 1016 1017 configureMediaControls(); 1018} 1019 1020void HTMLMediaElement::loadInternal() 1021{ 1022 // Some of the code paths below this function dispatch the BeforeLoad event. This ASSERT helps 1023 // us catch those bugs more quickly without needing all the branches to align to actually 1024 // trigger the event. 1025 ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden()); 1026 1027 // If we can't start a load right away, start it later. 1028 if (!m_mediaSession->pageAllowsDataLoading(*this)) { 1029 setShouldDelayLoadEvent(false); 1030 if (m_isWaitingUntilMediaCanStart) 1031 return; 1032 document().addMediaCanStartListener(this); 1033 m_isWaitingUntilMediaCanStart = true; 1034 return; 1035 } 1036 1037 clearFlags(m_pendingActionFlags, LoadMediaResource); 1038 1039 // Once the page has allowed an element to load media, it is free to load at will. This allows a 1040 // playlist that starts in a foreground tab to continue automatically if the tab is subsequently 1041 // put into the background. 1042 m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequirePageConsentToLoadMedia); 1043 1044#if ENABLE(VIDEO_TRACK) 1045 if (hasMediaControls()) 1046 mediaControls()->changedClosedCaptionsVisibility(); 1047 1048 // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the 1049 // disabled state when the element's resource selection algorithm last started". 1050 if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) { 1051 m_textTracksWhenResourceSelectionBegan.clear(); 1052 if (m_textTracks) { 1053 for (unsigned i = 0; i < m_textTracks->length(); ++i) { 1054 TextTrack* track = m_textTracks->item(i); 1055 if (track->mode() != TextTrack::disabledKeyword()) 1056 m_textTracksWhenResourceSelectionBegan.append(track); 1057 } 1058 } 1059 } 1060#endif 1061 1062 selectMediaResource(); 1063} 1064 1065void HTMLMediaElement::selectMediaResource() 1066{ 1067 LOG(Media, "HTMLMediaElement::selectMediaResource"); 1068 1069 enum Mode { attribute, children }; 1070 1071 // 3 - If the media element has a src attribute, then let mode be attribute. 1072 Mode mode = attribute; 1073 if (!fastHasAttribute(srcAttr)) { 1074 // Otherwise, if the media element does not have a src attribute but has a source 1075 // element child, then let mode be children and let candidate be the first such 1076 // source element child in tree order. 1077 if (auto firstSource = childrenOfType<HTMLSourceElement>(*this).first()) { 1078 mode = children; 1079 m_nextChildNodeToConsider = firstSource; 1080 m_currentSourceNode = 0; 1081 } else { 1082 // Otherwise the media element has neither a src attribute nor a source element 1083 // child: set the networkState to NETWORK_EMPTY, and abort these steps; the 1084 // synchronous section ends. 1085 m_loadState = WaitingForSource; 1086 setShouldDelayLoadEvent(false); 1087 m_networkState = NETWORK_EMPTY; 1088 1089 LOG(Media, "HTMLMediaElement::selectMediaResource, nothing to load"); 1090 return; 1091 } 1092 } 1093 1094 // 4 - Set the media element's delaying-the-load-event flag to true (this delays the load event), 1095 // and set its networkState to NETWORK_LOADING. 1096 setShouldDelayLoadEvent(true); 1097 m_networkState = NETWORK_LOADING; 1098 1099 // 5 - Queue a task to fire a simple event named loadstart at the media element. 1100 scheduleEvent(eventNames().loadstartEvent); 1101 1102 // 6 - If mode is attribute, then run these substeps 1103 if (mode == attribute) { 1104 m_loadState = LoadingFromSrcAttr; 1105 1106 // If the src attribute's value is the empty string ... jump down to the failed step below 1107 URL mediaURL = getNonEmptyURLAttribute(srcAttr); 1108 if (mediaURL.isEmpty()) { 1109 mediaLoadingFailed(MediaPlayer::FormatError); 1110 LOG(Media, "HTMLMediaElement::selectMediaResource, empty 'src'"); 1111 return; 1112 } 1113 1114 if (!isSafeToLoadURL(mediaURL, Complain) || !dispatchBeforeLoadEvent(mediaURL.string())) { 1115 mediaLoadingFailed(MediaPlayer::FormatError); 1116 return; 1117 } 1118 1119 // No type or key system information is available when the url comes 1120 // from the 'src' attribute so MediaPlayer 1121 // will have to pick a media engine based on the file extension. 1122 ContentType contentType((String())); 1123 loadResource(mediaURL, contentType, String()); 1124 LOG(Media, "HTMLMediaElement::selectMediaResource, using 'src' attribute url"); 1125 return; 1126 } 1127 1128 // Otherwise, the source elements will be used 1129 loadNextSourceChild(); 1130} 1131 1132void HTMLMediaElement::loadNextSourceChild() 1133{ 1134 ContentType contentType((String())); 1135 String keySystem; 1136 URL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain); 1137 if (!mediaURL.isValid()) { 1138 waitForSourceChange(); 1139 return; 1140 } 1141 1142 // Recreate the media player for the new url 1143 createMediaPlayer(); 1144 1145 m_loadState = LoadingFromSourceElement; 1146 loadResource(mediaURL, contentType, keySystem); 1147} 1148 1149static URL createFileURLForApplicationCacheResource(const String& path) 1150{ 1151 // URL should have a function to create a url from a path, but it does not. This function 1152 // is not suitable because URL::setPath uses encodeWithURLEscapeSequences, which it notes 1153 // does not correctly escape '#' and '?'. This function works for our purposes because 1154 // app cache media files are always created with encodeForFileName(createCanonicalUUIDString()). 1155 1156#if USE(CF) && PLATFORM(WIN) 1157 RetainPtr<CFURLRef> cfURL = adoptCF(CFURLCreateWithFileSystemPath(0, path.createCFString().get(), kCFURLWindowsPathStyle, false)); 1158 URL url(cfURL.get()); 1159#else 1160 URL url; 1161 1162 url.setProtocol(ASCIILiteral("file")); 1163 url.setPath(path); 1164#endif 1165 return url; 1166} 1167 1168void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentType, const String& keySystem) 1169{ 1170 ASSERT(isSafeToLoadURL(initialURL, Complain)); 1171 1172 LOG(Media, "HTMLMediaElement::loadResource(%s, %s, %s)", urlForLoggingMedia(initialURL).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data()); 1173 1174 Frame* frame = document().frame(); 1175 if (!frame) { 1176 mediaLoadingFailed(MediaPlayer::FormatError); 1177 return; 1178 } 1179 1180 URL url = initialURL; 1181 if (!frame->loader().willLoadMediaElementURL(url)) { 1182 mediaLoadingFailed(MediaPlayer::FormatError); 1183 return; 1184 } 1185 1186 // The resource fetch algorithm 1187 m_networkState = NETWORK_LOADING; 1188 1189 // If the url should be loaded from the application cache, pass the url of the cached file 1190 // to the media engine. 1191 ApplicationCacheHost* cacheHost = frame->loader().documentLoader()->applicationCacheHost(); 1192 ApplicationCacheResource* resource = 0; 1193 if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource)) { 1194 // Resources that are not present in the manifest will always fail to load (at least, after the 1195 // cache has been primed the first time), making the testing of offline applications simpler. 1196 if (!resource || resource->path().isEmpty()) { 1197 mediaLoadingFailed(MediaPlayer::NetworkError); 1198 return; 1199 } 1200 } 1201 1202 // Set m_currentSrc *before* changing to the cache url, the fact that we are loading from the app 1203 // cache is an internal detail not exposed through the media element API. 1204 m_currentSrc = url; 1205 1206 if (resource) { 1207 url = createFileURLForApplicationCacheResource(resource->path()); 1208 LOG(Media, "HTMLMediaElement::loadResource - will load from app cache -> %s", urlForLoggingMedia(url).utf8().data()); 1209 } 1210 1211 LOG(Media, "HTMLMediaElement::loadResource - m_currentSrc -> %s", urlForLoggingMedia(m_currentSrc).utf8().data()); 1212 1213#if ENABLE(MEDIA_STREAM) 1214 if (MediaStreamRegistry::registry().lookup(url.string())) 1215 m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequireUserGestureForRateChange); 1216#endif 1217 1218 if (m_sendProgressEvents) 1219 startProgressEventTimer(); 1220 1221 bool privateMode = document().page() && document().page()->usesEphemeralSession(); 1222 m_player->setPrivateBrowsingMode(privateMode); 1223 1224 // Reset display mode to force a recalculation of what to show because we are resetting the player. 1225 setDisplayMode(Unknown); 1226 1227 if (!autoplay()) 1228 m_player->setPreload(m_mediaSession->effectivePreloadForElement(*this)); 1229 m_player->setPreservesPitch(m_webkitPreservesPitch); 1230 1231 if (!m_explicitlyMuted) { 1232 m_explicitlyMuted = true; 1233 m_muted = fastHasAttribute(mutedAttr); 1234 } 1235 1236 updateVolume(); 1237 1238#if ENABLE(MEDIA_SOURCE) 1239 ASSERT(!m_mediaSource); 1240 1241 if (url.protocolIs(mediaSourceBlobProtocol)) 1242 m_mediaSource = MediaSource::lookup(url.string()); 1243 1244 if (m_mediaSource) { 1245 if (m_mediaSource->attachToElement(this)) 1246 m_player->load(url, contentType, m_mediaSource.get()); 1247 else { 1248 // Forget our reference to the MediaSource, so we leave it alone 1249 // while processing remainder of load failure. 1250 m_mediaSource = 0; 1251 mediaLoadingFailed(MediaPlayer::FormatError); 1252 } 1253 } else 1254#endif 1255 if (!m_player->load(url, contentType, keySystem)) 1256 mediaLoadingFailed(MediaPlayer::FormatError); 1257 1258 m_mediaSession->applyMediaPlayerRestrictions(*this); 1259 1260 // If there is no poster to display, allow the media engine to render video frames as soon as 1261 // they are available. 1262 updateDisplayState(); 1263 1264 if (renderer()) 1265 renderer()->updateFromElement(); 1266} 1267 1268#if ENABLE(VIDEO_TRACK) 1269static bool trackIndexCompare(TextTrack* a, 1270 TextTrack* b) 1271{ 1272 return a->trackIndex() - b->trackIndex() < 0; 1273} 1274 1275static bool eventTimeCueCompare(const std::pair<double, TextTrackCue*>& a, 1276 const std::pair<double, TextTrackCue*>& b) 1277{ 1278 // 12 - Sort the tasks in events in ascending time order (tasks with earlier 1279 // times first). 1280 if (a.first != b.first) 1281 return a.first - b.first < 0; 1282 1283 // If the cues belong to different text tracks, it doesn't make sense to 1284 // compare the two tracks by the relative cue order, so return the relative 1285 // track order. 1286 if (a.second->track() != b.second->track()) 1287 return trackIndexCompare(a.second->track(), b.second->track()); 1288 1289 // 12 - Further sort tasks in events that have the same time by the 1290 // relative text track cue order of the text track cues associated 1291 // with these tasks. 1292 return a.second->cueIndex() - b.second->cueIndex() < 0; 1293} 1294 1295static bool compareCueInterval(const CueInterval& one, const CueInterval& two) 1296{ 1297 return one.data()->isOrderedBefore(two.data()); 1298}; 1299 1300 1301void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) 1302{ 1303 // 4.8.10.8 Playing the media resource 1304 1305 // If the current playback position changes while the steps are running, 1306 // then the user agent must wait for the steps to complete, and then must 1307 // immediately rerun the steps. 1308 if (ignoreTrackDisplayUpdateRequests()) 1309 return; 1310 1311 LOG(Media, "HTMLMediaElement::updateActiveTextTracks"); 1312 1313 // 1 - Let current cues be a list of cues, initialized to contain all the 1314 // cues of all the hidden, showing, or showing by default text tracks of the 1315 // media element (not the disabled ones) whose start times are less than or 1316 // equal to the current playback position and whose end times are greater 1317 // than the current playback position. 1318 CueList currentCues; 1319 1320 // The user agent must synchronously unset [the text track cue active] flag 1321 // whenever ... the media element's readyState is changed back to HAVE_NOTHING. 1322 if (m_readyState != HAVE_NOTHING && m_player) { 1323 currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime)); 1324 if (currentCues.size() > 1) 1325 std::sort(currentCues.begin(), currentCues.end(), &compareCueInterval); 1326 } 1327 1328 CueList previousCues; 1329 CueList missedCues; 1330 1331 // 2 - Let other cues be a list of cues, initialized to contain all the cues 1332 // of hidden, showing, and showing by default text tracks of the media 1333 // element that are not present in current cues. 1334 previousCues = m_currentlyActiveCues; 1335 1336 // 3 - Let last time be the current playback position at the time this 1337 // algorithm was last run for this media element, if this is not the first 1338 // time it has run. 1339 double lastTime = m_lastTextTrackUpdateTime; 1340 1341 // 4 - If the current playback position has, since the last time this 1342 // algorithm was run, only changed through its usual monotonic increase 1343 // during normal playback, then let missed cues be the list of cues in other 1344 // cues whose start times are greater than or equal to last time and whose 1345 // end times are less than or equal to the current playback position. 1346 // Otherwise, let missed cues be an empty list. 1347 if (lastTime >= 0 && m_lastSeekTime < movieTime) { 1348 CueList potentiallySkippedCues = 1349 m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime)); 1350 1351 for (size_t i = 0; i < potentiallySkippedCues.size(); ++i) { 1352 double cueStartTime = potentiallySkippedCues[i].low(); 1353 double cueEndTime = potentiallySkippedCues[i].high(); 1354 1355 // Consider cues that may have been missed since the last seek time. 1356 if (cueStartTime > std::max(m_lastSeekTime, lastTime) && cueEndTime < movieTime) 1357 missedCues.append(potentiallySkippedCues[i]); 1358 } 1359 } 1360 1361 m_lastTextTrackUpdateTime = movieTime; 1362 1363 // 5 - If the time was reached through the usual monotonic increase of the 1364 // current playback position during normal playback, and if the user agent 1365 // has not fired a timeupdate event at the element in the past 15 to 250ms 1366 // and is not still running event handlers for such an event, then the user 1367 // agent must queue a task to fire a simple event named timeupdate at the 1368 // element. (In the other cases, such as explicit seeks, relevant events get 1369 // fired as part of the overall process of changing the current playback 1370 // position.) 1371 if (!m_paused && m_lastSeekTime <= lastTime) 1372 scheduleTimeupdateEvent(false); 1373 1374 // Explicitly cache vector sizes, as their content is constant from here. 1375 size_t currentCuesSize = currentCues.size(); 1376 size_t missedCuesSize = missedCues.size(); 1377 size_t previousCuesSize = previousCues.size(); 1378 1379 // 6 - If all of the cues in current cues have their text track cue active 1380 // flag set, none of the cues in other cues have their text track cue active 1381 // flag set, and missed cues is empty, then abort these steps. 1382 bool activeSetChanged = missedCuesSize; 1383 1384 for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i) 1385 if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive()) 1386 activeSetChanged = true; 1387 1388 for (size_t i = 0; i < currentCuesSize; ++i) { 1389 TextTrackCue* cue = currentCues[i].data(); 1390 1391 if (cue->isRenderable()) 1392 toVTTCue(cue)->updateDisplayTree(movieTime); 1393 1394 if (!cue->isActive()) 1395 activeSetChanged = true; 1396 } 1397 1398 if (!activeSetChanged) 1399 return; 1400 1401 // 7 - If the time was reached through the usual monotonic increase of the 1402 // current playback position during normal playback, and there are cues in 1403 // other cues that have their text track cue pause-on-exi flag set and that 1404 // either have their text track cue active flag set or are also in missed 1405 // cues, then immediately pause the media element. 1406 for (size_t i = 0; !m_paused && i < previousCuesSize; ++i) { 1407 if (previousCues[i].data()->pauseOnExit() 1408 && previousCues[i].data()->isActive() 1409 && !currentCues.contains(previousCues[i])) 1410 pause(); 1411 } 1412 1413 for (size_t i = 0; !m_paused && i < missedCuesSize; ++i) { 1414 if (missedCues[i].data()->pauseOnExit()) 1415 pause(); 1416 } 1417 1418 // 8 - Let events be a list of tasks, initially empty. Each task in this 1419 // list will be associated with a text track, a text track cue, and a time, 1420 // which are used to sort the list before the tasks are queued. 1421 Vector<std::pair<double, TextTrackCue*>> eventTasks; 1422 1423 // 8 - Let affected tracks be a list of text tracks, initially empty. 1424 Vector<TextTrack*> affectedTracks; 1425 1426 for (size_t i = 0; i < missedCuesSize; ++i) { 1427 // 9 - For each text track cue in missed cues, prepare an event named enter 1428 // for the TextTrackCue object with the text track cue start time. 1429 eventTasks.append(std::make_pair(missedCues[i].data()->startTime(), 1430 missedCues[i].data())); 1431 1432 // 10 - For each text track [...] in missed cues, prepare an event 1433 // named exit for the TextTrackCue object with the with the later of 1434 // the text track cue end time and the text track cue start time. 1435 1436 // Note: An explicit task is added only if the cue is NOT a zero or 1437 // negative length cue. Otherwise, the need for an exit event is 1438 // checked when these tasks are actually queued below. This doesn't 1439 // affect sorting events before dispatch either, because the exit 1440 // event has the same time as the enter event. 1441 if (missedCues[i].data()->startTime() < missedCues[i].data()->endTime()) 1442 eventTasks.append(std::make_pair(missedCues[i].data()->endTime(), 1443 missedCues[i].data())); 1444 } 1445 1446 for (size_t i = 0; i < previousCuesSize; ++i) { 1447 // 10 - For each text track cue in other cues that has its text 1448 // track cue active flag set prepare an event named exit for the 1449 // TextTrackCue object with the text track cue end time. 1450 if (!currentCues.contains(previousCues[i])) 1451 eventTasks.append(std::make_pair(previousCues[i].data()->endTime(), 1452 previousCues[i].data())); 1453 } 1454 1455 for (size_t i = 0; i < currentCuesSize; ++i) { 1456 // 11 - For each text track cue in current cues that does not have its 1457 // text track cue active flag set, prepare an event named enter for the 1458 // TextTrackCue object with the text track cue start time. 1459 if (!previousCues.contains(currentCues[i])) 1460 eventTasks.append(std::make_pair(currentCues[i].data()->startTime(), 1461 currentCues[i].data())); 1462 } 1463 1464 // 12 - Sort the tasks in events in ascending time order (tasks with earlier 1465 // times first). 1466 std::sort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare); 1467 1468 for (size_t i = 0; i < eventTasks.size(); ++i) { 1469 if (!affectedTracks.contains(eventTasks[i].second->track())) 1470 affectedTracks.append(eventTasks[i].second->track()); 1471 1472 // 13 - Queue each task in events, in list order. 1473 RefPtr<Event> event; 1474 1475 // Each event in eventTasks may be either an enterEvent or an exitEvent, 1476 // depending on the time that is associated with the event. This 1477 // correctly identifies the type of the event, if the startTime is 1478 // less than the endTime in the cue. 1479 if (eventTasks[i].second->startTime() >= eventTasks[i].second->endTime()) { 1480 event = Event::create(eventNames().enterEvent, false, false); 1481 event->setTarget(eventTasks[i].second); 1482 m_asyncEventQueue.enqueueEvent(event.release()); 1483 1484 event = Event::create(eventNames().exitEvent, false, false); 1485 event->setTarget(eventTasks[i].second); 1486 m_asyncEventQueue.enqueueEvent(event.release()); 1487 } else { 1488 if (eventTasks[i].first == eventTasks[i].second->startTime()) 1489 event = Event::create(eventNames().enterEvent, false, false); 1490 else 1491 event = Event::create(eventNames().exitEvent, false, false); 1492 1493 event->setTarget(eventTasks[i].second); 1494 m_asyncEventQueue.enqueueEvent(event.release()); 1495 } 1496 } 1497 1498 // 14 - Sort affected tracks in the same order as the text tracks appear in 1499 // the media element's list of text tracks, and remove duplicates. 1500 std::sort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare); 1501 1502 // 15 - For each text track in affected tracks, in the list order, queue a 1503 // task to fire a simple event named cuechange at the TextTrack object, and, ... 1504 for (size_t i = 0; i < affectedTracks.size(); ++i) { 1505 RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false); 1506 event->setTarget(affectedTracks[i]); 1507 1508 m_asyncEventQueue.enqueueEvent(event.release()); 1509 1510 // ... if the text track has a corresponding track element, to then fire a 1511 // simple event named cuechange at the track element as well. 1512 if (affectedTracks[i]->trackType() == TextTrack::TrackElement) { 1513 RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false); 1514 HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(affectedTracks[i])->trackElement(); 1515 ASSERT(trackElement); 1516 event->setTarget(trackElement); 1517 1518 m_asyncEventQueue.enqueueEvent(event.release()); 1519 } 1520 } 1521 1522 // 16 - Set the text track cue active flag of all the cues in the current 1523 // cues, and unset the text track cue active flag of all the cues in the 1524 // other cues. 1525 for (size_t i = 0; i < currentCuesSize; ++i) 1526 currentCues[i].data()->setIsActive(true); 1527 1528 for (size_t i = 0; i < previousCuesSize; ++i) 1529 if (!currentCues.contains(previousCues[i])) 1530 previousCues[i].data()->setIsActive(false); 1531 1532 // Update the current active cues. 1533 m_currentlyActiveCues = currentCues; 1534 1535 if (activeSetChanged) 1536 updateTextTrackDisplay(); 1537} 1538 1539bool HTMLMediaElement::textTracksAreReady() const 1540{ 1541 // 4.8.10.12.1 Text track model 1542 // ... 1543 // The text tracks of a media element are ready if all the text tracks whose mode was not 1544 // in the disabled state when the element's resource selection algorithm last started now 1545 // have a text track readiness state of loaded or failed to load. 1546 for (unsigned i = 0; i < m_textTracksWhenResourceSelectionBegan.size(); ++i) { 1547 if (m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::Loading 1548 || m_textTracksWhenResourceSelectionBegan[i]->readinessState() == TextTrack::NotLoaded) 1549 return false; 1550 } 1551 1552 return true; 1553} 1554 1555void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track) 1556{ 1557 if (m_player && m_textTracksWhenResourceSelectionBegan.contains(track)) { 1558 if (track->readinessState() != TextTrack::Loading) 1559 setReadyState(m_player->readyState()); 1560 } else { 1561 // The track readiness state might have changed as a result of the user 1562 // clicking the captions button. In this case, a check whether all the 1563 // resources have failed loading should be done in order to hide the CC button. 1564 if (hasMediaControls() && track->readinessState() == TextTrack::FailedToLoad) 1565 mediaControls()->refreshClosedCaptionsButtonVisibility(); 1566 } 1567} 1568 1569void HTMLMediaElement::audioTrackEnabledChanged(AudioTrack* track) 1570{ 1571 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 1572 return; 1573 if (m_audioTracks && m_audioTracks->contains(track)) 1574 m_audioTracks->scheduleChangeEvent(); 1575} 1576 1577void HTMLMediaElement::textTrackModeChanged(TextTrack* track) 1578{ 1579 bool trackIsLoaded = true; 1580 if (track->trackType() == TextTrack::TrackElement) { 1581 trackIsLoaded = false; 1582 for (auto& trackElement : childrenOfType<HTMLTrackElement>(*this)) { 1583 if (trackElement.track() == track) { 1584 if (trackElement.readyState() == HTMLTrackElement::LOADING || trackElement.readyState() == HTMLTrackElement::LOADED) 1585 trackIsLoaded = true; 1586 break; 1587 } 1588 } 1589 } 1590 1591 // If this is the first added track, create the list of text tracks. 1592 if (!m_textTracks) 1593 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext()); 1594 1595 // Mark this track as "configured" so configureTextTracks won't change the mode again. 1596 track->setHasBeenConfigured(true); 1597 1598 if (track->mode() != TextTrack::disabledKeyword() && trackIsLoaded) 1599 textTrackAddCues(track, track->cues()); 1600 1601#if USE(PLATFORM_TEXT_TRACK_MENU) 1602 if (platformTextTrackMenu()) 1603 platformTextTrackMenu()->trackWasSelected(track->platformTextTrack()); 1604#endif 1605 1606 configureTextTrackDisplay(AssumeTextTrackVisibilityChanged); 1607 1608 if (m_textTracks && m_textTracks->contains(track)) 1609 m_textTracks->scheduleChangeEvent(); 1610 1611#if ENABLE(AVF_CAPTIONS) 1612 if (track->trackType() == TextTrack::TrackElement && m_player) 1613 m_player->notifyTrackModeChanged(); 1614#endif 1615} 1616 1617void HTMLMediaElement::videoTrackSelectedChanged(VideoTrack* track) 1618{ 1619 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 1620 return; 1621 if (m_videoTracks && m_videoTracks->contains(track)) 1622 m_videoTracks->scheduleChangeEvent(); 1623} 1624 1625void HTMLMediaElement::textTrackKindChanged(TextTrack* track) 1626{ 1627 if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword() && track->mode() == TextTrack::showingKeyword()) 1628 track->setMode(TextTrack::hiddenKeyword()); 1629} 1630 1631void HTMLMediaElement::beginIgnoringTrackDisplayUpdateRequests() 1632{ 1633 ++m_ignoreTrackDisplayUpdate; 1634} 1635 1636void HTMLMediaElement::endIgnoringTrackDisplayUpdateRequests() 1637{ 1638 ASSERT(m_ignoreTrackDisplayUpdate); 1639 --m_ignoreTrackDisplayUpdate; 1640 if (!m_ignoreTrackDisplayUpdate && m_inActiveDocument) 1641 updateActiveTextTrackCues(currentTime()); 1642} 1643 1644void HTMLMediaElement::textTrackAddCues(TextTrack* track, const TextTrackCueList* cues) 1645{ 1646 if (track->mode() == TextTrack::disabledKeyword()) 1647 return; 1648 1649 TrackDisplayUpdateScope scope(this); 1650 for (size_t i = 0; i < cues->length(); ++i) 1651 textTrackAddCue(track, cues->item(i)); 1652} 1653 1654void HTMLMediaElement::textTrackRemoveCues(TextTrack*, const TextTrackCueList* cues) 1655{ 1656 TrackDisplayUpdateScope scope(this); 1657 for (size_t i = 0; i < cues->length(); ++i) 1658 textTrackRemoveCue(cues->item(i)->track(), cues->item(i)); 1659} 1660 1661void HTMLMediaElement::textTrackAddCue(TextTrack* track, PassRefPtr<TextTrackCue> cue) 1662{ 1663 if (track->mode() == TextTrack::disabledKeyword()) 1664 return; 1665 1666 // Negative duration cues need be treated in the interval tree as 1667 // zero-length cues. 1668 double endTime = std::max(cue->startTime(), cue->endTime()); 1669 1670 CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get()); 1671 if (!m_cueTree.contains(interval)) 1672 m_cueTree.add(interval); 1673 updateActiveTextTrackCues(currentTime()); 1674} 1675 1676void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtr<TextTrackCue> cue) 1677{ 1678 // Negative duration cues need to be treated in the interval tree as 1679 // zero-length cues. 1680 double endTime = std::max(cue->startTime(), cue->endTime()); 1681 1682 CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get()); 1683 m_cueTree.remove(interval); 1684 1685#if ENABLE(WEBVTT_REGIONS) 1686 // Since the cue will be removed from the media element and likely the 1687 // TextTrack might also be destructed, notifying the region of the cue 1688 // removal shouldn't be done. 1689 if (cue->isRenderable()) 1690 toVTTCue(cue.get())->notifyRegionWhenRemovingDisplayTree(false); 1691#endif 1692 1693 size_t index = m_currentlyActiveCues.find(interval); 1694 if (index != notFound) { 1695 cue->setIsActive(false); 1696 m_currentlyActiveCues.remove(index); 1697 } 1698 1699 if (cue->isRenderable()) 1700 toVTTCue(cue.get())->removeDisplayTree(); 1701 updateActiveTextTrackCues(currentTime()); 1702 1703#if ENABLE(WEBVTT_REGIONS) 1704 if (cue->isRenderable()) 1705 toVTTCue(cue.get())->notifyRegionWhenRemovingDisplayTree(true); 1706#endif 1707} 1708 1709#endif 1710 1711bool HTMLMediaElement::isSafeToLoadURL(const URL& url, InvalidURLAction actionIfInvalid) 1712{ 1713 if (!url.isValid()) { 1714 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE because url is invalid", urlForLoggingMedia(url).utf8().data()); 1715 return false; 1716 } 1717 1718 Frame* frame = document().frame(); 1719 if (!frame || !document().securityOrigin()->canDisplay(url)) { 1720 if (actionIfInvalid == Complain) 1721 FrameLoader::reportLocalLoadFailed(frame, url.stringCenterEllipsizedToLength()); 1722 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE rejected by SecurityOrigin", urlForLoggingMedia(url).utf8().data()); 1723 return false; 1724 } 1725 1726 if (!document().contentSecurityPolicy()->allowMediaFromSource(url)) { 1727 LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> rejected by Content Security Policy", urlForLoggingMedia(url).utf8().data()); 1728 return false; 1729 } 1730 1731 return true; 1732} 1733 1734void HTMLMediaElement::startProgressEventTimer() 1735{ 1736 if (m_progressEventTimer.isActive()) 1737 return; 1738 1739 m_previousProgressTime = monotonicallyIncreasingTime(); 1740 // 350ms is not magic, it is in the spec! 1741 m_progressEventTimer.startRepeating(0.350); 1742} 1743 1744void HTMLMediaElement::waitForSourceChange() 1745{ 1746 LOG(Media, "HTMLMediaElement::waitForSourceChange"); 1747 1748 stopPeriodicTimers(); 1749 m_loadState = WaitingForSource; 1750 1751 // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value 1752 m_networkState = NETWORK_NO_SOURCE; 1753 1754 // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. 1755 setShouldDelayLoadEvent(false); 1756 1757 updateDisplayState(); 1758 1759 if (renderer()) 1760 renderer()->updateFromElement(); 1761} 1762 1763void HTMLMediaElement::noneSupported() 1764{ 1765 LOG(Media, "HTMLMediaElement::noneSupported"); 1766 1767 stopPeriodicTimers(); 1768 m_loadState = WaitingForSource; 1769 m_currentSourceNode = 0; 1770 1771 // 4.8.10.5 1772 // 6 - Reaching this step indicates that the media resource failed to load or that the given 1773 // URL could not be resolved. In one atomic operation, run the following steps: 1774 1775 // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to 1776 // MEDIA_ERR_SRC_NOT_SUPPORTED. 1777 m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED); 1778 1779 // 6.2 - Forget the media element's media-resource-specific text tracks. 1780 forgetResourceSpecificTracks(); 1781 1782 // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value. 1783 m_networkState = NETWORK_NO_SOURCE; 1784 1785 // 7 - Queue a task to fire a simple event named error at the media element. 1786 scheduleEvent(eventNames().errorEvent); 1787 1788#if ENABLE(MEDIA_SOURCE) 1789 closeMediaSource(); 1790#endif 1791 1792 // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. 1793 setShouldDelayLoadEvent(false); 1794 1795 // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed, 1796 // the element won't attempt to load another resource. 1797 1798 updateDisplayState(); 1799 1800 if (renderer()) 1801 renderer()->updateFromElement(); 1802} 1803 1804void HTMLMediaElement::mediaLoadingFailedFatally(MediaPlayer::NetworkState error) 1805{ 1806 LOG(Media, "HTMLMediaElement::mediaLoadingFailedFatally(%d)", static_cast<int>(error)); 1807 1808 // 1 - The user agent should cancel the fetching process. 1809 stopPeriodicTimers(); 1810 m_loadState = WaitingForSource; 1811 1812 // 2 - Set the error attribute to a new MediaError object whose code attribute is 1813 // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE. 1814 if (error == MediaPlayer::NetworkError) 1815 m_error = MediaError::create(MediaError::MEDIA_ERR_NETWORK); 1816 else if (error == MediaPlayer::DecodeError) 1817 m_error = MediaError::create(MediaError::MEDIA_ERR_DECODE); 1818 else 1819 ASSERT_NOT_REACHED(); 1820 1821 // 3 - Queue a task to fire a simple event named error at the media element. 1822 scheduleEvent(eventNames().errorEvent); 1823 1824#if ENABLE(MEDIA_SOURCE) 1825 closeMediaSource(); 1826#endif 1827 1828 // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a 1829 // task to fire a simple event called emptied at the element. 1830 m_networkState = NETWORK_EMPTY; 1831 scheduleEvent(eventNames().emptiedEvent); 1832 1833 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. 1834 setShouldDelayLoadEvent(false); 1835 1836 // 6 - Abort the overall resource selection algorithm. 1837 m_currentSourceNode = 0; 1838 1839#if PLATFORM(COCOA) 1840 if (document().isMediaDocument()) 1841 toMediaDocument(document()).mediaElementSawUnsupportedTracks(); 1842#endif 1843} 1844 1845void HTMLMediaElement::cancelPendingEventsAndCallbacks() 1846{ 1847 LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks"); 1848 m_asyncEventQueue.cancelAllEvents(); 1849 1850 for (auto& source : childrenOfType<HTMLSourceElement>(*this)) 1851 source.cancelPendingErrorEvent(); 1852} 1853 1854Document* HTMLMediaElement::mediaPlayerOwningDocument() 1855{ 1856 return &document(); 1857} 1858 1859void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*) 1860{ 1861 beginProcessingMediaPlayerCallback(); 1862 setNetworkState(m_player->networkState()); 1863 endProcessingMediaPlayerCallback(); 1864} 1865 1866static void logMediaLoadRequest(Page* page, const String& mediaEngine, const String& errorMessage, bool succeeded) 1867{ 1868 if (!page || !page->settings().diagnosticLoggingEnabled()) 1869 return; 1870 1871 ChromeClient& chromeClient = page->chrome().client(); 1872 1873 if (!succeeded) { 1874 chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadingFailedKey(), errorMessage, DiagnosticLoggingKeys::failKey()); 1875 return; 1876 } 1877 1878 chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadedKey(), mediaEngine, DiagnosticLoggingKeys::noopKey()); 1879 1880 if (!page->hasSeenAnyMediaEngine()) 1881 chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOneMediaEngineKey(), emptyString(), DiagnosticLoggingKeys::noopKey()); 1882 1883 if (!page->hasSeenMediaEngine(mediaEngine)) 1884 chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsMediaEngineKey(), mediaEngine, DiagnosticLoggingKeys::noopKey()); 1885 1886 page->sawMediaEngine(mediaEngine); 1887} 1888 1889static String stringForNetworkState(MediaPlayer::NetworkState state) 1890{ 1891 switch (state) { 1892 case MediaPlayer::Empty: return ASCIILiteral("Empty"); 1893 case MediaPlayer::Idle: return ASCIILiteral("Idle"); 1894 case MediaPlayer::Loading: return ASCIILiteral("Loading"); 1895 case MediaPlayer::Loaded: return ASCIILiteral("Loaded"); 1896 case MediaPlayer::FormatError: return ASCIILiteral("FormatError"); 1897 case MediaPlayer::NetworkError: return ASCIILiteral("NetworkError"); 1898 case MediaPlayer::DecodeError: return ASCIILiteral("DecodeError"); 1899 default: return emptyString(); 1900 } 1901} 1902 1903void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error) 1904{ 1905 stopPeriodicTimers(); 1906 1907 // If we failed while trying to load a <source> element, the movie was never parsed, and there are more 1908 // <source> children, schedule the next one 1909 if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) { 1910 1911 // resource selection algorithm 1912 // Step 9.Otherwise.9 - Failed with elements: Queue a task, using the DOM manipulation task source, to fire a simple event named error at the candidate element. 1913 if (m_currentSourceNode) 1914 m_currentSourceNode->scheduleErrorEvent(); 1915 else 1916 LOG(Media, "HTMLMediaElement::setNetworkState - error event not sent, <source> was removed"); 1917 1918 // 9.Otherwise.10 - Asynchronously await a stable state. The synchronous section consists of all the remaining steps of this algorithm until the algorithm says the synchronous section has ended. 1919 1920 // 9.Otherwise.11 - Forget the media element's media-resource-specific tracks. 1921 forgetResourceSpecificTracks(); 1922 1923 if (havePotentialSourceChild()) { 1924 LOG(Media, "HTMLMediaElement::setNetworkState - scheduling next <source>"); 1925 scheduleNextSourceChild(); 1926 } else { 1927 LOG(Media, "HTMLMediaElement::setNetworkState - no more <source> elements, waiting"); 1928 waitForSourceChange(); 1929 } 1930 1931 return; 1932 } 1933 1934 if ((error == MediaPlayer::NetworkError && m_readyState >= HAVE_METADATA) || error == MediaPlayer::DecodeError) 1935 mediaLoadingFailedFatally(error); 1936 else if ((error == MediaPlayer::FormatError || error == MediaPlayer::NetworkError) && m_loadState == LoadingFromSrcAttr) 1937 noneSupported(); 1938 1939 updateDisplayState(); 1940 if (hasMediaControls()) { 1941 mediaControls()->reset(); 1942 mediaControls()->reportedError(); 1943 } 1944 1945 logMediaLoadRequest(document().page(), String(), stringForNetworkState(error), false); 1946} 1947 1948void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state) 1949{ 1950 LOG(Media, "HTMLMediaElement::setNetworkState(%d) - current state is %d", static_cast<int>(state), static_cast<int>(m_networkState)); 1951 1952 if (state == MediaPlayer::Empty) { 1953 // Just update the cached state and leave, we can't do anything. 1954 m_networkState = NETWORK_EMPTY; 1955 return; 1956 } 1957 1958 if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) { 1959 mediaLoadingFailed(state); 1960 return; 1961 } 1962 1963 if (state == MediaPlayer::Idle) { 1964 if (m_networkState > NETWORK_IDLE) { 1965 changeNetworkStateFromLoadingToIdle(); 1966 setShouldDelayLoadEvent(false); 1967 } else { 1968 m_networkState = NETWORK_IDLE; 1969 } 1970 } 1971 1972 if (state == MediaPlayer::Loading) { 1973 if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE) 1974 startProgressEventTimer(); 1975 m_networkState = NETWORK_LOADING; 1976 } 1977 1978 if (state == MediaPlayer::Loaded) { 1979 if (m_networkState != NETWORK_IDLE) 1980 changeNetworkStateFromLoadingToIdle(); 1981 m_completelyLoaded = true; 1982 } 1983 1984 if (hasMediaControls()) 1985 mediaControls()->updateStatusDisplay(); 1986} 1987 1988void HTMLMediaElement::changeNetworkStateFromLoadingToIdle() 1989{ 1990 m_progressEventTimer.stop(); 1991 if (hasMediaControls() && m_player->didLoadingProgress()) 1992 mediaControls()->bufferingProgressed(); 1993 1994 // Schedule one last progress event so we guarantee that at least one is fired 1995 // for files that load very quickly. 1996 scheduleEvent(eventNames().progressEvent); 1997 scheduleEvent(eventNames().suspendEvent); 1998 m_networkState = NETWORK_IDLE; 1999} 2000 2001void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*) 2002{ 2003 beginProcessingMediaPlayerCallback(); 2004 2005 setReadyState(m_player->readyState()); 2006 2007 endProcessingMediaPlayerCallback(); 2008} 2009 2010void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) 2011{ 2012 LOG(Media, "HTMLMediaElement::setReadyState(%d) - current state is %d,", static_cast<int>(state), static_cast<int>(m_readyState)); 2013 2014 // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it 2015 bool wasPotentiallyPlaying = potentiallyPlaying(); 2016 2017 ReadyState oldState = m_readyState; 2018 ReadyState newState = static_cast<ReadyState>(state); 2019 2020#if ENABLE(VIDEO_TRACK) 2021 bool tracksAreReady = !RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() || textTracksAreReady(); 2022 2023 if (newState == oldState && m_tracksAreReady == tracksAreReady) 2024 return; 2025 2026 m_tracksAreReady = tracksAreReady; 2027#else 2028 if (newState == oldState) 2029 return; 2030 bool tracksAreReady = true; 2031#endif 2032 2033 if (tracksAreReady) 2034 m_readyState = newState; 2035 else { 2036 // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until 2037 // the text tracks are ready, regardless of the state of the media file. 2038 if (newState <= HAVE_METADATA) 2039 m_readyState = newState; 2040 else 2041 m_readyState = HAVE_CURRENT_DATA; 2042 } 2043 2044 if (oldState > m_readyStateMaximum) 2045 m_readyStateMaximum = oldState; 2046 2047 if (m_networkState == NETWORK_EMPTY) 2048 return; 2049 2050 if (m_seeking) { 2051 // 4.8.10.9, step 11 2052 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) 2053 scheduleEvent(eventNames().waitingEvent); 2054 2055 // 4.8.10.10 step 14 & 15. 2056 if (m_readyState >= HAVE_CURRENT_DATA) 2057 finishSeek(); 2058 } else { 2059 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) { 2060 // 4.8.10.8 2061 invalidateCachedTime(); 2062 scheduleTimeupdateEvent(false); 2063 scheduleEvent(eventNames().waitingEvent); 2064 } 2065 } 2066 2067 if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) { 2068 prepareMediaFragmentURI(); 2069 scheduleEvent(eventNames().durationchangeEvent); 2070 scheduleEvent(eventNames().loadedmetadataEvent); 2071 if (hasMediaControls()) 2072 mediaControls()->loadedMetadata(); 2073 if (renderer()) 2074 renderer()->updateFromElement(); 2075 2076 logMediaLoadRequest(document().page(), m_player->engineDescription(), String(), true); 2077 } 2078 2079 bool shouldUpdateDisplayState = false; 2080 2081 if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) { 2082 m_haveFiredLoadedData = true; 2083 shouldUpdateDisplayState = true; 2084 scheduleEvent(eventNames().loadeddataEvent); 2085 setShouldDelayLoadEvent(false); 2086 applyMediaFragmentURI(); 2087 } 2088 2089 bool isPotentiallyPlaying = potentiallyPlaying(); 2090 if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) { 2091 scheduleEvent(eventNames().canplayEvent); 2092 if (isPotentiallyPlaying) 2093 scheduleEvent(eventNames().playingEvent); 2094 shouldUpdateDisplayState = true; 2095 } 2096 2097 if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA && tracksAreReady) { 2098 if (oldState <= HAVE_CURRENT_DATA) 2099 scheduleEvent(eventNames().canplayEvent); 2100 2101 scheduleEvent(eventNames().canplaythroughEvent); 2102 2103 if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA) 2104 scheduleEvent(eventNames().playingEvent); 2105 2106 if (m_autoplaying && m_paused && autoplay() && !document().isSandboxed(SandboxAutomaticFeatures) && m_mediaSession->playbackPermitted(*this)) { 2107 m_paused = false; 2108 invalidateCachedTime(); 2109 scheduleEvent(eventNames().playEvent); 2110 scheduleEvent(eventNames().playingEvent); 2111 } 2112 2113 shouldUpdateDisplayState = true; 2114 } 2115 2116 if (shouldUpdateDisplayState) { 2117 updateDisplayState(); 2118 if (hasMediaControls()) { 2119 mediaControls()->refreshClosedCaptionsButtonVisibility(); 2120 mediaControls()->updateStatusDisplay(); 2121 } 2122 } 2123 2124 updatePlayState(); 2125 updateMediaController(); 2126#if ENABLE(VIDEO_TRACK) 2127 if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 2128 updateActiveTextTrackCues(currentTime()); 2129#endif 2130} 2131 2132#if ENABLE(ENCRYPTED_MEDIA) 2133void HTMLMediaElement::mediaPlayerKeyAdded(MediaPlayer*, const String& keySystem, const String& sessionId) 2134{ 2135 MediaKeyEventInit initializer; 2136 initializer.keySystem = keySystem; 2137 initializer.sessionId = sessionId; 2138 initializer.bubbles = false; 2139 initializer.cancelable = false; 2140 2141 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeyaddedEvent, initializer); 2142 event->setTarget(this); 2143 m_asyncEventQueue.enqueueEvent(event.release()); 2144} 2145 2146void HTMLMediaElement::mediaPlayerKeyError(MediaPlayer*, const String& keySystem, const String& sessionId, MediaPlayerClient::MediaKeyErrorCode errorCode, unsigned short systemCode) 2147{ 2148 MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN; 2149 switch (errorCode) { 2150 case MediaPlayerClient::UnknownError: 2151 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN; 2152 break; 2153 case MediaPlayerClient::ClientError: 2154 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT; 2155 break; 2156 case MediaPlayerClient::ServiceError: 2157 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_SERVICE; 2158 break; 2159 case MediaPlayerClient::OutputError: 2160 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_OUTPUT; 2161 break; 2162 case MediaPlayerClient::HardwareChangeError: 2163 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_HARDWARECHANGE; 2164 break; 2165 case MediaPlayerClient::DomainError: 2166 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_DOMAIN; 2167 break; 2168 } 2169 2170 MediaKeyEventInit initializer; 2171 initializer.keySystem = keySystem; 2172 initializer.sessionId = sessionId; 2173 initializer.errorCode = MediaKeyError::create(mediaKeyErrorCode); 2174 initializer.systemCode = systemCode; 2175 initializer.bubbles = false; 2176 initializer.cancelable = false; 2177 2178 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeyerrorEvent, initializer); 2179 event->setTarget(this); 2180 m_asyncEventQueue.enqueueEvent(event.release()); 2181} 2182 2183void HTMLMediaElement::mediaPlayerKeyMessage(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* message, unsigned messageLength, const URL& defaultURL) 2184{ 2185 MediaKeyEventInit initializer; 2186 initializer.keySystem = keySystem; 2187 initializer.sessionId = sessionId; 2188 initializer.message = Uint8Array::create(message, messageLength); 2189 initializer.defaultURL = defaultURL; 2190 initializer.bubbles = false; 2191 initializer.cancelable = false; 2192 2193 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeymessageEvent, initializer); 2194 event->setTarget(this); 2195 m_asyncEventQueue.enqueueEvent(event.release()); 2196} 2197 2198bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* initData, unsigned initDataLength) 2199{ 2200 if (!hasEventListeners(eventNames().webkitneedkeyEvent)) { 2201 m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED); 2202 scheduleEvent(eventNames().errorEvent); 2203 return false; 2204 } 2205 2206 MediaKeyEventInit initializer; 2207 initializer.keySystem = keySystem; 2208 initializer.sessionId = sessionId; 2209 initializer.initData = Uint8Array::create(initData, initDataLength); 2210 initializer.bubbles = false; 2211 initializer.cancelable = false; 2212 2213 RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitneedkeyEvent, initializer); 2214 event->setTarget(this); 2215 m_asyncEventQueue.enqueueEvent(event.release()); 2216 return true; 2217} 2218#endif 2219 2220#if ENABLE(ENCRYPTED_MEDIA_V2) 2221bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, Uint8Array* initData) 2222{ 2223 if (!hasEventListeners("webkitneedkey")) { 2224 m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED); 2225 scheduleEvent(eventNames().errorEvent); 2226 return false; 2227 } 2228 2229 MediaKeyNeededEventInit initializer; 2230 initializer.initData = initData; 2231 initializer.bubbles = false; 2232 initializer.cancelable = false; 2233 2234 RefPtr<Event> event = MediaKeyNeededEvent::create(eventNames().webkitneedkeyEvent, initializer); 2235 event->setTarget(this); 2236 m_asyncEventQueue.enqueueEvent(event.release()); 2237 2238 return true; 2239} 2240 2241void HTMLMediaElement::setMediaKeys(MediaKeys* mediaKeys) 2242{ 2243 if (m_mediaKeys == mediaKeys) 2244 return; 2245 2246 if (m_mediaKeys) 2247 m_mediaKeys->setMediaElement(0); 2248 m_mediaKeys = mediaKeys; 2249 if (m_mediaKeys) 2250 m_mediaKeys->setMediaElement(this); 2251} 2252#endif 2253 2254void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>&) 2255{ 2256 ASSERT(m_player); 2257 if (m_networkState != NETWORK_LOADING) 2258 return; 2259 2260 double time = monotonicallyIncreasingTime(); 2261 double timedelta = time - m_previousProgressTime; 2262 2263 if (m_player->didLoadingProgress()) { 2264 scheduleEvent(eventNames().progressEvent); 2265 m_previousProgressTime = time; 2266 m_sentStalledEvent = false; 2267 if (renderer()) 2268 renderer()->updateFromElement(); 2269 if (hasMediaControls()) 2270 mediaControls()->bufferingProgressed(); 2271 } else if (timedelta > 3.0 && !m_sentStalledEvent) { 2272 scheduleEvent(eventNames().stalledEvent); 2273 m_sentStalledEvent = true; 2274 setShouldDelayLoadEvent(false); 2275 } 2276} 2277 2278void HTMLMediaElement::rewind(double timeDelta) 2279{ 2280 LOG(Media, "HTMLMediaElement::rewind(%f)", timeDelta); 2281 setCurrentTime(std::max(currentTime() - timeDelta, minTimeSeekable())); 2282} 2283 2284void HTMLMediaElement::returnToRealtime() 2285{ 2286 LOG(Media, "HTMLMediaElement::returnToRealtime"); 2287 setCurrentTime(maxTimeSeekable()); 2288} 2289 2290void HTMLMediaElement::addPlayedRange(double start, double end) 2291{ 2292 LOG(Media, "HTMLMediaElement::addPlayedRange(%f, %f)", start, end); 2293 if (!m_playedTimeRanges) 2294 m_playedTimeRanges = TimeRanges::create(); 2295 m_playedTimeRanges->add(start, end); 2296} 2297 2298bool HTMLMediaElement::supportsSave() const 2299{ 2300 return m_player ? m_player->supportsSave() : false; 2301} 2302 2303bool HTMLMediaElement::supportsScanning() const 2304{ 2305 return m_player ? m_player->supportsScanning() : false; 2306} 2307 2308void HTMLMediaElement::prepareToPlay() 2309{ 2310 LOG(Media, "HTMLMediaElement::prepareToPlay(%p)", this); 2311 if (m_havePreparedToPlay) 2312 return; 2313 m_havePreparedToPlay = true; 2314 m_player->prepareToPlay(); 2315} 2316 2317void HTMLMediaElement::fastSeek(double time) 2318{ 2319 LOG(Media, "HTMLMediaElement::fastSeek(%f)", time); 2320 // 4.7.10.9 Seeking 2321 // 9. If the approximate-for-speed flag is set, adjust the new playback position to a value that will 2322 // allow for playback to resume promptly. If new playback position before this step is before current 2323 // playback position, then the adjusted new playback position must also be before the current playback 2324 // position. Similarly, if the new playback position before this step is after current playback position, 2325 // then the adjusted new playback position must also be after the current playback position. 2326 refreshCachedTime(); 2327 double delta = time - currentTime(); 2328 double negativeTolerance = delta >= 0 ? delta : std::numeric_limits<double>::infinity(); 2329 double positiveTolerance = delta < 0 ? -delta : std::numeric_limits<double>::infinity(); 2330 2331 seekWithTolerance(time, negativeTolerance, positiveTolerance, true); 2332} 2333 2334void HTMLMediaElement::seek(double time) 2335{ 2336 LOG(Media, "HTMLMediaElement::seek(%f)", time); 2337 seekWithTolerance(time, 0, 0, true); 2338} 2339 2340void HTMLMediaElement::seekInternal(double time) 2341{ 2342 LOG(Media, "HTMLMediaElement::seekInternal(%f)", time); 2343 seekWithTolerance(time, 0, 0, false); 2344} 2345 2346void HTMLMediaElement::seekWithTolerance(double time, double negativeTolerance, double positiveTolerance, bool fromDOM) 2347{ 2348 // 4.8.10.9 Seeking 2349 2350 // 1 - Set the media element's show poster flag to false. 2351 setDisplayMode(Video); 2352 2353 // 2 - If the media element's readyState is HAVE_NOTHING, abort these steps. 2354 if (m_readyState == HAVE_NOTHING || !m_player) 2355 return; 2356 2357 // If the media engine has been told to postpone loading data, let it go ahead now. 2358 if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA) 2359 prepareToPlay(); 2360 2361 // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set. 2362 refreshCachedTime(); 2363 double now = currentTime(); 2364 2365 // 3 - If the element's seeking IDL attribute is true, then another instance of this algorithm is 2366 // already running. Abort that other instance of the algorithm without waiting for the step that 2367 // it is running to complete. 2368 if (m_seeking) { 2369 m_seekTimer.stop(); 2370 m_pendingSeek = nullptr; 2371 } 2372 2373 // 4 - Set the seeking IDL attribute to true. 2374 // The flag will be cleared when the engine tells us the time has actually changed. 2375 m_seeking = true; 2376 if (m_playing) { 2377 if (m_lastSeekTime < now) 2378 addPlayedRange(m_lastSeekTime, now); 2379 } 2380 m_lastSeekTime = time; 2381 2382 // 5 - If the seek was in response to a DOM method call or setting of an IDL attribute, then continue 2383 // the script. The remainder of these steps must be run asynchronously. 2384 m_pendingSeek = std::make_unique<PendingSeek>(now, time, negativeTolerance, positiveTolerance); 2385 if (fromDOM) 2386 m_seekTimer.startOneShot(0); 2387 else 2388 seekTimerFired(m_seekTimer); 2389} 2390 2391void HTMLMediaElement::seekTimerFired(Timer<HTMLMediaElement>&) 2392{ 2393 if (!m_player) { 2394 m_seeking = false; 2395 return; 2396 } 2397 2398 ASSERT(m_pendingSeek); 2399 double now = m_pendingSeek->now; 2400 double time = m_pendingSeek->targetTime; 2401 double negativeTolerance = m_pendingSeek->negativeTolerance; 2402 double positiveTolerance = m_pendingSeek->positiveTolerance; 2403 m_pendingSeek = nullptr; 2404 2405 // 6 - If the new playback position is later than the end of the media resource, then let it be the end 2406 // of the media resource instead. 2407 time = std::min(time, duration()); 2408 2409 // 7 - If the new playback position is less than the earliest possible position, let it be that position instead. 2410 double earliestTime = m_player->startTime(); 2411 time = std::max(time, earliestTime); 2412 2413 // Ask the media engine for the time value in the movie's time scale before comparing with current time. This 2414 // is necessary because if the seek time is not equal to currentTime but the delta is less than the movie's 2415 // time scale, we will ask the media engine to "seek" to the current movie time, which may be a noop and 2416 // not generate a timechanged callback. This means m_seeking will never be cleared and we will never 2417 // fire a 'seeked' event. 2418#if !LOG_DISABLED 2419 double mediaTime = m_player->mediaTimeForTimeValue(time); 2420 if (time != mediaTime) 2421 LOG(Media, "HTMLMediaElement::seekTimerFired(%f) - media timeline equivalent is %f", time, mediaTime); 2422#endif 2423 time = m_player->mediaTimeForTimeValue(time); 2424 2425 // 8 - If the (possibly now changed) new playback position is not in one of the ranges given in the 2426 // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute 2427 // that is the nearest to the new playback position. ... If there are no ranges given in the seekable 2428 // attribute then set the seeking IDL attribute to false and abort these steps. 2429 RefPtr<TimeRanges> seekableRanges = seekable(); 2430 2431 // Short circuit seeking to the current time by just firing the events if no seek is required. 2432 // Don't skip calling the media engine if we are in poster mode because a seek should always 2433 // cancel poster display. 2434 bool noSeekRequired = !seekableRanges->length() || (time == now && displayMode() != Poster); 2435 2436#if ENABLE(MEDIA_SOURCE) 2437 // Always notify the media engine of a seek if the source is not closed. This ensures that the source is 2438 // always in a flushed state when the 'seeking' event fires. 2439 if (m_mediaSource && m_mediaSource->isClosed()) 2440 noSeekRequired = false; 2441#endif 2442 2443 if (noSeekRequired) { 2444 if (time == now) { 2445 scheduleEvent(eventNames().seekingEvent); 2446 scheduleTimeupdateEvent(false); 2447 scheduleEvent(eventNames().seekedEvent); 2448 } 2449 m_seeking = false; 2450 return; 2451 } 2452 time = seekableRanges->nearest(time); 2453 2454 m_sentEndEvent = false; 2455 m_lastSeekTime = time; 2456 2457 // 10 - Queue a task to fire a simple event named seeking at the element. 2458 scheduleEvent(eventNames().seekingEvent); 2459 2460 // 11 - Set the current playback position to the given new playback position 2461 m_player->seekWithTolerance(time, negativeTolerance, positiveTolerance); 2462 2463 // 12 - Wait until the user agent has established whether or not the media data for the new playback 2464 // position is available, and, if it is, until it has decoded enough data to play back that position. 2465 // 13 - Await a stable state. The synchronous section consists of all the remaining steps of this algorithm. 2466} 2467 2468void HTMLMediaElement::finishSeek() 2469{ 2470 LOG(Media, "HTMLMediaElement::finishSeek"); 2471 2472 // 4.8.10.9 Seeking 2473 // 14 - Set the seeking IDL attribute to false. 2474 m_seeking = false; 2475 2476 // 15 - Run the time maches on steps. 2477 // Handled by mediaPlayerTimeChanged(). 2478 2479 // 16 - Queue a task to fire a simple event named timeupdate at the element. 2480 scheduleEvent(eventNames().timeupdateEvent); 2481 2482 // 17 - Queue a task to fire a simple event named seeked at the element. 2483 scheduleEvent(eventNames().seekedEvent); 2484 2485#if ENABLE(MEDIA_SOURCE) 2486 if (m_mediaSource) 2487 m_mediaSource->monitorSourceBuffers(); 2488#endif 2489} 2490 2491HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const 2492{ 2493 return m_readyState; 2494} 2495 2496MediaPlayer::MovieLoadType HTMLMediaElement::movieLoadType() const 2497{ 2498 return m_player ? m_player->movieLoadType() : MediaPlayer::Unknown; 2499} 2500 2501bool HTMLMediaElement::hasAudio() const 2502{ 2503 return m_player ? m_player->hasAudio() : false; 2504} 2505 2506bool HTMLMediaElement::seeking() const 2507{ 2508 return m_seeking; 2509} 2510 2511void HTMLMediaElement::refreshCachedTime() const 2512{ 2513 m_cachedTime = m_player->currentTime(); 2514 if (!m_cachedTime) { 2515 // Do not use m_cachedTime until the media engine returns a non-zero value because we can't 2516 // estimate current time until playback actually begins. 2517 invalidateCachedTime(); 2518 return; 2519 } 2520 2521 m_clockTimeAtLastCachedTimeUpdate = monotonicallyIncreasingTime(); 2522} 2523 2524void HTMLMediaElement::invalidateCachedTime() const 2525{ 2526#if !LOG_DISABLED 2527 if (m_cachedTime != MediaPlayer::invalidTime()) 2528 LOG(Media, "HTMLMediaElement::invalidateCachedTime"); 2529#endif 2530 2531 // Don't try to cache movie time when playback first starts as the time reported by the engine 2532 // sometimes fluctuates for a short amount of time, so the cached time will be off if we take it 2533 // too early. 2534 static const double minimumTimePlayingBeforeCacheSnapshot = 0.5; 2535 2536 m_minimumClockTimeToUpdateCachedTime = monotonicallyIncreasingTime() + minimumTimePlayingBeforeCacheSnapshot; 2537 m_cachedTime = MediaPlayer::invalidTime(); 2538} 2539 2540// playback state 2541double HTMLMediaElement::currentTime() const 2542{ 2543#if LOG_CACHED_TIME_WARNINGS 2544 static const double minCachedDeltaForWarning = 0.01; 2545#endif 2546 2547 if (!m_player) 2548 return 0; 2549 2550 if (m_seeking) { 2551 LOG(Media, "HTMLMediaElement::currentTime - seeking, returning %f", m_lastSeekTime); 2552 return m_lastSeekTime; 2553 } 2554 2555 if (m_cachedTime != MediaPlayer::invalidTime() && m_paused) { 2556#if LOG_CACHED_TIME_WARNINGS 2557 double delta = m_cachedTime - m_player->currentTime(); 2558 if (delta > minCachedDeltaForWarning) 2559 LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when paused", delta); 2560#endif 2561 return m_cachedTime; 2562 } 2563 2564 // Is it too soon use a cached time? 2565 double now = monotonicallyIncreasingTime(); 2566 double maximumDurationToCacheMediaTime = m_player->maximumDurationToCacheMediaTime(); 2567 2568 if (maximumDurationToCacheMediaTime && m_cachedTime != MediaPlayer::invalidTime() && !m_paused && now > m_minimumClockTimeToUpdateCachedTime) { 2569 double clockDelta = now - m_clockTimeAtLastCachedTimeUpdate; 2570 2571 // Not too soon, use the cached time only if it hasn't expired. 2572 if (clockDelta < maximumDurationToCacheMediaTime) { 2573 double adjustedCacheTime = m_cachedTime + (effectivePlaybackRate() * clockDelta); 2574 2575#if LOG_CACHED_TIME_WARNINGS 2576 double delta = adjustedCacheTime - m_player->currentTime(); 2577 if (delta > minCachedDeltaForWarning) 2578 LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when playing", delta); 2579#endif 2580 return adjustedCacheTime; 2581 } 2582 } 2583 2584#if LOG_CACHED_TIME_WARNINGS 2585 if (maximumDurationToCacheMediaTime && now > m_minimumClockTimeToUpdateCachedTime && m_cachedTime != MediaPlayer::invalidTime()) { 2586 double clockDelta = now - m_clockTimeAtLastCachedTimeUpdate; 2587 double delta = m_cachedTime + (effectivePlaybackRate() * clockDelta) - m_player->currentTime(); 2588 LOG(Media, "HTMLMediaElement::currentTime - cached time was %f seconds off of media time when it expired", delta); 2589 } 2590#endif 2591 2592 refreshCachedTime(); 2593 2594 if (m_cachedTime == MediaPlayer::invalidTime()) 2595 return 0; 2596 2597 return m_cachedTime; 2598} 2599 2600void HTMLMediaElement::setCurrentTime(double time) 2601{ 2602 if (m_mediaController) 2603 return; 2604 2605 seekInternal(time); 2606} 2607 2608void HTMLMediaElement::setCurrentTime(double time, ExceptionCode& ec) 2609{ 2610 // On setting, if the media element has a current media controller, then the user agent must 2611 // throw an InvalidStateError exception 2612 if (m_mediaController) { 2613 ec = INVALID_STATE_ERR; 2614 return; 2615 } 2616 2617 seek(time); 2618} 2619 2620double HTMLMediaElement::duration() const 2621{ 2622 if (m_player && m_readyState >= HAVE_METADATA) 2623 return m_player->duration(); 2624 2625 return std::numeric_limits<double>::quiet_NaN(); 2626} 2627 2628bool HTMLMediaElement::paused() const 2629{ 2630 // As of this writing, JavaScript garbage collection calls this function directly. In the past 2631 // we had problems where this was called on an object after a bad cast. The assertion below 2632 // made our regression test detect the problem, so we should keep it because of that. But note 2633 // that the value of the assertion relies on the compiler not being smart enough to know that 2634 // isHTMLUnknownElement is guaranteed to return false for an HTMLMediaElement. 2635 ASSERT(!isHTMLUnknownElement()); 2636 2637 return m_paused; 2638} 2639 2640double HTMLMediaElement::defaultPlaybackRate() const 2641{ 2642 return m_defaultPlaybackRate; 2643} 2644 2645void HTMLMediaElement::setDefaultPlaybackRate(double rate) 2646{ 2647 if (m_defaultPlaybackRate != rate) { 2648 m_defaultPlaybackRate = rate; 2649 scheduleEvent(eventNames().ratechangeEvent); 2650 } 2651} 2652 2653double HTMLMediaElement::effectivePlaybackRate() const 2654{ 2655 return m_mediaController ? m_mediaController->playbackRate() : m_playbackRate; 2656} 2657 2658double HTMLMediaElement::playbackRate() const 2659{ 2660 return m_playbackRate; 2661} 2662 2663void HTMLMediaElement::setPlaybackRate(double rate) 2664{ 2665 LOG(Media, "HTMLMediaElement::setPlaybackRate(%f)", rate); 2666 2667 if (m_player && potentiallyPlaying() && m_player->rate() != rate && !m_mediaController) 2668 m_player->setRate(rate); 2669 2670 if (m_playbackRate != rate) { 2671 m_playbackRate = rate; 2672 invalidateCachedTime(); 2673 scheduleEvent(eventNames().ratechangeEvent); 2674 } 2675} 2676 2677void HTMLMediaElement::updatePlaybackRate() 2678{ 2679 double effectiveRate = effectivePlaybackRate(); 2680 if (m_player && potentiallyPlaying() && m_player->rate() != effectiveRate) 2681 m_player->setRate(effectiveRate); 2682} 2683 2684bool HTMLMediaElement::webkitPreservesPitch() const 2685{ 2686 return m_webkitPreservesPitch; 2687} 2688 2689void HTMLMediaElement::setWebkitPreservesPitch(bool preservesPitch) 2690{ 2691 LOG(Media, "HTMLMediaElement::setWebkitPreservesPitch(%s)", boolString(preservesPitch)); 2692 2693 m_webkitPreservesPitch = preservesPitch; 2694 2695 if (!m_player) 2696 return; 2697 2698 m_player->setPreservesPitch(preservesPitch); 2699} 2700 2701bool HTMLMediaElement::ended() const 2702{ 2703 // 4.8.10.8 Playing the media resource 2704 // The ended attribute must return true if the media element has ended 2705 // playback and the direction of playback is forwards, and false otherwise. 2706 return endedPlayback() && effectivePlaybackRate() > 0; 2707} 2708 2709bool HTMLMediaElement::autoplay() const 2710{ 2711#if PLATFORM(IOS) 2712 // Unless the restriction on requiring user actions has been lifted, we do not 2713 // allow playback to start just because the page has "autoplay". They are either 2714 // lifted through Settings, or once the user explictly calls load() or play() 2715 // because they have OK'ed us loading data. This allows playback to continue if 2716 // the URL is changed while the movie is playing. 2717 if (!m_mediaSession->playbackPermitted(*this) || !m_mediaSession->dataLoadingPermitted(*this)) 2718 return false; 2719#endif 2720 2721 return fastHasAttribute(autoplayAttr); 2722} 2723 2724String HTMLMediaElement::preload() const 2725{ 2726 switch (m_preload) { 2727 case MediaPlayer::None: 2728 return ASCIILiteral("none"); 2729 case MediaPlayer::MetaData: 2730 return ASCIILiteral("metadata"); 2731 case MediaPlayer::Auto: 2732 return ASCIILiteral("auto"); 2733 } 2734 2735 ASSERT_NOT_REACHED(); 2736 return String(); 2737} 2738 2739void HTMLMediaElement::setPreload(const String& preload) 2740{ 2741 LOG(Media, "HTMLMediaElement::setPreload(%s)", preload.utf8().data()); 2742 setAttribute(preloadAttr, preload); 2743} 2744 2745void HTMLMediaElement::play() 2746{ 2747 LOG(Media, "HTMLMediaElement::play()"); 2748 2749 if (!m_mediaSession->playbackPermitted(*this)) 2750 return; 2751 if (ScriptController::processingUserGesture()) 2752 removeBehaviorsRestrictionsAfterFirstUserGesture(); 2753 2754 playInternal(); 2755} 2756 2757void HTMLMediaElement::playInternal() 2758{ 2759 LOG(Media, "HTMLMediaElement::playInternal"); 2760 2761 if (!m_mediaSession->clientWillBeginPlayback()) { 2762 LOG(Media, " returning because of interruption"); 2763 return; 2764 } 2765 2766 // 4.8.10.9. Playing the media resource 2767 if (!m_player || m_networkState == NETWORK_EMPTY) 2768 scheduleDelayedAction(LoadMediaResource); 2769 2770 if (endedPlayback()) 2771 seekInternal(0); 2772 2773 if (m_mediaController) 2774 m_mediaController->bringElementUpToSpeed(this); 2775 2776 if (m_paused) { 2777 m_paused = false; 2778 invalidateCachedTime(); 2779 scheduleEvent(eventNames().playEvent); 2780 2781 if (m_readyState <= HAVE_CURRENT_DATA) 2782 scheduleEvent(eventNames().waitingEvent); 2783 else if (m_readyState >= HAVE_FUTURE_DATA) 2784 scheduleEvent(eventNames().playingEvent); 2785 } 2786 m_autoplaying = false; 2787#if PLATFORM(IOS) 2788 m_requestingPlay = true; 2789#endif 2790 updatePlayState(); 2791 updateMediaController(); 2792} 2793 2794void HTMLMediaElement::pause() 2795{ 2796 LOG(Media, "HTMLMediaElement::pause()"); 2797 2798 if (!m_mediaSession->playbackPermitted(*this)) 2799 return; 2800 2801 pauseInternal(); 2802} 2803 2804 2805void HTMLMediaElement::pauseInternal() 2806{ 2807 LOG(Media, "HTMLMediaElement::pauseInternal"); 2808 2809 if (!m_mediaSession->clientWillPausePlayback()) { 2810 LOG(Media, " returning because of interruption"); 2811 return; 2812 } 2813 2814 // 4.8.10.9. Playing the media resource 2815 if (!m_player || m_networkState == NETWORK_EMPTY) { 2816#if PLATFORM(IOS) 2817 // Unless the restriction on media requiring user action has been lifted 2818 // don't trigger loading if a script calls pause(). 2819 if (!m_mediaSession->playbackPermitted(*this)) 2820 return; 2821#endif 2822 scheduleDelayedAction(LoadMediaResource); 2823 } 2824 2825 m_autoplaying = false; 2826 2827 if (!m_paused) { 2828 m_paused = true; 2829 scheduleTimeupdateEvent(false); 2830 scheduleEvent(eventNames().pauseEvent); 2831 } 2832 2833 updatePlayState(); 2834} 2835 2836#if ENABLE(MEDIA_SOURCE) 2837void HTMLMediaElement::closeMediaSource() 2838{ 2839 if (!m_mediaSource) 2840 return; 2841 2842 m_mediaSource->close(); 2843 m_mediaSource = 0; 2844} 2845#endif 2846 2847#if ENABLE(ENCRYPTED_MEDIA) 2848void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, PassRefPtr<Uint8Array> initData, ExceptionCode& ec) 2849{ 2850#if ENABLE(ENCRYPTED_MEDIA_V2) 2851 static bool firstTime = true; 2852 if (firstTime && scriptExecutionContext()) { 2853 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, ASCIILiteral("'HTMLMediaElement.webkitGenerateKeyRequest()' is deprecated. Use 'MediaKeys.createSession()' instead.")); 2854 firstTime = false; 2855 } 2856#endif 2857 2858 if (keySystem.isEmpty()) { 2859 ec = SYNTAX_ERR; 2860 return; 2861 } 2862 2863 if (!m_player) { 2864 ec = INVALID_STATE_ERR; 2865 return; 2866 } 2867 2868 const unsigned char* initDataPointer = 0; 2869 unsigned initDataLength = 0; 2870 if (initData) { 2871 initDataPointer = initData->data(); 2872 initDataLength = initData->length(); 2873 } 2874 2875 MediaPlayer::MediaKeyException result = m_player->generateKeyRequest(keySystem, initDataPointer, initDataLength); 2876 ec = exceptionCodeForMediaKeyException(result); 2877} 2878 2879void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, ExceptionCode& ec) 2880{ 2881 webkitGenerateKeyRequest(keySystem, Uint8Array::create(0), ec); 2882} 2883 2884void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, PassRefPtr<Uint8Array> initData, const String& sessionId, ExceptionCode& ec) 2885{ 2886#if ENABLE(ENCRYPTED_MEDIA_V2) 2887 static bool firstTime = true; 2888 if (firstTime && scriptExecutionContext()) { 2889 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, ASCIILiteral("'HTMLMediaElement.webkitAddKey()' is deprecated. Use 'MediaKeySession.update()' instead.")); 2890 firstTime = false; 2891 } 2892#endif 2893 2894 if (keySystem.isEmpty()) { 2895 ec = SYNTAX_ERR; 2896 return; 2897 } 2898 2899 if (!key) { 2900 ec = SYNTAX_ERR; 2901 return; 2902 } 2903 2904 if (!key->length()) { 2905 ec = TYPE_MISMATCH_ERR; 2906 return; 2907 } 2908 2909 if (!m_player) { 2910 ec = INVALID_STATE_ERR; 2911 return; 2912 } 2913 2914 const unsigned char* initDataPointer = 0; 2915 unsigned initDataLength = 0; 2916 if (initData) { 2917 initDataPointer = initData->data(); 2918 initDataLength = initData->length(); 2919 } 2920 2921 MediaPlayer::MediaKeyException result = m_player->addKey(keySystem, key->data(), key->length(), initDataPointer, initDataLength, sessionId); 2922 ec = exceptionCodeForMediaKeyException(result); 2923} 2924 2925void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, ExceptionCode& ec) 2926{ 2927 webkitAddKey(keySystem, key, Uint8Array::create(0), String(), ec); 2928} 2929 2930void HTMLMediaElement::webkitCancelKeyRequest(const String& keySystem, const String& sessionId, ExceptionCode& ec) 2931{ 2932 if (keySystem.isEmpty()) { 2933 ec = SYNTAX_ERR; 2934 return; 2935 } 2936 2937 if (!m_player) { 2938 ec = INVALID_STATE_ERR; 2939 return; 2940 } 2941 2942 MediaPlayer::MediaKeyException result = m_player->cancelKeyRequest(keySystem, sessionId); 2943 ec = exceptionCodeForMediaKeyException(result); 2944} 2945 2946#endif 2947 2948bool HTMLMediaElement::loop() const 2949{ 2950 return fastHasAttribute(loopAttr); 2951} 2952 2953void HTMLMediaElement::setLoop(bool b) 2954{ 2955 LOG(Media, "HTMLMediaElement::setLoop(%s)", boolString(b)); 2956 setBooleanAttribute(loopAttr, b); 2957} 2958 2959bool HTMLMediaElement::controls() const 2960{ 2961 Frame* frame = document().frame(); 2962 2963 // always show controls when scripting is disabled 2964 if (frame && !frame->script().canExecuteScripts(NotAboutToExecuteScript)) 2965 return true; 2966 2967 // always show controls for video when fullscreen playback is required. 2968 if (isVideo() && m_mediaSession->requiresFullscreenForVideoPlayback(*this)) 2969 return true; 2970 2971 // Always show controls when in full screen mode. 2972 if (isFullscreen()) 2973 return true; 2974 2975 return fastHasAttribute(controlsAttr); 2976} 2977 2978void HTMLMediaElement::setControls(bool b) 2979{ 2980 LOG(Media, "HTMLMediaElement::setControls(%s)", boolString(b)); 2981 setBooleanAttribute(controlsAttr, b); 2982} 2983 2984double HTMLMediaElement::volume() const 2985{ 2986 return m_volume; 2987} 2988 2989void HTMLMediaElement::setVolume(double vol, ExceptionCode& ec) 2990{ 2991 LOG(Media, "HTMLMediaElement::setVolume(%f)", vol); 2992 2993 if (vol < 0.0f || vol > 1.0f) { 2994 ec = INDEX_SIZE_ERR; 2995 return; 2996 } 2997 2998#if !PLATFORM(IOS) 2999 if (m_volume != vol) { 3000 m_volume = vol; 3001 m_volumeInitialized = true; 3002 updateVolume(); 3003 scheduleEvent(eventNames().volumechangeEvent); 3004 } 3005#endif 3006} 3007 3008bool HTMLMediaElement::muted() const 3009{ 3010 return m_explicitlyMuted ? m_muted : fastHasAttribute(mutedAttr); 3011} 3012 3013void HTMLMediaElement::setMuted(bool muted) 3014{ 3015 LOG(Media, "HTMLMediaElement::setMuted(%s)", boolString(muted)); 3016 3017#if PLATFORM(IOS) 3018 UNUSED_PARAM(muted); 3019#else 3020 if (m_muted != muted || !m_explicitlyMuted) { 3021 m_muted = muted; 3022 m_explicitlyMuted = true; 3023 // Avoid recursion when the player reports volume changes. 3024 if (!processingMediaPlayerCallback()) { 3025 if (m_player) { 3026 m_player->setMuted(m_muted); 3027 if (hasMediaControls()) 3028 mediaControls()->changedMute(); 3029 } 3030 } 3031 scheduleEvent(eventNames().volumechangeEvent); 3032 } 3033#endif 3034} 3035 3036void HTMLMediaElement::togglePlayState() 3037{ 3038 LOG(Media, "HTMLMediaElement::togglePlayState - canPlay() is %s", boolString(canPlay())); 3039 3040 // We can safely call the internal play/pause methods, which don't check restrictions, because 3041 // this method is only called from the built-in media controller 3042 if (canPlay()) { 3043 updatePlaybackRate(); 3044 playInternal(); 3045 } else 3046 pauseInternal(); 3047} 3048 3049void HTMLMediaElement::beginScrubbing() 3050{ 3051 LOG(Media, "HTMLMediaElement::beginScrubbing - paused() is %s", boolString(paused())); 3052 3053 if (!paused()) { 3054 if (ended()) { 3055 // Because a media element stays in non-paused state when it reaches end, playback resumes 3056 // when the slider is dragged from the end to another position unless we pause first. Do 3057 // a "hard pause" so an event is generated, since we want to stay paused after scrubbing finishes. 3058 pause(); 3059 } else { 3060 // Not at the end but we still want to pause playback so the media engine doesn't try to 3061 // continue playing during scrubbing. Pause without generating an event as we will 3062 // unpause after scrubbing finishes. 3063 setPausedInternal(true); 3064 } 3065 } 3066} 3067 3068void HTMLMediaElement::endScrubbing() 3069{ 3070 LOG(Media, "HTMLMediaElement::endScrubbing - m_pausedInternal is %s", boolString(m_pausedInternal)); 3071 3072 if (m_pausedInternal) 3073 setPausedInternal(false); 3074} 3075 3076void HTMLMediaElement::beginScanning(ScanDirection direction) 3077{ 3078 m_scanType = supportsScanning() ? Scan : Seek; 3079 m_scanDirection = direction; 3080 3081 if (m_scanType == Seek) { 3082 // Scanning by seeking requires the video to be paused during scanning. 3083 m_actionAfterScan = paused() ? Nothing : Play; 3084 pause(); 3085 } else { 3086 // Scanning by scanning requires the video to be playing during scanninging. 3087 m_actionAfterScan = paused() ? Pause : Nothing; 3088 play(); 3089 setPlaybackRate(nextScanRate()); 3090 } 3091 3092 m_scanTimer.start(0, m_scanType == Seek ? SeekRepeatDelay : ScanRepeatDelay); 3093} 3094 3095void HTMLMediaElement::endScanning() 3096{ 3097 if (m_scanType == Scan) 3098 setPlaybackRate(defaultPlaybackRate()); 3099 3100 if (m_actionAfterScan == Play) 3101 play(); 3102 else if (m_actionAfterScan == Pause) 3103 pause(); 3104 3105 if (m_scanTimer.isActive()) 3106 m_scanTimer.stop(); 3107} 3108 3109double HTMLMediaElement::nextScanRate() 3110{ 3111 double rate = std::min(ScanMaximumRate, fabs(playbackRate() * 2)); 3112 if (m_scanDirection == Backward) 3113 rate *= -1; 3114#if PLATFORM(IOS) 3115 rate = std::min(std::max(rate, minFastReverseRate()), maxFastForwardRate()); 3116#endif 3117 return rate; 3118} 3119 3120void HTMLMediaElement::scanTimerFired(Timer<HTMLMediaElement>&) 3121{ 3122 if (m_scanType == Seek) { 3123 double seekTime = m_scanDirection == Forward ? SeekTime : -SeekTime; 3124 setCurrentTime(currentTime() + seekTime); 3125 } else 3126 setPlaybackRate(nextScanRate()); 3127} 3128 3129// The spec says to fire periodic timeupdate events (those sent while playing) every 3130// "15 to 250ms", we choose the slowest frequency 3131static const double maxTimeupdateEventFrequency = 0.25; 3132 3133void HTMLMediaElement::startPlaybackProgressTimer() 3134{ 3135 if (m_playbackProgressTimer.isActive()) 3136 return; 3137 3138 m_previousProgressTime = monotonicallyIncreasingTime(); 3139 m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency); 3140} 3141 3142void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>&) 3143{ 3144 ASSERT(m_player); 3145 3146 if (m_fragmentEndTime != MediaPlayer::invalidTime() && currentTime() >= m_fragmentEndTime && effectivePlaybackRate() > 0) { 3147 m_fragmentEndTime = MediaPlayer::invalidTime(); 3148 if (!m_mediaController && !m_paused) { 3149 // changes paused to true and fires a simple event named pause at the media element. 3150 pauseInternal(); 3151 } 3152 } 3153 3154 scheduleTimeupdateEvent(true); 3155 3156 if (!effectivePlaybackRate()) 3157 return; 3158 3159 if (!m_paused && hasMediaControls()) 3160 mediaControls()->playbackProgressed(); 3161 3162#if ENABLE(VIDEO_TRACK) 3163 if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3164 updateActiveTextTrackCues(currentTime()); 3165#endif 3166 3167#if ENABLE(MEDIA_SOURCE) 3168 if (m_mediaSource) 3169 m_mediaSource->monitorSourceBuffers(); 3170#endif 3171} 3172 3173void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent) 3174{ 3175 double now = monotonicallyIncreasingTime(); 3176 double timedelta = now - m_clockTimeAtLastUpdateEvent; 3177 3178 // throttle the periodic events 3179 if (periodicEvent && timedelta < maxTimeupdateEventFrequency) 3180 return; 3181 3182 // Some media engines make multiple "time changed" callbacks at the same time, but we only want one 3183 // event at a given time so filter here 3184 double movieTime = currentTime(); 3185 if (movieTime != m_lastTimeUpdateEventMovieTime) { 3186 scheduleEvent(eventNames().timeupdateEvent); 3187 m_clockTimeAtLastUpdateEvent = now; 3188 m_lastTimeUpdateEventMovieTime = movieTime; 3189 } 3190} 3191 3192bool HTMLMediaElement::canPlay() const 3193{ 3194 return paused() || ended() || m_readyState < HAVE_METADATA; 3195} 3196 3197double HTMLMediaElement::percentLoaded() const 3198{ 3199 if (!m_player) 3200 return 0; 3201 double duration = m_player->duration(); 3202 3203 if (!duration || std::isinf(duration)) 3204 return 0; 3205 3206 MediaTime buffered = MediaTime::zeroTime(); 3207 bool ignored; 3208 std::unique_ptr<PlatformTimeRanges> timeRanges = m_player->buffered(); 3209 for (unsigned i = 0; i < timeRanges->length(); ++i) { 3210 MediaTime start = timeRanges->start(i, ignored); 3211 MediaTime end = timeRanges->end(i, ignored); 3212 buffered += end - start; 3213 } 3214 return buffered.toDouble() / duration; 3215} 3216 3217#if ENABLE(VIDEO_TRACK) 3218 3219void HTMLMediaElement::mediaPlayerDidAddAudioTrack(PassRefPtr<AudioTrackPrivate> prpTrack) 3220{ 3221 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3222 return; 3223 3224 addAudioTrack(AudioTrack::create(this, prpTrack)); 3225} 3226 3227void HTMLMediaElement::mediaPlayerDidAddTextTrack(PassRefPtr<InbandTextTrackPrivate> prpTrack) 3228{ 3229 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3230 return; 3231 3232 // 4.8.10.12.2 Sourcing in-band text tracks 3233 // 1. Associate the relevant data with a new text track and its corresponding new TextTrack object. 3234 RefPtr<InbandTextTrack> textTrack = InbandTextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, prpTrack); 3235 textTrack->setMediaElement(this); 3236 3237 // 2. Set the new text track's kind, label, and language based on the semantics of the relevant data, 3238 // as defined by the relevant specification. If there is no label in that data, then the label must 3239 // be set to the empty string. 3240 // 3. Associate the text track list of cues with the rules for updating the text track rendering appropriate 3241 // for the format in question. 3242 // 4. If the new text track's kind is metadata, then set the text track in-band metadata track dispatch type 3243 // as follows, based on the type of the media resource: 3244 // 5. Populate the new text track's list of cues with the cues parsed so far, folllowing the guidelines for exposing 3245 // cues, and begin updating it dynamically as necessary. 3246 // - Thess are all done by the media engine. 3247 3248 // 6. Set the new text track's readiness state to loaded. 3249 textTrack->setReadinessState(TextTrack::Loaded); 3250 3251 // 7. Set the new text track's mode to the mode consistent with the user's preferences and the requirements of 3252 // the relevant specification for the data. 3253 // - This will happen in configureTextTracks() 3254 scheduleDelayedAction(ConfigureTextTracks); 3255 3256 // 8. Add the new text track to the media element's list of text tracks. 3257 // 9. Fire an event with the name addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent 3258 // interface, with the track attribute initialized to the text track's TextTrack object, at the media element's 3259 // textTracks attribute's TextTrackList object. 3260 addTextTrack(textTrack.release()); 3261} 3262 3263void HTMLMediaElement::mediaPlayerDidAddVideoTrack(PassRefPtr<VideoTrackPrivate> prpTrack) 3264{ 3265 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3266 return; 3267 3268 addVideoTrack(VideoTrack::create(this, prpTrack)); 3269} 3270 3271void HTMLMediaElement::mediaPlayerDidRemoveAudioTrack(PassRefPtr<AudioTrackPrivate> prpTrack) 3272{ 3273 prpTrack->willBeRemoved(); 3274} 3275 3276void HTMLMediaElement::mediaPlayerDidRemoveTextTrack(PassRefPtr<InbandTextTrackPrivate> prpTrack) 3277{ 3278 prpTrack->willBeRemoved(); 3279} 3280 3281void HTMLMediaElement::mediaPlayerDidRemoveVideoTrack(PassRefPtr<VideoTrackPrivate> prpTrack) 3282{ 3283 prpTrack->willBeRemoved(); 3284} 3285 3286#if USE(PLATFORM_TEXT_TRACK_MENU) 3287void HTMLMediaElement::setSelectedTextTrack(PassRefPtr<PlatformTextTrack> platformTrack) 3288{ 3289 if (!m_textTracks) 3290 return; 3291 3292 TrackDisplayUpdateScope scope(this); 3293 3294 if (!platformTrack) { 3295 setSelectedTextTrack(TextTrack::captionMenuOffItem()); 3296 return; 3297 } 3298 3299 TextTrack* textTrack; 3300 if (platformTrack == PlatformTextTrack::captionMenuOffItem()) 3301 textTrack = TextTrack::captionMenuOffItem(); 3302 else if (platformTrack == PlatformTextTrack::captionMenuAutomaticItem()) 3303 textTrack = TextTrack::captionMenuAutomaticItem(); 3304 else { 3305 size_t i; 3306 for (i = 0; i < m_textTracks->length(); ++i) { 3307 textTrack = m_textTracks->item(i); 3308 3309 if (textTrack->platformTextTrack() == platformTrack) 3310 break; 3311 } 3312 if (i == m_textTracks->length()) 3313 return; 3314 } 3315 3316 setSelectedTextTrack(textTrack); 3317} 3318 3319Vector<RefPtr<PlatformTextTrack>> HTMLMediaElement::platformTextTracks() 3320{ 3321 if (!m_textTracks || !m_textTracks->length()) 3322 return Vector<RefPtr<PlatformTextTrack>>(); 3323 3324 Vector<RefPtr<PlatformTextTrack>> platformTracks; 3325 for (size_t i = 0; i < m_textTracks->length(); ++i) 3326 platformTracks.append(m_textTracks->item(i)->platformTextTrack()); 3327 3328 return platformTracks; 3329} 3330 3331void HTMLMediaElement::notifyMediaPlayerOfTextTrackChanges() 3332{ 3333 if (!m_textTracks || !m_textTracks->length() || !platformTextTrackMenu()) 3334 return; 3335 3336 m_platformMenu->tracksDidChange(); 3337} 3338 3339PlatformTextTrackMenuInterface* HTMLMediaElement::platformTextTrackMenu() 3340{ 3341 if (m_platformMenu) 3342 return m_platformMenu.get(); 3343 3344 if (!m_player || !m_player->implementsTextTrackControls()) 3345 return 0; 3346 3347 m_platformMenu = m_player->textTrackMenu(); 3348 if (!m_platformMenu) 3349 return 0; 3350 3351 m_platformMenu->setClient(this); 3352 3353 return m_platformMenu.get(); 3354} 3355#endif // #if USE(PLATFORM_TEXT_TRACK_MENU) 3356 3357void HTMLMediaElement::closeCaptionTracksChanged() 3358{ 3359 if (hasMediaControls()) 3360 mediaControls()->closedCaptionTracksChanged(); 3361 3362#if USE(PLATFORM_TEXT_TRACK_MENU) 3363 if (m_player && m_player->implementsTextTrackControls()) 3364 scheduleDelayedAction(TextTrackChangesNotification); 3365#endif 3366} 3367 3368void HTMLMediaElement::addAudioTrack(PassRefPtr<AudioTrack> track) 3369{ 3370 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3371 return; 3372 3373 audioTracks()->append(track); 3374} 3375 3376void HTMLMediaElement::addTextTrack(PassRefPtr<TextTrack> track) 3377{ 3378 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3379 return; 3380 3381 textTracks()->append(track); 3382 3383 closeCaptionTracksChanged(); 3384} 3385 3386void HTMLMediaElement::addVideoTrack(PassRefPtr<VideoTrack> track) 3387{ 3388 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3389 return; 3390 3391 videoTracks()->append(track); 3392} 3393 3394void HTMLMediaElement::removeAudioTrack(AudioTrack* track) 3395{ 3396 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3397 return; 3398 3399 m_audioTracks->remove(track); 3400} 3401 3402void HTMLMediaElement::removeTextTrack(TextTrack* track, bool scheduleEvent) 3403{ 3404 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3405 return; 3406 3407 TrackDisplayUpdateScope scope(this); 3408 TextTrackCueList* cues = track->cues(); 3409 if (cues) 3410 textTrackRemoveCues(track, cues); 3411 track->clearClient(); 3412 m_textTracks->remove(track, scheduleEvent); 3413 3414 closeCaptionTracksChanged(); 3415} 3416 3417void HTMLMediaElement::removeVideoTrack(VideoTrack* track) 3418{ 3419 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3420 return; 3421 3422 m_videoTracks->remove(track); 3423} 3424 3425void HTMLMediaElement::forgetResourceSpecificTracks() 3426{ 3427 while (m_audioTracks && m_audioTracks->length()) 3428 removeAudioTrack(m_audioTracks->lastItem()); 3429 3430 if (m_textTracks) { 3431 TrackDisplayUpdateScope scope(this); 3432 for (int i = m_textTracks->length() - 1; i >= 0; --i) { 3433 TextTrack* track = m_textTracks->item(i); 3434 3435 if (track->trackType() == TextTrack::InBand) 3436 removeTextTrack(track, false); 3437 } 3438 } 3439 3440 while (m_videoTracks && m_videoTracks->length()) 3441 removeVideoTrack(m_videoTracks->lastItem()); 3442} 3443 3444PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const String& kind, const String& label, const String& language, ExceptionCode& ec) 3445{ 3446 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3447 return 0; 3448 3449 // 4.8.10.12.4 Text track API 3450 // The addTextTrack(kind, label, language) method of media elements, when invoked, must run the following steps: 3451 3452 // 1. If kind is not one of the following strings, then throw a SyntaxError exception and abort these steps 3453 if (!TextTrack::isValidKindKeyword(kind)) { 3454 ec = SYNTAX_ERR; 3455 return 0; 3456 } 3457 3458 // 2. If the label argument was omitted, let label be the empty string. 3459 // 3. If the language argument was omitted, let language be the empty string. 3460 // 4. Create a new TextTrack object. 3461 3462 // 5. Create a new text track corresponding to the new object, and set its text track kind to kind, its text 3463 // track label to label, its text track language to language... 3464 RefPtr<TextTrack> textTrack = TextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, kind, emptyString(), label, language); 3465 3466 // Note, due to side effects when changing track parameters, we have to 3467 // first append the track to the text track list. 3468 3469 // 6. Add the new text track to the media element's list of text tracks. 3470 addTextTrack(textTrack); 3471 3472 // ... its text track readiness state to the text track loaded state ... 3473 textTrack->setReadinessState(TextTrack::Loaded); 3474 3475 // ... its text track mode to the text track hidden mode, and its text track list of cues to an empty list ... 3476 textTrack->setMode(TextTrack::hiddenKeyword()); 3477 3478 return textTrack.release(); 3479} 3480 3481AudioTrackList* HTMLMediaElement::audioTracks() 3482{ 3483 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3484 return 0; 3485 3486 if (!m_audioTracks) 3487 m_audioTracks = AudioTrackList::create(this, ActiveDOMObject::scriptExecutionContext()); 3488 3489 return m_audioTracks.get(); 3490} 3491 3492TextTrackList* HTMLMediaElement::textTracks() 3493{ 3494 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3495 return 0; 3496 3497 if (!m_textTracks) 3498 m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext()); 3499 3500 return m_textTracks.get(); 3501} 3502 3503VideoTrackList* HTMLMediaElement::videoTracks() 3504{ 3505 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3506 return 0; 3507 3508 if (!m_videoTracks) 3509 m_videoTracks = VideoTrackList::create(this, ActiveDOMObject::scriptExecutionContext()); 3510 3511 return m_videoTracks.get(); 3512} 3513 3514void HTMLMediaElement::didAddTextTrack(HTMLTrackElement* trackElement) 3515{ 3516 ASSERT(trackElement->hasTagName(trackTag)); 3517 3518 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3519 return; 3520 3521 // 4.8.10.12.3 Sourcing out-of-band text tracks 3522 // When a track element's parent element changes and the new parent is a media element, 3523 // then the user agent must add the track element's corresponding text track to the 3524 // media element's list of text tracks ... [continues in TextTrackList::append] 3525 RefPtr<TextTrack> textTrack = trackElement->track(); 3526 if (!textTrack) 3527 return; 3528 3529 addTextTrack(textTrack.release()); 3530 3531 // Do not schedule the track loading until parsing finishes so we don't start before all tracks 3532 // in the markup have been added. 3533 if (!m_parsingInProgress) 3534 scheduleDelayedAction(ConfigureTextTracks); 3535 3536 if (hasMediaControls()) 3537 mediaControls()->closedCaptionTracksChanged(); 3538} 3539 3540void HTMLMediaElement::didRemoveTextTrack(HTMLTrackElement* trackElement) 3541{ 3542 ASSERT(trackElement->hasTagName(trackTag)); 3543 3544 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 3545 return; 3546 3547#if !LOG_DISABLED 3548 if (trackElement->hasTagName(trackTag)) { 3549 URL url = trackElement->getNonEmptyURLAttribute(srcAttr); 3550 LOG(Media, "HTMLMediaElement::didRemoveTrack - 'src' is %s", urlForLoggingMedia(url).utf8().data()); 3551 } 3552#endif 3553 3554 RefPtr<TextTrack> textTrack = trackElement->track(); 3555 if (!textTrack) 3556 return; 3557 3558 textTrack->setHasBeenConfigured(false); 3559 3560 if (!m_textTracks) 3561 return; 3562 3563 // 4.8.10.12.3 Sourcing out-of-band text tracks 3564 // When a track element's parent element changes and the old parent was a media element, 3565 // then the user agent must remove the track element's corresponding text track from the 3566 // media element's list of text tracks. 3567 removeTextTrack(textTrack.get()); 3568 3569 size_t index = m_textTracksWhenResourceSelectionBegan.find(textTrack.get()); 3570 if (index != notFound) 3571 m_textTracksWhenResourceSelectionBegan.remove(index); 3572} 3573 3574void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group) 3575{ 3576 ASSERT(group.tracks.size()); 3577 3578 LOG(Media, "HTMLMediaElement::configureTextTrackGroup"); 3579 3580 Page* page = document().page(); 3581 CaptionUserPreferences* captionPreferences = page? page->group().captionPreferences() : 0; 3582 CaptionUserPreferences::CaptionDisplayMode displayMode = captionPreferences ? captionPreferences->captionDisplayMode() : CaptionUserPreferences::Automatic; 3583 3584 // First, find the track in the group that should be enabled (if any). 3585 Vector<RefPtr<TextTrack>> currentlyEnabledTracks; 3586 RefPtr<TextTrack> trackToEnable; 3587 RefPtr<TextTrack> defaultTrack; 3588 RefPtr<TextTrack> fallbackTrack; 3589 RefPtr<TextTrack> forcedSubitleTrack; 3590 int highestTrackScore = 0; 3591 int highestForcedScore = 0; 3592 3593 // If there is a visible track, it has already been configured so it won't be considered in the loop below. We don't want to choose another 3594 // track if it is less suitable, and we do want to disable it if another track is more suitable. 3595 int alreadyVisibleTrackScore = 0; 3596 if (group.visibleTrack && captionPreferences) { 3597 alreadyVisibleTrackScore = captionPreferences->textTrackSelectionScore(group.visibleTrack.get(), this); 3598 currentlyEnabledTracks.append(group.visibleTrack); 3599 } 3600 3601 for (size_t i = 0; i < group.tracks.size(); ++i) { 3602 RefPtr<TextTrack> textTrack = group.tracks[i]; 3603 3604 if (m_processingPreferenceChange && textTrack->mode() == TextTrack::showingKeyword()) 3605 currentlyEnabledTracks.append(textTrack); 3606 3607 int trackScore = captionPreferences ? captionPreferences->textTrackSelectionScore(textTrack.get(), this) : 0; 3608 LOG(Media, "HTMLMediaElement::configureTextTrackGroup - '%s' track with language '%s' has score %i", textTrack->kind().string().utf8().data(), textTrack->language().string().utf8().data(), trackScore); 3609 3610 if (trackScore) { 3611 3612 // * If the text track kind is { [subtitles or captions] [descriptions] } and the user has indicated an interest in having a 3613 // track with this text track kind, text track language, and text track label enabled, and there is no 3614 // other text track in the media element's list of text tracks with a text track kind of either subtitles 3615 // or captions whose text track mode is showing 3616 // ... 3617 // * If the text track kind is chapters and the text track language is one that the user agent has reason 3618 // to believe is appropriate for the user, and there is no other text track in the media element's list of 3619 // text tracks with a text track kind of chapters whose text track mode is showing 3620 // Let the text track mode be showing. 3621 if (trackScore > highestTrackScore && trackScore > alreadyVisibleTrackScore) { 3622 highestTrackScore = trackScore; 3623 trackToEnable = textTrack; 3624 } 3625 3626 if (!defaultTrack && textTrack->isDefault()) 3627 defaultTrack = textTrack; 3628 if (!defaultTrack && !fallbackTrack) 3629 fallbackTrack = textTrack; 3630 if (textTrack->containsOnlyForcedSubtitles() && trackScore > highestForcedScore) { 3631 forcedSubitleTrack = textTrack; 3632 highestForcedScore = trackScore; 3633 } 3634 } else if (!group.visibleTrack && !defaultTrack && textTrack->isDefault()) { 3635 // * If the track element has a default attribute specified, and there is no other text track in the media 3636 // element's list of text tracks whose text track mode is showing or showing by default 3637 // Let the text track mode be showing by default. 3638 if (group.kind != TrackGroup::CaptionsAndSubtitles || displayMode != CaptionUserPreferences::ForcedOnly) 3639 defaultTrack = textTrack; 3640 } 3641 } 3642 3643 if (!trackToEnable && defaultTrack) 3644 trackToEnable = defaultTrack; 3645 3646 // If no track matches the user's preferred language, none was marked as 'default', and there is a forced subtitle track 3647 // in the same language as the language of the primary audio track, enable it. 3648 if (!trackToEnable && forcedSubitleTrack) 3649 trackToEnable = forcedSubitleTrack; 3650 3651 // If no track matches, don't disable an already visible track unless preferences say they all should be off. 3652 if (group.kind != TrackGroup::CaptionsAndSubtitles || displayMode != CaptionUserPreferences::ForcedOnly) { 3653 if (!trackToEnable && !defaultTrack && group.visibleTrack) 3654 trackToEnable = group.visibleTrack; 3655 } 3656 3657 // If no track matches the user's preferred language and non was marked 'default', enable the first track 3658 // because the user has explicitly stated a preference for this kind of track. 3659 if (!trackToEnable && fallbackTrack) 3660 trackToEnable = fallbackTrack; 3661 3662 if (trackToEnable) 3663 m_subtitleTrackLanguage = trackToEnable->language(); 3664 else 3665 m_subtitleTrackLanguage = emptyString(); 3666 3667 if (currentlyEnabledTracks.size()) { 3668 for (size_t i = 0; i < currentlyEnabledTracks.size(); ++i) { 3669 RefPtr<TextTrack> textTrack = currentlyEnabledTracks[i]; 3670 if (textTrack != trackToEnable) 3671 textTrack->setMode(TextTrack::disabledKeyword()); 3672 } 3673 } 3674 3675 if (trackToEnable) { 3676 trackToEnable->setMode(TextTrack::showingKeyword()); 3677 3678 // If user preferences indicate we should always display captions, make sure we reflect the 3679 // proper status via the webkitClosedCaptionsVisible API call: 3680 if (!webkitClosedCaptionsVisible() && closedCaptionsVisible() && displayMode == CaptionUserPreferences::AlwaysOn) 3681 m_webkitLegacyClosedCaptionOverride = true; 3682 } 3683 3684 updateCaptionContainer(); 3685 3686 m_processingPreferenceChange = false; 3687} 3688 3689static JSC::JSValue controllerJSValue(JSC::ExecState& exec, JSDOMGlobalObject& globalObject, HTMLMediaElement& media) 3690{ 3691 auto mediaJSWrapper = toJS(&exec, &globalObject, &media); 3692 3693 // Retrieve the controller through the JS object graph 3694 JSC::JSObject* mediaJSWrapperObject = JSC::jsDynamicCast<JSC::JSObject*>(mediaJSWrapper); 3695 if (!mediaJSWrapperObject) 3696 return JSC::jsNull(); 3697 3698 JSC::Identifier controlsHost(&exec.vm(), "controlsHost"); 3699 JSC::JSValue controlsHostJSWrapper = mediaJSWrapperObject->get(&exec, controlsHost); 3700 if (exec.hadException()) 3701 return JSC::jsNull(); 3702 3703 JSC::JSObject* controlsHostJSWrapperObject = JSC::jsDynamicCast<JSC::JSObject*>(controlsHostJSWrapper); 3704 if (!controlsHostJSWrapperObject) 3705 return JSC::jsNull(); 3706 3707 JSC::Identifier controllerID(&exec.vm(), "controller"); 3708 JSC::JSValue controllerJSWrapper = controlsHostJSWrapperObject->get(&exec, controllerID); 3709 if (exec.hadException()) 3710 return JSC::jsNull(); 3711 3712 return controllerJSWrapper; 3713} 3714 3715void HTMLMediaElement::updateCaptionContainer() 3716{ 3717 LOG(Media, "HTMLMediaElement::updateCaptionContainer"); 3718#if ENABLE(MEDIA_CONTROLS_SCRIPT) 3719 Page* page = document().page(); 3720 if (!page) 3721 return; 3722 3723 DOMWrapperWorld& world = ensureIsolatedWorld(); 3724 3725 if (!ensureMediaControlsInjectedScript()) 3726 return; 3727 3728 ensureUserAgentShadowRoot(); 3729 3730 if (!m_mediaControlsHost) 3731 m_mediaControlsHost = MediaControlsHost::create(this); 3732 3733 ScriptController& scriptController = page->mainFrame().script(); 3734 JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world)); 3735 JSC::ExecState* exec = globalObject->globalExec(); 3736 JSC::JSLockHolder lock(exec); 3737 3738 JSC::JSValue controllerValue = controllerJSValue(*exec, *globalObject, *this); 3739 JSC::JSObject* controllerObject = JSC::jsDynamicCast<JSC::JSObject*>(controllerValue); 3740 if (!controllerObject) 3741 return; 3742 3743 // The media controls script must provide a method on the Controller object with the following details. 3744 // Name: updateCaptionContainer 3745 // Parameters: 3746 // None 3747 // Return value: 3748 // None 3749 JSC::JSValue methodValue = controllerObject->get(exec, JSC::Identifier(exec, "updateCaptionContainer")); 3750 JSC::JSObject* methodObject = JSC::jsDynamicCast<JSC::JSObject*>(methodValue); 3751 if (!methodObject) 3752 return; 3753 3754 JSC::CallData callData; 3755 JSC::CallType callType = methodObject->methodTable()->getCallData(methodObject, callData); 3756 if (callType == JSC::CallTypeNone) 3757 return; 3758 3759 JSC::MarkedArgumentBuffer noArguments; 3760 JSC::call(exec, methodObject, callType, callData, controllerObject, noArguments); 3761 3762 if (exec->hadException()) 3763 exec->clearException(); 3764#endif 3765} 3766 3767void HTMLMediaElement::setSelectedTextTrack(TextTrack* trackToSelect) 3768{ 3769 TextTrackList* trackList = textTracks(); 3770 if (!trackList || !trackList->length()) 3771 return; 3772 3773 if (trackToSelect != TextTrack::captionMenuOffItem() && trackToSelect != TextTrack::captionMenuAutomaticItem()) { 3774 if (!trackList->contains(trackToSelect)) 3775 return; 3776 3777 for (int i = 0, length = trackList->length(); i < length; ++i) { 3778 TextTrack* track = trackList->item(i); 3779 if (!trackToSelect || track != trackToSelect) 3780 track->setMode(TextTrack::disabledKeyword()); 3781 else 3782 track->setMode(TextTrack::showingKeyword()); 3783 } 3784 } else if (trackToSelect == TextTrack::captionMenuOffItem()) { 3785 for (int i = 0, length = trackList->length(); i < length; ++i) 3786 trackList->item(i)->setMode(TextTrack::disabledKeyword()); 3787 } 3788 3789 CaptionUserPreferences* captionPreferences = document().page() ? document().page()->group().captionPreferences() : 0; 3790 if (!captionPreferences) 3791 return; 3792 3793 CaptionUserPreferences::CaptionDisplayMode displayMode = captionPreferences->captionDisplayMode(); 3794 if (trackToSelect == TextTrack::captionMenuOffItem()) 3795 displayMode = CaptionUserPreferences::ForcedOnly; 3796 else if (trackToSelect == TextTrack::captionMenuAutomaticItem()) 3797 displayMode = CaptionUserPreferences::Automatic; 3798 else { 3799 displayMode = CaptionUserPreferences::AlwaysOn; 3800 if (trackToSelect->language().length()) 3801 captionPreferences->setPreferredLanguage(trackToSelect->language()); 3802 } 3803 3804 captionPreferences->setCaptionDisplayMode(displayMode); 3805} 3806 3807void HTMLMediaElement::configureTextTracks() 3808{ 3809 TrackGroup captionAndSubtitleTracks(TrackGroup::CaptionsAndSubtitles); 3810 TrackGroup descriptionTracks(TrackGroup::Description); 3811 TrackGroup chapterTracks(TrackGroup::Chapter); 3812 TrackGroup metadataTracks(TrackGroup::Metadata); 3813 TrackGroup otherTracks(TrackGroup::Other); 3814 3815 if (!m_textTracks) 3816 return; 3817 3818 for (size_t i = 0; i < m_textTracks->length(); ++i) { 3819 RefPtr<TextTrack> textTrack = m_textTracks->item(i); 3820 if (!textTrack) 3821 continue; 3822 3823 String kind = textTrack->kind(); 3824 TrackGroup* currentGroup; 3825 if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword() || kind == TextTrack::forcedKeyword()) 3826 currentGroup = &captionAndSubtitleTracks; 3827 else if (kind == TextTrack::descriptionsKeyword()) 3828 currentGroup = &descriptionTracks; 3829 else if (kind == TextTrack::chaptersKeyword()) 3830 currentGroup = &chapterTracks; 3831 else if (kind == TextTrack::metadataKeyword()) 3832 currentGroup = &metadataTracks; 3833 else 3834 currentGroup = &otherTracks; 3835 3836 if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::showingKeyword()) 3837 currentGroup->visibleTrack = textTrack; 3838 if (!currentGroup->defaultTrack && textTrack->isDefault()) 3839 currentGroup->defaultTrack = textTrack; 3840 3841 // Do not add this track to the group if it has already been automatically configured 3842 // as we only want to call configureTextTrack once per track so that adding another 3843 // track after the initial configuration doesn't reconfigure every track - only those 3844 // that should be changed by the new addition. For example all metadata tracks are 3845 // disabled by default, and we don't want a track that has been enabled by script 3846 // to be disabled automatically when a new metadata track is added later. 3847 if (textTrack->hasBeenConfigured()) 3848 continue; 3849 3850 if (textTrack->language().length()) 3851 currentGroup->hasSrcLang = true; 3852 currentGroup->tracks.append(textTrack); 3853 } 3854 3855 if (captionAndSubtitleTracks.tracks.size()) 3856 configureTextTrackGroup(captionAndSubtitleTracks); 3857 if (descriptionTracks.tracks.size()) 3858 configureTextTrackGroup(descriptionTracks); 3859 if (chapterTracks.tracks.size()) 3860 configureTextTrackGroup(chapterTracks); 3861 if (metadataTracks.tracks.size()) 3862 configureTextTrackGroup(metadataTracks); 3863 if (otherTracks.tracks.size()) 3864 configureTextTrackGroup(otherTracks); 3865 3866 configureTextTrackDisplay(); 3867 if (hasMediaControls()) 3868 mediaControls()->closedCaptionTracksChanged(); 3869} 3870#endif 3871 3872bool HTMLMediaElement::havePotentialSourceChild() 3873{ 3874 // Stash the current <source> node and next nodes so we can restore them after checking 3875 // to see there is another potential. 3876 RefPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode; 3877 RefPtr<Node> nextNode = m_nextChildNodeToConsider; 3878 3879 URL nextURL = selectNextSourceChild(0, 0, DoNothing); 3880 3881 m_currentSourceNode = currentSourceNode; 3882 m_nextChildNodeToConsider = nextNode; 3883 3884 return nextURL.isValid(); 3885} 3886 3887URL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid) 3888{ 3889#if !LOG_DISABLED 3890 // Don't log if this was just called to find out if there are any valid <source> elements. 3891 bool shouldLog = actionIfInvalid != DoNothing; 3892 if (shouldLog) 3893 LOG(Media, "HTMLMediaElement::selectNextSourceChild"); 3894#endif 3895 3896 if (!m_nextChildNodeToConsider) { 3897#if !LOG_DISABLED 3898 if (shouldLog) 3899 LOG(Media, "HTMLMediaElement::selectNextSourceChild -> 0x0000, \"\""); 3900#endif 3901 return URL(); 3902 } 3903 3904 URL mediaURL; 3905 HTMLSourceElement* source = 0; 3906 String type; 3907 String system; 3908 bool lookingForStartNode = m_nextChildNodeToConsider; 3909 bool canUseSourceElement = false; 3910 bool okToLoadSourceURL; 3911 3912 NodeVector potentialSourceNodes; 3913 getChildNodes(*this, potentialSourceNodes); 3914 3915 for (unsigned i = 0; !canUseSourceElement && i < potentialSourceNodes.size(); ++i) { 3916 Node& node = potentialSourceNodes[i].get(); 3917 if (lookingForStartNode && m_nextChildNodeToConsider != &node) 3918 continue; 3919 lookingForStartNode = false; 3920 3921 if (!node.hasTagName(sourceTag)) 3922 continue; 3923 if (node.parentNode() != this) 3924 continue; 3925 3926 source = toHTMLSourceElement(&node); 3927 3928 // 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 3929 mediaURL = source->getNonEmptyURLAttribute(srcAttr); 3930#if !LOG_DISABLED 3931 if (shouldLog) 3932 LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'src' is %s", urlForLoggingMedia(mediaURL).utf8().data()); 3933#endif 3934 if (mediaURL.isEmpty()) 3935 goto check_again; 3936 3937 if (source->fastHasAttribute(mediaAttr)) { 3938 MediaQueryEvaluator screenEval("screen", document().frame(), renderer() ? &renderer()->style() : nullptr); 3939 RefPtr<MediaQuerySet> media = MediaQuerySet::createAllowingDescriptionSyntax(source->media()); 3940#if !LOG_DISABLED 3941 if (shouldLog) 3942 LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'media' is %s", source->media().utf8().data()); 3943#endif 3944 if (!screenEval.eval(media.get())) 3945 goto check_again; 3946 } 3947 3948 type = source->type(); 3949 // FIXME(82965): Add support for keySystem in <source> and set system from source. 3950 if (type.isEmpty() && mediaURL.protocolIsData()) 3951 type = mimeTypeFromDataURL(mediaURL); 3952 if (!type.isEmpty() || !system.isEmpty()) { 3953#if !LOG_DISABLED 3954 if (shouldLog) 3955 LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'type' is '%s' - key system is '%s'", type.utf8().data(), system.utf8().data()); 3956#endif 3957 MediaEngineSupportParameters parameters; 3958 ContentType contentType(type); 3959 parameters.type = contentType.type().lower(); 3960 parameters.codecs = contentType.parameter(ASCIILiteral("codecs")); 3961 parameters.url = mediaURL; 3962#if ENABLE(ENCRYPTED_MEDIA) 3963 parameters.keySystem = system; 3964#endif 3965#if ENABLE(MEDIA_SOURCE) 3966 parameters.isMediaSource = mediaURL.protocolIs(mediaSourceBlobProtocol); 3967#endif 3968 if (!MediaPlayer::supportsType(parameters, this)) 3969 goto check_again; 3970 } 3971 3972 // Is it safe to load this url? 3973 okToLoadSourceURL = isSafeToLoadURL(mediaURL, actionIfInvalid) && dispatchBeforeLoadEvent(mediaURL.string()); 3974 3975 // A 'beforeload' event handler can mutate the DOM, so check to see if the source element is still a child node. 3976 if (node.parentNode() != this) { 3977 LOG(Media, "HTMLMediaElement::selectNextSourceChild : 'beforeload' removed current element"); 3978 source = 0; 3979 goto check_again; 3980 } 3981 3982 if (!okToLoadSourceURL) 3983 goto check_again; 3984 3985 // Making it this far means the <source> looks reasonable. 3986 canUseSourceElement = true; 3987 3988check_again: 3989 if (!canUseSourceElement && actionIfInvalid == Complain && source) 3990 source->scheduleErrorEvent(); 3991 } 3992 3993 if (canUseSourceElement) { 3994 if (contentType) 3995 *contentType = ContentType(type); 3996 if (keySystem) 3997 *keySystem = system; 3998 m_currentSourceNode = source; 3999 m_nextChildNodeToConsider = source->nextSibling(); 4000 } else { 4001 m_currentSourceNode = 0; 4002 m_nextChildNodeToConsider = 0; 4003 } 4004 4005#if !LOG_DISABLED 4006 if (shouldLog) 4007 LOG(Media, "HTMLMediaElement::selectNextSourceChild -> %p, %s", m_currentSourceNode.get(), canUseSourceElement ? urlForLoggingMedia(mediaURL).utf8().data() : ""); 4008#endif 4009 return canUseSourceElement ? mediaURL : URL(); 4010} 4011 4012void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source) 4013{ 4014 LOG(Media, "HTMLMediaElement::sourceWasAdded(%p)", source); 4015 4016#if !LOG_DISABLED 4017 if (source->hasTagName(sourceTag)) { 4018 URL url = source->getNonEmptyURLAttribute(srcAttr); 4019 LOG(Media, "HTMLMediaElement::sourceWasAdded - 'src' is %s", urlForLoggingMedia(url).utf8().data()); 4020 } 4021#endif 4022 4023 // We should only consider a <source> element when there is not src attribute at all. 4024 if (fastHasAttribute(srcAttr)) 4025 return; 4026 4027 // 4.8.8 - If a source element is inserted as a child of a media element that has no src 4028 // attribute and whose networkState has the value NETWORK_EMPTY, the user agent must invoke 4029 // the media element's resource selection algorithm. 4030 if (networkState() == HTMLMediaElement::NETWORK_EMPTY) { 4031 scheduleDelayedAction(LoadMediaResource); 4032 m_nextChildNodeToConsider = source; 4033 return; 4034 } 4035 4036 if (m_currentSourceNode && source == m_currentSourceNode->nextSibling()) { 4037 LOG(Media, "HTMLMediaElement::sourceWasAdded - <source> inserted immediately after current source"); 4038 m_nextChildNodeToConsider = source; 4039 return; 4040 } 4041 4042 if (m_nextChildNodeToConsider) 4043 return; 4044 4045 // 4.8.9.5, resource selection algorithm, source elements section: 4046 // 21. Wait until the node after pointer is a node other than the end of the list. (This step might wait forever.) 4047 // 22. Asynchronously await a stable state... 4048 // 23. Set the element's delaying-the-load-event flag back to true (this delays the load event again, in case 4049 // it hasn't been fired yet). 4050 setShouldDelayLoadEvent(true); 4051 4052 // 24. Set the networkState back to NETWORK_LOADING. 4053 m_networkState = NETWORK_LOADING; 4054 4055 // 25. Jump back to the find next candidate step above. 4056 m_nextChildNodeToConsider = source; 4057 scheduleNextSourceChild(); 4058} 4059 4060void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement* source) 4061{ 4062 LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p)", source); 4063 4064#if !LOG_DISABLED 4065 if (source->hasTagName(sourceTag)) { 4066 URL url = source->getNonEmptyURLAttribute(srcAttr); 4067 LOG(Media, "HTMLMediaElement::sourceWasRemoved - 'src' is %s", urlForLoggingMedia(url).utf8().data()); 4068 } 4069#endif 4070 4071 if (source != m_currentSourceNode && source != m_nextChildNodeToConsider) 4072 return; 4073 4074 if (source == m_nextChildNodeToConsider) { 4075 if (m_currentSourceNode) 4076 m_nextChildNodeToConsider = m_currentSourceNode->nextSibling(); 4077 LOG(Media, "HTMLMediaElement::sourceRemoved - m_nextChildNodeToConsider set to %p", m_nextChildNodeToConsider.get()); 4078 } else if (source == m_currentSourceNode) { 4079 // Clear the current source node pointer, but don't change the movie as the spec says: 4080 // 4.8.8 - Dynamically modifying a source element and its attribute when the element is already 4081 // inserted in a video or audio element will have no effect. 4082 m_currentSourceNode = 0; 4083 LOG(Media, "HTMLMediaElement::sourceRemoved - m_currentSourceNode set to 0"); 4084 } 4085} 4086 4087void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*) 4088{ 4089 LOG(Media, "HTMLMediaElement::mediaPlayerTimeChanged"); 4090 4091#if ENABLE(VIDEO_TRACK) 4092 if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 4093 updateActiveTextTrackCues(currentTime()); 4094#endif 4095 4096 beginProcessingMediaPlayerCallback(); 4097 4098 invalidateCachedTime(); 4099 4100 // 4.8.10.9 step 14 & 15. Needed if no ReadyState change is associated with the seek. 4101 if (m_seeking && m_readyState >= HAVE_CURRENT_DATA && !m_player->seeking()) 4102 finishSeek(); 4103 4104 // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity, 4105 // it will only queue a 'timeupdate' event if we haven't already posted one at the current 4106 // movie time. 4107 else 4108 scheduleTimeupdateEvent(false); 4109 4110 double now = currentTime(); 4111 double dur = duration(); 4112 double playbackRate = effectivePlaybackRate(); 4113 4114 // When the current playback position reaches the end of the media resource then the user agent must follow these steps: 4115 if (!std::isnan(dur) && dur) { 4116 // If the media element has a loop attribute specified and does not have a current media controller, 4117 if (loop() && !m_mediaController && playbackRate > 0) { 4118 m_sentEndEvent = false; 4119 // then seek to the earliest possible position of the media resource and abort these steps when the direction of 4120 // playback is forwards, 4121 if (now >= dur) 4122 seekInternal(0); 4123 } else if ((now <= 0 && playbackRate < 0) || (now >= dur && playbackRate > 0)) { 4124 // If the media element does not have a current media controller, and the media element 4125 // has still ended playback and paused is false, 4126 if (!m_mediaController && !m_paused) { 4127 // changes paused to true and fires a simple event named pause at the media element. 4128 m_paused = true; 4129 scheduleEvent(eventNames().pauseEvent); 4130 m_mediaSession->clientWillPausePlayback(); 4131 } 4132 // Queue a task to fire a simple event named ended at the media element. 4133 if (!m_sentEndEvent) { 4134 m_sentEndEvent = true; 4135 scheduleEvent(eventNames().endedEvent); 4136 } 4137 // If the media element has a current media controller, then report the controller state 4138 // for the media element's current media controller. 4139 updateMediaController(); 4140 } else 4141 m_sentEndEvent = false; 4142 } else { 4143#if PLATFORM(IOS) 4144 // The controller changes movie time directly instead of calling through here so we need 4145 // to post timeupdate events in response to time changes. 4146 scheduleTimeupdateEvent(false); 4147#endif 4148 m_sentEndEvent = false; 4149 } 4150 4151 updatePlayState(); 4152 endProcessingMediaPlayerCallback(); 4153} 4154 4155void HTMLMediaElement::mediaPlayerVolumeChanged(MediaPlayer*) 4156{ 4157 LOG(Media, "HTMLMediaElement::mediaPlayerVolumeChanged"); 4158 4159 beginProcessingMediaPlayerCallback(); 4160 if (m_player) { 4161 double vol = m_player->volume(); 4162 if (vol != m_volume) { 4163 m_volume = vol; 4164 updateVolume(); 4165 scheduleEvent(eventNames().volumechangeEvent); 4166 } 4167 } 4168 endProcessingMediaPlayerCallback(); 4169} 4170 4171void HTMLMediaElement::mediaPlayerMuteChanged(MediaPlayer*) 4172{ 4173 LOG(Media, "HTMLMediaElement::mediaPlayerMuteChanged"); 4174 4175 beginProcessingMediaPlayerCallback(); 4176 if (m_player) 4177 setMuted(m_player->muted()); 4178 endProcessingMediaPlayerCallback(); 4179} 4180 4181void HTMLMediaElement::mediaPlayerDurationChanged(MediaPlayer* player) 4182{ 4183 LOG(Media, "HTMLMediaElement::mediaPlayerDurationChanged"); 4184 4185 beginProcessingMediaPlayerCallback(); 4186 4187 scheduleEvent(eventNames().durationchangeEvent); 4188 mediaPlayerCharacteristicChanged(player); 4189 4190 double now = currentTime(); 4191 double dur = duration(); 4192 if (now > dur) 4193 seekInternal(dur); 4194 4195 endProcessingMediaPlayerCallback(); 4196} 4197 4198void HTMLMediaElement::mediaPlayerRateChanged(MediaPlayer*) 4199{ 4200 LOG(Media, "HTMLMediaElement::mediaPlayerRateChanged"); 4201 4202 beginProcessingMediaPlayerCallback(); 4203 4204 // Stash the rate in case the one we tried to set isn't what the engine is 4205 // using (eg. it can't handle the rate we set) 4206 m_playbackRate = m_player->rate(); 4207 if (m_playing) 4208 invalidateCachedTime(); 4209 4210 updateSleepDisabling(); 4211 4212 endProcessingMediaPlayerCallback(); 4213} 4214 4215void HTMLMediaElement::mediaPlayerPlaybackStateChanged(MediaPlayer*) 4216{ 4217 LOG(Media, "HTMLMediaElement::mediaPlayerPlaybackStateChanged"); 4218 4219 if (!m_player || m_pausedInternal) 4220 return; 4221 4222 beginProcessingMediaPlayerCallback(); 4223 if (m_player->paused()) 4224 pauseInternal(); 4225 else 4226 playInternal(); 4227 endProcessingMediaPlayerCallback(); 4228} 4229 4230void HTMLMediaElement::mediaPlayerSawUnsupportedTracks(MediaPlayer*) 4231{ 4232 LOG(Media, "HTMLMediaElement::mediaPlayerSawUnsupportedTracks"); 4233 4234 // The MediaPlayer came across content it cannot completely handle. 4235 // This is normally acceptable except when we are in a standalone 4236 // MediaDocument. If so, tell the document what has happened. 4237 if (document().isMediaDocument()) 4238 toMediaDocument(document()).mediaElementSawUnsupportedTracks(); 4239} 4240 4241void HTMLMediaElement::mediaPlayerResourceNotSupported(MediaPlayer*) 4242{ 4243 LOG(Media, "HTMLMediaElement::mediaPlayerResourceNotSupported"); 4244 4245 // The MediaPlayer came across content which no installed engine supports. 4246 mediaLoadingFailed(MediaPlayer::FormatError); 4247} 4248 4249// MediaPlayerPresentation methods 4250void HTMLMediaElement::mediaPlayerRepaint(MediaPlayer*) 4251{ 4252 beginProcessingMediaPlayerCallback(); 4253 updateDisplayState(); 4254 if (renderer()) 4255 renderer()->repaint(); 4256 endProcessingMediaPlayerCallback(); 4257} 4258 4259void HTMLMediaElement::mediaPlayerSizeChanged(MediaPlayer*) 4260{ 4261 LOG(Media, "HTMLMediaElement::mediaPlayerSizeChanged"); 4262 4263 beginProcessingMediaPlayerCallback(); 4264 if (renderer()) 4265 renderer()->updateFromElement(); 4266 endProcessingMediaPlayerCallback(); 4267} 4268 4269bool HTMLMediaElement::mediaPlayerRenderingCanBeAccelerated(MediaPlayer*) 4270{ 4271 if (renderer() && renderer()->isVideo()) 4272 return renderer()->view().compositor().canAccelerateVideoRendering(toRenderVideo(*renderer())); 4273 return false; 4274} 4275 4276void HTMLMediaElement::mediaPlayerRenderingModeChanged(MediaPlayer*) 4277{ 4278 LOG(Media, "HTMLMediaElement::mediaPlayerRenderingModeChanged"); 4279 4280 // Kick off a fake recalcStyle that will update the compositing tree. 4281 setNeedsStyleRecalc(SyntheticStyleChange); 4282} 4283 4284#if PLATFORM(WIN) && USE(AVFOUNDATION) 4285GraphicsDeviceAdapter* HTMLMediaElement::mediaPlayerGraphicsDeviceAdapter(const MediaPlayer*) const 4286{ 4287 if (!document().page()) 4288 return 0; 4289 4290 return document().page()->chrome().client().graphicsDeviceAdapter(); 4291} 4292#endif 4293 4294void HTMLMediaElement::mediaPlayerEngineUpdated(MediaPlayer*) 4295{ 4296 LOG(Media, "HTMLMediaElement::mediaPlayerEngineUpdated"); 4297 beginProcessingMediaPlayerCallback(); 4298 if (renderer()) 4299 renderer()->updateFromElement(); 4300 endProcessingMediaPlayerCallback(); 4301 4302#if ENABLE(MEDIA_SOURCE) 4303 m_droppedVideoFrames = 0; 4304#endif 4305 4306 m_havePreparedToPlay = false; 4307 4308#if PLATFORM(IOS) 4309 if (!m_player) 4310 return; 4311 m_player->setVideoFullscreenFrame(m_videoFullscreenFrame); 4312 m_player->setVideoFullscreenGravity(m_videoFullscreenGravity); 4313 m_player->setVideoFullscreenLayer(m_videoFullscreenLayer.get()); 4314#endif 4315} 4316 4317void HTMLMediaElement::mediaPlayerFirstVideoFrameAvailable(MediaPlayer*) 4318{ 4319 LOG(Media, "HTMLMediaElement::mediaPlayerFirstVideoFrameAvailable(%p) - current display mode = %i", this, (int)displayMode()); 4320 4321 beginProcessingMediaPlayerCallback(); 4322 if (displayMode() == PosterWaitingForVideo) { 4323 setDisplayMode(Video); 4324 mediaPlayerRenderingModeChanged(m_player.get()); 4325 } 4326 endProcessingMediaPlayerCallback(); 4327} 4328 4329void HTMLMediaElement::mediaPlayerCharacteristicChanged(MediaPlayer*) 4330{ 4331 LOG(Media, "HTMLMediaElement::mediaPlayerCharacteristicChanged"); 4332 4333 beginProcessingMediaPlayerCallback(); 4334 4335#if ENABLE(VIDEO_TRACK) 4336 if (m_captionDisplayMode == CaptionUserPreferences::Automatic && m_subtitleTrackLanguage != m_player->languageOfPrimaryAudioTrack()) 4337 markCaptionAndSubtitleTracksAsUnconfigured(AfterDelay); 4338#endif 4339 4340 if (potentiallyPlaying() && displayMode() == PosterWaitingForVideo) { 4341 setDisplayMode(Video); 4342 mediaPlayerRenderingModeChanged(m_player.get()); 4343 } 4344 4345 if (hasMediaControls()) 4346 mediaControls()->reset(); 4347 if (renderer()) 4348 renderer()->updateFromElement(); 4349 endProcessingMediaPlayerCallback(); 4350} 4351 4352PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const 4353{ 4354 if (!m_player) 4355 return TimeRanges::create(); 4356 4357#if ENABLE(MEDIA_SOURCE) 4358 if (m_mediaSource) 4359 return TimeRanges::create(*m_mediaSource->buffered()); 4360#endif 4361 4362 return TimeRanges::create(*m_player->buffered()); 4363} 4364 4365PassRefPtr<TimeRanges> HTMLMediaElement::played() 4366{ 4367 if (m_playing) { 4368 double time = currentTime(); 4369 if (time > m_lastSeekTime) 4370 addPlayedRange(m_lastSeekTime, time); 4371 } 4372 4373 if (!m_playedTimeRanges) 4374 m_playedTimeRanges = TimeRanges::create(); 4375 4376 return m_playedTimeRanges->copy(); 4377} 4378 4379PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const 4380{ 4381 if (m_player) 4382 return TimeRanges::create(*m_player->seekable()); 4383 4384 return TimeRanges::create(); 4385} 4386 4387bool HTMLMediaElement::potentiallyPlaying() const 4388{ 4389 if (isBlockedOnMediaController()) 4390 return false; 4391 4392 if (!couldPlayIfEnoughData()) 4393 return false; 4394 4395 if (m_readyState >= HAVE_FUTURE_DATA) 4396 return true; 4397 4398 return m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA; 4399} 4400 4401bool HTMLMediaElement::couldPlayIfEnoughData() const 4402{ 4403 if (paused()) 4404 return false; 4405 4406 if (endedPlayback()) 4407 return false; 4408 4409 if (stoppedDueToErrors()) 4410 return false; 4411 4412 if (pausedForUserInteraction()) 4413 return false; 4414 4415 return true; 4416} 4417 4418bool HTMLMediaElement::endedPlayback() const 4419{ 4420 double dur = duration(); 4421 if (!m_player || std::isnan(dur)) 4422 return false; 4423 4424 // 4.8.10.8 Playing the media resource 4425 4426 // A media element is said to have ended playback when the element's 4427 // readyState attribute is HAVE_METADATA or greater, 4428 if (m_readyState < HAVE_METADATA) 4429 return false; 4430 4431 // and the current playback position is the end of the media resource and the direction 4432 // of playback is forwards, Either the media element does not have a loop attribute specified, 4433 // or the media element has a current media controller. 4434 double now = currentTime(); 4435 if (effectivePlaybackRate() > 0) 4436 return dur > 0 && now >= dur && (!loop() || m_mediaController); 4437 4438 // or the current playback position is the earliest possible position and the direction 4439 // of playback is backwards 4440 if (effectivePlaybackRate() < 0) 4441 return now <= 0; 4442 4443 return false; 4444} 4445 4446bool HTMLMediaElement::stoppedDueToErrors() const 4447{ 4448 if (m_readyState >= HAVE_METADATA && m_error) { 4449 RefPtr<TimeRanges> seekableRanges = seekable(); 4450 if (!seekableRanges->contain(currentTime())) 4451 return true; 4452 } 4453 4454 return false; 4455} 4456 4457bool HTMLMediaElement::pausedForUserInteraction() const 4458{ 4459 if (m_mediaSession->state() == MediaSession::Interrupted) 4460 return true; 4461 4462 return false; 4463} 4464 4465double HTMLMediaElement::minTimeSeekable() const 4466{ 4467 return m_player ? m_player->minTimeSeekable() : 0; 4468} 4469 4470double HTMLMediaElement::maxTimeSeekable() const 4471{ 4472 return m_player ? m_player->maxTimeSeekable() : 0; 4473} 4474 4475void HTMLMediaElement::updateVolume() 4476{ 4477#if PLATFORM(IOS) 4478 // Only the user can change audio volume so update the cached volume and post the changed event. 4479 float volume = m_player->volume(); 4480 if (m_volume != volume) { 4481 m_volume = volume; 4482 scheduleEvent(eventNames().volumechangeEvent); 4483 } 4484#else 4485 if (!m_player) 4486 return; 4487 4488 // Avoid recursion when the player reports volume changes. 4489 if (!processingMediaPlayerCallback()) { 4490 Page* page = document().page(); 4491 double volumeMultiplier = page ? page->mediaVolume() : 1; 4492 bool shouldMute = muted(); 4493 4494 if (m_mediaController) { 4495 volumeMultiplier *= m_mediaController->volume(); 4496 shouldMute = m_mediaController->muted(); 4497 } 4498 4499 m_player->setMuted(shouldMute); 4500 if (m_volumeInitialized) 4501 m_player->setVolume(m_volume * volumeMultiplier); 4502 } 4503 4504 if (hasMediaControls()) 4505 mediaControls()->changedVolume(); 4506#endif 4507} 4508 4509void HTMLMediaElement::updatePlayState() 4510{ 4511 if (!m_player) 4512 return; 4513 4514 if (m_pausedInternal) { 4515 if (!m_player->paused()) 4516 m_player->pause(); 4517 refreshCachedTime(); 4518 m_playbackProgressTimer.stop(); 4519 if (hasMediaControls()) 4520 mediaControls()->playbackStopped(); 4521 m_activityToken = nullptr; 4522 return; 4523 } 4524 4525 bool shouldBePlaying = potentiallyPlaying(); 4526 bool playerPaused = m_player->paused(); 4527 4528 LOG(Media, "HTMLMediaElement::updatePlayState - shouldBePlaying = %s, playerPaused = %s", 4529 boolString(shouldBePlaying), boolString(playerPaused)); 4530 4531 if (shouldBePlaying) { 4532 setDisplayMode(Video); 4533 invalidateCachedTime(); 4534 4535 if (playerPaused) { 4536 m_mediaSession->clientWillBeginPlayback(); 4537 4538 if (m_mediaSession->requiresFullscreenForVideoPlayback(*this) && !isFullscreen()) 4539 enterFullscreen(); 4540 4541 // Set rate, muted before calling play in case they were set before the media engine was setup. 4542 // The media engine should just stash the rate and muted values since it isn't already playing. 4543 m_player->setRate(effectivePlaybackRate()); 4544 m_player->setMuted(muted()); 4545 4546 m_player->play(); 4547 } 4548 4549 if (hasMediaControls()) 4550 mediaControls()->playbackStarted(); 4551 if (document().page() && document().page()->pageThrottler()) 4552 m_activityToken = document().page()->pageThrottler()->mediaActivityToken(); 4553 4554 startPlaybackProgressTimer(); 4555 m_playing = true; 4556 4557 } else { 4558 if (!playerPaused) 4559 m_player->pause(); 4560 refreshCachedTime(); 4561 4562 m_playbackProgressTimer.stop(); 4563 m_playing = false; 4564 double time = currentTime(); 4565 if (time > m_lastSeekTime) 4566 addPlayedRange(m_lastSeekTime, time); 4567 4568 if (couldPlayIfEnoughData()) 4569 prepareToPlay(); 4570 4571 if (hasMediaControls()) 4572 mediaControls()->playbackStopped(); 4573 m_activityToken = nullptr; 4574 } 4575 4576#if PLATFORM(IOS) 4577 m_requestingPlay = false; 4578#endif 4579 4580 updateMediaController(); 4581 4582 if (renderer()) 4583 renderer()->updateFromElement(); 4584} 4585 4586void HTMLMediaElement::setPausedInternal(bool b) 4587{ 4588 m_pausedInternal = b; 4589 updatePlayState(); 4590} 4591 4592void HTMLMediaElement::stopPeriodicTimers() 4593{ 4594 m_progressEventTimer.stop(); 4595 m_playbackProgressTimer.stop(); 4596} 4597 4598void HTMLMediaElement::userCancelledLoad() 4599{ 4600 LOG(Media, "HTMLMediaElement::userCancelledLoad"); 4601 4602 // FIXME: We should look to reconcile the iOS and non-iOS code (below). 4603#if PLATFORM(IOS) 4604 if (m_networkState == NETWORK_EMPTY || m_readyState >= HAVE_METADATA) 4605 return; 4606#else 4607 if (m_networkState == NETWORK_EMPTY || m_completelyLoaded) 4608 return; 4609#endif 4610 4611 // If the media data fetching process is aborted by the user: 4612 4613 // 1 - The user agent should cancel the fetching process. 4614 clearMediaPlayer(-1); 4615 4616 // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED. 4617 m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED); 4618 4619 // 3 - Queue a task to fire a simple event named error at the media element. 4620 scheduleEvent(eventNames().abortEvent); 4621 4622#if ENABLE(MEDIA_SOURCE) 4623 closeMediaSource(); 4624#endif 4625 4626 // 4 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the 4627 // element's networkState attribute to the NETWORK_EMPTY value and queue a task to fire a 4628 // simple event named emptied at the element. Otherwise, set the element's networkState 4629 // attribute to the NETWORK_IDLE value. 4630 if (m_readyState == HAVE_NOTHING) { 4631 m_networkState = NETWORK_EMPTY; 4632 scheduleEvent(eventNames().emptiedEvent); 4633 } 4634 else 4635 m_networkState = NETWORK_IDLE; 4636 4637 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. 4638 setShouldDelayLoadEvent(false); 4639 4640 // 6 - Abort the overall resource selection algorithm. 4641 m_currentSourceNode = 0; 4642 4643 // Reset m_readyState since m_player is gone. 4644 m_readyState = HAVE_NOTHING; 4645 updateMediaController(); 4646#if ENABLE(VIDEO_TRACK) 4647 if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 4648 updateActiveTextTrackCues(0); 4649#endif 4650} 4651 4652void HTMLMediaElement::clearMediaPlayer(int flags) 4653{ 4654#if USE(PLATFORM_TEXT_TRACK_MENU) 4655 if (platformTextTrackMenu()) { 4656 m_platformMenu->setClient(0); 4657 m_platformMenu = 0; 4658 } 4659#endif 4660 4661#if ENABLE(VIDEO_TRACK) 4662 forgetResourceSpecificTracks(); 4663#endif 4664 4665#if ENABLE(MEDIA_SOURCE) 4666 closeMediaSource(); 4667#endif 4668 4669 m_player.clear(); 4670 4671 stopPeriodicTimers(); 4672 m_loadTimer.stop(); 4673 4674 clearFlags(m_pendingActionFlags, flags); 4675 m_loadState = WaitingForSource; 4676 4677#if ENABLE(VIDEO_TRACK) 4678 if (m_textTracks) 4679 configureTextTrackDisplay(); 4680#endif 4681 4682 updateSleepDisabling(); 4683} 4684 4685bool HTMLMediaElement::canSuspend() const 4686{ 4687 return true; 4688} 4689 4690void HTMLMediaElement::stop() 4691{ 4692 LOG(Media, "HTMLMediaElement::stop"); 4693 if (m_isFullscreen) 4694 exitFullscreen(); 4695 4696 m_inActiveDocument = false; 4697 4698 // Stop the playback without generating events 4699 m_playing = false; 4700 setPausedInternal(true); 4701 4702 userCancelledLoad(); 4703 4704 if (renderer()) 4705 renderer()->updateFromElement(); 4706 4707 stopPeriodicTimers(); 4708 cancelPendingEventsAndCallbacks(); 4709 4710 m_asyncEventQueue.close(); 4711 4712 // Once an active DOM object has been stopped it can not be restarted, so we can deallocate 4713 // the media player now. Note that userCancelledLoad will already have cleared the player 4714 // if the media was not fully loaded. This handles all other cases. 4715 m_player.clear(); 4716 4717 updateSleepDisabling(); 4718} 4719 4720void HTMLMediaElement::suspend(ReasonForSuspension why) 4721{ 4722 LOG(Media, "HTMLMediaElement::suspend"); 4723 4724 switch (why) 4725 { 4726 case DocumentWillBecomeInactive: 4727 stop(); 4728 m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequirePageConsentToResumeMedia); 4729 break; 4730 case DocumentWillBePaused: 4731 case JavaScriptDebuggerPaused: 4732 case PageWillBeSuspended: 4733 case WillDeferLoading: 4734 // Do nothing, we don't pause media playback in these cases. 4735 break; 4736 } 4737} 4738 4739void HTMLMediaElement::resume() 4740{ 4741 LOG(Media, "HTMLMediaElement::resume"); 4742 4743 m_inActiveDocument = true; 4744 4745 if (!m_mediaSession->pageAllowsPlaybackAfterResuming(*this)) 4746 document().addMediaCanStartListener(this); 4747 else 4748 setPausedInternal(false); 4749 4750 m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequirePageConsentToResumeMedia); 4751 4752 if (m_error && m_error->code() == MediaError::MEDIA_ERR_ABORTED) { 4753 // Restart the load if it was aborted in the middle by moving the document to the page cache. 4754 // m_error is only left at MEDIA_ERR_ABORTED when the document becomes inactive (it is set to 4755 // MEDIA_ERR_ABORTED while the abortEvent is being sent, but cleared immediately afterwards). 4756 // This behavior is not specified but it seems like a sensible thing to do. 4757#if PLATFORM(IOS) 4758 // FIXME: <rdar://problem/9751303> Merge: Does r1033092 need to be refixed in ToT? 4759#endif 4760 // As it is not safe to immedately start loading now, let's schedule a load. 4761 scheduleDelayedAction(LoadMediaResource); 4762 } 4763 4764 if (renderer()) 4765 renderer()->updateFromElement(); 4766} 4767 4768bool HTMLMediaElement::hasPendingActivity() const 4769{ 4770 return (hasAudio() && isPlaying()) || m_asyncEventQueue.hasPendingEvents(); 4771} 4772 4773void HTMLMediaElement::mediaVolumeDidChange() 4774{ 4775 LOG(Media, "HTMLMediaElement::mediaVolumeDidChange"); 4776 updateVolume(); 4777} 4778 4779void HTMLMediaElement::visibilityStateChanged() 4780{ 4781 LOG(Media, "HTMLMediaElement::visibilityStateChanged"); 4782 m_elementIsHidden = document().hidden(); 4783 updateSleepDisabling(); 4784 m_mediaSession->visibilityChanged(); 4785} 4786 4787#if ENABLE(VIDEO_TRACK) 4788bool HTMLMediaElement::requiresTextTrackRepresentation() const 4789{ 4790 return m_isFullscreen && m_player ? m_player->requiresTextTrackRepresentation() : false; 4791} 4792 4793void HTMLMediaElement::setTextTrackRepresentation(TextTrackRepresentation* representation) 4794{ 4795 if (m_player) 4796 m_player->setTextTrackRepresentation(representation); 4797} 4798 4799void HTMLMediaElement::syncTextTrackBounds() 4800{ 4801 if (m_player) 4802 m_player->syncTextTrackBounds(); 4803} 4804#endif // ENABLE(VIDEO_TRACK) 4805 4806#if ENABLE(IOS_AIRPLAY) 4807void HTMLMediaElement::webkitShowPlaybackTargetPicker() 4808{ 4809 m_mediaSession->showPlaybackTargetPicker(*this); 4810} 4811 4812bool HTMLMediaElement::webkitCurrentPlaybackTargetIsWireless() const 4813{ 4814 return m_mediaSession->currentPlaybackTargetIsWireless(*this); 4815} 4816 4817void HTMLMediaElement::mediaPlayerCurrentPlaybackTargetIsWirelessChanged(MediaPlayer*) 4818{ 4819 LOG(Media, "HTMLMediaElement::mediaPlayerCurrentPlaybackTargetIsWirelessChanged - webkitCurrentPlaybackTargetIsWireless = %s", boolString(webkitCurrentPlaybackTargetIsWireless())); 4820 scheduleEvent(eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent); 4821} 4822 4823void HTMLMediaElement::mediaPlayerPlaybackTargetAvailabilityChanged(MediaPlayer*) 4824{ 4825 enqueuePlaybackTargetAvailabilityChangedEvent(); 4826} 4827 4828bool HTMLMediaElement::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> listener, bool useCapture) 4829{ 4830 if (eventType != eventNames().webkitplaybacktargetavailabilitychangedEvent) 4831 return Node::addEventListener(eventType, listener, useCapture); 4832 4833 bool isFirstAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent); 4834 if (!Node::addEventListener(eventType, listener, useCapture)) 4835 return false; 4836 4837 if (isFirstAvailabilityChangedListener) 4838 m_mediaSession->setHasPlaybackTargetAvailabilityListeners(*this, true); 4839 4840 LOG(Media, "HTMLMediaElement::addEventListener('webkitplaybacktargetavailabilitychanged')"); 4841 4842 enqueuePlaybackTargetAvailabilityChangedEvent(); // Ensure the event listener gets at least one event. 4843 return true; 4844} 4845 4846bool HTMLMediaElement::removeEventListener(const AtomicString& eventType, EventListener* listener, bool useCapture) 4847{ 4848 if (eventType != eventNames().webkitplaybacktargetavailabilitychangedEvent) 4849 return Node::removeEventListener(eventType, listener, useCapture); 4850 4851 if (!Node::removeEventListener(eventType, listener, useCapture)) 4852 return false; 4853 4854 bool didRemoveLastAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent); 4855 if (didRemoveLastAvailabilityChangedListener) 4856 m_mediaSession->setHasPlaybackTargetAvailabilityListeners(*this, false); 4857 4858 return true; 4859} 4860 4861void HTMLMediaElement::enqueuePlaybackTargetAvailabilityChangedEvent() 4862{ 4863 LOG(Media, "HTMLMediaElement::enqueuePlaybackTargetAvailabilityChangedEvent"); 4864 RefPtr<Event> event = WebKitPlaybackTargetAvailabilityEvent::create(eventNames().webkitplaybacktargetavailabilitychangedEvent, m_mediaSession->hasWirelessPlaybackTargets(*this)); 4865 event->setTarget(this); 4866 m_asyncEventQueue.enqueueEvent(event.release()); 4867} 4868#endif 4869 4870double HTMLMediaElement::minFastReverseRate() const 4871{ 4872 return m_player ? m_player->minFastReverseRate() : 0; 4873} 4874 4875double HTMLMediaElement::maxFastForwardRate() const 4876{ 4877 return m_player ? m_player->maxFastForwardRate() : 0; 4878} 4879 4880bool HTMLMediaElement::isFullscreen() const 4881{ 4882 if (m_isFullscreen) 4883 return true; 4884 4885#if ENABLE(FULLSCREEN_API) 4886 if (document().webkitIsFullScreen() && document().webkitCurrentFullScreenElement() == this) 4887 return true; 4888#endif 4889 4890 return false; 4891} 4892 4893void HTMLMediaElement::toggleFullscreenState() 4894{ 4895 LOG(Media, "HTMLMediaElement::toggleFullscreenState - isFullscreen() is %s", boolString(isFullscreen())); 4896 4897 if (isFullscreen()) 4898 exitFullscreen(); 4899 else 4900 enterFullscreen(); 4901} 4902 4903void HTMLMediaElement::enterFullscreen() 4904{ 4905 LOG(Media, "HTMLMediaElement::enterFullscreen"); 4906 if (m_isFullscreen) 4907 return; 4908 4909#if ENABLE(FULLSCREEN_API) 4910 if (document().settings() && document().settings()->fullScreenEnabled()) { 4911 document().requestFullScreenForElement(this, 0, Document::ExemptIFrameAllowFullScreenRequirement); 4912 return; 4913 } 4914#endif 4915 4916 m_isFullscreen = true; 4917 if (hasMediaControls()) 4918 mediaControls()->enteredFullscreen(); 4919 if (document().page()) { 4920 if (document().page()->chrome().client().supportsFullscreenForNode(this)) { 4921 document().page()->chrome().client().enterFullscreenForNode(this); 4922 scheduleEvent(eventNames().webkitbeginfullscreenEvent); 4923 } 4924 } 4925} 4926 4927void HTMLMediaElement::exitFullscreen() 4928{ 4929 LOG(Media, "HTMLMediaElement::exitFullscreen"); 4930 4931#if ENABLE(FULLSCREEN_API) 4932 if (document().settings() && document().settings()->fullScreenEnabled()) { 4933 if (document().webkitIsFullScreen() && document().webkitCurrentFullScreenElement() == this) 4934 document().webkitCancelFullScreen(); 4935 return; 4936 } 4937#endif 4938 ASSERT(m_isFullscreen); 4939 m_isFullscreen = false; 4940 if (hasMediaControls()) 4941 mediaControls()->exitedFullscreen(); 4942 if (document().page()) { 4943 if (m_mediaSession->requiresFullscreenForVideoPlayback(*this)) 4944 pauseInternal(); 4945 4946 if (document().page()->chrome().client().supportsFullscreenForNode(this)) { 4947 document().page()->chrome().client().exitFullscreenForNode(this); 4948 scheduleEvent(eventNames().webkitendfullscreenEvent); 4949 } 4950 } 4951} 4952 4953void HTMLMediaElement::didBecomeFullscreenElement() 4954{ 4955 if (hasMediaControls()) 4956 mediaControls()->enteredFullscreen(); 4957} 4958 4959void HTMLMediaElement::willStopBeingFullscreenElement() 4960{ 4961 if (hasMediaControls()) 4962 mediaControls()->exitedFullscreen(); 4963} 4964 4965PlatformMedia HTMLMediaElement::platformMedia() const 4966{ 4967 return m_player ? m_player->platformMedia() : NoPlatformMedia; 4968} 4969 4970PlatformLayer* HTMLMediaElement::platformLayer() const 4971{ 4972 return m_player ? m_player->platformLayer() : nullptr; 4973} 4974 4975#if PLATFORM(IOS) 4976void HTMLMediaElement::setVideoFullscreenLayer(PlatformLayer* platformLayer) 4977{ 4978 m_videoFullscreenLayer = platformLayer; 4979 if (!m_player) 4980 return; 4981 4982 m_player->setVideoFullscreenLayer(platformLayer); 4983 setNeedsStyleRecalc(SyntheticStyleChange); 4984#if ENABLE(VIDEO_TRACK) 4985 if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) 4986 updateTextTrackDisplay(); 4987#endif 4988} 4989 4990void HTMLMediaElement::setVideoFullscreenFrame(FloatRect frame) 4991{ 4992 m_videoFullscreenFrame = frame; 4993 if (m_player) 4994 m_player->setVideoFullscreenFrame(frame); 4995} 4996 4997void HTMLMediaElement::setVideoFullscreenGravity(MediaPlayer::VideoGravity gravity) 4998{ 4999 m_videoFullscreenGravity = gravity; 5000 if (m_player) 5001 m_player->setVideoFullscreenGravity(gravity); 5002} 5003#endif 5004 5005bool HTMLMediaElement::hasClosedCaptions() const 5006{ 5007 if (m_player && m_player->hasClosedCaptions()) 5008 return true; 5009 5010#if ENABLE(VIDEO_TRACK) 5011 if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() || !m_textTracks) 5012 return false; 5013 5014 for (unsigned i = 0; i < m_textTracks->length(); ++i) { 5015 if (m_textTracks->item(i)->readinessState() == TextTrack::FailedToLoad) 5016 continue; 5017 5018 if (m_textTracks->item(i)->kind() == TextTrack::captionsKeyword() 5019 || m_textTracks->item(i)->kind() == TextTrack::subtitlesKeyword()) 5020 return true; 5021 } 5022#endif 5023 5024 return false; 5025} 5026 5027bool HTMLMediaElement::closedCaptionsVisible() const 5028{ 5029 return m_closedCaptionsVisible; 5030} 5031 5032#if ENABLE(VIDEO_TRACK) 5033void HTMLMediaElement::updateTextTrackDisplay() 5034{ 5035#if ENABLE(MEDIA_CONTROLS_SCRIPT) 5036 ensureUserAgentShadowRoot(); 5037 ASSERT(m_mediaControlsHost); 5038 m_mediaControlsHost->updateTextTrackContainer(); 5039 return; 5040#endif 5041 if (!hasMediaControls() && !createMediaControls()) 5042 return; 5043 5044 mediaControls()->updateTextTrackDisplay(); 5045} 5046#endif 5047 5048void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible) 5049{ 5050 LOG(Media, "HTMLMediaElement::setClosedCaptionsVisible(%s)", boolString(closedCaptionVisible)); 5051 5052 m_closedCaptionsVisible = false; 5053 5054 if (!m_player || !hasClosedCaptions()) 5055 return; 5056 5057 m_closedCaptionsVisible = closedCaptionVisible; 5058 m_player->setClosedCaptionsVisible(closedCaptionVisible); 5059 5060#if ENABLE(VIDEO_TRACK) 5061 if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) { 5062 markCaptionAndSubtitleTracksAsUnconfigured(Immediately); 5063 updateTextTrackDisplay(); 5064 } 5065#else 5066 if (hasMediaControls()) 5067 mediaControls()->changedClosedCaptionsVisibility(); 5068#endif 5069} 5070 5071void HTMLMediaElement::setWebkitClosedCaptionsVisible(bool visible) 5072{ 5073 m_webkitLegacyClosedCaptionOverride = visible; 5074 setClosedCaptionsVisible(visible); 5075} 5076 5077bool HTMLMediaElement::webkitClosedCaptionsVisible() const 5078{ 5079 return m_webkitLegacyClosedCaptionOverride && m_closedCaptionsVisible; 5080} 5081 5082 5083bool HTMLMediaElement::webkitHasClosedCaptions() const 5084{ 5085 return hasClosedCaptions(); 5086} 5087 5088#if ENABLE(MEDIA_STATISTICS) 5089unsigned HTMLMediaElement::webkitAudioDecodedByteCount() const 5090{ 5091 if (!m_player) 5092 return 0; 5093 return m_player->audioDecodedByteCount(); 5094} 5095 5096unsigned HTMLMediaElement::webkitVideoDecodedByteCount() const 5097{ 5098 if (!m_player) 5099 return 0; 5100 return m_player->videoDecodedByteCount(); 5101} 5102#endif 5103 5104void HTMLMediaElement::mediaCanStart() 5105{ 5106 LOG(Media, "HTMLMediaElement::mediaCanStart"); 5107 5108 ASSERT(m_isWaitingUntilMediaCanStart || m_pausedInternal); 5109 if (m_isWaitingUntilMediaCanStart) { 5110 m_isWaitingUntilMediaCanStart = false; 5111 loadInternal(); 5112 } 5113 if (m_pausedInternal) 5114 setPausedInternal(false); 5115} 5116 5117bool HTMLMediaElement::isURLAttribute(const Attribute& attribute) const 5118{ 5119 return attribute.name() == srcAttr || HTMLElement::isURLAttribute(attribute); 5120} 5121 5122void HTMLMediaElement::setShouldDelayLoadEvent(bool shouldDelay) 5123{ 5124 if (m_shouldDelayLoadEvent == shouldDelay) 5125 return; 5126 5127 LOG(Media, "HTMLMediaElement::setShouldDelayLoadEvent(%s)", boolString(shouldDelay)); 5128 5129 m_shouldDelayLoadEvent = shouldDelay; 5130 if (shouldDelay) 5131 document().incrementLoadEventDelayCount(); 5132 else 5133 document().decrementLoadEventDelayCount(); 5134} 5135 5136 5137void HTMLMediaElement::getSitesInMediaCache(Vector<String>& sites) 5138{ 5139 MediaPlayer::getSitesInMediaCache(sites); 5140} 5141 5142void HTMLMediaElement::clearMediaCache() 5143{ 5144 MediaPlayer::clearMediaCache(); 5145} 5146 5147void HTMLMediaElement::clearMediaCacheForSite(const String& site) 5148{ 5149 MediaPlayer::clearMediaCacheForSite(site); 5150} 5151 5152void HTMLMediaElement::resetMediaEngines() 5153{ 5154 MediaPlayer::resetMediaEngines(); 5155} 5156 5157void HTMLMediaElement::privateBrowsingStateDidChange() 5158{ 5159 if (!m_player) 5160 return; 5161 5162 bool privateMode = document().page() && document().page()->usesEphemeralSession(); 5163 LOG(Media, "HTMLMediaElement::privateBrowsingStateDidChange(%s)", boolString(privateMode)); 5164 m_player->setPrivateBrowsingMode(privateMode); 5165} 5166 5167MediaControls* HTMLMediaElement::mediaControls() const 5168{ 5169#if ENABLE(MEDIA_CONTROLS_SCRIPT) 5170 return 0; 5171#else 5172 return toMediaControls(userAgentShadowRoot()->firstChild()); 5173#endif 5174} 5175 5176bool HTMLMediaElement::hasMediaControls() const 5177{ 5178#if ENABLE(MEDIA_CONTROLS_SCRIPT) 5179 return false; 5180#else 5181 5182 if (ShadowRoot* userAgent = userAgentShadowRoot()) { 5183 Node* node = userAgent->firstChild(); 5184 ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isMediaControls()); 5185 return node; 5186 } 5187 5188 return false; 5189#endif 5190} 5191 5192bool HTMLMediaElement::createMediaControls() 5193{ 5194#if ENABLE(MEDIA_CONTROLS_SCRIPT) 5195 ensureUserAgentShadowRoot(); 5196 return false; 5197#else 5198 if (hasMediaControls()) 5199 return true; 5200 5201 RefPtr<MediaControls> mediaControls = MediaControls::create(document()); 5202 if (!mediaControls) 5203 return false; 5204 5205 mediaControls->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this)); 5206 mediaControls->reset(); 5207 if (isFullscreen()) 5208 mediaControls->enteredFullscreen(); 5209 5210 ensureUserAgentShadowRoot().appendChild(mediaControls, ASSERT_NO_EXCEPTION); 5211 5212 if (!controls() || !inDocument()) 5213 mediaControls->hide(); 5214 5215 return true; 5216#endif 5217} 5218 5219void HTMLMediaElement::configureMediaControls() 5220{ 5221#if ENABLE(MEDIA_CONTROLS_SCRIPT) 5222 if (!controls() || !inDocument()) 5223 return; 5224 5225 ensureUserAgentShadowRoot(); 5226 return; 5227#endif 5228 5229 if (!controls() || !inDocument()) { 5230 if (hasMediaControls()) 5231 mediaControls()->hide(); 5232 return; 5233 } 5234 5235 if (!hasMediaControls() && !createMediaControls()) 5236 return; 5237 5238 mediaControls()->show(); 5239} 5240 5241#if ENABLE(VIDEO_TRACK) 5242void HTMLMediaElement::configureTextTrackDisplay(TextTrackVisibilityCheckType checkType) 5243{ 5244 ASSERT(m_textTracks); 5245 5246 if (m_processingPreferenceChange) 5247 return; 5248 5249 LOG(Media, "HTMLMediaElement::configureTextTrackDisplay"); 5250 5251 bool haveVisibleTextTrack = false; 5252 for (unsigned i = 0; i < m_textTracks->length(); ++i) { 5253 if (m_textTracks->item(i)->mode() == TextTrack::showingKeyword()) { 5254 haveVisibleTextTrack = true; 5255 break; 5256 } 5257 } 5258 5259 if (checkType == CheckTextTrackVisibility && m_haveVisibleTextTrack == haveVisibleTextTrack) { 5260 updateActiveTextTrackCues(currentTime()); 5261 return; 5262 } 5263 5264 m_haveVisibleTextTrack = haveVisibleTextTrack; 5265 m_closedCaptionsVisible = m_haveVisibleTextTrack; 5266 5267#if ENABLE(MEDIA_CONTROLS_SCRIPT) 5268 if (!m_haveVisibleTextTrack) 5269 return; 5270 5271 ensureUserAgentShadowRoot(); 5272 return; 5273#endif 5274 5275 if (!m_haveVisibleTextTrack && !hasMediaControls()) 5276 return; 5277 if (!hasMediaControls() && !createMediaControls()) 5278 return; 5279 5280 mediaControls()->changedClosedCaptionsVisibility(); 5281 5282 if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) { 5283 updateTextTrackDisplay(); 5284 updateActiveTextTrackCues(currentTime()); 5285 } 5286} 5287 5288void HTMLMediaElement::captionPreferencesChanged() 5289{ 5290 if (!isVideo()) 5291 return; 5292 5293 if (hasMediaControls()) 5294 mediaControls()->textTrackPreferencesChanged(); 5295 5296#if ENABLE(MEDIA_CONTROLS_SCRIPT) 5297 if (m_mediaControlsHost) 5298 m_mediaControlsHost->updateCaptionDisplaySizes(); 5299#endif 5300 5301 if (!document().page()) 5302 return; 5303 5304 CaptionUserPreferences::CaptionDisplayMode displayMode = document().page()->group().captionPreferences()->captionDisplayMode(); 5305 if (m_captionDisplayMode == displayMode) 5306 return; 5307 5308 m_captionDisplayMode = displayMode; 5309 setWebkitClosedCaptionsVisible(m_captionDisplayMode == CaptionUserPreferences::AlwaysOn); 5310} 5311 5312void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured(ReconfigureMode mode) 5313{ 5314 if (!m_textTracks) 5315 return; 5316 5317 LOG(Media, "HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured"); 5318 5319 // Mark all tracks as not "configured" so that configureTextTracks() 5320 // will reconsider which tracks to display in light of new user preferences 5321 // (e.g. default tracks should not be displayed if the user has turned off 5322 // captions and non-default tracks should be displayed based on language 5323 // preferences if the user has turned captions on). 5324 for (unsigned i = 0; i < m_textTracks->length(); ++i) { 5325 5326 RefPtr<TextTrack> textTrack = m_textTracks->item(i); 5327 String kind = textTrack->kind(); 5328 5329 if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword()) 5330 textTrack->setHasBeenConfigured(false); 5331 } 5332 5333 m_processingPreferenceChange = true; 5334 clearFlags(m_pendingActionFlags, ConfigureTextTracks); 5335 if (mode == Immediately) 5336 configureTextTracks(); 5337 else 5338 scheduleDelayedAction(ConfigureTextTracks); 5339} 5340 5341#endif 5342 5343void HTMLMediaElement::createMediaPlayer() 5344{ 5345#if ENABLE(WEB_AUDIO) 5346 if (m_audioSourceNode) 5347 m_audioSourceNode->lock(); 5348#endif 5349 5350#if ENABLE(MEDIA_SOURCE) 5351 if (m_mediaSource) 5352 m_mediaSource->close(); 5353#endif 5354 5355#if ENABLE(VIDEO_TRACK) 5356 forgetResourceSpecificTracks(); 5357#endif 5358 m_player = MediaPlayer::create(this); 5359 5360#if ENABLE(WEB_AUDIO) 5361 if (m_audioSourceNode) { 5362 // When creating the player, make sure its AudioSourceProvider knows about the MediaElementAudioSourceNode. 5363 if (audioSourceProvider()) 5364 audioSourceProvider()->setClient(m_audioSourceNode); 5365 5366 m_audioSourceNode->unlock(); 5367 } 5368#endif 5369 5370#if ENABLE(IOS_AIRPLAY) 5371 if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) { 5372 m_mediaSession->setHasPlaybackTargetAvailabilityListeners(*this, true); 5373 enqueuePlaybackTargetAvailabilityChangedEvent(); // Ensure the event listener gets at least one event. 5374 } 5375#endif 5376} 5377 5378#if ENABLE(WEB_AUDIO) 5379void HTMLMediaElement::setAudioSourceNode(MediaElementAudioSourceNode* sourceNode) 5380{ 5381 m_audioSourceNode = sourceNode; 5382 5383 if (audioSourceProvider()) 5384 audioSourceProvider()->setClient(m_audioSourceNode); 5385} 5386 5387AudioSourceProvider* HTMLMediaElement::audioSourceProvider() 5388{ 5389 if (m_player) 5390 return m_player->audioSourceProvider(); 5391 5392 return 0; 5393} 5394#endif 5395 5396const String& HTMLMediaElement::mediaGroup() const 5397{ 5398 return m_mediaGroup; 5399} 5400 5401void HTMLMediaElement::setMediaGroup(const String& group) 5402{ 5403 if (m_mediaGroup == group) 5404 return; 5405 m_mediaGroup = group; 5406 5407 // When a media element is created with a mediagroup attribute, and when a media element's mediagroup 5408 // attribute is set, changed, or removed, the user agent must run the following steps: 5409 // 1. Let m [this] be the media element in question. 5410 // 2. Let m have no current media controller, if it currently has one. 5411 setController(0); 5412 5413 // 3. If m's mediagroup attribute is being removed, then abort these steps. 5414 if (group.isNull() || group.isEmpty()) 5415 return; 5416 5417 // 4. If there is another media element whose Document is the same as m's Document (even if one or both 5418 // of these elements are not actually in the Document), 5419 HashSet<HTMLMediaElement*> elements = documentToElementSetMap().get(&document()); 5420 for (HashSet<HTMLMediaElement*>::iterator i = elements.begin(); i != elements.end(); ++i) { 5421 if (*i == this) 5422 continue; 5423 5424 // and which also has a mediagroup attribute, and whose mediagroup attribute has the same value as 5425 // the new value of m's mediagroup attribute, 5426 if ((*i)->mediaGroup() == group) { 5427 // then let controller be that media element's current media controller. 5428 setController((*i)->controller()); 5429 return; 5430 } 5431 } 5432 5433 // Otherwise, let controller be a newly created MediaController. 5434 setController(MediaController::create(document())); 5435} 5436 5437MediaController* HTMLMediaElement::controller() const 5438{ 5439 return m_mediaController.get(); 5440} 5441 5442void HTMLMediaElement::setController(PassRefPtr<MediaController> controller) 5443{ 5444 if (m_mediaController) 5445 m_mediaController->removeMediaElement(this); 5446 5447 m_mediaController = controller; 5448 5449 if (m_mediaController) 5450 m_mediaController->addMediaElement(this); 5451 5452 if (hasMediaControls()) 5453 mediaControls()->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this)); 5454} 5455 5456void HTMLMediaElement::updateMediaController() 5457{ 5458 if (m_mediaController) 5459 m_mediaController->reportControllerState(); 5460} 5461 5462bool HTMLMediaElement::isBlocked() const 5463{ 5464 // A media element is a blocked media element if its readyState attribute is in the 5465 // HAVE_NOTHING state, the HAVE_METADATA state, or the HAVE_CURRENT_DATA state, 5466 if (m_readyState <= HAVE_CURRENT_DATA) 5467 return true; 5468 5469 // or if the element has paused for user interaction. 5470 return pausedForUserInteraction(); 5471} 5472 5473bool HTMLMediaElement::isBlockedOnMediaController() const 5474{ 5475 if (!m_mediaController) 5476 return false; 5477 5478 // A media element is blocked on its media controller if the MediaController is a blocked 5479 // media controller, 5480 if (m_mediaController->isBlocked()) 5481 return true; 5482 5483 // or if its media controller position is either before the media resource's earliest possible 5484 // position relative to the MediaController's timeline or after the end of the media resource 5485 // relative to the MediaController's timeline. 5486 double mediaControllerPosition = m_mediaController->currentTime(); 5487 if (mediaControllerPosition < 0 || mediaControllerPosition > duration()) 5488 return true; 5489 5490 return false; 5491} 5492 5493void HTMLMediaElement::prepareMediaFragmentURI() 5494{ 5495 MediaFragmentURIParser fragmentParser(m_currentSrc); 5496 double dur = duration(); 5497 5498 double start = fragmentParser.startTime(); 5499 if (start != MediaFragmentURIParser::invalidTimeValue() && start > 0) { 5500 m_fragmentStartTime = start; 5501 if (m_fragmentStartTime > dur) 5502 m_fragmentStartTime = dur; 5503 } else 5504 m_fragmentStartTime = MediaPlayer::invalidTime(); 5505 5506 double end = fragmentParser.endTime(); 5507 if (end != MediaFragmentURIParser::invalidTimeValue() && end > 0 && end > m_fragmentStartTime) { 5508 m_fragmentEndTime = end; 5509 if (m_fragmentEndTime > dur) 5510 m_fragmentEndTime = dur; 5511 } else 5512 m_fragmentEndTime = MediaPlayer::invalidTime(); 5513 5514 if (m_fragmentStartTime != MediaPlayer::invalidTime() && m_readyState < HAVE_FUTURE_DATA) 5515 prepareToPlay(); 5516} 5517 5518void HTMLMediaElement::applyMediaFragmentURI() 5519{ 5520 if (m_fragmentStartTime != MediaPlayer::invalidTime()) { 5521 m_sentEndEvent = false; 5522 seek(m_fragmentStartTime); 5523 } 5524} 5525 5526void HTMLMediaElement::updateSleepDisabling() 5527{ 5528 if (!shouldDisableSleep() && m_sleepDisabler) 5529 m_sleepDisabler = nullptr; 5530 else if (shouldDisableSleep() && !m_sleepDisabler) 5531 m_sleepDisabler = DisplaySleepDisabler::create("com.apple.WebCore: HTMLMediaElement playback"); 5532} 5533 5534bool HTMLMediaElement::shouldDisableSleep() const 5535{ 5536#if !PLATFORM(COCOA) 5537 return false; 5538#endif 5539 5540 if (m_elementIsHidden) 5541 return false; 5542 5543 return m_player && !m_player->paused() && hasVideo() && hasAudio() && !loop(); 5544} 5545 5546String HTMLMediaElement::mediaPlayerReferrer() const 5547{ 5548 Frame* frame = document().frame(); 5549 if (!frame) 5550 return String(); 5551 5552 return SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), m_currentSrc, frame->loader().outgoingReferrer()); 5553} 5554 5555String HTMLMediaElement::mediaPlayerUserAgent() const 5556{ 5557 Frame* frame = document().frame(); 5558 if (!frame) 5559 return String(); 5560 5561 return frame->loader().userAgent(m_currentSrc); 5562 5563} 5564 5565#if ENABLE(AVF_CAPTIONS) 5566Vector<RefPtr<PlatformTextTrack>> HTMLMediaElement::outOfBandTrackSources() 5567{ 5568 Vector<RefPtr<PlatformTextTrack>> outOfBandTrackSources; 5569 for (auto& trackElement : childrenOfType<HTMLTrackElement>(*this)) { 5570 5571 if (!trackElement.fastHasAttribute(srcAttr)) 5572 continue; 5573 5574 URL url = trackElement.getNonEmptyURLAttribute(srcAttr); 5575 if (url.isEmpty()) 5576 continue; 5577 5578 if (!document().contentSecurityPolicy()->allowMediaFromSource(url)) 5579 continue; 5580 5581 PlatformTextTrack::TrackKind platformKind = PlatformTextTrack::Caption; 5582 if (trackElement.kind() == TextTrack::captionsKeyword()) 5583 platformKind = PlatformTextTrack::Caption; 5584 else if (trackElement.kind() == TextTrack::subtitlesKeyword()) 5585 platformKind = PlatformTextTrack::Subtitle; 5586 else if (trackElement.kind() == TextTrack::descriptionsKeyword()) 5587 platformKind = PlatformTextTrack::Description; 5588 else if (trackElement.kind() == TextTrack::forcedKeyword()) 5589 platformKind = PlatformTextTrack::Forced; 5590 else 5591 continue; 5592 5593 const AtomicString& mode = trackElement.track()->mode(); 5594 5595 PlatformTextTrack::TrackMode platformMode = PlatformTextTrack::Disabled; 5596 if (TextTrack::hiddenKeyword() == mode) 5597 platformMode = PlatformTextTrack::Hidden; 5598 else if (TextTrack::disabledKeyword() == mode) 5599 platformMode = PlatformTextTrack::Disabled; 5600 else if (TextTrack::showingKeyword() == mode) 5601 platformMode = PlatformTextTrack::Showing; 5602 5603 outOfBandTrackSources.append(PlatformTextTrack::createOutOfBand(trackElement.label(), trackElement.srclang(), url.string(), platformMode, platformKind, trackElement.track()->uniqueId(), trackElement.isDefault())); 5604 } 5605 5606 return outOfBandTrackSources; 5607} 5608#endif 5609 5610MediaPlayerClient::CORSMode HTMLMediaElement::mediaPlayerCORSMode() const 5611{ 5612 if (!fastHasAttribute(HTMLNames::crossoriginAttr)) 5613 return Unspecified; 5614 if (equalIgnoringCase(fastGetAttribute(HTMLNames::crossoriginAttr), "use-credentials")) 5615 return UseCredentials; 5616 return Anonymous; 5617} 5618 5619bool HTMLMediaElement::mediaPlayerNeedsSiteSpecificHacks() const 5620{ 5621 Settings* settings = document().settings(); 5622 return settings && settings->needsSiteSpecificQuirks(); 5623} 5624 5625String HTMLMediaElement::mediaPlayerDocumentHost() const 5626{ 5627 return document().url().host(); 5628} 5629 5630void HTMLMediaElement::mediaPlayerEnterFullscreen() 5631{ 5632 enterFullscreen(); 5633} 5634 5635void HTMLMediaElement::mediaPlayerExitFullscreen() 5636{ 5637 exitFullscreen(); 5638} 5639 5640bool HTMLMediaElement::mediaPlayerIsFullscreen() const 5641{ 5642 return isFullscreen(); 5643} 5644 5645bool HTMLMediaElement::mediaPlayerIsFullscreenPermitted() const 5646{ 5647 return m_mediaSession->fullscreenPermitted(*this); 5648} 5649 5650bool HTMLMediaElement::mediaPlayerIsVideo() const 5651{ 5652 return isVideo(); 5653} 5654 5655LayoutRect HTMLMediaElement::mediaPlayerContentBoxRect() const 5656{ 5657 if (renderer()) 5658 return renderer()->enclosingBox().contentBoxRect(); 5659 return LayoutRect(); 5660} 5661 5662void HTMLMediaElement::mediaPlayerSetSize(const IntSize& size) 5663{ 5664 setIntegralAttribute(widthAttr, size.width()); 5665 setIntegralAttribute(heightAttr, size.height()); 5666} 5667 5668void HTMLMediaElement::mediaPlayerPause() 5669{ 5670 pause(); 5671} 5672 5673void HTMLMediaElement::mediaPlayerPlay() 5674{ 5675 play(); 5676} 5677 5678bool HTMLMediaElement::mediaPlayerPlatformVolumeConfigurationRequired() const 5679{ 5680 return !m_volumeInitialized; 5681} 5682 5683bool HTMLMediaElement::mediaPlayerIsPaused() const 5684{ 5685 return paused(); 5686} 5687 5688bool HTMLMediaElement::mediaPlayerIsLooping() const 5689{ 5690 return loop(); 5691} 5692 5693HostWindow* HTMLMediaElement::mediaPlayerHostWindow() 5694{ 5695 return mediaPlayerOwningDocument()->view()->hostWindow(); 5696} 5697 5698IntRect HTMLMediaElement::mediaPlayerWindowClipRect() 5699{ 5700 return mediaPlayerOwningDocument()->view()->windowClipRect(); 5701} 5702 5703CachedResourceLoader* HTMLMediaElement::mediaPlayerCachedResourceLoader() 5704{ 5705 return mediaPlayerOwningDocument()->cachedResourceLoader(); 5706} 5707 5708bool HTMLMediaElement::mediaPlayerShouldWaitForResponseToAuthenticationChallenge(const AuthenticationChallenge& challenge) 5709{ 5710 Frame* frame = document().frame(); 5711 if (!frame) 5712 return false; 5713 5714 Page* page = frame->page(); 5715 if (!page) 5716 return false; 5717 5718 ResourceRequest request(m_currentSrc); 5719 ResourceLoadNotifier& notifier = frame->loader().notifier(); 5720 DocumentLoader* documentLoader = document().loader(); 5721 unsigned long identifier = page->progress().createUniqueIdentifier(); 5722 5723 notifier.assignIdentifierToInitialRequest(identifier, documentLoader, request); 5724 notifier.didReceiveAuthenticationChallenge(identifier, documentLoader, challenge); 5725 5726 return true; 5727} 5728 5729String HTMLMediaElement::mediaPlayerSourceApplicationIdentifier() const 5730{ 5731 if (Frame* frame = document().frame()) { 5732 if (NetworkingContext* networkingContext = frame->loader().networkingContext()) 5733 return networkingContext->sourceApplicationIdentifier(); 5734 } 5735 return emptyString(); 5736} 5737 5738#if PLATFORM(IOS) 5739String HTMLMediaElement::mediaPlayerNetworkInterfaceName() const 5740{ 5741 Settings* settings = document().settings(); 5742 if (!settings) 5743 return emptyString(); 5744 5745 return settings->networkInterfaceName(); 5746} 5747 5748bool HTMLMediaElement::mediaPlayerGetRawCookies(const URL& url, Vector<Cookie>& cookies) const 5749{ 5750 return getRawCookies(&document(), url, cookies); 5751} 5752#endif 5753 5754void HTMLMediaElement::removeBehaviorsRestrictionsAfterFirstUserGesture() 5755{ 5756 m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequireUserGestureForLoad); 5757 m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequireUserGestureForRateChange); 5758 m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequireUserGestureForFullscreen); 5759#if ENABLE(IOS_AIRPLAY) 5760 m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequireUserGestureToShowPlaybackTargetPicker); 5761#endif 5762} 5763 5764#if ENABLE(MEDIA_SOURCE) 5765RefPtr<VideoPlaybackQuality> HTMLMediaElement::getVideoPlaybackQuality() 5766{ 5767#if ENABLE(WEB_TIMING) 5768 DOMWindow* domWindow = document().domWindow(); 5769 Performance* performance = domWindow ? domWindow->performance() : nullptr; 5770 double now = performance ? performance->now() : 0; 5771#else 5772 DocumentLoader* loader = document().loader(); 5773 double now = loader ? 1000.0 * loader->timing()->monotonicTimeToZeroBasedDocumentTime(monotonicallyIncreasingTime()) : 0; 5774#endif 5775 5776 if (!m_player) 5777 return VideoPlaybackQuality::create(now, 0, 0, 0, 0); 5778 5779 return VideoPlaybackQuality::create(now, 5780 m_droppedVideoFrames + m_player->totalVideoFrames(), 5781 m_droppedVideoFrames + m_player->droppedVideoFrames(), 5782 m_player->corruptedVideoFrames(), 5783 m_player->totalFrameDelay()); 5784} 5785#endif 5786 5787#if ENABLE(MEDIA_CONTROLS_SCRIPT) 5788DOMWrapperWorld& HTMLMediaElement::ensureIsolatedWorld() 5789{ 5790 if (!m_isolatedWorld) 5791 m_isolatedWorld = DOMWrapperWorld::create(JSDOMWindow::commonVM()); 5792 return *m_isolatedWorld; 5793} 5794 5795bool HTMLMediaElement::ensureMediaControlsInjectedScript() 5796{ 5797 LOG(Media, "HTMLMediaElement::ensureMediaControlsInjectedScript"); 5798 Page* page = document().page(); 5799 if (!page) 5800 return false; 5801 5802 String mediaControlsScript = RenderTheme::themeForPage(page)->mediaControlsScript(); 5803 if (!mediaControlsScript.length()) 5804 return false; 5805 5806 DOMWrapperWorld& world = ensureIsolatedWorld(); 5807 ScriptController& scriptController = page->mainFrame().script(); 5808 JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world)); 5809 JSC::ExecState* exec = globalObject->globalExec(); 5810 JSC::JSLockHolder lock(exec); 5811 5812 JSC::JSValue functionValue = globalObject->get(exec, JSC::Identifier(exec, "createControls")); 5813 if (functionValue.isFunction()) 5814 return true; 5815 5816#ifndef NDEBUG 5817 // Setting a scriptURL allows the source to be debuggable in the inspector. 5818 URL scriptURL = URL(ParsedURLString, ASCIILiteral("mediaControlsScript")); 5819#else 5820 URL scriptURL; 5821#endif 5822 scriptController.evaluateInWorld(ScriptSourceCode(mediaControlsScript, scriptURL), world); 5823 if (exec->hadException()) { 5824 exec->clearException(); 5825 return false; 5826 } 5827 5828 return true; 5829} 5830 5831static void setPageScaleFactorProperty(JSC::ExecState* exec, JSC::JSValue controllerValue, float pageScaleFactor) 5832{ 5833 JSC::PutPropertySlot propertySlot(controllerValue); 5834 JSC::JSObject* controllerObject = controllerValue.toObject(exec); 5835 controllerObject->methodTable()->put(controllerObject, exec, JSC::Identifier(exec, "pageScaleFactor"), JSC::jsNumber(pageScaleFactor), propertySlot); 5836} 5837 5838void HTMLMediaElement::didAddUserAgentShadowRoot(ShadowRoot* root) 5839{ 5840 LOG(Media, "HTMLMediaElement::didAddUserAgentShadowRoot"); 5841 Page* page = document().page(); 5842 if (!page) 5843 return; 5844 5845 DOMWrapperWorld& world = ensureIsolatedWorld(); 5846 5847 if (!ensureMediaControlsInjectedScript()) 5848 return; 5849 5850 ScriptController& scriptController = page->mainFrame().script(); 5851 JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world)); 5852 JSC::ExecState* exec = globalObject->globalExec(); 5853 JSC::JSLockHolder lock(exec); 5854 5855 // The media controls script must provide a method with the following details. 5856 // Name: createControls 5857 // Parameters: 5858 // 1. The ShadowRoot element that will hold the controls. 5859 // 2. This object (and HTMLMediaElement). 5860 // 3. The MediaControlsHost object. 5861 // Return value: 5862 // A reference to the created media controller instance. 5863 5864 JSC::JSValue functionValue = globalObject->get(exec, JSC::Identifier(exec, "createControls")); 5865 if (functionValue.isUndefinedOrNull()) 5866 return; 5867 5868 if (!m_mediaControlsHost) 5869 m_mediaControlsHost = MediaControlsHost::create(this); 5870 5871 auto mediaJSWrapper = toJS(exec, globalObject, this); 5872 auto mediaControlsHostJSWrapper = toJS(exec, globalObject, m_mediaControlsHost.get()); 5873 5874 JSC::MarkedArgumentBuffer argList; 5875 argList.append(toJS(exec, globalObject, root)); 5876 argList.append(mediaJSWrapper); 5877 argList.append(mediaControlsHostJSWrapper); 5878 5879 JSC::JSObject* function = functionValue.toObject(exec); 5880 JSC::CallData callData; 5881 JSC::CallType callType = function->methodTable()->getCallData(function, callData); 5882 if (callType == JSC::CallTypeNone) 5883 return; 5884 5885 JSC::JSValue controllerValue = JSC::call(exec, function, callType, callData, globalObject, argList); 5886 JSC::JSObject* controllerObject = JSC::jsDynamicCast<JSC::JSObject*>(controllerValue); 5887 if (!controllerObject) 5888 return; 5889 5890 // Connect the Media, MediaControllerHost, and Controller so the GC knows about their relationship 5891 JSC::JSObject* mediaJSWrapperObject = mediaJSWrapper.toObject(exec); 5892 JSC::Identifier controlsHost(&exec->vm(), "controlsHost"); 5893 5894 ASSERT(!mediaJSWrapperObject->hasProperty(exec, controlsHost)); 5895 5896 mediaJSWrapperObject->putDirect(exec->vm(), controlsHost, mediaControlsHostJSWrapper, JSC::DontDelete | JSC::DontEnum | JSC::ReadOnly); 5897 5898 JSC::JSObject* mediaControlsHostJSWrapperObject = JSC::jsDynamicCast<JSC::JSObject*>(mediaControlsHostJSWrapper); 5899 if (!mediaControlsHostJSWrapperObject) 5900 return; 5901 5902 JSC::Identifier controller(&exec->vm(), "controller"); 5903 5904 ASSERT(!controllerObject->hasProperty(exec, controller)); 5905 5906 mediaControlsHostJSWrapperObject->putDirect(exec->vm(), controller, controllerValue, JSC::DontDelete | JSC::DontEnum | JSC::ReadOnly); 5907 5908 setPageScaleFactorProperty(exec, controllerValue, page->pageScaleFactor()); 5909 5910 if (exec->hadException()) 5911 exec->clearException(); 5912} 5913 5914void HTMLMediaElement::setMediaControlsDependOnPageScaleFactor(bool dependsOnPageScale) 5915{ 5916 LOG(Media, "MediaElement::setMediaControlsDependPageScaleFactor = %s", boolString(dependsOnPageScale)); 5917 if (m_mediaControlsDependOnPageScaleFactor == dependsOnPageScale) 5918 return; 5919 5920 m_mediaControlsDependOnPageScaleFactor = dependsOnPageScale; 5921 5922 if (m_mediaControlsDependOnPageScaleFactor) 5923 document().registerForPageScaleFactorChangedCallbacks(this); 5924 else 5925 document().unregisterForPageScaleFactorChangedCallbacks(this); 5926} 5927 5928void HTMLMediaElement::pageScaleFactorChanged() 5929{ 5930 Page* page = document().page(); 5931 if (!page) 5932 return; 5933 5934 LOG(Media, "HTMLMediaElement::pageScaleFactorChanged = %f", page->pageScaleFactor()); 5935 DOMWrapperWorld& world = ensureIsolatedWorld(); 5936 ScriptController& scriptController = page->mainFrame().script(); 5937 JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world)); 5938 JSC::ExecState* exec = globalObject->globalExec(); 5939 JSC::JSLockHolder lock(exec); 5940 5941 JSC::JSValue controllerValue = controllerJSValue(*exec, *globalObject, *this); 5942 5943 setPageScaleFactorProperty(exec, controllerValue, page->pageScaleFactor()); 5944} 5945#endif // ENABLE(MEDIA_CONTROLS_SCRIPT) 5946 5947unsigned long long HTMLMediaElement::fileSize() const 5948{ 5949 if (m_player) 5950 return m_player->fileSize(); 5951 5952 return 0; 5953} 5954 5955MediaSession::MediaType HTMLMediaElement::mediaType() const 5956{ 5957 if (m_player && m_readyState >= HAVE_METADATA) 5958 return hasVideo() ? MediaSession::Video : MediaSession::Audio; 5959 5960 if (hasTagName(HTMLNames::videoTag)) 5961 return MediaSession::Video; 5962 5963 return MediaSession::Audio; 5964} 5965 5966MediaSession::MediaType HTMLMediaElement::presentationType() const 5967{ 5968 if (hasTagName(HTMLNames::videoTag)) 5969 return MediaSession::Video; 5970 5971 return MediaSession::Audio; 5972} 5973 5974#if ENABLE(MEDIA_SOURCE) 5975size_t HTMLMediaElement::maximumSourceBufferSize(const SourceBuffer& buffer) const 5976{ 5977 return m_mediaSession->maximumMediaSourceBufferSize(buffer); 5978} 5979#endif 5980 5981void HTMLMediaElement::pausePlayback() 5982{ 5983 LOG(Media, "HTMLMediaElement::pausePlayback - paused = %s", boolString(paused())); 5984 if (!paused()) 5985 pause(); 5986} 5987 5988void HTMLMediaElement::resumePlayback() 5989{ 5990 LOG(Media, "HTMLMediaElement::resumePlayback - paused = %s", boolString(paused())); 5991 if (paused()) 5992 play(); 5993} 5994 5995String HTMLMediaElement::mediaSessionTitle() const 5996{ 5997 if (fastHasAttribute(titleAttr)) 5998 return fastGetAttribute(titleAttr); 5999 6000 return m_currentSrc; 6001} 6002 6003void HTMLMediaElement::didReceiveRemoteControlCommand(MediaSession::RemoteControlCommandType command) 6004{ 6005 LOG(Media, "HTMLMediaElement::didReceiveRemoteControlCommand(%i)", static_cast<int>(command)); 6006 6007 switch (command) { 6008 case MediaSession::PlayCommand: 6009 play(); 6010 break; 6011 case MediaSession::PauseCommand: 6012 pause(); 6013 break; 6014 case MediaSession::TogglePlayPauseCommand: 6015 canPlay() ? play() : pause(); 6016 break; 6017 case MediaSession::BeginSeekingBackwardCommand: 6018 beginScanning(Backward); 6019 break; 6020 case MediaSession::BeginSeekingForwardCommand: 6021 beginScanning(Forward); 6022 break; 6023 case MediaSession::EndSeekingBackwardCommand: 6024 case MediaSession::EndSeekingForwardCommand: 6025 endScanning(); 6026 break; 6027 default: 6028 { } // Do nothing 6029 } 6030} 6031 6032bool HTMLMediaElement::overrideBackgroundPlaybackRestriction() const 6033{ 6034#if ENABLE(IOS_AIRPLAY) 6035 if (m_player && m_player->isCurrentPlaybackTargetWireless()) 6036 return true; 6037#endif 6038 return false; 6039} 6040 6041bool HTMLMediaElement::doesHaveAttribute(const AtomicString& attribute, AtomicString* value) const 6042{ 6043 QualifiedName attributeName(nullAtom, attribute, nullAtom); 6044 6045 AtomicString elementValue = fastGetAttribute(attributeName); 6046 if (elementValue.isNull()) 6047 return false; 6048 6049 if (Settings* settings = document().settings()) { 6050 if (attributeName == HTMLNames::x_itunes_inherit_uri_query_componentAttr && !settings->enableInheritURIQueryComponent()) 6051 return false; 6052 } 6053 6054 if (value) 6055 *value = elementValue; 6056 6057 return true; 6058} 6059 6060void HTMLMediaElement::setShouldBufferData(bool shouldBuffer) 6061{ 6062 if (m_player) 6063 return m_player->setShouldBufferData(shouldBuffer); 6064} 6065 6066} 6067 6068#endif 6069