diff options
Diffstat (limited to 'Source/WebCore/html/HTMLMediaElement.cpp')
-rw-r--r-- | Source/WebCore/html/HTMLMediaElement.cpp | 4158 |
1 files changed, 2921 insertions, 1237 deletions
diff --git a/Source/WebCore/html/HTMLMediaElement.cpp b/Source/WebCore/html/HTMLMediaElement.cpp index ea0fe4c4c..3d3f8ab76 100644 --- a/Source/WebCore/html/HTMLMediaElement.cpp +++ b/Source/WebCore/html/HTMLMediaElement.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2007-2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,17 +10,17 @@ * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" @@ -30,83 +30,77 @@ #include "ApplicationCacheHost.h" #include "ApplicationCacheResource.h" #include "Attribute.h" -#include "Chrome.h" +#include "CSSPropertyNames.h" +#include "CSSValueKeywords.h" #include "ChromeClient.h" #include "ClientRect.h" #include "ClientRectList.h" #include "ContentSecurityPolicy.h" #include "ContentType.h" -#include "CSSPropertyNames.h" -#include "CSSValueKeywords.h" +#include "CookieJar.h" +#include "DiagnosticLoggingClient.h" #include "DiagnosticLoggingKeys.h" +#include "DisplaySleepDisabler.h" +#include "Document.h" #include "DocumentLoader.h" -#include "ElementShadow.h" -#include "Event.h" +#include "ElementIterator.h" #include "EventNames.h" -#include "ExceptionCode.h" #include "ExceptionCodePlaceholder.h" -#include "Frame.h" #include "FrameLoader.h" #include "FrameLoaderClient.h" #include "FrameView.h" -#include "HTMLDocument.h" -#include "HTMLNames.h" #include "HTMLSourceElement.h" #include "HTMLVideoElement.h" +#include "JSHTMLMediaElement.h" #include "Language.h" #include "Logging.h" +#include "MIMETypeRegistry.h" +#include "MainFrame.h" #include "MediaController.h" #include "MediaControls.h" #include "MediaDocument.h" #include "MediaError.h" #include "MediaFragmentURIParser.h" -#include "MediaKeyError.h" #include "MediaKeyEvent.h" #include "MediaList.h" #include "MediaPlayer.h" #include "MediaQueryEvaluator.h" -#include "MouseEvent.h" -#include "MIMETypeRegistry.h" -#include "NodeRenderingContext.h" -#include "Page.h" -#include "PageActivityAssertionToken.h" +#include "MediaResourceLoader.h" +#include "MemoryPressureHandler.h" +#include "NetworkingContext.h" #include "PageGroup.h" +#include "PageThrottler.h" +#include "PlatformMediaSessionManager.h" +#include "ProgressTracker.h" +#include "RenderLayerCompositor.h" #include "RenderVideo.h" #include "RenderView.h" +#include "ResourceLoadInfo.h" #include "ScriptController.h" -#include "ScriptEventListener.h" +#include "ScriptSourceCode.h" #include "SecurityPolicy.h" +#include "SessionID.h" #include "Settings.h" #include "ShadowRoot.h" #include "TimeRanges.h" +#include "UserContentController.h" #include <limits> +#include <runtime/Uint8Array.h> #include <wtf/CurrentTime.h> #include <wtf/MathExtras.h> -#include <wtf/NonCopyingSort.h> -#include <wtf/Uint8Array.h> +#include <wtf/Ref.h> #include <wtf/text/CString.h> -#if USE(ACCELERATED_COMPOSITING) -#include "RenderLayerCompositor.h" -#endif - -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) -#include "RenderEmbeddedObject.h" -#include "Widget.h" -#endif - #if ENABLE(VIDEO_TRACK) #include "AudioTrackList.h" -#include "AudioTrackPrivate.h" -#include "CaptionUserPreferences.h" #include "HTMLTrackElement.h" -#include "InbandTextTrack.h" +#include "InbandGenericTextTrack.h" #include "InbandTextTrackPrivate.h" +#include "InbandWebVTTTextTrack.h" #include "RuntimeEnabledFeatures.h" #include "TextTrackCueList.h" #include "TextTrackList.h" #include "VideoTrackList.h" -#include "VideoTrackPrivate.h" #endif #if ENABLE(WEB_AUDIO) @@ -114,16 +108,33 @@ #include "MediaElementAudioSourceNode.h" #endif -#if PLATFORM(MAC) -#include "DisplaySleepDisabler.h" +#if PLATFORM(IOS) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)) +#include "WebVideoFullscreenInterface.h" +#endif + +#if PLATFORM(IOS) +#include "RuntimeApplicationChecksIOS.h" +#include "WebVideoFullscreenInterfaceAVKit.h" +#endif + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) +#include "WebKitPlaybackTargetAvailabilityEvent.h" +#endif + +#if ENABLE(MEDIA_SESSION) +#include "MediaSession.h" #endif #if ENABLE(MEDIA_SOURCE) +#include "DOMWindow.h" #include "MediaSource.h" -#include "MediaSourceRegistry.h" +#include "Performance.h" +#include "VideoPlaybackQuality.h" #endif #if ENABLE(MEDIA_STREAM) +#include "DOMURL.h" +#include "MediaStream.h" #include "MediaStreamRegistry.h" #endif @@ -132,18 +143,20 @@ #include "MediaKeys.h" #endif -#if USE(PLATFORM_TEXT_TRACK_MENU) -#include "PlatformTextTrack.h" +#if ENABLE(MEDIA_CONTROLS_SCRIPT) +#include "JSMediaControlsHost.h" +#include "MediaControlsHost.h" +#include "ScriptGlobalObject.h" +#include <bindings/ScriptObject.h> #endif -#if USE(AUDIO_SESSION) -#include "AudioSessionManager.h" -#endif - -using namespace std; - namespace WebCore { +static const double SeekRepeatDelay = 0.1; +static const double SeekTime = 0.2; +static const double ScanRepeatDelay = 1.5; +static const double ScanMaximumRate = 8; + static void setFlags(unsigned& value, unsigned flags) { value |= flags; @@ -155,7 +168,7 @@ static void clearFlags(unsigned& value, unsigned flags) } #if !LOG_DISABLED -static String urlForLoggingMedia(const KURL& url) +static String urlForLoggingMedia(const URL& url) { static const unsigned maximumURLLengthForLogging = 128; @@ -168,6 +181,30 @@ static const char* boolString(bool val) { return val ? "true" : "false"; } + +static String actionName(HTMLMediaElementEnums::DelayedActionType action) +{ + StringBuilder actionBuilder; + +#define CASE(_actionType) \ + if (action & (HTMLMediaElementEnums::_actionType)) { \ + if (!actionBuilder.isEmpty()) \ + actionBuilder.append(", "); \ + actionBuilder.append(#_actionType); \ + } \ + + CASE(LoadMediaResource); + CASE(ConfigureTextTracks); + CASE(TextTrackChangesNotification); + CASE(ConfigureTextTrackDisplay); + CASE(CheckPlaybackTargetCompatablity); + CASE(CheckMediaState); + + return actionBuilder.toString(); + +#undef CASE +} + #endif #ifndef LOG_MEDIA_EVENTS @@ -187,31 +224,35 @@ static const char* boolString(bool val) static const char* mediaSourceBlobProtocol = "blob"; #endif +#if ENABLE(MEDIA_STREAM) +// URL protocol used to signal that the media stream API is being used. +static const char* mediaStreamBlobProtocol = "blob"; +#endif + using namespace HTMLNames; -using namespace std; -typedef HashMap<Document*, HashSet<HTMLMediaElement*> > DocumentElementSetMap; +typedef HashMap<Document*, HashSet<HTMLMediaElement*>> DocumentElementSetMap; static DocumentElementSetMap& documentToElementSetMap() { - DEFINE_STATIC_LOCAL(DocumentElementSetMap, map, ()); + static NeverDestroyed<DocumentElementSetMap> map; return map; } -static void addElementToDocumentMap(HTMLMediaElement* element, Document* document) +static void addElementToDocumentMap(HTMLMediaElement& element, Document& document) { DocumentElementSetMap& map = documentToElementSetMap(); - HashSet<HTMLMediaElement*> set = map.take(document); - set.add(element); - map.add(document, set); + HashSet<HTMLMediaElement*> set = map.take(&document); + set.add(&element); + map.add(&document, set); } -static void removeElementFromDocumentMap(HTMLMediaElement* element, Document* document) +static void removeElementFromDocumentMap(HTMLMediaElement& element, Document& document) { DocumentElementSetMap& map = documentToElementSetMap(); - HashSet<HTMLMediaElement*> set = map.take(document); - set.remove(element); + HashSet<HTMLMediaElement*> set = map.take(&document); + set.remove(&element); if (!set.isEmpty()) - map.add(document, set); + map.add(&document, set); } #if ENABLE(ENCRYPTED_MEDIA) @@ -250,40 +291,96 @@ private: }; #endif -HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* document, bool createdByParser) +struct HTMLMediaElement::TrackGroup { + enum GroupKind { CaptionsAndSubtitles, Description, Chapter, Metadata, Other }; + + TrackGroup(GroupKind kind) + : visibleTrack(0) + , defaultTrack(0) + , kind(kind) + , hasSrcLang(false) + { + } + + Vector<RefPtr<TextTrack>> tracks; + RefPtr<TextTrack> visibleTrack; + RefPtr<TextTrack> defaultTrack; + GroupKind kind; + bool hasSrcLang; +}; + +HashSet<HTMLMediaElement*>& HTMLMediaElement::allMediaElements() +{ + static NeverDestroyed<HashSet<HTMLMediaElement*>> elements; + return elements; +} + +#if ENABLE(MEDIA_SESSION) +typedef HashMap<uint64_t, HTMLMediaElement*> IDToElementMap; + +static IDToElementMap& elementIDsToElements() +{ + static NeverDestroyed<IDToElementMap> map; + return map; +} + +HTMLMediaElement* HTMLMediaElement::elementWithID(uint64_t id) +{ + if (id == HTMLMediaElementInvalidID) + return nullptr; + + return elementIDsToElements().get(id); +} + +static uint64_t nextElementID() +{ + static uint64_t elementID = 0; + return ++elementID; +} +#endif + +HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document, bool createdByParser) : HTMLElement(tagName, document) - , ActiveDOMObject(document) - , m_loadTimer(this, &HTMLMediaElement::loadTimerFired) - , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired) - , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired) + , ActiveDOMObject(&document) + , m_pendingActionTimer(*this, &HTMLMediaElement::pendingActionTimerFired) + , m_progressEventTimer(*this, &HTMLMediaElement::progressEventTimerFired) + , m_playbackProgressTimer(*this, &HTMLMediaElement::playbackProgressTimerFired) + , m_scanTimer(*this, &HTMLMediaElement::scanTimerFired) + , m_seekTaskQueue(document) + , m_resizeTaskQueue(document) + , m_shadowDOMTaskQueue(document) , m_playedTimeRanges() - , m_asyncEventQueue(GenericEventQueue::create(this)) - , m_playbackRate(1.0f) - , m_defaultPlaybackRate(1.0f) + , m_asyncEventQueue(*this) + , m_requestedPlaybackRate(1) + , m_reportedPlaybackRate(1) + , m_defaultPlaybackRate(1) , m_webkitPreservesPitch(true) , m_networkState(NETWORK_EMPTY) , m_readyState(HAVE_NOTHING) , m_readyStateMaximum(HAVE_NOTHING) , m_volume(1.0f) , m_volumeInitialized(false) - , m_lastSeekTime(0) - , m_previousProgressTime(numeric_limits<double>::max()) - , m_lastTimeUpdateEventWallTime(0) - , m_lastTimeUpdateEventMovieTime(numeric_limits<double>::max()) + , m_previousProgressTime(std::numeric_limits<double>::max()) + , m_clockTimeAtLastUpdateEvent(0) + , m_lastTimeUpdateEventMovieTime(MediaTime::positiveInfiniteTime()) , m_loadState(WaitingForSource) -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) - , m_proxyWidget(0) + , m_videoFullscreenMode(VideoFullscreenModeNone) +#if PLATFORM(IOS) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)) + , m_videoFullscreenGravity(MediaPlayer::VideoGravityResizeAspect) #endif - , m_restrictions(RequireUserGestureForFullscreenRestriction | RequirePageConsentToLoadMediaRestriction) , m_preload(MediaPlayer::Auto) , m_displayMode(Unknown) , m_processingMediaPlayerCallback(0) - , m_cachedTime(MediaPlayer::invalidTime()) - , m_cachedTimeWallClockUpdateTime(0) - , m_minimumWallClockTimeToCacheMediaTime(0) - , m_fragmentStartTime(MediaPlayer::invalidTime()) - , m_fragmentEndTime(MediaPlayer::invalidTime()) +#if ENABLE(MEDIA_SOURCE) + , m_droppedVideoFrames(0) +#endif + , m_clockTimeAtLastCachedTimeUpdate(0) + , m_minimumClockTimeToUpdateCachedTime(0) , m_pendingActionFlags(0) + , m_actionAfterScan(Nothing) + , m_scanType(Scan) + , m_scanDirection(Forward) + , m_firstTimePlaying(true) , m_playing(false) , m_isWaitingUntilMediaCanStart(false) , m_shouldDelayLoadEvent(false) @@ -291,28 +388,30 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* docum , m_inActiveDocument(true) , m_autoplaying(true) , m_muted(false) + , m_explicitlyMuted(false) + , m_initiallyMuted(false) , m_paused(true) , m_seeking(false) , m_sentStalledEvent(false) , m_sentEndEvent(false) , m_pausedInternal(false) , m_sendProgressEvents(true) - , m_isFullscreen(false) , m_closedCaptionsVisible(false) , m_webkitLegacyClosedCaptionOverride(false) -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) - , m_needWidgetUpdate(false) -#endif - , m_dispatchingCanPlayEvent(false) - , m_loadInitiatedByUserGesture(false) , m_completelyLoaded(false) , m_havePreparedToPlay(false) , m_parsingInProgress(createdByParser) + , m_elementIsHidden(document.hidden()) + , m_creatingControls(false) +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + , m_mediaControlsDependOnPageScaleFactor(false) + , m_haveSetUpCaptionContainer(false) +#endif #if ENABLE(VIDEO_TRACK) , m_tracksAreReady(true) , m_haveVisibleTextTrack(false) , m_processingPreferenceChange(false) - , m_lastTextTrackUpdateTime(-1) + , m_lastTextTrackUpdateTime(MediaTime(-1, 1)) , m_captionDisplayMode(CaptionUserPreferences::Automatic) , m_audioTracks(0) , m_textTracks(0) @@ -322,41 +421,80 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* docum #if ENABLE(WEB_AUDIO) , m_audioSourceNode(0) #endif -#if USE(AUDIO_SESSION) - , m_audioSessionManagerToken(AudioSessionManagerToken::create(tagName == videoTag ? AudioSessionManager::Video : AudioSessionManager::Audio)) -#endif + , m_mediaSession(std::make_unique<MediaElementSession>(*this)) , m_reportedExtraMemoryCost(0) +#if ENABLE(MEDIA_STREAM) + , m_mediaStreamSrcObject(nullptr) +#endif { - LOG(Media, "HTMLMediaElement::HTMLMediaElement"); - document->registerForMediaVolumeCallbacks(this); - document->registerForPrivateBrowsingStateChangedCallbacks(this); + allMediaElements().add(this); - if (document->settings() && document->settings()->mediaPlaybackRequiresUserGesture()) { - addBehaviorRestriction(RequireUserGestureForRateChangeRestriction); - addBehaviorRestriction(RequireUserGestureForLoadRestriction); + LOG(Media, "HTMLMediaElement::HTMLMediaElement(%p)", this); + setHasCustomStyleResolveCallbacks(); + + m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForFullscreen); + m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePageConsentToLoadMedia); +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToAutoplayToExternalDevice); +#endif + + // FIXME: We should clean up and look to better merge the iOS and non-iOS code below. + Settings* settings = document.settings(); +#if !PLATFORM(IOS) + if (settings && settings->requiresUserGestureForMediaPlayback()) { + m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForRateChange); + m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForLoad); } +#else + m_sendProgressEvents = false; + if (!settings || settings->requiresUserGestureForMediaPlayback()) { + // Allow autoplay in a MediaDocument that is not in an iframe. + if (document.ownerElement() || !document.isMediaDocument()) + m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForRateChange); +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToShowPlaybackTargetPicker); +#endif + } else { + // Relax RequireUserGestureForFullscreen when requiresUserGestureForMediaPlayback is not set: + m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequireUserGestureForFullscreen); + } + if (settings && settings->invisibleAutoplayNotPermitted()) + m_mediaSession->addBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted); +#endif // !PLATFORM(IOS) + + if (settings && settings->audioPlaybackRequiresUserGesture() && settings->requiresUserGestureForMediaPlayback()) + m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForAudioRateChange); - setHasCustomStyleCallbacks(); - addElementToDocumentMap(this, document); + if (!settings || !settings->mediaDataLoadsAutomatically()) + m_mediaSession->addBehaviorRestriction(MediaElementSession::AutoPreloadingNotPermitted); #if ENABLE(VIDEO_TRACK) - document->registerForCaptionPreferencesChangedCallbacks(this); + if (document.page()) + m_captionDisplayMode = document.page()->group().captionPreferences().captionDisplayMode(); #endif + +#if ENABLE(MEDIA_SESSION) + m_elementID = nextElementID(); + elementIDsToElements().add(m_elementID, this); + + setSessionInternal(document.defaultMediaSession()); +#endif + + registerWithDocument(document); } HTMLMediaElement::~HTMLMediaElement() { - LOG(Media, "HTMLMediaElement::~HTMLMediaElement"); + LOG(Media, "HTMLMediaElement::~HTMLMediaElement(%p)", this); - m_asyncEventQueue->close(); + allMediaElements().remove(this); + + m_asyncEventQueue.close(); - if (m_isWaitingUntilMediaCanStart) - document()->removeMediaCanStartListener(this); setShouldDelayLoadEvent(false); - document()->unregisterForMediaVolumeCallbacks(this); - document()->unregisterForPrivateBrowsingStateChangedCallbacks(this); + unregisterWithDocument(document()); + #if ENABLE(VIDEO_TRACK) - document()->unregisterForCaptionPreferencesChangedCallbacks(this); if (m_audioTracks) { m_audioTracks->clearElement(); for (unsigned i = 0; i < m_audioTracks->length(); ++i) @@ -375,51 +513,144 @@ HTMLMediaElement::~HTMLMediaElement() } #endif +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) { + m_hasPlaybackTargetAvailabilityListeners = false; + m_mediaSession->setHasPlaybackTargetAvailabilityListeners(*this, false); + updateMediaState(); + } +#endif + if (m_mediaController) { m_mediaController->removeMediaElement(this); - m_mediaController = 0; + m_mediaController = nullptr; } #if ENABLE(MEDIA_SOURCE) - setSourceState(MediaSource::closedKeyword()); + closeMediaSource(); #endif #if ENABLE(ENCRYPTED_MEDIA_V2) setMediaKeys(0); #endif - removeElementFromDocumentMap(this, document()); +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + if (m_isolatedWorld) + m_isolatedWorld->clearWrappers(); +#endif + +#if ENABLE(MEDIA_SESSION) + if (m_session) { + m_session->removeMediaElement(*this); + m_session = nullptr; + } + + elementIDsToElements().remove(m_elementID); +#endif + + m_seekTaskQueue.close(); m_completelyLoaded = true; - if (m_player) - m_player->clearMediaPlayerClient(); } -void HTMLMediaElement::didMoveToNewDocument(Document* oldDocument) +void HTMLMediaElement::registerWithDocument(Document& document) { - if (m_isWaitingUntilMediaCanStart) { - if (oldDocument) - oldDocument->removeMediaCanStartListener(this); - document()->addMediaCanStartListener(this); - } + m_mediaSession->registerWithDocument(document); + + if (m_isWaitingUntilMediaCanStart) + document.addMediaCanStartListener(this); + +#if !PLATFORM(IOS) + document.registerForMediaVolumeCallbacks(this); + document.registerForPrivateBrowsingStateChangedCallbacks(this); +#endif + + document.registerForVisibilityStateChangedCallbacks(this); + +#if ENABLE(VIDEO_TRACK) + if (m_requireCaptionPreferencesChangedCallbacks) + document.registerForCaptionPreferencesChangedCallbacks(this); +#endif + +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + if (m_mediaControlsDependOnPageScaleFactor) + document.registerForPageScaleFactorChangedCallbacks(this); +#endif + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + document.registerForDocumentSuspensionCallbacks(this); +#endif + + document.registerForAllowsMediaDocumentInlinePlaybackChangedCallbacks(*this); + + document.addAudioProducer(this); + addElementToDocumentMap(*this, document); +} + +void HTMLMediaElement::unregisterWithDocument(Document& document) +{ + m_mediaSession->unregisterWithDocument(document); + + if (m_isWaitingUntilMediaCanStart) + document.removeMediaCanStartListener(this); + +#if !PLATFORM(IOS) + document.unregisterForMediaVolumeCallbacks(this); + document.unregisterForPrivateBrowsingStateChangedCallbacks(this); +#endif + + document.unregisterForVisibilityStateChangedCallbacks(this); + +#if ENABLE(VIDEO_TRACK) + if (m_requireCaptionPreferencesChangedCallbacks) + document.unregisterForCaptionPreferencesChangedCallbacks(this); +#endif +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + if (m_mediaControlsDependOnPageScaleFactor) + document.unregisterForPageScaleFactorChangedCallbacks(this); +#endif + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + document.unregisterForDocumentSuspensionCallbacks(this); +#endif + + document.unregisterForAllowsMediaDocumentInlinePlaybackChangedCallbacks(*this); + + document.removeAudioProducer(this); + removeElementFromDocumentMap(*this, document); +} + +void HTMLMediaElement::didMoveToNewDocument(Document* oldDocument) +{ if (m_shouldDelayLoadEvent) { if (oldDocument) oldDocument->decrementLoadEventDelayCount(); - document()->incrementLoadEventDelayCount(); + document().incrementLoadEventDelayCount(); } - if (oldDocument) { - oldDocument->unregisterForMediaVolumeCallbacks(this); - removeElementFromDocumentMap(this, oldDocument); - } + if (oldDocument) + unregisterWithDocument(*oldDocument); - document()->registerForMediaVolumeCallbacks(this); - addElementToDocumentMap(this, document()); + registerWithDocument(document()); HTMLElement::didMoveToNewDocument(oldDocument); + updateShouldAutoplay(); } +#if ENABLE(WIRELESS_PLAYBACK_TARGET) +void HTMLMediaElement::prepareForDocumentSuspension() +{ + m_mediaSession->unregisterWithDocument(document()); +} + +void HTMLMediaElement::resumeFromDocumentSuspension() +{ + m_mediaSession->registerWithDocument(document()); + updateShouldAutoplay(); +} +#endif + bool HTMLMediaElement::hasCustomFocusLogic() const { return true; @@ -427,7 +658,7 @@ bool HTMLMediaElement::hasCustomFocusLogic() const bool HTMLMediaElement::supportsFocus() const { - if (ownerDocument()->isMediaDocument()) + if (document().isMediaDocument()) return false; // If no controls specified, we should still be able to focus the element if it has tabIndex. @@ -442,21 +673,26 @@ bool HTMLMediaElement::isMouseFocusable() const void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value) { if (name == srcAttr) { +#if PLATFORM(IOS) + // Note, unless the restriction on requiring user action has been removed, + // do not begin downloading data on iOS. + if (!value.isNull() && m_mediaSession->dataLoadingPermitted(*this)) +#else // Trigger a reload, as long as the 'src' attribute is present. - if (!value.isNull()) { + if (!value.isNull()) +#endif + { clearMediaPlayer(LoadMediaResource); scheduleDelayedAction(LoadMediaResource); } } else if (name == controlsAttr) configureMediaControls(); -#if PLATFORM(MAC) else if (name == loopAttr) - updateDisableSleep(); -#endif + updateSleepDisabling(); else if (name == preloadAttr) { - if (equalIgnoringCase(value, "none")) + if (equalLettersIgnoringASCIICase(value, "none")) m_preload = MediaPlayer::None; - else if (equalIgnoringCase(value, "metadata")) + else if (equalLettersIgnoringASCIICase(value, "metadata")) m_preload = MediaPlayer::MetaData; else { // The spec does not define an "invalid value default" but "auto" is suggested as the @@ -466,60 +702,10 @@ void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicStr // The attribute must be ignored if the autoplay attribute is present if (!autoplay() && m_player) - m_player->setPreload(m_preload); + m_player->setPreload(m_mediaSession->effectivePreloadForElement(*this)); } else if (name == mediagroupAttr) setMediaGroup(value); - else if (name == onabortAttr) - setAttributeEventListener(eventNames().abortEvent, createAttributeEventListener(this, name, value)); - else if (name == onbeforeloadAttr) - setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, name, value)); - else if (name == oncanplayAttr) - setAttributeEventListener(eventNames().canplayEvent, createAttributeEventListener(this, name, value)); - else if (name == oncanplaythroughAttr) - setAttributeEventListener(eventNames().canplaythroughEvent, createAttributeEventListener(this, name, value)); - else if (name == ondurationchangeAttr) - setAttributeEventListener(eventNames().durationchangeEvent, createAttributeEventListener(this, name, value)); - else if (name == onemptiedAttr) - setAttributeEventListener(eventNames().emptiedEvent, createAttributeEventListener(this, name, value)); - else if (name == onendedAttr) - setAttributeEventListener(eventNames().endedEvent, createAttributeEventListener(this, name, value)); - else if (name == onerrorAttr) - setAttributeEventListener(eventNames().errorEvent, createAttributeEventListener(this, name, value)); - else if (name == onloadeddataAttr) - setAttributeEventListener(eventNames().loadeddataEvent, createAttributeEventListener(this, name, value)); - else if (name == onloadedmetadataAttr) - setAttributeEventListener(eventNames().loadedmetadataEvent, createAttributeEventListener(this, name, value)); - else if (name == onloadstartAttr) - setAttributeEventListener(eventNames().loadstartEvent, createAttributeEventListener(this, name, value)); - else if (name == onpauseAttr) - setAttributeEventListener(eventNames().pauseEvent, createAttributeEventListener(this, name, value)); - else if (name == onplayAttr) - setAttributeEventListener(eventNames().playEvent, createAttributeEventListener(this, name, value)); - else if (name == onplayingAttr) - setAttributeEventListener(eventNames().playingEvent, createAttributeEventListener(this, name, value)); - else if (name == onprogressAttr) - setAttributeEventListener(eventNames().progressEvent, createAttributeEventListener(this, name, value)); - else if (name == onratechangeAttr) - setAttributeEventListener(eventNames().ratechangeEvent, createAttributeEventListener(this, name, value)); - else if (name == onseekedAttr) - setAttributeEventListener(eventNames().seekedEvent, createAttributeEventListener(this, name, value)); - else if (name == onseekingAttr) - setAttributeEventListener(eventNames().seekingEvent, createAttributeEventListener(this, name, value)); - else if (name == onstalledAttr) - setAttributeEventListener(eventNames().stalledEvent, createAttributeEventListener(this, name, value)); - else if (name == onsuspendAttr) - setAttributeEventListener(eventNames().suspendEvent, createAttributeEventListener(this, name, value)); - else if (name == ontimeupdateAttr) - setAttributeEventListener(eventNames().timeupdateEvent, createAttributeEventListener(this, name, value)); - else if (name == onvolumechangeAttr) - setAttributeEventListener(eventNames().volumechangeEvent, createAttributeEventListener(this, name, value)); - else if (name == onwaitingAttr) - setAttributeEventListener(eventNames().waitingEvent, createAttributeEventListener(this, name, value)); - else if (name == onwebkitbeginfullscreenAttr) - setAttributeEventListener(eventNames().webkitbeginfullscreenEvent, createAttributeEventListener(this, name, value)); - else if (name == onwebkitendfullscreenAttr) - setAttributeEventListener(eventNames().webkitendfullscreenEvent, createAttributeEventListener(this, name, value)); else HTMLElement::parseAttribute(name, value); } @@ -528,133 +714,126 @@ void HTMLMediaElement::finishParsingChildren() { HTMLElement::finishParsingChildren(); m_parsingInProgress = false; - -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) - document()->updateStyleIfNeeded(); - createMediaPlayerProxy(); -#endif #if ENABLE(VIDEO_TRACK) - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return; - - for (Node* node = firstChild(); node; node = node->nextSibling()) { - if (node->hasTagName(trackTag)) { - scheduleDelayedAction(ConfigureTextTracks); - break; - } - } + + if (descendantsOfType<HTMLTrackElement>(*this).first()) + scheduleDelayedAction(ConfigureTextTracks); #endif } -bool HTMLMediaElement::rendererIsNeeded(const NodeRenderingContext& context) +bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style) { -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) - UNUSED_PARAM(context); - Frame* frame = document()->frame(); - if (!frame) - return false; - - return true; -#else - return controls() ? HTMLElement::rendererIsNeeded(context) : false; -#endif + return controls() && HTMLElement::rendererIsNeeded(style); } -RenderObject* HTMLMediaElement::createRenderer(RenderArena* arena, RenderStyle*) +RenderPtr<RenderElement> HTMLMediaElement::createElementRenderer(Ref<RenderStyle>&& style, const RenderTreePosition&) { -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) - // Setup the renderer if we already have a proxy widget. - RenderEmbeddedObject* mediaRenderer = new (arena) RenderEmbeddedObject(this); - if (m_proxyWidget) { - mediaRenderer->setWidget(m_proxyWidget); - - if (Frame* frame = document()->frame()) - frame->loader()->client()->showMediaPlayerProxyPlugin(m_proxyWidget.get()); - } - return mediaRenderer; -#else - return new (arena) RenderMedia(this); -#endif + return createRenderer<RenderMedia>(*this, WTFMove(style)); } -bool HTMLMediaElement::childShouldCreateRenderer(const NodeRenderingContext& childContext) const +bool HTMLMediaElement::childShouldCreateRenderer(const Node& child) const { +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + return hasShadowRootParent(child) && HTMLElement::childShouldCreateRenderer(child); +#else if (!hasMediaControls()) return false; // <media> doesn't allow its content, including shadow subtree, to // be rendered. So this should return false for most of the children. // One exception is a shadow tree built for rendering controls which should be visible. // So we let them go here by comparing its subtree root with one of the controls. - return (mediaControls()->treeScope() == childContext.node()->treeScope() - && childContext.isOnUpperEncapsulationBoundary() && HTMLElement::childShouldCreateRenderer(childContext)); + return &mediaControls()->treeScope() == &child.treeScope() + && hasShadowRootParent(child) + && HTMLElement::childShouldCreateRenderer(child); +#endif } -Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode* insertionPoint) +Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode& insertionPoint) { - LOG(Media, "HTMLMediaElement::insertedInto"); + LOG(Media, "HTMLMediaElement::insertedInto(%p)", this); HTMLElement::insertedInto(insertionPoint); - if (insertionPoint->inDocument()) { + if (insertionPoint.inDocument()) { m_inActiveDocument = true; - if (m_networkState == NETWORK_EMPTY && !getAttribute(srcAttr).isEmpty()) +#if PLATFORM(IOS) + if (m_networkState == NETWORK_EMPTY && !fastGetAttribute(srcAttr).isEmpty() && m_mediaSession->dataLoadingPermitted(*this)) +#else + if (m_networkState == NETWORK_EMPTY && !fastGetAttribute(srcAttr).isEmpty()) +#endif scheduleDelayedAction(LoadMediaResource); } + if (!m_explicitlyMuted) { + m_explicitlyMuted = true; + m_muted = fastHasAttribute(mutedAttr); + } + configureMediaControls(); return InsertionDone; } -void HTMLMediaElement::removedFrom(ContainerNode* insertionPoint) +void HTMLMediaElement::removedFrom(ContainerNode& insertionPoint) { - LOG(Media, "HTMLMediaElement::removedFrom"); + LOG(Media, "HTMLMediaElement::removedFrom(%p)", this); m_inActiveDocument = false; - if (insertionPoint->inDocument()) { - configureMediaControls(); + if (insertionPoint.inDocument()) { + if (hasMediaControls()) + mediaControls()->hide(); if (m_networkState > NETWORK_EMPTY) pause(); - if (m_isFullscreen) + if (m_videoFullscreenMode != VideoFullscreenModeNone) exitFullscreen(); if (m_player) { - JSC::VM* vm = JSDOMWindowBase::commonVM(); - JSC::JSLockHolder lock(vm); - size_t extraMemoryCost = m_player->extraMemoryCost(); - size_t extraMemoryCostDelta = extraMemoryCost - m_reportedExtraMemoryCost; - m_reportedExtraMemoryCost = extraMemoryCost; - - if (extraMemoryCostDelta > 0) - vm->heap.reportExtraMemoryCost(extraMemoryCostDelta); + if (extraMemoryCost > m_reportedExtraMemoryCost) { + JSC::VM& vm = JSDOMWindowBase::commonVM(); + JSC::JSLockHolder lock(vm); + + size_t extraMemoryCostDelta = extraMemoryCost - m_reportedExtraMemoryCost; + m_reportedExtraMemoryCost = extraMemoryCost; + // FIXME: Adopt reportExtraMemoryVisited, and switch to reportExtraMemoryAllocated. + // https://bugs.webkit.org/show_bug.cgi?id=142595 + vm.heap.deprecatedReportExtraMemory(extraMemoryCostDelta); + } } } HTMLElement::removedFrom(insertionPoint); } -void HTMLMediaElement::attach(const AttachContext& context) +void HTMLMediaElement::willAttachRenderers() { - ASSERT(!attached()); - -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) - m_needWidgetUpdate = true; -#endif + ASSERT(!renderer()); +} - HTMLElement::attach(context); +void HTMLMediaElement::didAttachRenderers() +{ + if (RenderElement* renderer = this->renderer()) { + renderer->updateFromElement(); + if (m_mediaSession->hasBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted)) + renderer->registerForVisibleInViewportCallback(); + } + updateShouldAutoplay(); +} +void HTMLMediaElement::willDetachRenderers() +{ if (renderer()) - renderer()->updateFromElement(); -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) - else if (m_proxyWidget) { - if (Frame* frame = document()->frame()) - frame->loader()->client()->hideMediaPlayerProxyPlugin(m_proxyWidget.get()); - } -#endif + renderer()->unregisterForVisibleInViewportCallback(); } -void HTMLMediaElement::didRecalcStyle(StyleChange) +void HTMLMediaElement::didDetachRenderers() +{ + updateShouldAutoplay(); +} + +void HTMLMediaElement::didRecalcStyle(Style::Change) { if (renderer()) renderer()->updateFromElement(); @@ -662,56 +841,56 @@ void HTMLMediaElement::didRecalcStyle(StyleChange) void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType) { - LOG(Media, "HTMLMediaElement::scheduleLoad"); + LOG(Media, "HTMLMediaElement::scheduleDelayedAction(%p) - setting %s flag", this, actionName(actionType).utf8().data()); if ((actionType & LoadMediaResource) && !(m_pendingActionFlags & LoadMediaResource)) { -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) - createMediaPlayerProxy(); -#endif - prepareForLoad(); setFlags(m_pendingActionFlags, LoadMediaResource); } #if ENABLE(VIDEO_TRACK) - if (RuntimeEnabledFeatures::webkitVideoTrackEnabled() && (actionType & ConfigureTextTracks)) + if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() && (actionType & ConfigureTextTracks)) setFlags(m_pendingActionFlags, ConfigureTextTracks); #endif -#if USE(PLATFORM_TEXT_TRACK_MENU) - if (actionType & TextTrackChangesNotification) - setFlags(m_pendingActionFlags, TextTrackChangesNotification); +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + if (actionType & CheckPlaybackTargetCompatablity) + setFlags(m_pendingActionFlags, CheckPlaybackTargetCompatablity); #endif - m_loadTimer.startOneShot(0); + if (actionType & CheckMediaState) + setFlags(m_pendingActionFlags, CheckMediaState); + + m_pendingActionTimer.startOneShot(0); } void HTMLMediaElement::scheduleNextSourceChild() { // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad. + LOG(Media, "HTMLMediaElement::scheduleNextSourceChild(%p) - setting %s flag", this, actionName(LoadMediaResource).utf8().data()); setFlags(m_pendingActionFlags, LoadMediaResource); - m_loadTimer.startOneShot(0); + m_pendingActionTimer.startOneShot(0); } void HTMLMediaElement::scheduleEvent(const AtomicString& eventName) { #if LOG_MEDIA_EVENTS - LOG(Media, "HTMLMediaElement::scheduleEvent - scheduling '%s'", eventName.string().ascii().data()); + LOG(Media, "HTMLMediaElement::scheduleEvent(%p) - scheduling '%s'", this, eventName.string().ascii().data()); #endif RefPtr<Event> event = Event::create(eventName, false, true); // Don't set the event target, the event queue will set it in GenericEventQueue::timerFired and setting it here // will trigger an ASSERT if this element has been marked for deletion. - m_asyncEventQueue->enqueueEvent(event.release()); + m_asyncEventQueue.enqueueEvent(event.release()); } -void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*) +void HTMLMediaElement::pendingActionTimerFired() { - RefPtr<HTMLMediaElement> protect(this); // loadNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations. + Ref<HTMLMediaElement> protect(*this); // loadNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations. #if ENABLE(VIDEO_TRACK) - if (RuntimeEnabledFeatures::webkitVideoTrackEnabled() && (m_pendingActionFlags & ConfigureTextTracks)) + if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() && (m_pendingActionFlags & ConfigureTextTracks)) configureTextTracks(); #endif @@ -722,9 +901,15 @@ void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*) loadInternal(); } -#if USE(PLATFORM_TEXT_TRACK_MENU) - if (RuntimeEnabledFeatures::webkitVideoTrackEnabled() && (m_pendingActionFlags & TextTrackChangesNotification)) - notifyMediaPlayerOfTextTrackChanges(); +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + if (m_pendingActionFlags & CheckPlaybackTargetCompatablity && m_player && m_player->isCurrentPlaybackTargetWireless() && !m_player->canPlayToWirelessPlaybackTarget()) { + LOG(Media, "HTMLMediaElement::pendingActionTimerFired(%p) - calling setShouldPlayToPlaybackTarget(false)", this); + m_failedToPlayToWirelessTarget = true; + m_player->setShouldPlayToPlaybackTarget(false); + } + + if (m_pendingActionFlags & CheckMediaState) + updateMediaState(); #endif m_pendingActionFlags = 0; @@ -740,14 +925,39 @@ void HTMLMediaElement::setSrc(const String& url) setAttribute(srcAttr, url); } +#if ENABLE(MEDIA_STREAM) +void HTMLMediaElement::setSrcObject(MediaStream* mediaStream) +{ + // FIXME: Setting the srcObject attribute may cause other changes to the media element's internal state: + // Specifically, if srcObject is specified, the UA must use it as the source of media, even if the src + // attribute is also set or children are present. If the value of srcObject is replaced or set to null + // the UA must re-run the media element load algorithm. + // + // https://bugs.webkit.org/show_bug.cgi?id=124896 + + m_mediaStreamSrcObject = mediaStream; + setSrc(DOMURL::createPublicURL(ActiveDOMObject::scriptExecutionContext(), mediaStream)); +} +#endif + HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const { return m_networkState; } -String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySystem, const KURL& url) const +String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySystem, const URL& url) const { - MediaPlayer::SupportsType support = MediaPlayer::supportsType(ContentType(mimeType), keySystem, url, this); + MediaEngineSupportParameters parameters; + ContentType contentType(mimeType); + parameters.type = contentType.type().convertToASCIILowercase(); + parameters.codecs = contentType.parameter(ASCIILiteral("codecs")); + parameters.url = url; +#if ENABLE(ENCRYPTED_MEDIA) + parameters.keySystem = keySystem; +#else + UNUSED_PARAM(keySystem); +#endif + MediaPlayer::SupportsType support = MediaPlayer::supportsType(parameters, this); String canPlay; // 4.8.10.3 @@ -764,23 +974,27 @@ String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySy break; } - LOG(Media, "HTMLMediaElement::canPlayType(%s, %s, %s) -> %s", mimeType.utf8().data(), keySystem.utf8().data(), url.stringCenterEllipsizedToLength().utf8().data(), canPlay.utf8().data()); + LOG(Media, "HTMLMediaElement::canPlayType(%p) - [%s, %s, %s] -> %s", this, mimeType.utf8().data(), keySystem.utf8().data(), url.stringCenterEllipsizedToLength().utf8().data(), canPlay.utf8().data()); return canPlay; } +double HTMLMediaElement::getStartDate() const +{ + return m_player->getStartDate().toDouble(); +} + void HTMLMediaElement::load() { - RefPtr<HTMLMediaElement> protect(this); // loadInternal may result in a 'beforeload' event, which can make arbitrary DOM mutations. + Ref<HTMLMediaElement> protect(*this); // loadInternal may result in a 'beforeload' event, which can make arbitrary DOM mutations. - LOG(Media, "HTMLMediaElement::load()"); + LOG(Media, "HTMLMediaElement::load(%p)", this); - if (userGestureRequiredForLoad() && !ScriptController::processingUserGesture()) + if (!m_mediaSession->dataLoadingPermitted(*this)) return; - - m_loadInitiatedByUserGesture = ScriptController::processingUserGesture(); - if (m_loadInitiatedByUserGesture) + if (ScriptController::processingUserGestureForMedia()) removeBehaviorsRestrictionsAfterFirstUserGesture(); + prepareForLoad(); loadInternal(); prepareToPlay(); @@ -788,21 +1002,28 @@ void HTMLMediaElement::load() void HTMLMediaElement::prepareForLoad() { - LOG(Media, "HTMLMediaElement::prepareForLoad"); + LOG(Media, "HTMLMediaElement::prepareForLoad(%p)", this); // Perform the cleanup required for the resource load algorithm to run. stopPeriodicTimers(); - m_loadTimer.stop(); + m_pendingActionTimer.stop(); + // FIXME: Figure out appropriate place to reset LoadTextTrackResource if necessary and set m_pendingActionFlags to 0 here. + m_pendingActionFlags &= ~LoadMediaResource; m_sentEndEvent = false; m_sentStalledEvent = false; m_haveFiredLoadedData = false; m_completelyLoaded = false; m_havePreparedToPlay = false; m_displayMode = Unknown; + m_currentSrc = URL(); + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + m_failedToPlayToWirelessTarget = false; +#endif // 1 - Abort any already-running instance of the resource selection algorithm for this element. m_loadState = WaitingForSource; - m_currentSourceNode = 0; + m_currentSourceNode = nullptr; // 2 - If there are any tasks from the media element's media element event task source in // one of the task queues, then remove those tasks. @@ -814,32 +1035,51 @@ void HTMLMediaElement::prepareForLoad() scheduleEvent(eventNames().abortEvent); #if ENABLE(MEDIA_SOURCE) - setSourceState(MediaSource::closedKeyword()); + closeMediaSource(); #endif -#if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) createMediaPlayer(); -#else - if (m_player) - m_player->cancelLoad(); - else - createMediaPlayerProxy(); -#endif // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps if (m_networkState != NETWORK_EMPTY) { + // 4.1 - Queue a task to fire a simple event named emptied at the media element. + scheduleEvent(eventNames().emptiedEvent); + + // 4.2 - If a fetching process is in progress for the media element, the user agent should stop it. m_networkState = NETWORK_EMPTY; + + // 4.3 - Forget the media element's media-resource-specific tracks. + forgetResourceSpecificTracks(); + + // 4.4 - If readyState is not set to HAVE_NOTHING, then set it to that state. m_readyState = HAVE_NOTHING; m_readyStateMaximum = HAVE_NOTHING; - refreshCachedTime(); + + // 4.5 - If the paused attribute is false, then set it to true. m_paused = true; - m_seeking = false; + + // 4.6 - If seeking is true, set it to false. + clearSeeking(); + + // 4.7 - Set the current playback position to 0. + // Set the official playback position to 0. + // If this changed the official playback position, then queue a task to fire a simple event named timeupdate at the media element. + // FIXME: Add support for firing this event. e.g., scheduleEvent(eventNames().timeUpdateEvent); + + // 4.8 - Set the initial playback position to 0. + // FIXME: Make this less subtle. The position only becomes 0 because of the createMediaPlayer() call + // above. + refreshCachedTime(); + invalidateCachedTime(); - scheduleEvent(eventNames().emptiedEvent); + + // 4.9 - Set the timeline offset to Not-a-Number (NaN). + // 4.10 - Update the duration attribute to Not-a-Number (NaN). + updateMediaController(); #if ENABLE(VIDEO_TRACK) - if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) - updateActiveTextTrackCues(0); + if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) + updateActiveTextTrackCues(MediaTime::zeroTime()); #endif } @@ -847,7 +1087,7 @@ void HTMLMediaElement::prepareForLoad() setPlaybackRate(defaultPlaybackRate()); // 6 - Set the error attribute to null and the autoplaying flag to true. - m_error = 0; + m_error = nullptr; m_autoplaying = true; // 7 - Invoke the media element's resource selection algorithm. @@ -861,32 +1101,43 @@ void HTMLMediaElement::prepareForLoad() // 2 - Asynchronously await a stable state. m_playedTimeRanges = TimeRanges::create(); - m_lastSeekTime = 0; + + // FIXME: Investigate whether these can be moved into m_networkState != NETWORK_EMPTY block above + // so they are closer to the relevant spec steps. + m_lastSeekTime = MediaTime::zeroTime(); // The spec doesn't say to block the load event until we actually run the asynchronous section // algorithm, but do it now because we won't start that until after the timer fires and the // event may have already fired by then. - if (m_preload != MediaPlayer::None) + MediaPlayer::Preload effectivePreload = m_mediaSession->effectivePreloadForElement(*this); + if (effectivePreload != MediaPlayer::None) setShouldDelayLoadEvent(true); +#if PLATFORM(IOS) + if (effectivePreload != MediaPlayer::None && m_mediaSession->allowsAutomaticMediaDataLoading(*this)) + prepareToPlay(); +#endif + configureMediaControls(); } void HTMLMediaElement::loadInternal() { + LOG(Media, "HTMLMediaElement::loadInternal(%p)", this); + // Some of the code paths below this function dispatch the BeforeLoad event. This ASSERT helps // us catch those bugs more quickly without needing all the branches to align to actually // trigger the event. ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden()); // If we can't start a load right away, start it later. - Page* page = document()->page(); - if (pageConsentRequiredForLoad() && page && !page->canStartMedia()) { + if (!m_mediaSession->pageAllowsDataLoading(*this)) { + LOG(Media, "HTMLMediaElement::loadInternal(%p) - not allowed to load in background, waiting", this); setShouldDelayLoadEvent(false); if (m_isWaitingUntilMediaCanStart) return; - document()->addMediaCanStartListener(this); m_isWaitingUntilMediaCanStart = true; + document().addMediaCanStartListener(this); return; } @@ -894,8 +1145,8 @@ void HTMLMediaElement::loadInternal() // Once the page has allowed an element to load media, it is free to load at will. This allows a // playlist that starts in a foreground tab to continue automatically if the tab is subsequently - // put in the the background. - removeBehaviorRestriction(RequirePageConsentToLoadMediaRestriction); + // put into the background. + m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequirePageConsentToLoadMedia); #if ENABLE(VIDEO_TRACK) if (hasMediaControls()) @@ -903,7 +1154,7 @@ void HTMLMediaElement::loadInternal() // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the // disabled state when the element's resource selection algorithm last started". - if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) { + if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) { m_textTracksWhenResourceSelectionBegan.clear(); if (m_textTracks) { for (unsigned i = 0; i < m_textTracks->length(); ++i) { @@ -920,26 +1171,24 @@ void HTMLMediaElement::loadInternal() void HTMLMediaElement::selectMediaResource() { - LOG(Media, "HTMLMediaElement::selectMediaResource"); + LOG(Media, "HTMLMediaElement::selectMediaResource(%p)", this); + + ASSERT(m_player); + if (!m_player) + return; enum Mode { attribute, children }; // 3 - If the media element has a src attribute, then let mode be attribute. Mode mode = attribute; if (!fastHasAttribute(srcAttr)) { - Node* node; - for (node = firstChild(); node; node = node->nextSibling()) { - if (node->hasTagName(sourceTag)) - break; - } - // Otherwise, if the media element does not have a src attribute but has a source // element child, then let mode be children and let candidate be the first such // source element child in tree order. - if (node) { + if (auto firstSource = childrenOfType<HTMLSourceElement>(*this).first()) { mode = children; - m_nextChildNodeToConsider = node; - m_currentSourceNode = 0; + m_nextChildNodeToConsider = firstSource; + m_currentSourceNode = nullptr; } else { // Otherwise the media element has neither a src attribute nor a source element // child: set the networkState to NETWORK_EMPTY, and abort these steps; the @@ -948,7 +1197,7 @@ void HTMLMediaElement::selectMediaResource() setShouldDelayLoadEvent(false); m_networkState = NETWORK_EMPTY; - LOG(Media, "HTMLMediaElement::selectMediaResource, nothing to load"); + LOG(Media, "HTMLMediaElement::selectMediaResource(%p) - nothing to load", this); return; } } @@ -966,10 +1215,10 @@ void HTMLMediaElement::selectMediaResource() m_loadState = LoadingFromSrcAttr; // If the src attribute's value is the empty string ... jump down to the failed step below - KURL mediaURL = getNonEmptyURLAttribute(srcAttr); + URL mediaURL = getNonEmptyURLAttribute(srcAttr); if (mediaURL.isEmpty()) { mediaLoadingFailed(MediaPlayer::FormatError); - LOG(Media, "HTMLMediaElement::selectMediaResource, empty 'src'"); + LOG(Media, "HTMLMediaElement::selectMediaResource(%p) - empty 'src'", this); return; } @@ -983,7 +1232,7 @@ void HTMLMediaElement::selectMediaResource() // will have to pick a media engine based on the file extension. ContentType contentType((String())); loadResource(mediaURL, contentType, String()); - LOG(Media, "HTMLMediaElement::selectMediaResource, using 'src' attribute url"); + LOG(Media, "HTMLMediaElement::selectMediaResource(%p) - using 'src' attribute url", this); return; } @@ -995,64 +1244,60 @@ void HTMLMediaElement::loadNextSourceChild() { ContentType contentType((String())); String keySystem; - KURL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain); + URL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain); if (!mediaURL.isValid()) { waitForSourceChange(); return; } -#if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) // Recreate the media player for the new url createMediaPlayer(); -#endif m_loadState = LoadingFromSourceElement; loadResource(mediaURL, contentType, keySystem); } -static KURL createFileURLForApplicationCacheResource(const String& path) -{ - // KURL should have a function to create a url from a path, but it does not. This function - // is not suitable because KURL::setPath uses encodeWithURLEscapeSequences, which it notes - // does not correctly escape '#' and '?'. This function works for our purposes because - // app cache media files are always created with encodeForFileName(createCanonicalUUIDString()). - -#if USE(CF) && PLATFORM(WIN) - RetainPtr<CFURLRef> cfURL = adoptCF(CFURLCreateWithFileSystemPath(0, path.createCFString().get(), kCFURLWindowsPathStyle, false)); - KURL url(cfURL.get()); -#else - KURL url; - - url.setProtocol(ASCIILiteral("file")); - url.setPath(path); -#endif - return url; -} - -void HTMLMediaElement::loadResource(const KURL& initialURL, ContentType& contentType, const String& keySystem) +void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentType, const String& keySystem) { ASSERT(isSafeToLoadURL(initialURL, Complain)); - LOG(Media, "HTMLMediaElement::loadResource(%s, %s, %s)", urlForLoggingMedia(initialURL).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data()); + LOG(Media, "HTMLMediaElement::loadResource(%p) - %s, %s, %s", this, urlForLoggingMedia(initialURL).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data()); - Frame* frame = document()->frame(); + Frame* frame = document().frame(); if (!frame) { mediaLoadingFailed(MediaPlayer::FormatError); return; } - KURL url = initialURL; - if (!frame->loader()->willLoadMediaElementURL(url)) { + Page* page = frame->page(); + if (!page) { mediaLoadingFailed(MediaPlayer::FormatError); return; } - + + URL url = initialURL; + if (!frame->loader().willLoadMediaElementURL(url)) { + mediaLoadingFailed(MediaPlayer::FormatError); + return; + } + +#if ENABLE(CONTENT_EXTENSIONS) + ResourceRequest request(url); + DocumentLoader* documentLoader = frame->loader().documentLoader(); + + if (page->userContentController() && documentLoader && page->userContentController()->processContentExtensionRulesForLoad(request, ResourceType::Media, *documentLoader) == ContentExtensions::BlockedStatus::Blocked) { + request = { }; + mediaLoadingFailed(MediaPlayer::FormatError); + return; + } +#endif + // The resource fetch algorithm m_networkState = NETWORK_LOADING; // If the url should be loaded from the application cache, pass the url of the cached file // to the media engine. - ApplicationCacheHost* cacheHost = frame->loader()->documentLoader()->applicationCacheHost(); + ApplicationCacheHost* cacheHost = frame->loader().documentLoader()->applicationCacheHost(); ApplicationCacheResource* resource = 0; if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource)) { // Resources that are not present in the manifest will always fail to load (at least, after the @@ -1063,52 +1308,77 @@ void HTMLMediaElement::loadResource(const KURL& initialURL, ContentType& content } } + // Log that we started loading a media element. + if (Frame* frame = document().frame()) + frame->mainFrame().diagnosticLoggingClient().logDiagnosticMessageWithValue(DiagnosticLoggingKeys::mediaKey(), isVideo() ? DiagnosticLoggingKeys::videoKey() : DiagnosticLoggingKeys::audioKey(), DiagnosticLoggingKeys::loadingKey(), ShouldSample::No); + + m_firstTimePlaying = true; + // Set m_currentSrc *before* changing to the cache url, the fact that we are loading from the app // cache is an internal detail not exposed through the media element API. m_currentSrc = url; if (resource) { - url = createFileURLForApplicationCacheResource(resource->path()); - LOG(Media, "HTMLMediaElement::loadResource - will load from app cache -> %s", urlForLoggingMedia(url).utf8().data()); + url = ApplicationCacheHost::createFileURL(resource->path()); + LOG(Media, "HTMLMediaElement::loadResource(%p) - will load from app cache -> %s", this, urlForLoggingMedia(url).utf8().data()); } - LOG(Media, "HTMLMediaElement::loadResource - m_currentSrc -> %s", urlForLoggingMedia(m_currentSrc).utf8().data()); + LOG(Media, "HTMLMediaElement::loadResource(%p) - m_currentSrc -> %s", this, urlForLoggingMedia(m_currentSrc).utf8().data()); -#if ENABLE(MEDIA_STREAM) - if (MediaStreamRegistry::registry().lookupMediaStreamDescriptor(url.string())) - removeBehaviorRestriction(RequireUserGestureForRateChangeRestriction); -#endif - - if (m_sendProgressEvents) + if (m_sendProgressEvents) startProgressEventTimer(); - Settings* settings = document()->settings(); - bool privateMode = !settings || settings->privateBrowsingEnabled(); + bool privateMode = document().page() && document().page()->usesEphemeralSession(); m_player->setPrivateBrowsingMode(privateMode); // Reset display mode to force a recalculation of what to show because we are resetting the player. setDisplayMode(Unknown); if (!autoplay()) - m_player->setPreload(m_preload); + m_player->setPreload(m_mediaSession->effectivePreloadForElement(*this)); m_player->setPreservesPitch(m_webkitPreservesPitch); - if (fastHasAttribute(mutedAttr)) - m_muted = true; + if (!m_explicitlyMuted) { + m_explicitlyMuted = true; + m_muted = fastHasAttribute(mutedAttr); + } + updateVolume(); + bool loadAttempted = false; #if ENABLE(MEDIA_SOURCE) ASSERT(!m_mediaSource); if (url.protocolIs(mediaSourceBlobProtocol)) - m_mediaSource = MediaSourceRegistry::registry().lookupMediaSource(url.string()); + m_mediaSource = MediaSource::lookup(url.string()); if (m_mediaSource) { - if (!m_player->load(url, m_mediaSource)) + if (m_mediaSource->attachToElement(this)) + m_player->load(url, contentType, m_mediaSource.get()); + else { + // Forget our reference to the MediaSource, so we leave it alone + // while processing remainder of load failure. + m_mediaSource = nullptr; mediaLoadingFailed(MediaPlayer::FormatError); - } else + } + loadAttempted = true; + } #endif - if (!m_player->load(url, contentType, keySystem)) + +#if ENABLE(MEDIA_STREAM) + if (!loadAttempted) { + if (!m_mediaStreamSrcObject && url.protocolIs(mediaStreamBlobProtocol)) + m_mediaStreamSrcObject = MediaStreamRegistry::shared().lookUp(url); + + if (m_mediaStreamSrcObject) { + loadAttempted = true; + if (!m_player->load(m_mediaStreamSrcObject->privateStream())) + mediaLoadingFailed(MediaPlayer::FormatError); + } + } +#endif + + if (!loadAttempted && !m_player->load(url, contentType, keySystem)) mediaLoadingFailed(MediaPlayer::FormatError); // If there is no poster to display, allow the media engine to render video frames as soon as @@ -1126,13 +1396,12 @@ static bool trackIndexCompare(TextTrack* a, return a->trackIndex() - b->trackIndex() < 0; } -static bool eventTimeCueCompare(const std::pair<double, TextTrackCue*>& a, - const std::pair<double, TextTrackCue*>& b) +static bool eventTimeCueCompare(const std::pair<MediaTime, TextTrackCue*>& a, const std::pair<MediaTime, TextTrackCue*>& b) { // 12 - Sort the tasks in events in ascending time order (tasks with earlier // times first). if (a.first != b.first) - return a.first - b.first < 0; + return a.first - b.first < MediaTime::zeroTime(); // If the cues belong to different text tracks, it doesn't make sense to // compare the two tracks by the relative cue order, so return the relative @@ -1152,7 +1421,7 @@ static bool compareCueInterval(const CueInterval& one, const CueInterval& two) }; -void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) +void HTMLMediaElement::updateActiveTextTrackCues(const MediaTime& movieTime) { // 4.8.10.8 Playing the media resource @@ -1162,7 +1431,7 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) if (ignoreTrackDisplayUpdateRequests()) return; - LOG(Media, "HTMLMediaElement::updateActiveTextTracks"); + LOG(Media, "HTMLMediaElement::updateActiveTextTrackCues(%p)", this); // 1 - Let current cues be a list of cues, initialized to contain all the // cues of all the hidden, showing, or showing by default text tracks of the @@ -1175,7 +1444,8 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) // whenever ... the media element's readyState is changed back to HAVE_NOTHING. if (m_readyState != HAVE_NOTHING && m_player) { currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime)); - std::sort(currentCues.begin(), currentCues.end(), &compareCueInterval); + if (currentCues.size() > 1) + std::sort(currentCues.begin(), currentCues.end(), &compareCueInterval); } CueList previousCues; @@ -1189,7 +1459,7 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) // 3 - Let last time be the current playback position at the time this // algorithm was last run for this media element, if this is not the first // time it has run. - double lastTime = m_lastTextTrackUpdateTime; + MediaTime lastTime = m_lastTextTrackUpdateTime; // 4 - If the current playback position has, since the last time this // algorithm was run, only changed through its usual monotonic increase @@ -1197,16 +1467,16 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) // cues whose start times are greater than or equal to last time and whose // end times are less than or equal to the current playback position. // Otherwise, let missed cues be an empty list. - if (lastTime >= 0 && m_lastSeekTime < movieTime) { + if (lastTime >= MediaTime::zeroTime() && m_lastSeekTime < movieTime) { CueList potentiallySkippedCues = m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime)); for (size_t i = 0; i < potentiallySkippedCues.size(); ++i) { - double cueStartTime = potentiallySkippedCues[i].low(); - double cueEndTime = potentiallySkippedCues[i].high(); + MediaTime cueStartTime = potentiallySkippedCues[i].low(); + MediaTime cueEndTime = potentiallySkippedCues[i].high(); // Consider cues that may have been missed since the last seek time. - if (cueStartTime > max(m_lastSeekTime, lastTime) && cueEndTime < movieTime) + if (cueStartTime > std::max(m_lastSeekTime, lastTime) && cueEndTime < movieTime) missedCues.append(potentiallySkippedCues[i]); } } @@ -1221,7 +1491,7 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) // element. (In the other cases, such as explicit seeks, relevant events get // fired as part of the overall process of changing the current playback // position.) - if (m_lastSeekTime <= lastTime) + if (!m_paused && m_lastSeekTime <= lastTime) scheduleTimeupdateEvent(false); // Explicitly cache vector sizes, as their content is constant from here. @@ -1239,22 +1509,17 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) activeSetChanged = true; for (size_t i = 0; i < currentCuesSize; ++i) { - currentCues[i].data()->updateDisplayTree(movieTime); + TextTrackCue* cue = currentCues[i].data(); - if (!currentCues[i].data()->isActive()) + if (cue->isRenderable()) + toVTTCue(cue)->updateDisplayTree(movieTime); + + if (!cue->isActive()) activeSetChanged = true; } - if (!activeSetChanged) { - // Even though the active set has not changed, it is possible that the - // the mode of a track has changed from 'hidden' to 'showing' and the - // cues have not yet been rendered. - // Note: don't call updateTextTrackDisplay() unless we have controls because it will - // create them. - if (hasMediaControls()) - updateTextTrackDisplay(); + if (!activeSetChanged) return; - } // 7 - If the time was reached through the usual monotonic increase of the // current playback position during normal playback, and there are cues in @@ -1276,7 +1541,7 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) // 8 - Let events be a list of tasks, initially empty. Each task in this // list will be associated with a text track, a text track cue, and a time, // which are used to sort the list before the tasks are queued. - Vector<std::pair<double, TextTrackCue*> > eventTasks; + Vector<std::pair<MediaTime, TextTrackCue*>> eventTasks; // 8 - Let affected tracks be a list of text tracks, initially empty. Vector<TextTrack*> affectedTracks; @@ -1284,7 +1549,7 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) for (size_t i = 0; i < missedCuesSize; ++i) { // 9 - For each text track cue in missed cues, prepare an event named enter // for the TextTrackCue object with the text track cue start time. - eventTasks.append(std::make_pair(missedCues[i].data()->startTime(), + eventTasks.append(std::make_pair(missedCues[i].data()->startMediaTime(), missedCues[i].data())); // 10 - For each text track [...] in missed cues, prepare an event @@ -1296,9 +1561,8 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) // checked when these tasks are actually queued below. This doesn't // affect sorting events before dispatch either, because the exit // event has the same time as the enter event. - if (missedCues[i].data()->startTime() < missedCues[i].data()->endTime()) - eventTasks.append(std::make_pair(missedCues[i].data()->endTime(), - missedCues[i].data())); + if (missedCues[i].data()->startMediaTime() < missedCues[i].data()->endMediaTime()) + eventTasks.append(std::make_pair(missedCues[i].data()->endMediaTime(), missedCues[i].data())); } for (size_t i = 0; i < previousCuesSize; ++i) { @@ -1306,7 +1570,7 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) // track cue active flag set prepare an event named exit for the // TextTrackCue object with the text track cue end time. if (!currentCues.contains(previousCues[i])) - eventTasks.append(std::make_pair(previousCues[i].data()->endTime(), + eventTasks.append(std::make_pair(previousCues[i].data()->endMediaTime(), previousCues[i].data())); } @@ -1315,13 +1579,13 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) // text track cue active flag set, prepare an event named enter for the // TextTrackCue object with the text track cue start time. if (!previousCues.contains(currentCues[i])) - eventTasks.append(std::make_pair(currentCues[i].data()->startTime(), + eventTasks.append(std::make_pair(currentCues[i].data()->startMediaTime(), currentCues[i].data())); } // 12 - Sort the tasks in events in ascending time order (tasks with earlier // times first). - nonCopyingSort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare); + std::sort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare); for (size_t i = 0; i < eventTasks.size(); ++i) { if (!affectedTracks.contains(eventTasks[i].second->track())) @@ -1337,25 +1601,25 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) if (eventTasks[i].second->startTime() >= eventTasks[i].second->endTime()) { event = Event::create(eventNames().enterEvent, false, false); event->setTarget(eventTasks[i].second); - m_asyncEventQueue->enqueueEvent(event.release()); + m_asyncEventQueue.enqueueEvent(event.release()); event = Event::create(eventNames().exitEvent, false, false); event->setTarget(eventTasks[i].second); - m_asyncEventQueue->enqueueEvent(event.release()); + m_asyncEventQueue.enqueueEvent(event.release()); } else { - if (eventTasks[i].first == eventTasks[i].second->startTime()) + if (eventTasks[i].first == eventTasks[i].second->startMediaTime()) event = Event::create(eventNames().enterEvent, false, false); else event = Event::create(eventNames().exitEvent, false, false); event->setTarget(eventTasks[i].second); - m_asyncEventQueue->enqueueEvent(event.release()); + m_asyncEventQueue.enqueueEvent(event.release()); } } // 14 - Sort affected tracks in the same order as the text tracks appear in // the media element's list of text tracks, and remove duplicates. - nonCopyingSort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare); + std::sort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare); // 15 - For each text track in affected tracks, in the list order, queue a // task to fire a simple event named cuechange at the TextTrack object, and, ... @@ -1363,17 +1627,17 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false); event->setTarget(affectedTracks[i]); - m_asyncEventQueue->enqueueEvent(event.release()); + m_asyncEventQueue.enqueueEvent(event.release()); // ... if the text track has a corresponding track element, to then fire a // simple event named cuechange at the track element as well. - if (affectedTracks[i]->trackType() == TextTrack::TrackElement) { + if (is<LoadableTextTrack>(*affectedTracks[i])) { RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false); - HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(affectedTracks[i])->trackElement(); + HTMLTrackElement* trackElement = downcast<LoadableTextTrack>(*affectedTracks[i]).trackElement(); ASSERT(trackElement); event->setTarget(trackElement); - m_asyncEventQueue->enqueueEvent(event.release()); + m_asyncEventQueue.enqueueEvent(event.release()); } } @@ -1424,9 +1688,12 @@ void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track) } } -void HTMLMediaElement::audioTrackEnabledChanged(AudioTrack*) +void HTMLMediaElement::audioTrackEnabledChanged(AudioTrack* track) { - // We will want to change the media controls here once they exist + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) + return; + if (m_audioTracks && m_audioTracks->contains(track)) + m_audioTracks->scheduleChangeEvent(); } void HTMLMediaElement::textTrackModeChanged(TextTrack* track) @@ -1434,13 +1701,9 @@ void HTMLMediaElement::textTrackModeChanged(TextTrack* track) bool trackIsLoaded = true; if (track->trackType() == TextTrack::TrackElement) { trackIsLoaded = false; - for (Node* node = firstChild(); node; node = node->nextSibling()) { - if (!node->hasTagName(trackTag)) - continue; - - HTMLTrackElement* trackElement = static_cast<HTMLTrackElement*>(node); - if (trackElement->track() == track) { - if (trackElement->readyState() == HTMLTrackElement::LOADING || trackElement->readyState() == HTMLTrackElement::LOADED) + for (auto& trackElement : childrenOfType<HTMLTrackElement>(*this)) { + if (trackElement.track() == track) { + if (trackElement.readyState() == HTMLTrackElement::LOADING || trackElement.readyState() == HTMLTrackElement::LOADED) trackIsLoaded = true; break; } @@ -1457,18 +1720,23 @@ void HTMLMediaElement::textTrackModeChanged(TextTrack* track) if (track->mode() != TextTrack::disabledKeyword() && trackIsLoaded) textTrackAddCues(track, track->cues()); -#if USE(PLATFORM_TEXT_TRACK_MENU) - if (platformTextTrackMenu()) - platformTextTrackMenu()->trackWasSelected(track->platformTextTrack()); + configureTextTrackDisplay(AssumeTextTrackVisibilityChanged); + + if (m_textTracks && m_textTracks->contains(track)) + m_textTracks->scheduleChangeEvent(); + +#if ENABLE(AVF_CAPTIONS) + if (track->trackType() == TextTrack::TrackElement && m_player) + m_player->notifyTrackModeChanged(); #endif - - configureTextTrackDisplay(); - updateActiveTextTrackCues(currentTime()); } -void HTMLMediaElement::videoTrackSelectedChanged(VideoTrack*) +void HTMLMediaElement::videoTrackSelectedChanged(VideoTrack* track) { - // We will want to change the media controls here once they exist + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) + return; + if (m_videoTracks && m_videoTracks->contains(track)) + m_videoTracks->scheduleChangeEvent(); } void HTMLMediaElement::textTrackKindChanged(TextTrack* track) @@ -1487,7 +1755,7 @@ void HTMLMediaElement::endIgnoringTrackDisplayUpdateRequests() ASSERT(m_ignoreTrackDisplayUpdate); --m_ignoreTrackDisplayUpdate; if (!m_ignoreTrackDisplayUpdate && m_inActiveDocument) - updateActiveTextTrackCues(currentTime()); + updateActiveTextTrackCues(currentMediaTime()); } void HTMLMediaElement::textTrackAddCues(TextTrack* track, const TextTrackCueList* cues) @@ -1507,59 +1775,72 @@ void HTMLMediaElement::textTrackRemoveCues(TextTrack*, const TextTrackCueList* c textTrackRemoveCue(cues->item(i)->track(), cues->item(i)); } -void HTMLMediaElement::textTrackAddCue(TextTrack* track, PassRefPtr<TextTrackCue> cue) +void HTMLMediaElement::textTrackAddCue(TextTrack* track, PassRefPtr<TextTrackCue> prpCue) { if (track->mode() == TextTrack::disabledKeyword()) return; + RefPtr<TextTrackCue> cue = prpCue; + // Negative duration cues need be treated in the interval tree as // zero-length cues. - double endTime = max(cue->startTime(), cue->endTime()); + MediaTime endTime = std::max(cue->startMediaTime(), cue->endMediaTime()); - CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get()); + CueInterval interval = m_cueTree.createInterval(cue->startMediaTime(), endTime, cue.get()); if (!m_cueTree.contains(interval)) m_cueTree.add(interval); - updateActiveTextTrackCues(currentTime()); + updateActiveTextTrackCues(currentMediaTime()); } -void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtr<TextTrackCue> cue) +void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtr<TextTrackCue> prpCue) { + RefPtr<TextTrackCue> cue = prpCue; // Negative duration cues need to be treated in the interval tree as // zero-length cues. - double endTime = max(cue->startTime(), cue->endTime()); + MediaTime endTime = std::max(cue->startMediaTime(), cue->endMediaTime()); - CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get()); + CueInterval interval = m_cueTree.createInterval(cue->startMediaTime(), endTime, cue.get()); m_cueTree.remove(interval); + // Since the cue will be removed from the media element and likely the + // TextTrack might also be destructed, notifying the region of the cue + // removal shouldn't be done. + if (cue->isRenderable()) + toVTTCue(cue.get())->notifyRegionWhenRemovingDisplayTree(false); + size_t index = m_currentlyActiveCues.find(interval); if (index != notFound) { cue->setIsActive(false); m_currentlyActiveCues.remove(index); } - cue->removeDisplayTree(); - updateActiveTextTrackCues(currentTime()); + if (cue->isRenderable()) + toVTTCue(cue.get())->removeDisplayTree(); + updateActiveTextTrackCues(currentMediaTime()); + + if (cue->isRenderable()) + toVTTCue(cue.get())->notifyRegionWhenRemovingDisplayTree(true); } #endif -bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidURLAction actionIfInvalid) +bool HTMLMediaElement::isSafeToLoadURL(const URL& url, InvalidURLAction actionIfInvalid) { if (!url.isValid()) { - LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE because url is invalid", urlForLoggingMedia(url).utf8().data()); + LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p) - %s -> FALSE because url is invalid", this, urlForLoggingMedia(url).utf8().data()); return false; } - Frame* frame = document()->frame(); - if (!frame || !document()->securityOrigin()->canDisplay(url)) { + Frame* frame = document().frame(); + if (!frame || !document().securityOrigin()->canDisplay(url)) { if (actionIfInvalid == Complain) FrameLoader::reportLocalLoadFailed(frame, url.stringCenterEllipsizedToLength()); - LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE rejected by SecurityOrigin", urlForLoggingMedia(url).utf8().data()); + LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p) - %s -> FALSE rejected by SecurityOrigin", this, urlForLoggingMedia(url).utf8().data()); return false; } - if (!document()->contentSecurityPolicy()->allowMediaFromSource(url)) { - LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> rejected by Content Security Policy", urlForLoggingMedia(url).utf8().data()); + if (!document().contentSecurityPolicy()->allowMediaFromSource(url, isInUserAgentShadowTree())) { + LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p) - %s -> rejected by Content Security Policy", this, urlForLoggingMedia(url).utf8().data()); return false; } @@ -1571,14 +1852,14 @@ void HTMLMediaElement::startProgressEventTimer() if (m_progressEventTimer.isActive()) return; - m_previousProgressTime = WTF::currentTime(); + m_previousProgressTime = monotonicallyIncreasingTime(); // 350ms is not magic, it is in the spec! m_progressEventTimer.startRepeating(0.350); } void HTMLMediaElement::waitForSourceChange() { - LOG(Media, "HTMLMediaElement::waitForSourceChange"); + LOG(Media, "HTMLMediaElement::waitForSourceChange(%p)", this); stopPeriodicTimers(); m_loadState = WaitingForSource; @@ -1597,11 +1878,11 @@ void HTMLMediaElement::waitForSourceChange() void HTMLMediaElement::noneSupported() { - LOG(Media, "HTMLMediaElement::noneSupported"); + LOG(Media, "HTMLMediaElement::noneSupported(%p)", this); stopPeriodicTimers(); m_loadState = WaitingForSource; - m_currentSourceNode = 0; + m_currentSourceNode = nullptr; // 4.8.10.5 // 6 - Reaching this step indicates that the media resource failed to load or that the given @@ -1612,6 +1893,7 @@ void HTMLMediaElement::noneSupported() m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED); // 6.2 - Forget the media element's media-resource-specific text tracks. + forgetResourceSpecificTracks(); // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value. m_networkState = NETWORK_NO_SOURCE; @@ -1620,7 +1902,7 @@ void HTMLMediaElement::noneSupported() scheduleEvent(eventNames().errorEvent); #if ENABLE(MEDIA_SOURCE) - setSourceState(MediaSource::closedKeyword()); + closeMediaSource(); #endif // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. @@ -1635,9 +1917,9 @@ void HTMLMediaElement::noneSupported() renderer()->updateFromElement(); } -void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err) +void HTMLMediaElement::mediaLoadingFailedFatally(MediaPlayer::NetworkState error) { - LOG(Media, "HTMLMediaElement::mediaEngineError(%d)", static_cast<int>(err->code())); + LOG(Media, "HTMLMediaElement::mediaLoadingFailedFatally(%p) - error = %d", this, static_cast<int>(error)); // 1 - The user agent should cancel the fetching process. stopPeriodicTimers(); @@ -1645,13 +1927,18 @@ void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err) // 2 - Set the error attribute to a new MediaError object whose code attribute is // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE. - m_error = err; + if (error == MediaPlayer::NetworkError) + m_error = MediaError::create(MediaError::MEDIA_ERR_NETWORK); + else if (error == MediaPlayer::DecodeError) + m_error = MediaError::create(MediaError::MEDIA_ERR_DECODE); + else + ASSERT_NOT_REACHED(); // 3 - Queue a task to fire a simple event named error at the media element. scheduleEvent(eventNames().errorEvent); #if ENABLE(MEDIA_SOURCE) - setSourceState(MediaSource::closedKeyword()); + closeMediaSource(); #endif // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a @@ -1663,28 +1950,26 @@ void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err) setShouldDelayLoadEvent(false); // 6 - Abort the overall resource selection algorithm. - m_currentSourceNode = 0; + m_currentSourceNode = nullptr; + +#if PLATFORM(COCOA) + if (is<MediaDocument>(document())) + downcast<MediaDocument>(document()).mediaElementSawUnsupportedTracks(); +#endif } void HTMLMediaElement::cancelPendingEventsAndCallbacks() { - LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks"); - m_asyncEventQueue->cancelAllEvents(); + LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks(%p)", this); + m_asyncEventQueue.cancelAllEvents(); - for (Node* node = firstChild(); node; node = node->nextSibling()) { - if (node->hasTagName(sourceTag)) - static_cast<HTMLSourceElement*>(node)->cancelPendingErrorEvent(); - } + for (auto& source : childrenOfType<HTMLSourceElement>(*this)) + source.cancelPendingErrorEvent(); } Document* HTMLMediaElement::mediaPlayerOwningDocument() { - Document* d = document(); - - if (!d) - d = ownerDocument(); - - return d; + return &document(); } void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*) @@ -1696,23 +1981,22 @@ void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*) static void logMediaLoadRequest(Page* page, const String& mediaEngine, const String& errorMessage, bool succeeded) { - if (!page || !page->settings()->diagnosticLoggingEnabled()) + if (!page) return; - ChromeClient* client = page->chrome().client(); - + DiagnosticLoggingClient& diagnosticLoggingClient = page->mainFrame().diagnosticLoggingClient(); if (!succeeded) { - client->logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadingFailedKey(), errorMessage, DiagnosticLoggingKeys::failKey()); + diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::mediaLoadingFailedKey(), errorMessage, DiagnosticLoggingResultFail, ShouldSample::No); return; } - client->logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadedKey(), mediaEngine, DiagnosticLoggingKeys::noopKey()); + diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadedKey(), mediaEngine, ShouldSample::No); if (!page->hasSeenAnyMediaEngine()) - client->logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOneMediaEngineKey(), emptyString(), DiagnosticLoggingKeys::noopKey()); + diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOneMediaEngineKey(), emptyString(), ShouldSample::No); if (!page->hasSeenMediaEngine(mediaEngine)) - client->logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsMediaEngineKey(), mediaEngine, DiagnosticLoggingKeys::noopKey()); + diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsMediaEngineKey(), mediaEngine, ShouldSample::No); page->sawMediaEngine(mediaEngine); } @@ -1739,26 +2023,31 @@ void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error) // <source> children, schedule the next one if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) { + // resource selection algorithm + // 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. if (m_currentSourceNode) m_currentSourceNode->scheduleErrorEvent(); else - LOG(Media, "HTMLMediaElement::setNetworkState - error event not sent, <source> was removed"); + LOG(Media, "HTMLMediaElement::setNetworkState(%p) - error event not sent, <source> was removed", this); + // 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. + + // 9.Otherwise.11 - Forget the media element's media-resource-specific tracks. + forgetResourceSpecificTracks(); + if (havePotentialSourceChild()) { - LOG(Media, "HTMLMediaElement::setNetworkState - scheduling next <source>"); + LOG(Media, "HTMLMediaElement::setNetworkState(%p) - scheduling next <source>", this); scheduleNextSourceChild(); } else { - LOG(Media, "HTMLMediaElement::setNetworkState - no more <source> elements, waiting"); + LOG(Media, "HTMLMediaElement::setNetworkState(%p) - no more <source> elements, waiting", this); waitForSourceChange(); } return; } - if (error == MediaPlayer::NetworkError && m_readyState >= HAVE_METADATA) - mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK)); - else if (error == MediaPlayer::DecodeError) - mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE)); + if ((error == MediaPlayer::NetworkError && m_readyState >= HAVE_METADATA) || error == MediaPlayer::DecodeError) + mediaLoadingFailedFatally(error); else if ((error == MediaPlayer::FormatError || error == MediaPlayer::NetworkError) && m_loadState == LoadingFromSrcAttr) noneSupported(); @@ -1768,12 +2057,12 @@ void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error) mediaControls()->reportedError(); } - logMediaLoadRequest(document()->page(), String(), stringForNetworkState(error), false); + logMediaLoadRequest(document().page(), String(), stringForNetworkState(error), false); } void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state) { - LOG(Media, "HTMLMediaElement::setNetworkState(%d) - current state is %d", static_cast<int>(state), static_cast<int>(m_networkState)); + LOG(Media, "HTMLMediaElement::setNetworkState(%p) - new state = %d, current state = %d", this, static_cast<int>(state), static_cast<int>(m_networkState)); if (state == MediaPlayer::Empty) { // Just update the cached state and leave, we can't do anything. @@ -1833,9 +2122,18 @@ void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*) endProcessingMediaPlayerCallback(); } +static bool elementCanTransitionFromAutoplayToPlay(HTMLMediaElement& element) +{ + return element.isAutoplaying() + && element.paused() + && element.autoplay() + && !element.document().isSandboxed(SandboxAutomaticFeatures) + && element.mediaSession().playbackPermitted(element); +} + void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) { - LOG(Media, "HTMLMediaElement::setReadyState(%d) - current state is %d,", static_cast<int>(state), static_cast<int>(m_readyState)); + LOG(Media, "HTMLMediaElement::setReadyState(%p) - new state = %d, current state = %d,", this, static_cast<int>(state), static_cast<int>(m_readyState)); // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it bool wasPotentiallyPlaying = potentiallyPlaying(); @@ -1844,7 +2142,7 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) ReadyState newState = static_cast<ReadyState>(state); #if ENABLE(VIDEO_TRACK) - bool tracksAreReady = !RuntimeEnabledFeatures::webkitVideoTrackEnabled() || textTracksAreReady(); + bool tracksAreReady = !RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() || textTracksAreReady(); if (newState == oldState && m_tracksAreReady == tracksAreReady) return; @@ -1879,11 +2177,12 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) scheduleEvent(eventNames().waitingEvent); // 4.8.10.10 step 14 & 15. - if (m_readyState >= HAVE_CURRENT_DATA) + if (!m_player->seeking() && m_readyState >= HAVE_CURRENT_DATA) finishSeek(); } else { if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) { // 4.8.10.8 + invalidateCachedTime(); scheduleTimeupdateEvent(false); scheduleEvent(eventNames().waitingEvent); } @@ -1892,13 +2191,27 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) { prepareMediaFragmentURI(); scheduleEvent(eventNames().durationchangeEvent); + scheduleResizeEvent(); scheduleEvent(eventNames().loadedmetadataEvent); +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) + enqueuePlaybackTargetAvailabilityChangedEvent(); +#endif + m_initiallyMuted = m_volume < 0.05 || muted(); + if (hasMediaControls()) mediaControls()->loadedMetadata(); if (renderer()) renderer()->updateFromElement(); - logMediaLoadRequest(document()->page(), m_player->engineDescription(), String(), true); + if (is<MediaDocument>(document())) + downcast<MediaDocument>(document()).mediaElementNaturalSizeChanged(expandedIntSize(m_player->naturalSize())); + + logMediaLoadRequest(document().page(), m_player->engineDescription(), String(), true); + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + updateMediaState(UpdateMediaState::Asynchronously); +#endif } bool shouldUpdateDisplayState = false; @@ -1928,7 +2241,7 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA) scheduleEvent(eventNames().playingEvent); - if (m_autoplaying && m_paused && autoplay() && !document()->isSandboxed(SandboxAutomaticFeatures) && !userGestureRequiredForRateChange()) { + if (elementCanTransitionFromAutoplayToPlay(*this)) { m_paused = false; invalidateCachedTime(); scheduleEvent(eventNames().playEvent); @@ -1949,23 +2262,17 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) updatePlayState(); updateMediaController(); #if ENABLE(VIDEO_TRACK) - if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) - updateActiveTextTrackCues(currentTime()); + if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) + updateActiveTextTrackCues(currentMediaTime()); #endif } #if ENABLE(ENCRYPTED_MEDIA) void HTMLMediaElement::mediaPlayerKeyAdded(MediaPlayer*, const String& keySystem, const String& sessionId) { - MediaKeyEventInit initializer; - initializer.keySystem = keySystem; - initializer.sessionId = sessionId; - initializer.bubbles = false; - initializer.cancelable = false; - - RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeyaddedEvent, initializer); + Ref<Event> event = MediaKeyEvent::create(eventNames().webkitkeyaddedEvent, keySystem, sessionId, nullptr, nullptr, emptyString(), nullptr, 0); event->setTarget(this); - m_asyncEventQueue->enqueueEvent(event.release()); + m_asyncEventQueue.enqueueEvent(WTFMove(event)); } void HTMLMediaElement::mediaPlayerKeyError(MediaPlayer*, const String& keySystem, const String& sessionId, MediaPlayerClient::MediaKeyErrorCode errorCode, unsigned short systemCode) @@ -1992,32 +2299,16 @@ void HTMLMediaElement::mediaPlayerKeyError(MediaPlayer*, const String& keySystem break; } - MediaKeyEventInit initializer; - initializer.keySystem = keySystem; - initializer.sessionId = sessionId; - initializer.errorCode = MediaKeyError::create(mediaKeyErrorCode); - initializer.systemCode = systemCode; - initializer.bubbles = false; - initializer.cancelable = false; - - RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeyerrorEvent, initializer); + Ref<Event> event = MediaKeyEvent::create(eventNames().webkitkeyerrorEvent, keySystem, sessionId, nullptr, nullptr, emptyString(), MediaKeyError::create(mediaKeyErrorCode), systemCode); event->setTarget(this); - m_asyncEventQueue->enqueueEvent(event.release()); + m_asyncEventQueue.enqueueEvent(WTFMove(event)); } -void HTMLMediaElement::mediaPlayerKeyMessage(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* message, unsigned messageLength, const KURL& defaultURL) +void HTMLMediaElement::mediaPlayerKeyMessage(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* message, unsigned messageLength, const URL& defaultURL) { - MediaKeyEventInit initializer; - initializer.keySystem = keySystem; - initializer.sessionId = sessionId; - initializer.message = Uint8Array::create(message, messageLength); - initializer.defaultURL = defaultURL; - initializer.bubbles = false; - initializer.cancelable = false; - - RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeymessageEvent, initializer); + Ref<Event> event = MediaKeyEvent::create(eventNames().webkitkeymessageEvent, keySystem, sessionId, nullptr, Uint8Array::create(message, messageLength), defaultURL, nullptr, 0); event->setTarget(this); - m_asyncEventQueue->enqueueEvent(event.release()); + m_asyncEventQueue.enqueueEvent(WTFMove(event)); } bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* initData, unsigned initDataLength) @@ -2028,21 +2319,19 @@ bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, const String& keySyste return false; } - MediaKeyEventInit initializer; - initializer.keySystem = keySystem; - initializer.sessionId = sessionId; - initializer.initData = Uint8Array::create(initData, initDataLength); - initializer.bubbles = false; - initializer.cancelable = false; - - RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitneedkeyEvent, initializer); + Ref<Event> event = MediaKeyEvent::create(eventNames().webkitneedkeyEvent, keySystem, sessionId, Uint8Array::create(initData, initDataLength), nullptr, emptyString(), nullptr, 0); event->setTarget(this); - m_asyncEventQueue->enqueueEvent(event.release()); + m_asyncEventQueue.enqueueEvent(WTFMove(event)); return true; } #endif #if ENABLE(ENCRYPTED_MEDIA_V2) +RefPtr<ArrayBuffer> HTMLMediaElement::mediaPlayerCachedKeyForKeyId(const String& keyId) const +{ + return m_mediaKeys ? m_mediaKeys->cachedKeyForKeyId(keyId) : nullptr; +} + bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, Uint8Array* initData) { if (!hasEventListeners("webkitneedkey")) { @@ -2051,18 +2340,30 @@ bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, Uint8Array* initData) return false; } - MediaKeyNeededEventInit initializer; - initializer.initData = initData; - initializer.bubbles = false; - initializer.cancelable = false; - - RefPtr<Event> event = MediaKeyNeededEvent::create(eventNames().webkitneedkeyEvent, initializer); + RefPtr<Event> event = MediaKeyNeededEvent::create(eventNames().webkitneedkeyEvent, initData); event->setTarget(this); - m_asyncEventQueue->enqueueEvent(event.release()); + m_asyncEventQueue.enqueueEvent(event.release()); return true; } +String HTMLMediaElement::mediaPlayerMediaKeysStorageDirectory() const +{ + Settings* settings = document().settings(); + if (!settings) + return emptyString(); + + String storageDirectory = settings->mediaKeysStorageDirectory(); + if (storageDirectory.isEmpty()) + return emptyString(); + + SecurityOrigin* origin = document().securityOrigin(); + if (!origin) + return emptyString(); + + return pathByAppendingComponent(storageDirectory, origin->databaseIdentifier()); +} + void HTMLMediaElement::setMediaKeys(MediaKeys* mediaKeys) { if (m_mediaKeys == mediaKeys) @@ -2074,15 +2375,40 @@ void HTMLMediaElement::setMediaKeys(MediaKeys* mediaKeys) if (m_mediaKeys) m_mediaKeys->setMediaElement(this); } + +void HTMLMediaElement::keyAdded() +{ + if (m_player) + m_player->keyAdded(); +} +#endif + +#if ENABLE(MEDIA_STREAM) +String HTMLMediaElement::mediaPlayerMediaDeviceIdentifierStorageDirectory() const +{ + Settings* settings = document().settings(); + if (!settings) + return emptyString(); + + String storageDirectory = settings->mediaDeviceIdentifierStorageDirectory(); + if (storageDirectory.isEmpty()) + return emptyString(); + + SecurityOrigin* origin = document().securityOrigin(); + if (!origin) + return emptyString(); + + return pathByAppendingComponent(storageDirectory, origin->databaseIdentifier()); +} #endif -void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*) +void HTMLMediaElement::progressEventTimerFired() { ASSERT(m_player); if (m_networkState != NETWORK_LOADING) return; - double time = WTF::currentTime(); + double time = monotonicallyIncreasingTime(); double timedelta = time - m_previousProgressTime; if (m_player->didLoadingProgress()) { @@ -2102,29 +2428,24 @@ void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*) void HTMLMediaElement::rewind(double timeDelta) { - LOG(Media, "HTMLMediaElement::rewind(%f)", timeDelta); - setCurrentTime(max(currentTime() - timeDelta, minTimeSeekable()), IGNORE_EXCEPTION); + LOG(Media, "HTMLMediaElement::rewind(%p) - %f", this, timeDelta); + setCurrentTime(std::max(currentMediaTime() - MediaTime::createWithDouble(timeDelta), minTimeSeekable())); } void HTMLMediaElement::returnToRealtime() { - LOG(Media, "HTMLMediaElement::returnToRealtime"); - setCurrentTime(maxTimeSeekable(), IGNORE_EXCEPTION); + LOG(Media, "HTMLMediaElement::returnToRealtime(%p)", this); + setCurrentTime(maxTimeSeekable()); } -void HTMLMediaElement::addPlayedRange(double start, double end) +void HTMLMediaElement::addPlayedRange(const MediaTime& start, const MediaTime& end) { - LOG(Media, "HTMLMediaElement::addPlayedRange(%f, %f)", start, end); + LOG(Media, "HTMLMediaElement::addPlayedRange(%p) - [%s, %s]", this, toString(start).utf8().data(), toString(end).utf8().data()); if (!m_playedTimeRanges) m_playedTimeRanges = TimeRanges::create(); - m_playedTimeRanges->add(start, end); + m_playedTimeRanges->ranges().add(start, end); } -bool HTMLMediaElement::supportsSave() const -{ - return m_player ? m_player->supportsSave() : false; -} - bool HTMLMediaElement::supportsScanning() const { return m_player ? m_player->supportsScanning() : false; @@ -2139,17 +2460,51 @@ void HTMLMediaElement::prepareToPlay() m_player->prepareToPlay(); } -void HTMLMediaElement::seek(double time, ExceptionCode& ec) +void HTMLMediaElement::fastSeek(double time) +{ + fastSeek(MediaTime::createWithDouble(time)); +} + +void HTMLMediaElement::fastSeek(const MediaTime& time) { - LOG(Media, "HTMLMediaElement::seek(%f)", time); + LOG(Media, "HTMLMediaElement::fastSeek(%p) - %s", this, toString(time).utf8().data()); + // 4.7.10.9 Seeking + // 9. If the approximate-for-speed flag is set, adjust the new playback position to a value that will + // allow for playback to resume promptly. If new playback position before this step is before current + // playback position, then the adjusted new playback position must also be before the current playback + // position. Similarly, if the new playback position before this step is after current playback position, + // then the adjusted new playback position must also be after the current playback position. + refreshCachedTime(); + MediaTime delta = time - currentMediaTime(); + MediaTime negativeTolerance = delta >= MediaTime::zeroTime() ? delta : MediaTime::positiveInfiniteTime(); + MediaTime positiveTolerance = delta < MediaTime::zeroTime() ? -delta : MediaTime::positiveInfiniteTime(); - // 4.8.9.9 Seeking + seekWithTolerance(time, negativeTolerance, positiveTolerance, true); +} - // 1 - If the media element's readyState is HAVE_NOTHING, then raise an INVALID_STATE_ERR exception. - if (m_readyState == HAVE_NOTHING || !m_player) { - ec = INVALID_STATE_ERR; +void HTMLMediaElement::seek(const MediaTime& time) +{ + LOG(Media, "HTMLMediaElement::seek(%p) - %s", this, toString(time).utf8().data()); + seekWithTolerance(time, MediaTime::zeroTime(), MediaTime::zeroTime(), true); +} + +void HTMLMediaElement::seekInternal(const MediaTime& time) +{ + LOG(Media, "HTMLMediaElement::seekInternal(%p) - %s", this, toString(time).utf8().data()); + seekWithTolerance(time, MediaTime::zeroTime(), MediaTime::zeroTime(), false); +} + +void HTMLMediaElement::seekWithTolerance(const MediaTime& inTime, const MediaTime& negativeTolerance, const MediaTime& positiveTolerance, bool fromDOM) +{ + // 4.8.10.9 Seeking + MediaTime time = inTime; + + // 1 - Set the media element's show poster flag to false. + setDisplayMode(Video); + + // 2 - If the media element's readyState is HAVE_NOTHING, abort these steps. + if (m_readyState == HAVE_NOTHING || !m_player) return; - } // If the media engine has been told to postpone loading data, let it go ahead now. if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA) @@ -2157,24 +2512,63 @@ void HTMLMediaElement::seek(double time, ExceptionCode& ec) // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set. refreshCachedTime(); - double now = currentTime(); + MediaTime now = currentMediaTime(); - // 2 - If the element's seeking IDL attribute is true, then another instance of this algorithm is + // 3 - If the element's seeking IDL attribute is true, then another instance of this algorithm is // already running. Abort that other instance of the algorithm without waiting for the step that // it is running to complete. - // Nothing specific to be done here. + if (m_seekTaskQueue.hasPendingTasks()) { + LOG(Media, "HTMLMediaElement::seekWithTolerance(%p) - cancelling pending seeks", this); + m_seekTaskQueue.cancelAllTasks(); + if (m_pendingSeek) { + now = m_pendingSeek->now; + m_pendingSeek = nullptr; + } + m_pendingSeekType = NoSeek; + } - // 3 - Set the seeking IDL attribute to true. + // 4 - Set the seeking IDL attribute to true. // The flag will be cleared when the engine tells us the time has actually changed. m_seeking = true; + if (m_playing) { + if (m_lastSeekTime < now) + addPlayedRange(m_lastSeekTime, now); + } + m_lastSeekTime = time; + + // 5 - If the seek was in response to a DOM method call or setting of an IDL attribute, then continue + // the script. The remainder of these steps must be run asynchronously. + m_pendingSeek = std::make_unique<PendingSeek>(now, time, negativeTolerance, positiveTolerance); + if (fromDOM) { + LOG(Media, "HTMLMediaElement::seekWithTolerance(%p) - enqueuing seek from %s to %s", this, toString(now).utf8().data(), toString(time).utf8().data()); + m_seekTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::seekTask, this)); + } else + seekTask(); +} + +void HTMLMediaElement::seekTask() +{ + LOG(Media, "HTMLMediaElement::seekTask(%p)", this); + + if (!m_player) { + clearSeeking(); + return; + } - // 5 - If the new playback position is later than the end of the media resource, then let it be the end + ASSERT(m_pendingSeek); + MediaTime now = m_pendingSeek->now; + MediaTime time = m_pendingSeek->targetTime; + MediaTime negativeTolerance = m_pendingSeek->negativeTolerance; + MediaTime positiveTolerance = m_pendingSeek->positiveTolerance; + m_pendingSeek = nullptr; + + // 6 - If the new playback position is later than the end of the media resource, then let it be the end // of the media resource instead. - time = min(time, duration()); + time = std::min(time, durationMediaTime()); - // 6 - If the new playback position is less than the earliest possible position, let it be that position instead. - double earliestTime = m_player->startTime(); - time = max(time, earliestTime); + // 7 - If the new playback position is less than the earliest possible position, let it be that position instead. + MediaTime earliestTime = m_player->startTime(); + time = std::max(time, earliestTime); // Ask the media engine for the time value in the movie's time scale before comparing with current time. This // is necessary because if the seek time is not equal to currentTime but the delta is less than the movie's @@ -2182,71 +2576,89 @@ void HTMLMediaElement::seek(double time, ExceptionCode& ec) // not generate a timechanged callback. This means m_seeking will never be cleared and we will never // fire a 'seeked' event. #if !LOG_DISABLED - double mediaTime = m_player->mediaTimeForTimeValue(time); + MediaTime mediaTime = m_player->mediaTimeForTimeValue(time); if (time != mediaTime) - LOG(Media, "HTMLMediaElement::seek(%f) - media timeline equivalent is %f", time, mediaTime); + LOG(Media, "HTMLMediaElement::seekTask(%p) - %s - media timeline equivalent is %s", this, toString(time).utf8().data(), toString(mediaTime).utf8().data()); #endif time = m_player->mediaTimeForTimeValue(time); - // 7 - If the (possibly now changed) new playback position is not in one of the ranges given in the + // 8 - If the (possibly now changed) new playback position is not in one of the ranges given in the // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute // that is the nearest to the new playback position. ... If there are no ranges given in the seekable // attribute then set the seeking IDL attribute to false and abort these steps. RefPtr<TimeRanges> seekableRanges = seekable(); + bool noSeekRequired = !seekableRanges->length(); // Short circuit seeking to the current time by just firing the events if no seek is required. - // Don't skip calling the media engine if we are in poster mode because a seek should always - // cancel poster display. - bool noSeekRequired = !seekableRanges->length() || (time == now && displayMode() != Poster); + // Don't skip calling the media engine if 1) we are in poster mode (because a seek should always cancel + // poster display), or 2) if there is a pending fast seek, or 3) if this seek is not an exact seek + SeekType thisSeekType = (negativeTolerance == MediaTime::zeroTime() && positiveTolerance == MediaTime::zeroTime()) ? Precise : Fast; + if (!noSeekRequired && time == now && thisSeekType == Precise && m_pendingSeekType != Fast && displayMode() != Poster) + noSeekRequired = true; #if ENABLE(MEDIA_SOURCE) // Always notify the media engine of a seek if the source is not closed. This ensures that the source is // always in a flushed state when the 'seeking' event fires. - if (m_mediaSource && m_mediaSource->readyState() != MediaSource::closedKeyword()) + if (m_mediaSource && !m_mediaSource->isClosed()) noSeekRequired = false; #endif if (noSeekRequired) { + LOG(Media, "HTMLMediaElement::seekTask(%p) - seek to %s ignored", this, toString(time).utf8().data()); if (time == now) { scheduleEvent(eventNames().seekingEvent); scheduleTimeupdateEvent(false); scheduleEvent(eventNames().seekedEvent); } - m_seeking = false; + clearSeeking(); return; } - time = seekableRanges->nearest(time); + time = seekableRanges->ranges().nearest(time); - if (m_playing) { - if (m_lastSeekTime < now) - addPlayedRange(m_lastSeekTime, now); - } - m_lastSeekTime = time; m_sentEndEvent = false; + m_lastSeekTime = time; + m_pendingSeekType = thisSeekType; + m_seeking = true; - // 8 - Set the current playback position to the given new playback position - m_player->seek(time); - - // 9 - Queue a task to fire a simple event named seeking at the element. + // 10 - Queue a task to fire a simple event named seeking at the element. scheduleEvent(eventNames().seekingEvent); - // 10 - Queue a task to fire a simple event named timeupdate at the element. - scheduleTimeupdateEvent(false); + // 11 - Set the current playback position to the given new playback position + m_player->seekWithTolerance(time, negativeTolerance, positiveTolerance); - // 11-15 are handled, if necessary, when the engine signals a readystate change. + // 12 - Wait until the user agent has established whether or not the media data for the new playback + // position is available, and, if it is, until it has decoded enough data to play back that position. + // 13 - Await a stable state. The synchronous section consists of all the remaining steps of this algorithm. +} + +void HTMLMediaElement::clearSeeking() +{ + m_seeking = false; + m_pendingSeekType = NoSeek; + invalidateCachedTime(); } void HTMLMediaElement::finishSeek() { - LOG(Media, "HTMLMediaElement::finishSeek"); + // 4.8.10.9 Seeking + // 14 - Set the seeking IDL attribute to false. + clearSeeking(); - // 4.8.10.9 Seeking step 14 - m_seeking = false; + LOG(Media, "HTMLMediaElement::finishSeek(%p) - current time = %s", this, toString(currentMediaTime()).utf8().data()); - // 4.8.10.9 Seeking step 15 + // 15 - Run the time maches on steps. + // Handled by mediaPlayerTimeChanged(). + + // 16 - Queue a task to fire a simple event named timeupdate at the element. + scheduleEvent(eventNames().timeupdateEvent); + + // 17 - Queue a task to fire a simple event named seeked at the element. scheduleEvent(eventNames().seekedEvent); - setDisplayMode(Video); +#if ENABLE(MEDIA_SOURCE) + if (m_mediaSource) + m_mediaSource->monitorSourceBuffers(); +#endif } HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const @@ -2271,113 +2683,140 @@ bool HTMLMediaElement::seeking() const void HTMLMediaElement::refreshCachedTime() const { + if (!m_player) + return; + m_cachedTime = m_player->currentTime(); - m_cachedTimeWallClockUpdateTime = WTF::currentTime(); + if (!m_cachedTime) { + // Do not use m_cachedTime until the media engine returns a non-zero value because we can't + // estimate current time until playback actually begins. + invalidateCachedTime(); + return; + } + + m_clockTimeAtLastCachedTimeUpdate = monotonicallyIncreasingTime(); } -void HTMLMediaElement::invalidateCachedTime() +void HTMLMediaElement::invalidateCachedTime() const { - LOG(Media, "HTMLMediaElement::invalidateCachedTime"); + m_cachedTime = MediaTime::invalidTime(); + if (!m_player || !m_player->maximumDurationToCacheMediaTime()) + return; + +#if !LOG_DISABLED + if (m_cachedTime.isValid()) + LOG(Media, "HTMLMediaElement::invalidateCachedTime(%p)", this); +#endif // Don't try to cache movie time when playback first starts as the time reported by the engine // sometimes fluctuates for a short amount of time, so the cached time will be off if we take it // too early. static const double minimumTimePlayingBeforeCacheSnapshot = 0.5; - m_minimumWallClockTimeToCacheMediaTime = WTF::currentTime() + minimumTimePlayingBeforeCacheSnapshot; - m_cachedTime = MediaPlayer::invalidTime(); + m_minimumClockTimeToUpdateCachedTime = monotonicallyIncreasingTime() + minimumTimePlayingBeforeCacheSnapshot; } // playback state double HTMLMediaElement::currentTime() const { + return currentMediaTime().toDouble(); +} + +MediaTime HTMLMediaElement::currentMediaTime() const +{ #if LOG_CACHED_TIME_WARNINGS - static const double minCachedDeltaForWarning = 0.01; + static const MediaTime minCachedDeltaForWarning = MediaTime::create(1, 100); #endif if (!m_player) - return 0; + return MediaTime::zeroTime(); if (m_seeking) { - LOG(Media, "HTMLMediaElement::currentTime - seeking, returning %f", m_lastSeekTime); + LOG(Media, "HTMLMediaElement::currentTime(%p) - seeking, returning %s", this, toString(m_lastSeekTime).utf8().data()); return m_lastSeekTime; } - if (m_cachedTime != MediaPlayer::invalidTime() && m_paused) { + if (m_cachedTime.isValid() && m_paused) { #if LOG_CACHED_TIME_WARNINGS - double delta = m_cachedTime - m_player->currentTime(); + MediaTime delta = m_cachedTime - m_player->currentTime(); if (delta > minCachedDeltaForWarning) - LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when paused", delta); + LOG(Media, "HTMLMediaElement::currentTime(%p) - WARNING, cached time is %s seconds off of media time when paused", this, toString(delta).utf8().data()); #endif return m_cachedTime; } // Is it too soon use a cached time? - double now = WTF::currentTime(); + double now = monotonicallyIncreasingTime(); double maximumDurationToCacheMediaTime = m_player->maximumDurationToCacheMediaTime(); - if (maximumDurationToCacheMediaTime && m_cachedTime != MediaPlayer::invalidTime() && !m_paused && now > m_minimumWallClockTimeToCacheMediaTime) { - double wallClockDelta = now - m_cachedTimeWallClockUpdateTime; + if (maximumDurationToCacheMediaTime && m_cachedTime.isValid() && !m_paused && now > m_minimumClockTimeToUpdateCachedTime) { + double clockDelta = now - m_clockTimeAtLastCachedTimeUpdate; // Not too soon, use the cached time only if it hasn't expired. - if (wallClockDelta < maximumDurationToCacheMediaTime) { - double adjustedCacheTime = m_cachedTime + (m_playbackRate * wallClockDelta); + if (clockDelta < maximumDurationToCacheMediaTime) { + MediaTime adjustedCacheTime = m_cachedTime + MediaTime::createWithDouble(effectivePlaybackRate() * clockDelta); #if LOG_CACHED_TIME_WARNINGS - double delta = adjustedCacheTime - m_player->currentTime(); + MediaTime delta = adjustedCacheTime - m_player->currentTime(); if (delta > minCachedDeltaForWarning) - LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when playing", delta); + LOG(Media, "HTMLMediaElement::currentTime(%p) - WARNING, cached time is %f seconds off of media time when playing", this, delta); #endif return adjustedCacheTime; } } #if LOG_CACHED_TIME_WARNINGS - if (maximumDurationToCacheMediaTime && now > m_minimumWallClockTimeToCacheMediaTime && m_cachedTime != MediaPlayer::invalidTime()) { - double wallClockDelta = now - m_cachedTimeWallClockUpdateTime; - double delta = m_cachedTime + (m_playbackRate * wallClockDelta) - m_player->currentTime(); - LOG(Media, "HTMLMediaElement::currentTime - cached time was %f seconds off of media time when it expired", delta); + if (maximumDurationToCacheMediaTime && now > m_minimumClockTimeToUpdateCachedTime && m_cachedTime != MediaPlayer::invalidTime()) { + double clockDelta = now - m_clockTimeAtLastCachedTimeUpdate; + MediaTime delta = m_cachedTime + MediaTime::createWithDouble(effectivePlaybackRate() * clockDelta) - m_player->currentTime(); + LOG(Media, "HTMLMediaElement::currentTime(%p) - cached time was %s seconds off of media time when it expired", this, toString(delta).utf8().data()); } #endif refreshCachedTime(); + if (m_cachedTime.isInvalid()) + return MediaTime::zeroTime(); + return m_cachedTime; } +void HTMLMediaElement::setCurrentTime(double time) +{ + setCurrentTime(MediaTime::createWithDouble(time)); +} + +void HTMLMediaElement::setCurrentTime(const MediaTime& time) +{ + if (m_mediaController) + return; + + seekInternal(time); +} + void HTMLMediaElement::setCurrentTime(double time, ExceptionCode& ec) { + // On setting, if the media element has a current media controller, then the user agent must + // throw an InvalidStateError exception if (m_mediaController) { ec = INVALID_STATE_ERR; return; } - seek(time, ec); -} -double HTMLMediaElement::startTime() const -{ - if (!m_player) - return 0; - return m_player->startTime(); + seek(MediaTime::createWithDouble(time)); } -double HTMLMediaElement::initialTime() const +double HTMLMediaElement::duration() const { - if (m_fragmentStartTime != MediaPlayer::invalidTime()) - return m_fragmentStartTime; - - if (!m_player) - return 0; - - return m_player->initialTime(); + return durationMediaTime().toDouble(); } -double HTMLMediaElement::duration() const +MediaTime HTMLMediaElement::durationMediaTime() const { if (m_player && m_readyState >= HAVE_METADATA) return m_player->duration(); - return numeric_limits<double>::quiet_NaN(); + return MediaTime::invalidTime(); } bool HTMLMediaElement::paused() const @@ -2394,41 +2833,90 @@ bool HTMLMediaElement::paused() const double HTMLMediaElement::defaultPlaybackRate() const { +#if ENABLE(MEDIA_STREAM) + // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements + // "defaultPlaybackRate" - On setting: ignored. On getting: return 1.0 + // A MediaStream is not seekable. Therefore, this attribute must always have the + // value 1.0 and any attempt to alter it must be ignored. Note that this also means + // that the ratechange event will not fire. + if (m_mediaStreamSrcObject) + return 1; +#endif + return m_defaultPlaybackRate; } void HTMLMediaElement::setDefaultPlaybackRate(double rate) { +#if ENABLE(MEDIA_STREAM) + // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements + // "defaultPlaybackRate" - On setting: ignored. On getting: return 1.0 + // A MediaStream is not seekable. Therefore, this attribute must always have the + // value 1.0 and any attempt to alter it must be ignored. Note that this also means + // that the ratechange event will not fire. + if (m_mediaStreamSrcObject) + return; +#endif + if (m_defaultPlaybackRate != rate) { + LOG(Media, "HTMLMediaElement::setDefaultPlaybackRate(%p) - %f", this, rate); m_defaultPlaybackRate = rate; scheduleEvent(eventNames().ratechangeEvent); } } +double HTMLMediaElement::effectivePlaybackRate() const +{ + return m_mediaController ? m_mediaController->playbackRate() : m_reportedPlaybackRate; +} + +double HTMLMediaElement::requestedPlaybackRate() const +{ + return m_mediaController ? m_mediaController->playbackRate() : m_requestedPlaybackRate; +} + double HTMLMediaElement::playbackRate() const { - return m_playbackRate; +#if ENABLE(MEDIA_STREAM) + // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements + // "playbackRate" - A MediaStream is not seekable. Therefore, this attribute must always + // have the value 1.0 and any attempt to alter it must be ignored. Note that this also + // means that the ratechange event will not fire. + if (m_mediaStreamSrcObject) + return 1; +#endif + + return m_requestedPlaybackRate; } void HTMLMediaElement::setPlaybackRate(double rate) { - LOG(Media, "HTMLMediaElement::setPlaybackRate(%f)", rate); - - if (m_playbackRate != rate) { - m_playbackRate = rate; - invalidateCachedTime(); - scheduleEvent(eventNames().ratechangeEvent); - } + LOG(Media, "HTMLMediaElement::setPlaybackRate(%p) - %f", this, rate); + +#if ENABLE(MEDIA_STREAM) + // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements + // "playbackRate" - A MediaStream is not seekable. Therefore, this attribute must always + // have the value 1.0 and any attempt to alter it must be ignored. Note that this also + // means that the ratechange event will not fire. + if (m_mediaStreamSrcObject) + return; +#endif if (m_player && potentiallyPlaying() && m_player->rate() != rate && !m_mediaController) m_player->setRate(rate); + + if (m_requestedPlaybackRate != rate) { + m_reportedPlaybackRate = m_requestedPlaybackRate = rate; + invalidateCachedTime(); + scheduleEvent(eventNames().ratechangeEvent); + } } void HTMLMediaElement::updatePlaybackRate() { - double effectiveRate = m_mediaController ? m_mediaController->playbackRate() : m_playbackRate; - if (m_player && potentiallyPlaying() && m_player->rate() != effectiveRate) - m_player->setRate(effectiveRate); + double requestedRate = requestedPlaybackRate(); + if (m_player && potentiallyPlaying() && m_player->rate() != requestedRate) + m_player->setRate(requestedRate); } bool HTMLMediaElement::webkitPreservesPitch() const @@ -2438,7 +2926,7 @@ bool HTMLMediaElement::webkitPreservesPitch() const void HTMLMediaElement::setWebkitPreservesPitch(bool preservesPitch) { - LOG(Media, "HTMLMediaElement::setWebkitPreservesPitch(%s)", boolString(preservesPitch)); + LOG(Media, "HTMLMediaElement::setWebkitPreservesPitch(%p) - %s", this, boolString(preservesPitch)); m_webkitPreservesPitch = preservesPitch; @@ -2450,10 +2938,18 @@ void HTMLMediaElement::setWebkitPreservesPitch(bool preservesPitch) bool HTMLMediaElement::ended() const { +#if ENABLE(MEDIA_STREAM) + // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements + // When the MediaStream state moves from the active to the inactive state, the User Agent + // must raise an ended event on the HTMLMediaElement and set its ended attribute to true. + if (m_mediaStreamSrcObject && m_player && m_player->ended()) + return true; +#endif + // 4.8.10.8 Playing the media resource // The ended attribute must return true if the media element has ended // playback and the direction of playback is forwards, and false otherwise. - return endedPlayback() && m_playbackRate > 0; + return endedPlayback() && requestedPlaybackRate() > 0; } bool HTMLMediaElement::autoplay() const @@ -2461,24 +2957,22 @@ bool HTMLMediaElement::autoplay() const return fastHasAttribute(autoplayAttr); } -void HTMLMediaElement::setAutoplay(bool b) -{ - LOG(Media, "HTMLMediaElement::setAutoplay(%s)", boolString(b)); - setBooleanAttribute(autoplayAttr, b); -} - String HTMLMediaElement::preload() const { +#if ENABLE(MEDIA_STREAM) + // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements + // "preload" - On getting: none. On setting: ignored. + if (m_mediaStreamSrcObject) + return ASCIILiteral("none"); +#endif + switch (m_preload) { case MediaPlayer::None: return ASCIILiteral("none"); - break; case MediaPlayer::MetaData: return ASCIILiteral("metadata"); - break; case MediaPlayer::Auto: return ASCIILiteral("auto"); - break; } ASSERT_NOT_REACHED(); @@ -2487,42 +2981,44 @@ String HTMLMediaElement::preload() const void HTMLMediaElement::setPreload(const String& preload) { - LOG(Media, "HTMLMediaElement::setPreload(%s)", preload.utf8().data()); + LOG(Media, "HTMLMediaElement::setPreload(%p) - %s", this, preload.utf8().data()); +#if ENABLE(MEDIA_STREAM) + // http://w3c.github.io/mediacapture-main/#mediastreams-in-media-elements + // "preload" - On getting: none. On setting: ignored. + if (m_mediaStreamSrcObject) + return; +#endif + setAttribute(preloadAttr, preload); } void HTMLMediaElement::play() { - LOG(Media, "HTMLMediaElement::play()"); + LOG(Media, "HTMLMediaElement::play(%p)", this); - if (userGestureRequiredForRateChange() && !ScriptController::processingUserGesture()) + if (!m_mediaSession->playbackPermitted(*this)) return; - if (ScriptController::processingUserGesture()) + if (ScriptController::processingUserGestureForMedia()) removeBehaviorsRestrictionsAfterFirstUserGesture(); - Settings* settings = document()->settings(); - if (settings && settings->needsSiteSpecificQuirks() && m_dispatchingCanPlayEvent && !m_loadInitiatedByUserGesture) { - // It should be impossible to be processing the canplay event while handling a user gesture - // since it is dispatched asynchronously. - ASSERT(!ScriptController::processingUserGesture()); - String host = document()->baseURL().host(); - if (host.endsWith(".npr.org", false) || equalIgnoringCase(host, "npr.org")) - return; - } - playInternal(); } void HTMLMediaElement::playInternal() { - LOG(Media, "HTMLMediaElement::playInternal"); + LOG(Media, "HTMLMediaElement::playInternal(%p)", this); + + if (!m_mediaSession->clientWillBeginPlayback()) { + LOG(Media, " returning because of interruption"); + return; + } // 4.8.10.9. Playing the media resource if (!m_player || m_networkState == NETWORK_EMPTY) scheduleDelayedAction(LoadMediaResource); if (endedPlayback()) - seek(0, IGNORE_EXCEPTION); + seekInternal(MediaTime::zeroTime()); if (m_mediaController) m_mediaController->bringElementUpToSpeed(this); @@ -2536,6 +3032,32 @@ void HTMLMediaElement::playInternal() scheduleEvent(eventNames().waitingEvent); else if (m_readyState >= HAVE_FUTURE_DATA) scheduleEvent(eventNames().playingEvent); + +#if ENABLE(MEDIA_SESSION) + // 6.3 Activating a media session from a media element + // When the play() method is invoked, the paused attribute is true, and the readyState attribute has the value + // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, then + // 1. Let media session be the value of the current media session. + // 2. If we are not currently in media session's list of active participating media elements then append + // ourselves to this list. + // 3. Let activated be the result of running the media session invocation algorithm for media session. + // 4. If activated is failure, pause ourselves. + if (m_readyState == HAVE_ENOUGH_DATA || m_readyState == HAVE_FUTURE_DATA) { + if (m_session) { + m_session->addActiveMediaElement(*this); + + if (m_session->kindEnum() == MediaSession::Kind::Content) { + if (Page* page = document().page()) + page->chrome().client().focusedContentMediaElementDidChange(m_elementID); + } + + if (!m_session->invoke()) { + pause(); + return; + } + } + } +#endif } m_autoplaying = false; updatePlayState(); @@ -2544,9 +3066,9 @@ void HTMLMediaElement::playInternal() void HTMLMediaElement::pause() { - LOG(Media, "HTMLMediaElement::pause()"); + LOG(Media, "HTMLMediaElement::pause(%p)", this); - if (userGestureRequiredForRateChange() && !ScriptController::processingUserGesture()) + if (!m_mediaSession->playbackPermitted(*this)) return; pauseInternal(); @@ -2555,11 +3077,21 @@ void HTMLMediaElement::pause() void HTMLMediaElement::pauseInternal() { - LOG(Media, "HTMLMediaElement::pauseInternal"); + LOG(Media, "HTMLMediaElement::pauseInternal(%p)", this); + + if (!m_mediaSession->clientWillPausePlayback()) { + LOG(Media, " returning because of interruption"); + return; + } // 4.8.10.9. Playing the media resource - if (!m_player || m_networkState == NETWORK_EMPTY) + if (!m_player || m_networkState == NETWORK_EMPTY) { + // Unless the restriction on media requiring user action has been lifted + // don't trigger loading if a script calls pause(). + if (!m_mediaSession->playbackPermitted(*this)) + return; scheduleDelayedAction(LoadMediaResource); + } m_autoplaying = false; @@ -2567,20 +3099,22 @@ void HTMLMediaElement::pauseInternal() m_paused = true; scheduleTimeupdateEvent(false); scheduleEvent(eventNames().pauseEvent); + + if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) + purgeBufferedDataIfPossible(); } updatePlayState(); } #if ENABLE(MEDIA_SOURCE) -void HTMLMediaElement::setSourceState(const String& state) +void HTMLMediaElement::closeMediaSource() { if (!m_mediaSource) - return; + return; - m_mediaSource->setReadyState(state); - if (state == MediaSource::closedKeyword()) - m_mediaSource = 0; + m_mediaSource->close(); + m_mediaSource = nullptr; } #endif @@ -2590,7 +3124,7 @@ void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, PassRef #if ENABLE(ENCRYPTED_MEDIA_V2) static bool firstTime = true; if (firstTime && scriptExecutionContext()) { - scriptExecutionContext()->addConsoleMessage(JSMessageSource, WarningMessageLevel, "'HTMLMediaElement.webkitGenerateKeyRequest()' is deprecated. Use 'MediaKeys.createSession()' instead."); + scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, ASCIILiteral("'HTMLMediaElement.webkitGenerateKeyRequest()' is deprecated. Use 'MediaKeys.createSession()' instead.")); firstTime = false; } #endif @@ -2626,7 +3160,7 @@ void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Arr #if ENABLE(ENCRYPTED_MEDIA_V2) static bool firstTime = true; if (firstTime && scriptExecutionContext()) { - scriptExecutionContext()->addConsoleMessage(JSMessageSource, WarningMessageLevel, "'HTMLMediaElement.webkitAddKey()' is deprecated. Use 'MediaKeySession.update()' instead."); + scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, ASCIILiteral("'HTMLMediaElement.webkitAddKey()' is deprecated. Use 'MediaKeySession.update()' instead.")); firstTime = false; } #endif @@ -2692,24 +3226,16 @@ bool HTMLMediaElement::loop() const void HTMLMediaElement::setLoop(bool b) { - LOG(Media, "HTMLMediaElement::setLoop(%s)", boolString(b)); + LOG(Media, "HTMLMediaElement::setLoop(%p) - %s", this, boolString(b)); setBooleanAttribute(loopAttr, b); } bool HTMLMediaElement::controls() const { - Frame* frame = document()->frame(); + Frame* frame = document().frame(); // always show controls when scripting is disabled - if (frame && !frame->script()->canExecuteScripts(NotAboutToExecuteScript)) - return true; - - // always show controls for video when fullscreen playback is required. - if (isVideo() && document()->page() && document()->page()->chrome().requiresFullscreenForVideoPlayback()) - return true; - - // Always show controls when in full screen mode. - if (isFullscreen()) + if (frame && !frame->script().canExecuteScripts(NotAboutToExecuteScript)) return true; return fastHasAttribute(controlsAttr); @@ -2717,7 +3243,7 @@ bool HTMLMediaElement::controls() const void HTMLMediaElement::setControls(bool b) { - LOG(Media, "HTMLMediaElement::setControls(%s)", boolString(b)); + LOG(Media, "HTMLMediaElement::setControls(%p) - %s", this, boolString(b)); setBooleanAttribute(controlsAttr, b); } @@ -2728,47 +3254,64 @@ double HTMLMediaElement::volume() const void HTMLMediaElement::setVolume(double vol, ExceptionCode& ec) { - LOG(Media, "HTMLMediaElement::setVolume(%f)", vol); + LOG(Media, "HTMLMediaElement::setVolume(%p) - %f", this, vol); if (vol < 0.0f || vol > 1.0f) { ec = INDEX_SIZE_ERR; return; } +#if !PLATFORM(IOS) if (m_volume != vol) { m_volume = vol; m_volumeInitialized = true; updateVolume(); scheduleEvent(eventNames().volumechangeEvent); } +#endif } bool HTMLMediaElement::muted() const { - return m_muted; + return m_explicitlyMuted ? m_muted : fastHasAttribute(mutedAttr); } void HTMLMediaElement::setMuted(bool muted) { - LOG(Media, "HTMLMediaElement::setMuted(%s)", boolString(muted)); + LOG(Media, "HTMLMediaElement::setMuted(%p) - %s", this, boolString(muted)); - if (m_muted != muted) { +#if PLATFORM(IOS) + UNUSED_PARAM(muted); +#else + if (m_muted != muted || !m_explicitlyMuted) { m_muted = muted; + m_explicitlyMuted = true; // Avoid recursion when the player reports volume changes. if (!processingMediaPlayerCallback()) { if (m_player) { - m_player->setMuted(m_muted); + m_player->setMuted(effectiveMuted()); if (hasMediaControls()) mediaControls()->changedMute(); } } scheduleEvent(eventNames().volumechangeEvent); + +#if ENABLE(MEDIA_SESSION) + document().updateIsPlayingMedia(m_elementID); +#else + document().updateIsPlayingMedia(); +#endif + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + updateMediaState(UpdateMediaState::Asynchronously); +#endif } +#endif } void HTMLMediaElement::togglePlayState() { - LOG(Media, "HTMLMediaElement::togglePlayState - canPlay() is %s", boolString(canPlay())); + LOG(Media, "HTMLMediaElement::togglePlayState(%p) - canPlay() is %s", this, boolString(canPlay())); // We can safely call the internal play/pause methods, which don't check restrictions, because // this method is only called from the built-in media controller @@ -2781,7 +3324,7 @@ void HTMLMediaElement::togglePlayState() void HTMLMediaElement::beginScrubbing() { - LOG(Media, "HTMLMediaElement::beginScrubbing - paused() is %s", boolString(paused())); + LOG(Media, "HTMLMediaElement::beginScrubbing(%p) - paused() is %s", this, boolString(paused())); if (!paused()) { if (ended()) { @@ -2800,12 +3343,65 @@ void HTMLMediaElement::beginScrubbing() void HTMLMediaElement::endScrubbing() { - LOG(Media, "HTMLMediaElement::endScrubbing - m_pausedInternal is %s", boolString(m_pausedInternal)); + LOG(Media, "HTMLMediaElement::endScrubbing(%p) - m_pausedInternal is %s", this, boolString(m_pausedInternal)); if (m_pausedInternal) setPausedInternal(false); } +void HTMLMediaElement::beginScanning(ScanDirection direction) +{ + m_scanType = supportsScanning() ? Scan : Seek; + m_scanDirection = direction; + + if (m_scanType == Seek) { + // Scanning by seeking requires the video to be paused during scanning. + m_actionAfterScan = paused() ? Nothing : Play; + pause(); + } else { + // Scanning by scanning requires the video to be playing during scanninging. + m_actionAfterScan = paused() ? Pause : Nothing; + play(); + setPlaybackRate(nextScanRate()); + } + + m_scanTimer.start(0, m_scanType == Seek ? SeekRepeatDelay : ScanRepeatDelay); +} + +void HTMLMediaElement::endScanning() +{ + if (m_scanType == Scan) + setPlaybackRate(defaultPlaybackRate()); + + if (m_actionAfterScan == Play) + play(); + else if (m_actionAfterScan == Pause) + pause(); + + if (m_scanTimer.isActive()) + m_scanTimer.stop(); +} + +double HTMLMediaElement::nextScanRate() +{ + double rate = std::min(ScanMaximumRate, fabs(playbackRate() * 2)); + if (m_scanDirection == Backward) + rate *= -1; +#if PLATFORM(IOS) + rate = std::min(std::max(rate, minFastReverseRate()), maxFastForwardRate()); +#endif + return rate; +} + +void HTMLMediaElement::scanTimerFired() +{ + if (m_scanType == Seek) { + double seekTime = m_scanDirection == Forward ? SeekTime : -SeekTime; + setCurrentTime(currentTime() + seekTime); + } else + setPlaybackRate(nextScanRate()); +} + // The spec says to fire periodic timeupdate events (those sent while playing) every // "15 to 250ms", we choose the slowest frequency static const double maxTimeupdateEventFrequency = 0.25; @@ -2815,16 +3411,16 @@ void HTMLMediaElement::startPlaybackProgressTimer() if (m_playbackProgressTimer.isActive()) return; - m_previousProgressTime = WTF::currentTime(); + m_previousProgressTime = monotonicallyIncreasingTime(); m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency); } -void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*) +void HTMLMediaElement::playbackProgressTimerFired() { ASSERT(m_player); - if (m_fragmentEndTime != MediaPlayer::invalidTime() && currentTime() >= m_fragmentEndTime && m_playbackRate > 0) { - m_fragmentEndTime = MediaPlayer::invalidTime(); + if (m_fragmentEndTime.isValid() && currentMediaTime() >= m_fragmentEndTime && requestedPlaybackRate() > 0) { + m_fragmentEndTime = MediaTime::invalidTime(); if (!m_mediaController && !m_paused) { // changes paused to true and fires a simple event named pause at the media element. pauseInternal(); @@ -2833,22 +3429,27 @@ void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*) scheduleTimeupdateEvent(true); - if (!m_playbackRate) + if (!requestedPlaybackRate()) return; if (!m_paused && hasMediaControls()) mediaControls()->playbackProgressed(); #if ENABLE(VIDEO_TRACK) - if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) - updateActiveTextTrackCues(currentTime()); + if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) + updateActiveTextTrackCues(currentMediaTime()); +#endif + +#if ENABLE(MEDIA_SOURCE) + if (m_mediaSource) + m_mediaSource->monitorSourceBuffers(); #endif } void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent) { - double now = WTF::currentTime(); - double timedelta = now - m_lastTimeUpdateEventWallTime; + double now = monotonicallyIncreasingTime(); + double timedelta = now - m_clockTimeAtLastUpdateEvent; // throttle the periodic events if (periodicEvent && timedelta < maxTimeupdateEventFrequency) @@ -2856,10 +3457,10 @@ void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent) // Some media engines make multiple "time changed" callbacks at the same time, but we only want one // event at a given time so filter here - double movieTime = currentTime(); + MediaTime movieTime = currentMediaTime(); if (movieTime != m_lastTimeUpdateEventMovieTime) { scheduleEvent(eventNames().timeupdateEvent); - m_lastTimeUpdateEventWallTime = now; + m_clockTimeAtLastUpdateEvent = now; m_lastTimeUpdateEventMovieTime = movieTime; } } @@ -2873,26 +3474,30 @@ double HTMLMediaElement::percentLoaded() const { if (!m_player) return 0; - double duration = m_player->duration(); + MediaTime duration = m_player->duration(); - if (!duration || std::isinf(duration)) + if (!duration || duration.isPositiveInfinite() || duration.isNegativeInfinite()) return 0; - double buffered = 0; - RefPtr<TimeRanges> timeRanges = m_player->buffered(); + MediaTime buffered = MediaTime::zeroTime(); + bool ignored; + std::unique_ptr<PlatformTimeRanges> timeRanges = m_player->buffered(); for (unsigned i = 0; i < timeRanges->length(); ++i) { - double start = timeRanges->start(i, IGNORE_EXCEPTION); - double end = timeRanges->end(i, IGNORE_EXCEPTION); + MediaTime start = timeRanges->start(i, ignored); + MediaTime end = timeRanges->end(i, ignored); buffered += end - start; } - return buffered / duration; + return buffered.toDouble() / duration.toDouble(); } #if ENABLE(VIDEO_TRACK) void HTMLMediaElement::mediaPlayerDidAddAudioTrack(PassRefPtr<AudioTrackPrivate> prpTrack) { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (isPlaying() && !m_mediaSession->playbackPermitted(*this)) + pauseInternal(); + + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return; addAudioTrack(AudioTrack::create(this, prpTrack)); @@ -2900,12 +3505,13 @@ void HTMLMediaElement::mediaPlayerDidAddAudioTrack(PassRefPtr<AudioTrackPrivate> void HTMLMediaElement::mediaPlayerDidAddTextTrack(PassRefPtr<InbandTextTrackPrivate> prpTrack) { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return; // 4.8.10.12.2 Sourcing in-band text tracks // 1. Associate the relevant data with a new text track and its corresponding new TextTrack object. RefPtr<InbandTextTrack> textTrack = InbandTextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, prpTrack); + textTrack->setMediaElement(this); // 2. Set the new text track's kind, label, and language based on the semantics of the relevant data, // as defined by the relevant specification. If there is no label in that data, then the label must @@ -2935,7 +3541,7 @@ void HTMLMediaElement::mediaPlayerDidAddTextTrack(PassRefPtr<InbandTextTrackPriv void HTMLMediaElement::mediaPlayerDidAddVideoTrack(PassRefPtr<VideoTrackPrivate> prpTrack) { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return; addVideoTrack(VideoTrack::create(this, prpTrack)); @@ -2956,91 +3562,15 @@ void HTMLMediaElement::mediaPlayerDidRemoveVideoTrack(PassRefPtr<VideoTrackPriva prpTrack->willBeRemoved(); } -#if USE(PLATFORM_TEXT_TRACK_MENU) -void HTMLMediaElement::setSelectedTextTrack(PassRefPtr<PlatformTextTrack> platformTrack) -{ - if (!m_textTracks) - return; - - TrackDisplayUpdateScope scope(this); - - if (!platformTrack) { - setSelectedTextTrack(TextTrack::captionMenuOffItem()); - return; - } - - TextTrack* textTrack; - if (platformTrack == PlatformTextTrack::captionMenuOffItem()) - textTrack = TextTrack::captionMenuOffItem(); - else if (platformTrack == PlatformTextTrack::captionMenuAutomaticItem()) - textTrack = TextTrack::captionMenuAutomaticItem(); - else { - size_t i; - for (i = 0; i < m_textTracks->length(); ++i) { - textTrack = m_textTracks->item(i); - - if (textTrack->platformTextTrack() == platformTrack) - break; - } - if (i == m_textTracks->length()) - return; - } - - setSelectedTextTrack(textTrack); -} - -Vector<RefPtr<PlatformTextTrack> > HTMLMediaElement::platformTextTracks() -{ - if (!m_textTracks || !m_textTracks->length()) - return Vector<RefPtr<PlatformTextTrack> >(); - - Vector<RefPtr<PlatformTextTrack> > platformTracks; - for (size_t i = 0; i < m_textTracks->length(); ++i) - platformTracks.append(m_textTracks->item(i)->platformTextTrack()); - - return platformTracks; -} - -void HTMLMediaElement::notifyMediaPlayerOfTextTrackChanges() -{ - if (!m_textTracks || !m_textTracks->length() || !platformTextTrackMenu()) - return; - - m_platformMenu->tracksDidChange(); -} - -PlatformTextTrackMenuInterface* HTMLMediaElement::platformTextTrackMenu() -{ - if (m_platformMenu) - return m_platformMenu.get(); - - if (!m_player || !m_player->implementsTextTrackControls()) - return 0; - - m_platformMenu = m_player->textTrackMenu(); - if (!m_platformMenu) - return 0; - - m_platformMenu->setClient(this); - - return m_platformMenu.get(); -} -#endif // #if USE(PLATFORM_TEXT_TRACK_MENU) - void HTMLMediaElement::closeCaptionTracksChanged() { if (hasMediaControls()) mediaControls()->closedCaptionTracksChanged(); - -#if USE(PLATFORM_TEXT_TRACK_MENU) - if (m_player && m_player->implementsTextTrackControls()) - scheduleDelayedAction(TextTrackChangesNotification); -#endif } void HTMLMediaElement::addAudioTrack(PassRefPtr<AudioTrack> track) { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return; audioTracks()->append(track); @@ -3048,9 +3578,17 @@ void HTMLMediaElement::addAudioTrack(PassRefPtr<AudioTrack> track) void HTMLMediaElement::addTextTrack(PassRefPtr<TextTrack> track) { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return; + if (!m_requireCaptionPreferencesChangedCallbacks) { + m_requireCaptionPreferencesChangedCallbacks = true; + Document& document = this->document(); + document.registerForCaptionPreferencesChangedCallbacks(this); + if (Page* page = document.page()) + m_captionDisplayMode = page->group().captionPreferences().captionDisplayMode(); + } + textTracks()->append(track); closeCaptionTracksChanged(); @@ -3058,7 +3596,7 @@ void HTMLMediaElement::addTextTrack(PassRefPtr<TextTrack> track) void HTMLMediaElement::addVideoTrack(PassRefPtr<VideoTrack> track) { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return; videoTracks()->append(track); @@ -3066,36 +3604,36 @@ void HTMLMediaElement::addVideoTrack(PassRefPtr<VideoTrack> track) void HTMLMediaElement::removeAudioTrack(AudioTrack* track) { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return; m_audioTracks->remove(track); } -void HTMLMediaElement::removeTextTrack(TextTrack* track) +void HTMLMediaElement::removeTextTrack(TextTrack* track, bool scheduleEvent) { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return; TrackDisplayUpdateScope scope(this); - TextTrackCueList* cues = track->cues(); - if (cues) + if (TextTrackCueList* cues = track->cues()) textTrackRemoveCues(track, cues); track->clearClient(); - m_textTracks->remove(track); + if (m_textTracks) + m_textTracks->remove(track, scheduleEvent); closeCaptionTracksChanged(); } void HTMLMediaElement::removeVideoTrack(VideoTrack* track) { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return; m_videoTracks->remove(track); } -void HTMLMediaElement::removeAllInbandTracks() +void HTMLMediaElement::forgetResourceSpecificTracks() { while (m_audioTracks && m_audioTracks->length()) removeAudioTrack(m_audioTracks->lastItem()); @@ -3106,7 +3644,7 @@ void HTMLMediaElement::removeAllInbandTracks() TextTrack* track = m_textTracks->item(i); if (track->trackType() == TextTrack::InBand) - removeTextTrack(track); + removeTextTrack(track, false); } } @@ -3116,7 +3654,7 @@ void HTMLMediaElement::removeAllInbandTracks() PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const String& kind, const String& label, const String& language, ExceptionCode& ec) { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return 0; // 4.8.10.12.4 Text track API @@ -3134,7 +3672,7 @@ PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const String& kind, const S // 5. Create a new text track corresponding to the new object, and set its text track kind to kind, its text // track label to label, its text track language to language... - RefPtr<TextTrack> textTrack = TextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, kind, label, language); + RefPtr<TextTrack> textTrack = TextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, kind, emptyString(), label, language); // Note, due to side effects when changing track parameters, we have to // first append the track to the text track list. @@ -3153,7 +3691,7 @@ PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const String& kind, const S AudioTrackList* HTMLMediaElement::audioTracks() { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return 0; if (!m_audioTracks) @@ -3164,7 +3702,7 @@ AudioTrackList* HTMLMediaElement::audioTracks() TextTrackList* HTMLMediaElement::textTracks() { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return 0; if (!m_textTracks) @@ -3175,7 +3713,7 @@ TextTrackList* HTMLMediaElement::textTracks() VideoTrackList* HTMLMediaElement::videoTracks() { - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return 0; if (!m_videoTracks) @@ -3188,7 +3726,7 @@ void HTMLMediaElement::didAddTextTrack(HTMLTrackElement* trackElement) { ASSERT(trackElement->hasTagName(trackTag)); - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return; // 4.8.10.12.3 Sourcing out-of-band text tracks @@ -3214,13 +3752,13 @@ void HTMLMediaElement::didRemoveTextTrack(HTMLTrackElement* trackElement) { ASSERT(trackElement->hasTagName(trackTag)); - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled()) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) return; #if !LOG_DISABLED if (trackElement->hasTagName(trackTag)) { - KURL url = trackElement->getNonEmptyURLAttribute(srcAttr); - LOG(Media, "HTMLMediaElement::didRemoveTrack - 'src' is %s", urlForLoggingMedia(url).utf8().data()); + URL url = trackElement->getNonEmptyURLAttribute(srcAttr); + LOG(Media, "HTMLMediaElement::didRemoveTrack(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data()); } #endif @@ -3248,19 +3786,29 @@ void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group) { ASSERT(group.tracks.size()); - LOG(Media, "HTMLMediaElement::configureTextTrackGroup"); + LOG(Media, "HTMLMediaElement::configureTextTrackGroup(%p)", this); - Page* page = document()->page(); - CaptionUserPreferences* captionPreferences = page? page->group().captionPreferences() : 0; + Page* page = document().page(); + CaptionUserPreferences* captionPreferences = page ? &page->group().captionPreferences() : 0; + CaptionUserPreferences::CaptionDisplayMode displayMode = captionPreferences ? captionPreferences->captionDisplayMode() : CaptionUserPreferences::Automatic; // First, find the track in the group that should be enabled (if any). - Vector<RefPtr<TextTrack> > currentlyEnabledTracks; + Vector<RefPtr<TextTrack>> currentlyEnabledTracks; RefPtr<TextTrack> trackToEnable; RefPtr<TextTrack> defaultTrack; RefPtr<TextTrack> fallbackTrack; RefPtr<TextTrack> forcedSubitleTrack; int highestTrackScore = 0; int highestForcedScore = 0; + + // 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 + // track if it is less suitable, and we do want to disable it if another track is more suitable. + int alreadyVisibleTrackScore = 0; + if (group.visibleTrack && captionPreferences) { + alreadyVisibleTrackScore = captionPreferences->textTrackSelectionScore(group.visibleTrack.get(), this); + currentlyEnabledTracks.append(group.visibleTrack); + } + for (size_t i = 0; i < group.tracks.size(); ++i) { RefPtr<TextTrack> textTrack = group.tracks[i]; @@ -3268,7 +3816,7 @@ void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group) currentlyEnabledTracks.append(textTrack); int trackScore = captionPreferences ? captionPreferences->textTrackSelectionScore(textTrack.get(), this) : 0; - LOG(Media, "HTMLMediaElement::configureTextTrackGroup - '%s' track with language '%s' has score %i", textTrack->kind().string().utf8().data(), textTrack->language().string().utf8().data(), trackScore); + LOG(Media, "HTMLMediaElement::configureTextTrackGroup(%p) - '%s' track with language '%s' has score %i", this, textTrack->kind().string().utf8().data(), textTrack->language().string().utf8().data(), trackScore); if (trackScore) { @@ -3281,7 +3829,7 @@ void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group) // to believe is appropriate for the user, and there is no other text track in the media element's list of // text tracks with a text track kind of chapters whose text track mode is showing // Let the text track mode be showing. - if (trackScore > highestTrackScore) { + if (trackScore > highestTrackScore && trackScore > alreadyVisibleTrackScore) { highestTrackScore = trackScore; trackToEnable = textTrack; } @@ -3298,28 +3846,37 @@ void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group) // * If the track element has a default attribute specified, and there is no other text track in the media // element's list of text tracks whose text track mode is showing or showing by default // Let the text track mode be showing by default. - defaultTrack = textTrack; + if (group.kind != TrackGroup::CaptionsAndSubtitles || displayMode != CaptionUserPreferences::ForcedOnly) + defaultTrack = textTrack; } } - if (!trackToEnable && defaultTrack) - trackToEnable = defaultTrack; + if (displayMode != CaptionUserPreferences::Manual) { + if (!trackToEnable && defaultTrack) + trackToEnable = defaultTrack; - // If no track matches the user's preferred language, none was marked as 'default', and there is a forced subtitle track - // in the same language as the language of the primary audio track, enable it. - if (!trackToEnable && forcedSubitleTrack) - trackToEnable = forcedSubitleTrack; + // If no track matches the user's preferred language, none was marked as 'default', and there is a forced subtitle track + // in the same language as the language of the primary audio track, enable it. + if (!trackToEnable && forcedSubitleTrack) + trackToEnable = forcedSubitleTrack; - // If no track matches the user's preferred language and non was marked 'default', enable the first track - // because the user has explicitly stated a preference for this kind of track. - if (!trackToEnable && fallbackTrack) - trackToEnable = fallbackTrack; + // If no track matches, don't disable an already visible track unless preferences say they all should be off. + if (group.kind != TrackGroup::CaptionsAndSubtitles || displayMode != CaptionUserPreferences::ForcedOnly) { + if (!trackToEnable && !defaultTrack && group.visibleTrack) + trackToEnable = group.visibleTrack; + } + + // If no track matches the user's preferred language and non was marked 'default', enable the first track + // because the user has explicitly stated a preference for this kind of track. + if (!trackToEnable && fallbackTrack) + trackToEnable = fallbackTrack; + + if (trackToEnable) + m_subtitleTrackLanguage = trackToEnable->language(); + else + m_subtitleTrackLanguage = emptyString(); + } - if (!defaultTrack && trackToEnable && trackToEnable != fallbackTrack && m_captionDisplayMode != CaptionUserPreferences::AlwaysOn) - m_forcedOrAutomaticSubtitleTrackLanguage = trackToEnable->language(); - else - m_forcedOrAutomaticSubtitleTrackLanguage = emptyString(); - if (currentlyEnabledTracks.size()) { for (size_t i = 0; i < currentlyEnabledTracks.size(); ++i) { RefPtr<TextTrack> textTrack = currentlyEnabledTracks[i]; @@ -3328,12 +3885,124 @@ void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group) } } - if (trackToEnable) + if (trackToEnable) { trackToEnable->setMode(TextTrack::showingKeyword()); + // If user preferences indicate we should always display captions, make sure we reflect the + // proper status via the webkitClosedCaptionsVisible API call: + if (!webkitClosedCaptionsVisible() && closedCaptionsVisible() && displayMode == CaptionUserPreferences::AlwaysOn) + m_webkitLegacyClosedCaptionOverride = true; + } + m_processingPreferenceChange = false; } +static JSC::JSValue controllerJSValue(JSC::ExecState& exec, JSDOMGlobalObject& globalObject, HTMLMediaElement& media) +{ + auto mediaJSWrapper = toJS(&exec, &globalObject, &media); + + // Retrieve the controller through the JS object graph + JSC::JSObject* mediaJSWrapperObject = JSC::jsDynamicCast<JSC::JSObject*>(mediaJSWrapper); + if (!mediaJSWrapperObject) + return JSC::jsNull(); + + JSC::Identifier controlsHost = JSC::Identifier::fromString(&exec.vm(), "controlsHost"); + JSC::JSValue controlsHostJSWrapper = mediaJSWrapperObject->get(&exec, controlsHost); + if (exec.hadException()) + return JSC::jsNull(); + + JSC::JSObject* controlsHostJSWrapperObject = JSC::jsDynamicCast<JSC::JSObject*>(controlsHostJSWrapper); + if (!controlsHostJSWrapperObject) + return JSC::jsNull(); + + JSC::Identifier controllerID = JSC::Identifier::fromString(&exec.vm(), "controller"); + JSC::JSValue controllerJSWrapper = controlsHostJSWrapperObject->get(&exec, controllerID); + if (exec.hadException()) + return JSC::jsNull(); + + return controllerJSWrapper; +} + +void HTMLMediaElement::ensureMediaControlsShadowRoot() +{ + ASSERT(!m_creatingControls); + m_creatingControls = true; + ensureUserAgentShadowRoot(); + m_creatingControls = false; +} + +void HTMLMediaElement::updateCaptionContainer() +{ + LOG(Media, "HTMLMediaElement::updateCaptionContainer(%p)", this); +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + if (m_haveSetUpCaptionContainer) + return; + + Page* page = document().page(); + if (!page) + return; + + DOMWrapperWorld& world = ensureIsolatedWorld(); + + if (!ensureMediaControlsInjectedScript()) + return; + + ensureMediaControlsShadowRoot(); + + if (!m_mediaControlsHost) + m_mediaControlsHost = MediaControlsHost::create(this); + + ScriptController& scriptController = document().frame()->script(); + JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world)); + JSC::ExecState* exec = globalObject->globalExec(); + JSC::JSLockHolder lock(exec); + + JSC::JSValue controllerValue = controllerJSValue(*exec, *globalObject, *this); + JSC::JSObject* controllerObject = JSC::jsDynamicCast<JSC::JSObject*>(controllerValue); + if (!controllerObject) + return; + + // The media controls script must provide a method on the Controller object with the following details. + // Name: updateCaptionContainer + // Parameters: + // None + // Return value: + // None + JSC::JSValue methodValue = controllerObject->get(exec, JSC::Identifier::fromString(exec, "updateCaptionContainer")); + JSC::JSObject* methodObject = JSC::jsDynamicCast<JSC::JSObject*>(methodValue); + if (!methodObject) + return; + + JSC::CallData callData; + JSC::CallType callType = methodObject->methodTable()->getCallData(methodObject, callData); + if (callType == JSC::CallTypeNone) + return; + + JSC::MarkedArgumentBuffer noArguments; + JSC::call(exec, methodObject, callType, callData, controllerObject, noArguments); + exec->clearException(); + + m_haveSetUpCaptionContainer = true; +#endif +} + +void HTMLMediaElement::layoutSizeChanged() +{ +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + RefPtr<HTMLMediaElement> strongThis = this; + std::function<void()> task = [strongThis] { + if (ShadowRoot* root = strongThis->userAgentShadowRoot()) + root->dispatchEvent(Event::create("resize", false, false)); + }; + m_resizeTaskQueue.enqueueTask(task); +#endif +} + +void HTMLMediaElement::visibilityDidChange() +{ + updateShouldAutoplay(); +} + void HTMLMediaElement::setSelectedTextTrack(TextTrack* trackToSelect) { TextTrackList* trackList = textTracks(); @@ -3351,13 +4020,16 @@ void HTMLMediaElement::setSelectedTextTrack(TextTrack* trackToSelect) else track->setMode(TextTrack::showingKeyword()); } + } else if (trackToSelect == TextTrack::captionMenuOffItem()) { + for (int i = 0, length = trackList->length(); i < length; ++i) + trackList->item(i)->setMode(TextTrack::disabledKeyword()); } - CaptionUserPreferences* captionPreferences = document()->page() ? document()->page()->group().captionPreferences() : 0; - if (!captionPreferences) + if (!document().page()) return; - CaptionUserPreferences::CaptionDisplayMode displayMode = captionPreferences->captionDisplayMode(); + auto& captionPreferences = document().page()->group().captionPreferences(); + CaptionUserPreferences::CaptionDisplayMode displayMode; if (trackToSelect == TextTrack::captionMenuOffItem()) displayMode = CaptionUserPreferences::ForcedOnly; else if (trackToSelect == TextTrack::captionMenuAutomaticItem()) @@ -3365,13 +4037,10 @@ void HTMLMediaElement::setSelectedTextTrack(TextTrack* trackToSelect) else { displayMode = CaptionUserPreferences::AlwaysOn; if (trackToSelect->language().length()) - captionPreferences->setPreferredLanguage(trackToSelect->language()); - - // Set m_captionDisplayMode here so we don't reconfigure again when the preference changed notification comes through. - m_captionDisplayMode = displayMode; + captionPreferences.setPreferredLanguage(trackToSelect->language()); } - captionPreferences->setCaptionDisplayMode(displayMode); + captionPreferences.setCaptionDisplayMode(displayMode); } void HTMLMediaElement::configureTextTracks() @@ -3433,6 +4102,7 @@ void HTMLMediaElement::configureTextTracks() if (otherTracks.tracks.size()) configureTextTrackGroup(otherTracks); + updateCaptionContainer(); configureTextTrackDisplay(); if (hasMediaControls()) mediaControls()->closedCaptionTracksChanged(); @@ -3446,7 +4116,7 @@ bool HTMLMediaElement::havePotentialSourceChild() RefPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode; RefPtr<Node> nextNode = m_nextChildNodeToConsider; - KURL nextURL = selectNextSourceChild(0, 0, DoNothing); + URL nextURL = selectNextSourceChild(0, 0, DoNothing); m_currentSourceNode = currentSourceNode; m_nextChildNodeToConsider = nextNode; @@ -3454,26 +4124,25 @@ bool HTMLMediaElement::havePotentialSourceChild() return nextURL.isValid(); } -KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid) +URL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid) { #if !LOG_DISABLED // Don't log if this was just called to find out if there are any valid <source> elements. bool shouldLog = actionIfInvalid != DoNothing; if (shouldLog) - LOG(Media, "HTMLMediaElement::selectNextSourceChild"); + LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p)", this); #endif if (!m_nextChildNodeToConsider) { #if !LOG_DISABLED if (shouldLog) - LOG(Media, "HTMLMediaElement::selectNextSourceChild -> 0x0000, \"\""); + LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) - end of list, stopping", this); #endif - return KURL(); + return URL(); } - KURL mediaURL; - Node* node; - HTMLSourceElement* source = 0; + URL mediaURL; + HTMLSourceElement* source = nullptr; String type; String system; bool lookingForStartNode = m_nextChildNodeToConsider; @@ -3481,36 +4150,36 @@ KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* k bool okToLoadSourceURL; NodeVector potentialSourceNodes; - getChildNodes(this, potentialSourceNodes); + getChildNodes(*this, potentialSourceNodes); for (unsigned i = 0; !canUseSourceElement && i < potentialSourceNodes.size(); ++i) { - node = potentialSourceNodes[i].get(); - if (lookingForStartNode && m_nextChildNodeToConsider != node) + Node& node = potentialSourceNodes[i].get(); + if (lookingForStartNode && m_nextChildNodeToConsider != &node) continue; lookingForStartNode = false; - if (!node->hasTagName(sourceTag)) + if (!node.hasTagName(sourceTag)) continue; - if (node->parentNode() != this) + if (node.parentNode() != this) continue; - source = static_cast<HTMLSourceElement*>(node); + source = downcast<HTMLSourceElement>(&node); // 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 mediaURL = source->getNonEmptyURLAttribute(srcAttr); #if !LOG_DISABLED if (shouldLog) - LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'src' is %s", urlForLoggingMedia(mediaURL).utf8().data()); + LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) - 'src' is %s", this, urlForLoggingMedia(mediaURL).utf8().data()); #endif if (mediaURL.isEmpty()) goto check_again; if (source->fastHasAttribute(mediaAttr)) { - MediaQueryEvaluator screenEval("screen", document()->frame(), renderer() ? renderer()->style() : 0); - RefPtr<MediaQuerySet> media = MediaQuerySet::createAllowingDescriptionSyntax(source->media()); + MediaQueryEvaluator screenEval("screen", document().frame(), renderer() ? &renderer()->style() : nullptr); + RefPtr<MediaQuerySet> media = source->mediaQuerySet(); #if !LOG_DISABLED if (shouldLog) - LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'media' is %s", source->media().utf8().data()); + LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) - 'media' is %s", this, source->media().utf8().data()); #endif if (!screenEval.eval(media.get())) goto check_again; @@ -3523,9 +4192,23 @@ KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* k if (!type.isEmpty() || !system.isEmpty()) { #if !LOG_DISABLED if (shouldLog) - LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'type' is '%s' - key system is '%s'", type.utf8().data(), system.utf8().data()); + LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) - 'type' is '%s' - key system is '%s'", this, type.utf8().data(), system.utf8().data()); #endif - if (!MediaPlayer::supportsType(ContentType(type), system, mediaURL, this)) + MediaEngineSupportParameters parameters; + ContentType contentType(type); + parameters.type = contentType.type().convertToASCIILowercase(); + parameters.codecs = contentType.parameter(ASCIILiteral("codecs")); + parameters.url = mediaURL; +#if ENABLE(ENCRYPTED_MEDIA) + parameters.keySystem = system; +#endif +#if ENABLE(MEDIA_SOURCE) + parameters.isMediaSource = mediaURL.protocolIs(mediaSourceBlobProtocol); +#endif +#if ENABLE(MEDIA_STREAM) + parameters.isMediaStream = mediaURL.protocolIs(mediaStreamBlobProtocol); +#endif + if (!MediaPlayer::supportsType(parameters, this)) goto check_again; } @@ -3533,8 +4216,8 @@ KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* k okToLoadSourceURL = isSafeToLoadURL(mediaURL, actionIfInvalid) && dispatchBeforeLoadEvent(mediaURL.string()); // A 'beforeload' event handler can mutate the DOM, so check to see if the source element is still a child node. - if (node->parentNode() != this) { - LOG(Media, "HTMLMediaElement::selectNextSourceChild : 'beforeload' removed current element"); + if (node.parentNode() != this) { + LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) - 'beforeload' removed current element", this); source = 0; goto check_again; } @@ -3558,25 +4241,25 @@ check_again: m_currentSourceNode = source; m_nextChildNodeToConsider = source->nextSibling(); } else { - m_currentSourceNode = 0; - m_nextChildNodeToConsider = 0; + m_currentSourceNode = nullptr; + m_nextChildNodeToConsider = nullptr; } #if !LOG_DISABLED if (shouldLog) - LOG(Media, "HTMLMediaElement::selectNextSourceChild -> %p, %s", m_currentSourceNode.get(), canUseSourceElement ? urlForLoggingMedia(mediaURL).utf8().data() : ""); + LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) -> %p, %s", this, m_currentSourceNode.get(), canUseSourceElement ? urlForLoggingMedia(mediaURL).utf8().data() : ""); #endif - return canUseSourceElement ? mediaURL : KURL(); + return canUseSourceElement ? mediaURL : URL(); } void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source) { - LOG(Media, "HTMLMediaElement::sourceWasAdded(%p)", source); + LOG(Media, "HTMLMediaElement::sourceWasAdded(%p) - %p", this, source); #if !LOG_DISABLED if (source->hasTagName(sourceTag)) { - KURL url = source->getNonEmptyURLAttribute(srcAttr); - LOG(Media, "HTMLMediaElement::sourceWasAdded - 'src' is %s", urlForLoggingMedia(url).utf8().data()); + URL url = source->getNonEmptyURLAttribute(srcAttr); + LOG(Media, "HTMLMediaElement::sourceWasAdded(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data()); } #endif @@ -3594,7 +4277,7 @@ void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source) } if (m_currentSourceNode && source == m_currentSourceNode->nextSibling()) { - LOG(Media, "HTMLMediaElement::sourceWasAdded - <source> inserted immediately after current source"); + LOG(Media, "HTMLMediaElement::sourceWasAdded(%p) - <source> inserted immediately after current source", this); m_nextChildNodeToConsider = source; return; } @@ -3619,12 +4302,12 @@ void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source) void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement* source) { - LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p)", source); + LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p) - %p", this, source); #if !LOG_DISABLED if (source->hasTagName(sourceTag)) { - KURL url = source->getNonEmptyURLAttribute(srcAttr); - LOG(Media, "HTMLMediaElement::sourceWasRemoved - 'src' is %s", urlForLoggingMedia(url).utf8().data()); + URL url = source->getNonEmptyURLAttribute(srcAttr); + LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data()); } #endif @@ -3634,23 +4317,23 @@ void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement* source) if (source == m_nextChildNodeToConsider) { if (m_currentSourceNode) m_nextChildNodeToConsider = m_currentSourceNode->nextSibling(); - LOG(Media, "HTMLMediaElement::sourceRemoved - m_nextChildNodeToConsider set to %p", m_nextChildNodeToConsider.get()); + LOG(Media, "HTMLMediaElement::sourceRemoved(%p) - m_nextChildNodeToConsider set to %p", this, m_nextChildNodeToConsider.get()); } else if (source == m_currentSourceNode) { // Clear the current source node pointer, but don't change the movie as the spec says: // 4.8.8 - Dynamically modifying a source element and its attribute when the element is already // inserted in a video or audio element will have no effect. - m_currentSourceNode = 0; - LOG(Media, "HTMLMediaElement::sourceRemoved - m_currentSourceNode set to 0"); + m_currentSourceNode = nullptr; + LOG(Media, "HTMLMediaElement::sourceRemoved(%p) - m_currentSourceNode set to 0", this); } } void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*) { - LOG(Media, "HTMLMediaElement::mediaPlayerTimeChanged"); + LOG(Media, "HTMLMediaElement::mediaPlayerTimeChanged(%p)", this); #if ENABLE(VIDEO_TRACK) - if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) - updateActiveTextTrackCues(currentTime()); + if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) + updateActiveTextTrackCues(currentMediaTime()); #endif beginProcessingMediaPlayerCallback(); @@ -3664,27 +4347,30 @@ void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*) // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity, // it will only queue a 'timeupdate' event if we haven't already posted one at the current // movie time. - scheduleTimeupdateEvent(false); + else + scheduleTimeupdateEvent(false); - double now = currentTime(); - double dur = duration(); - - // When the current playback position reaches the end of the media resource when the direction of - // playback is forwards, then the user agent must follow these steps: - if (!std::isnan(dur) && dur && now >= dur && m_playbackRate > 0) { + MediaTime now = currentMediaTime(); + MediaTime dur = durationMediaTime(); + double playbackRate = requestedPlaybackRate(); + + // When the current playback position reaches the end of the media resource then the user agent must follow these steps: + if (dur && dur.isValid() && !dur.isPositiveInfinite() && !dur.isNegativeInfinite()) { // If the media element has a loop attribute specified and does not have a current media controller, - if (loop() && !m_mediaController) { + if (loop() && !m_mediaController && playbackRate > 0) { m_sentEndEvent = false; - // then seek to the earliest possible position of the media resource and abort these steps. - seek(startTime(), IGNORE_EXCEPTION); - } else { + // then seek to the earliest possible position of the media resource and abort these steps when the direction of + // playback is forwards, + if (now >= dur) + seekInternal(MediaTime::zeroTime()); + } else if ((now <= MediaTime::zeroTime() && playbackRate < 0) || (now >= dur && playbackRate > 0)) { // If the media element does not have a current media controller, and the media element - // has still ended playback, and the direction of playback is still forwards, and paused - // is false, + // has still ended playback and paused is false, if (!m_mediaController && !m_paused) { // changes paused to true and fires a simple event named pause at the media element. m_paused = true; scheduleEvent(eventNames().pauseEvent); + m_mediaSession->clientWillPausePlayback(); } // Queue a task to fire a simple event named ended at the media element. if (!m_sentEndEvent) { @@ -3694,10 +4380,29 @@ void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*) // If the media element has a current media controller, then report the controller state // for the media element's current media controller. updateMediaController(); - } - } - else + } else + m_sentEndEvent = false; + } else { +#if ENABLE(MEDIA_STREAM) + if (m_mediaStreamSrcObject) { + // http://w3c.github.io/mediacapture-main/#event-mediastream-inactive + // 6. MediaStreams in Media Elements + // When the MediaStream state moves from the active to the inactive state, the User Agent + // must raise an ended event on the HTMLMediaElement and set its ended attribute to true. + // Note that once ended equals true the HTMLMediaElement will not play media even if new + // MediaStreamTrack's are added to the MediaStream (causing it to return to the active + // state) unless autoplay is true or the web application restarts the element, e.g., + // by calling play() + if (!m_sentEndEvent && m_player && m_player->ended()) { + m_sentEndEvent = true; + scheduleEvent(eventNames().endedEvent); + m_paused = true; + setPlaying(false); + } + } else +#endif m_sentEndEvent = false; + } updatePlayState(); endProcessingMediaPlayerCallback(); @@ -3705,7 +4410,7 @@ void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*) void HTMLMediaElement::mediaPlayerVolumeChanged(MediaPlayer*) { - LOG(Media, "HTMLMediaElement::mediaPlayerVolumeChanged"); + LOG(Media, "HTMLMediaElement::mediaPlayerVolumeChanged(%p)", this); beginProcessingMediaPlayerCallback(); if (m_player) { @@ -3721,7 +4426,7 @@ void HTMLMediaElement::mediaPlayerVolumeChanged(MediaPlayer*) void HTMLMediaElement::mediaPlayerMuteChanged(MediaPlayer*) { - LOG(Media, "HTMLMediaElement::mediaPlayerMuteChanged"); + LOG(Media, "HTMLMediaElement::mediaPlayerMuteChanged(%p)", this); beginProcessingMediaPlayerCallback(); if (m_player) @@ -3731,43 +4436,42 @@ void HTMLMediaElement::mediaPlayerMuteChanged(MediaPlayer*) void HTMLMediaElement::mediaPlayerDurationChanged(MediaPlayer* player) { - LOG(Media, "HTMLMediaElement::mediaPlayerDurationChanged"); + LOG(Media, "HTMLMediaElement::mediaPlayerDurationChanged(%p)", this); beginProcessingMediaPlayerCallback(); scheduleEvent(eventNames().durationchangeEvent); mediaPlayerCharacteristicChanged(player); - double now = currentTime(); - double dur = duration(); + MediaTime now = currentMediaTime(); + MediaTime dur = durationMediaTime(); if (now > dur) - seek(dur, IGNORE_EXCEPTION); + seekInternal(dur); endProcessingMediaPlayerCallback(); } void HTMLMediaElement::mediaPlayerRateChanged(MediaPlayer*) { - LOG(Media, "HTMLMediaElement::mediaPlayerRateChanged"); - beginProcessingMediaPlayerCallback(); // Stash the rate in case the one we tried to set isn't what the engine is // using (eg. it can't handle the rate we set) - m_playbackRate = m_player->rate(); + m_reportedPlaybackRate = m_player->rate(); + + LOG(Media, "HTMLMediaElement::mediaPlayerRateChanged(%p) - rate: %lf", this, m_reportedPlaybackRate); + if (m_playing) invalidateCachedTime(); -#if PLATFORM(MAC) - updateDisableSleep(); -#endif + updateSleepDisabling(); endProcessingMediaPlayerCallback(); } void HTMLMediaElement::mediaPlayerPlaybackStateChanged(MediaPlayer*) { - LOG(Media, "HTMLMediaElement::mediaPlayerPlaybackStateChanged"); + LOG(Media, "HTMLMediaElement::mediaPlayerPlaybackStateChanged(%p)", this); if (!m_player || m_pausedInternal) return; @@ -3782,20 +4486,18 @@ void HTMLMediaElement::mediaPlayerPlaybackStateChanged(MediaPlayer*) void HTMLMediaElement::mediaPlayerSawUnsupportedTracks(MediaPlayer*) { - LOG(Media, "HTMLMediaElement::mediaPlayerSawUnsupportedTracks"); + LOG(Media, "HTMLMediaElement::mediaPlayerSawUnsupportedTracks(%p)", this); // The MediaPlayer came across content it cannot completely handle. // This is normally acceptable except when we are in a standalone // MediaDocument. If so, tell the document what has happened. - if (ownerDocument()->isMediaDocument()) { - MediaDocument* mediaDocument = toMediaDocument(ownerDocument()); - mediaDocument->mediaElementSawUnsupportedTracks(); - } + if (is<MediaDocument>(document())) + downcast<MediaDocument>(document()).mediaElementSawUnsupportedTracks(); } void HTMLMediaElement::mediaPlayerResourceNotSupported(MediaPlayer*) { - LOG(Media, "HTMLMediaElement::mediaPlayerResourceNotSupported"); + LOG(Media, "HTMLMediaElement::mediaPlayerResourceNotSupported(%p)", this); // The MediaPlayer came across content which no installed engine supports. mediaLoadingFailed(MediaPlayer::FormatError); @@ -3813,80 +4515,124 @@ void HTMLMediaElement::mediaPlayerRepaint(MediaPlayer*) void HTMLMediaElement::mediaPlayerSizeChanged(MediaPlayer*) { - LOG(Media, "HTMLMediaElement::mediaPlayerSizeChanged"); + LOG(Media, "HTMLMediaElement::mediaPlayerSizeChanged(%p)", this); + + if (is<MediaDocument>(document()) && m_player) + downcast<MediaDocument>(document()).mediaElementNaturalSizeChanged(expandedIntSize(m_player->naturalSize())); beginProcessingMediaPlayerCallback(); + if (m_readyState > HAVE_NOTHING) + scheduleResizeEventIfSizeChanged(); if (renderer()) renderer()->updateFromElement(); endProcessingMediaPlayerCallback(); } -#if USE(ACCELERATED_COMPOSITING) bool HTMLMediaElement::mediaPlayerRenderingCanBeAccelerated(MediaPlayer*) { - if (renderer() && renderer()->isVideo()) { - ASSERT(renderer()->view()); - return renderer()->view()->compositor()->canAccelerateVideoRendering(toRenderVideo(renderer())); - } + if (is<RenderVideo>(renderer())) + return renderer()->view().compositor().canAccelerateVideoRendering(downcast<RenderVideo>(*renderer())); return false; } void HTMLMediaElement::mediaPlayerRenderingModeChanged(MediaPlayer*) { - LOG(Media, "HTMLMediaElement::mediaPlayerRenderingModeChanged"); + LOG(Media, "HTMLMediaElement::mediaPlayerRenderingModeChanged(%p)", this); // Kick off a fake recalcStyle that will update the compositing tree. setNeedsStyleRecalc(SyntheticStyleChange); } -#endif #if PLATFORM(WIN) && USE(AVFOUNDATION) GraphicsDeviceAdapter* HTMLMediaElement::mediaPlayerGraphicsDeviceAdapter(const MediaPlayer*) const { - if (!document() || !document()->page()) + if (!document().page()) return 0; - return document()->page()->chrome().client()->graphicsDeviceAdapter(); + return document().page()->chrome().client().graphicsDeviceAdapter(); } #endif void HTMLMediaElement::mediaPlayerEngineUpdated(MediaPlayer*) { - LOG(Media, "HTMLMediaElement::mediaPlayerEngineUpdated"); + LOG(Media, "HTMLMediaElement::mediaPlayerEngineUpdated(%p)", this); beginProcessingMediaPlayerCallback(); if (renderer()) renderer()->updateFromElement(); endProcessingMediaPlayerCallback(); + +#if ENABLE(MEDIA_SOURCE) + m_droppedVideoFrames = 0; +#endif + + m_havePreparedToPlay = false; + + m_mediaSession->mediaEngineUpdated(*this); + +#if ENABLE(WEB_AUDIO) + if (m_audioSourceNode && audioSourceProvider()) { + m_audioSourceNode->lock(); + audioSourceProvider()->setClient(m_audioSourceNode); + m_audioSourceNode->unlock(); + } +#endif + +#if PLATFORM(IOS) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)) + if (!m_player) + return; + m_player->setVideoFullscreenFrame(m_videoFullscreenFrame); + m_player->setVideoFullscreenGravity(m_videoFullscreenGravity); + m_player->setVideoFullscreenLayer(m_videoFullscreenLayer.get()); +#endif +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + updateMediaState(UpdateMediaState::Asynchronously); +#endif } void HTMLMediaElement::mediaPlayerFirstVideoFrameAvailable(MediaPlayer*) { - LOG(Media, "HTMLMediaElement::mediaPlayerFirstVideoFrameAvailable"); + LOG(Media, "HTMLMediaElement::mediaPlayerFirstVideoFrameAvailable(%p) - current display mode = %i", this, (int)displayMode()); + beginProcessingMediaPlayerCallback(); if (displayMode() == PosterWaitingForVideo) { setDisplayMode(Video); -#if USE(ACCELERATED_COMPOSITING) mediaPlayerRenderingModeChanged(m_player.get()); -#endif } endProcessingMediaPlayerCallback(); } void HTMLMediaElement::mediaPlayerCharacteristicChanged(MediaPlayer*) { - LOG(Media, "HTMLMediaElement::mediaPlayerCharacteristicChanged"); + LOG(Media, "HTMLMediaElement::mediaPlayerCharacteristicChanged(%p)", this); beginProcessingMediaPlayerCallback(); #if ENABLE(VIDEO_TRACK) - if (m_forcedOrAutomaticSubtitleTrackLanguage != m_player->languageOfPrimaryAudioTrack()) + if (m_captionDisplayMode == CaptionUserPreferences::Automatic && m_subtitleTrackLanguage != m_player->languageOfPrimaryAudioTrack()) markCaptionAndSubtitleTracksAsUnconfigured(AfterDelay); #endif + if (potentiallyPlaying() && displayMode() == PosterWaitingForVideo) { + setDisplayMode(Video); + mediaPlayerRenderingModeChanged(m_player.get()); + } + if (hasMediaControls()) mediaControls()->reset(); if (renderer()) renderer()->updateFromElement(); + + if (isPlaying() && !m_mediaSession->playbackPermitted(*this)) + pauseInternal(); + + m_mediaSession->setCanProduceAudio(m_player && m_readyState >= HAVE_METADATA && hasAudio()); + +#if ENABLE(MEDIA_SESSION) + document().updateIsPlayingMedia(m_elementID); +#else + document().updateIsPlayingMedia(); +#endif + endProcessingMediaPlayerCallback(); } @@ -3894,13 +4640,29 @@ PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const { if (!m_player) return TimeRanges::create(); - return m_player->buffered(); + +#if ENABLE(MEDIA_SOURCE) + if (m_mediaSource) + return TimeRanges::create(*m_mediaSource->buffered()); +#endif + + return TimeRanges::create(*m_player->buffered()); +} + +double HTMLMediaElement::maxBufferedTime() const +{ + RefPtr<TimeRanges> bufferedRanges = buffered(); + unsigned numRanges = bufferedRanges->length(); + if (!numRanges) + return 0; + + return bufferedRanges->end(numRanges - 1, ASSERT_NO_EXCEPTION); } PassRefPtr<TimeRanges> HTMLMediaElement::played() { if (m_playing) { - double time = currentTime(); + MediaTime time = currentMediaTime(); if (time > m_lastSeekTime) addPlayedRange(m_lastSeekTime, time); } @@ -3913,27 +4675,47 @@ PassRefPtr<TimeRanges> HTMLMediaElement::played() PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const { - return m_player ? m_player->seekable() : TimeRanges::create(); + if (m_player) + return TimeRanges::create(*m_player->seekable()); + + return TimeRanges::create(); } bool HTMLMediaElement::potentiallyPlaying() const { - // "pausedToBuffer" means the media engine's rate is 0, but only because it had to stop playing - // when it ran out of buffered data. A movie is this state is "potentially playing", modulo the - // checks in couldPlayIfEnoughData(). - bool pausedToBuffer = m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA; - return (pausedToBuffer || m_readyState >= HAVE_FUTURE_DATA) && couldPlayIfEnoughData() && !isBlockedOnMediaController(); + if (isBlockedOnMediaController()) + return false; + + if (!couldPlayIfEnoughData()) + return false; + + if (m_readyState >= HAVE_FUTURE_DATA) + return true; + + return m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA; } bool HTMLMediaElement::couldPlayIfEnoughData() const { - return !paused() && !endedPlayback() && !stoppedDueToErrors() && !pausedForUserInteraction(); + if (paused()) + return false; + + if (endedPlayback()) + return false; + + if (stoppedDueToErrors()) + return false; + + if (pausedForUserInteraction()) + return false; + + return true; } bool HTMLMediaElement::endedPlayback() const { - double dur = duration(); - if (!m_player || std::isnan(dur)) + MediaTime dur = durationMediaTime(); + if (!m_player || !dur.isValid()) return false; // 4.8.10.8 Playing the media resource @@ -3946,14 +4728,14 @@ bool HTMLMediaElement::endedPlayback() const // and the current playback position is the end of the media resource and the direction // of playback is forwards, Either the media element does not have a loop attribute specified, // or the media element has a current media controller. - double now = currentTime(); - if (m_playbackRate > 0) - return dur > 0 && now >= dur && (!loop() || m_mediaController); + MediaTime now = currentMediaTime(); + if (requestedPlaybackRate() > 0) + return dur > MediaTime::zeroTime() && now >= dur && (!loop() || m_mediaController); // or the current playback position is the earliest possible position and the direction // of playback is backwards - if (m_playbackRate < 0) - return now <= 0; + if (requestedPlaybackRate() < 0) + return now <= MediaTime::zeroTime(); return false; } @@ -3971,42 +4753,58 @@ bool HTMLMediaElement::stoppedDueToErrors() const bool HTMLMediaElement::pausedForUserInteraction() const { -// return !paused() && m_readyState >= HAVE_FUTURE_DATA && [UA requires a decitions from the user] + if (m_mediaSession->state() == PlatformMediaSession::Interrupted) + return true; + return false; } -double HTMLMediaElement::minTimeSeekable() const +MediaTime HTMLMediaElement::minTimeSeekable() const { - return m_player ? m_player->minTimeSeekable() : 0; + return m_player ? m_player->minTimeSeekable() : MediaTime::zeroTime(); } -double HTMLMediaElement::maxTimeSeekable() const +MediaTime HTMLMediaElement::maxTimeSeekable() const { - return m_player ? m_player->maxTimeSeekable() : 0; + return m_player ? m_player->maxTimeSeekable() : MediaTime::zeroTime(); } void HTMLMediaElement::updateVolume() { +#if PLATFORM(IOS) + // Only the user can change audio volume so update the cached volume and post the changed event. + float volume = m_player->volume(); + if (m_volume != volume) { + m_volume = volume; + scheduleEvent(eventNames().volumechangeEvent); + } +#else if (!m_player) return; // Avoid recursion when the player reports volume changes. if (!processingMediaPlayerCallback()) { - Page* page = document()->page(); + Page* page = document().page(); double volumeMultiplier = page ? page->mediaVolume() : 1; - bool shouldMute = m_muted; + bool shouldMute = effectiveMuted(); if (m_mediaController) { volumeMultiplier *= m_mediaController->volume(); - shouldMute = m_mediaController->muted(); + shouldMute = m_mediaController->muted() || (page && page->isMuted()); } +#if ENABLE(MEDIA_SESSION) + if (m_shouldDuck) + volumeMultiplier *= 0.25; +#endif + m_player->setMuted(shouldMute); m_player->setVolume(m_volume * volumeMultiplier); } if (hasMediaControls()) mediaControls()->changedVolume(); +#endif } void HTMLMediaElement::updatePlayState() @@ -4028,41 +4826,48 @@ void HTMLMediaElement::updatePlayState() bool shouldBePlaying = potentiallyPlaying(); bool playerPaused = m_player->paused(); - LOG(Media, "HTMLMediaElement::updatePlayState - shouldBePlaying = %s, playerPaused = %s", - boolString(shouldBePlaying), boolString(playerPaused)); + LOG(Media, "HTMLMediaElement::updatePlayState(%p) - shouldBePlaying = %s, playerPaused = %s", this, boolString(shouldBePlaying), boolString(playerPaused)); if (shouldBePlaying) { setDisplayMode(Video); invalidateCachedTime(); if (playerPaused) { - if (!m_isFullscreen && isVideo() && document() && document()->page() && document()->page()->chrome().requiresFullscreenForVideoPlayback()) + m_mediaSession->clientWillBeginPlayback(); + + if (m_mediaSession->requiresFullscreenForVideoPlayback(*this) && !isFullscreen()) enterFullscreen(); // Set rate, muted before calling play in case they were set before the media engine was setup. // The media engine should just stash the rate and muted values since it isn't already playing. - m_player->setRate(m_playbackRate); - m_player->setMuted(m_muted); + m_player->setRate(requestedPlaybackRate()); + m_player->setMuted(effectiveMuted()); + + if (m_firstTimePlaying) { + // Log that a media element was played. + if (Frame* frame = document().frame()) + frame->mainFrame().diagnosticLoggingClient().logDiagnosticMessageWithValue(DiagnosticLoggingKeys::mediaKey(), isVideo() ? DiagnosticLoggingKeys::videoKey() : DiagnosticLoggingKeys::audioKey(), DiagnosticLoggingKeys::playedKey(), ShouldSample::No); + m_firstTimePlaying = false; + } m_player->play(); } if (hasMediaControls()) mediaControls()->playbackStarted(); - if (document()->page()) - m_activityToken = document()->page()->createActivityToken(); + if (document().page()) + m_activityToken = document().page()->pageThrottler().mediaActivityToken(); startPlaybackProgressTimer(); - m_playing = true; - - } else { // Should not be playing right now + setPlaying(true); + } else { if (!playerPaused) m_player->pause(); refreshCachedTime(); m_playbackProgressTimer.stop(); - m_playing = false; - double time = currentTime(); + setPlaying(false); + MediaTime time = currentMediaTime(); if (time > m_lastSeekTime) addPlayedRange(m_lastSeekTime, time); @@ -4073,13 +4878,31 @@ void HTMLMediaElement::updatePlayState() mediaControls()->playbackStopped(); m_activityToken = nullptr; } - + updateMediaController(); if (renderer()) renderer()->updateFromElement(); } +void HTMLMediaElement::setPlaying(bool playing) +{ + if (m_playing == playing) + return; + + m_playing = playing; + +#if ENABLE(MEDIA_SESSION) + document().updateIsPlayingMedia(m_elementID); +#else + document().updateIsPlayingMedia(); +#endif + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + updateMediaState(UpdateMediaState::Asynchronously); +#endif +} + void HTMLMediaElement::setPausedInternal(bool b) { m_pausedInternal = b; @@ -4094,15 +4917,21 @@ void HTMLMediaElement::stopPeriodicTimers() void HTMLMediaElement::userCancelledLoad() { - LOG(Media, "HTMLMediaElement::userCancelledLoad"); + LOG(Media, "HTMLMediaElement::userCancelledLoad(%p)", this); + // FIXME: We should look to reconcile the iOS and non-iOS code (below). +#if PLATFORM(IOS) + if (m_networkState == NETWORK_EMPTY || m_readyState >= HAVE_METADATA) + return; +#else if (m_networkState == NETWORK_EMPTY || m_completelyLoaded) return; +#endif // If the media data fetching process is aborted by the user: // 1 - The user agent should cancel the fetching process. - clearMediaPlayer(-1); + clearMediaPlayer(EveryDelayedAction); // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED. m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED); @@ -4111,7 +4940,7 @@ void HTMLMediaElement::userCancelledLoad() scheduleEvent(eventNames().abortEvent); #if ENABLE(MEDIA_SOURCE) - setSourceState(MediaSource::closedKeyword()); + closeMediaSource(); #endif // 4 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the @@ -4129,40 +4958,49 @@ void HTMLMediaElement::userCancelledLoad() setShouldDelayLoadEvent(false); // 6 - Abort the overall resource selection algorithm. - m_currentSourceNode = 0; + m_currentSourceNode = nullptr; // Reset m_readyState since m_player is gone. m_readyState = HAVE_NOTHING; updateMediaController(); #if ENABLE(VIDEO_TRACK) - if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) - updateActiveTextTrackCues(0); + if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) + updateActiveTextTrackCues(MediaTime::zeroTime()); #endif } -void HTMLMediaElement::clearMediaPlayer(int flags) +void HTMLMediaElement::clearMediaPlayer(DelayedActionType flags) { -#if USE(PLATFORM_TEXT_TRACK_MENU) - if (platformTextTrackMenu()) { - m_platformMenu->setClient(0); - m_platformMenu = 0; - } -#endif + LOG(Media, "HTMLMediaElement::clearMediaPlayer(%p) - flags = %s", this, actionName(flags).utf8().data()); #if ENABLE(VIDEO_TRACK) - removeAllInbandTracks(); + forgetResourceSpecificTracks(); #endif -#if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) - #if ENABLE(MEDIA_SOURCE) - setSourceState(MediaSource::closedKeyword()); + closeMediaSource(); #endif - m_player.clear(); +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) { + m_hasPlaybackTargetAvailabilityListeners = false; + m_mediaSession->setHasPlaybackTargetAvailabilityListeners(*this, false); + + // Send an availability event in case scripts want to hide the picker when the element + // doesn't support playback to a target. + enqueuePlaybackTargetAvailabilityChangedEvent(); + } #endif + + if (m_isWaitingUntilMediaCanStart) { + m_isWaitingUntilMediaCanStart = false; + document().removeMediaCanStartListener(this); + } + + m_player = nullptr; + stopPeriodicTimers(); - m_loadTimer.stop(); + m_pendingActionTimer.stop(); clearFlags(m_pendingActionFlags, flags); m_loadState = WaitingForSource; @@ -4171,52 +5009,84 @@ void HTMLMediaElement::clearMediaPlayer(int flags) if (m_textTracks) configureTextTrackDisplay(); #endif + + m_mediaSession->setCanProduceAudio(false); + + updateSleepDisabling(); } -bool HTMLMediaElement::canSuspend() const +bool HTMLMediaElement::canSuspendForDocumentSuspension() const { return true; } -void HTMLMediaElement::stop() +const char* HTMLMediaElement::activeDOMObjectName() const { - LOG(Media, "HTMLMediaElement::stop"); - if (m_isFullscreen) + return "HTMLMediaElement"; +} + +void HTMLMediaElement::stopWithoutDestroyingMediaPlayer() +{ + LOG(Media, "HTMLMediaElement::stopWithoutDestroyingMediaPlayer(%p)", this); + + if (m_videoFullscreenMode != VideoFullscreenModeNone) exitFullscreen(); m_inActiveDocument = false; - userCancelledLoad(); - + // Stop the playback without generating events - m_playing = false; + setPlaying(false); setPausedInternal(true); - + m_mediaSession->clientWillPausePlayback(); + + userCancelledLoad(); + if (renderer()) renderer()->updateFromElement(); stopPeriodicTimers(); - cancelPendingEventsAndCallbacks(); - m_asyncEventQueue->close(); + updateSleepDisabling(); +} + +void HTMLMediaElement::contextDestroyed() +{ + m_seekTaskQueue.close(); + m_resizeTaskQueue.close(); + m_shadowDOMTaskQueue.close(); + + ActiveDOMObject::contextDestroyed(); +} + +void HTMLMediaElement::stop() +{ + LOG(Media, "HTMLMediaElement::stop(%p)", this); + + stopWithoutDestroyingMediaPlayer(); + + m_asyncEventQueue.close(); // Once an active DOM object has been stopped it can not be restarted, so we can deallocate - // the media player now. Note that userCancelledLoad will already have cleared the player - // if the media was not fully loaded. This handles all other cases. - m_player.clear(); + // the media player now. Note that userCancelledLoad will already called clearMediaPlayer + // if the media was not fully loaded, but we need the same cleanup if the file was completely + // loaded and calling it again won't cause any problems. + clearMediaPlayer(EveryDelayedAction); } void HTMLMediaElement::suspend(ReasonForSuspension why) { - LOG(Media, "HTMLMediaElement::suspend"); + LOG(Media, "HTMLMediaElement::suspend(%p)", this); switch (why) { - case DocumentWillBecomeInactive: - stop(); - addBehaviorRestriction(RequirePageConsentToResumeMediaRestriction); + case PageCache: + stopWithoutDestroyingMediaPlayer(); + m_asyncEventQueue.suspend(); + setShouldBufferData(false); + m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePageConsentToResumeMedia); break; - case PageWillBeSuspended: case JavaScriptDebuggerPaused: + case PageWillBeSuspended: case WillDeferLoading: // Do nothing, we don't pause media playback in these cases. break; @@ -4225,17 +5095,20 @@ void HTMLMediaElement::suspend(ReasonForSuspension why) void HTMLMediaElement::resume() { - LOG(Media, "HTMLMediaElement::resume"); + LOG(Media, "HTMLMediaElement::resume(%p)", this); m_inActiveDocument = true; - Page* page = document()->page(); - if (pageConsentRequiredForResume() && page && !page->canStartMedia()) - document()->addMediaCanStartListener(this); + m_asyncEventQueue.resume(); + + setShouldBufferData(true); + + if (!m_mediaSession->pageAllowsPlaybackAfterResuming(*this)) + document().addMediaCanStartListener(this); else setPausedInternal(false); - removeBehaviorRestriction(RequirePageConsentToResumeMediaRestriction); + m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequirePageConsentToResumeMedia); if (m_error && m_error->code() == MediaError::MEDIA_ERR_ABORTED) { // Restart the load if it was aborted in the middle by moving the document to the page cache. @@ -4252,43 +5125,27 @@ void HTMLMediaElement::resume() bool HTMLMediaElement::hasPendingActivity() const { - return (hasAudio() && isPlaying()) || m_asyncEventQueue->hasPendingEvents(); + return (hasAudio() && isPlaying()) || m_asyncEventQueue.hasPendingEvents() || m_creatingControls; } void HTMLMediaElement::mediaVolumeDidChange() { - LOG(Media, "HTMLMediaElement::mediaVolumeDidChange"); + LOG(Media, "HTMLMediaElement::mediaVolumeDidChange(%p)", this); updateVolume(); } -void HTMLMediaElement::defaultEventHandler(Event* event) +void HTMLMediaElement::visibilityStateChanged() { -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) - RenderObject* r = renderer(); - if (!r || !r->isWidget()) - return; - - Widget* widget = toRenderWidget(r)->widget(); - if (widget) - widget->handleEvent(event); -#else - HTMLElement::defaultEventHandler(event); -#endif -} - -bool HTMLMediaElement::willRespondToMouseClickEvents() -{ -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) - return true; -#else - return HTMLElement::willRespondToMouseClickEvents(); -#endif + m_elementIsHidden = document().hidden(); + LOG(Media, "HTMLMediaElement::visibilityStateChanged(%p) - visible = %s", this, boolString(!m_elementIsHidden)); + updateSleepDisabling(); + m_mediaSession->visibilityChanged(); } #if ENABLE(VIDEO_TRACK) bool HTMLMediaElement::requiresTextTrackRepresentation() const { - return m_player ? m_player->requiresTextTrackRepresentation() : 0; + return (m_videoFullscreenMode != VideoFullscreenModeNone) && m_player ? m_player->requiresTextTrackRepresentation() : false; } void HTMLMediaElement::setTextTrackRepresentation(TextTrackRepresentation* representation) @@ -4296,114 +5153,155 @@ void HTMLMediaElement::setTextTrackRepresentation(TextTrackRepresentation* repre if (m_player) m_player->setTextTrackRepresentation(representation); } + +void HTMLMediaElement::syncTextTrackBounds() +{ + if (m_player) + m_player->syncTextTrackBounds(); +} #endif // ENABLE(VIDEO_TRACK) -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) +#if ENABLE(WIRELESS_PLAYBACK_TARGET) +void HTMLMediaElement::webkitShowPlaybackTargetPicker() +{ + LOG(Media, "HTMLMediaElement::webkitShowPlaybackTargetPicker(%p)", this); + m_mediaSession->showPlaybackTargetPicker(*this); +} -void HTMLMediaElement::ensureMediaPlayer() +bool HTMLMediaElement::webkitCurrentPlaybackTargetIsWireless() const { - if (!m_player) - createMediaPlayer(); + return m_player && m_player->isCurrentPlaybackTargetWireless(); } -void HTMLMediaElement::deliverNotification(MediaPlayerProxyNotificationType notification) +void HTMLMediaElement::wirelessRoutesAvailableDidChange() { - if (notification == MediaPlayerNotificationPlayPauseButtonPressed) { - togglePlayState(); - return; - } + enqueuePlaybackTargetAvailabilityChangedEvent(); +} - if (m_player) - m_player->deliverNotification(notification); +void HTMLMediaElement::mediaPlayerCurrentPlaybackTargetIsWirelessChanged(MediaPlayer*) +{ + LOG(Media, "HTMLMediaElement::mediaPlayerCurrentPlaybackTargetIsWirelessChanged(%p) - webkitCurrentPlaybackTargetIsWireless = %s", this, boolString(webkitCurrentPlaybackTargetIsWireless())); + ASSERT(m_player); + configureMediaControls(); + scheduleEvent(eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent); + m_mediaSession->isPlayingToWirelessPlaybackTargetChanged(m_player->isCurrentPlaybackTargetWireless()); + updateMediaState(UpdateMediaState::Asynchronously); } -void HTMLMediaElement::setMediaPlayerProxy(WebMediaPlayerProxy* proxy) +bool HTMLMediaElement::dispatchEvent(Event& event) { - ensureMediaPlayer(); - m_player->setMediaPlayerProxy(proxy); + if (event.type() == eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent) + scheduleDelayedAction(CheckPlaybackTargetCompatablity); + return HTMLElement::dispatchEvent(event); } -void HTMLMediaElement::getPluginProxyParams(KURL& url, Vector<String>& names, Vector<String>& values) +bool HTMLMediaElement::addEventListener(const AtomicString& eventType, RefPtr<EventListener>&& listener, bool useCapture) { - RefPtr<HTMLMediaElement> protect(this); // selectNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations. + if (eventType != eventNames().webkitplaybacktargetavailabilitychangedEvent) + return Node::addEventListener(eventType, WTFMove(listener), useCapture); - Frame* frame = document()->frame(); + bool isFirstAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent); + if (!Node::addEventListener(eventType, WTFMove(listener), useCapture)) + return false; - if (isVideo()) { - HTMLVideoElement* video = toHTMLVideoElement(this); - KURL posterURL = video->posterImageURL(); - if (!posterURL.isEmpty() && frame && frame->loader()->willLoadMediaElementURL(posterURL)) { - names.append(ASCIILiteral("_media_element_poster_")); - values.append(posterURL.string()); - } + if (isFirstAvailabilityChangedListener) { + m_hasPlaybackTargetAvailabilityListeners = true; + m_mediaSession->setHasPlaybackTargetAvailabilityListeners(*this, true); } - if (controls()) { - names.append(ASCIILiteral("_media_element_controls_")); - values.append(ASCIILiteral("true")); - } + LOG(Media, "HTMLMediaElement::addEventListener(%p) - 'webkitplaybacktargetavailabilitychanged'", this); + + enqueuePlaybackTargetAvailabilityChangedEvent(); // Ensure the event listener gets at least one event. + return true; +} - url = src(); - if (!isSafeToLoadURL(url, Complain)) - url = selectNextSourceChild(0, 0, DoNothing); +bool HTMLMediaElement::removeEventListener(const AtomicString& eventType, EventListener* listener, bool useCapture) +{ + if (eventType != eventNames().webkitplaybacktargetavailabilitychangedEvent) + return Node::removeEventListener(eventType, listener, useCapture); - m_currentSrc = url; - if (url.isValid() && frame && frame->loader()->willLoadMediaElementURL(url)) { - names.append(ASCIILiteral("_media_element_src_")); - values.append(m_currentSrc.string()); + if (!Node::removeEventListener(eventType, listener, useCapture)) + return false; + + bool didRemoveLastAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent); + LOG(Media, "HTMLMediaElement::removeEventListener(%p) - removed last listener = %s", this, boolString(didRemoveLastAvailabilityChangedListener)); + if (didRemoveLastAvailabilityChangedListener) { + m_hasPlaybackTargetAvailabilityListeners = false; + m_mediaSession->setHasPlaybackTargetAvailabilityListeners(*this, false); + updateMediaState(UpdateMediaState::Asynchronously); } + + return true; } -void HTMLMediaElement::createMediaPlayerProxy() +void HTMLMediaElement::enqueuePlaybackTargetAvailabilityChangedEvent() { - ensureMediaPlayer(); + bool hasTargets = m_mediaSession->hasWirelessPlaybackTargets(*this); + LOG(Media, "HTMLMediaElement::enqueuePlaybackTargetAvailabilityChangedEvent(%p) - hasTargets = %s", this, boolString(hasTargets)); + RefPtr<Event> event = WebKitPlaybackTargetAvailabilityEvent::create(eventNames().webkitplaybacktargetavailabilitychangedEvent, hasTargets); + event->setTarget(this); + m_asyncEventQueue.enqueueEvent(event.release()); + updateMediaState(UpdateMediaState::Asynchronously); +} - if (m_proxyWidget || (inDocument() && !m_needWidgetUpdate)) - return; +void HTMLMediaElement::setWirelessPlaybackTarget(Ref<MediaPlaybackTarget>&& device) +{ + LOG(Media, "HTMLMediaElement::setWirelessPlaybackTarget(%p)", this); + if (m_player) + m_player->setWirelessPlaybackTarget(WTFMove(device)); +} - Frame* frame = document()->frame(); - if (!frame) - return; +bool HTMLMediaElement::canPlayToWirelessPlaybackTarget() const +{ + bool canPlay = m_player && m_player->canPlayToWirelessPlaybackTarget(); - LOG(Media, "HTMLMediaElement::createMediaPlayerProxy"); + LOG(Media, "HTMLMediaElement::canPlayToWirelessPlaybackTarget(%p) - returning %s", this, boolString(canPlay)); - KURL url; - Vector<String> paramNames; - Vector<String> paramValues; + return canPlay; +} - getPluginProxyParams(url, paramNames, paramValues); +bool HTMLMediaElement::isPlayingToWirelessPlaybackTarget() const +{ + bool isPlaying = m_player && m_player->isCurrentPlaybackTargetWireless(); + + LOG(Media, "HTMLMediaElement::isPlayingToWirelessPlaybackTarget(%p) - returning %s", this, boolString(isPlaying)); - // Hang onto the proxy widget so it won't be destroyed if the plug-in is set to - // display:none - m_proxyWidget = frame->loader()->subframeLoader()->loadMediaPlayerProxyPlugin(this, url, paramNames, paramValues); - if (m_proxyWidget) - m_needWidgetUpdate = false; + return isPlaying; } -void HTMLMediaElement::updateWidget(PluginCreationOption) +void HTMLMediaElement::setShouldPlayToPlaybackTarget(bool shouldPlay) { - mediaElement->setNeedWidgetUpdate(false); + LOG(Media, "HTMLMediaElement::setShouldPlayToPlaybackTarget(%p) - shouldPlay = %s", this, boolString(shouldPlay)); - Vector<String> paramNames; - Vector<String> paramValues; - // FIXME: Rename kurl to something more sensible. - KURL kurl; + if (m_player) + m_player->setShouldPlayToPlaybackTarget(shouldPlay); +} +#else // ENABLE(WIRELESS_PLAYBACK_TARGET) - mediaElement->getPluginProxyParams(kurl, paramNames, paramValues); - // FIXME: What if document()->frame() is 0? - SubframeLoader* loader = document()->frame()->loader()->subframeLoader(); - loader->loadMediaPlayerProxyPlugin(mediaElement, kurl, paramNames, paramValues); +bool HTMLMediaElement::webkitCurrentPlaybackTargetIsWireless() const +{ + return false; } -#endif // ENABLE(PLUGIN_PROXY_FOR_VIDEO) +#endif // ENABLE(WIRELESS_PLAYBACK_TARGET) +double HTMLMediaElement::minFastReverseRate() const +{ + return m_player ? m_player->minFastReverseRate() : 0; +} + +double HTMLMediaElement::maxFastForwardRate() const +{ + return m_player ? m_player->maxFastForwardRate() : 0; +} + bool HTMLMediaElement::isFullscreen() const { - if (m_isFullscreen) + if (m_videoFullscreenMode != VideoFullscreenModeNone) return true; #if ENABLE(FULLSCREEN_API) - if (document()->webkitIsFullScreen() && document()->webkitCurrentFullScreenElement() == this) + if (document().webkitIsFullScreen() && document().webkitCurrentFullScreenElement() == this) return true; #endif @@ -4412,7 +5310,7 @@ bool HTMLMediaElement::isFullscreen() const void HTMLMediaElement::toggleFullscreenState() { - LOG(Media, "HTMLMediaElement::toggleFullscreenState - isFullscreen() is %s", boolString(isFullscreen())); + LOG(Media, "HTMLMediaElement::toggleFullscreenState(%p) - isFullscreen() is %s", this, boolString(isFullscreen())); if (isFullscreen()) exitFullscreen(); @@ -4420,46 +5318,63 @@ void HTMLMediaElement::toggleFullscreenState() enterFullscreen(); } -void HTMLMediaElement::enterFullscreen() +void HTMLMediaElement::enterFullscreen(VideoFullscreenMode mode) { - LOG(Media, "HTMLMediaElement::enterFullscreen"); + LOG(Media, "HTMLMediaElement::enterFullscreen(%p)", this); + ASSERT(mode != VideoFullscreenModeNone); + + if (m_videoFullscreenMode == mode) + return; #if ENABLE(FULLSCREEN_API) - if (document() && document()->settings() && document()->settings()->fullScreenEnabled()) { - document()->requestFullScreenForElement(this, 0, Document::ExemptIFrameAllowFullScreenRequirement); + if (mode == VideoFullscreenModeStandard && document().settings() && document().settings()->fullScreenEnabled()) { + document().requestFullScreenForElement(this, 0, Document::ExemptIFrameAllowFullScreenRequirement); return; } #endif - ASSERT(!m_isFullscreen); - m_isFullscreen = true; + + fullscreenModeChanged(mode); if (hasMediaControls()) mediaControls()->enteredFullscreen(); - if (document() && document()->page()) { - document()->page()->chrome().client()->enterFullscreenForNode(this); - scheduleEvent(eventNames().webkitbeginfullscreenEvent); + if (document().page() && is<HTMLVideoElement>(*this)) { + HTMLVideoElement& asVideo = downcast<HTMLVideoElement>(*this); + if (document().page()->chrome().client().supportsVideoFullscreen(m_videoFullscreenMode)) { + document().page()->chrome().client().enterVideoFullscreenForVideoElement(asVideo, m_videoFullscreenMode); + scheduleEvent(eventNames().webkitbeginfullscreenEvent); + } } } +void HTMLMediaElement::enterFullscreen() +{ + enterFullscreen(VideoFullscreenModeStandard); +} + void HTMLMediaElement::exitFullscreen() { - LOG(Media, "HTMLMediaElement::exitFullscreen"); + LOG(Media, "HTMLMediaElement::exitFullscreen(%p)", this); #if ENABLE(FULLSCREEN_API) - if (document() && document()->settings() && document()->settings()->fullScreenEnabled()) { - if (document()->webkitIsFullScreen() && document()->webkitCurrentFullScreenElement() == this) - document()->webkitCancelFullScreen(); + if (document().settings() && document().settings()->fullScreenEnabled() && document().webkitCurrentFullScreenElement() == this) { + if (document().webkitIsFullScreen()) + document().webkitCancelFullScreen(); return; } #endif - ASSERT(m_isFullscreen); - m_isFullscreen = false; + + ASSERT(m_videoFullscreenMode != VideoFullscreenModeNone); + VideoFullscreenMode oldVideoFullscreenMode = m_videoFullscreenMode; + fullscreenModeChanged(VideoFullscreenModeNone); if (hasMediaControls()) mediaControls()->exitedFullscreen(); - if (document() && document()->page()) { - if (document()->page()->chrome().requiresFullscreenForVideoPlayback()) + if (document().page() && is<HTMLVideoElement>(*this)) { + if (m_mediaSession->requiresFullscreenForVideoPlayback(*this)) pauseInternal(); - document()->page()->chrome().client()->exitFullscreenForNode(this); - scheduleEvent(eventNames().webkitendfullscreenEvent); + + if (document().page()->chrome().client().supportsVideoFullscreen(oldVideoFullscreenMode)) { + document().page()->chrome().client().exitVideoFullscreenForVideoElement(downcast<HTMLVideoElement>(*this)); + scheduleEvent(eventNames().webkitendfullscreenEvent); + } } } @@ -4480,10 +5395,38 @@ PlatformMedia HTMLMediaElement::platformMedia() const return m_player ? m_player->platformMedia() : NoPlatformMedia; } -#if USE(ACCELERATED_COMPOSITING) PlatformLayer* HTMLMediaElement::platformLayer() const { - return m_player ? m_player->platformLayer() : 0; + return m_player ? m_player->platformLayer() : nullptr; +} + +#if PLATFORM(IOS) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)) +void HTMLMediaElement::setVideoFullscreenLayer(PlatformLayer* platformLayer) +{ + m_videoFullscreenLayer = platformLayer; + if (!m_player) + return; + + m_player->setVideoFullscreenLayer(platformLayer); + setNeedsStyleRecalc(SyntheticStyleChange); +#if ENABLE(VIDEO_TRACK) + if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) + updateTextTrackDisplay(); +#endif +} + +void HTMLMediaElement::setVideoFullscreenFrame(FloatRect frame) +{ + m_videoFullscreenFrame = frame; + if (m_player) + m_player->setVideoFullscreenFrame(frame); +} + +void HTMLMediaElement::setVideoFullscreenGravity(MediaPlayer::VideoGravity gravity) +{ + m_videoFullscreenGravity = gravity; + if (m_player) + m_player->setVideoFullscreenGravity(gravity); } #endif @@ -4493,7 +5436,7 @@ bool HTMLMediaElement::hasClosedCaptions() const return true; #if ENABLE(VIDEO_TRACK) - if (!RuntimeEnabledFeatures::webkitVideoTrackEnabled() || !m_textTracks) + if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() || !m_textTracks) return false; for (unsigned i = 0; i < m_textTracks->length(); ++i) { @@ -4515,18 +5458,26 @@ bool HTMLMediaElement::closedCaptionsVisible() const } #if ENABLE(VIDEO_TRACK) + void HTMLMediaElement::updateTextTrackDisplay() { +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + ensureMediaControlsShadowRoot(); + ASSERT(m_mediaControlsHost); + m_mediaControlsHost->updateTextTrackContainer(); +#else if (!hasMediaControls() && !createMediaControls()) return; mediaControls()->updateTextTrackDisplay(); +#endif } + #endif void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible) { - LOG(Media, "HTMLMediaElement::setClosedCaptionsVisible(%s)", boolString(closedCaptionVisible)); + LOG(Media, "HTMLMediaElement::setClosedCaptionsVisible(%p) - %s", this, boolString(closedCaptionVisible)); m_closedCaptionsVisible = false; @@ -4537,7 +5488,7 @@ void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible) m_player->setClosedCaptionsVisible(closedCaptionVisible); #if ENABLE(VIDEO_TRACK) - if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) { + if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) { markCaptionAndSubtitleTracksAsUnconfigured(Immediately); updateTextTrackDisplay(); } @@ -4582,7 +5533,8 @@ unsigned HTMLMediaElement::webkitVideoDecodedByteCount() const void HTMLMediaElement::mediaCanStart() { - LOG(Media, "HTMLMediaElement::mediaCanStart"); + LOG(Media, "HTMLMediaElement::mediaCanStart(%p) - m_isWaitingUntilMediaCanStart = %s, m_pausedInternal = %s", + this, boolString(m_isWaitingUntilMediaCanStart), boolString(m_pausedInternal) ); ASSERT(m_isWaitingUntilMediaCanStart || m_pausedInternal); if (m_isWaitingUntilMediaCanStart) { @@ -4603,13 +5555,13 @@ void HTMLMediaElement::setShouldDelayLoadEvent(bool shouldDelay) if (m_shouldDelayLoadEvent == shouldDelay) return; - LOG(Media, "HTMLMediaElement::setShouldDelayLoadEvent(%s)", boolString(shouldDelay)); + LOG(Media, "HTMLMediaElement::setShouldDelayLoadEvent(%p) - %s", this, boolString(shouldDelay)); m_shouldDelayLoadEvent = shouldDelay; if (shouldDelay) - document()->incrementLoadEventDelayCount(); + document().incrementLoadEventDelayCount(); else - document()->decrementLoadEventDelayCount(); + document().decrementLoadEventDelayCount(); } @@ -4638,19 +5590,26 @@ void HTMLMediaElement::privateBrowsingStateDidChange() if (!m_player) return; - Settings* settings = document()->settings(); - bool privateMode = !settings || settings->privateBrowsingEnabled(); - LOG(Media, "HTMLMediaElement::privateBrowsingStateDidChange(%s)", boolString(privateMode)); + bool privateMode = document().page() && document().page()->usesEphemeralSession(); + LOG(Media, "HTMLMediaElement::privateBrowsingStateDidChange(%p) - %s", this, boolString(privateMode)); m_player->setPrivateBrowsingMode(privateMode); } MediaControls* HTMLMediaElement::mediaControls() const { +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + return 0; +#else return toMediaControls(userAgentShadowRoot()->firstChild()); +#endif } bool HTMLMediaElement::hasMediaControls() const { +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + return false; +#else + if (ShadowRoot* userAgent = userAgentShadowRoot()) { Node* node = userAgent->firstChild(); ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isMediaControls()); @@ -4658,10 +5617,15 @@ bool HTMLMediaElement::hasMediaControls() const } return false; +#endif } bool HTMLMediaElement::createMediaControls() { +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + ensureMediaControlsShadowRoot(); + return false; +#else if (hasMediaControls()) return true; @@ -4674,18 +5638,39 @@ bool HTMLMediaElement::createMediaControls() if (isFullscreen()) mediaControls->enteredFullscreen(); - ensureUserAgentShadowRoot()->appendChild(mediaControls, ASSERT_NO_EXCEPTION); + ensureUserAgentShadowRoot().appendChild(mediaControls, ASSERT_NO_EXCEPTION); if (!controls() || !inDocument()) mediaControls->hide(); return true; +#endif } void HTMLMediaElement::configureMediaControls() { -#if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) - if (!controls() || !inDocument()) { + bool requireControls = controls(); + + // Always create controls for video when fullscreen playback is required. + if (isVideo() && m_mediaSession->requiresFullscreenForVideoPlayback(*this)) + requireControls = true; + + // Always create controls when in full screen mode. + if (isFullscreen()) + requireControls = true; + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + if (m_player && m_player->isCurrentPlaybackTargetWireless()) + requireControls = true; +#endif + +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + if (!requireControls || !inDocument() || !inActiveDocument()) + return; + + ensureMediaControlsShadowRoot(); +#else + if (!requireControls || !inDocument() || !inActiveDocument()) { if (hasMediaControls()) mediaControls()->hide(); return; @@ -4695,24 +5680,19 @@ void HTMLMediaElement::configureMediaControls() return; mediaControls()->show(); -#else - if (m_player) - m_player->setControls(controls()); - - if (!hasMediaControls() && inDocument()) - createMediaControls(); #endif } #if ENABLE(VIDEO_TRACK) -void HTMLMediaElement::configureTextTrackDisplay() +void HTMLMediaElement::configureTextTrackDisplay(TextTrackVisibilityCheckType checkType) { ASSERT(m_textTracks); if (m_processingPreferenceChange) return; - LOG(Media, "HTMLMediaElement::configureTextTrackDisplay"); + if (document().activeDOMObjectsAreStopped()) + return; bool haveVisibleTextTrack = false; for (unsigned i = 0; i < m_textTracks->length(); ++i) { @@ -4722,14 +5702,20 @@ void HTMLMediaElement::configureTextTrackDisplay() } } - if (m_haveVisibleTextTrack == haveVisibleTextTrack) { - updateActiveTextTrackCues(currentTime()); + if (checkType == CheckTextTrackVisibility && m_haveVisibleTextTrack == haveVisibleTextTrack) { + updateActiveTextTrackCues(currentMediaTime()); return; } m_haveVisibleTextTrack = haveVisibleTextTrack; m_closedCaptionsVisible = m_haveVisibleTextTrack; +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + if (!m_haveVisibleTextTrack) + return; + + ensureMediaControlsShadowRoot(); +#else if (!m_haveVisibleTextTrack && !hasMediaControls()) return; if (!hasMediaControls() && !createMediaControls()) @@ -4737,10 +5723,11 @@ void HTMLMediaElement::configureTextTrackDisplay() mediaControls()->changedClosedCaptionsVisibility(); - if (RuntimeEnabledFeatures::webkitVideoTrackEnabled()) { + if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) { updateTextTrackDisplay(); - updateActiveTextTrackCues(currentTime()); + updateActiveTextTrackCues(currentMediaTime()); } +#endif } void HTMLMediaElement::captionPreferencesChanged() @@ -4751,15 +5738,23 @@ void HTMLMediaElement::captionPreferencesChanged() if (hasMediaControls()) mediaControls()->textTrackPreferencesChanged(); - if (!document()->page()) +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + if (m_mediaControlsHost) + m_mediaControlsHost->updateCaptionDisplaySizes(); +#endif + + if (m_player) + m_player->tracksChanged(); + + if (!document().page()) return; - CaptionUserPreferences::CaptionDisplayMode displayMode = document()->page()->group().captionPreferences()->captionDisplayMode(); + CaptionUserPreferences::CaptionDisplayMode displayMode = document().page()->group().captionPreferences().captionDisplayMode(); if (m_captionDisplayMode == displayMode) return; m_captionDisplayMode = displayMode; - setClosedCaptionsVisible(m_captionDisplayMode == CaptionUserPreferences::AlwaysOn); + setWebkitClosedCaptionsVisible(m_captionDisplayMode == CaptionUserPreferences::AlwaysOn); } void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured(ReconfigureMode mode) @@ -4767,7 +5762,7 @@ void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured(ReconfigureMod if (!m_textTracks) return; - LOG(Media, "HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured"); + LOG(Media, "HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured(%p)", this); // Mark all tracks as not "configured" so that configureTextTracks() // will reconsider which tracks to display in light of new user preferences @@ -4793,16 +5788,10 @@ void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured(ReconfigureMod #endif -void* HTMLMediaElement::preDispatchEventHandler(Event* event) -{ - if (event && event->type() == eventNames().webkitfullscreenchangeEvent) - configureMediaControls(); - - return 0; -} - void HTMLMediaElement::createMediaPlayer() { + LOG(Media, "HTMLMediaElement::createMediaPlayer(%p)", this); + #if ENABLE(WEB_AUDIO) if (m_audioSourceNode) m_audioSourceNode->lock(); @@ -4810,10 +5799,17 @@ void HTMLMediaElement::createMediaPlayer() #if ENABLE(MEDIA_SOURCE) if (m_mediaSource) - m_mediaSource->setReadyState(MediaSource::closedKeyword()); + m_mediaSource->close(); +#endif + +#if ENABLE(MEDIA_STREAM) + m_mediaStreamSrcObject = nullptr; #endif - m_player = MediaPlayer::create(this); +#if ENABLE(VIDEO_TRACK) + forgetResourceSpecificTracks(); +#endif + m_player = std::make_unique<MediaPlayer>(static_cast<MediaPlayerClient&>(*this)); #if ENABLE(WEB_AUDIO) if (m_audioSourceNode) { @@ -4824,6 +5820,16 @@ void HTMLMediaElement::createMediaPlayer() m_audioSourceNode->unlock(); } #endif + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) { + m_hasPlaybackTargetAvailabilityListeners = true; + m_mediaSession->setHasPlaybackTargetAvailabilityListeners(*this, true); + enqueuePlaybackTargetAvailabilityChangedEvent(); // Ensure the event listener gets at least one event. + } +#endif + + updateSleepDisabling(); } #if ENABLE(WEB_AUDIO) @@ -4844,18 +5850,6 @@ AudioSourceProvider* HTMLMediaElement::audioSourceProvider() } #endif -#if ENABLE(MICRODATA) -String HTMLMediaElement::itemValueText() const -{ - return getURLAttribute(srcAttr); -} - -void HTMLMediaElement::setItemValueText(const String& value, ExceptionCode&) -{ - setAttribute(srcAttr, value); -} -#endif - const String& HTMLMediaElement::mediaGroup() const { return m_mediaGroup; @@ -4879,22 +5873,22 @@ void HTMLMediaElement::setMediaGroup(const String& group) // 4. If there is another media element whose Document is the same as m's Document (even if one or both // of these elements are not actually in the Document), - HashSet<HTMLMediaElement*> elements = documentToElementSetMap().get(document()); - for (HashSet<HTMLMediaElement*>::iterator i = elements.begin(); i != elements.end(); ++i) { - if (*i == this) + HashSet<HTMLMediaElement*> elements = documentToElementSetMap().get(&document()); + for (auto& element : elements) { + if (element == this) continue; // and which also has a mediagroup attribute, and whose mediagroup attribute has the same value as // the new value of m's mediagroup attribute, - if ((*i)->mediaGroup() == group) { + if (element->mediaGroup() == group) { // then let controller be that media element's current media controller. - setController((*i)->controller()); + setController(element->controller()); return; } } // Otherwise, let controller be a newly created MediaController. - setController(MediaController::create(Node::scriptExecutionContext())); + setController(MediaController::create(document())); } MediaController* HTMLMediaElement::controller() const @@ -4922,24 +5916,6 @@ void HTMLMediaElement::updateMediaController() m_mediaController->reportControllerState(); } -bool HTMLMediaElement::dispatchEvent(PassRefPtr<Event> event) -{ - bool dispatchResult; - bool isCanPlayEvent; - - isCanPlayEvent = (event->type() == eventNames().canplayEvent); - - if (isCanPlayEvent) - m_dispatchingCanPlayEvent = true; - - dispatchResult = HTMLElement::dispatchEvent(event); - - if (isCanPlayEvent) - m_dispatchingCanPlayEvent = false; - - return dispatchResult; -} - bool HTMLMediaElement::isBlocked() const { // A media element is a blocked media element if its readyState attribute is in the @@ -4965,7 +5941,7 @@ bool HTMLMediaElement::isBlockedOnMediaController() const // position relative to the MediaController's timeline or after the end of the media resource // relative to the MediaController's timeline. double mediaControllerPosition = m_mediaController->currentTime(); - if (mediaControllerPosition < startTime() || mediaControllerPosition > startTime() + duration()) + if (mediaControllerPosition < 0 || mediaControllerPosition > duration()) return true; return false; @@ -4974,38 +5950,37 @@ bool HTMLMediaElement::isBlockedOnMediaController() const void HTMLMediaElement::prepareMediaFragmentURI() { MediaFragmentURIParser fragmentParser(m_currentSrc); - double dur = duration(); + MediaTime dur = durationMediaTime(); - double start = fragmentParser.startTime(); - if (start != MediaFragmentURIParser::invalidTimeValue() && start > 0) { + MediaTime start = fragmentParser.startTime(); + if (start.isValid() && start > MediaTime::zeroTime()) { m_fragmentStartTime = start; if (m_fragmentStartTime > dur) m_fragmentStartTime = dur; } else - m_fragmentStartTime = MediaPlayer::invalidTime(); + m_fragmentStartTime = MediaTime::invalidTime(); - double end = fragmentParser.endTime(); - if (end != MediaFragmentURIParser::invalidTimeValue() && end > 0 && end > m_fragmentStartTime) { + MediaTime end = fragmentParser.endTime(); + if (end.isValid() && end > MediaTime::zeroTime() && (!m_fragmentStartTime.isValid() || end > m_fragmentStartTime)) { m_fragmentEndTime = end; if (m_fragmentEndTime > dur) m_fragmentEndTime = dur; } else - m_fragmentEndTime = MediaPlayer::invalidTime(); + m_fragmentEndTime = MediaTime::invalidTime(); - if (m_fragmentStartTime != MediaPlayer::invalidTime() && m_readyState < HAVE_FUTURE_DATA) + if (m_fragmentStartTime.isValid() && m_readyState < HAVE_FUTURE_DATA) prepareToPlay(); } void HTMLMediaElement::applyMediaFragmentURI() { - if (m_fragmentStartTime != MediaPlayer::invalidTime()) { + if (m_fragmentStartTime.isValid()) { m_sentEndEvent = false; - seek(m_fragmentStartTime, IGNORE_EXCEPTION); + seek(m_fragmentStartTime); } } -#if PLATFORM(MAC) -void HTMLMediaElement::updateDisableSleep() +void HTMLMediaElement::updateSleepDisabling() { if (!shouldDisableSleep() && m_sleepDisabler) m_sleepDisabler = nullptr; @@ -5015,47 +5990,88 @@ void HTMLMediaElement::updateDisableSleep() bool HTMLMediaElement::shouldDisableSleep() const { +#if !PLATFORM(COCOA) + return false; +#endif + + if (m_elementIsHidden) + return false; + return m_player && !m_player->paused() && hasVideo() && hasAudio() && !loop(); } -#endif String HTMLMediaElement::mediaPlayerReferrer() const { - Frame* frame = document()->frame(); + Frame* frame = document().frame(); if (!frame) return String(); - return SecurityPolicy::generateReferrerHeader(document()->referrerPolicy(), m_currentSrc, frame->loader()->outgoingReferrer()); + return SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), m_currentSrc, frame->loader().outgoingReferrer()); } String HTMLMediaElement::mediaPlayerUserAgent() const { - Frame* frame = document()->frame(); + Frame* frame = document().frame(); if (!frame) return String(); - return frame->loader()->userAgent(m_currentSrc); - + return frame->loader().userAgent(m_currentSrc); } -MediaPlayerClient::CORSMode HTMLMediaElement::mediaPlayerCORSMode() const +#if ENABLE(AVF_CAPTIONS) +Vector<RefPtr<PlatformTextTrack>> HTMLMediaElement::outOfBandTrackSources() { - if (!fastHasAttribute(HTMLNames::crossoriginAttr)) - return Unspecified; - if (equalIgnoringCase(fastGetAttribute(HTMLNames::crossoriginAttr), "use-credentials")) - return UseCredentials; - return Anonymous; + Vector<RefPtr<PlatformTextTrack>> outOfBandTrackSources; + for (auto& trackElement : childrenOfType<HTMLTrackElement>(*this)) { + + if (!trackElement.fastHasAttribute(srcAttr)) + continue; + + URL url = trackElement.getNonEmptyURLAttribute(srcAttr); + if (url.isEmpty()) + continue; + + if (!document().contentSecurityPolicy()->allowMediaFromSource(url, trackElement.isInUserAgentShadowTree())) + continue; + + PlatformTextTrack::TrackKind platformKind = PlatformTextTrack::Caption; + if (trackElement.kind() == TextTrack::captionsKeyword()) + platformKind = PlatformTextTrack::Caption; + else if (trackElement.kind() == TextTrack::subtitlesKeyword()) + platformKind = PlatformTextTrack::Subtitle; + else if (trackElement.kind() == TextTrack::descriptionsKeyword()) + platformKind = PlatformTextTrack::Description; + else if (trackElement.kind() == TextTrack::forcedKeyword()) + platformKind = PlatformTextTrack::Forced; + else + continue; + + const AtomicString& mode = trackElement.track()->mode(); + + PlatformTextTrack::TrackMode platformMode = PlatformTextTrack::Disabled; + if (TextTrack::hiddenKeyword() == mode) + platformMode = PlatformTextTrack::Hidden; + else if (TextTrack::disabledKeyword() == mode) + platformMode = PlatformTextTrack::Disabled; + else if (TextTrack::showingKeyword() == mode) + platformMode = PlatformTextTrack::Showing; + + outOfBandTrackSources.append(PlatformTextTrack::createOutOfBand(trackElement.label(), trackElement.srclang(), url.string(), platformMode, platformKind, trackElement.track()->uniqueId(), trackElement.isDefault())); + } + + return outOfBandTrackSources; } +#endif bool HTMLMediaElement::mediaPlayerNeedsSiteSpecificHacks() const { - Settings* settings = document()->settings(); + Settings* settings = document().settings(); return settings && settings->needsSiteSpecificQuirks(); } String HTMLMediaElement::mediaPlayerDocumentHost() const { - return document()->url().host(); + return document().url().host(); } void HTMLMediaElement::mediaPlayerEnterFullscreen() @@ -5075,7 +6091,7 @@ bool HTMLMediaElement::mediaPlayerIsFullscreen() const bool HTMLMediaElement::mediaPlayerIsFullscreenPermitted() const { - return !userGestureRequiredForFullscreen() || ScriptController::processingUserGesture(); + return m_mediaSession->fullscreenPermitted(*this); } bool HTMLMediaElement::mediaPlayerIsVideo() const @@ -5086,14 +6102,21 @@ bool HTMLMediaElement::mediaPlayerIsVideo() const LayoutRect HTMLMediaElement::mediaPlayerContentBoxRect() const { if (renderer()) - return renderer()->enclosingBox()->contentBoxRect(); + return renderer()->enclosingBox().contentBoxRect(); return LayoutRect(); } +float HTMLMediaElement::mediaPlayerContentsScale() const +{ + if (auto page = document().page()) + return page->pageScaleFactor() * page->deviceScaleFactor(); + return 1; +} + void HTMLMediaElement::mediaPlayerSetSize(const IntSize& size) { - setAttribute(widthAttr, String::number(size.width())); - setAttribute(heightAttr, String::number(size.height())); + setIntegralAttribute(widthAttr, size.width()); + setIntegralAttribute(heightAttr, size.height()); } void HTMLMediaElement::mediaPlayerPause() @@ -5121,25 +6144,686 @@ bool HTMLMediaElement::mediaPlayerIsLooping() const return loop(); } -HostWindow* HTMLMediaElement::mediaPlayerHostWindow() +CachedResourceLoader* HTMLMediaElement::mediaPlayerCachedResourceLoader() { - return mediaPlayerOwningDocument()->view()->hostWindow(); + return &document().cachedResourceLoader(); } -IntRect HTMLMediaElement::mediaPlayerWindowClipRect() +RefPtr<PlatformMediaResourceLoader> HTMLMediaElement::mediaPlayerCreateResourceLoader() { - return mediaPlayerOwningDocument()->view()->windowClipRect(); + return adoptRef(*new MediaResourceLoader(document(), fastGetAttribute(HTMLNames::crossoriginAttr))); } -CachedResourceLoader* HTMLMediaElement::mediaPlayerCachedResourceLoader() +bool HTMLMediaElement::mediaPlayerShouldWaitForResponseToAuthenticationChallenge(const AuthenticationChallenge& challenge) +{ + Frame* frame = document().frame(); + if (!frame) + return false; + + Page* page = frame->page(); + if (!page) + return false; + + ResourceRequest request(m_currentSrc); + ResourceLoadNotifier& notifier = frame->loader().notifier(); + DocumentLoader* documentLoader = document().loader(); + unsigned long identifier = page->progress().createUniqueIdentifier(); + + notifier.assignIdentifierToInitialRequest(identifier, documentLoader, request); + notifier.didReceiveAuthenticationChallenge(identifier, documentLoader, challenge); + + return true; +} + +String HTMLMediaElement::mediaPlayerSourceApplicationIdentifier() const +{ + if (Frame* frame = document().frame()) { + if (NetworkingContext* networkingContext = frame->loader().networkingContext()) + return networkingContext->sourceApplicationIdentifier(); + } + return emptyString(); +} + +Vector<String> HTMLMediaElement::mediaPlayerPreferredAudioCharacteristics() const +{ + if (Page* page = document().page()) + return page->group().captionPreferences().preferredAudioCharacteristics(); + return Vector<String>(); +} + +#if PLATFORM(IOS) +String HTMLMediaElement::mediaPlayerNetworkInterfaceName() const +{ + Settings* settings = document().settings(); + if (!settings) + return emptyString(); + + return settings->networkInterfaceName(); +} + +bool HTMLMediaElement::mediaPlayerGetRawCookies(const URL& url, Vector<Cookie>& cookies) const +{ + return getRawCookies(&document(), url, cookies); +} +#endif + +bool HTMLMediaElement::mediaPlayerIsInMediaDocument() const { - return mediaPlayerOwningDocument()->cachedResourceLoader(); + return document().isMediaDocument(); } +void HTMLMediaElement::mediaPlayerEngineFailedToLoad() const +{ + if (!m_player) + return; + + if (Frame* frame = document().frame()) + frame->mainFrame().diagnosticLoggingClient().logDiagnosticMessageWithValue(DiagnosticLoggingKeys::engineFailedToLoadKey(), m_player->engineDescription(), String::number(m_player->platformErrorCode()), ShouldSample::No); +} + +double HTMLMediaElement::mediaPlayerRequestedPlaybackRate() const +{ + return potentiallyPlaying() ? requestedPlaybackRate() : 0; +} + +#if USE(GSTREAMER) +void HTMLMediaElement::requestInstallMissingPlugins(const String& details, const String& description, MediaPlayerRequestInstallMissingPluginsCallback& callback) +{ + if (!document().page()) + return; + + document().page()->chrome().client().requestInstallMissingMediaPlugins(details, description, callback); +} +#endif + void HTMLMediaElement::removeBehaviorsRestrictionsAfterFirstUserGesture() { - m_restrictions = NoRestrictions; + MediaElementSession::BehaviorRestrictions restrictionsToRemove = MediaElementSession::RequireUserGestureForLoad +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + | MediaElementSession::RequireUserGestureToShowPlaybackTargetPicker + | MediaElementSession::RequireUserGestureToAutoplayToExternalDevice +#endif + | MediaElementSession::RequireUserGestureForLoad + | MediaElementSession::RequireUserGestureForRateChange + | MediaElementSession::RequireUserGestureForAudioRateChange + | MediaElementSession::RequireUserGestureForFullscreen + | MediaElementSession::InvisibleAutoplayNotPermitted; + m_mediaSession->removeBehaviorRestriction(restrictionsToRemove); +} + +#if ENABLE(MEDIA_SOURCE) +RefPtr<VideoPlaybackQuality> HTMLMediaElement::getVideoPlaybackQuality() +{ +#if ENABLE(WEB_TIMING) + DOMWindow* domWindow = document().domWindow(); + Performance* performance = domWindow ? domWindow->performance() : nullptr; + double now = performance ? performance->now() : 0; +#else + DocumentLoader* loader = document().loader(); + double now = loader ? 1000.0 * loader->timing().monotonicTimeToZeroBasedDocumentTime(monotonicallyIncreasingTime()) : 0; +#endif + + if (!m_player) + return VideoPlaybackQuality::create(now, 0, 0, 0, 0); + + return VideoPlaybackQuality::create(now, + m_droppedVideoFrames + m_player->totalVideoFrames(), + m_droppedVideoFrames + m_player->droppedVideoFrames(), + m_player->corruptedVideoFrames(), + m_player->totalFrameDelay().toDouble()); +} +#endif + +#if ENABLE(MEDIA_CONTROLS_SCRIPT) +DOMWrapperWorld& HTMLMediaElement::ensureIsolatedWorld() +{ + if (!m_isolatedWorld) + m_isolatedWorld = DOMWrapperWorld::create(JSDOMWindow::commonVM()); + return *m_isolatedWorld; +} + +bool HTMLMediaElement::ensureMediaControlsInjectedScript() +{ + LOG(Media, "HTMLMediaElement::ensureMediaControlsInjectedScript(%p)", this); + Page* page = document().page(); + if (!page) + return false; + + String mediaControlsScript = RenderTheme::themeForPage(page)->mediaControlsScript(); + if (!mediaControlsScript.length()) + return false; + + DOMWrapperWorld& world = ensureIsolatedWorld(); + ScriptController& scriptController = document().frame()->script(); + JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world)); + JSC::ExecState* exec = globalObject->globalExec(); + JSC::JSLockHolder lock(exec); + + JSC::JSValue functionValue = globalObject->get(exec, JSC::Identifier::fromString(exec, "createControls")); + if (functionValue.isFunction()) + return true; + +#ifndef NDEBUG + // Setting a scriptURL allows the source to be debuggable in the inspector. + URL scriptURL = URL(ParsedURLString, ASCIILiteral("mediaControlsScript")); +#else + URL scriptURL; +#endif + scriptController.evaluateInWorld(ScriptSourceCode(mediaControlsScript, scriptURL), world); + if (exec->hadException()) { + exec->clearException(); + return false; + } + + return true; +} + +static void setPageScaleFactorProperty(JSC::ExecState* exec, JSC::JSValue controllerValue, float pageScaleFactor) +{ + JSC::PutPropertySlot propertySlot(controllerValue); + JSC::JSObject* controllerObject = controllerValue.toObject(exec); + controllerObject->methodTable()->put(controllerObject, exec, JSC::Identifier::fromString(exec, "pageScaleFactor"), JSC::jsNumber(pageScaleFactor), propertySlot); +} + +void HTMLMediaElement::didAddUserAgentShadowRoot(ShadowRoot* root) +{ + LOG(Media, "HTMLMediaElement::didAddUserAgentShadowRoot(%p)", this); + + Page* page = document().page(); + if (!page) + return; + + DOMWrapperWorld& world = ensureIsolatedWorld(); + + if (!ensureMediaControlsInjectedScript()) + return; + + ScriptController& scriptController = document().frame()->script(); + JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world)); + JSC::ExecState* exec = globalObject->globalExec(); + JSC::JSLockHolder lock(exec); + + // The media controls script must provide a method with the following details. + // Name: createControls + // Parameters: + // 1. The ShadowRoot element that will hold the controls. + // 2. This object (and HTMLMediaElement). + // 3. The MediaControlsHost object. + // Return value: + // A reference to the created media controller instance. + + JSC::JSValue functionValue = globalObject->get(exec, JSC::Identifier::fromString(exec, "createControls")); + if (functionValue.isUndefinedOrNull()) + return; + + if (!m_mediaControlsHost) + m_mediaControlsHost = MediaControlsHost::create(this); + + auto mediaJSWrapper = toJS(exec, globalObject, this); + auto mediaControlsHostJSWrapper = toJS(exec, globalObject, m_mediaControlsHost.get()); + + JSC::MarkedArgumentBuffer argList; + argList.append(toJS(exec, globalObject, root)); + argList.append(mediaJSWrapper); + argList.append(mediaControlsHostJSWrapper); + + JSC::JSObject* function = functionValue.toObject(exec); + JSC::CallData callData; + JSC::CallType callType = function->methodTable()->getCallData(function, callData); + if (callType == JSC::CallTypeNone) + return; + + JSC::JSValue controllerValue = JSC::call(exec, function, callType, callData, globalObject, argList); + exec->clearException(); + JSC::JSObject* controllerObject = JSC::jsDynamicCast<JSC::JSObject*>(controllerValue); + if (!controllerObject) + return; + + // Connect the Media, MediaControllerHost, and Controller so the GC knows about their relationship + JSC::JSObject* mediaJSWrapperObject = mediaJSWrapper.toObject(exec); + JSC::Identifier controlsHost = JSC::Identifier::fromString(&exec->vm(), "controlsHost"); + + ASSERT(!mediaJSWrapperObject->hasProperty(exec, controlsHost)); + + mediaJSWrapperObject->putDirect(exec->vm(), controlsHost, mediaControlsHostJSWrapper, JSC::DontDelete | JSC::DontEnum | JSC::ReadOnly); + + JSC::JSObject* mediaControlsHostJSWrapperObject = JSC::jsDynamicCast<JSC::JSObject*>(mediaControlsHostJSWrapper); + if (!mediaControlsHostJSWrapperObject) + return; + + JSC::Identifier controller = JSC::Identifier::fromString(&exec->vm(), "controller"); + + ASSERT(!controllerObject->hasProperty(exec, controller)); + + mediaControlsHostJSWrapperObject->putDirect(exec->vm(), controller, controllerValue, JSC::DontDelete | JSC::DontEnum | JSC::ReadOnly); + + setPageScaleFactorProperty(exec, controllerValue, page->pageScaleFactor()); + + if (exec->hadException()) + exec->clearException(); +} + +void HTMLMediaElement::setMediaControlsDependOnPageScaleFactor(bool dependsOnPageScale) +{ + LOG(Media, "MediaElement::setMediaControlsDependPageScaleFactor(%p) = %s", this, boolString(dependsOnPageScale)); + + Settings* settings = document().settings(); + if (settings && settings->mediaControlsScaleWithPageZoom()) { + LOG(Media, "MediaElement::setMediaControlsDependPageScaleFactor(%p) forced to false by Settings value", this); + m_mediaControlsDependOnPageScaleFactor = false; + return; + } + + if (m_mediaControlsDependOnPageScaleFactor == dependsOnPageScale) + return; + + m_mediaControlsDependOnPageScaleFactor = dependsOnPageScale; + + if (m_mediaControlsDependOnPageScaleFactor) + document().registerForPageScaleFactorChangedCallbacks(this); + else + document().unregisterForPageScaleFactorChangedCallbacks(this); +} + +void HTMLMediaElement::pageScaleFactorChanged() +{ + Page* page = document().page(); + if (!page) + return; + + LOG(Media, "HTMLMediaElement::pageScaleFactorChanged(%p) = %f", this, page->pageScaleFactor()); + DOMWrapperWorld& world = ensureIsolatedWorld(); + ScriptController& scriptController = document().frame()->script(); + JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world)); + JSC::ExecState* exec = globalObject->globalExec(); + JSC::JSLockHolder lock(exec); + + JSC::JSValue controllerValue = controllerJSValue(*exec, *globalObject, *this); + + setPageScaleFactorProperty(exec, controllerValue, page->pageScaleFactor()); +} + +String HTMLMediaElement::getCurrentMediaControlsStatus() +{ + DOMWrapperWorld& world = ensureIsolatedWorld(); + ScriptController& scriptController = document().frame()->script(); + JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world)); + JSC::ExecState* exec = globalObject->globalExec(); + JSC::JSLockHolder lock(exec); + + JSC::JSValue controllerValue = controllerJSValue(*exec, *globalObject, *this); + JSC::JSObject* controllerObject = controllerValue.toObject(exec); + + if (exec->hadException()) + return ""; + + JSC::JSValue functionValue = controllerObject->get(exec, JSC::Identifier::fromString(exec, "getCurrentControlsStatus")); + if (exec->hadException() || functionValue.isUndefinedOrNull()) + return ""; + + JSC::JSObject* function = functionValue.toObject(exec); + JSC::CallData callData; + JSC::CallType callType = function->methodTable()->getCallData(function, callData); + JSC::MarkedArgumentBuffer argList; + if (callType == JSC::CallTypeNone) + return ""; + + JSC::JSValue outputValue = JSC::call(exec, function, callType, callData, controllerObject, argList); + + if (exec->hadException()) + return ""; + + return outputValue.getString(exec); +} +#endif // ENABLE(MEDIA_CONTROLS_SCRIPT) + +unsigned long long HTMLMediaElement::fileSize() const +{ + if (m_player) + return m_player->fileSize(); + + return 0; +} + +PlatformMediaSession::MediaType HTMLMediaElement::mediaType() const +{ + if (m_player && m_readyState >= HAVE_METADATA) + return hasVideo() ? PlatformMediaSession::Video : PlatformMediaSession::Audio; + + return presentationType(); +} + +PlatformMediaSession::MediaType HTMLMediaElement::presentationType() const +{ + if (hasTagName(HTMLNames::videoTag)) + return PlatformMediaSession::Video; + + return PlatformMediaSession::Audio; +} + +PlatformMediaSession::DisplayType HTMLMediaElement::displayType() const +{ + if (m_videoFullscreenMode == VideoFullscreenModeStandard) + return PlatformMediaSession::Fullscreen; + if (m_videoFullscreenMode & VideoFullscreenModePictureInPicture) + return PlatformMediaSession::Optimized; + if (m_videoFullscreenMode == VideoFullscreenModeNone) + return PlatformMediaSession::Normal; + + ASSERT_NOT_REACHED(); + return PlatformMediaSession::Normal; +} + +#if ENABLE(MEDIA_SOURCE) +size_t HTMLMediaElement::maximumSourceBufferSize(const SourceBuffer& buffer) const +{ + return m_mediaSession->maximumMediaSourceBufferSize(buffer); +} +#endif + +void HTMLMediaElement::suspendPlayback() +{ + LOG(Media, "HTMLMediaElement::suspendPlayback(%p) - paused = %s", this, boolString(paused())); + if (!paused()) + pause(); +} + +void HTMLMediaElement::resumeAutoplaying() +{ + LOG(Media, "HTMLMediaElement::resumeAutoplaying(%p) - paused = %s", this, boolString(paused())); + m_autoplaying = true; + + if (elementCanTransitionFromAutoplayToPlay(*this)) + play(); +} + +void HTMLMediaElement::mayResumePlayback(bool shouldResume) +{ + LOG(Media, "HTMLMediaElement::mayResumePlayback(%p) - paused = %s", this, boolString(paused())); + if (paused() && shouldResume) + play(); +} + +String HTMLMediaElement::mediaSessionTitle() const +{ + if (fastHasAttribute(titleAttr)) + return fastGetAttribute(titleAttr); + + return m_currentSrc; } +void HTMLMediaElement::didReceiveRemoteControlCommand(PlatformMediaSession::RemoteControlCommandType command) +{ + LOG(Media, "HTMLMediaElement::didReceiveRemoteControlCommand(%p) - %i", this, static_cast<int>(command)); + + switch (command) { + case PlatformMediaSession::PlayCommand: + play(); + break; + case PlatformMediaSession::PauseCommand: + pause(); + break; + case PlatformMediaSession::TogglePlayPauseCommand: + canPlay() ? play() : pause(); + break; + case PlatformMediaSession::BeginSeekingBackwardCommand: + beginScanning(Backward); + break; + case PlatformMediaSession::BeginSeekingForwardCommand: + beginScanning(Forward); + break; + case PlatformMediaSession::EndSeekingBackwardCommand: + case PlatformMediaSession::EndSeekingForwardCommand: + endScanning(); + break; + default: + { } // Do nothing + } } + +bool HTMLMediaElement::shouldOverrideBackgroundPlaybackRestriction(PlatformMediaSession::InterruptionType type) const +{ + if (type != PlatformMediaSession::EnteringBackground) + return false; + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + if (m_player && m_player->isCurrentPlaybackTargetWireless()) + return true; +#endif + if (m_videoFullscreenMode & VideoFullscreenModePictureInPicture) + return true; +#if PLATFORM(IOS) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)) + if (m_videoFullscreenMode == VideoFullscreenModeStandard && supportsPictureInPicture() && isPlaying()) + return true; +#endif + return false; +} + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) +void HTMLMediaElement::updateMediaState(UpdateMediaState updateState) +{ + if (updateState == UpdateMediaState::Asynchronously) { + scheduleDelayedAction(CheckMediaState); + return; + } + + MediaProducer::MediaStateFlags state = mediaState(); + if (m_mediaState == state) + return; + + m_mediaState = state; + m_mediaSession->mediaStateDidChange(*this, m_mediaState); +} +#endif + +MediaProducer::MediaStateFlags HTMLMediaElement::mediaState() const +{ + MediaStateFlags state = IsNotPlaying; + + bool hasActiveVideo = isVideo() && hasVideo(); + bool hasAudio = this->hasAudio(); +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + if (m_player && m_player->isCurrentPlaybackTargetWireless()) + state |= IsPlayingToExternalDevice; + + if (m_hasPlaybackTargetAvailabilityListeners) { + state |= HasPlaybackTargetAvailabilityListener; + if (!m_mediaSession->wirelessVideoPlaybackDisabled(*this)) + state |= RequiresPlaybackTargetMonitoring; + } + + bool requireUserGesture = m_mediaSession->hasBehaviorRestriction(MediaElementSession::RequireUserGestureToAutoplayToExternalDevice); + if (m_readyState >= HAVE_METADATA && !requireUserGesture && !m_failedToPlayToWirelessTarget) + state |= ExternalDeviceAutoPlayCandidate; + + if (hasActiveVideo || hasAudio) + state |= HasAudioOrVideo; + + if (hasActiveVideo && endedPlayback()) + state |= DidPlayToEnd; +#endif + + if (!isPlaying()) + return state; + + if (hasAudio && !muted()) + state |= IsPlayingAudio; + + if (hasActiveVideo) + state |= IsPlayingVideo; + + return state; +} + +void HTMLMediaElement::pageMutedStateDidChange() +{ + updateVolume(); +} + +bool HTMLMediaElement::effectiveMuted() const +{ + return muted() || (document().page() && document().page()->isMuted()); +} + +bool HTMLMediaElement::doesHaveAttribute(const AtomicString& attribute, AtomicString* value) const +{ + QualifiedName attributeName(nullAtom, attribute, nullAtom); + + AtomicString elementValue = fastGetAttribute(attributeName); + if (elementValue.isNull()) + return false; + + if (Settings* settings = document().settings()) { + if (attributeName == HTMLNames::x_itunes_inherit_uri_query_componentAttr && !settings->enableInheritURIQueryComponent()) + return false; + } + + if (value) + *value = elementValue; + + return true; +} + +void HTMLMediaElement::setShouldBufferData(bool shouldBuffer) +{ + if (m_player) + m_player->setShouldBufferData(shouldBuffer); +} + +void HTMLMediaElement::purgeBufferedDataIfPossible() +{ +#if PLATFORM(IOS) + if (!MemoryPressureHandler::singleton().isUnderMemoryPressure() && PlatformMediaSessionManager::sharedManager().sessionCanLoadMedia(*m_mediaSession)) + return; + + // This is called to relieve memory pressure. Turning off buffering causes the media playback + // daemon to release memory associated with queued-up video frames. + // We turn it back on right away, but new frames won't get loaded unless playback is resumed. + setShouldBufferData(false); + setShouldBufferData(true); +#endif +} + +bool HTMLMediaElement::canSaveMediaData() const +{ + if (m_player) + return m_player->canSaveMediaData(); + + return false; +} + +#if ENABLE(MEDIA_SESSION) +double HTMLMediaElement::playerVolume() const +{ + return m_player ? m_player->volume() : 0; +} + +MediaSession* HTMLMediaElement::session() const +{ + MediaSession* session = m_session.get(); + if (session && session == &document().defaultMediaSession()) + return nullptr; + + return session; +} + +void HTMLMediaElement::setSession(MediaSession* session) +{ + // 6.1. Extensions to the HTMLMediaElement interface + // 1. Let m be the media element in question. + // 2. Let old media session be m’s current media session, if it has one, and null otherwise. + // 3. Let m’s current media session be the new value or the top-level browsing context’s media session if the new value is null. + // 4. Let new media session be m’s current media session. + + // 5. Update media sessions: If old media session and new media session are the same (whether both null or both the same media session), then terminate these steps. + if (m_session.get() == session) + return; + + if (m_session) { + // 6. If m is an audio-producing participant of old media session, then pause m and remove m from old media session’s list of audio-producing participants. + if (m_session->isMediaElementActive(*this)) + pause(); + + m_session->removeMediaElement(*this); + + // 7. If old media session is not null and no longer has one or more audio-producing participants, then run the media session deactivation algorithm for old media session. + if (!m_session->hasActiveMediaElements()) + m_session->deactivate(); + } + + if (session) + setSessionInternal(*session); + else + setSessionInternal(document().defaultMediaSession()); +} + +void HTMLMediaElement::setSessionInternal(MediaSession& session) +{ + m_session = &session; + session.addMediaElement(*this); + m_kind = session.kind(); +} + +void HTMLMediaElement::setShouldDuck(bool duck) +{ + if (m_shouldDuck == duck) + return; + + m_shouldDuck = duck; + updateVolume(); +} + +#endif + +void HTMLMediaElement::allowsMediaDocumentInlinePlaybackChanged() +{ + if (potentiallyPlaying() && m_mediaSession->requiresFullscreenForVideoPlayback(*this) && !isFullscreen()) + enterFullscreen(); +} + +static bool mediaElementIsAllowedToAutoplay(const HTMLMediaElement& element) +{ + const Document& document = element.document(); + if (document.inPageCache()) + return false; + if (document.activeDOMObjectsAreSuspended()) + return false; + + RenderElement* renderer = element.renderer(); + if (!renderer) + return false; + if (renderer->style().visibility() != VISIBLE) + return false; + if (renderer->view().frameView().isOffscreen()) + return false; + if (renderer->visibleInViewportState() != RenderElement::VisibleInViewport) + return false; + return true; +} + +void HTMLMediaElement::isVisibleInViewportChanged() +{ + updateShouldAutoplay(); +} + +void HTMLMediaElement::updateShouldAutoplay() +{ + if (!autoplay()) + return; + + if (!m_mediaSession->hasBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted)) + return; + + bool canAutoplay = mediaElementIsAllowedToAutoplay(*this); + if (canAutoplay + && m_mediaSession->state() == PlatformMediaSession::Interrupted + && m_mediaSession->interruptionType() == PlatformMediaSession::InvisibleAutoplay) + m_mediaSession->endInterruption(PlatformMediaSession::MayResumePlaying); + else if (!canAutoplay + && m_mediaSession->state() != PlatformMediaSession::Interrupted) + m_mediaSession->beginInterruption(PlatformMediaSession::InvisibleAutoplay); +} + +} + #endif |