summaryrefslogtreecommitdiff
path: root/Source/WebCore/html/HTMLMediaElement.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/html/HTMLMediaElement.cpp
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebCore/html/HTMLMediaElement.cpp')
-rw-r--r--Source/WebCore/html/HTMLMediaElement.cpp5533
1 files changed, 3514 insertions, 2019 deletions
diff --git a/Source/WebCore/html/HTMLMediaElement.cpp b/Source/WebCore/html/HTMLMediaElement.cpp
index ce4da65e5..c04368eda 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-2017 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,10 +10,10 @@
* 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
@@ -24,29 +24,38 @@
*/
#include "config.h"
-#if ENABLE(VIDEO)
#include "HTMLMediaElement.h"
+#if ENABLE(VIDEO)
+
#include "ApplicationCacheHost.h"
#include "ApplicationCacheResource.h"
#include "Attribute.h"
+#include "Blob.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
#include "ChromeClient.h"
#include "ClientRect.h"
#include "ClientRectList.h"
+#include "CommonVM.h"
#include "ContentSecurityPolicy.h"
#include "ContentType.h"
+#include "CookieJar.h"
+#include "DiagnosticLoggingClient.h"
#include "DiagnosticLoggingKeys.h"
+#include "DisplaySleepDisabler.h"
+#include "Document.h"
#include "DocumentLoader.h"
#include "ElementIterator.h"
#include "EventNames.h"
-#include "ExceptionCodePlaceholder.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "FrameView.h"
+#include "HTMLParserIdioms.h"
#include "HTMLSourceElement.h"
#include "HTMLVideoElement.h"
+#include "JSDOMError.h"
+#include "JSDOMPromise.h"
#include "JSHTMLMediaElement.h"
#include "Language.h"
#include "Logging.h"
@@ -57,21 +66,31 @@
#include "MediaDocument.h"
#include "MediaError.h"
#include "MediaFragmentURIParser.h"
-#include "MediaKeyEvent.h"
#include "MediaList.h"
+#include "MediaPlayer.h"
#include "MediaQueryEvaluator.h"
-#include "MediaSessionManager.h"
-#include "PageActivityAssertionToken.h"
+#include "MediaResourceLoader.h"
+#include "MemoryPressureHandler.h"
+#include "NetworkingContext.h"
+#include "NoEventDispatchAssertion.h"
+#include "Page.h"
#include "PageGroup.h"
+#include "PlatformMediaSessionManager.h"
#include "ProgressTracker.h"
+#include "RenderLayerCompositor.h"
#include "RenderVideo.h"
#include "RenderView.h"
+#include "ResourceLoadInfo.h"
#include "ScriptController.h"
#include "ScriptSourceCode.h"
+#include "SecurityOriginData.h"
#include "SecurityPolicy.h"
+#include "SessionID.h"
#include "Settings.h"
#include "ShadowRoot.h"
#include "TimeRanges.h"
+#include "UserContentController.h"
+#include "UserGestureIndicator.h"
#include <limits>
#include <runtime/Uint8Array.h>
#include <wtf/CurrentTime.h>
@@ -79,16 +98,6 @@
#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 "SubframeLoader.h"
-#include "Widget.h"
-#endif
-
#if ENABLE(VIDEO_TRACK)
#include "AudioTrackList.h"
#include "HTMLTrackElement.h"
@@ -106,49 +115,59 @@
#include "MediaElementAudioSourceNode.h"
#endif
+#if PLATFORM(IOS) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
+#include "WebVideoFullscreenInterface.h"
+#endif
+
#if PLATFORM(IOS)
-#include "RuntimeApplicationChecksIOS.h"
+#include "RuntimeApplicationChecks.h"
+#include "WebVideoFullscreenInterfaceAVKit.h"
#endif
-#if ENABLE(IOS_AIRPLAY)
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
#include "WebKitPlaybackTargetAvailabilityEvent.h"
#endif
-#if PLATFORM(MAC)
-#include "DisplaySleepDisabler.h"
+#if ENABLE(MEDIA_SESSION)
+#include "MediaSession.h"
#endif
#if ENABLE(MEDIA_SOURCE)
#include "DOMWindow.h"
-#include "HTMLMediaSource.h"
-#include "Performance.h"
+#include "MediaSource.h"
#include "VideoPlaybackQuality.h"
#endif
#if ENABLE(MEDIA_STREAM)
+#include "DOMURL.h"
#include "MediaStream.h"
#include "MediaStreamRegistry.h"
#endif
-#if ENABLE(ENCRYPTED_MEDIA_V2)
-#include "MediaKeyNeededEvent.h"
-#include "MediaKeys.h"
-#endif
-
-#if USE(PLATFORM_TEXT_TRACK_MENU)
-#include "PlatformTextTrack.h"
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+#include "WebKitMediaKeyNeededEvent.h"
+#include "WebKitMediaKeys.h"
#endif
#if ENABLE(MEDIA_CONTROLS_SCRIPT)
#include "JSMediaControlsHost.h"
#include "MediaControlsHost.h"
-#include "ScriptGlobalObject.h"
-#include "UserAgentScripts.h"
#include <bindings/ScriptObject.h>
#endif
+#if ENABLE(ENCRYPTED_MEDIA)
+#include "NotImplemented.h"
+#endif
+
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 const double HideMediaControlsAfterEndedDelay = 6;
+
static void setFlags(unsigned& value, unsigned flags)
{
value |= flags;
@@ -173,6 +192,30 @@ static const char* boolString(bool val)
{
return val ? "true" : "false";
}
+
+static String actionName(HTMLMediaElementEnums::DelayedActionType action)
+{
+ StringBuilder actionBuilder;
+
+#define ACTION(_actionType) \
+ if (action & (HTMLMediaElementEnums::_actionType)) { \
+ if (!actionBuilder.isEmpty()) \
+ actionBuilder.appendLiteral(", "); \
+ actionBuilder.append(#_actionType); \
+ } \
+
+ ACTION(ConfigureTextTracks);
+ ACTION(TextTrackChangesNotification);
+ ACTION(ConfigureTextTrackDisplay);
+ ACTION(CheckPlaybackTargetCompatablity);
+ ACTION(CheckMediaState);
+ ACTION(MediaEngineUpdated);
+
+ return actionBuilder.toString();
+
+#undef ACTION
+}
+
#endif
#ifndef LOG_MEDIA_EVENTS
@@ -192,12 +235,17 @@ 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;
typedef HashMap<Document*, HashSet<HTMLMediaElement*>> DocumentElementSetMap;
static DocumentElementSetMap& documentToElementSetMap()
{
- DEFINE_STATIC_LOCAL(DocumentElementSetMap, map, ());
+ static NeverDestroyed<DocumentElementSetMap> map;
return map;
}
@@ -218,82 +266,141 @@ static void removeElementFromDocumentMap(HTMLMediaElement& element, Document& do
map.add(&document, set);
}
-#if ENABLE(ENCRYPTED_MEDIA)
-static ExceptionCode exceptionCodeForMediaKeyException(MediaPlayer::MediaKeyException exception)
-{
- switch (exception) {
- case MediaPlayer::NoError:
- return 0;
- case MediaPlayer::InvalidPlayerState:
- return INVALID_STATE_ERR;
- case MediaPlayer::KeySystemNotSupported:
- return NOT_SUPPORTED_ERR;
- }
-
- ASSERT_NOT_REACHED();
- return INVALID_STATE_ERR;
-}
-#endif
-
#if ENABLE(VIDEO_TRACK)
+
class TrackDisplayUpdateScope {
public:
- TrackDisplayUpdateScope(HTMLMediaElement* mediaElement)
+ TrackDisplayUpdateScope(HTMLMediaElement& element)
+ : m_element(element)
{
- m_mediaElement = mediaElement;
- m_mediaElement->beginIgnoringTrackDisplayUpdateRequests();
+ m_element.beginIgnoringTrackDisplayUpdateRequests();
}
~TrackDisplayUpdateScope()
{
- ASSERT(m_mediaElement);
- m_mediaElement->endIgnoringTrackDisplayUpdateRequests();
+ m_element.endIgnoringTrackDisplayUpdateRequests();
}
private:
- HTMLMediaElement* m_mediaElement;
+ HTMLMediaElement& m_element;
};
+
#endif
+struct HTMLMediaElement::TrackGroup {
+ enum GroupKind { CaptionsAndSubtitles, Description, Chapter, Metadata, Other };
+
+ TrackGroup(GroupKind kind)
+ : kind(kind)
+ {
+ }
+
+ Vector<RefPtr<TextTrack>> tracks;
+ RefPtr<TextTrack> visibleTrack;
+ RefPtr<TextTrack> defaultTrack;
+ GroupKind kind;
+ bool hasSrcLang { false };
+};
+
+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
+
+struct MediaElementSessionInfo {
+ const MediaElementSession* session;
+ MediaElementSession::PlaybackControlsPurpose purpose;
+
+ double timeOfLastUserInteraction;
+ bool canShowControlsManager : 1;
+ bool isVisibleInViewportOrFullscreen : 1;
+ bool isLargeEnoughForMainContent : 1;
+ bool isPlayingAudio : 1;
+};
+
+static MediaElementSessionInfo mediaElementSessionInfoForSession(const MediaElementSession& session, MediaElementSession::PlaybackControlsPurpose purpose)
+{
+ const HTMLMediaElement& element = session.element();
+ return {
+ &session,
+ purpose,
+ session.mostRecentUserInteractionTime(),
+ session.canShowControlsManager(purpose),
+ element.isFullscreen() || element.isVisibleInViewport(),
+ session.isLargeEnoughForMainContent(MediaSessionMainContentPurpose::MediaControls),
+ element.isPlaying() && element.hasAudio() && !element.muted()
+ };
+}
+
+static bool preferMediaControlsForCandidateSessionOverOtherCandidateSession(const MediaElementSessionInfo& session, const MediaElementSessionInfo& otherSession)
+{
+ MediaElementSession::PlaybackControlsPurpose purpose = session.purpose;
+ ASSERT(purpose == otherSession.purpose);
+
+ // For the controls manager, prioritize visible media over offscreen media.
+ if (purpose == MediaElementSession::PlaybackControlsPurpose::ControlsManager && session.isVisibleInViewportOrFullscreen != otherSession.isVisibleInViewportOrFullscreen)
+ return session.isVisibleInViewportOrFullscreen;
+
+ // For Now Playing, prioritize elements that would normally satisfy main content.
+ if (purpose == MediaElementSession::PlaybackControlsPurpose::NowPlaying && session.isLargeEnoughForMainContent != otherSession.isLargeEnoughForMainContent)
+ return session.isLargeEnoughForMainContent;
+
+ // As a tiebreaker, prioritize elements that the user recently interacted with.
+ return session.timeOfLastUserInteraction > otherSession.timeOfLastUserInteraction;
+}
+
+static bool mediaSessionMayBeConfusedWithMainContent(const MediaElementSessionInfo& session, MediaElementSession::PlaybackControlsPurpose purpose)
+{
+ if (purpose == MediaElementSession::PlaybackControlsPurpose::NowPlaying)
+ return session.isPlayingAudio;
+
+ if (!session.isVisibleInViewportOrFullscreen)
+ return false;
+
+ if (!session.isLargeEnoughForMainContent)
+ return false;
+
+ // Even if this video is not a candidate, if it is visible to the user and large enough
+ // to be main content, it poses a risk for being confused with main content.
+ return true;
+}
+
HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document, bool createdByParser)
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- : HTMLFrameOwnerElement(tagName, document)
-#else
: HTMLElement(tagName, document)
-#endif
, ActiveDOMObject(&document)
- , m_loadTimer(this, &HTMLMediaElement::loadTimerFired)
- , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired)
- , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired)
- , m_playedTimeRanges()
+ , m_pendingActionTimer(*this, &HTMLMediaElement::pendingActionTimerFired)
+ , m_progressEventTimer(*this, &HTMLMediaElement::progressEventTimerFired)
+ , m_playbackProgressTimer(*this, &HTMLMediaElement::playbackProgressTimerFired)
+ , m_scanTimer(*this, &HTMLMediaElement::scanTimerFired)
+ , m_playbackControlsManagerBehaviorRestrictionsTimer(*this, &HTMLMediaElement::playbackControlsManagerBehaviorRestrictionsTimerFired)
+ , m_seekToPlaybackPositionEndedTimer(*this, &HTMLMediaElement::seekToPlaybackPositionEndedTimerFired)
, m_asyncEventQueue(*this)
- , m_playbackRate(1.0f)
- , m_defaultPlaybackRate(1.0f)
- , 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(std::numeric_limits<double>::max())
- , m_clockTimeAtLastUpdateEvent(0)
- , m_lastTimeUpdateEventMovieTime(std::numeric_limits<double>::max())
- , m_loadState(WaitingForSource)
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- , m_proxyWidget(0)
-#endif
- , m_preload(MediaPlayer::Auto)
- , m_displayMode(Unknown)
- , m_processingMediaPlayerCallback(0)
-#if ENABLE(MEDIA_SOURCE)
- , m_droppedVideoFrames(0)
-#endif
- , m_cachedTime(MediaPlayer::invalidTime())
- , m_clockTimeAtLastCachedTimeUpdate(0)
- , m_minimumClockTimeToUpdateCachedTime(0)
- , m_fragmentStartTime(MediaPlayer::invalidTime())
- , m_fragmentEndTime(MediaPlayer::invalidTime())
- , m_pendingActionFlags(0)
+ , m_lastTimeUpdateEventMovieTime(MediaTime::positiveInfiniteTime())
+ , m_firstTimePlaying(true)
, m_playing(false)
, m_isWaitingUntilMediaCanStart(false)
, m_shouldDelayLoadEvent(false)
@@ -302,177 +409,323 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum
, 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_completelyLoaded(false)
, m_havePreparedToPlay(false)
, m_parsingInProgress(createdByParser)
-#if ENABLE(PAGE_VISIBILITY_API)
- , m_isDisplaySleepDisablingSuspended(document.hidden())
-#endif
-#if PLATFORM(IOS)
- , m_requestingPlay(false)
- , m_platformLayerBorrowed(false)
+ , m_elementIsHidden(document.hidden())
+ , m_creatingControls(false)
+ , m_receivedLayoutSizeChanged(false)
+ , m_hasEverNotifiedAboutPlaying(false)
+ , m_hasEverHadAudio(false)
+ , m_hasEverHadVideo(false)
+#if ENABLE(MEDIA_CONTROLS_SCRIPT)
+ , m_mediaControlsDependOnPageScaleFactor(false)
+ , m_haveSetUpCaptionContainer(false)
#endif
+ , m_isScrubbingRemotely(false)
#if ENABLE(VIDEO_TRACK)
, m_tracksAreReady(true)
, m_haveVisibleTextTrack(false)
, m_processingPreferenceChange(false)
- , m_lastTextTrackUpdateTime(-1)
- , m_captionDisplayMode(CaptionUserPreferences::Automatic)
- , m_audioTracks(0)
- , m_textTracks(0)
- , m_videoTracks(0)
- , m_ignoreTrackDisplayUpdate(0)
-#endif
-#if ENABLE(WEB_AUDIO)
- , m_audioSourceNode(0)
-#endif
- , m_mediaSession(HTMLMediaSession::create(*this))
- , m_reportedExtraMemoryCost(0)
-#if ENABLE(MEDIA_STREAM)
- , m_mediaStreamSrcObject(nullptr)
#endif
+ , m_mediaSession(std::make_unique<MediaElementSession>(*this))
{
- LOG(Media, "HTMLMediaElement::HTMLMediaElement");
- setHasCustomStyleResolveCallbacks();
-
- m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequireUserGestureForFullscreen);
- m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequirePageConsentToLoadMedia);
+ allMediaElements().add(this);
- // FIXME: We should clean up and look to better merge the iOS and non-iOS code below.
- Settings* settings = document.settings();
-#if !PLATFORM(IOS)
- document.registerForMediaVolumeCallbacks(this);
- document.registerForPrivateBrowsingStateChangedCallbacks(this);
+ LOG(Media, "HTMLMediaElement::HTMLMediaElement(%p)", this);
+ setHasCustomStyleResolveCallbacks();
-#if ENABLE(PAGE_VISIBILITY_API)
- document.registerForVisibilityStateChangedCallbacks(this);
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForFullscreen);
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePageConsentToLoadMedia);
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToAutoplayToExternalDevice);
#endif
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager);
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePlaybackToControlControlsManager);
- if (settings && settings->mediaPlaybackRequiresUserGesture()) {
- m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequireUserGestureForRateChange);
- m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequireUserGestureForLoad);
- }
-#else
+#if PLATFORM(IOS)
m_sendProgressEvents = false;
- if (!settings || settings->mediaPlaybackRequiresUserGesture()) {
- m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequireUserGestureForRateChange);
-#if ENABLE(IOS_AIRPLAY)
- m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequireUserGestureToShowPlaybackTargetPicker);
#endif
+
+ if (document.settings().invisibleAutoplayNotPermitted())
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted);
+
+ if (document.ownerElement() || !document.isMediaDocument()) {
+ const auto& topDocument = document.topDocument();
+ bool shouldAudioPlaybackRequireUserGesture = topDocument.audioPlaybackRequiresUserGesture();
+ bool shouldVideoPlaybackRequireUserGesture = topDocument.videoPlaybackRequiresUserGesture();
+
+ if (shouldVideoPlaybackRequireUserGesture) {
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForVideoRateChange);
+ if (document.settings().requiresUserGestureToLoadVideo())
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForLoad);
+ }
+
+ if (shouldAudioPlaybackRequireUserGesture)
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureForAudioRateChange);
+
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+ if (shouldVideoPlaybackRequireUserGesture || shouldAudioPlaybackRequireUserGesture)
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToShowPlaybackTargetPicker);
+#endif
+
+ if (!document.settings().mediaDataLoadsAutomatically())
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::AutoPreloadingNotPermitted);
+
+ if (document.settings().mainContentUserGestureOverrideEnabled())
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::OverrideUserGestureRequirementForMainContent);
}
-#endif // !PLATFORM(IOS)
- addElementToDocumentMap(*this, document);
+#if PLATFORM(IOS)
+ if (!document.settings().videoPlaybackRequiresUserGesture() && !document.settings().audioPlaybackRequiresUserGesture()) {
+ // Relax RequireUserGestureForFullscreen when videoPlaybackRequiresUserGesture and audioPlaybackRequiresUserGesture is not set:
+ m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequireUserGestureForFullscreen);
+ }
+#endif
#if ENABLE(VIDEO_TRACK)
- document.registerForCaptionPreferencesChangedCallbacks(this);
if (document.page())
- m_captionDisplayMode = document.page()->group().captionPreferences()->captionDisplayMode();
+ 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);
+
+ beginIgnoringTrackDisplayUpdateRequests();
+ allMediaElements().remove(this);
m_asyncEventQueue.close();
- if (m_isWaitingUntilMediaCanStart)
- document().removeMediaCanStartListener(this);
setShouldDelayLoadEvent(false);
- document().unregisterForMediaVolumeCallbacks(this);
- document().unregisterForPrivateBrowsingStateChangedCallbacks(this);
-
-#if ENABLE(PAGE_VISIBILITY_API)
- document().unregisterForVisibilityStateChangedCallbacks(this);
-#endif
+ unregisterWithDocument(document());
#if ENABLE(VIDEO_TRACK)
- document().unregisterForCaptionPreferencesChangedCallbacks(this);
- if (m_audioTracks) {
+ if (m_audioTracks)
m_audioTracks->clearElement();
- for (unsigned i = 0; i < m_audioTracks->length(); ++i)
- m_audioTracks->item(i)->clearClient();
- }
if (m_textTracks)
m_textTracks->clearElement();
- if (m_textTracks) {
- for (unsigned i = 0; i < m_textTracks->length(); ++i)
- m_textTracks->item(i)->clearClient();
- }
- if (m_videoTracks) {
+ if (m_videoTracks)
m_videoTracks->clearElement();
- for (unsigned i = 0; i < m_videoTracks->length(); ++i)
- m_videoTracks->item(i)->clearClient();
- }
#endif
-#if ENABLE(IOS_AIRPLAY)
- if (m_player && !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent))
- m_player->setHasPlaybackTargetAvailabilityListeners(false);
+#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->removeMediaElement(*this);
+ m_mediaController = nullptr;
}
#if ENABLE(MEDIA_SOURCE)
- closeMediaSource();
+ detachMediaSource();
#endif
-#if ENABLE(ENCRYPTED_MEDIA_V2)
- setMediaKeys(0);
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+ webkitSetMediaKeys(nullptr);
+#endif
+
+#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
- removeElementFromDocumentMap(*this, document());
+ m_seekTaskQueue.close();
+ m_promiseTaskQueue.close();
+ m_pauseAfterDetachedTaskQueue.close();
+ m_updatePlaybackControlsManagerQueue.close();
+ m_playbackControlsManagerBehaviorRestrictionsQueue.close();
+ m_resourceSelectionTaskQueue.close();
m_completelyLoaded = true;
- if (m_player)
- m_player->clearMediaPlayerClient();
+
+ if (m_player) {
+ m_player->invalidate();
+ m_player = nullptr;
+ }
+
+ m_mediaSession = nullptr;
+ updatePlaybackControlsManager();
}
-void HTMLMediaElement::didMoveToNewDocument(Document* oldDocument)
+static bool needsPlaybackControlsManagerQuirk(Page& page)
{
- if (m_isWaitingUntilMediaCanStart) {
- if (oldDocument)
- oldDocument->removeMediaCanStartListener(this);
- document().addMediaCanStartListener(this);
+ if (!page.settings().needsSiteSpecificQuirks())
+ return false;
+
+ auto* document = page.mainFrame().document();
+ if (!document)
+ return false;
+
+ String host = document->url().host();
+ return equalLettersIgnoringASCIICase(host, "netflix.com") || host.endsWithIgnoringASCIICase(".netflix.com");
+}
+
+HTMLMediaElement* HTMLMediaElement::bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose purpose)
+{
+ auto allSessions = PlatformMediaSessionManager::sharedManager().currentSessionsMatching([] (const PlatformMediaSession& session) {
+ return is<MediaElementSession>(session);
+ });
+
+ Vector<MediaElementSessionInfo> candidateSessions;
+ bool atLeastOneNonCandidateMayBeConfusedForMainContent = false;
+ for (auto& session : allSessions) {
+ auto mediaElementSessionInfo = mediaElementSessionInfoForSession(downcast<MediaElementSession>(*session), purpose);
+ if (mediaElementSessionInfo.canShowControlsManager)
+ candidateSessions.append(mediaElementSessionInfo);
+ else if (mediaSessionMayBeConfusedWithMainContent(mediaElementSessionInfo, purpose))
+ atLeastOneNonCandidateMayBeConfusedForMainContent = true;
+ }
+
+ if (!candidateSessions.size())
+ return nullptr;
+
+ std::sort(candidateSessions.begin(), candidateSessions.end(), preferMediaControlsForCandidateSessionOverOtherCandidateSession);
+ auto strongestSessionCandidate = candidateSessions.first();
+ if (!strongestSessionCandidate.isVisibleInViewportOrFullscreen && !strongestSessionCandidate.isPlayingAudio && atLeastOneNonCandidateMayBeConfusedForMainContent)
+ return nullptr;
+
+ HTMLMediaElement* strongestElementCandidate = &strongestSessionCandidate.session->element();
+ if (strongestElementCandidate) {
+ if (Page* page = strongestElementCandidate->document().page()) {
+ if (needsPlaybackControlsManagerQuirk(*page))
+ return nullptr;
+ }
}
+ return strongestElementCandidate;
+}
+
+void HTMLMediaElement::registerWithDocument(Document& document)
+{
+ 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);
+ document.registerForUserInterfaceLayoutDirectionChangedCallbacks(*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);
+ document.unregisterForUserInterfaceLayoutDirectionChangedCallbacks(*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();
+ oldDocument.decrementLoadEventDelayCount();
document().incrementLoadEventDelayCount();
}
- if (oldDocument) {
- oldDocument->unregisterForMediaVolumeCallbacks(this);
- removeElementFromDocumentMap(*this, *oldDocument);
- }
+ unregisterWithDocument(oldDocument);
- document().registerForMediaVolumeCallbacks(this);
- addElementToDocumentMap(*this, document());
+ registerWithDocument(document());
HTMLElement::didMoveToNewDocument(oldDocument);
+ updateShouldAutoplay();
}
-bool HTMLMediaElement::hasCustomFocusLogic() const
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+void HTMLMediaElement::prepareForDocumentSuspension()
{
- return true;
+ m_mediaSession->unregisterWithDocument(document());
+}
+
+void HTMLMediaElement::resumeFromDocumentSuspension()
+{
+ m_mediaSession->registerWithDocument(document());
+ updateShouldAutoplay();
}
+#endif
bool HTMLMediaElement::supportsFocus() const
{
@@ -488,57 +741,31 @@ bool HTMLMediaElement::isMouseFocusable() const
return false;
}
-#if PLATFORM(IOS)
-bool HTMLMediaElement::parseMediaPlayerAttribute(const QualifiedName& name, const AtomicString& value)
-{
- ASSERT(m_player);
- if (name == data_youtube_idAttr) {
- m_player->attributeChanged(name.toString(), value);
- return true;
- }
- if (name == titleAttr) {
- m_player->attributeChanged(name.toString(), value);
- return true;
- }
-
- if (Settings* settings = document().settings()) {
-#if ENABLE(IOS_AIRPLAY)
- if (name == webkitairplayAttr && settings->mediaPlaybackAllowsAirPlay()) {
- m_player->attributeChanged(name.toString(), value);
- return true;
- }
-#endif
- if (name == webkit_playsinlineAttr && settings->mediaPlaybackAllowsInline()) {
- m_player->attributeChanged(name.toString(), ASCIILiteral(value.isNull() ? "false" : "true"));
- return true;
- }
- }
- return false;
-}
-#endif
-
void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
{
if (name == srcAttr) {
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#location-of-the-media-resource
+ // Location of the Media Resource
+ // 12 February 2017
+
+ // If a src attribute of a media element is set or changed, the user
+ // agent must invoke the media element's media element load algorithm.
#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)) {
+ 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);
- }
+ prepareForLoad();
} else if (name == controlsAttr)
configureMediaControls();
else if (name == loopAttr)
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
@@ -547,71 +774,11 @@ void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicStr
}
// The attribute must be ignored if the autoplay attribute is present
- if (!autoplay() && m_player)
+ if (!autoplay() && !m_havePreparedToPlay && m_player)
m_player->setPreload(m_mediaSession->effectivePreloadForElement(*this));
} else if (name == mediagroupAttr)
setMediaGroup(value);
- else if (name == onabortAttr)
- setAttributeEventListener(eventNames().abortEvent, name, value);
- else if (name == onbeforeloadAttr)
- setAttributeEventListener(eventNames().beforeloadEvent, name, value);
- else if (name == oncanplayAttr)
- setAttributeEventListener(eventNames().canplayEvent, name, value);
- else if (name == oncanplaythroughAttr)
- setAttributeEventListener(eventNames().canplaythroughEvent, name, value);
- else if (name == ondurationchangeAttr)
- setAttributeEventListener(eventNames().durationchangeEvent, name, value);
- else if (name == onemptiedAttr)
- setAttributeEventListener(eventNames().emptiedEvent, name, value);
- else if (name == onendedAttr)
- setAttributeEventListener(eventNames().endedEvent, name, value);
- else if (name == onerrorAttr)
- setAttributeEventListener(eventNames().errorEvent, name, value);
- else if (name == onloadeddataAttr)
- setAttributeEventListener(eventNames().loadeddataEvent, name, value);
- else if (name == onloadedmetadataAttr)
- setAttributeEventListener(eventNames().loadedmetadataEvent, name, value);
- else if (name == onloadstartAttr)
- setAttributeEventListener(eventNames().loadstartEvent, name, value);
- else if (name == onpauseAttr)
- setAttributeEventListener(eventNames().pauseEvent, name, value);
- else if (name == onplayAttr)
- setAttributeEventListener(eventNames().playEvent, name, value);
- else if (name == onplayingAttr)
- setAttributeEventListener(eventNames().playingEvent, name, value);
- else if (name == onprogressAttr)
- setAttributeEventListener(eventNames().progressEvent, name, value);
- else if (name == onratechangeAttr)
- setAttributeEventListener(eventNames().ratechangeEvent, name, value);
- else if (name == onseekedAttr)
- setAttributeEventListener(eventNames().seekedEvent, name, value);
- else if (name == onseekingAttr)
- setAttributeEventListener(eventNames().seekingEvent, name, value);
- else if (name == onstalledAttr)
- setAttributeEventListener(eventNames().stalledEvent, name, value);
- else if (name == onsuspendAttr)
- setAttributeEventListener(eventNames().suspendEvent, name, value);
- else if (name == ontimeupdateAttr)
- setAttributeEventListener(eventNames().timeupdateEvent, name, value);
- else if (name == onvolumechangeAttr)
- setAttributeEventListener(eventNames().volumechangeEvent, name, value);
- else if (name == onwaitingAttr)
- setAttributeEventListener(eventNames().waitingEvent, name, value);
- else if (name == onwebkitbeginfullscreenAttr)
- setAttributeEventListener(eventNames().webkitbeginfullscreenEvent, name, value);
- else if (name == onwebkitendfullscreenAttr)
- setAttributeEventListener(eventNames().webkitendfullscreenEvent, name, value);
-#if ENABLE(IOS_AIRPLAY)
- else if (name == onwebkitcurrentplaybacktargetiswirelesschangedAttr)
- setAttributeEventListener(eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent, name, value);
- else if (name == onwebkitplaybacktargetavailabilitychangedAttr)
- setAttributeEventListener(eventNames().webkitplaybacktargetavailabilitychangedEvent, name, value);
-#endif
-#if PLATFORM(IOS)
- else if (m_player && parseMediaPlayerAttribute(name, value))
- return;
-#endif
else
HTMLElement::parseAttribute(name, value);
}
@@ -620,18 +787,8 @@ void HTMLMediaElement::finishParsingChildren()
{
HTMLElement::finishParsingChildren();
m_parsingInProgress = false;
-
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- if (shouldUseVideoPluginProxy()) {
- document().updateStyleIfNeeded();
- createMediaPlayerProxy();
- }
-#endif
#if ENABLE(VIDEO_TRACK)
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return;
-
if (descendantsOfType<HTMLTrackElement>(*this).first())
scheduleDelayedAction(ConfigureTextTracks);
#endif
@@ -639,29 +796,12 @@ void HTMLMediaElement::finishParsingChildren()
bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style)
{
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- if (shouldUseVideoPluginProxy())
- return true;
-#endif
return controls() && HTMLElement::rendererIsNeeded(style);
}
-RenderPtr<RenderElement> HTMLMediaElement::createElementRenderer(PassRef<RenderStyle> style)
+RenderPtr<RenderElement> HTMLMediaElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
{
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- if (shouldUseVideoPluginProxy()) {
- // Setup the renderer if we already have a proxy widget.
- auto mediaRenderer = createRenderer<RenderEmbeddedObject>(*this, std::move(style));
- if (m_proxyWidget) {
- mediaRenderer->setWidget(m_proxyWidget);
-
- if (Frame* frame = document().frame())
- frame->loader().client().showMediaPlayerProxyPlugin(m_proxyWidget.get());
- }
- return std::move(mediaRenderer);
- }
-#endif
- return createRenderer<RenderMedia>(*this, std::move(style));
+ return createRenderer<RenderMedia>(*this, WTFMove(style));
}
bool HTMLMediaElement::childShouldCreateRenderer(const Node& child) const
@@ -683,52 +823,71 @@ bool HTMLMediaElement::childShouldCreateRenderer(const Node& child) const
Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode& insertionPoint)
{
- LOG(Media, "HTMLMediaElement::insertedInto");
+ LOG(Media, "HTMLMediaElement::insertedInto(%p)", this);
HTMLElement::insertedInto(insertionPoint);
- if (insertionPoint.inDocument()) {
+ if (insertionPoint.isConnected()) {
m_inActiveDocument = true;
#if PLATFORM(IOS)
- if (m_networkState == NETWORK_EMPTY && !fastGetAttribute(srcAttr).isEmpty() && m_mediaSession->dataLoadingPermitted(*this))
+ if (m_networkState == NETWORK_EMPTY && !attributeWithoutSynchronization(srcAttr).isEmpty() && m_mediaSession->dataLoadingPermitted(*this))
#else
- if (m_networkState == NETWORK_EMPTY && !fastGetAttribute(srcAttr).isEmpty())
+ if (m_networkState == NETWORK_EMPTY && !attributeWithoutSynchronization(srcAttr).isEmpty())
#endif
- scheduleDelayedAction(LoadMediaResource);
+ prepareForLoad();
}
if (!m_explicitlyMuted) {
m_explicitlyMuted = true;
- m_muted = fastHasAttribute(mutedAttr);
+ m_muted = hasAttributeWithoutSynchronization(mutedAttr);
+ m_mediaSession->canProduceAudioChanged();
}
+ return InsertionShouldCallFinishedInsertingSubtree;
+}
+
+void HTMLMediaElement::finishedInsertingSubtree()
+{
configureMediaControls();
- return InsertionDone;
}
-void HTMLMediaElement::removedFrom(ContainerNode& insertionPoint)
+void HTMLMediaElement::pauseAfterDetachedTask()
{
- LOG(Media, "HTMLMediaElement::removedFrom");
+ // If we were re-inserted into an active document, no need to pause.
+ if (m_inActiveDocument)
+ return;
- m_inActiveDocument = false;
- if (insertionPoint.inDocument()) {
- configureMediaControls();
- if (m_networkState > NETWORK_EMPTY)
- pause();
- if (m_isFullscreen)
- exitFullscreen();
+ if (hasMediaControls())
+ mediaControls()->hide();
+ if (m_networkState > NETWORK_EMPTY)
+ pause();
+ if (m_videoFullscreenMode != VideoFullscreenModeNone)
+ exitFullscreen();
+
+ if (!m_player)
+ return;
- if (m_player) {
- JSC::VM* vm = JSDOMWindowBase::commonVM();
- JSC::JSLockHolder lock(vm);
+ size_t extraMemoryCost = m_player->extraMemoryCost();
+ if (extraMemoryCost > m_reportedExtraMemoryCost) {
+ JSC::VM& vm = commonVM();
+ JSC::JSLockHolder lock(vm);
- size_t extraMemoryCost = m_player->extraMemoryCost();
- size_t extraMemoryCostDelta = extraMemoryCost - m_reportedExtraMemoryCost;
- m_reportedExtraMemoryCost = extraMemoryCost;
+ 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);
+ }
+}
- if (extraMemoryCostDelta > 0)
- vm->heap.reportExtraMemoryCost(extraMemoryCostDelta);
- }
+void HTMLMediaElement::removedFrom(ContainerNode& insertionPoint)
+{
+ LOG(Media, "HTMLMediaElement::removedFrom(%p)", this);
+
+ m_inActiveDocument = false;
+ if (insertionPoint.isConnected()) {
+ // Pause asynchronously to let the operation that removed us finish, in case we get inserted back into a document.
+ m_pauseAfterDetachedTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::pauseAfterDetachedTask, this));
}
HTMLElement::removedFrom(insertionPoint);
@@ -737,114 +896,175 @@ void HTMLMediaElement::removedFrom(ContainerNode& insertionPoint)
void HTMLMediaElement::willAttachRenderers()
{
ASSERT(!renderer());
+}
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- if (shouldUseVideoPluginProxy())
- m_needWidgetUpdate = true;
-#endif
+inline void HTMLMediaElement::updateRenderer()
+{
+ if (auto* renderer = this->renderer())
+ renderer->updateFromElement();
}
void HTMLMediaElement::didAttachRenderers()
{
- 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());
+ if (auto* renderer = this->renderer()) {
+ renderer->updateFromElement();
+ if (m_mediaSession && m_mediaSession->wantsToObserveViewportVisibilityForAutoplay())
+ renderer->registerForVisibleInViewportCallback();
}
-#endif
+ updateShouldAutoplay();
}
-void HTMLMediaElement::didRecalcStyle(Style::Change)
+void HTMLMediaElement::willDetachRenderers()
{
- if (renderer())
- renderer()->updateFromElement();
+ if (auto* renderer = this->renderer())
+ renderer->unregisterForVisibleInViewportCallback();
}
-void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType)
+void HTMLMediaElement::didDetachRenderers()
{
- LOG(Media, "HTMLMediaElement::scheduleLoad");
+ updateShouldAutoplay();
+}
- if ((actionType & LoadMediaResource) && !(m_pendingActionFlags & LoadMediaResource)) {
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- if (shouldUseVideoPluginProxy())
- createMediaPlayerProxy();
-#endif
+void HTMLMediaElement::didRecalcStyle(Style::Change)
+{
+ updateRenderer();
+}
- prepareForLoad();
- setFlags(m_pendingActionFlags, LoadMediaResource);
- }
+void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType)
+{
+ LOG(Media, "HTMLMediaElement::scheduleDelayedAction(%p) - setting %s flag", this, actionName(actionType).utf8().data());
#if ENABLE(VIDEO_TRACK)
- if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() && (actionType & ConfigureTextTracks))
+ if (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);
+
+ if (actionType & MediaEngineUpdated)
+ setFlags(m_pendingActionFlags, MediaEngineUpdated);
+
+ if (actionType & UpdatePlayState)
+ setFlags(m_pendingActionFlags, UpdatePlayState);
+
+ m_pendingActionTimer.startOneShot(0);
}
void HTMLMediaElement::scheduleNextSourceChild()
{
// Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad.
- setFlags(m_pendingActionFlags, LoadMediaResource);
- m_loadTimer.startOneShot(0);
+ m_resourceSelectionTaskQueue.enqueueTask([this] {
+ loadNextSourceChild();
+ });
+}
+
+void HTMLMediaElement::mediaPlayerActiveSourceBuffersChanged(const MediaPlayer*)
+{
+ m_hasEverHadAudio |= hasAudio();
+ m_hasEverHadVideo |= hasVideo();
}
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(WTFMove(event));
+}
+
+void HTMLMediaElement::scheduleResolvePendingPlayPromises()
+{
+ m_promiseTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::resolvePendingPlayPromises, this));
+}
+
+void HTMLMediaElement::rejectPendingPlayPromises(DOMError& error)
+{
+ Vector<DOMPromise<void>> pendingPlayPromises = WTFMove(m_pendingPlayPromises);
+
+ for (auto& promise : pendingPlayPromises)
+ promise.rejectType<IDLInterface<DOMError>>(error);
}
-void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>&)
+void HTMLMediaElement::resolvePendingPlayPromises()
{
- Ref<HTMLMediaElement> protect(*this); // loadNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations.
+ Vector<DOMPromise<void>> pendingPlayPromises = WTFMove(m_pendingPlayPromises);
+
+ for (auto& promise : pendingPlayPromises)
+ promise.resolve();
+}
+
+void HTMLMediaElement::scheduleNotifyAboutPlaying()
+{
+ m_promiseTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::notifyAboutPlaying, this));
+}
+
+void HTMLMediaElement::notifyAboutPlaying()
+{
+ Ref<HTMLMediaElement> protectedThis(*this); // The 'playing' event can make arbitrary DOM mutations.
+ m_playbackStartedTime = currentMediaTime().toDouble();
+ dispatchEvent(Event::create(eventNames().playingEvent, false, true));
+ resolvePendingPlayPromises();
+
+ m_hasEverNotifiedAboutPlaying = true;
+ scheduleUpdatePlaybackControlsManager();
+}
+
+bool HTMLMediaElement::hasEverNotifiedAboutPlaying() const
+{
+ return m_hasEverNotifiedAboutPlaying;
+}
+
+void HTMLMediaElement::pendingActionTimerFired()
+{
+ Ref<HTMLMediaElement> protectedThis(*this); // loadNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations.
+ PendingActionFlags pendingActions = m_pendingActionFlags;
+ m_pendingActionFlags = 0;
#if ENABLE(VIDEO_TRACK)
- if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() && (m_pendingActionFlags & ConfigureTextTracks))
+ if (pendingActions & ConfigureTextTracks)
configureTextTracks();
#endif
- if (m_pendingActionFlags & LoadMediaResource) {
- if (m_loadState == LoadingFromSourceElement)
- loadNextSourceChild();
- else
- loadInternal();
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+ if (pendingActions & CheckPlaybackTargetCompatablity && m_isPlayingToWirelessTarget && !m_player->canPlayToWirelessPlaybackTarget()) {
+ LOG(Media, "HTMLMediaElement::pendingActionTimerFired(%p) - calling setShouldPlayToPlaybackTarget(false)", this);
+ m_failedToPlayToWirelessTarget = true;
+ m_player->setShouldPlayToPlaybackTarget(false);
}
-#if USE(PLATFORM_TEXT_TRACK_MENU)
- if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() && (m_pendingActionFlags & TextTrackChangesNotification))
- notifyMediaPlayerOfTextTrackChanges();
+ if (pendingActions & CheckMediaState)
+ updateMediaState();
#endif
- m_pendingActionFlags = 0;
+ if (pendingActions & MediaEngineUpdated)
+ mediaEngineWasUpdated();
+
+ if (pendingActions & UpdatePlayState)
+ updatePlayState();
}
-PassRefPtr<MediaError> HTMLMediaElement::error() const
+MediaError* HTMLMediaElement::error() const
{
- return m_error;
+ return m_error.get();
}
void HTMLMediaElement::setSrc(const String& url)
{
- setAttribute(srcAttr, url);
+ setAttributeWithoutSynchronization(srcAttr, url);
}
-#if ENABLE(MEDIA_STREAM)
-void HTMLMediaElement::setSrcObject(MediaStream* mediaStream)
+void HTMLMediaElement::setSrcObject(MediaProvider&& mediaProvider)
{
// 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
@@ -853,27 +1073,36 @@ void HTMLMediaElement::setSrcObject(MediaStream* mediaStream)
//
// https://bugs.webkit.org/show_bug.cgi?id=124896
- m_mediaStreamSrcObject = mediaStream;
+
+ // https://www.w3.org/TR/html51/semantics-embedded-content.html#dom-htmlmediaelement-srcobject
+ // 4.7.14.2. Location of the media resource
+ // srcObject: On setting, it must set the element’s assigned media provider object to the new
+ // value, and then invoke the element’s media element load algorithm.
+ m_mediaProvider = WTFMove(mediaProvider);
+ prepareForLoad();
+}
+
+void HTMLMediaElement::setCrossOrigin(const AtomicString& value)
+{
+ setAttributeWithoutSynchronization(crossoriginAttr, value);
+}
+
+String HTMLMediaElement::crossOrigin() const
+{
+ return parseCORSSettingsAttribute(attributeWithoutSynchronization(crossoriginAttr));
}
-#endif
HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
{
return m_networkState;
}
-String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySystem, const URL& url) const
+String HTMLMediaElement::canPlayType(const String& mimeType) const
{
MediaEngineSupportParameters parameters;
ContentType contentType(mimeType);
- parameters.type = contentType.type().lower();
+ 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;
@@ -891,34 +1120,49 @@ 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", this, mimeType.utf8().data(), canPlay.utf8().data());
return canPlay;
}
+double HTMLMediaElement::getStartDate() const
+{
+ if (!m_player)
+ return std::numeric_limits<double>::quiet_NaN();
+ return m_player->getStartDate().toDouble();
+}
+
void HTMLMediaElement::load()
{
- Ref<HTMLMediaElement> protect(*this); // loadInternal may result in a 'beforeload' event, which can make arbitrary DOM mutations.
+ Ref<HTMLMediaElement> protectedThis(*this); // prepareForLoad may result in a 'beforeload' event, which can make arbitrary DOM mutations.
- LOG(Media, "HTMLMediaElement::load()");
+ LOG(Media, "HTMLMediaElement::load(%p)", this);
if (!m_mediaSession->dataLoadingPermitted(*this))
return;
- if (ScriptController::processingUserGesture())
+ if (ScriptController::processingUserGestureForMedia())
removeBehaviorsRestrictionsAfterFirstUserGesture();
prepareForLoad();
- loadInternal();
- prepareToPlay();
+ m_resourceSelectionTaskQueue.enqueueTask([this] {
+ prepareToPlay();
+ });
}
void HTMLMediaElement::prepareForLoad()
{
- LOG(Media, "HTMLMediaElement::prepareForLoad");
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#media-element-load-algorithm
+ // The Media Element Load Algorithm
+ // 12 February 2017
+ LOG(Media, "HTMLMediaElement::prepareForLoad(%p)", this);
+
+ // 1 - Abort any already-running instance of the resource selection algorithm for this element.
// Perform the cleanup required for the resource load algorithm to run.
stopPeriodicTimers();
- m_loadTimer.stop();
+ m_pendingActionTimer.stop();
+ m_resourceSelectionTaskQueue.cancelAllTasks();
+ // FIXME: Figure out appropriate place to reset LoadTextTrackResource if necessary and set m_pendingActionFlags to 0 here.
m_sentEndEvent = false;
m_sentStalledEvent = false;
m_haveFiredLoadedData = false;
@@ -927,201 +1171,258 @@ void HTMLMediaElement::prepareForLoad()
m_displayMode = Unknown;
m_currentSrc = URL();
- // 1 - Abort any already-running instance of the resource selection algorithm for this element.
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+ m_failedToPlayToWirelessTarget = false;
+#endif
+
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.
+ createMediaPlayer();
+
+ // 2 - Let pending tasks be a list of all tasks from the media element's media element event task source in one of the task queues.
+ // 3 - For each task in pending tasks that would resolve pending play promises or reject pending play promises, immediately resolve or reject those promises in the order the corresponding tasks were queued.
+ // 4 - Remove each task in pending tasks from its task queue
cancelPendingEventsAndCallbacks();
- // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
+ // 5 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue
// a task to fire a simple event named abort at the media element.
if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE)
scheduleEvent(eventNames().abortEvent);
+ // 6 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
+ if (m_networkState != NETWORK_EMPTY) {
+ // 6.1 - Queue a task to fire a simple event named emptied at the media element.
+ scheduleEvent(eventNames().emptiedEvent);
+
+ // 6.2 - If a fetching process is in progress for the media element, the user agent should stop it.
+ m_networkState = NETWORK_EMPTY;
+
+ // 6.3 - If the media element’s assigned media provider object is a MediaSource object, then detach it.
#if ENABLE(MEDIA_SOURCE)
- closeMediaSource();
+ detachMediaSource();
#endif
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- if (shouldUseVideoPluginProxy()) {
- if (m_player)
- m_player->cancelLoad();
- else
- createMediaPlayerProxy();
- } else
-#endif
- createMediaPlayer();
+ // 6.4 - Forget the media element's media-resource-specific tracks.
+ forgetResourceSpecificTracks();
- // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
- if (m_networkState != NETWORK_EMPTY) {
- m_networkState = NETWORK_EMPTY;
+ // 6.5 - If readyState is not set to HAVE_NOTHING, then set it to that state.
m_readyState = HAVE_NOTHING;
m_readyStateMaximum = HAVE_NOTHING;
- refreshCachedTime();
+
+ // 6.6 - If the paused attribute is false, then set it to true.
m_paused = true;
- m_seeking = false;
+
+ // 6.7 - If seeking is true, set it to false.
+ clearSeeking();
+
+ // 6.8 - 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.
+ m_lastSeekTime = MediaTime::zeroTime();
+ m_playedTimeRanges = TimeRanges::create();
+ // FIXME: Add support for firing this event. e.g., scheduleEvent(eventNames().timeUpdateEvent);
+
+ // 4.9 - 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.10 - Set the timeline offset to Not-a-Number (NaN).
+ // 4.11 - Update the duration attribute to Not-a-Number (NaN).
+
updateMediaController();
#if ENABLE(VIDEO_TRACK)
- if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- updateActiveTextTrackCues(0);
+ updateActiveTextTrackCues(MediaTime::zeroTime());
#endif
}
- // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
+ // 7 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
setPlaybackRate(defaultPlaybackRate());
- // 6 - Set the error attribute to null and the autoplaying flag to true.
- m_error = 0;
+ // 8 - Set the error attribute to null and the autoplaying flag to true.
+ m_error = nullptr;
m_autoplaying = true;
+ mediaSession().clientWillBeginAutoplaying();
- // 7 - Invoke the media element's resource selection algorithm.
+ // 9 - Invoke the media element's resource selection algorithm.
+ selectMediaResource();
- // 8 - Note: Playback of any previously playing media resource for this element stops.
+ // 10 - Note: Playback of any previously playing media resource for this element stops.
- // The resource selection algorithm
- // 1 - Set the networkState to NETWORK_NO_SOURCE
- m_networkState = NETWORK_NO_SOURCE;
-
- // 2 - Asynchronously await a stable state.
+ configureMediaControls();
+}
- m_playedTimeRanges = TimeRanges::create();
- m_lastSeekTime = 0;
+void HTMLMediaElement::selectMediaResource()
+{
+ // https://www.w3.org/TR/2016/REC-html51-20161101/semantics-embedded-content.html#resource-selection-algorithm
+ // The Resource Selection Algorithm
- // 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.
- MediaPlayer::Preload effectivePreload = m_mediaSession->effectivePreloadForElement(*this);
- if (effectivePreload != MediaPlayer::None)
- setShouldDelayLoadEvent(true);
+ // 1. Set the element’s networkState attribute to the NETWORK_NO_SOURCE value.
+ m_networkState = NETWORK_NO_SOURCE;
-#if PLATFORM(IOS)
- Settings* settings = document().settings();
- if (effectivePreload != MediaPlayer::None && settings && settings->mediaDataLoadsAutomatically())
- prepareToPlay();
-#endif
+ // 2. Set the element’s show poster flag to true.
+ setDisplayMode(Poster);
- configureMediaControls();
-}
+ // 3. Set the media element’s delaying-the-load-event flag to true (this delays the load event).
+ setShouldDelayLoadEvent(true);
-void HTMLMediaElement::loadInternal()
-{
- // 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());
+ // 4. in parallel await a stable state, allowing the task that invoked this algorithm to continue.
+ if (m_resourceSelectionTaskQueue.hasPendingTasks())
+ return;
- // If we can't start a load right away, start it later.
if (!m_mediaSession->pageAllowsDataLoading(*this)) {
+ LOG(Media, "HTMLMediaElement::selectMediaResource(%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;
}
- clearFlags(m_pendingActionFlags, LoadMediaResource);
-
- // 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
+ // 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 into the background.
- m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequirePageConsentToLoadMedia);
+ m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequirePageConsentToLoadMedia);
+
+ m_resourceSelectionTaskQueue.enqueueTask([this] {
+ // 5. If the media element’s blocked-on-parser flag is false, then populate the list of pending text tracks.
#if ENABLE(VIDEO_TRACK)
- if (hasMediaControls())
- mediaControls()->changedClosedCaptionsVisibility();
+ if (hasMediaControls())
+ mediaControls()->changedClosedCaptionsVisibility();
- // 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::sharedFeatures().webkitVideoTrackEnabled()) {
+ // HTMLMediaElement::textTracksAreReady will need "... the text tracks whose mode was not in the
+ // disabled state when the element's resource selection algorithm last started".
+ // FIXME: Update this to match "populate the list of pending text tracks" step.
m_textTracksWhenResourceSelectionBegan.clear();
if (m_textTracks) {
for (unsigned i = 0; i < m_textTracks->length(); ++i) {
TextTrack* track = m_textTracks->item(i);
- if (track->mode() != TextTrack::disabledKeyword())
+ if (track->mode() != TextTrack::Mode::Disabled)
m_textTracksWhenResourceSelectionBegan.append(track);
}
}
- }
#endif
- selectMediaResource();
-}
-
-void HTMLMediaElement::selectMediaResource()
-{
- LOG(Media, "HTMLMediaElement::selectMediaResource");
-
- enum Mode { attribute, children };
-
- // 3 - If the media element has a src attribute, then let mode be attribute.
- Mode mode = attribute;
- if (!fastHasAttribute(srcAttr)) {
- // 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 (auto firstSource = childrenOfType<HTMLSourceElement>(*this).first()) {
- mode = children;
+ enum Mode { None, Object, Attribute, Children };
+ Mode mode = None;
+
+ if (m_mediaProvider) {
+ // 6. If the media element has an assigned media provider object, then let mode be object.
+ mode = Object;
+ } else if (hasAttributeWithoutSynchronization(srcAttr)) {
+ // Otherwise, if the media element has no assigned media provider object but has a src attribute, then let mode be attribute.
+ mode = Attribute;
+ } else if (auto firstSource = childrenOfType<HTMLSourceElement>(*this).first()) {
+ // Otherwise, if the media element does not have an assigned media provider object and does not have a src attribute,
+ // but does have a source element child, then let mode be children and let candidate be the first such source element
+ // child in tree order.
+ mode = Children;
m_nextChildNodeToConsider = firstSource;
- m_currentSourceNode = 0;
+ 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
- // synchronous section ends.
+ // Otherwise the media element has no assigned media provider object and has neither a src attribute nor a source
+ // element child: set the networkState to NETWORK_EMPTY, and abort these steps; the synchronous section ends.
m_loadState = WaitingForSource;
setShouldDelayLoadEvent(false);
m_networkState = NETWORK_EMPTY;
- LOG(Media, "HTMLMediaElement::selectMediaResource, nothing to load");
+ LOG(Media, "HTMLMediaElement::selectMediaResource(%p) - nothing to load", this);
return;
}
- }
- // 4 - Set the media element's delaying-the-load-event flag to true (this delays the load event),
- // and set its networkState to NETWORK_LOADING.
- setShouldDelayLoadEvent(true);
- m_networkState = NETWORK_LOADING;
+ // 7. Set the media element’s networkState to NETWORK_LOADING.
+ m_networkState = NETWORK_LOADING;
- // 5 - Queue a task to fire a simple event named loadstart at the media element.
- scheduleEvent(eventNames().loadstartEvent);
+ // 8. Queue a task to fire a simple event named loadstart at the media element.
+ scheduleEvent(eventNames().loadstartEvent);
- // 6 - If mode is attribute, then run these substeps
- if (mode == attribute) {
- m_loadState = LoadingFromSrcAttr;
+ // 9. Run the appropriate steps from the following list:
+ // ↳ If mode is object
+ if (mode == Object) {
+ // 1. Set the currentSrc attribute to the empty string.
+ m_currentSrc = URL();
- // If the src attribute's value is the empty string ... jump down to the failed step below
- URL mediaURL = getNonEmptyURLAttribute(srcAttr);
- if (mediaURL.isEmpty()) {
- mediaLoadingFailed(MediaPlayer::FormatError);
- LOG(Media, "HTMLMediaElement::selectMediaResource, empty 'src'");
+ // 2. End the synchronous section, continuing the remaining steps in parallel.
+ // 3. Run the resource fetch algorithm with the assigned media provider object.
+ WTF::visit(WTF::makeVisitor(
+#if ENABLE(MEDIA_STREAM)
+ [this](RefPtr<MediaStream> stream) { m_mediaStreamSrcObject = stream; },
+#endif
+#if ENABLE(MEDIA_SOURCE)
+ [this](RefPtr<MediaSource> source) { m_mediaSource = source; },
+#endif
+ [this](RefPtr<Blob> blob) { m_blob = blob; }
+ ), m_mediaProvider.value());
+
+ ContentType contentType;
+ loadResource(URL(), contentType, String());
+ LOG(Media, "HTMLMediaElement::selectMediaResource(%p) - using 'srcObject' property", this);
+
+ // If that algorithm returns without aborting this one, then the load failed.
+ // 4. Failed with media provider: Reaching this step indicates that the media resource
+ // failed to load. Queue a task to run the dedicated media source failure steps.
+ // 5. Wait for the task queued by the previous step to have executed.
+ // 6. Abort these steps. The element won’t attempt to load another resource until this
+ // algorithm is triggered again.
return;
}
- if (!isSafeToLoadURL(mediaURL, Complain) || !dispatchBeforeLoadEvent(mediaURL.string())) {
- mediaLoadingFailed(MediaPlayer::FormatError);
+ // ↳ If mode is attribute
+ if (mode == Attribute) {
+ m_loadState = LoadingFromSrcAttr;
+
+ // 1. If the src attribute’s value is the empty string, then end the synchronous section,
+ // and jump down to the failed with attribute step below.
+ // 2. Let absolute URL be the absolute URL that would have resulted from parsing the URL
+ // specified by the src attribute’s value relative to the media element when the src
+ // attribute was last changed.
+ URL absoluteURL = getNonEmptyURLAttribute(srcAttr);
+ if (absoluteURL.isEmpty()) {
+ mediaLoadingFailed(MediaPlayer::FormatError);
+ LOG(Media, "HTMLMediaElement::selectMediaResource(%p) - empty 'src'", this);
+ return;
+ }
+
+ if (!isSafeToLoadURL(absoluteURL, Complain) || !dispatchBeforeLoadEvent(absoluteURL.string())) {
+ mediaLoadingFailed(MediaPlayer::FormatError);
+ return;
+ }
+
+ // 3. If absolute URL was obtained successfully, set the currentSrc attribute to absolute URL.
+ m_currentSrc = absoluteURL;
+
+ // 4. End the synchronous section, continuing the remaining steps in parallel.
+ // 5. If absolute URL was obtained successfully, run the resource fetch algorithm with absolute
+ // URL. If that algorithm returns without aborting this one, then the load failed.
+
+ // No type or key system information is available when the url comes
+ // from the 'src' attribute so MediaPlayer
+ // will have to pick a media engine based on the file extension.
+ ContentType contentType;
+ loadResource(absoluteURL, contentType, String());
+ LOG(Media, "HTMLMediaElement::selectMediaResource(%p) - using 'src' attribute url", this);
+
+ // 6. Failed with attribute: Reaching this step indicates that the media resource failed to load
+ // or that the given URL could not be resolved. Queue a task to run the dedicated media source failure steps.
+ // 7. Wait for the task queued by the previous step to have executed.
+ // 8. Abort these steps. The element won’t attempt to load another resource until this algorithm is triggered again.
return;
}
- // No type or key system information is available when the url comes
- // from the 'src' attribute so MediaPlayer
- // 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");
- return;
- }
-
- // Otherwise, the source elements will be used
- loadNextSourceChild();
+ // ↳ Otherwise (mode is children)
+ // (Ctd. in loadNextSourceChild())
+ loadNextSourceChild();
+ });
}
void HTMLMediaElement::loadNextSourceChild()
{
- ContentType contentType((String()));
+ ContentType contentType;
String keySystem;
URL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain);
if (!mediaURL.isValid()) {
@@ -1129,40 +1430,18 @@ void HTMLMediaElement::loadNextSourceChild()
return;
}
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- if (!shouldUseVideoPluginProxy())
-#endif
- // Recreate the media player for the new url
- createMediaPlayer();
+ // Recreate the media player for the new url
+ createMediaPlayer();
m_loadState = LoadingFromSourceElement;
loadResource(mediaURL, contentType, keySystem);
}
-static URL createFileURLForApplicationCacheResource(const String& path)
-{
- // URL should have a function to create a url from a path, but it does not. This function
- // is not suitable because URL::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));
- URL url(cfURL.get());
-#else
- URL url;
-
- url.setProtocol(ASCIILiteral("file"));
- url.setPath(path);
-#endif
- return url;
-}
-
void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentType, const String& keySystem)
{
- ASSERT(isSafeToLoadURL(initialURL, Complain));
+ ASSERT(initialURL.isEmpty() || 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();
if (!frame) {
@@ -1170,20 +1449,33 @@ void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentT
return;
}
+ Page* page = frame->page();
+ if (!page) {
+ mediaLoadingFailed(MediaPlayer::FormatError);
+ return;
+ }
+
URL url = initialURL;
- if (!frame->loader().willLoadMediaElementURL(url)) {
+ if (!url.isEmpty() && !frame->loader().willLoadMediaElementURL(url)) {
mediaLoadingFailed(MediaPlayer::FormatError);
return;
}
-
+
+#if ENABLE(CONTENT_EXTENSIONS)
+ if (auto* documentLoader = frame->loader().documentLoader()) {
+ if (page->userContentProvider().processContentExtensionRulesForLoad(url, ResourceType::Media, *documentLoader).blockedLoad) {
+ 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();
- ApplicationCacheResource* resource = 0;
- if (cacheHost && cacheHost->shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource)) {
+ // If the URL should be loaded from the application cache, pass the URL of the cached file to the media engine.
+ ApplicationCacheResource* resource = nullptr;
+ if (!url.isEmpty() && frame->loader().documentLoader()->applicationCacheHost().shouldLoadResourceFromApplicationCache(ResourceRequest(url), resource)) {
// Resources that are not present in the manifest will always fail to load (at least, after the
// cache has been primed the first time), making the testing of offline applications simpler.
if (!resource || resource->path().isEmpty()) {
@@ -1192,85 +1484,101 @@ void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentT
}
}
- // Set m_currentSrc *before* changing to the cache url, the fact that we are loading from the app
+ // Log that we started loading a media element.
+ page->diagnosticLoggingClient().logDiagnosticMessage(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());
-
-#if ENABLE(MEDIA_STREAM)
- if (MediaStreamRegistry::registry().lookup(url.string()))
- m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequireUserGestureForRateChange);
-#endif
+ LOG(Media, "HTMLMediaElement::loadResource(%p) - m_currentSrc -> %s", this, urlForLoggingMedia(m_currentSrc).utf8().data());
- 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())
+ if (!autoplay() && !m_havePreparedToPlay)
m_player->setPreload(m_mediaSession->effectivePreloadForElement(*this));
m_player->setPreservesPitch(m_webkitPreservesPitch);
if (!m_explicitlyMuted) {
m_explicitlyMuted = true;
- m_muted = fastHasAttribute(mutedAttr);
+ m_muted = hasAttributeWithoutSynchronization(mutedAttr);
+ m_mediaSession->canProduceAudioChanged();
}
updateVolume();
+ bool loadAttempted = false;
#if ENABLE(MEDIA_SOURCE)
- ASSERT(!m_mediaSource);
-
- if (url.protocolIs(mediaSourceBlobProtocol))
- m_mediaSource = HTMLMediaSource::lookup(url.string());
+ if (!m_mediaSource && url.protocolIs(mediaSourceBlobProtocol))
+ m_mediaSource = MediaSource::lookup(url.string());
if (m_mediaSource) {
- if (m_mediaSource->attachToElement(this))
- m_player->load(url, contentType, m_mediaSource);
- else {
+ loadAttempted = true;
+ if (!m_mediaSource->attachToElement(*this) || !m_player->load(url, contentType, m_mediaSource.get())) {
// Forget our reference to the MediaSource, so we leave it alone
// while processing remainder of load failure.
- m_mediaSource = 0;
+ m_mediaSource = nullptr;
mediaLoadingFailed(MediaPlayer::FormatError);
}
- } else
+ }
+#endif
+
+#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 (!m_player->load(url, contentType, keySystem))
+
+ if (!loadAttempted && m_blob) {
+ loadAttempted = true;
+ if (!m_player->load(m_blob->url(), contentType, keySystem))
+ mediaLoadingFailed(MediaPlayer::FormatError);
+ }
+
+ 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
// they are available.
updateDisplayState();
- if (renderer())
- renderer()->updateFromElement();
+ updateRenderer();
}
#if ENABLE(VIDEO_TRACK)
-static bool trackIndexCompare(TextTrack* a,
- TextTrack* b)
+
+static bool trackIndexCompare(TextTrack* a, TextTrack* b)
{
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
@@ -1281,16 +1589,15 @@ static bool eventTimeCueCompare(const std::pair<double, TextTrackCue*>& a,
// 12 - Further sort tasks in events that have the same time by the
// relative text track cue order of the text track cues associated
// with these tasks.
- return a.second->cueIndex() - b.second->cueIndex() < 0;
+ return a.second->isOrderedBefore(b.second);
}
static bool compareCueInterval(const CueInterval& one, const CueInterval& two)
{
return one.data()->isOrderedBefore(two.data());
-};
-
+}
-void HTMLMediaElement::updateActiveTextTrackCues(double movieTime)
+void HTMLMediaElement::updateActiveTextTrackCues(const MediaTime& movieTime)
{
// 4.8.10.8 Playing the media resource
@@ -1300,7 +1607,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
@@ -1313,7 +1620,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;
@@ -1327,7 +1635,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
@@ -1335,17 +1643,11 @@ 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) {
- 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();
-
+ if (lastTime >= MediaTime::zeroTime() && m_lastSeekTime < movieTime) {
+ for (auto& cue : m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime))) {
// Consider cues that may have been missed since the last seek time.
- if (cueStartTime > std::max(m_lastSeekTime, lastTime) && cueEndTime < movieTime)
- missedCues.append(potentiallySkippedCues[i]);
+ if (cue.low() > std::max(m_lastSeekTime, lastTime) && cue.high() < movieTime)
+ missedCues.append(cue);
}
}
@@ -1359,7 +1661,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.
@@ -1377,9 +1679,12 @@ 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;
}
@@ -1406,7 +1711,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;
@@ -1414,8 +1719,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(),
- missedCues[i].data()));
+ eventTasks.append({ missedCues[i].data()->startMediaTime(), missedCues[i].data() });
// 10 - For each text track [...] in missed cues, prepare an event
// named exit for the TextTrackCue object with the with the later of
@@ -1426,9 +1730,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({ missedCues[i].data()->endMediaTime(), missedCues[i].data() });
}
for (size_t i = 0; i < previousCuesSize; ++i) {
@@ -1436,8 +1739,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(),
- previousCues[i].data()));
+ eventTasks.append({ previousCues[i].data()->endMediaTime(), previousCues[i].data() });
}
for (size_t i = 0; i < currentCuesSize; ++i) {
@@ -1445,41 +1747,39 @@ 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(),
- currentCues[i].data()));
+ eventTasks.append({ currentCues[i].data()->startMediaTime(), currentCues[i].data() });
}
// 12 - Sort the tasks in events in ascending time order (tasks with earlier
// times first).
std::sort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare);
- for (size_t i = 0; i < eventTasks.size(); ++i) {
- if (!affectedTracks.contains(eventTasks[i].second->track()))
- affectedTracks.append(eventTasks[i].second->track());
+ for (auto& eventTask : eventTasks) {
+ if (!affectedTracks.contains(eventTask.second->track()))
+ affectedTracks.append(eventTask.second->track());
// 13 - Queue each task in events, in list order.
- RefPtr<Event> event;
// Each event in eventTasks may be either an enterEvent or an exitEvent,
// depending on the time that is associated with the event. This
// correctly identifies the type of the event, if the startTime is
// less than the endTime in the cue.
- 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());
-
- event = Event::create(eventNames().exitEvent, false, false);
- event->setTarget(eventTasks[i].second);
- m_asyncEventQueue.enqueueEvent(event.release());
+ if (eventTask.second->startTime() >= eventTask.second->endTime()) {
+ auto enterEvent = Event::create(eventNames().enterEvent, false, false);
+ enterEvent->setTarget(eventTask.second);
+ m_asyncEventQueue.enqueueEvent(WTFMove(enterEvent));
+
+ auto exitEvent = Event::create(eventNames().exitEvent, false, false);
+ exitEvent->setTarget(eventTask.second);
+ m_asyncEventQueue.enqueueEvent(WTFMove(exitEvent));
} else {
- if (eventTasks[i].first == eventTasks[i].second->startTime())
+ RefPtr<Event> event;
+ if (eventTask.first == eventTask.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());
+ event->setTarget(eventTask.second);
+ m_asyncEventQueue.enqueueEvent(WTFMove(event));
}
}
@@ -1489,21 +1789,19 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime)
// 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, ...
- for (size_t i = 0; i < affectedTracks.size(); ++i) {
- RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false);
- event->setTarget(affectedTracks[i]);
-
- m_asyncEventQueue.enqueueEvent(event.release());
+ for (auto& affectedTrack : affectedTracks) {
+ auto event = Event::create(eventNames().cuechangeEvent, false, false);
+ event->setTarget(affectedTrack);
+ m_asyncEventQueue.enqueueEvent(WTFMove(event));
// ... 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) {
- RefPtr<Event> event = Event::create(eventNames().cuechangeEvent, false, false);
- HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(affectedTracks[i])->trackElement();
+ if (is<LoadableTextTrack>(*affectedTrack)) {
+ auto event = Event::create(eventNames().cuechangeEvent, false, false);
+ auto* trackElement = downcast<LoadableTextTrack>(*affectedTrack).trackElement();
ASSERT(trackElement);
event->setTarget(trackElement);
-
- m_asyncEventQueue.enqueueEvent(event.release());
+ m_asyncEventQueue.enqueueEvent(WTFMove(event));
}
}
@@ -1554,21 +1852,21 @@ void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track)
}
}
-void HTMLMediaElement::audioTrackEnabledChanged(AudioTrack* track)
+void HTMLMediaElement::audioTrackEnabledChanged(AudioTrack& track)
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return;
if (m_audioTracks && m_audioTracks->contains(track))
m_audioTracks->scheduleChangeEvent();
+ if (ScriptController::processingUserGestureForMedia())
+ removeBehaviorsRestrictionsAfterFirstUserGesture(MediaElementSession::AllRestrictions & ~MediaElementSession::RequireUserGestureToControlControlsManager);
}
-void HTMLMediaElement::textTrackModeChanged(TextTrack* track)
+void HTMLMediaElement::textTrackModeChanged(TextTrack& track)
{
bool trackIsLoaded = true;
- if (track->trackType() == TextTrack::TrackElement) {
+ if (track.trackType() == TextTrack::TrackElement) {
trackIsLoaded = false;
for (auto& trackElement : childrenOfType<HTMLTrackElement>(*this)) {
- if (trackElement.track() == track) {
+ if (&trackElement.track() == &track) {
if (trackElement.readyState() == HTMLTrackElement::LOADING || trackElement.readyState() == HTMLTrackElement::LOADED)
trackIsLoaded = true;
break;
@@ -1581,34 +1879,32 @@ void HTMLMediaElement::textTrackModeChanged(TextTrack* track)
m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
// Mark this track as "configured" so configureTextTracks won't change the mode again.
- track->setHasBeenConfigured(true);
+ track.setHasBeenConfigured(true);
- if (track->mode() != TextTrack::disabledKeyword() && trackIsLoaded)
- textTrackAddCues(track, track->cues());
+ if (track.mode() != TextTrack::Mode::Disabled && trackIsLoaded)
+ textTrackAddCues(track, *track.cues());
-#if USE(PLATFORM_TEXT_TRACK_MENU)
- if (platformTextTrackMenu())
- platformTextTrackMenu()->trackWasSelected(track->platformTextTrack());
-#endif
-
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
}
-void HTMLMediaElement::videoTrackSelectedChanged(VideoTrack* track)
+void HTMLMediaElement::videoTrackSelectedChanged(VideoTrack& track)
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return;
if (m_videoTracks && m_videoTracks->contains(track))
m_videoTracks->scheduleChangeEvent();
}
-void HTMLMediaElement::textTrackKindChanged(TextTrack* track)
+void HTMLMediaElement::textTrackKindChanged(TextTrack& track)
{
- if (track->kind() != TextTrack::captionsKeyword() && track->kind() != TextTrack::subtitlesKeyword() && track->mode() == TextTrack::showingKeyword())
- track->setMode(TextTrack::hiddenKeyword());
+ if (track.kind() != TextTrack::Kind::Captions && track.kind() != TextTrack::Kind::Subtitles && track.mode() == TextTrack::Mode::Showing)
+ track.setMode(TextTrack::Mode::Hidden);
}
void HTMLMediaElement::beginIgnoringTrackDisplayUpdateRequests()
@@ -1621,79 +1917,101 @@ 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)
+void HTMLMediaElement::textTrackAddCues(TextTrack& track, const TextTrackCueList& cues)
{
- if (track->mode() == TextTrack::disabledKeyword())
+ if (track.mode() == TextTrack::Mode::Disabled)
return;
- TrackDisplayUpdateScope scope(this);
- for (size_t i = 0; i < cues->length(); ++i)
- textTrackAddCue(track, cues->item(i));
+ TrackDisplayUpdateScope scope { *this };
+ for (unsigned i = 0; i < cues.length(); ++i)
+ textTrackAddCue(track, *cues.item(i));
}
-void HTMLMediaElement::textTrackRemoveCues(TextTrack*, const TextTrackCueList* cues)
+void HTMLMediaElement::textTrackRemoveCues(TextTrack&, const TextTrackCueList& cues)
{
- TrackDisplayUpdateScope scope(this);
- for (size_t i = 0; i < cues->length(); ++i)
- textTrackRemoveCue(cues->item(i)->track(), cues->item(i));
+ TrackDisplayUpdateScope scope { *this };
+ for (unsigned i = 0; i < cues.length(); ++i) {
+ auto& cue = *cues.item(i);
+ textTrackRemoveCue(*cue.track(), cue);
+ }
}
-void HTMLMediaElement::textTrackAddCue(TextTrack* track, PassRefPtr<TextTrackCue> cue)
+void HTMLMediaElement::textTrackAddCue(TextTrack& track, TextTrackCue& cue)
{
- if (track->mode() == TextTrack::disabledKeyword())
+ if (track.mode() == TextTrack::Mode::Disabled)
return;
// Negative duration cues need be treated in the interval tree as
// zero-length cues.
- double endTime = std::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);
if (!m_cueTree.contains(interval))
m_cueTree.add(interval);
- updateActiveTextTrackCues(currentTime());
+ updateActiveTextTrackCues(currentMediaTime());
}
-void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtr<TextTrackCue> cue)
+void HTMLMediaElement::textTrackRemoveCue(TextTrack&, TextTrackCue& cue)
{
// Negative duration cues need to be treated in the interval tree as
// zero-length cues.
- double endTime = std::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);
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)->notifyRegionWhenRemovingDisplayTree(false);
+
size_t index = m_currentlyActiveCues.find(interval);
if (index != notFound) {
- cue->setIsActive(false);
+ cue.setIsActive(false);
m_currentlyActiveCues.remove(index);
}
- cue->removeDisplayTree();
- updateActiveTextTrackCues(currentTime());
+ if (cue.isRenderable())
+ toVTTCue(&cue)->removeDisplayTree();
+ updateActiveTextTrackCues(currentMediaTime());
+
+ if (cue.isRenderable())
+ toVTTCue(&cue)->notifyRegionWhenRemovingDisplayTree(true);
}
#endif
+static inline bool isAllowedToLoadMediaURL(HTMLMediaElement& element, const URL& url, bool isInUserAgentShadowTree)
+{
+ // Elements in user agent show tree should load whatever the embedding document policy is.
+ if (isInUserAgentShadowTree)
+ return true;
+
+ ASSERT(element.document().contentSecurityPolicy());
+ return element.document().contentSecurityPolicy()->allowMediaFromSource(url);
+}
+
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)) {
+ 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 (!isAllowedToLoadMediaURL(*this, url, isInUserAgentShadowTree())) {
+ LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%p) - %s -> rejected by Content Security Policy", this, urlForLoggingMedia(url).utf8().data());
return false;
}
@@ -1712,7 +2030,7 @@ void HTMLMediaElement::startProgressEventTimer()
void HTMLMediaElement::waitForSourceChange()
{
- LOG(Media, "HTMLMediaElement::waitForSourceChange");
+ LOG(Media, "HTMLMediaElement::waitForSourceChange(%p)", this);
stopPeriodicTimers();
m_loadState = WaitingForSource;
@@ -1724,18 +2042,16 @@ void HTMLMediaElement::waitForSourceChange()
setShouldDelayLoadEvent(false);
updateDisplayState();
-
- if (renderer())
- renderer()->updateFromElement();
+ updateRenderer();
}
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
@@ -1746,6 +2062,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;
@@ -1753,8 +2070,10 @@ void HTMLMediaElement::noneSupported()
// 7 - Queue a task to fire a simple event named error at the media element.
scheduleEvent(eventNames().errorEvent);
+ rejectPendingPlayPromises(DOMError::create("NotSupportedError", "The operation is not supported."));
+
#if ENABLE(MEDIA_SOURCE)
- closeMediaSource();
+ detachMediaSource();
#endif
// 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event.
@@ -1764,14 +2083,12 @@ void HTMLMediaElement::noneSupported()
// the element won't attempt to load another resource.
updateDisplayState();
-
- if (renderer())
- renderer()->updateFromElement();
+ updateRenderer();
}
void HTMLMediaElement::mediaLoadingFailedFatally(MediaPlayer::NetworkState error)
{
- LOG(Media, "HTMLMediaElement::mediaLoadingFailedFatally(%d)", static_cast<int>(error));
+ LOG(Media, "HTMLMediaElement::mediaLoadingFailedFatally(%p) - error = %d", this, static_cast<int>(error));
// 1 - The user agent should cancel the fetching process.
stopPeriodicTimers();
@@ -1790,7 +2107,7 @@ void HTMLMediaElement::mediaLoadingFailedFatally(MediaPlayer::NetworkState error
scheduleEvent(eventNames().errorEvent);
#if ENABLE(MEDIA_SOURCE)
- closeMediaSource();
+ detachMediaSource();
#endif
// 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a
@@ -1802,21 +2119,23 @@ void HTMLMediaElement::mediaLoadingFailedFatally(MediaPlayer::NetworkState error
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");
+ LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks(%p)", this);
m_asyncEventQueue.cancelAllEvents();
for (auto& source : childrenOfType<HTMLSourceElement>(*this))
source.cancelPendingErrorEvent();
-}
-Document* HTMLMediaElement::mediaPlayerOwningDocument()
-{
- return &document();
+ rejectPendingPlayPromises(DOMError::create("AbortError", "The operation was aborted."));
}
void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*)
@@ -1828,23 +2147,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& chromeClient = page->chrome().client();
-
+ DiagnosticLoggingClient& diagnosticLoggingClient = page->diagnosticLoggingClient();
if (!succeeded) {
- chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadingFailedKey(), errorMessage, DiagnosticLoggingKeys::failKey());
+ diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::mediaLoadingFailedKey(), errorMessage, DiagnosticLoggingResultFail, ShouldSample::No);
return;
}
- chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadedKey(), mediaEngine, DiagnosticLoggingKeys::noopKey());
+ diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::mediaLoadedKey(), mediaEngine, ShouldSample::No);
if (!page->hasSeenAnyMediaEngine())
- chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOneMediaEngineKey(), emptyString(), DiagnosticLoggingKeys::noopKey());
+ diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOneMediaEngineKey(), emptyString(), ShouldSample::No);
if (!page->hasSeenMediaEngine(mediaEngine))
- chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsMediaEngineKey(), mediaEngine, DiagnosticLoggingKeys::noopKey());
+ diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsMediaEngineKey(), mediaEngine, ShouldSample::No);
page->sawMediaEngine(mediaEngine);
}
@@ -1871,16 +2189,23 @@ 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();
}
@@ -1899,11 +2224,13 @@ void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error)
}
logMediaLoadRequest(document().page(), String(), stringForNetworkState(error), false);
+
+ m_mediaSession->clientCharacteristicsChanged();
}
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.
@@ -1963,9 +2290,22 @@ void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*)
endProcessingMediaPlayerCallback();
}
+SuccessOr<MediaPlaybackDenialReason> HTMLMediaElement::canTransitionFromAutoplayToPlay() const
+{
+ if (isAutoplaying()
+ && mediaSession().autoplayPermitted()
+ && paused()
+ && autoplay()
+ && !pausedForUserInteraction()
+ && !document().isSandboxed(SandboxAutomaticFeatures))
+ return mediaSession().playbackPermitted(*this);
+
+ return MediaPlaybackDenialReason::PageConsentRequired;
+}
+
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();
@@ -1974,7 +2314,7 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
ReadyState newState = static_cast<ReadyState>(state);
#if ENABLE(VIDEO_TRACK)
- bool tracksAreReady = !RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() || textTracksAreReady();
+ bool tracksAreReady = textTracksAreReady();
if (newState == oldState && m_tracksAreReady == tracksAreReady)
return;
@@ -2009,7 +2349,7 @@ 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) {
@@ -2023,13 +2363,28 @@ 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();
+ updateRenderer();
+
+ 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(UpdateState::Asynchronously);
+#endif
+
+ m_mediaSession->clientCharacteristicsChanged();
}
bool shouldUpdateDisplayState = false;
@@ -2046,7 +2401,7 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
scheduleEvent(eventNames().canplayEvent);
if (isPotentiallyPlaying)
- scheduleEvent(eventNames().playingEvent);
+ scheduleNotifyAboutPlaying();
shouldUpdateDisplayState = true;
}
@@ -2057,14 +2412,17 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
scheduleEvent(eventNames().canplaythroughEvent);
if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA)
- scheduleEvent(eventNames().playingEvent);
+ scheduleNotifyAboutPlaying();
- if (m_autoplaying && m_paused && autoplay() && !document().isSandboxed(SandboxAutomaticFeatures) && m_mediaSession->playbackPermitted(*this)) {
+ auto success = canTransitionFromAutoplayToPlay();
+ if (success) {
m_paused = false;
invalidateCachedTime();
+ m_playbackStartedTime = currentMediaTime().toDouble();
scheduleEvent(eventNames().playEvent);
- scheduleEvent(eventNames().playingEvent);
- }
+ scheduleNotifyAboutPlaying();
+ } else if (success.value() == MediaPlaybackDenialReason::UserGestureRequired)
+ m_preventedFromPlayingWithoutUserGesture = true;
shouldUpdateDisplayState = true;
}
@@ -2080,134 +2438,75 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
updatePlayState();
updateMediaController();
#if ENABLE(VIDEO_TRACK)
- if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- updateActiveTextTrackCues(currentTime());
+ updateActiveTextTrackCues(currentMediaTime());
#endif
}
-#if ENABLE(ENCRYPTED_MEDIA)
-void HTMLMediaElement::mediaPlayerKeyAdded(MediaPlayer*, const String& keySystem, const String& sessionId)
+#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
+RefPtr<ArrayBuffer> HTMLMediaElement::mediaPlayerCachedKeyForKeyId(const String& keyId) const
{
- MediaKeyEventInit initializer;
- initializer.keySystem = keySystem;
- initializer.sessionId = sessionId;
- initializer.bubbles = false;
- initializer.cancelable = false;
-
- RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeyaddedEvent, initializer);
- event->setTarget(this);
- m_asyncEventQueue.enqueueEvent(event.release());
+ return m_webKitMediaKeys ? m_webKitMediaKeys->cachedKeyForKeyId(keyId) : nullptr;
}
-void HTMLMediaElement::mediaPlayerKeyError(MediaPlayer*, const String& keySystem, const String& sessionId, MediaPlayerClient::MediaKeyErrorCode errorCode, unsigned short systemCode)
+bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, Uint8Array* initData)
{
- MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
- switch (errorCode) {
- case MediaPlayerClient::UnknownError:
- mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
- break;
- case MediaPlayerClient::ClientError:
- mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT;
- break;
- case MediaPlayerClient::ServiceError:
- mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_SERVICE;
- break;
- case MediaPlayerClient::OutputError:
- mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_OUTPUT;
- break;
- case MediaPlayerClient::HardwareChangeError:
- mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_HARDWARECHANGE;
- break;
- case MediaPlayerClient::DomainError:
- mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_DOMAIN;
- break;
+ if (!hasEventListeners("webkitneedkey")) {
+ m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED);
+ scheduleEvent(eventNames().errorEvent);
+ return false;
}
- 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);
+ auto event = WebKitMediaKeyNeededEvent::create(eventNames().webkitneedkeyEvent, initData);
event->setTarget(this);
- m_asyncEventQueue.enqueueEvent(event.release());
+ m_asyncEventQueue.enqueueEvent(WTFMove(event));
+
+ return true;
}
-void HTMLMediaElement::mediaPlayerKeyMessage(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* message, unsigned messageLength, const URL& defaultURL)
+String HTMLMediaElement::mediaPlayerMediaKeysStorageDirectory() const
{
- MediaKeyEventInit initializer;
- initializer.keySystem = keySystem;
- initializer.sessionId = sessionId;
- initializer.message = Uint8Array::create(message, messageLength);
- initializer.defaultURL = defaultURL;
- initializer.bubbles = false;
- initializer.cancelable = false;
+ String storageDirectory = document().settings().mediaKeysStorageDirectory();
+ if (storageDirectory.isEmpty())
+ return emptyString();
- RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitkeymessageEvent, initializer);
- event->setTarget(this);
- m_asyncEventQueue.enqueueEvent(event.release());
+ return pathByAppendingComponent(storageDirectory, SecurityOriginData::fromSecurityOrigin(document().securityOrigin()).databaseIdentifier());
}
-bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, const String& keySystem, const String& sessionId, const unsigned char* initData, unsigned initDataLength)
+void HTMLMediaElement::webkitSetMediaKeys(WebKitMediaKeys* mediaKeys)
{
- if (!hasEventListeners(eventNames().webkitneedkeyEvent)) {
- m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED);
- scheduleEvent(eventNames().errorEvent);
- return false;
- }
-
- MediaKeyEventInit initializer;
- initializer.keySystem = keySystem;
- initializer.sessionId = sessionId;
- initializer.initData = Uint8Array::create(initData, initDataLength);
- initializer.bubbles = false;
- initializer.cancelable = false;
+ if (m_webKitMediaKeys == mediaKeys)
+ return;
- RefPtr<Event> event = MediaKeyEvent::create(eventNames().webkitneedkeyEvent, initializer);
- event->setTarget(this);
- m_asyncEventQueue.enqueueEvent(event.release());
- return true;
+ if (m_webKitMediaKeys)
+ m_webKitMediaKeys->setMediaElement(nullptr);
+ m_webKitMediaKeys = mediaKeys;
+ if (m_webKitMediaKeys)
+ m_webKitMediaKeys->setMediaElement(this);
}
-#endif
-#if ENABLE(ENCRYPTED_MEDIA_V2)
-bool HTMLMediaElement::mediaPlayerKeyNeeded(MediaPlayer*, Uint8Array* initData)
+void HTMLMediaElement::keyAdded()
{
- if (!hasEventListeners("webkitneedkey")) {
- m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED);
- scheduleEvent(eventNames().errorEvent);
- return false;
- }
+ if (m_player)
+ m_player->keyAdded();
+}
- MediaKeyNeededEventInit initializer;
- initializer.initData = initData;
- initializer.bubbles = false;
- initializer.cancelable = false;
+#endif
- RefPtr<Event> event = MediaKeyNeededEvent::create(eventNames().webkitneedkeyEvent, initializer);
- event->setTarget(this);
- m_asyncEventQueue.enqueueEvent(event.release());
+#if ENABLE(ENCRYPTED_MEDIA)
- return true;
+MediaKeys* HTMLMediaElement::mediaKeys() const
+{
+ return nullptr;
}
-void HTMLMediaElement::setMediaKeys(MediaKeys* mediaKeys)
+void HTMLMediaElement::setMediaKeys(MediaKeys*, Ref<DeferredPromise>&&)
{
- if (m_mediaKeys == mediaKeys)
- return;
-
- if (m_mediaKeys)
- m_mediaKeys->setMediaElement(0);
- m_mediaKeys = mediaKeys;
- if (m_mediaKeys)
- m_mediaKeys->setMediaElement(this);
+ notImplemented();
}
-#endif
-void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>&)
+#endif // ENABLE(ENCRYPTED_MEDIA)
+
+void HTMLMediaElement::progressEventTimerFired()
{
ASSERT(m_player);
if (m_networkState != NETWORK_LOADING)
@@ -2220,8 +2519,7 @@ void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>&)
scheduleEvent(eventNames().progressEvent);
m_previousProgressTime = time;
m_sentStalledEvent = false;
- if (renderer())
- renderer()->updateFromElement();
+ updateRenderer();
if (hasMediaControls())
mediaControls()->bufferingProgressed();
} else if (timedelta > 3.0 && !m_sentStalledEvent) {
@@ -2233,29 +2531,24 @@ void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>&)
void HTMLMediaElement::rewind(double timeDelta)
{
- LOG(Media, "HTMLMediaElement::rewind(%f)", timeDelta);
- setCurrentTime(std::max(currentTime() - timeDelta, minTimeSeekable()));
+ LOG(Media, "HTMLMediaElement::rewind(%p) - %f", this, timeDelta);
+ setCurrentTime(std::max(currentMediaTime() - MediaTime::createWithDouble(timeDelta), minTimeSeekable()));
}
void HTMLMediaElement::returnToRealtime()
{
- LOG(Media, "HTMLMediaElement::returnToRealtime");
+ 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;
@@ -2267,12 +2560,18 @@ void HTMLMediaElement::prepareToPlay()
if (m_havePreparedToPlay)
return;
m_havePreparedToPlay = true;
- m_player->prepareToPlay();
+ if (m_player)
+ m_player->prepareToPlay();
}
void HTMLMediaElement::fastSeek(double time)
{
- LOG(Media, "HTMLMediaElement::fastSeek(%f)", time);
+ fastSeek(MediaTime::createWithDouble(time));
+}
+
+void HTMLMediaElement::fastSeek(const MediaTime& 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
@@ -2280,22 +2579,29 @@ void HTMLMediaElement::fastSeek(double time)
// 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();
- double delta = time - currentTime();
- double negativeTolerance = delta >= 0 ? delta : std::numeric_limits<double>::infinity();
- double positiveTolerance = delta < 0 ? -delta : std::numeric_limits<double>::infinity();
+ MediaTime delta = time - currentMediaTime();
+ MediaTime negativeTolerance = delta >= MediaTime::zeroTime() ? delta : MediaTime::positiveInfiniteTime();
+ MediaTime positiveTolerance = delta < MediaTime::zeroTime() ? -delta : MediaTime::positiveInfiniteTime();
- seekWithTolerance(time, negativeTolerance, positiveTolerance);
+ seekWithTolerance(time, negativeTolerance, positiveTolerance, true);
}
-void HTMLMediaElement::seek(double time)
+void HTMLMediaElement::seek(const MediaTime& time)
{
- LOG(Media, "HTMLMediaElement::seek(%f)", time);
- seekWithTolerance(time, 0, 0);
+ LOG(Media, "HTMLMediaElement::seek(%p) - %s", this, toString(time).utf8().data());
+ seekWithTolerance(time, MediaTime::zeroTime(), MediaTime::zeroTime(), true);
}
-void HTMLMediaElement::seekWithTolerance(double time, double negativeTolerance, double positiveTolerance)
+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);
@@ -2310,27 +2616,65 @@ void HTMLMediaElement::seekWithTolerance(double time, double negativeTolerance,
// Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set.
refreshCachedTime();
- double now = currentTime();
+ MediaTime now = currentMediaTime();
// 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;
+ }
// 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.
- // Nothing to be done here.
+ 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();
+
+ if (ScriptController::processingUserGestureForMedia())
+ m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager);
+}
+
+void HTMLMediaElement::seekTask()
+{
+ LOG(Media, "HTMLMediaElement::seekTask(%p)", this);
+
+ if (!m_player) {
+ clearSeeking();
+ return;
+ }
+
+ 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 = std::min(time, duration());
+ time = std::min(time, durationMediaTime());
// 7 - If the new playback position is less than the earliest possible position, let it be that position instead.
- double earliestTime = m_player->startTime();
+ 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
@@ -2339,9 +2683,9 @@ void HTMLMediaElement::seekWithTolerance(double time, double negativeTolerance,
// 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);
@@ -2350,36 +2694,38 @@ void HTMLMediaElement::seekWithTolerance(double time, double negativeTolerance,
// 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->isClosed())
+ 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;
// 10 - Queue a task to fire a simple event named seeking at the element.
scheduleEvent(eventNames().seekingEvent);
@@ -2392,18 +2738,20 @@ void HTMLMediaElement::seekWithTolerance(double time, double negativeTolerance,
// 13 - Await a stable state. The synchronous section consists of all the remaining steps of this algorithm.
}
-void HTMLMediaElement::finishSeek()
+void HTMLMediaElement::clearSeeking()
{
- LOG(Media, "HTMLMediaElement::finishSeek");
-
-#if ENABLE(MEDIA_SOURCE)
- if (m_mediaSource)
- m_mediaSource->monitorSourceBuffers();
-#endif
+ m_seeking = false;
+ m_pendingSeekType = NoSeek;
+ invalidateCachedTime();
+}
+void HTMLMediaElement::finishSeek()
+{
// 4.8.10.9 Seeking
// 14 - Set the seeking IDL attribute to false.
- m_seeking = false;
+ clearSeeking();
+
+ LOG(Media, "HTMLMediaElement::finishSeek(%p) - current time = %s", this, toString(currentMediaTime()).utf8().data());
// 15 - Run the time maches on steps.
// Handled by mediaPlayerTimeChanged().
@@ -2413,6 +2761,11 @@ void HTMLMediaElement::finishSeek()
// 17 - Queue a task to fire a simple event named seeked at the element.
scheduleEvent(eventNames().seekedEvent);
+
+#if ENABLE(MEDIA_SOURCE)
+ if (m_mediaSource)
+ m_mediaSource->monitorSourceBuffers();
+#endif
}
HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
@@ -2441,12 +2794,26 @@ void HTMLMediaElement::refreshCachedTime() const
return;
m_cachedTime = m_player->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
@@ -2454,29 +2821,33 @@ void HTMLMediaElement::invalidateCachedTime()
static const double minimumTimePlayingBeforeCacheSnapshot = 0.5;
m_minimumClockTimeToUpdateCachedTime = monotonicallyIncreasingTime() + minimumTimePlayingBeforeCacheSnapshot;
- m_cachedTime = MediaPlayer::invalidTime();
}
// 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;
}
@@ -2485,17 +2856,17 @@ double HTMLMediaElement::currentTime() const
double now = monotonicallyIncreasingTime();
double maximumDurationToCacheMediaTime = m_player->maximumDurationToCacheMediaTime();
- if (maximumDurationToCacheMediaTime && m_cachedTime != MediaPlayer::invalidTime() && !m_paused && now > m_minimumClockTimeToUpdateCachedTime) {
+ 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 (clockDelta < maximumDurationToCacheMediaTime) {
- double adjustedCacheTime = m_cachedTime + (m_playbackRate * clockDelta);
+ 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;
}
@@ -2504,30 +2875,51 @@ double HTMLMediaElement::currentTime() const
#if LOG_CACHED_TIME_WARNINGS
if (maximumDurationToCacheMediaTime && now > m_minimumClockTimeToUpdateCachedTime && m_cachedTime != MediaPlayer::invalidTime()) {
double clockDelta = now - m_clockTimeAtLastCachedTimeUpdate;
- double delta = m_cachedTime + (m_playbackRate * clockDelta) - m_player->currentTime();
- LOG(Media, "HTMLMediaElement::currentTime - cached time was %f seconds off of media time when it expired", delta);
+ 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;
- seek(time);
+ seekInternal(time);
+}
+
+ExceptionOr<void> HTMLMediaElement::setCurrentTimeForBindings(double time)
+{
+ if (m_mediaController)
+ return Exception { INVALID_STATE_ERR };
+ seek(MediaTime::createWithDouble(time));
+ return { };
}
double HTMLMediaElement::duration() const
{
+ return durationMediaTime().toDouble();
+}
+
+MediaTime HTMLMediaElement::durationMediaTime() const
+{
if (m_player && m_readyState >= HAVE_METADATA)
return m_player->duration();
- return std::numeric_limits<double>::quiet_NaN();
+ return MediaTime::invalidTime();
}
bool HTMLMediaElement::paused() const
@@ -2544,31 +2936,80 @@ 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);
+ 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_playbackRate != rate) {
- m_playbackRate = rate;
+ if (m_requestedPlaybackRate != rate) {
+ m_reportedPlaybackRate = m_requestedPlaybackRate = rate;
invalidateCachedTime();
scheduleEvent(eventNames().ratechangeEvent);
}
@@ -2576,9 +3017,9 @@ void HTMLMediaElement::setPlaybackRate(double rate)
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
@@ -2588,7 +3029,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;
@@ -2600,39 +3041,41 @@ 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
{
-#if PLATFORM(IOS)
- // Unless the restriction on requiring user actions has been lifted, we do not
- // allow playback to start just because the page has "autoplay". They are either
- // lifted through Settings, or once the user explictly calls load() or play()
- // because they have OK'ed us loading data. This allows playback to continue if
- // the URL is changed while the movie is playing.
- if (!m_mediaSession->playbackPermitted(*this) || !m_mediaSession->dataLoadingPermitted(*this))
- return false;
-#endif
-
- return fastHasAttribute(autoplayAttr);
+ return hasAttributeWithoutSynchronization(autoplayAttr);
}
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();
@@ -2641,88 +3084,161 @@ String HTMLMediaElement::preload() const
void HTMLMediaElement::setPreload(const String& preload)
{
- LOG(Media, "HTMLMediaElement::setPreload(%s)", preload.utf8().data());
- setAttribute(preloadAttr, preload);
+ 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
+
+ setAttributeWithoutSynchronization(preloadAttr, preload);
}
-void HTMLMediaElement::play()
+void HTMLMediaElement::play(DOMPromise<void>&& promise)
{
- LOG(Media, "HTMLMediaElement::play()");
+ LOG(Media, "HTMLMediaElement::play(%p)", this);
- if (!m_mediaSession->playbackPermitted(*this))
+ auto success = m_mediaSession->playbackPermitted(*this);
+ if (!success) {
+ if (success.value() == MediaPlaybackDenialReason::UserGestureRequired)
+ m_preventedFromPlayingWithoutUserGesture = true;
+ promise.reject(NotAllowedError);
+ return;
+ }
+
+ if (m_error && m_error->code() == MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED) {
+ promise.reject(NOT_SUPPORTED_ERR, "The operation is not supported.");
return;
- if (ScriptController::processingUserGesture())
+ }
+
+ if (ScriptController::processingUserGestureForMedia())
removeBehaviorsRestrictionsAfterFirstUserGesture();
- if (m_mediaSession->state() == MediaSession::Interrupted) {
- m_resumePlaybackAfterInterruption = true;
+ if (!playInternal()) {
+ promise.reject(NotAllowedError);
return;
}
-
+
+ m_pendingPlayPromises.append(WTFMove(promise));
+}
+
+void HTMLMediaElement::play()
+{
+ LOG(Media, "HTMLMediaElement::play(%p)", this);
+
+ auto success = m_mediaSession->playbackPermitted(*this);
+ if (!success) {
+ if (success.value() == MediaPlaybackDenialReason::UserGestureRequired)
+ m_preventedFromPlayingWithoutUserGesture = true;
+ return;
+ }
+ if (ScriptController::processingUserGestureForMedia())
+ removeBehaviorsRestrictionsAfterFirstUserGesture();
+
playInternal();
}
-void HTMLMediaElement::playInternal()
+bool HTMLMediaElement::playInternal()
{
- LOG(Media, "HTMLMediaElement::playInternal");
+ LOG(Media, "HTMLMediaElement::playInternal(%p)", this);
+
+ if (!m_mediaSession->clientWillBeginPlayback()) {
+ LOG(Media, " returning because of interruption");
+ return true; // Treat as success because we will begin playback on cessation of the interruption.
+ }
// 4.8.10.9. Playing the media resource
if (!m_player || m_networkState == NETWORK_EMPTY)
- scheduleDelayedAction(LoadMediaResource);
+ prepareForLoad();
if (endedPlayback())
- seek(0);
+ seekInternal(MediaTime::zeroTime());
if (m_mediaController)
- m_mediaController->bringElementUpToSpeed(this);
+ m_mediaController->bringElementUpToSpeed(*this);
if (m_paused) {
m_paused = false;
invalidateCachedTime();
+ m_playbackStartedTime = currentMediaTime().toDouble();
scheduleEvent(eventNames().playEvent);
if (m_readyState <= HAVE_CURRENT_DATA)
scheduleEvent(eventNames().waitingEvent);
else if (m_readyState >= HAVE_FUTURE_DATA)
- scheduleEvent(eventNames().playingEvent);
+ scheduleNotifyAboutPlaying();
+
+#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->kind() == MediaSessionKind::Content) {
+ if (Page* page = document().page())
+ page->chrome().client().focusedContentMediaElementDidChange(m_elementID);
+ }
+
+ if (!m_session->invoke()) {
+ pause();
+ return false;
+ }
+ }
+ }
+#endif
+ } else if (m_readyState >= HAVE_FUTURE_DATA)
+ scheduleResolvePendingPlayPromises();
+
+ if (ScriptController::processingUserGestureForMedia() && m_preventedFromPlayingWithoutUserGesture) {
+ if (Page* page = document().page())
+ page->chrome().client().didPlayMediaPreventedFromPlayingWithoutUserGesture();
+ m_preventedFromPlayingWithoutUserGesture = false;
}
+
m_autoplaying = false;
-#if PLATFORM(IOS)
- m_requestingPlay = true;
-#endif
updatePlayState();
- updateMediaController();
+
+ return true;
}
void HTMLMediaElement::pause()
{
- LOG(Media, "HTMLMediaElement::pause()");
+ LOG(Media, "HTMLMediaElement::pause(%p)", this);
if (!m_mediaSession->playbackPermitted(*this))
return;
- if (m_mediaSession->state() == MediaSession::Interrupted) {
- m_resumePlaybackAfterInterruption = false;
- return;
- }
-
+ if (ScriptController::processingUserGestureForMedia())
+ removeBehaviorsRestrictionsAfterFirstUserGesture(MediaElementSession::RequireUserGestureToControlControlsManager);
+
pauseInternal();
}
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 PLATFORM(IOS)
// 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;
-#endif
- scheduleDelayedAction(LoadMediaResource);
+ prepareForLoad();
}
m_autoplaying = false;
@@ -2731,131 +3247,36 @@ void HTMLMediaElement::pauseInternal()
m_paused = true;
scheduleTimeupdateEvent(false);
scheduleEvent(eventNames().pauseEvent);
+ rejectPendingPlayPromises(DOMError::create("AbortError", "The operation was aborted."));
+
+ if (MemoryPressureHandler::singleton().isUnderMemoryPressure())
+ purgeBufferedDataIfPossible();
}
updatePlayState();
}
#if ENABLE(MEDIA_SOURCE)
-void HTMLMediaElement::closeMediaSource()
-{
- if (!m_mediaSource)
- return;
-
- m_mediaSource->close();
- m_mediaSource = 0;
-}
-#endif
-
-#if ENABLE(ENCRYPTED_MEDIA)
-void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, PassRefPtr<Uint8Array> initData, ExceptionCode& ec)
-{
-#if ENABLE(ENCRYPTED_MEDIA_V2)
- static bool firstTime = true;
- if (firstTime && scriptExecutionContext()) {
- scriptExecutionContext()->addConsoleMessage(JSMessageSource, WarningMessageLevel, "'HTMLMediaElement.webkitGenerateKeyRequest()' is deprecated. Use 'MediaKeys.createSession()' instead.");
- firstTime = false;
- }
-#endif
-
- if (keySystem.isEmpty()) {
- ec = SYNTAX_ERR;
- return;
- }
-
- if (!m_player) {
- ec = INVALID_STATE_ERR;
- return;
- }
-
- const unsigned char* initDataPointer = 0;
- unsigned initDataLength = 0;
- if (initData) {
- initDataPointer = initData->data();
- initDataLength = initData->length();
- }
-
- MediaPlayer::MediaKeyException result = m_player->generateKeyRequest(keySystem, initDataPointer, initDataLength);
- ec = exceptionCodeForMediaKeyException(result);
-}
-
-void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, ExceptionCode& ec)
-{
- webkitGenerateKeyRequest(keySystem, Uint8Array::create(0), ec);
-}
-
-void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, PassRefPtr<Uint8Array> initData, const String& sessionId, ExceptionCode& ec)
-{
-#if ENABLE(ENCRYPTED_MEDIA_V2)
- static bool firstTime = true;
- if (firstTime && scriptExecutionContext()) {
- scriptExecutionContext()->addConsoleMessage(JSMessageSource, WarningMessageLevel, "'HTMLMediaElement.webkitAddKey()' is deprecated. Use 'MediaKeySession.update()' instead.");
- firstTime = false;
- }
-#endif
-
- if (keySystem.isEmpty()) {
- ec = SYNTAX_ERR;
- return;
- }
-
- if (!key) {
- ec = SYNTAX_ERR;
- return;
- }
-
- if (!key->length()) {
- ec = TYPE_MISMATCH_ERR;
- return;
- }
-
- if (!m_player) {
- ec = INVALID_STATE_ERR;
- return;
- }
-
- const unsigned char* initDataPointer = 0;
- unsigned initDataLength = 0;
- if (initData) {
- initDataPointer = initData->data();
- initDataLength = initData->length();
- }
-
- MediaPlayer::MediaKeyException result = m_player->addKey(keySystem, key->data(), key->length(), initDataPointer, initDataLength, sessionId);
- ec = exceptionCodeForMediaKeyException(result);
-}
-void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, ExceptionCode& ec)
+void HTMLMediaElement::detachMediaSource()
{
- webkitAddKey(keySystem, key, Uint8Array::create(0), String(), ec);
-}
-
-void HTMLMediaElement::webkitCancelKeyRequest(const String& keySystem, const String& sessionId, ExceptionCode& ec)
-{
- if (keySystem.isEmpty()) {
- ec = SYNTAX_ERR;
+ if (!m_mediaSource)
return;
- }
- if (!m_player) {
- ec = INVALID_STATE_ERR;
- return;
- }
-
- MediaPlayer::MediaKeyException result = m_player->cancelKeyRequest(keySystem, sessionId);
- ec = exceptionCodeForMediaKeyException(result);
+ m_mediaSource->detachFromElement(*this);
+ m_mediaSource = nullptr;
}
#endif
bool HTMLMediaElement::loop() const
{
- return fastHasAttribute(loopAttr);
+ return hasAttributeWithoutSynchronization(loopAttr);
}
void HTMLMediaElement::setLoop(bool b)
{
- LOG(Media, "HTMLMediaElement::setLoop(%s)", boolString(b));
+ LOG(Media, "HTMLMediaElement::setLoop(%p) - %s", this, boolString(b));
setBooleanAttribute(loopAttr, b);
}
@@ -2867,20 +3288,12 @@ bool HTMLMediaElement::controls() const
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())
- return true;
-
- return fastHasAttribute(controlsAttr);
+ return hasAttributeWithoutSynchronization(controlsAttr);
}
void HTMLMediaElement::setControls(bool b)
{
- LOG(Media, "HTMLMediaElement::setControls(%s)", boolString(b));
+ LOG(Media, "HTMLMediaElement::setControls(%p) - %s", this, boolString(b));
setBooleanAttribute(controlsAttr, b);
}
@@ -2889,56 +3302,74 @@ double HTMLMediaElement::volume() const
return m_volume;
}
-void HTMLMediaElement::setVolume(double vol, ExceptionCode& ec)
+ExceptionOr<void> HTMLMediaElement::setVolume(double volume)
{
- LOG(Media, "HTMLMediaElement::setVolume(%f)", vol);
+ LOG(Media, "HTMLMediaElement::setVolume(%p) - %f", this, volume);
+
+ if (!(volume >= 0 && volume <= 1))
+ return Exception { INDEX_SIZE_ERR };
- 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);
- }
+ if (m_volume == volume)
+ return { };
+
+ m_volume = volume;
+ m_volumeInitialized = true;
+ updateVolume();
+ scheduleEvent(eventNames().volumechangeEvent);
#endif
+ return { };
}
bool HTMLMediaElement::muted() const
{
- return m_explicitlyMuted ? m_muted : fastHasAttribute(mutedAttr);
+ return m_explicitlyMuted ? m_muted : hasAttributeWithoutSynchronization(mutedAttr);
}
void HTMLMediaElement::setMuted(bool muted)
{
- LOG(Media, "HTMLMediaElement::setMuted(%s)", boolString(muted));
+ LOG(Media, "HTMLMediaElement::setMuted(%p) - %s", this, boolString(muted));
-#if PLATFORM(IOS)
- UNUSED_PARAM(muted);
-#else
- if (m_muted != muted || !m_explicitlyMuted) {
+ bool mutedStateChanged = m_muted != muted;
+ if (mutedStateChanged || !m_explicitlyMuted) {
m_muted = muted;
m_explicitlyMuted = true;
+
+ if (ScriptController::processingUserGestureForMedia())
+ removeBehaviorsRestrictionsAfterFirstUserGesture(MediaElementSession::AllRestrictions & ~MediaElementSession::RequireUserGestureToControlControlsManager);
+
// 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 (mutedStateChanged)
+ scheduleEvent(eventNames().volumechangeEvent);
+
+ updateShouldPlay();
+
+#if ENABLE(MEDIA_SESSION)
+ document().updateIsPlayingMedia(m_elementID);
+#else
+ document().updateIsPlayingMedia();
#endif
+
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+ updateMediaState(UpdateState::Asynchronously);
+#endif
+ m_mediaSession->canProduceAudioChanged();
+ }
+
+ scheduleUpdatePlaybackControlsManager();
}
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
@@ -2951,7 +3382,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()) {
@@ -2966,16 +3397,71 @@ void HTMLMediaElement::beginScrubbing()
setPausedInternal(true);
}
}
+
+ m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager);
}
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;
@@ -2989,12 +3475,12 @@ void HTMLMediaElement::startPlaybackProgressTimer()
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();
@@ -3003,15 +3489,14 @@ 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::sharedFeatures().webkitVideoTrackEnabled())
- updateActiveTextTrackCues(currentTime());
+ updateActiveTextTrackCues(currentMediaTime());
#endif
#if ENABLE(MEDIA_SOURCE)
@@ -3031,7 +3516,7 @@ 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_clockTimeAtLastUpdateEvent = now;
@@ -3048,39 +3533,37 @@ 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)
+void HTMLMediaElement::mediaPlayerDidAddAudioTrack(AudioTrackPrivate& track)
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return;
+ if (isPlaying() && !m_mediaSession->playbackPermitted(*this))
+ pauseInternal();
- addAudioTrack(AudioTrack::create(this, prpTrack));
+ addAudioTrack(AudioTrack::create(*this, track));
}
-void HTMLMediaElement::mediaPlayerDidAddTextTrack(PassRefPtr<InbandTextTrackPrivate> prpTrack)
+void HTMLMediaElement::mediaPlayerDidAddTextTrack(InbandTextTrackPrivate& track)
{
- 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);
+ auto textTrack = InbandTextTrack::create(*ActiveDOMObject::scriptExecutionContext(), *this, track);
textTrack->setMediaElement(this);
// 2. Set the new text track's kind, label, and language based on the semantics of the relevant data,
@@ -3106,203 +3589,110 @@ void HTMLMediaElement::mediaPlayerDidAddTextTrack(PassRefPtr<InbandTextTrackPriv
// 9. Fire an event with the name addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent
// interface, with the track attribute initialized to the text track's TextTrack object, at the media element's
// textTracks attribute's TextTrackList object.
- addTextTrack(textTrack.release());
+ addTextTrack(WTFMove(textTrack));
}
-void HTMLMediaElement::mediaPlayerDidAddVideoTrack(PassRefPtr<VideoTrackPrivate> prpTrack)
+void HTMLMediaElement::mediaPlayerDidAddVideoTrack(VideoTrackPrivate& track)
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return;
-
- addVideoTrack(VideoTrack::create(this, prpTrack));
+ addVideoTrack(VideoTrack::create(*this, track));
}
-void HTMLMediaElement::mediaPlayerDidRemoveAudioTrack(PassRefPtr<AudioTrackPrivate> prpTrack)
+void HTMLMediaElement::mediaPlayerDidRemoveAudioTrack(AudioTrackPrivate& track)
{
- prpTrack->willBeRemoved();
+ track.willBeRemoved();
}
-void HTMLMediaElement::mediaPlayerDidRemoveTextTrack(PassRefPtr<InbandTextTrackPrivate> prpTrack)
+void HTMLMediaElement::mediaPlayerDidRemoveTextTrack(InbandTextTrackPrivate& track)
{
- prpTrack->willBeRemoved();
+ track.willBeRemoved();
}
-void HTMLMediaElement::mediaPlayerDidRemoveVideoTrack(PassRefPtr<VideoTrackPrivate> prpTrack)
+void HTMLMediaElement::mediaPlayerDidRemoveVideoTrack(VideoTrackPrivate& track)
{
- prpTrack->willBeRemoved();
+ track.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)
+void HTMLMediaElement::addAudioTrack(Ref<AudioTrack>&& track)
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return;
-
- audioTracks()->append(track);
+ audioTracks().append(WTFMove(track));
}
-void HTMLMediaElement::addTextTrack(PassRefPtr<TextTrack> track)
+void HTMLMediaElement::addTextTrack(Ref<TextTrack>&& track)
{
- 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);
+ textTracks().append(WTFMove(track));
closeCaptionTracksChanged();
}
-void HTMLMediaElement::addVideoTrack(PassRefPtr<VideoTrack> track)
+void HTMLMediaElement::addVideoTrack(Ref<VideoTrack>&& track)
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return;
-
- videoTracks()->append(track);
+ videoTracks().append(WTFMove(track));
}
-void HTMLMediaElement::removeAudioTrack(AudioTrack* track)
+void HTMLMediaElement::removeAudioTrack(AudioTrack& track)
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return;
-
m_audioTracks->remove(track);
+ track.clearClient();
}
-void HTMLMediaElement::removeTextTrack(TextTrack* track)
+void HTMLMediaElement::removeTextTrack(TextTrack& track, bool scheduleEvent)
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return;
-
- TrackDisplayUpdateScope scope(this);
- TextTrackCueList* cues = track->cues();
- if (cues)
- textTrackRemoveCues(track, cues);
- track->clearClient();
- m_textTracks->remove(track);
+ TrackDisplayUpdateScope scope { *this };
+ if (auto* cues = track.cues())
+ textTrackRemoveCues(track, *cues);
+ track.clearClient();
+ if (m_textTracks)
+ m_textTracks->remove(track, scheduleEvent);
closeCaptionTracksChanged();
}
-void HTMLMediaElement::removeVideoTrack(VideoTrack* track)
+void HTMLMediaElement::removeVideoTrack(VideoTrack& track)
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return;
-
m_videoTracks->remove(track);
+ track.clearClient();
}
-void HTMLMediaElement::removeAllInbandTracks()
+void HTMLMediaElement::forgetResourceSpecificTracks()
{
while (m_audioTracks && m_audioTracks->length())
- removeAudioTrack(m_audioTracks->lastItem());
+ removeAudioTrack(*m_audioTracks->lastItem());
if (m_textTracks) {
- TrackDisplayUpdateScope scope(this);
+ TrackDisplayUpdateScope scope { *this };
for (int i = m_textTracks->length() - 1; i >= 0; --i) {
- TextTrack* track = m_textTracks->item(i);
-
- if (track->trackType() == TextTrack::InBand)
- removeTextTrack(track);
+ auto& track = *m_textTracks->item(i);
+ if (track.trackType() == TextTrack::InBand)
+ removeTextTrack(track, false);
}
}
while (m_videoTracks && m_videoTracks->length())
- removeVideoTrack(m_videoTracks->lastItem());
+ removeVideoTrack(*m_videoTracks->lastItem());
}
-PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const String& kind, const String& label, const String& language, ExceptionCode& ec)
+ExceptionOr<TextTrack&> HTMLMediaElement::addTextTrack(const String& kind, const String& label, const String& language)
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return 0;
-
// 4.8.10.12.4 Text track API
// The addTextTrack(kind, label, language) method of media elements, when invoked, must run the following steps:
// 1. If kind is not one of the following strings, then throw a SyntaxError exception and abort these steps
- if (!TextTrack::isValidKindKeyword(kind)) {
- ec = SYNTAX_ERR;
- return 0;
- }
+ if (!TextTrack::isValidKindKeyword(kind))
+ return Exception { TypeError };
// 2. If the label argument was omitted, let label be the empty string.
// 3. If the language argument was omitted, let language be the empty string.
@@ -3310,73 +3700,58 @@ 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, emptyString(), label, language);
+ auto track = TextTrack::create(ActiveDOMObject::scriptExecutionContext(), this, kind, emptyString(), label, language);
+ auto& trackReference = track.get();
// Note, due to side effects when changing track parameters, we have to
// first append the track to the text track list.
// 6. Add the new text track to the media element's list of text tracks.
- addTextTrack(textTrack);
+ addTextTrack(WTFMove(track));
// ... its text track readiness state to the text track loaded state ...
- textTrack->setReadinessState(TextTrack::Loaded);
+ trackReference.setReadinessState(TextTrack::Loaded);
// ... its text track mode to the text track hidden mode, and its text track list of cues to an empty list ...
- textTrack->setMode(TextTrack::hiddenKeyword());
+ trackReference.setMode(TextTrack::Mode::Hidden);
- return textTrack.release();
+ return trackReference;
}
-AudioTrackList* HTMLMediaElement::audioTracks()
+AudioTrackList& HTMLMediaElement::audioTracks()
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return 0;
-
if (!m_audioTracks)
m_audioTracks = AudioTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
- return m_audioTracks.get();
+ return *m_audioTracks;
}
-TextTrackList* HTMLMediaElement::textTracks()
+TextTrackList& HTMLMediaElement::textTracks()
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return 0;
-
if (!m_textTracks)
m_textTracks = TextTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
- return m_textTracks.get();
+ return *m_textTracks;
}
-VideoTrackList* HTMLMediaElement::videoTracks()
+VideoTrackList& HTMLMediaElement::videoTracks()
{
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return 0;
-
if (!m_videoTracks)
m_videoTracks = VideoTrackList::create(this, ActiveDOMObject::scriptExecutionContext());
- return m_videoTracks.get();
+ return *m_videoTracks;
}
-void HTMLMediaElement::didAddTextTrack(HTMLTrackElement* trackElement)
+void HTMLMediaElement::didAddTextTrack(HTMLTrackElement& trackElement)
{
- ASSERT(trackElement->hasTagName(trackTag));
-
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return;
+ ASSERT(trackElement.hasTagName(trackTag));
// 4.8.10.12.3 Sourcing out-of-band text tracks
// When a track element's parent element changes and the new parent is a media element,
// then the user agent must add the track element's corresponding text track to the
// media element's list of text tracks ... [continues in TextTrackList::append]
- RefPtr<TextTrack> textTrack = trackElement->track();
- if (!textTrack)
- return;
-
- addTextTrack(textTrack.release());
-
+ addTextTrack(trackElement.track());
+
// Do not schedule the track loading until parsing finishes so we don't start before all tracks
// in the markup have been added.
if (!m_parsingInProgress)
@@ -3386,25 +3761,20 @@ void HTMLMediaElement::didAddTextTrack(HTMLTrackElement* trackElement)
mediaControls()->closedCaptionTracksChanged();
}
-void HTMLMediaElement::didRemoveTextTrack(HTMLTrackElement* trackElement)
+void HTMLMediaElement::didRemoveTextTrack(HTMLTrackElement& trackElement)
{
- ASSERT(trackElement->hasTagName(trackTag));
-
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled())
- return;
+ ASSERT(trackElement.hasTagName(trackTag));
#if !LOG_DISABLED
- if (trackElement->hasTagName(trackTag)) {
- URL url = trackElement->getNonEmptyURLAttribute(srcAttr);
- LOG(Media, "HTMLMediaElement::didRemoveTrack - 'src' is %s", urlForLoggingMedia(url).utf8().data());
+ if (trackElement.hasTagName(trackTag)) {
+ URL url = trackElement.getNonEmptyURLAttribute(srcAttr);
+ LOG(Media, "HTMLMediaElement::didRemoveTrack(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data());
}
#endif
- RefPtr<TextTrack> textTrack = trackElement->track();
- if (!textTrack)
- return;
-
- textTrack->setHasBeenConfigured(false);
+ auto& textTrack = trackElement.track();
+
+ textTrack.setHasBeenConfigured(false);
if (!m_textTracks)
return;
@@ -3413,21 +3783,19 @@ void HTMLMediaElement::didRemoveTextTrack(HTMLTrackElement* trackElement)
// When a track element's parent element changes and the old parent was a media element,
// then the user agent must remove the track element's corresponding text track from the
// media element's list of text tracks.
- removeTextTrack(textTrack.get());
+ removeTextTrack(textTrack);
- size_t index = m_textTracksWhenResourceSelectionBegan.find(textTrack.get());
- if (index != notFound)
- m_textTracksWhenResourceSelectionBegan.remove(index);
+ m_textTracksWhenResourceSelectionBegan.removeFirst(&textTrack);
}
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;
+ 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).
@@ -3438,14 +3806,23 @@ void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group)
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];
- if (m_processingPreferenceChange && textTrack->mode() == TextTrack::showingKeyword())
+ if (m_processingPreferenceChange && textTrack->mode() == TextTrack::Mode::Showing)
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' and BCP 47 language '%s' has score %d", this, textTrack->kindKeyword().string().utf8().data(), textTrack->language().string().utf8().data(), textTrack->validBCP47Language().string().utf8().data(), trackScore);
if (trackScore) {
@@ -3458,7 +3835,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;
}
@@ -3480,82 +3857,207 @@ void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group)
}
}
- 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 (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, 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 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 (trackToEnable)
+ m_subtitleTrackLanguage = trackToEnable->language();
+ else
+ m_subtitleTrackLanguage = emptyString();
}
-
- // 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 (currentlyEnabledTracks.size()) {
for (size_t i = 0; i < currentlyEnabledTracks.size(); ++i) {
RefPtr<TextTrack> textTrack = currentlyEnabledTracks[i];
if (textTrack != trackToEnable)
- textTrack->setMode(TextTrack::disabledKeyword());
+ textTrack->setMode(TextTrack::Mode::Disabled);
}
}
- if (trackToEnable)
- trackToEnable->setMode(TextTrack::showingKeyword());
+ if (trackToEnable) {
+ trackToEnable->setMode(TextTrack::Mode::Showing);
+
+ // 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)
+{
+ JSC::VM& vm = globalObject.vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ auto mediaJSWrapper = toJS(&exec, &globalObject, media);
+
+ // Retrieve the controller through the JS object graph
+ JSC::JSObject* mediaJSWrapperObject = jsDynamicDowncast<JSC::JSObject*>(vm, mediaJSWrapper);
+ if (!mediaJSWrapperObject)
+ return JSC::jsNull();
+
+ JSC::Identifier controlsHost = JSC::Identifier::fromString(&vm, "controlsHost");
+ JSC::JSValue controlsHostJSWrapper = mediaJSWrapperObject->get(&exec, controlsHost);
+ RETURN_IF_EXCEPTION(scope, JSC::jsNull());
+
+ JSC::JSObject* controlsHostJSWrapperObject = jsDynamicDowncast<JSC::JSObject*>(vm, controlsHostJSWrapper);
+ if (!controlsHostJSWrapperObject)
+ return JSC::jsNull();
+
+ JSC::Identifier controllerID = JSC::Identifier::fromString(&vm, "controller");
+ JSC::JSValue controllerJSWrapper = controlsHostJSWrapperObject->get(&exec, controllerID);
+ RETURN_IF_EXCEPTION(scope, 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::VM& vm = globalObject->vm();
+ JSC::JSLockHolder lock(vm);
+ auto scope = DECLARE_CATCH_SCOPE(vm);
+ JSC::ExecState* exec = globalObject->globalExec();
+
+ JSC::JSValue controllerValue = controllerJSValue(*exec, *globalObject, *this);
+ JSC::JSObject* controllerObject = jsDynamicDowncast<JSC::JSObject*>(vm, 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 = jsDynamicDowncast<JSC::JSObject*>(vm, methodValue);
+ if (!methodObject)
+ return;
+
+ JSC::CallData callData;
+ JSC::CallType callType = methodObject->methodTable()->getCallData(methodObject, callData);
+ if (callType == JSC::CallType::None)
+ return;
+
+ JSC::MarkedArgumentBuffer noArguments;
+ JSC::call(exec, methodObject, callType, callData, controllerObject, noArguments);
+ scope.clearException();
+
+ m_haveSetUpCaptionContainer = true;
+#endif
+}
+
+void HTMLMediaElement::layoutSizeChanged()
+{
+#if ENABLE(MEDIA_CONTROLS_SCRIPT)
+ auto task = [this, protectedThis = makeRef(*this)] {
+ if (ShadowRoot* root = userAgentShadowRoot())
+ root->dispatchEvent(Event::create("resize", false, false));
+ };
+ m_resizeTaskQueue.enqueueTask(WTFMove(task));
+#endif
+
+ if (!m_receivedLayoutSizeChanged) {
+ m_receivedLayoutSizeChanged = true;
+ scheduleUpdatePlaybackControlsManager();
+ }
+
+ // If the video is a candidate for main content, we should register it for viewport visibility callbacks
+ // if it hasn't already been registered.
+ if (renderer() && m_mediaSession && !m_mediaSession->wantsToObserveViewportVisibilityForAutoplay() && m_mediaSession->wantsToObserveViewportVisibilityForMediaControls())
+ renderer()->registerForVisibleInViewportCallback();
+}
+
+void HTMLMediaElement::visibilityDidChange()
+{
+ updateShouldAutoplay();
+}
+
void HTMLMediaElement::setSelectedTextTrack(TextTrack* trackToSelect)
{
- TextTrackList* trackList = textTracks();
- if (!trackList || !trackList->length())
+ TextTrackList& trackList = textTracks();
+ if (!trackList.length())
return;
if (trackToSelect != TextTrack::captionMenuOffItem() && trackToSelect != TextTrack::captionMenuAutomaticItem()) {
- if (!trackList->contains(trackToSelect))
+ if (!trackToSelect || !trackList.contains(*trackToSelect))
return;
- for (int i = 0, length = trackList->length(); i < length; ++i) {
- TextTrack* track = trackList->item(i);
- if (!trackToSelect || track != trackToSelect)
- track->setMode(TextTrack::disabledKeyword());
+ for (int i = 0, length = trackList.length(); i < length; ++i) {
+ auto& track = *trackList.item(i);
+ if (&track != trackToSelect)
+ track.setMode(TextTrack::Mode::Disabled);
else
- track->setMode(TextTrack::showingKeyword());
+ track.setMode(TextTrack::Mode::Showing);
}
+ } else if (trackToSelect == TextTrack::captionMenuOffItem()) {
+ for (int i = 0, length = trackList.length(); i < length; ++i)
+ trackList.item(i)->setMode(TextTrack::Mode::Disabled);
}
- 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())
displayMode = CaptionUserPreferences::Automatic;
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;
+ if (trackToSelect->validBCP47Language().length())
+ captionPreferences.setPreferredLanguage(trackToSelect->validBCP47Language());
}
- captionPreferences->setCaptionDisplayMode(displayMode);
+ captionPreferences.setCaptionDisplayMode(displayMode);
}
void HTMLMediaElement::configureTextTracks()
@@ -3574,20 +4076,20 @@ void HTMLMediaElement::configureTextTracks()
if (!textTrack)
continue;
- String kind = textTrack->kind();
+ auto kind = textTrack->kind();
TrackGroup* currentGroup;
- if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword() || kind == TextTrack::forcedKeyword())
+ if (kind == TextTrack::Kind::Subtitles || kind == TextTrack::Kind::Captions || kind == TextTrack::Kind::Forced)
currentGroup = &captionAndSubtitleTracks;
- else if (kind == TextTrack::descriptionsKeyword())
+ else if (kind == TextTrack::Kind::Descriptions)
currentGroup = &descriptionTracks;
- else if (kind == TextTrack::chaptersKeyword())
+ else if (kind == TextTrack::Kind::Chapters)
currentGroup = &chapterTracks;
- else if (kind == TextTrack::metadataKeyword())
+ else if (kind == TextTrack::Kind::Metadata)
currentGroup = &metadataTracks;
else
currentGroup = &otherTracks;
- if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::showingKeyword())
+ if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::Mode::Showing)
currentGroup->visibleTrack = textTrack;
if (!currentGroup->defaultTrack && textTrack->isDefault())
currentGroup->defaultTrack = textTrack;
@@ -3617,6 +4119,7 @@ void HTMLMediaElement::configureTextTracks()
if (otherTracks.tracks.size())
configureTextTrackGroup(otherTracks);
+ updateCaptionContainer();
configureTextTrackDisplay();
if (hasMediaControls())
mediaControls()->closedCaptionTracksChanged();
@@ -3640,25 +4143,25 @@ bool HTMLMediaElement::havePotentialSourceChild()
URL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid)
{
+ UNUSED_PARAM(keySystem);
#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 URL();
}
URL mediaURL;
- HTMLSourceElement* source = 0;
+ HTMLSourceElement* source = nullptr;
String type;
- String system;
bool lookingForStartNode = m_nextChildNodeToConsider;
bool canUseSourceElement = false;
bool okToLoadSourceURL;
@@ -3677,50 +4180,51 @@ URL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* ke
if (node.parentNode() != this)
continue;
- source = toHTMLSourceElement(&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;
+ goto CheckAgain;
- if (source->fastHasAttribute(mediaAttr)) {
- MediaQueryEvaluator screenEval("screen", document().frame(), renderer() ? &renderer()->style() : nullptr);
- RefPtr<MediaQuerySet> media = MediaQuerySet::createAllowingDescriptionSyntax(source->media());
+ if (source->hasAttributeWithoutSynchronization(mediaAttr)) {
+ auto 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;
+ if (media) {
+ auto* renderer = this->renderer();
+ if (!MediaQueryEvaluator { "screen", document(), renderer ? &renderer->style() : nullptr }.evaluate(*media))
+ goto CheckAgain;
+ }
}
type = source->type();
- // FIXME(82965): Add support for keySystem in <source> and set system from source.
if (type.isEmpty() && mediaURL.protocolIsData())
type = mimeTypeFromDataURL(mediaURL);
- if (!type.isEmpty() || !system.isEmpty()) {
+ if (!type.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'", this, type.utf8().data());
#endif
MediaEngineSupportParameters parameters;
ContentType contentType(type);
- parameters.type = contentType.type().lower();
+ 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;
+ goto CheckAgain;
}
// Is it safe to load this url?
@@ -3728,18 +4232,18 @@ URL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* ke
// 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");
- source = 0;
- goto check_again;
+ LOG(Media, "HTMLMediaElement::selectNextSourceChild(%p) - 'beforeload' removed current element", this);
+ source = nullptr;
+ goto CheckAgain;
}
if (!okToLoadSourceURL)
- goto check_again;
+ goto CheckAgain;
// Making it this far means the <source> looks reasonable.
canUseSourceElement = true;
-check_again:
+CheckAgain:
if (!canUseSourceElement && actionIfInvalid == Complain && source)
source->scheduleErrorEvent();
}
@@ -3747,48 +4251,46 @@ check_again:
if (canUseSourceElement) {
if (contentType)
*contentType = ContentType(type);
- if (keySystem)
- *keySystem = system;
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 : 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)) {
URL url = source->getNonEmptyURLAttribute(srcAttr);
- LOG(Media, "HTMLMediaElement::sourceWasAdded - 'src' is %s", urlForLoggingMedia(url).utf8().data());
+ LOG(Media, "HTMLMediaElement::sourceWasAdded(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data());
}
#endif
// We should only consider a <source> element when there is not src attribute at all.
- if (fastHasAttribute(srcAttr))
+ if (hasAttributeWithoutSynchronization(srcAttr))
return;
// 4.8.8 - If a source element is inserted as a child of a media element that has no src
// attribute and whose networkState has the value NETWORK_EMPTY, the user agent must invoke
// the media element's resource selection algorithm.
if (networkState() == HTMLMediaElement::NETWORK_EMPTY) {
- scheduleDelayedAction(LoadMediaResource);
m_nextChildNodeToConsider = source;
+ selectMediaResource();
return;
}
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;
}
@@ -3813,12 +4315,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)) {
URL url = source->getNonEmptyURLAttribute(srcAttr);
- LOG(Media, "HTMLMediaElement::sourceWasRemoved - 'src' is %s", urlForLoggingMedia(url).utf8().data());
+ LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p) - 'src' is %s", this, urlForLoggingMedia(url).utf8().data());
}
#endif
@@ -3828,28 +4330,28 @@ 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::sharedFeatures().webkitVideoTrackEnabled())
- updateActiveTextTrackCues(currentTime());
+ updateActiveTextTrackCues(currentMediaTime());
#endif
beginProcessingMediaPlayerCallback();
invalidateCachedTime();
+ bool wasSeeking = seeking();
// 4.8.10.9 step 14 & 15. Needed if no ReadyState change is associated with the seek.
if (m_seeking && m_readyState >= HAVE_CURRENT_DATA && !m_player->seeking())
@@ -3858,32 +4360,37 @@ 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);
+
+ MediaTime now = currentMediaTime();
+ MediaTime dur = durationMediaTime();
+ double playbackRate = requestedPlaybackRate();
- double now = currentTime();
- double dur = duration();
-
// When the current playback position reaches the end of the media resource then the user agent must follow these steps:
- if (!std::isnan(dur) && dur) {
+ 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 && m_playbackRate > 0) {
+ if (loop() && !m_mediaController && playbackRate > 0) {
m_sentEndEvent = false;
// 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)
- seek(0);
- } else if ((now <= 0 && m_playbackRate < 0) || (now >= dur && m_playbackRate > 0)) {
+ 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 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) {
m_sentEndEvent = true;
scheduleEvent(eventNames().endedEvent);
+ if (!wasSeeking)
+ addBehaviorRestrictionsOnEndIfNecessary();
}
// If the media element has a current media controller, then report the controller state
// for the media element's current media controller.
@@ -3891,21 +4398,77 @@ void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*)
} else
m_sentEndEvent = false;
} else {
-#if PLATFORM(IOS)
- // The controller changes movie time directly instead of calling through here so we need
- // to post timeupdate events in response to time changes.
- scheduleTimeupdateEvent(false);
+#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);
+ if (!wasSeeking)
+ addBehaviorRestrictionsOnEndIfNecessary();
+ m_paused = true;
+ setPlaying(false);
+ }
+ } else
#endif
m_sentEndEvent = false;
}
- updatePlayState();
+ updatePlayState(UpdateState::Asynchronously);
endProcessingMediaPlayerCallback();
}
+void HTMLMediaElement::addBehaviorRestrictionsOnEndIfNecessary()
+{
+ if (isFullscreen())
+ return;
+
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager);
+ m_playbackControlsManagerBehaviorRestrictionsTimer.stop();
+ m_playbackControlsManagerBehaviorRestrictionsTimer.startOneShot(HideMediaControlsAfterEndedDelay);
+}
+
+void HTMLMediaElement::handleSeekToPlaybackPosition(double position)
+{
+#if PLATFORM(MAC)
+ // FIXME: This should ideally use faskSeek, but this causes MediaRemote's playhead to flicker upon release.
+ // Please see <rdar://problem/28457219> for more details.
+ seek(MediaTime::createWithDouble(position));
+ m_seekToPlaybackPositionEndedTimer.stop();
+ m_seekToPlaybackPositionEndedTimer.startOneShot(0.5);
+
+ if (!m_isScrubbingRemotely) {
+ m_isScrubbingRemotely = true;
+ if (!paused())
+ pauseInternal();
+ }
+#else
+ fastSeek(position);
+#endif
+}
+
+void HTMLMediaElement::seekToPlaybackPositionEndedTimerFired()
+{
+#if PLATFORM(MAC)
+ if (!m_isScrubbingRemotely)
+ return;
+
+ PlatformMediaSessionManager::sharedManager().sessionDidEndRemoteScrubbing(*m_mediaSession);
+ m_isScrubbingRemotely = false;
+ m_seekToPlaybackPositionEndedTimer.stop();
+#endif
+}
+
void HTMLMediaElement::mediaPlayerVolumeChanged(MediaPlayer*)
{
- LOG(Media, "HTMLMediaElement::mediaPlayerVolumeChanged");
+ LOG(Media, "HTMLMediaElement::mediaPlayerVolumeChanged(%p)", this);
beginProcessingMediaPlayerCallback();
if (m_player) {
@@ -3921,7 +4484,7 @@ void HTMLMediaElement::mediaPlayerVolumeChanged(MediaPlayer*)
void HTMLMediaElement::mediaPlayerMuteChanged(MediaPlayer*)
{
- LOG(Media, "HTMLMediaElement::mediaPlayerMuteChanged");
+ LOG(Media, "HTMLMediaElement::mediaPlayerMuteChanged(%p)", this);
beginProcessingMediaPlayerCallback();
if (m_player)
@@ -3931,30 +4494,31 @@ 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);
+ 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();
@@ -3965,7 +4529,7 @@ void HTMLMediaElement::mediaPlayerRateChanged(MediaPlayer*)
void HTMLMediaElement::mediaPlayerPlaybackStateChanged(MediaPlayer*)
{
- LOG(Media, "HTMLMediaElement::mediaPlayerPlaybackStateChanged");
+ LOG(Media, "HTMLMediaElement::mediaPlayerPlaybackStateChanged(%p)", this);
if (!m_player || m_pausedInternal)
return;
@@ -3980,18 +4544,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 (document().isMediaDocument())
- toMediaDocument(document()).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);
@@ -4002,77 +4566,115 @@ void HTMLMediaElement::mediaPlayerRepaint(MediaPlayer*)
{
beginProcessingMediaPlayerCallback();
updateDisplayState();
- if (renderer())
- renderer()->repaint();
+ if (auto* renderer = this->renderer())
+ renderer->repaint();
endProcessingMediaPlayerCallback();
}
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 (renderer())
- renderer()->updateFromElement();
+ if (m_readyState > HAVE_NOTHING)
+ scheduleResizeEventIfSizeChanged();
+ updateRenderer();
endProcessingMediaPlayerCallback();
}
-#if USE(ACCELERATED_COMPOSITING)
bool HTMLMediaElement::mediaPlayerRenderingCanBeAccelerated(MediaPlayer*)
{
- if (renderer() && renderer()->isVideo())
- return renderer()->view().compositor().canAccelerateVideoRendering(toRenderVideo(*renderer()));
- return false;
+ auto* renderer = this->renderer();
+ return is<RenderVideo>(renderer)
+ && downcast<RenderVideo>(*renderer).view().compositor().canAccelerateVideoRendering(downcast<RenderVideo>(*renderer));
}
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);
+ invalidateStyleAndLayerComposition();
+}
+
+bool HTMLMediaElement::mediaPlayerAcceleratedCompositingEnabled()
+{
+ return document().settings().acceleratedCompositingEnabled();
}
-#endif
#if PLATFORM(WIN) && USE(AVFOUNDATION)
+
GraphicsDeviceAdapter* HTMLMediaElement::mediaPlayerGraphicsDeviceAdapter(const MediaPlayer*) const
{
- if (!document().page())
- return 0;
-
- return document().page()->chrome().client().graphicsDeviceAdapter();
+ auto* page = document().page();
+ if (!page)
+ return nullptr;
+ return page->chrome().client().graphicsDeviceAdapter();
}
+
#endif
-void HTMLMediaElement::mediaPlayerEngineUpdated(MediaPlayer*)
+void HTMLMediaElement::mediaEngineWasUpdated()
{
- LOG(Media, "HTMLMediaElement::mediaPlayerEngineUpdated");
+ LOG(Media, "HTMLMediaElement::mediaEngineWasUpdated(%p)", this);
beginProcessingMediaPlayerCallback();
- if (renderer())
- renderer()->updateFromElement();
+ updateRenderer();
endProcessingMediaPlayerCallback();
+ 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(UpdateState::Asynchronously);
+#endif
+}
+
+void HTMLMediaElement::mediaPlayerEngineUpdated(MediaPlayer*)
+{
+ LOG(Media, "HTMLMediaElement::mediaPlayerEngineUpdated(%p)", this);
+
#if ENABLE(MEDIA_SOURCE)
m_droppedVideoFrames = 0;
#endif
+
+ m_havePreparedToPlay = false;
+
+ scheduleDelayedAction(MediaEngineUpdated);
}
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();
@@ -4081,30 +4683,56 @@ void HTMLMediaElement::mediaPlayerCharacteristicChanged(MediaPlayer*)
markCaptionAndSubtitleTracksAsUnconfigured(AfterDelay);
#endif
+ if (potentiallyPlaying() && displayMode() == PosterWaitingForVideo) {
+ setDisplayMode(Video);
+ mediaPlayerRenderingModeChanged(m_player.get());
+ }
+
if (hasMediaControls())
mediaControls()->reset();
- if (renderer())
- renderer()->updateFromElement();
+ updateRenderer();
+
+ if (!paused() && !m_mediaSession->playbackPermitted(*this))
+ pauseInternal();
+
+#if ENABLE(MEDIA_SESSION)
+ document().updateIsPlayingMedia(m_elementID);
+#else
+ document().updateIsPlayingMedia();
+#endif
+
+ m_hasEverHadAudio |= hasAudio();
+ m_hasEverHadVideo |= hasVideo();
+
endProcessingMediaPlayerCallback();
}
-PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const
+Ref<TimeRanges> HTMLMediaElement::buffered() const
{
if (!m_player)
return TimeRanges::create();
#if ENABLE(MEDIA_SOURCE)
if (m_mediaSource)
- return m_mediaSource->buffered();
+ return TimeRanges::create(*m_mediaSource->buffered());
#endif
- return m_player->buffered();
+ return TimeRanges::create(*m_player->buffered());
+}
+
+double HTMLMediaElement::maxBufferedTime() const
+{
+ auto bufferedRanges = buffered();
+ unsigned numRanges = bufferedRanges->length();
+ if (!numRanges)
+ return 0;
+ return bufferedRanges.get().ranges().end(numRanges - 1).toDouble();
}
-PassRefPtr<TimeRanges> HTMLMediaElement::played()
+Ref<TimeRanges> HTMLMediaElement::played()
{
if (m_playing) {
- double time = currentTime();
+ MediaTime time = currentMediaTime();
if (time > m_lastSeekTime)
addPlayedRange(m_lastSeekTime, time);
}
@@ -4115,9 +4743,17 @@ PassRefPtr<TimeRanges> HTMLMediaElement::played()
return m_playedTimeRanges->copy();
}
-PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const
+Ref<TimeRanges> HTMLMediaElement::seekable() const
{
- return m_player ? m_player->seekable() : TimeRanges::create();
+#if ENABLE(MEDIA_SOURCE)
+ if (m_mediaSource)
+ return m_mediaSource->seekable();
+#endif
+
+ if (m_player)
+ return TimeRanges::create(*m_player->seekable());
+
+ return TimeRanges::create();
}
bool HTMLMediaElement::potentiallyPlaying() const
@@ -4153,8 +4789,8 @@ bool HTMLMediaElement::couldPlayIfEnoughData() const
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
@@ -4167,14 +4803,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;
}
@@ -4192,24 +4828,26 @@ bool HTMLMediaElement::stoppedDueToErrors() const
bool HTMLMediaElement::pausedForUserInteraction() const
{
- if (m_mediaSession->state() == MediaSession::Interrupted)
+ 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 (!m_player)
+ return;
#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();
@@ -4218,32 +4856,44 @@ void HTMLMediaElement::updateVolume()
scheduleEvent(eventNames().volumechangeEvent);
}
#else
- if (!m_player)
- return;
-
// Avoid recursion when the player reports volume changes.
if (!processingMediaPlayerCallback()) {
Page* page = document().page();
double volumeMultiplier = page ? page->mediaVolume() : 1;
- bool shouldMute = muted();
+ bool shouldMute = effectiveMuted();
if (m_mediaController) {
volumeMultiplier *= m_mediaController->volume();
- shouldMute = m_mediaController->muted();
+ shouldMute = m_mediaController->muted() || (page && page->isAudioMuted());
}
+#if ENABLE(MEDIA_SESSION)
+ if (m_shouldDuck)
+ volumeMultiplier *= 0.25;
+#endif
+
m_player->setMuted(shouldMute);
- if (m_volumeInitialized)
- m_player->setVolume(m_volume * volumeMultiplier);
+ m_player->setVolume(m_volume * volumeMultiplier);
}
+#if ENABLE(MEDIA_SESSION)
+ document().updateIsPlayingMedia(m_elementID);
+#else
+ document().updateIsPlayingMedia();
+#endif
+
if (hasMediaControls())
mediaControls()->changedVolume();
#endif
}
-void HTMLMediaElement::updatePlayState()
+void HTMLMediaElement::updatePlayState(UpdateState updateState)
{
+ if (updateState == UpdateState::Asynchronously) {
+ scheduleDelayedAction(UpdatePlayState);
+ return;
+ }
+
if (!m_player)
return;
@@ -4254,59 +4904,56 @@ void HTMLMediaElement::updatePlayState()
m_playbackProgressTimer.stop();
if (hasMediaControls())
mediaControls()->playbackStopped();
- m_activityToken = nullptr;
return;
}
bool shouldBePlaying = potentiallyPlaying();
bool playerPaused = m_player->paused();
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- if (shouldUseVideoPluginProxy()) {
- if (shouldBePlaying && !m_requestingPlay && !m_player->readyForPlayback())
- shouldBePlaying = false;
- else if (!shouldBePlaying && m_requestingPlay && m_player->readyForPlayback())
- shouldBePlaying = true;
- }
-#endif
-
- 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) {
+ scheduleUpdatePlaybackControlsManager();
+
setDisplayMode(Video);
invalidateCachedTime();
if (playerPaused) {
m_mediaSession->clientWillBeginPlayback();
- if (m_mediaSession->requiresFullscreenForVideoPlayback(*this))
+ 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(muted());
+ m_player->setRate(requestedPlaybackRate());
+ m_player->setMuted(effectiveMuted());
+
+ if (m_firstTimePlaying) {
+ // Log that a media element was played.
+ if (auto* page = document().page())
+ page->diagnosticLoggingClient().logDiagnosticMessage(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();
startPlaybackProgressTimer();
- m_playing = true;
+ setPlaying(true);
+ } else {
+ scheduleUpdatePlaybackControlsManager();
- } else { // Should not be playing right now
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);
@@ -4315,23 +4962,40 @@ void HTMLMediaElement::updatePlayState()
if (hasMediaControls())
mediaControls()->playbackStopped();
- m_activityToken = nullptr;
}
-#if PLATFORM(IOS)
- m_requestingPlay = false;
-#endif
-
updateMediaController();
+ updateRenderer();
- if (renderer())
- renderer()->updateFromElement();
+ m_hasEverHadAudio |= hasAudio();
+ m_hasEverHadVideo |= hasVideo();
+}
+
+void HTMLMediaElement::setPlaying(bool playing)
+{
+ if (playing && m_mediaSession)
+ m_mediaSession->removeBehaviorRestriction(MediaElementSession::RequirePlaybackToControlControlsManager);
+
+ 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(UpdateState::Asynchronously);
+#endif
}
void HTMLMediaElement::setPausedInternal(bool b)
{
m_pausedInternal = b;
- updatePlayState();
+ updatePlayState(UpdateState::Asynchronously);
}
void HTMLMediaElement::stopPeriodicTimers()
@@ -4342,7 +5006,7 @@ 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)
@@ -4356,7 +5020,7 @@ void HTMLMediaElement::userCancelledLoad()
// 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);
@@ -4365,7 +5029,7 @@ void HTMLMediaElement::userCancelledLoad()
scheduleEvent(eventNames().abortEvent);
#if ENABLE(MEDIA_SOURCE)
- closeMediaSource();
+ detachMediaSource();
#endif
// 4 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the
@@ -4383,41 +5047,64 @@ 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::sharedFeatures().webkitVideoTrackEnabled())
- updateActiveTextTrackCues(0);
+ 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();
+#if ENABLE(MEDIA_STREAM)
+ if (!m_settingMediaStreamSrcObject)
+ m_mediaStreamSrcObject = nullptr;
#endif
#if ENABLE(MEDIA_SOURCE)
- closeMediaSource();
+ detachMediaSource();
#endif
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- if (!shouldUseVideoPluginProxy())
+ m_blob = nullptr;
+
+#if ENABLE(VIDEO_TRACK)
+ forgetResourceSpecificTracks();
+#endif
+
+#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();
+ }
+
+ if (m_isPlayingToWirelessTarget) {
+ m_isPlayingToWirelessTarget = false;
+ scheduleEvent(eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent);
+ }
#endif
- m_player.clear();
+
+ if (m_isWaitingUntilMediaCanStart) {
+ m_isWaitingUntilMediaCanStart = false;
+ document().removeMediaCanStartListener(this);
+ }
+
+ if (m_player) {
+ m_player->invalidate();
+ m_player = nullptr;
+ }
+ updatePlaybackControlsManager();
stopPeriodicTimers();
- m_loadTimer.stop();
+ m_pendingActionTimer.stop();
clearFlags(m_pendingActionFlags, flags);
m_loadState = WaitingForSource;
@@ -4427,54 +5114,97 @@ void HTMLMediaElement::clearMediaPlayer(int flags)
configureTextTrackDisplay();
#endif
+ m_mediaSession->clientCharacteristicsChanged();
+ m_mediaSession->canProduceAudioChanged();
+
+ m_resourceSelectionTaskQueue.cancelAllTasks();
+
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();
-
+
+ setPreparedToReturnVideoLayerToInline(true);
+
+ updatePlaybackControlsManager();
m_inActiveDocument = false;
- userCancelledLoad();
-
+
// Stop the playback without generating events
- m_playing = false;
+ setPlaying(false);
setPausedInternal(true);
-
- if (renderer())
- renderer()->updateFromElement();
-
+ m_mediaSession->clientWillPausePlayback();
+
+ userCancelledLoad();
+
+ updateRenderer();
+
stopPeriodicTimers();
- cancelPendingEventsAndCallbacks();
+
+ updateSleepDisabling();
+}
+
+void HTMLMediaElement::contextDestroyed()
+{
+ m_seekTaskQueue.close();
+ m_resizeTaskQueue.close();
+ m_shadowDOMTaskQueue.close();
+ m_promiseTaskQueue.close();
+ m_pauseAfterDetachedTaskQueue.close();
+ m_updatePlaybackControlsManagerQueue.close();
+
+ m_pendingPlayPromises.clear();
+
+ ActiveDOMObject::contextDestroyed();
+}
+
+void HTMLMediaElement::stop()
+{
+ LOG(Media, "HTMLMediaElement::stop(%p)", this);
+
+ Ref<HTMLMediaElement> protectedThis(*this);
+ stopWithoutDestroyingMediaPlayer();
m_asyncEventQueue.close();
+ m_promiseTaskQueue.close();
+ m_updatePlaybackControlsManagerQueue.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);
- updateSleepDisabling();
+ m_mediaSession->stopSession();
}
void HTMLMediaElement::suspend(ReasonForSuspension why)
{
- LOG(Media, "HTMLMediaElement::suspend");
+ LOG(Media, "HTMLMediaElement::suspend(%p)", this);
+ Ref<HTMLMediaElement> protectedThis(*this);
switch (why)
{
- case DocumentWillBecomeInactive:
- stop();
- m_mediaSession->addBehaviorRestriction(HTMLMediaSession::RequirePageConsentToResumeMedia);
+ case PageCache:
+ stopWithoutDestroyingMediaPlayer();
+ m_asyncEventQueue.suspend();
+ setShouldBufferData(false);
+ m_mediaSession->addBehaviorRestriction(MediaElementSession::RequirePageConsentToResumeMedia);
break;
- case DocumentWillBePaused:
case JavaScriptDebuggerPaused:
case PageWillBeSuspended:
case WillDeferLoading:
@@ -4485,84 +5215,56 @@ void HTMLMediaElement::suspend(ReasonForSuspension why)
void HTMLMediaElement::resume()
{
- LOG(Media, "HTMLMediaElement::resume");
+ LOG(Media, "HTMLMediaElement::resume(%p)", this);
m_inActiveDocument = true;
+ m_asyncEventQueue.resume();
+
+ setShouldBufferData(true);
+
if (!m_mediaSession->pageAllowsPlaybackAfterResuming(*this))
document().addMediaCanStartListener(this);
else
setPausedInternal(false);
- m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequirePageConsentToResumeMedia);
+ 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.
// m_error is only left at MEDIA_ERR_ABORTED when the document becomes inactive (it is set to
// MEDIA_ERR_ABORTED while the abortEvent is being sent, but cleared immediately afterwards).
// This behavior is not specified but it seems like a sensible thing to do.
-#if PLATFORM(IOS)
- // FIXME: <rdar://problem/9751303> Merge: Does r1033092 need to be refixed in ToT?
-#endif
// As it is not safe to immedately start loading now, let's schedule a load.
- scheduleDelayedAction(LoadMediaResource);
+ prepareForLoad();
}
- if (renderer())
- renderer()->updateFromElement();
+ updateRenderer();
}
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();
}
-#if ENABLE(PAGE_VISIBILITY_API)
void HTMLMediaElement::visibilityStateChanged()
{
- LOG(Media, "HTMLMediaElement::visibilityStateChanged");
- m_isDisplaySleepDisablingSuspended = document().hidden();
+ m_elementIsHidden = document().hidden();
+ LOG(Media, "HTMLMediaElement::visibilityStateChanged(%p) - visible = %s", this, boolString(!m_elementIsHidden));
updateSleepDisabling();
+ m_mediaSession->visibilityChanged();
}
-#endif
-
-void HTMLMediaElement::defaultEventHandler(Event* event)
-{
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- if (shouldUseVideoPluginProxy()) {
- auto renderer = this->renderer();
- if (!renderer || !renderer->isWidget())
- return;
-
- if (Widget* widget = toRenderWidget(renderer)->widget())
- widget->handleEvent(event);
- return;
- }
-#endif
- HTMLElement::defaultEventHandler(event);
-}
-
-#if !PLATFORM(IOS)
-bool HTMLMediaElement::willRespondToMouseClickEvents()
-{
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- if (shouldUseVideoPluginProxy())
- return true;
-#endif
- return HTMLElement::willRespondToMouseClickEvents();
-}
-#endif // !PLATFORM(IOS)
#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)
@@ -4570,268 +5272,154 @@ void HTMLMediaElement::setTextTrackRepresentation(TextTrackRepresentation* repre
if (m_player)
m_player->setTextTrackRepresentation(representation);
}
-#endif // ENABLE(VIDEO_TRACK)
-
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
-void HTMLMediaElement::ensureMediaPlayer()
+void HTMLMediaElement::syncTextTrackBounds()
{
- if (!m_player)
- createMediaPlayer();
+ if (m_player)
+ m_player->syncTextTrackBounds();
}
+#endif // ENABLE(VIDEO_TRACK)
-void HTMLMediaElement::deliverNotification(MediaPlayerProxyNotificationType notification)
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+void HTMLMediaElement::webkitShowPlaybackTargetPicker()
{
- if (notification == MediaPlayerNotificationPlayPauseButtonPressed) {
-#if PLATFORM(IOS)
+ LOG(Media, "HTMLMediaElement::webkitShowPlaybackTargetPicker(%p)", this);
+ if (ScriptController::processingUserGestureForMedia())
removeBehaviorsRestrictionsAfterFirstUserGesture();
-#endif
- togglePlayState();
- return;
- }
-#if PLATFORM(IOS)
- else if (notification == MediaPlayerRequestBeginPlayback) {
- if (m_paused)
- playInternal();
- } else if (notification == MediaPlayerRequestPausePlayback) {
- if (!m_paused)
- pauseInternal();
- } else if (notification == MediaPlayerNotificationLoseFocus) {
- if (!m_paused)
- pauseInternal();
- } else if (notification == MediaPlayerNotificationDidPlayToTheEnd) {
- if (!m_paused && !loop())
- pauseInternal();
- } else if (notification == MediaPlayerNotificationMediaValidated || notification == MediaPlayerNotificationReadyForInspection) {
- // The media player sometimes reports an apparently spurious error just as we request playback, and then follows almost
- // immediately with ReadyForInspection and/or MediaValidated. The spec doesn't deal with a "fatal" error followed
- // by ressurection, so if we have set an error clear it now.
- m_error = 0;
- } else if (notification == MediaPlayerNotificationEnteredFullscreen) {
- scheduleEvent(eventNames().webkitbeginfullscreenEvent);
- m_isFullscreen = true;
- } else if (notification == MediaPlayerNotificationExitedFullscreen) {
- scheduleEvent(eventNames().webkitendfullscreenEvent);
- m_isFullscreen = false;
- }
-#endif
-
- if (m_player)
- m_player->deliverNotification(notification);
-
-#if PLATFORM(IOS)
- if (notification == MediaPlayerNotificationMediaValidated) {
- // If the element is supposed to autoplay and we allow it, tell the media engine to begin loading
- // data now. Playback will begin automatically when enough data has loaded.
- if (m_autoplaying && m_paused && autoplay())
- prepareToPlay();
- } else if (notification == MediaPlayerNotificationEnteredFullscreen)
- didBecomeFullscreenElement();
- else if (notification == MediaPlayerNotificationExitedFullscreen)
- willStopBeingFullscreenElement();
-#endif
+ m_mediaSession->showPlaybackTargetPicker(*this);
}
-void HTMLMediaElement::setMediaPlayerProxy(WebMediaPlayerProxy* proxy)
+bool HTMLMediaElement::webkitCurrentPlaybackTargetIsWireless() const
{
- ensureMediaPlayer();
- m_player->setMediaPlayerProxy(proxy);
+ return m_isPlayingToWirelessTarget;
}
-void HTMLMediaElement::getPluginProxyParams(URL& url, Vector<String>& names, Vector<String>& values)
+void HTMLMediaElement::wirelessRoutesAvailableDidChange()
{
- Ref<HTMLMediaElement> protect(*this); // selectNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations.
-
- Frame* frame = document().frame();
-
- if (isVideo()) {
- HTMLVideoElement* video = toHTMLVideoElement(this);
- URL posterURL = video->posterImageURL();
- if (!posterURL.isEmpty() && frame && frame->loader().willLoadMediaElementURL(posterURL)) {
- names.append(ASCIILiteral("_media_element_poster_"));
- values.append(posterURL.string());
- }
- }
-
- if (controls()) {
- names.append(ASCIILiteral("_media_element_controls_"));
- values.append(ASCIILiteral("true"));
- }
-
-#if PLATFORM(IOS)
- // Don't pass the URL to the plug-in as part of the initialization arguments, we always pass the URL
- // in loadResource and calling selectNextSourceChild here can mess up the processing of <source>
- // elements later.
- UNUSED_PARAM(url);
-#else
- url = getNonEmptyURLAttribute(srcAttr);
- if (!isSafeToLoadURL(url, Complain))
- url = selectNextSourceChild(0, 0, DoNothing);
-
- m_currentSrc = url;
- if (url.isValid() && frame && frame->loader().willLoadMediaElementURL(url)) {
- names.append(ASCIILiteral("_media_element_src_"));
- values.append(m_currentSrc.string());
- }
-#endif
-
-#if PLATFORM(IOS)
- Settings* settings = document().settings();
- if (settings && settings->mediaPlaybackAllowsInline() && (applicationIsDumpRenderTree() || fastHasAttribute(webkit_playsinlineAttr))) {
- names.append(ASCIILiteral("_media_element_allow_inline_"));
- values.append(ASCIILiteral("true"));
- }
-
- String airplay = fastGetAttribute(webkitairplayAttr);
- if (equalIgnoringCase(airplay, "allow") || equalIgnoringCase(airplay, "deny")) {
- names.append(ASCIILiteral("_media_element_airplay_"));
- values.append(airplay.lower());
- }
-
- if (fastHasAttribute(data_youtube_idAttr)) {
- names.append(ASCIILiteral("_media_element_youtube_video_id_"));
- values.append(fastGetAttribute(data_youtube_idAttr));
- }
+ enqueuePlaybackTargetAvailabilityChangedEvent();
+}
- String interfaceName = settings ? settings->networkInterfaceName() : String();
- if (!interfaceName.isEmpty()) {
- names.append(ASCIILiteral("_media_element_network_interface_name"));
- values.append(interfaceName);
- }
+void HTMLMediaElement::mediaPlayerCurrentPlaybackTargetIsWirelessChanged(MediaPlayer*)
+{
+ m_isPlayingToWirelessTarget = m_player && m_player->isCurrentPlaybackTargetWireless();
- if (fastHasAttribute(titleAttr)) {
- names.append(titleAttr.toString());
- values.append(fastGetAttribute(titleAttr));
- }
-#endif
+ LOG(Media, "HTMLMediaElement::mediaPlayerCurrentPlaybackTargetIsWirelessChanged(%p) - webkitCurrentPlaybackTargetIsWireless = %s", this, boolString(m_isPlayingToWirelessTarget));
+ ASSERT(m_player);
+ configureMediaControls();
+ scheduleEvent(eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent);
+ m_mediaSession->isPlayingToWirelessPlaybackTargetChanged(m_isPlayingToWirelessTarget);
+ m_mediaSession->canProduceAudioChanged();
+ updateMediaState(UpdateState::Asynchronously);
+}
-#if ENABLE(IOS_AIRPLAY)
- if (isVideo() && fastHasAttribute(webkitwirelessvideoplaybackdisabledAttr)) {
- names.append(ASCIILiteral("_media_element_wireless_video_playback_disabled"));
- values.append(ASCIILiteral("true"));
+bool HTMLMediaElement::dispatchEvent(Event& event)
+{
+ if (event.type() == eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent) {
+ m_failedToPlayToWirelessTarget = false;
+ scheduleDelayedAction(CheckPlaybackTargetCompatablity);
}
-#endif
+ return HTMLElement::dispatchEvent(event);
}
-void HTMLMediaElement::createMediaPlayerProxy()
+bool HTMLMediaElement::addEventListener(const AtomicString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options)
{
- ensureMediaPlayer();
-
- if (m_proxyWidget || (inDocument() && !m_needWidgetUpdate))
- return;
-
- Frame* frame = document().frame();
- if (!frame)
- return;
+ if (eventType != eventNames().webkitplaybacktargetavailabilitychangedEvent)
+ return Node::addEventListener(eventType, WTFMove(listener), options);
- LOG(Media, "HTMLMediaElement::createMediaPlayerProxy");
+ bool isFirstAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent);
+ if (!Node::addEventListener(eventType, WTFMove(listener), options))
+ return false;
- URL url;
- Vector<String> paramNames;
- Vector<String> paramValues;
+ if (isFirstAvailabilityChangedListener) {
+ m_hasPlaybackTargetAvailabilityListeners = true;
+ m_mediaSession->setHasPlaybackTargetAvailabilityListeners(*this, true);
+ }
- getPluginProxyParams(url, paramNames, paramValues);
+ LOG(Media, "HTMLMediaElement::addEventListener(%p) - 'webkitplaybacktargetavailabilitychanged'", this);
- // 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;
+ enqueuePlaybackTargetAvailabilityChangedEvent(); // Ensure the event listener gets at least one event.
+ return true;
}
-void HTMLMediaElement::updateWidget(PluginCreationOption)
+bool HTMLMediaElement::removeEventListener(const AtomicString& eventType, EventListener& listener, const ListenerOptions& options)
{
- setNeedWidgetUpdate(false);
+ if (eventType != eventNames().webkitplaybacktargetavailabilitychangedEvent)
+ return Node::removeEventListener(eventType, listener, options);
- // FIXME: What if document().frame() is 0?
+ if (!Node::removeEventListener(eventType, listener, options))
+ return false;
- URL url;
- Vector<String> paramNames;
- Vector<String> paramValues;
+ 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(UpdateState::Asynchronously);
+ }
- getPluginProxyParams(url, paramNames, paramValues);
- SubframeLoader& loader = document().frame()->loader().subframeLoader();
- loader.loadMediaPlayerProxyPlugin(*this, url, paramNames, paramValues);
+ return true;
}
-#endif // ENABLE(PLUGIN_PROXY_FOR_VIDEO)
-
-#if ENABLE(IOS_AIRPLAY)
-void HTMLMediaElement::webkitShowPlaybackTargetPicker()
+void HTMLMediaElement::enqueuePlaybackTargetAvailabilityChangedEvent()
{
- if (!document().page())
- return;
-
- if (!m_player)
- return;
-
- if (!m_mediaSession->showingPlaybackTargetPickerPermitted(*this))
- return;
-
- if (document().settings()->mediaPlaybackAllowsAirPlay())
- return;
-
- m_player->showPlaybackTargetPicker();
+ bool hasTargets = m_mediaSession->hasWirelessPlaybackTargets(*this);
+ LOG(Media, "HTMLMediaElement::enqueuePlaybackTargetAvailabilityChangedEvent(%p) - hasTargets = %s", this, boolString(hasTargets));
+ auto event = WebKitPlaybackTargetAvailabilityEvent::create(eventNames().webkitplaybacktargetavailabilitychangedEvent, hasTargets);
+ event->setTarget(this);
+ m_asyncEventQueue.enqueueEvent(WTFMove(event));
+ updateMediaState(UpdateState::Asynchronously);
}
-bool HTMLMediaElement::webkitCurrentPlaybackTargetIsWireless() const
+void HTMLMediaElement::setWirelessPlaybackTarget(Ref<MediaPlaybackTarget>&& device)
{
- return m_player && m_player->isCurrentPlaybackTargetWireless();
+ LOG(Media, "HTMLMediaElement::setWirelessPlaybackTarget(%p)", this);
+ if (m_player)
+ m_player->setWirelessPlaybackTarget(WTFMove(device));
}
-void HTMLMediaElement::mediaPlayerCurrentPlaybackTargetIsWirelessChanged(MediaPlayer*)
+bool HTMLMediaElement::canPlayToWirelessPlaybackTarget() const
{
- scheduleEvent(eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent);
+ bool canPlay = m_player && m_player->canPlayToWirelessPlaybackTarget();
+
+ LOG(Media, "HTMLMediaElement::canPlayToWirelessPlaybackTarget(%p) - returning %s", this, boolString(canPlay));
+
+ return canPlay;
}
-void HTMLMediaElement::mediaPlayerPlaybackTargetAvailabilityChanged(MediaPlayer*)
+bool HTMLMediaElement::isPlayingToWirelessPlaybackTarget() const
{
- enqueuePlaybackTargetAvailabilityChangedEvent();
+ return m_isPlayingToWirelessTarget;
}
-bool HTMLMediaElement::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> listener, bool useCapture)
+void HTMLMediaElement::setShouldPlayToPlaybackTarget(bool shouldPlay)
{
- if (eventType != eventNames().webkitplaybacktargetavailabilitychangedEvent)
- return Node::addEventListener(eventType, listener, useCapture);
+ LOG(Media, "HTMLMediaElement::setShouldPlayToPlaybackTarget(%p) - shouldPlay = %s", this, boolString(shouldPlay));
- bool isFirstAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent);
- if (!Node::addEventListener(eventType, listener, useCapture))
- return false;
- if (m_player && isFirstAvailabilityChangedListener)
- m_player->setHasPlaybackTargetAvailabilityListeners(true);
-
- enqueuePlaybackTargetAvailabilityChangedEvent(); // Ensure the event listener gets at least one event.
- return true;
+ if (m_player)
+ m_player->setShouldPlayToPlaybackTarget(shouldPlay);
}
+#else // ENABLE(WIRELESS_PLAYBACK_TARGET)
-bool HTMLMediaElement::removeEventListener(const AtomicString& eventType, EventListener* listener, bool useCapture)
+bool HTMLMediaElement::webkitCurrentPlaybackTargetIsWireless() const
{
- if (eventType != eventNames().webkitplaybacktargetavailabilitychangedEvent)
- return Node::removeEventListener(eventType, listener, useCapture);
+ return false;
+}
- if (!Node::removeEventListener(eventType, listener, useCapture))
- return false;
+#endif // ENABLE(WIRELESS_PLAYBACK_TARGET)
- bool didRemoveLastAvailabilityChangedListener = !hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent);
- if (m_player && didRemoveLastAvailabilityChangedListener)
- m_player->setHasPlaybackTargetAvailabilityListeners(false);
- return true;
+double HTMLMediaElement::minFastReverseRate() const
+{
+ return m_player ? m_player->minFastReverseRate() : 0;
}
-void HTMLMediaElement::enqueuePlaybackTargetAvailabilityChangedEvent()
+double HTMLMediaElement::maxFastForwardRate() const
{
- if (!m_player)
- return;
- bool isAirPlayAvailable = m_player->hasWirelessPlaybackTargets();
- RefPtr<Event> event = WebKitPlaybackTargetAvailabilityEvent::create(eventNames().webkitplaybacktargetavailabilitychangedEvent, isAirPlayAvailable);
- event->setTarget(this);
- m_asyncEventQueue.enqueueEvent(event.release());
+ return m_player ? m_player->maxFastForwardRate() : 0;
}
-#endif
-
+
bool HTMLMediaElement::isFullscreen() const
{
- if (m_isFullscreen)
+ if (m_videoFullscreenMode != VideoFullscreenModeNone)
return true;
#if ENABLE(FULLSCREEN_API)
@@ -4842,70 +5430,140 @@ bool HTMLMediaElement::isFullscreen() const
return false;
}
-void HTMLMediaElement::toggleFullscreenState()
+bool HTMLMediaElement::isStandardFullscreen() const
+{
+#if ENABLE(FULLSCREEN_API)
+ if (document().webkitIsFullScreen() && document().webkitCurrentFullScreenElement() == this)
+ return true;
+#endif
+
+ return m_videoFullscreenMode == VideoFullscreenModeStandard;
+}
+
+void HTMLMediaElement::toggleStandardFullscreenState()
{
- LOG(Media, "HTMLMediaElement::toggleFullscreenState - isFullscreen() is %s", boolString(isFullscreen()));
+ LOG(Media, "HTMLMediaElement::toggleStandardFullscreenState(%p) - isStandardFullscreen() is %s", this, boolString(isStandardFullscreen()));
- if (isFullscreen())
+ if (isStandardFullscreen())
exitFullscreen();
else
enterFullscreen();
}
-void HTMLMediaElement::enterFullscreen()
+void HTMLMediaElement::enterFullscreen(VideoFullscreenMode mode)
{
- LOG(Media, "HTMLMediaElement::enterFullscreen");
+ LOG(Media, "HTMLMediaElement::enterFullscreen(%p)", this);
+ ASSERT(mode != VideoFullscreenModeNone);
-#if ENABLE(FULLSCREEN_API)
- if (document().settings() && document().settings()->fullScreenEnabled()) {
- document().requestFullScreenForElement(this, 0, Document::ExemptIFrameAllowFullScreenRequirement);
+ if (m_videoFullscreenMode == mode)
return;
+
+ m_temporarilyAllowingInlinePlaybackAfterFullscreen = false;
+
+#if ENABLE(FULLSCREEN_API)
+ if (document().settings().fullScreenEnabled()) {
+ if (mode == VideoFullscreenModeStandard) {
+ document().requestFullScreenForElement(this, Document::ExemptIFrameAllowFullScreenRequirement);
+ return;
+ }
+
+ // If this media element is not going to standard fullscreen mode but there's
+ // an element that's currently in full screen in the document, exit full screen
+ // if it contains this media element.
+ if (Element* fullscreenElement = document().webkitCurrentFullScreenElement()) {
+ if (fullscreenElement->contains(this))
+ document().webkitCancelFullScreen();
+ }
}
#endif
- ASSERT(!m_isFullscreen);
- m_isFullscreen = true;
+
+ fullscreenModeChanged(mode);
+ configureMediaControls();
if (hasMediaControls())
mediaControls()->enteredFullscreen();
- if (document().page()) {
- if (document().page()->chrome().client().supportsFullscreenForNode(this)) {
- document().page()->chrome().client().enterFullscreenForNode(this);
+ 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);
}
-#if PLATFORM(IOS)
- else if (m_player)
- m_player->enterFullscreen();
-#endif
}
}
+void HTMLMediaElement::enterFullscreen()
+{
+ enterFullscreen(VideoFullscreenModeStandard);
+}
+
void HTMLMediaElement::exitFullscreen()
{
- LOG(Media, "HTMLMediaElement::exitFullscreen");
+ LOG(Media, "HTMLMediaElement::exitFullscreen(%p)", this);
#if ENABLE(FULLSCREEN_API)
- if (document().settings() && document().settings()->fullScreenEnabled()) {
- if (document().webkitIsFullScreen() && document().webkitCurrentFullScreenElement() == this)
+ if (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 ENABLE(MEDIA_CONTROLS_SCRIPT)
+ updateMediaControlsAfterPresentationModeChange();
+#endif
if (hasMediaControls())
mediaControls()->exitedFullscreen();
- if (document().page()) {
- if (document().page()->chrome().requiresFullscreenForVideoPlayback())
- pauseInternal();
- if (document().page()->chrome().client().supportsFullscreenForNode(this)) {
- document().page()->chrome().client().exitFullscreenForNode(this);
- scheduleEvent(eventNames().webkitendfullscreenEvent);
+ if (!document().page() || !is<HTMLVideoElement>(*this))
+ return;
+
+ if (!paused() && m_mediaSession->requiresFullscreenForVideoPlayback(*this)) {
+ if (!document().settings().allowsInlineMediaPlaybackAfterFullscreen() || isVideoTooSmallForInlinePlayback())
+ pauseInternal();
+ else {
+ // Allow inline playback, but set a flag so pausing and starting again (e.g. when scrubbing or looping) won't go back to fullscreen.
+ // Also set the controls attribute so the user will be able to control playback.
+ m_temporarilyAllowingInlinePlaybackAfterFullscreen = true;
+ setControls(true);
}
-#if PLATFORM(IOS)
- else if (m_player)
- m_player->exitFullscreen();
+ }
+
+#if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
+ if (document().activeDOMObjectsAreSuspended() || document().activeDOMObjectsAreStopped())
+ document().page()->chrome().client().exitVideoFullscreenToModeWithoutAnimation(downcast<HTMLVideoElement>(*this), VideoFullscreenModeNone);
+ else
#endif
+ if (document().page()->chrome().client().supportsVideoFullscreen(oldVideoFullscreenMode)) {
+ document().page()->chrome().client().exitVideoFullscreenForVideoElement(downcast<HTMLVideoElement>(*this));
+ scheduleEvent(eventNames().webkitendfullscreenEvent);
+ }
+}
+
+void HTMLMediaElement::willBecomeFullscreenElement()
+{
+#if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
+ HTMLMediaElementEnums::VideoFullscreenMode oldVideoFullscreenMode = m_videoFullscreenMode;
+#endif
+
+ fullscreenModeChanged(VideoFullscreenModeStandard);
+
+#if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
+ switch (oldVideoFullscreenMode) {
+ case VideoFullscreenModeNone:
+ case VideoFullscreenModeStandard:
+ // Don't need to do anything if we are not in any special fullscreen mode or it's already
+ // in standard fullscreen mode.
+ break;
+ case VideoFullscreenModePictureInPicture:
+ if (is<HTMLVideoElement>(*this))
+ downcast<HTMLVideoElement>(this)->exitToFullscreenModeWithoutAnimationIfPossible(oldVideoFullscreenMode, VideoFullscreenModeStandard);
+ break;
}
+#endif
+
+ Element::willBecomeFullscreenElement();
}
void HTMLMediaElement::didBecomeFullscreenElement()
@@ -4918,6 +5576,9 @@ void HTMLMediaElement::willStopBeingFullscreenElement()
{
if (hasMediaControls())
mediaControls()->exitedFullscreen();
+
+ if (fullscreenMode() == VideoFullscreenModeStandard)
+ fullscreenModeChanged(VideoFullscreenModeNone);
}
PlatformMedia HTMLMediaElement::platformMedia() const
@@ -4925,35 +5586,74 @@ PlatformMedia HTMLMediaElement::platformMedia() const
return m_player ? m_player->platformMedia() : NoPlatformMedia;
}
-#if USE(ACCELERATED_COMPOSITING)
PlatformLayer* HTMLMediaElement::platformLayer() const
{
-#if PLATFORM(IOS)
- if (m_platformLayerBorrowed)
- return nullptr;
-#endif
return m_player ? m_player->platformLayer() : nullptr;
}
-#if PLATFORM(IOS)
-PlatformLayer* HTMLMediaElement::borrowPlatformLayer()
+void HTMLMediaElement::setPreparedToReturnVideoLayerToInline(bool value)
{
- ASSERT(!m_platformLayerBorrowed);
- m_platformLayerBorrowed = true;
- if (renderer())
- renderer()->updateFromElement();
- return m_player ? m_player->platformLayer() : nullptr;
+ m_preparedForInline = value;
+ if (m_preparedForInline && m_preparedForInlineCompletionHandler) {
+ m_preparedForInlineCompletionHandler();
+ m_preparedForInlineCompletionHandler = nullptr;
+ }
}
-void HTMLMediaElement::returnPlatformLayer(PlatformLayer* platformLayer)
+void HTMLMediaElement::waitForPreparedForInlineThen(std::function<void()> completionHandler)
{
- ASSERT_UNUSED(platformLayer, platformLayer == (m_player ? m_player->platformLayer() : nullptr));
- ASSERT(m_platformLayerBorrowed);
- m_platformLayerBorrowed = false;
- if (renderer())
- renderer()->updateFromElement();
+ ASSERT(!m_preparedForInlineCompletionHandler);
+ if (m_preparedForInline) {
+ completionHandler();
+ return;
+ }
+
+ m_preparedForInlineCompletionHandler = completionHandler;
}
+
+#if PLATFORM(IOS) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
+
+bool HTMLMediaElement::isVideoLayerInline()
+{
+ return !m_videoFullscreenLayer;
+};
+
+void HTMLMediaElement::setVideoFullscreenLayer(PlatformLayer* platformLayer, std::function<void()> completionHandler)
+{
+ m_videoFullscreenLayer = platformLayer;
+ if (!m_player) {
+ completionHandler();
+ return;
+ }
+
+ m_player->setVideoFullscreenLayer(platformLayer, completionHandler);
+ invalidateStyleAndLayerComposition();
+#if ENABLE(VIDEO_TRACK)
+ 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);
+}
+
+#else
+
+bool HTMLMediaElement::isVideoLayerInline()
+{
+ return true;
+};
+
#endif
bool HTMLMediaElement::hasClosedCaptions() const
@@ -4962,15 +5662,14 @@ bool HTMLMediaElement::hasClosedCaptions() const
return true;
#if ENABLE(VIDEO_TRACK)
- if (!RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled() || !m_textTracks)
+ if (!m_textTracks)
return false;
for (unsigned i = 0; i < m_textTracks->length(); ++i) {
- if (m_textTracks->item(i)->readinessState() == TextTrack::FailedToLoad)
+ auto& track = *m_textTracks->item(i);
+ if (track.readinessState() == TextTrack::FailedToLoad)
continue;
-
- if (m_textTracks->item(i)->kind() == TextTrack::captionsKeyword()
- || m_textTracks->item(i)->kind() == TextTrack::subtitlesKeyword())
+ if (track.kind() == TextTrack::Kind::Captions || track.kind() == TextTrack::Kind::Subtitles)
return true;
}
#endif
@@ -4984,22 +5683,26 @@ bool HTMLMediaElement::closedCaptionsVisible() const
}
#if ENABLE(VIDEO_TRACK)
+
void HTMLMediaElement::updateTextTrackDisplay()
{
#if ENABLE(MEDIA_CONTROLS_SCRIPT)
- ensureUserAgentShadowRoot();
- return;
-#endif
+ 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;
@@ -5010,10 +5713,8 @@ void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible)
m_player->setClosedCaptionsVisible(closedCaptionVisible);
#if ENABLE(VIDEO_TRACK)
- if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) {
- markCaptionAndSubtitleTracksAsUnconfigured(Immediately);
- updateTextTrackDisplay();
- }
+ markCaptionAndSubtitleTracksAsUnconfigured(Immediately);
+ updateTextTrackDisplay();
#else
if (hasMediaControls())
mediaControls()->changedClosedCaptionsVisibility();
@@ -5053,14 +5754,16 @@ unsigned HTMLMediaElement::webkitVideoDecodedByteCount() const
}
#endif
-void HTMLMediaElement::mediaCanStart()
+void HTMLMediaElement::mediaCanStart(Document& document)
{
- LOG(Media, "HTMLMediaElement::mediaCanStart");
+ ASSERT_UNUSED(document, &document == &this->document());
+ 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) {
m_isWaitingUntilMediaCanStart = false;
- loadInternal();
+ selectMediaResource();
}
if (m_pausedInternal)
setPausedInternal(false);
@@ -5076,7 +5779,7 @@ 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)
@@ -5084,21 +5787,36 @@ void HTMLMediaElement::setShouldDelayLoadEvent(bool shouldDelay)
else
document().decrementLoadEventDelayCount();
}
-
-void HTMLMediaElement::getSitesInMediaCache(Vector<String>& sites)
+static String& sharedMediaCacheDirectory()
+{
+ static NeverDestroyed<String> sharedMediaCacheDirectory;
+ return sharedMediaCacheDirectory;
+}
+
+void HTMLMediaElement::setMediaCacheDirectory(const String& path)
+{
+ sharedMediaCacheDirectory() = path;
+}
+
+const String& HTMLMediaElement::mediaCacheDirectory()
{
- MediaPlayer::getSitesInMediaCache(sites);
+ return sharedMediaCacheDirectory();
}
-void HTMLMediaElement::clearMediaCache()
+HashSet<RefPtr<SecurityOrigin>> HTMLMediaElement::originsInMediaCache(const String& path)
{
- MediaPlayer::clearMediaCache();
+ return MediaPlayer::originsInMediaCache(path);
}
-void HTMLMediaElement::clearMediaCacheForSite(const String& site)
+void HTMLMediaElement::clearMediaCache(const String& path, std::chrono::system_clock::time_point modifiedSince)
{
- MediaPlayer::clearMediaCacheForSite(site);
+ MediaPlayer::clearMediaCache(path, modifiedSince);
+}
+
+void HTMLMediaElement::clearMediaCacheForOrigins(const String& path, const HashSet<RefPtr<SecurityOrigin>>& origins)
+{
+ MediaPlayer::clearMediaCacheForOrigins(path, origins);
}
void HTMLMediaElement::resetMediaEngines()
@@ -5111,18 +5829,21 @@ 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;
+ return nullptr;
#else
- return toMediaControls(userAgentShadowRoot()->firstChild());
+ ShadowRoot* root = userAgentShadowRoot();
+ if (!root)
+ return nullptr;
+
+ return childrenOfType<MediaControls>(*root).first();
#endif
}
@@ -5133,7 +5854,7 @@ bool HTMLMediaElement::hasMediaControls() const
#else
if (ShadowRoot* userAgent = userAgentShadowRoot()) {
- Node* node = userAgent->firstChild();
+ Node* node = childrenOfType<MediaControls>(*root).first();
ASSERT_WITH_SECURITY_IMPLICATION(!node || node->isMediaControls());
return node;
}
@@ -5145,7 +5866,7 @@ bool HTMLMediaElement::hasMediaControls() const
bool HTMLMediaElement::createMediaControls()
{
#if ENABLE(MEDIA_CONTROLS_SCRIPT)
- ensureUserAgentShadowRoot();
+ ensureMediaControlsShadowRoot();
return false;
#else
if (hasMediaControls())
@@ -5160,9 +5881,9 @@ bool HTMLMediaElement::createMediaControls()
if (isFullscreen())
mediaControls->enteredFullscreen();
- ensureUserAgentShadowRoot().appendChild(mediaControls, ASSERT_NO_EXCEPTION);
+ ensureUserAgentShadowRoot().appendChild(mediaControls);
- if (!controls() || !inDocument())
+ if (!controls() || !isConnected())
mediaControls->hide();
return true;
@@ -5171,26 +5892,28 @@ bool HTMLMediaElement::createMediaControls()
void HTMLMediaElement::configureMediaControls()
{
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- if (shouldUseVideoPluginProxy()) {
- if (m_player)
- m_player->setControls(controls());
+ bool requireControls = controls();
- if (!hasMediaControls() && inDocument())
- createMediaControls();
- return;
- }
+ // 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_isPlayingToWirelessTarget)
+ requireControls = true;
#endif
#if ENABLE(MEDIA_CONTROLS_SCRIPT)
- if (!controls() || !inDocument() || !inActiveDocument())
+ if (!requireControls || !isConnected() || !inActiveDocument())
return;
- ensureUserAgentShadowRoot();
- return;
-#endif
-
- if (!controls() || !inDocument() || !inActiveDocument()) {
+ ensureMediaControlsShadowRoot();
+#else
+ if (!requireControls || !isConnected() || !inActiveDocument()) {
if (hasMediaControls())
mediaControls()->hide();
return;
@@ -5200,6 +5923,7 @@ void HTMLMediaElement::configureMediaControls()
return;
mediaControls()->show();
+#endif
}
#if ENABLE(VIDEO_TRACK)
@@ -5210,18 +5934,19 @@ void HTMLMediaElement::configureTextTrackDisplay(TextTrackVisibilityCheckType ch
if (m_processingPreferenceChange)
return;
- LOG(Media, "HTMLMediaElement::configureTextTrackDisplay");
+ if (document().activeDOMObjectsAreStopped())
+ return;
bool haveVisibleTextTrack = false;
for (unsigned i = 0; i < m_textTracks->length(); ++i) {
- if (m_textTracks->item(i)->mode() == TextTrack::showingKeyword()) {
+ if (m_textTracks->item(i)->mode() == TextTrack::Mode::Showing) {
haveVisibleTextTrack = true;
break;
}
}
if (checkType == CheckTextTrackVisibility && m_haveVisibleTextTrack == haveVisibleTextTrack) {
- updateActiveTextTrackCues(currentTime());
+ updateActiveTextTrackCues(currentMediaTime());
return;
}
@@ -5232,10 +5957,8 @@ void HTMLMediaElement::configureTextTrackDisplay(TextTrackVisibilityCheckType ch
if (!m_haveVisibleTextTrack)
return;
- ensureUserAgentShadowRoot();
- return;
-#endif
-
+ ensureMediaControlsShadowRoot();
+#else
if (!m_haveVisibleTextTrack && !hasMediaControls())
return;
if (!hasMediaControls() && !createMediaControls())
@@ -5243,10 +5966,9 @@ void HTMLMediaElement::configureTextTrackDisplay(TextTrackVisibilityCheckType ch
mediaControls()->changedClosedCaptionsVisibility();
- if (RuntimeEnabledFeatures::sharedFeatures().webkitVideoTrackEnabled()) {
- updateTextTrackDisplay();
- updateActiveTextTrackCues(currentTime());
- }
+ updateTextTrackDisplay();
+ updateActiveTextTrackCues(currentMediaTime());
+#endif
}
void HTMLMediaElement::captionPreferencesChanged()
@@ -5257,15 +5979,23 @@ void HTMLMediaElement::captionPreferencesChanged()
if (hasMediaControls())
mediaControls()->textTrackPreferencesChanged();
+#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)
@@ -5273,7 +6003,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
@@ -5281,12 +6011,10 @@ void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured(ReconfigureMod
// captions and non-default tracks should be displayed based on language
// preferences if the user has turned captions on).
for (unsigned i = 0; i < m_textTracks->length(); ++i) {
-
- RefPtr<TextTrack> textTrack = m_textTracks->item(i);
- String kind = textTrack->kind();
-
- if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword())
- textTrack->setHasBeenConfigured(false);
+ auto& track = *m_textTracks->item(i);
+ auto kind = track.kind();
+ if (kind == TextTrack::Kind::Subtitles || kind == TextTrack::Kind::Captions)
+ track.setHasBeenConfigured(false);
}
m_processingPreferenceChange = true;
@@ -5301,20 +6029,22 @@ void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured(ReconfigureMod
void HTMLMediaElement::createMediaPlayer()
{
+ LOG(Media, "HTMLMediaElement::createMediaPlayer(%p)", this);
+
#if ENABLE(WEB_AUDIO)
if (m_audioSourceNode)
m_audioSourceNode->lock();
#endif
#if ENABLE(MEDIA_SOURCE)
- if (m_mediaSource)
- m_mediaSource->close();
+ detachMediaSource();
#endif
#if ENABLE(VIDEO_TRACK)
- removeAllInbandTracks();
+ forgetResourceSpecificTracks();
#endif
- m_player = MediaPlayer::create(this);
+ m_player = MediaPlayer::create(*this);
+ scheduleUpdatePlaybackControlsManager();
#if ENABLE(WEB_AUDIO)
if (m_audioSourceNode) {
@@ -5326,12 +6056,15 @@ void HTMLMediaElement::createMediaPlayer()
}
#endif
-#if ENABLE(IOS_AIRPLAY)
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) {
- m_player->setHasPlaybackTargetAvailabilityListeners(true);
+ m_hasPlaybackTargetAvailabilityListeners = true;
+ m_mediaSession->setHasPlaybackTargetAvailabilityListeners(*this, true);
enqueuePlaybackTargetAvailabilityChangedEvent(); // Ensure the event listener gets at least one event.
}
#endif
+
+ updateSleepDisabling();
}
#if ENABLE(WEB_AUDIO)
@@ -5367,24 +6100,24 @@ void HTMLMediaElement::setMediaGroup(const String& group)
// attribute is set, changed, or removed, the user agent must run the following steps:
// 1. Let m [this] be the media element in question.
// 2. Let m have no current media controller, if it currently has one.
- setController(0);
+ setController(nullptr);
// 3. If m's mediagroup attribute is being removed, then abort these steps.
- if (group.isNull() || group.isEmpty())
+ if (group.isEmpty())
return;
// 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)
+ 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;
}
}
@@ -5398,20 +6131,29 @@ MediaController* HTMLMediaElement::controller() const
return m_mediaController.get();
}
-void HTMLMediaElement::setController(PassRefPtr<MediaController> controller)
+void HTMLMediaElement::setController(RefPtr<MediaController>&& controller)
{
if (m_mediaController)
- m_mediaController->removeMediaElement(this);
+ m_mediaController->removeMediaElement(*this);
- m_mediaController = controller;
+ m_mediaController = WTFMove(controller);
if (m_mediaController)
- m_mediaController->addMediaElement(this);
+ m_mediaController->addMediaElement(*this);
if (hasMediaControls())
mediaControls()->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this));
}
+void HTMLMediaElement::setControllerForBindings(MediaController* controller)
+{
+ // 4.8.10.11.2 Media controllers: controller attribute.
+ // On setting, it must first remove the element's mediagroup attribute, if any,
+ setMediaGroup({ });
+ // and then set the current media controller to the given value.
+ setController(controller);
+}
+
void HTMLMediaElement::updateMediaController()
{
if (m_mediaController)
@@ -5452,31 +6194,31 @@ 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);
}
@@ -5484,25 +6226,27 @@ void HTMLMediaElement::applyMediaFragmentURI()
void HTMLMediaElement::updateSleepDisabling()
{
-#if PLATFORM(MAC)
- if (!shouldDisableSleep() && m_sleepDisabler)
+ bool shouldDisableSleep = this->shouldDisableSleep();
+ if (!shouldDisableSleep && m_sleepDisabler)
m_sleepDisabler = nullptr;
- else if (shouldDisableSleep() && !m_sleepDisabler)
+ else if (shouldDisableSleep && !m_sleepDisabler)
m_sleepDisabler = DisplaySleepDisabler::create("com.apple.WebCore: HTMLMediaElement playback");
-#endif
+
+ if (m_player)
+ m_player->setShouldDisableSleep(shouldDisableSleep);
}
-#if PLATFORM(MAC)
bool HTMLMediaElement::shouldDisableSleep() const
{
-#if ENABLE(PAGE_VISIBILITY_API)
- if (m_isDisplaySleepDisablingSuspended)
- return false;
+#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
{
@@ -5520,22 +6264,82 @@ String HTMLMediaElement::mediaPlayerUserAgent() const
return String();
return frame->loader().userAgent(m_currentSrc);
+}
+
+#if ENABLE(AVF_CAPTIONS)
+static inline PlatformTextTrack::TrackKind toPlatform(TextTrack::Kind kind)
+{
+ switch (kind) {
+ case TextTrack::Kind::Captions:
+ return PlatformTextTrack::Caption;
+ case TextTrack::Kind::Chapters:
+ return PlatformTextTrack::Chapter;
+ case TextTrack::Kind::Descriptions:
+ return PlatformTextTrack::Description;
+ case TextTrack::Kind::Forced:
+ return PlatformTextTrack::Forced;
+ case TextTrack::Kind::Metadata:
+ return PlatformTextTrack::MetaData;
+ case TextTrack::Kind::Subtitles:
+ return PlatformTextTrack::Subtitle;
+ }
+ ASSERT_NOT_REACHED();
+ return PlatformTextTrack::Caption;
}
-MediaPlayerClient::CORSMode HTMLMediaElement::mediaPlayerCORSMode() const
+static inline PlatformTextTrack::TrackMode toPlatform(TextTrack::Mode mode)
{
- if (!fastHasAttribute(HTMLNames::crossoriginAttr))
- return Unspecified;
- if (equalIgnoringCase(fastGetAttribute(HTMLNames::crossoriginAttr), "use-credentials"))
- return UseCredentials;
- return Anonymous;
+ switch (mode) {
+ case TextTrack::Mode::Disabled:
+ return PlatformTextTrack::Disabled;
+ case TextTrack::Mode::Hidden:
+ return PlatformTextTrack::Hidden;
+ case TextTrack::Mode::Showing:
+ return PlatformTextTrack::Showing;
+ }
+ ASSERT_NOT_REACHED();
+ return PlatformTextTrack::Disabled;
}
+Vector<RefPtr<PlatformTextTrack>> HTMLMediaElement::outOfBandTrackSources()
+{
+ Vector<RefPtr<PlatformTextTrack>> outOfBandTrackSources;
+ for (auto& trackElement : childrenOfType<HTMLTrackElement>(*this)) {
+ URL url = trackElement.getNonEmptyURLAttribute(srcAttr);
+ if (url.isEmpty())
+ continue;
+
+ if (!isAllowedToLoadMediaURL(*this, url, trackElement.isInUserAgentShadowTree()))
+ continue;
+
+ auto& track = trackElement.track();
+ auto kind = track.kind();
+
+ // FIXME: The switch statement below preserves existing behavior where we ignore chapters and metadata tracks.
+ // If we confirm this behavior is valuable, we should remove this comment. Otherwise, remove both comment and switch.
+ switch (kind) {
+ case TextTrack::Kind::Captions:
+ case TextTrack::Kind::Descriptions:
+ case TextTrack::Kind::Forced:
+ case TextTrack::Kind::Subtitles:
+ break;
+ case TextTrack::Kind::Chapters:
+ case TextTrack::Kind::Metadata:
+ continue;
+ }
+
+ outOfBandTrackSources.append(PlatformTextTrack::createOutOfBand(trackElement.label(), trackElement.srclang(), url.string(), toPlatform(track.mode()), toPlatform(kind), track.uniqueId(), trackElement.isDefault()));
+ }
+
+ return outOfBandTrackSources;
+}
+
+#endif
+
bool HTMLMediaElement::mediaPlayerNeedsSiteSpecificHacks() const
{
- Settings* settings = document().settings();
- return settings && settings->needsSiteSpecificQuirks();
+ return document().settings().needsSiteSpecificQuirks();
}
String HTMLMediaElement::mediaPlayerDocumentHost() const
@@ -5570,9 +6374,17 @@ bool HTMLMediaElement::mediaPlayerIsVideo() const
LayoutRect HTMLMediaElement::mediaPlayerContentBoxRect() const
{
- if (renderer())
- return renderer()->enclosingBox()->contentBoxRect();
- return LayoutRect();
+ auto* renderer = this->renderer();
+ if (!renderer)
+ return { };
+ return renderer->enclosingBox().contentBoxRect();
+}
+
+float HTMLMediaElement::mediaPlayerContentsScale() const
+{
+ if (auto page = document().page())
+ return page->pageScaleFactor() * page->deviceScaleFactor();
+ return 1;
}
void HTMLMediaElement::mediaPlayerSetSize(const IntSize& size)
@@ -5606,19 +6418,27 @@ 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(), crossOrigin()));
}
-CachedResourceLoader* HTMLMediaElement::mediaPlayerCachedResourceLoader()
+bool HTMLMediaElement::mediaPlayerShouldUsePersistentCache() const
+{
+ if (Page* page = document().page())
+ return !page->usesEphemeralSession() && !page->isResourceCachingDisabled();
+
+ return false;
+}
+
+const String& HTMLMediaElement::mediaPlayerMediaCacheDirectory() const
{
- return mediaPlayerOwningDocument()->cachedResourceLoader();
+ return mediaCacheDirectory();
}
bool HTMLMediaElement::mediaPlayerShouldWaitForResponseToAuthenticationChallenge(const AuthenticationChallenge& challenge)
@@ -5642,43 +6462,95 @@ bool HTMLMediaElement::mediaPlayerShouldWaitForResponseToAuthenticationChallenge
return true;
}
-void HTMLMediaElement::removeBehaviorsRestrictionsAfterFirstUserGesture()
+String HTMLMediaElement::sourceApplicationIdentifier() 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
+{
+ return document().settings().networkInterfaceName();
+}
+
+bool HTMLMediaElement::mediaPlayerGetRawCookies(const URL& url, Vector<Cookie>& cookies) const
{
- m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequireUserGestureForLoad);
- m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequireUserGestureForRateChange);
- m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequireUserGestureForFullscreen);
-#if ENABLE(IOS_AIRPLAY)
- m_mediaSession->removeBehaviorRestriction(HTMLMediaSession::RequireUserGestureToShowPlaybackTargetPicker);
+ return getRawCookies(document(), url, cookies);
+}
#endif
+
+bool HTMLMediaElement::mediaPlayerIsInMediaDocument() const
+{
+ return document().isMediaDocument();
}
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
-bool HTMLMediaElement::shouldUseVideoPluginProxy() const
+void HTMLMediaElement::mediaPlayerEngineFailedToLoad() const
{
- return document().settings() && document().settings()->isVideoPluginProxyEnabled();
+ if (!m_player)
+ return;
+
+ if (auto* page = document().page())
+ page->diagnosticLoggingClient().logDiagnosticMessageWithValue(DiagnosticLoggingKeys::engineFailedToLoadKey(), m_player->engineDescription(), m_player->platformErrorCode(), 4, 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(MediaElementSession::BehaviorRestrictions mask)
+{
+ MediaElementSession::BehaviorRestrictions restrictionsToRemove = mask &
+ (MediaElementSession::RequireUserGestureForLoad
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+ | MediaElementSession::RequireUserGestureToShowPlaybackTargetPicker
+ | MediaElementSession::RequireUserGestureToAutoplayToExternalDevice
#endif
+ | MediaElementSession::RequireUserGestureForLoad
+ | MediaElementSession::RequireUserGestureForVideoRateChange
+ | MediaElementSession::RequireUserGestureForAudioRateChange
+ | MediaElementSession::RequireUserGestureForFullscreen
+ | MediaElementSession::InvisibleAutoplayNotPermitted
+ | MediaElementSession::RequireUserGestureToControlControlsManager);
+
+ 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
+ double timestamp = domWindow ? 1000 * domWindow->nowTimestamp() : 0;
if (!m_player)
- return VideoPlaybackQuality::create(now, 0, 0, 0, 0);
+ return VideoPlaybackQuality::create(timestamp, 0, 0, 0, 0);
- return VideoPlaybackQuality::create(now,
+ return VideoPlaybackQuality::create(timestamp,
m_droppedVideoFrames + m_player->totalVideoFrames(),
m_droppedVideoFrames + m_player->droppedVideoFrames(),
m_player->corruptedVideoFrames(),
- m_player->totalFrameDelay());
+ m_player->totalFrameDelay().toDouble());
}
#endif
@@ -5686,12 +6558,13 @@ RefPtr<VideoPlaybackQuality> HTMLMediaElement::getVideoPlaybackQuality()
DOMWrapperWorld& HTMLMediaElement::ensureIsolatedWorld()
{
if (!m_isolatedWorld)
- m_isolatedWorld = DOMWrapperWorld::create(JSDOMWindow::commonVM());
+ m_isolatedWorld = DOMWrapperWorld::create(commonVM());
return *m_isolatedWorld;
}
bool HTMLMediaElement::ensureMediaControlsInjectedScript()
{
+ LOG(Media, "HTMLMediaElement::ensureMediaControlsInjectedScript(%p)", this);
Page* page = document().page();
if (!page)
return false;
@@ -5701,30 +6574,75 @@ bool HTMLMediaElement::ensureMediaControlsInjectedScript()
return false;
DOMWrapperWorld& world = ensureIsolatedWorld();
- ScriptController& scriptController = page->mainFrame().script();
+ ScriptController& scriptController = document().frame()->script();
JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world));
+ JSC::VM& vm = globalObject->vm();
+ JSC::JSLockHolder lock(vm);
+ auto scope = DECLARE_CATCH_SCOPE(vm);
JSC::ExecState* exec = globalObject->globalExec();
- JSC::JSValue overlayValue = globalObject->get(exec, JSC::Identifier(exec, "createControls"));
- if (overlayValue.isFunction())
+ JSC::JSValue functionValue = globalObject->get(exec, JSC::Identifier::fromString(exec, "createControls"));
+ if (functionValue.isFunction())
return true;
- scriptController.evaluateInWorld(ScriptSourceCode(mediaControlsScript), world);
- if (exec->hadException()) {
- exec->clearException();
+#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 (UNLIKELY(scope.exception())) {
+ scope.clearException();
return false;
}
return true;
}
-void HTMLMediaElement::didAddUserAgentShadowRoot(ShadowRoot* root)
+void HTMLMediaElement::updatePageScaleFactorJSProperty()
{
-#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
- // JavaScript controls are not enabled with the video plugin proxy.
- if (shouldUseVideoPluginProxy())
+ Page* page = document().page();
+ if (!page)
return;
-#endif
+
+ setControllerJSProperty("pageScaleFactor", JSC::jsNumber(page->pageScaleFactor()));
+}
+
+void HTMLMediaElement::updateUsesLTRUserInterfaceLayoutDirectionJSProperty()
+{
+ Page* page = document().page();
+ if (!page)
+ return;
+
+ bool usesLTRUserInterfaceLayoutDirectionProperty = page->userInterfaceLayoutDirection() == UserInterfaceLayoutDirection::LTR;
+ setControllerJSProperty("usesLTRUserInterfaceLayoutDirection", JSC::jsBoolean(usesLTRUserInterfaceLayoutDirectionProperty));
+}
+
+void HTMLMediaElement::setControllerJSProperty(const char* propertyName, JSC::JSValue propertyValue)
+{
+ 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);
+ if (controllerValue.isNull())
+ return;
+
+ JSC::PutPropertySlot propertySlot(controllerValue);
+ JSC::JSObject* controllerObject = controllerValue.toObject(exec);
+ if (!controllerObject)
+ return;
+
+ controllerObject->methodTable()->put(controllerObject, exec, JSC::Identifier::fromString(exec, propertyName), propertyValue, propertySlot);
+}
+
+void HTMLMediaElement::didAddUserAgentShadowRoot(ShadowRoot* root)
+{
+ LOG(Media, "HTMLMediaElement::didAddUserAgentShadowRoot(%p)", this);
+
Page* page = document().page();
if (!page)
return;
@@ -5734,33 +6652,176 @@ void HTMLMediaElement::didAddUserAgentShadowRoot(ShadowRoot* root)
if (!ensureMediaControlsInjectedScript())
return;
- ScriptController& scriptController = page->mainFrame().script();
+ ScriptController& scriptController = document().frame()->script();
JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world));
+ JSC::VM& vm = globalObject->vm();
+ JSC::JSLockHolder lock(vm);
+ auto scope = DECLARE_CATCH_SCOPE(vm);
JSC::ExecState* exec = globalObject->globalExec();
- JSC::JSLockHolder lock(exec);
- // It is expected the JS file provides a createControls(shadowRoot, video, mediaControlsHost) function.
- JSC::JSValue overlayValue = globalObject->get(exec, JSC::Identifier(exec, "createControls"));
- if (overlayValue.isUndefinedOrNull())
+ // 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);
+
JSC::MarkedArgumentBuffer argList;
argList.append(toJS(exec, globalObject, root));
- argList.append(toJS(exec, globalObject, this));
- argList.append(toJS(exec, globalObject, m_mediaControlsHost.get()));
+ argList.append(mediaJSWrapper);
+ argList.append(mediaControlsHostJSWrapper);
- JSC::JSObject* overlay = overlayValue.toObject(exec);
+ JSC::JSObject* function = functionValue.toObject(exec);
+ ASSERT(!scope.exception());
JSC::CallData callData;
- JSC::CallType callType = overlay->methodTable()->getCallData(overlay, callData);
- if (callType == JSC::CallTypeNone)
+ JSC::CallType callType = function->methodTable()->getCallData(function, callData);
+ if (callType == JSC::CallType::None)
+ return;
+
+ JSC::JSValue controllerValue = JSC::call(exec, function, callType, callData, globalObject, argList);
+ scope.clearException();
+ JSC::JSObject* controllerObject = jsDynamicDowncast<JSC::JSObject*>(vm, controllerValue);
+ if (!controllerObject)
+ return;
+
+ // Connect the Media, MediaControllerHost, and Controller so the GC knows about their relationship
+ JSC::JSObject* mediaJSWrapperObject = mediaJSWrapper.toObject(exec);
+ ASSERT(!scope.exception());
+ 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 = jsDynamicDowncast<JSC::JSObject*>(vm, 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);
+
+ updatePageScaleFactorJSProperty();
+ updateUsesLTRUserInterfaceLayoutDirectionJSProperty();
+
+ if (UNLIKELY(scope.exception()))
+ scope.clearException();
+}
+
+void HTMLMediaElement::setMediaControlsDependOnPageScaleFactor(bool dependsOnPageScale)
+{
+ LOG(Media, "MediaElement::setMediaControlsDependPageScaleFactor(%p) = %s", this, boolString(dependsOnPageScale));
+
+ if (document().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::updateMediaControlsAfterPresentationModeChange()
+{
+ // Don't execute script if the controls script hasn't been injected yet, or we have
+ // stopped/suspended the object.
+ if (!m_mediaControlsHost || document().activeDOMObjectsAreSuspended() || document().activeDOMObjectsAreStopped())
return;
- JSC::call(exec, overlay, callType, callData, globalObject, argList);
- if (exec->hadException())
- exec->clearException();
+ DOMWrapperWorld& world = ensureIsolatedWorld();
+ ScriptController& scriptController = document().frame()->script();
+ JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world));
+ JSC::VM& vm = globalObject->vm();
+ JSC::JSLockHolder lock(vm);
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ JSC::ExecState* exec = globalObject->globalExec();
+
+ JSC::JSValue controllerValue = controllerJSValue(*exec, *globalObject, *this);
+ JSC::JSObject* controllerObject = controllerValue.toObject(exec);
+
+ RETURN_IF_EXCEPTION(scope, void());
+
+ JSC::JSValue functionValue = controllerObject->get(exec, JSC::Identifier::fromString(exec, "handlePresentationModeChange"));
+ if (UNLIKELY(scope.exception()) || functionValue.isUndefinedOrNull())
+ return;
+
+ JSC::JSObject* function = functionValue.toObject(exec);
+ ASSERT(!scope.exception());
+ JSC::CallData callData;
+ JSC::CallType callType = function->methodTable()->getCallData(function, callData);
+ if (callType == JSC::CallType::None)
+ return;
+
+ JSC::MarkedArgumentBuffer argList;
+ JSC::call(exec, function, callType, callData, controllerObject, argList);
+}
+
+void HTMLMediaElement::pageScaleFactorChanged()
+{
+ updatePageScaleFactorJSProperty();
+}
+
+void HTMLMediaElement::userInterfaceLayoutDirectionChanged()
+{
+ updateUsesLTRUserInterfaceLayoutDirectionJSProperty();
+}
+
+String HTMLMediaElement::getCurrentMediaControlsStatus()
+{
+ DOMWrapperWorld& world = ensureIsolatedWorld();
+ ensureMediaControlsShadowRoot();
+
+ ScriptController& scriptController = document().frame()->script();
+ JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(world));
+ JSC::VM& vm = globalObject->vm();
+ JSC::JSLockHolder lock(vm);
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ JSC::ExecState* exec = globalObject->globalExec();
+
+ JSC::JSValue controllerValue = controllerJSValue(*exec, *globalObject, *this);
+ JSC::JSObject* controllerObject = controllerValue.toObject(exec);
+
+ RETURN_IF_EXCEPTION(scope, emptyString());
+
+ JSC::JSValue functionValue = controllerObject->get(exec, JSC::Identifier::fromString(exec, "getCurrentControlsStatus"));
+ if (UNLIKELY(scope.exception()) || functionValue.isUndefinedOrNull())
+ return emptyString();
+
+ JSC::JSObject* function = functionValue.toObject(exec);
+ ASSERT(!scope.exception());
+ JSC::CallData callData;
+ JSC::CallType callType = function->methodTable()->getCallData(function, callData);
+ JSC::MarkedArgumentBuffer argList;
+ if (callType == JSC::CallType::None)
+ return emptyString();
+
+ JSC::JSValue outputValue = JSC::call(exec, function, callType, callData, controllerObject, argList);
+
+ RETURN_IF_EXCEPTION(scope, emptyString());
+
+ return outputValue.getString(exec);
}
#endif // ENABLE(MEDIA_CONTROLS_SCRIPT)
@@ -5772,39 +6833,473 @@ unsigned long long HTMLMediaElement::fileSize() const
return 0;
}
-MediaSession::MediaType HTMLMediaElement::mediaType() const
+PlatformMediaSession::MediaType HTMLMediaElement::mediaType() const
+{
+ if (m_player && m_readyState >= HAVE_METADATA) {
+ if (hasVideo() && hasAudio() && !muted())
+ return PlatformMediaSession::VideoAudio;
+ return hasVideo() ? PlatformMediaSession::Video : PlatformMediaSession::Audio;
+ }
+
+ return presentationType();
+}
+
+PlatformMediaSession::MediaType HTMLMediaElement::presentationType() const
{
if (hasTagName(HTMLNames::videoTag))
- return MediaSession::Video;
+ return muted() ? PlatformMediaSession::Video : PlatformMediaSession::VideoAudio;
- return MediaSession::Audio;
+ return PlatformMediaSession::Audio;
}
-void HTMLMediaElement::beginInterruption()
+PlatformMediaSession::DisplayType HTMLMediaElement::displayType() const
{
- LOG(Media, "HTMLMediaElement::beginInterruption");
+ 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;
+}
+
+PlatformMediaSession::CharacteristicsFlags HTMLMediaElement::characteristics() const
+{
+ if (m_readyState < HAVE_METADATA)
+ return PlatformMediaSession::HasNothing;
+
+ PlatformMediaSession::CharacteristicsFlags state = PlatformMediaSession::HasNothing;
+ if (isVideo() && hasVideo())
+ state |= PlatformMediaSession::HasVideo;
+ if (this->hasAudio())
+ state |= PlatformMediaSession::HasAudio;
+
+ return state;
+}
+
+bool HTMLMediaElement::canProduceAudio() const
+{
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+ // Because the remote target could unmute playback without notifying us, we must assume
+ // that we may be playing audio.
+ if (m_isPlayingToWirelessTarget)
+ return true;
+#endif
+
+ if (muted())
+ return false;
+
+ return m_player && m_readyState >= HAVE_METADATA && hasAudio();
+}
+
+#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 (canTransitionFromAutoplayToPlay())
+ 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 (hasAttributeWithoutSynchronization(titleAttr))
+ return attributeWithoutSynchronization(titleAttr);
- m_resumePlaybackAfterInterruption = !paused();
- if (m_resumePlaybackAfterInterruption)
+ return m_currentSrc;
+}
+
+void HTMLMediaElement::didReceiveRemoteControlCommand(PlatformMediaSession::RemoteControlCommandType command, const PlatformMediaSession::RemoteCommandArgument* argument)
+{
+ LOG(Media, "HTMLMediaElement::didReceiveRemoteControlCommand(%p) - %i", this, static_cast<int>(command));
+
+ UserGestureIndicator remoteControlUserGesture(ProcessingUserGesture, &document());
+ switch (command) {
+ case PlatformMediaSession::PlayCommand:
+ play();
+ break;
+ case PlatformMediaSession::StopCommand:
+ 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;
+ case PlatformMediaSession::SeekToPlaybackPositionCommand:
+ ASSERT(argument);
+ if (argument)
+ handleSeekToPlaybackPosition(argument->asDouble);
+ break;
+ default:
+ { } // Do nothing
+ }
+}
+
+bool HTMLMediaElement::supportsSeeking() const
+{
+ return !isLiveStream();
+}
+
+bool HTMLMediaElement::shouldOverrideBackgroundPlaybackRestriction(PlatformMediaSession::InterruptionType type) const
+{
+ if (type == PlatformMediaSession::EnteringBackground) {
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+ if (m_isPlayingToWirelessTarget) {
+ LOG(Media, "HTMLMediaElement::shouldOverrideBackgroundPlaybackRestriction(%p) - returning true because m_isPlayingToWirelessTarget is true", this);
+ 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
+ } else if (type == PlatformMediaSession::SuspendedUnderLock) {
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+ if (m_isPlayingToWirelessTarget) {
+ LOG(Media, "HTMLMediaElement::shouldOverrideBackgroundPlaybackRestriction(%p) - returning true because m_isPlayingToWirelessTarget is true", this);
+ return true;
+ }
+#endif
+ }
+ return false;
+}
+
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+void HTMLMediaElement::updateMediaState(UpdateState updateState)
+{
+ if (updateState == UpdateState::Asynchronously) {
+ scheduleDelayedAction(CheckMediaState);
+ return;
+ }
+
+ MediaProducer::MediaStateFlags state = mediaState();
+ if (m_mediaState == state)
+ return;
+
+ m_mediaState = state;
+ m_mediaSession->mediaStateDidChange(*this, m_mediaState);
+#if ENABLE(MEDIA_SESSION)
+ document().updateIsPlayingMedia(m_elementID);
+#else
+ document().updateIsPlayingMedia();
+#endif
+}
+#endif
+
+MediaProducer::MediaStateFlags HTMLMediaElement::mediaState() const
+{
+ MediaStateFlags state = IsNotPlaying;
+
+ bool hasActiveVideo = isVideo() && hasVideo();
+ bool hasAudio = this->hasAudio();
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+ if (m_isPlayingToWirelessTarget)
+ 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() && volume())
+ state |= IsPlayingAudio;
+
+ if (hasActiveVideo)
+ state |= IsPlayingVideo;
+
+ return state;
+}
+
+void HTMLMediaElement::pageMutedStateDidChange()
+{
+ updateVolume();
+}
+
+bool HTMLMediaElement::effectiveMuted() const
+{
+ return muted() || (document().page() && document().page()->isAudioMuted());
+}
+
+bool HTMLMediaElement::doesHaveAttribute(const AtomicString& attribute, AtomicString* value) const
+{
+ QualifiedName attributeName(nullAtom, attribute, nullAtom);
+
+ AtomicString elementValue = attributeWithoutSynchronization(attributeName);
+ if (elementValue.isNull())
+ return false;
+
+ if (attributeName == HTMLNames::x_itunes_inherit_uri_query_componentAttr && !document().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;
+
+ if (m_isPlayingToWirelessTarget) {
+ LOG(Media, "HTMLMediaElement::purgeBufferedDataIfPossible(%p) - early return because m_isPlayingToWirelessTarget is true", this);
+ 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();
}
-void HTMLMediaElement::endInterruption(MediaSession::EndInterruptionFlags flags)
+#endif
+
+void HTMLMediaElement::allowsMediaDocumentInlinePlaybackChanged()
+{
+ if (potentiallyPlaying() && m_mediaSession->requiresFullscreenForVideoPlayback(*this) && !isFullscreen())
+ enterFullscreen();
+}
+
+bool HTMLMediaElement::isVideoTooSmallForInlinePlayback()
{
- bool shouldResumePlayback = m_resumePlaybackAfterInterruption;
- m_resumePlaybackAfterInterruption = false;
+ auto* renderer = this->renderer();
+
+ if (!renderer || !is<RenderVideo>(*renderer))
+ return true;
+
+ IntRect videoBox = downcast<RenderVideo>(*renderer).videoBox();
+ return (videoBox.width() <= 1 || videoBox.height() <= 1);
+}
- if (!flags & MediaSession::MayResumePlaying)
+void HTMLMediaElement::isVisibleInViewportChanged()
+{
+ updateShouldAutoplay();
+ scheduleUpdatePlaybackControlsManager();
+}
+
+void HTMLMediaElement::updateShouldAutoplay()
+{
+ if (!autoplay())
return;
- if (shouldResumePlayback)
+ if (!m_mediaSession->hasBehaviorRestriction(MediaElementSession::InvisibleAutoplayNotPermitted))
+ return;
+
+ bool canAutoplay = mediaSession().autoplayPermitted();
+ 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);
+}
+
+void HTMLMediaElement::updateShouldPlay()
+{
+ if (!paused() && !m_mediaSession->playbackPermitted(*this))
+ pauseInternal();
+ else if (canTransitionFromAutoplayToPlay())
play();
}
-void HTMLMediaElement::pausePlayback()
+void HTMLMediaElement::resetPlaybackSessionState()
{
- if (!paused())
- pause();
+ if (m_mediaSession)
+ m_mediaSession->resetPlaybackSessionState();
+}
+
+bool HTMLMediaElement::isVisibleInViewport() const
+{
+ auto renderer = this->renderer();
+ return renderer && renderer->visibleInViewportState() == RenderElement::VisibleInViewport;
+}
+
+void HTMLMediaElement::updatePlaybackControlsManager()
+{
+ Page* page = document().page();
+ if (!page)
+ return;
+
+ // FIXME: Ensure that the renderer here should be up to date.
+ if (auto bestMediaElement = bestMediaElementForShowingPlaybackControlsManager(MediaElementSession::PlaybackControlsPurpose::ControlsManager))
+ page->chrome().client().setUpPlaybackControlsManager(*bestMediaElement);
+ else
+ page->chrome().client().clearPlaybackControlsManager();
+}
+
+void HTMLMediaElement::scheduleUpdatePlaybackControlsManager()
+{
+ if (!m_updatePlaybackControlsManagerQueue.hasPendingTasks())
+ m_updatePlaybackControlsManagerQueue.enqueueTask(std::bind(&HTMLMediaElement::updatePlaybackControlsManager, this));
+}
+
+void HTMLMediaElement::playbackControlsManagerBehaviorRestrictionsTimerFired()
+{
+ if (m_playbackControlsManagerBehaviorRestrictionsQueue.hasPendingTasks())
+ return;
+
+ if (!m_mediaSession->hasBehaviorRestriction(MediaElementSession::RequireUserGestureToControlControlsManager))
+ return;
+
+ RefPtr<HTMLMediaElement> protectedThis(this);
+ m_playbackControlsManagerBehaviorRestrictionsQueue.enqueueTask([protectedThis] () {
+ MediaElementSession* mediaElementSession = protectedThis->m_mediaSession.get();
+ if (protectedThis->isPlaying() || mediaElementSession->state() == PlatformMediaSession::Autoplaying || mediaElementSession->state() == PlatformMediaSession::Playing)
+ return;
+
+ mediaElementSession->addBehaviorRestriction(MediaElementSession::RequirePlaybackToControlControlsManager);
+ protectedThis->scheduleUpdatePlaybackControlsManager();
+ });
+}
+
+bool HTMLMediaElement::shouldOverrideBackgroundLoadingRestriction() const
+{
+#if ENABLE(WIRELESS_PLAYBACK_TARGET)
+ if (isPlayingToWirelessPlaybackTarget())
+ return true;
+#endif
+
+ return m_videoFullscreenMode == VideoFullscreenModePictureInPicture;
+}
+
+void HTMLMediaElement::fullscreenModeChanged(VideoFullscreenMode mode)
+{
+ if (m_videoFullscreenMode == mode)
+ return;
+
+ m_videoFullscreenMode = mode;
+ m_mediaSession->scheduleClientDataBufferingCheck();
+ scheduleUpdatePlaybackControlsManager();
}
}