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