diff options
Diffstat (limited to 'chromium/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp')
-rw-r--r-- | chromium/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp | 1580 |
1 files changed, 817 insertions, 763 deletions
diff --git a/chromium/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp b/chromium/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp index c43d49009f1..ed492e58133 100644 --- a/chromium/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp +++ b/chromium/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp @@ -27,55 +27,52 @@ #include "core/html/HTMLMediaElement.h" #include <limits> -#include "HTMLNames.h" -#include "RuntimeEnabledFeatures.h" #include "bindings/v8/ExceptionState.h" #include "bindings/v8/ExceptionStatePlaceholder.h" #include "bindings/v8/ScriptController.h" #include "bindings/v8/ScriptEventListener.h" +#include "core/HTMLNames.h" #include "core/css/MediaList.h" -#include "core/css/MediaQueryEvaluator.h" #include "core/dom/Attribute.h" #include "core/dom/ExceptionCode.h" #include "core/dom/FullscreenElementStack.h" #include "core/dom/shadow/ShadowRoot.h" #include "core/events/Event.h" -#include "core/events/ThreadLocalEventNames.h" -#include "core/frame/ContentSecurityPolicy.h" -#include "core/frame/Frame.h" +#include "core/frame/LocalFrame.h" #include "core/frame/Settings.h" #include "core/frame/UseCounter.h" +#include "core/frame/csp/ContentSecurityPolicy.h" #include "core/html/HTMLMediaSource.h" #include "core/html/HTMLSourceElement.h" #include "core/html/HTMLTrackElement.h" #include "core/html/MediaController.h" #include "core/html/MediaError.h" #include "core/html/MediaFragmentURIParser.h" -#include "core/html/MediaKeyError.h" -#include "core/html/MediaKeyEvent.h" #include "core/html/TimeRanges.h" #include "core/html/shadow/MediaControls.h" +#include "core/html/track/AudioTrack.h" +#include "core/html/track/AudioTrackList.h" #include "core/html/track/InbandTextTrack.h" #include "core/html/track/TextTrackCueList.h" #include "core/html/track/TextTrackList.h" +#include "core/html/track/VideoTrack.h" +#include "core/html/track/VideoTrackList.h" #include "core/loader/FrameLoader.h" -#include "core/rendering/RenderLayerCompositor.h" #include "core/rendering/RenderVideo.h" #include "core/rendering/RenderView.h" -// FIXME: Remove dependency on modules/encryptedmedia (http://crbug.com/242754). -#include "modules/encryptedmedia/MediaKeyNeededEvent.h" -#include "modules/encryptedmedia/MediaKeys.h" -#include "modules/mediastream/MediaStreamRegistry.h" +#include "core/rendering/compositing/RenderLayerCompositor.h" #include "platform/ContentType.h" #include "platform/Language.h" #include "platform/Logging.h" #include "platform/MIMETypeFromURL.h" #include "platform/MIMETypeRegistry.h" #include "platform/NotImplemented.h" +#include "platform/RuntimeEnabledFeatures.h" #include "platform/UserGestureIndicator.h" #include "platform/graphics/GraphicsLayer.h" #include "platform/weborigin/SecurityOrigin.h" #include "public/platform/Platform.h" +#include "public/platform/WebContentDecryptionModule.h" #include "public/platform/WebInbandTextTrack.h" #include "wtf/CurrentTime.h" #include "wtf/MathExtras.h" @@ -85,12 +82,13 @@ #if ENABLE(WEB_AUDIO) #include "platform/audio/AudioSourceProvider.h" -#include "modules/webaudio/MediaElementAudioSourceNode.h" +#include "platform/audio/AudioSourceProviderClient.h" #endif -using namespace std; using blink::WebInbandTextTrack; +using blink::WebMediaPlayer; using blink::WebMimeRegistry; +using blink::WebMediaPlayerClient; namespace WebCore { @@ -126,19 +124,19 @@ static const char* boolString(bool val) static const char mediaSourceBlobProtocol[] = "blob"; using namespace HTMLNames; -using namespace std; -typedef HashMap<Document*, HashSet<HTMLMediaElement*> > DocumentElementSetMap; +typedef WillBeHeapHashSet<RawPtrWillBeWeakMember<HTMLMediaElement> > WeakMediaElementSet; +typedef WillBeHeapHashMap<RawPtrWillBeWeakMember<Document>, WeakMediaElementSet> DocumentElementSetMap; static DocumentElementSetMap& documentToElementSetMap() { - DEFINE_STATIC_LOCAL(DocumentElementSetMap, map, ()); - return map; + DEFINE_STATIC_LOCAL(OwnPtrWillBePersistent<DocumentElementSetMap>, map, (adoptPtrWillBeNoop(new DocumentElementSetMap()))); + return *map; } static void addElementToDocumentMap(HTMLMediaElement* element, Document* document) { DocumentElementSetMap& map = documentToElementSetMap(); - HashSet<HTMLMediaElement*> set = map.take(document); + WeakMediaElementSet set = map.take(document); set.add(element); map.add(document, set); } @@ -146,33 +144,14 @@ static void addElementToDocumentMap(HTMLMediaElement* element, Document* documen static void removeElementFromDocumentMap(HTMLMediaElement* element, Document* document) { DocumentElementSetMap& map = documentToElementSetMap(); - HashSet<HTMLMediaElement*> set = map.take(document); + WeakMediaElementSet set = map.take(document); set.remove(element); if (!set.isEmpty()) map.add(document, set); } -static void throwExceptionForMediaKeyException(MediaPlayer::MediaKeyException exception, ExceptionState& exceptionState) -{ - switch (exception) { - case MediaPlayer::NoError: - return; - case MediaPlayer::InvalidPlayerState: - exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); - return; - case MediaPlayer::KeySystemNotSupported: - exceptionState.throwUninformativeAndGenericDOMException(NotSupportedError); - return; - case MediaPlayer::InvalidAccess: - exceptionState.throwUninformativeAndGenericDOMException(InvalidAccessError); - return; - } - - ASSERT_NOT_REACHED(); - return; -} - class TrackDisplayUpdateScope { + STACK_ALLOCATED(); public: TrackDisplayUpdateScope(HTMLMediaElement* mediaElement) { @@ -186,9 +165,55 @@ public: } private: - HTMLMediaElement* m_mediaElement; + RawPtrWillBeMember<HTMLMediaElement> m_mediaElement; }; +static const AtomicString& AudioKindToString(WebMediaPlayerClient::AudioTrackKind kind) +{ + switch (kind) { + case WebMediaPlayerClient::AudioTrackKindNone: + return emptyAtom; + case WebMediaPlayerClient::AudioTrackKindAlternative: + return AudioTrack::alternativeKeyword(); + case WebMediaPlayerClient::AudioTrackKindDescriptions: + return AudioTrack::descriptionsKeyword(); + case WebMediaPlayerClient::AudioTrackKindMain: + return AudioTrack::mainKeyword(); + case WebMediaPlayerClient::AudioTrackKindMainDescriptions: + return AudioTrack::mainDescriptionsKeyword(); + case WebMediaPlayerClient::AudioTrackKindTranslation: + return AudioTrack::translationKeyword(); + case WebMediaPlayerClient::AudioTrackKindCommentary: + return AudioTrack::commentaryKeyword(); + } + + ASSERT_NOT_REACHED(); + return emptyAtom; +} + +static const AtomicString& VideoKindToString(WebMediaPlayerClient::VideoTrackKind kind) +{ + switch (kind) { + case WebMediaPlayerClient::VideoTrackKindNone: + return emptyAtom; + case WebMediaPlayerClient::VideoTrackKindAlternative: + return VideoTrack::alternativeKeyword(); + case WebMediaPlayerClient::VideoTrackKindCaptions: + return VideoTrack::captionsKeyword(); + case WebMediaPlayerClient::VideoTrackKindMain: + return VideoTrack::mainKeyword(); + case WebMediaPlayerClient::VideoTrackKindSign: + return VideoTrack::signKeyword(); + case WebMediaPlayerClient::VideoTrackKindSubtitles: + return VideoTrack::subtitlesKeyword(); + case WebMediaPlayerClient::VideoTrackKindCommentary: + return VideoTrack::commentaryKeyword(); + } + + ASSERT_NOT_REACHED(); + return emptyAtom; +} + static bool canLoadURL(const KURL& url, const ContentType& contentType, const String& keySystem) { DEFINE_STATIC_LOCAL(const String, codecs, ("codecs")); @@ -241,12 +266,26 @@ WebMimeRegistry::SupportsType HTMLMediaElement::supportsType(const ContentType& return blink::Platform::current()->mimeRegistry()->supportsMediaMIMEType(type, typeCodecs, system); } -HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document, bool createdByParser) +URLRegistry* HTMLMediaElement::s_mediaStreamRegistry = 0; + +void HTMLMediaElement::setMediaStreamRegistry(URLRegistry* registry) +{ + ASSERT(!s_mediaStreamRegistry); + s_mediaStreamRegistry = registry; +} + +bool HTMLMediaElement::isMediaStreamURL(const String& url) +{ + return s_mediaStreamRegistry ? s_mediaStreamRegistry->contains(url) : false; +} + +HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& document) : HTMLElement(tagName, document) , ActiveDOMObject(&document) , m_loadTimer(this, &HTMLMediaElement::loadTimerFired) , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired) , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired) + , m_audioTracksTimer(this, &HTMLMediaElement::audioTracksTimerFired) , m_playedTimeRanges() , m_asyncEventQueue(GenericEventQueue::create(this)) , m_playbackRate(1.0f) @@ -256,14 +295,14 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum , m_readyStateMaximum(HAVE_NOTHING) , m_volume(1.0f) , m_lastSeekTime(0) - , m_previousProgressTime(numeric_limits<double>::max()) - , m_duration(numeric_limits<double>::quiet_NaN()) + , m_previousProgressTime(std::numeric_limits<double>::max()) + , m_duration(std::numeric_limits<double>::quiet_NaN()) , m_lastTimeUpdateEventWallTime(0) - , m_lastTimeUpdateEventMovieTime(numeric_limits<double>::max()) + , m_lastTimeUpdateEventMovieTime(std::numeric_limits<double>::max()) , m_loadState(WaitingForSource) + , m_deferredLoadState(NotDeferred) + , m_deferredLoadTimer(this, &HTMLMediaElement::deferredLoadTimerFired) , m_webLayer(0) - , m_opaque(false) - , m_restrictions(RequirePageConsentToLoadMediaRestriction) , m_preload(MediaPlayer::Auto) , m_displayMode(Unknown) , m_cachedTime(MediaPlayer::invalidTime()) @@ -272,6 +311,7 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum , m_fragmentStartTime(MediaPlayer::invalidTime()) , m_fragmentEndTime(MediaPlayer::invalidTime()) , m_pendingActionFlags(0) + , m_userGestureRequiredForPlay(false) , m_playing(false) , m_shouldDelayLoadEvent(false) , m_haveFiredLoadedData(false) @@ -284,18 +324,21 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum , m_sentEndEvent(false) , m_pausedInternal(false) , m_closedCaptionsVisible(false) - , m_loadInitiatedByUserGesture(false) , m_completelyLoaded(false) , m_havePreparedToPlay(false) - , m_parsingInProgress(createdByParser) , m_tracksAreReady(true) , m_haveVisibleTextTrack(false) , m_processingPreferenceChange(false) +#if ENABLE(OILPAN) + , m_isFinalizing(false) +#endif , m_lastTextTrackUpdateTime(-1) - , m_textTracks(0) + , m_audioTracks(AudioTrackList::create(*this)) + , m_videoTracks(VideoTrackList::create(*this)) + , m_textTracks(nullptr) , m_ignoreTrackDisplayUpdate(0) #if ENABLE(WEB_AUDIO) - , m_audioSourceNode(0) + , m_audioSourceNode(nullptr) #endif { ASSERT(RuntimeEnabledFeatures::mediaEnabled()); @@ -303,45 +346,54 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum WTF_LOG(Media, "HTMLMediaElement::HTMLMediaElement"); ScriptWrappable::init(this); - if (document.settings()) { - if (document.settings()->mediaPlaybackRequiresUserGesture()) { - addBehaviorRestriction(RequireUserGestureForRateChangeRestriction); - addBehaviorRestriction(RequireUserGestureForLoadRestriction); - } - if (document.settings()->mediaFullscreenRequiresUserGesture()) { - addBehaviorRestriction(RequireUserGestureForFullscreenRestriction); - } - } + if (document.settings() && document.settings()->mediaPlaybackRequiresUserGesture()) + m_userGestureRequiredForPlay = true; setHasCustomStyleCallbacks(); addElementToDocumentMap(this, &document); - } HTMLMediaElement::~HTMLMediaElement() { WTF_LOG(Media, "HTMLMediaElement::~HTMLMediaElement"); +#if ENABLE(OILPAN) + // If the HTMLMediaElement dies with the document we are not + // allowed to touch the document to adjust delay load event counts + // because the document could have been already + // destructed. However, if the HTMLMediaElement dies with the + // document there is no need to change the delayed load counts + // because no load event will fire anyway. If the document is + // still alive we do have to decrement the load delay counts. We + // determine if the document is alive via the ActiveDOMObject + // which is a context lifecycle observer. If the Document has been + // destructed ActiveDOMObject::executionContext() returns 0. + if (ActiveDOMObject::executionContext()) + setShouldDelayLoadEvent(false); +#else + // HTMLMediaElement and m_asyncEventQueue always become unreachable + // together. So HTMLMediaElemenet and m_asyncEventQueue are destructed in + // the same GC. We don't need to close it explicitly in Oilpan. m_asyncEventQueue->close(); setShouldDelayLoadEvent(false); + if (m_textTracks) m_textTracks->clearOwner(); - if (m_textTracks) { - for (unsigned i = 0; i < m_textTracks->length(); ++i) - m_textTracks->item(i)->clearClient(); - } + m_audioTracks->shutdown(); + m_videoTracks->shutdown(); if (m_mediaController) { m_mediaController->removeMediaElement(this); - m_mediaController = 0; + m_mediaController = nullptr; } +#endif closeMediaSource(); - setMediaKeys(0); - +#if !ENABLE(OILPAN) removeElementFromDocumentMap(this, &document()); +#endif // Destroying the player may cause a resource load to be canceled, // which could result in userCancelledLoad() being called back. @@ -350,6 +402,10 @@ HTMLMediaElement::~HTMLMediaElement() // See http://crbug.com/233654 for more details. m_completelyLoaded = true; + // With Oilpan load events on the Document are always delayed during + // sweeping so we don't need to explicitly increment and decrement + // load event delay counts. +#if !ENABLE(OILPAN) // Destroying the player may cause a resource load to be canceled, // which could result in Document::dispatchWindowLoadEvent() being // called via ResourceFetch::didLoadResource() then @@ -357,10 +413,33 @@ HTMLMediaElement::~HTMLMediaElement() // object destruction, we use Document::incrementLoadEventDelayCount(). // See http://crbug.com/275223 for more details. document().incrementLoadEventDelayCount(); +#endif + +#if ENABLE(OILPAN) + // Oilpan: the player must be released, but the player object + // cannot safely access this player client any longer as parts of + // it may have been finalized already (like the media element's + // supplementable table.) Handled for now by entering an + // is-finalizing state, which is explicitly checked for if the + // player tries to access the media element during shutdown. + // + // FIXME: Oilpan: move the media player to the heap instead and + // avoid having to finalize it from here; this whole #if block + // could then be removed (along with the state bit it depends on.) + // crbug.com/378229 + m_isFinalizing = true; +#endif - clearMediaPlayerAndAudioSourceProviderClient(); + // The m_audioSourceNode is either dead already or it is dying together with + // this HTMLMediaElement which it strongly keeps alive. +#if ENABLE(WEB_AUDIO) && !ENABLE(OILPAN) + ASSERT(!m_audioSourceNode); +#endif + clearMediaPlayerAndAudioSourceProviderClientWithoutLocking(); +#if !ENABLE(OILPAN) document().decrementLoadEventDelayCount(); +#endif } void HTMLMediaElement::didMoveToNewDocument(Document& oldDocument) @@ -382,9 +461,9 @@ void HTMLMediaElement::didMoveToNewDocument(Document& oldDocument) addElementToDocumentMap(this, &document()); // FIXME: This is a temporary fix to prevent this object from causing the - // MediaPlayer to dereference Frame and FrameLoader pointers from the + // MediaPlayer to dereference LocalFrame and FrameLoader pointers from the // previous document. A proper fix would provide a mechanism to allow this - // object to refresh the MediaPlayer's Frame and FrameLoader references on + // object to refresh the MediaPlayer's LocalFrame and FrameLoader references on // document changes so that playback can be resumed properly. userCancelledLoad(); @@ -392,14 +471,10 @@ void HTMLMediaElement::didMoveToNewDocument(Document& oldDocument) // and there is no risk of dispatching a load event from within the destructor. oldDocument.decrementLoadEventDelayCount(); + ActiveDOMObject::didMoveToNewExecutionContext(&document()); HTMLElement::didMoveToNewDocument(oldDocument); } -bool HTMLMediaElement::hasCustomFocusLogic() const -{ - return true; -} - bool HTMLMediaElement::supportsFocus() const { if (ownerDocument()->isMediaDocument()) @@ -422,9 +497,9 @@ void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicStr clearMediaPlayer(LoadMediaResource); scheduleDelayedAction(LoadMediaResource); } - } else if (name == controlsAttr) + } else if (name == controlsAttr) { configureMediaControls(); - else if (name == preloadAttr) { + } else if (name == preloadAttr) { if (equalIgnoringCase(value, "none")) m_preload = MediaPlayer::None; else if (equalIgnoringCase(value, "metadata")) @@ -437,30 +512,21 @@ 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); + setPlayerPreload(); - } else if (name == mediagroupAttr) + } else if (name == mediagroupAttr && RuntimeEnabledFeatures::mediaControllerEnabled()) { setMediaGroup(value); - else if (name == onbeforeloadAttr) - setAttributeEventListener(EventTypeNames::beforeload, createAttributeEventListener(this, name, value)); - else + } else { HTMLElement::parseAttribute(name, value); + } } void HTMLMediaElement::finishParsingChildren() { HTMLElement::finishParsingChildren(); - m_parsingInProgress = false; - - if (!RuntimeEnabledFeatures::videoTrackEnabled()) - return; - for (Node* node = firstChild(); node; node = node->nextSibling()) { - if (node->hasTagName(trackTag)) { - scheduleDelayedAction(LoadTextTrackResource); - break; - } - } + if (Traversal<HTMLTrackElement>::firstChild(*this)) + scheduleDelayedAction(LoadTextTrackResource); } bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style) @@ -473,11 +539,6 @@ RenderObject* HTMLMediaElement::createRenderer(RenderStyle*) return new RenderMedia(this); } -bool HTMLMediaElement::childShouldCreateRenderer(const Node& child) const -{ - return hasMediaControls() && HTMLElement::childShouldCreateRenderer(child); -} - Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode* insertionPoint) { WTF_LOG(Media, "HTMLMediaElement::insertedInto"); @@ -490,8 +551,12 @@ Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode* scheduleDelayedAction(LoadMediaResource); } + return InsertionShouldCallDidNotifySubtreeInsertions; +} + +void HTMLMediaElement::didNotifySubtreeInsertionsToDocument() +{ configureMediaControls(); - return InsertionDone; } void HTMLMediaElement::removedFrom(ContainerNode* insertionPoint) @@ -499,7 +564,7 @@ void HTMLMediaElement::removedFrom(ContainerNode* insertionPoint) WTF_LOG(Media, "HTMLMediaElement::removedFrom"); m_active = false; - if (insertionPoint->inDocument()) { + if (insertionPoint->inDocument() && insertionPoint->document().isActive()) { configureMediaControls(); if (m_networkState > NETWORK_EMPTY) pause(); @@ -531,33 +596,36 @@ void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType) m_pendingActionFlags |= LoadMediaResource; } - if (RuntimeEnabledFeatures::videoTrackEnabled() && (actionType & LoadTextTrackResource)) + if (actionType & LoadTextTrackResource) m_pendingActionFlags |= LoadTextTrackResource; if (!m_loadTimer.isActive()) - m_loadTimer.startOneShot(0); + m_loadTimer.startOneShot(0, FROM_HERE); } void HTMLMediaElement::scheduleNextSourceChild() { // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad. m_pendingActionFlags |= LoadMediaResource; - m_loadTimer.startOneShot(0); + m_loadTimer.startOneShot(0, FROM_HERE); } void HTMLMediaElement::scheduleEvent(const AtomicString& eventName) { + scheduleEvent(Event::createCancelable(eventName)); +} + +void HTMLMediaElement::scheduleEvent(PassRefPtrWillBeRawPtr<Event> event) +{ #if LOG_MEDIA_EVENTS - WTF_LOG(Media, "HTMLMediaElement::scheduleEvent - scheduling '%s'", eventName.string().ascii().data()); + WTF_LOG(Media, "HTMLMediaElement::scheduleEvent - scheduling '%s'", event->type().ascii().data()); #endif - m_asyncEventQueue->enqueueEvent(Event::createCancelable(eventName)); + m_asyncEventQueue->enqueueEvent(event); } void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*) { - RefPtr<HTMLMediaElement> protect(this); // loadNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations. - - if (RuntimeEnabledFeatures::videoTrackEnabled() && (m_pendingActionFlags & LoadTextTrackResource)) + if (m_pendingActionFlags & LoadTextTrackResource) configureTextTracks(); if (m_pendingActionFlags & LoadMediaResource) { @@ -570,7 +638,7 @@ void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*) m_pendingActionFlags = 0; } -PassRefPtr<MediaError> HTMLMediaElement::error() const +PassRefPtrWillBeRawPtr<MediaError> HTMLMediaElement::error() const { return m_error; } @@ -585,8 +653,11 @@ 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 { + if (!keySystem.isNull()) + UseCounter::count(document(), UseCounter::CanPlayTypeKeySystem); + WebMimeRegistry::SupportsType support = supportsType(ContentType(mimeType), keySystem); String canPlay; @@ -604,26 +675,18 @@ String HTMLMediaElement::canPlayType(const String& mimeType, const String& keySy break; } - WTF_LOG(Media, "HTMLMediaElement::canPlayType(%s, %s, %s) -> %s", mimeType.utf8().data(), keySystem.utf8().data(), url.elidedString().utf8().data(), canPlay.utf8().data()); + WTF_LOG(Media, "HTMLMediaElement::canPlayType(%s, %s) -> %s", mimeType.utf8().data(), keySystem.utf8().data(), canPlay.utf8().data()); return canPlay; } void HTMLMediaElement::load() { - RefPtr<HTMLMediaElement> protect(this); // loadInternal may result in a 'beforeload' event, which can make arbitrary DOM mutations. - WTF_LOG(Media, "HTMLMediaElement::load()"); - if (document().settings() && !document().settings()->mediaEnabled()) - return; - - if (userGestureRequiredForLoad() && !UserGestureIndicator::processingUserGesture()) - return; + if (UserGestureIndicator::processingUserGesture()) + m_userGestureRequiredForPlay = false; - m_loadInitiatedByUserGesture = UserGestureIndicator::processingUserGesture(); - if (m_loadInitiatedByUserGesture) - removeBehaviorsRestrictionsAfterFirstUserGesture(); prepareForLoad(); loadInternal(); prepareToPlay(); @@ -636,6 +699,9 @@ void HTMLMediaElement::prepareForLoad() // Perform the cleanup required for the resource load algorithm to run. stopPeriodicTimers(); m_loadTimer.stop(); + cancelDeferredLoad(); + // 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; @@ -645,7 +711,7 @@ void HTMLMediaElement::prepareForLoad() // 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. @@ -656,30 +722,53 @@ void HTMLMediaElement::prepareForLoad() if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE) scheduleEvent(EventTypeNames::abort); - closeMediaSource(); - createMediaPlayer(); // 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(EventTypeNames::emptied); + + // 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; + + // 4.6 - If seeking is true, set it to false. m_seeking = false; + + // 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. + + // 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(EventTypeNames::emptied); + + // 4.9 - Set the timeline offset to Not-a-Number (NaN). + // 4.10 - Update the duration attribute to Not-a-Number (NaN). + + updateMediaController(); - if (RuntimeEnabledFeatures::videoTrackEnabled()) - updateActiveTextTrackCues(0); + updateActiveTextTrackCues(0); } // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute. setPlaybackRate(defaultPlaybackRate()); // 6 - Set the error attribute to null and the autoplaying flag to true. - m_error = 0; + m_error = nullptr; m_autoplaying = true; // 7 - Invoke the media element's resource selection algorithm. @@ -693,8 +782,11 @@ void HTMLMediaElement::prepareForLoad() // 2 - Asynchronously await a stable state. m_playedTimeRanges = TimeRanges::create(); + + // 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 = 0; - m_duration = numeric_limits<double>::quiet_NaN(); + m_duration = std::numeric_limits<double>::quiet_NaN(); // 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 @@ -706,26 +798,14 @@ void HTMLMediaElement::prepareForLoad() void HTMLMediaElement::loadInternal() { - // Some of the code paths below this function dispatch the BeforeLoad event. This ASSERT helps - // us catch those bugs more quickly without needing all the branches to align to actually - // trigger the event. - ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden()); - - // 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); - // 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::videoTrackEnabled()) { - m_textTracksWhenResourceSelectionBegan.clear(); - if (m_textTracks) { - for (unsigned i = 0; i < m_textTracks->length(); ++i) { - TextTrack* track = m_textTracks->item(i); - if (track->mode() != TextTrack::disabledKeyword()) - m_textTracksWhenResourceSelectionBegan.append(track); - } + m_textTracksWhenResourceSelectionBegan.clear(); + if (m_textTracks) { + for (unsigned i = 0; i < m_textTracks->length(); ++i) { + TextTrack* track = m_textTracks->item(i); + if (track->mode() != TextTrack::disabledKeyword()) + m_textTracksWhenResourceSelectionBegan.append(track); } } @@ -741,19 +821,13 @@ void HTMLMediaElement::selectMediaResource() // 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 (HTMLSourceElement* element = Traversal<HTMLSourceElement>::firstChild(*this)) { mode = children; - m_nextChildNodeToConsider = node; - m_currentSourceNode = 0; + m_nextChildNodeToConsider = element; + 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 @@ -787,7 +861,7 @@ void HTMLMediaElement::selectMediaResource() return; } - if (!isSafeToLoadURL(mediaURL, Complain) || !dispatchBeforeLoadEvent(mediaURL.string())) { + if (!isSafeToLoadURL(mediaURL, Complain)) { mediaLoadingFailed(MediaPlayer::FormatError); return; } @@ -828,7 +902,7 @@ void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType, c WTF_LOG(Media, "HTMLMediaElement::loadResource(%s, %s, %s)", urlForLoggingMedia(url).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data()); - Frame* frame = document().frame(); + LocalFrame* frame = document().frame(); if (!frame) { mediaLoadingFailed(MediaPlayer::FormatError); return; @@ -843,16 +917,13 @@ void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType, c WTF_LOG(Media, "HTMLMediaElement::loadResource - m_currentSrc -> %s", urlForLoggingMedia(m_currentSrc).utf8().data()); - if (MediaStreamRegistry::registry().lookupMediaStreamDescriptor(url.string())) - removeBehaviorRestriction(RequireUserGestureForRateChangeRestriction); - startProgressEventTimer(); // 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); + setPlayerPreload(); if (fastHasAttribute(mutedAttr)) m_muted = true; @@ -860,20 +931,34 @@ void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType, c ASSERT(!m_mediaSource); - if (url.protocolIs(mediaSourceBlobProtocol)) - m_mediaSource = HTMLMediaSource::lookup(url.string()); + bool attemptLoad = true; - if (m_mediaSource) { - if (m_mediaSource->attachToElement(this)) { - m_player->load(url, m_mediaSource); + if (url.protocolIs(mediaSourceBlobProtocol)) { + if (isMediaStreamURL(url.string())) { + m_userGestureRequiredForPlay = false; } else { - // Forget our reference to the MediaSource, so we leave it alone - // while processing remainder of load failure. - m_mediaSource = 0; - mediaLoadingFailed(MediaPlayer::FormatError); + m_mediaSource = HTMLMediaSource::lookup(url.string()); + + if (m_mediaSource) { + if (!m_mediaSource->attachToElement(this)) { + // Forget our reference to the MediaSource, so we leave it alone + // while processing remainder of load failure. + m_mediaSource = nullptr; + attemptLoad = false; + } + } + } + } + + if (attemptLoad && canLoadURL(url, contentType, keySystem)) { + ASSERT(!webMediaPlayer()); + + if (!m_havePreparedToPlay && !autoplay() && m_preload == MediaPlayer::None) { + WTF_LOG(Media, "HTMLMediaElement::loadResource : Delaying load because preload == 'none'"); + deferLoad(); + } else { + startPlayerLoad(); } - } else if (canLoadURL(url, contentType, keySystem)) { - m_player->load(url); } else { mediaLoadingFailed(MediaPlayer::FormatError); } @@ -886,6 +971,118 @@ void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType, c renderer()->updateFromElement(); } +void HTMLMediaElement::startPlayerLoad() +{ + // Filter out user:pass as those two URL components aren't + // considered for media resource fetches (including for the CORS + // use-credentials mode.) That behavior aligns with Gecko, with IE + // being more restrictive and not allowing fetches to such URLs. + // + // Spec reference: http://whatwg.org/c/#concept-media-load-resource + // + // FIXME: when the HTML spec switches to specifying resource + // fetches in terms of Fetch (http://fetch.spec.whatwg.org), and + // along with that potentially also specifying a setting for its + // 'authentication flag' to control how user:pass embedded in a + // media resource URL should be treated, then update the handling + // here to match. + KURL requestURL = m_currentSrc; + if (!requestURL.user().isEmpty()) + requestURL.setUser(String()); + if (!requestURL.pass().isEmpty()) + requestURL.setPass(String()); + + m_player->load(loadType(), requestURL, corsMode()); +} + +void HTMLMediaElement::setPlayerPreload() +{ + m_player->setPreload(m_preload); + + if (loadIsDeferred() && m_preload != MediaPlayer::None) + startDeferredLoad(); +} + +bool HTMLMediaElement::loadIsDeferred() const +{ + return m_deferredLoadState != NotDeferred; +} + +void HTMLMediaElement::deferLoad() +{ + // This implements the "optional" step 3 from the resource fetch algorithm. + ASSERT(!m_deferredLoadTimer.isActive()); + ASSERT(m_deferredLoadState == NotDeferred); + // 1. Set the networkState to NETWORK_IDLE. + // 2. Queue a task to fire a simple event named suspend at the element. + changeNetworkStateFromLoadingToIdle(); + // 3. Queue a task to set the element's delaying-the-load-event + // flag to false. This stops delaying the load event. + m_deferredLoadTimer.startOneShot(0, FROM_HERE); + // 4. Wait for the task to be run. + m_deferredLoadState = WaitingForStopDelayingLoadEventTask; + // Continued in executeDeferredLoad(). +} + +void HTMLMediaElement::cancelDeferredLoad() +{ + m_deferredLoadTimer.stop(); + m_deferredLoadState = NotDeferred; +} + +void HTMLMediaElement::executeDeferredLoad() +{ + ASSERT(m_deferredLoadState >= WaitingForTrigger); + + // resource fetch algorithm step 3 - continued from deferLoad(). + + // 5. Wait for an implementation-defined event (e.g. the user requesting that the media element begin playback). + // This is assumed to be whatever 'event' ended up calling this method. + cancelDeferredLoad(); + // 6. Set the element's delaying-the-load-event flag back to true (this + // delays the load event again, in case it hasn't been fired yet). + setShouldDelayLoadEvent(true); + // 7. Set the networkState to NETWORK_LOADING. + m_networkState = NETWORK_LOADING; + + startProgressEventTimer(); + + startPlayerLoad(); +} + +void HTMLMediaElement::startDeferredLoad() +{ + if (m_deferredLoadState == WaitingForTrigger) { + executeDeferredLoad(); + return; + } + ASSERT(m_deferredLoadState == WaitingForStopDelayingLoadEventTask); + m_deferredLoadState = ExecuteOnStopDelayingLoadEventTask; +} + +void HTMLMediaElement::deferredLoadTimerFired(Timer<HTMLMediaElement>*) +{ + setShouldDelayLoadEvent(false); + + if (m_deferredLoadState == ExecuteOnStopDelayingLoadEventTask) { + executeDeferredLoad(); + return; + } + ASSERT(m_deferredLoadState == WaitingForStopDelayingLoadEventTask); + m_deferredLoadState = WaitingForTrigger; +} + +WebMediaPlayer::LoadType HTMLMediaElement::loadType() const +{ + if (m_mediaSource) + return WebMediaPlayer::LoadTypeMediaSource; + + if (isMediaStreamURL(m_currentSrc.string())) + return WebMediaPlayer::LoadTypeMediaStream; + + return WebMediaPlayer::LoadTypeURL; +} + static bool trackIndexCompare(TextTrack* a, TextTrack* b) { @@ -923,8 +1120,6 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) if (ignoreTrackDisplayUpdateRequests()) return; - WTF_LOG(Media, "HTMLMediaElement::updateActiveTextTrackCues"); - // 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 // media element (not the disabled ones) whose start times are less than or @@ -965,7 +1160,7 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) double 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]); } } @@ -1079,7 +1274,7 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) affectedTracks.append(eventTasks[i].second->track()); // 13 - Queue each task in events, in list order. - RefPtr<Event> event; + RefPtrWillBeRawPtr<Event> event = nullptr; // Each event in eventTasks may be either an enterEvent or an exitEvent, // depending on the time that is associated with the event. This @@ -1111,7 +1306,7 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) // 15 - For each text track in affected tracks, in the list order, queue a // task to fire a simple event named cuechange at the TextTrack object, and, ... for (size_t i = 0; i < affectedTracks.size(); ++i) { - RefPtr<Event> event = Event::create(EventTypeNames::cuechange); + RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::cuechange); event->setTarget(affectedTracks[i]); m_asyncEventQueue->enqueueEvent(event.release()); @@ -1119,7 +1314,7 @@ void HTMLMediaElement::updateActiveTextTrackCues(double movieTime) // ... if the text track has a corresponding track element, to then fire a // simple event named cuechange at the track element as well. if (affectedTracks[i]->trackType() == TextTrack::TrackElement) { - RefPtr<Event> event = Event::create(EventTypeNames::cuechange); + RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::cuechange); HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(affectedTracks[i])->trackElement(); ASSERT(trackElement); event->setTarget(trackElement); @@ -1163,9 +1358,9 @@ bool HTMLMediaElement::textTracksAreReady() const void HTMLMediaElement::textTrackReadyStateChanged(TextTrack* track) { - if (m_player && m_textTracksWhenResourceSelectionBegan.contains(track)) { + if (webMediaPlayer()&& m_textTracksWhenResourceSelectionBegan.contains(track)) { if (track->readinessState() != TextTrack::Loading) - setReadyState(m_player->readyState()); + setReadyState(static_cast<ReadyState>(webMediaPlayer()->readyState())); } else { // The track readiness state might have changed as a result of the user // clicking the captions button. In this case, a check whether all the @@ -1184,10 +1379,7 @@ void HTMLMediaElement::textTrackModeChanged(TextTrack* track) // or showing by default for the first time, the user agent must immediately and synchronously // run the following algorithm ... - for (Node* node = firstChild(); node; node = node->nextSibling()) { - if (!node->hasTagName(trackTag)) - continue; - HTMLTrackElement* trackElement = toHTMLTrackElement(node); + for (HTMLTrackElement* trackElement = Traversal<HTMLTrackElement>::firstChild(*this); trackElement; trackElement = Traversal<HTMLTrackElement>::nextSibling(*trackElement)) { if (trackElement->track() != track) continue; @@ -1203,8 +1395,9 @@ void HTMLMediaElement::textTrackModeChanged(TextTrack* track) } break; } - } else if (track->trackType() == TextTrack::AddTrack && track->mode() != TextTrack::disabledKeyword()) + } else if (track->trackType() == TextTrack::AddTrack && track->mode() != TextTrack::disabledKeyword()) { textTrackAddCues(track, track->cues()); + } configureTextTrackDisplay(AssumeVisibleChange); @@ -1251,14 +1444,14 @@ 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, PassRefPtrWillBeRawPtr<TextTrackCue> cue) { if (track->mode() == TextTrack::disabledKeyword()) return; // Negative duration cues need be treated in the interval tree as // zero-length cues. - double endTime = max(cue->startTime(), cue->endTime()); + double endTime = std::max(cue->startTime(), cue->endTime()); CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get()); if (!m_cueTree.contains(interval)) @@ -1266,11 +1459,11 @@ void HTMLMediaElement::textTrackAddCue(TextTrack* track, PassRefPtr<TextTrackCue updateActiveTextTrackCues(currentTime()); } -void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtr<TextTrackCue> cue) +void HTMLMediaElement::textTrackRemoveCue(TextTrack*, PassRefPtrWillBeRawPtr<TextTrackCue> cue) { // Negative duration cues need to be treated in the interval tree as // zero-length cues. - double endTime = max(cue->startTime(), cue->endTime()); + double endTime = std::max(cue->startTime(), cue->endTime()); CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, cue.get()); m_cueTree.remove(interval); @@ -1299,7 +1492,7 @@ bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidURLAction actionI return false; } - Frame* frame = document().frame(); + LocalFrame* frame = document().frame(); if (!frame || !document().securityOrigin()->canDisplay(url)) { if (actionIfInvalid == Complain) FrameLoader::reportLocalLoadFailed(frame, url.elidedString()); @@ -1322,7 +1515,7 @@ void HTMLMediaElement::startProgressEventTimer() m_previousProgressTime = WTF::currentTime(); // 350ms is not magic, it is in the spec! - m_progressEventTimer.startRepeating(0.350); + m_progressEventTimer.startRepeating(0.350, FROM_HERE); } void HTMLMediaElement::waitForSourceChange() @@ -1350,7 +1543,7 @@ void HTMLMediaElement::noneSupported() 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 @@ -1361,6 +1554,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; @@ -1382,7 +1576,7 @@ void HTMLMediaElement::noneSupported() renderer()->updateFromElement(); } -void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err) +void HTMLMediaElement::mediaEngineError(PassRefPtrWillBeRawPtr<MediaError> err) { WTF_LOG(Media, "HTMLMediaElement::mediaEngineError(%d)", static_cast<int>(err->code())); @@ -1408,7 +1602,7 @@ void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err) setShouldDelayLoadEvent(false); // 6 - Abort the overall resource selection algorithm. - m_currentSourceNode = 0; + m_currentSourceNode = nullptr; } void HTMLMediaElement::cancelPendingEventsAndCallbacks() @@ -1416,10 +1610,8 @@ void HTMLMediaElement::cancelPendingEventsAndCallbacks() WTF_LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks"); m_asyncEventQueue->cancelAllEvents(); - for (Node* node = firstChild(); node; node = node->nextSibling()) { - if (node->hasTagName(sourceTag)) - toHTMLSourceElement(node)->cancelPendingErrorEvent(); - } + for (HTMLSourceElement* source = Traversal<HTMLSourceElement>::firstChild(*this); source; source = Traversal<HTMLSourceElement>::nextSibling(*source)) + source->cancelPendingErrorEvent(); } void HTMLMediaElement::mediaPlayerNetworkStateChanged() @@ -1435,11 +1627,18 @@ 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 WTF_LOG(Media, "HTMLMediaElement::setNetworkState - error event not sent, <source> was removed"); + // 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()) { WTF_LOG(Media, "HTMLMediaElement::setNetworkState - scheduling next <source>"); scheduleNextSourceChild(); @@ -1459,10 +1658,8 @@ void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error) noneSupported(); updateDisplayState(); - if (hasMediaControls()) { + if (hasMediaControls()) mediaControls()->reset(); - mediaControls()->reportedError(); - } } void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state) @@ -1500,30 +1697,27 @@ void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state) changeNetworkStateFromLoadingToIdle(); m_completelyLoaded = true; } - - if (hasMediaControls()) - mediaControls()->updateStatusDisplay(); } void HTMLMediaElement::changeNetworkStateFromLoadingToIdle() { + ASSERT(m_player); m_progressEventTimer.stop(); - if (hasMediaControls() && m_player->didLoadingProgress()) - mediaControls()->bufferingProgressed(); // Schedule one last progress event so we guarantee that at least one is fired // for files that load very quickly. - scheduleEvent(EventTypeNames::progress); + if (m_player->didLoadingProgress()) + scheduleEvent(EventTypeNames::progress); scheduleEvent(EventTypeNames::suspend); m_networkState = NETWORK_IDLE; } void HTMLMediaElement::mediaPlayerReadyStateChanged() { - setReadyState(m_player->readyState()); + setReadyState(static_cast<ReadyState>(webMediaPlayer()->readyState())); } -void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) +void HTMLMediaElement::setReadyState(ReadyState state) { WTF_LOG(Media, "HTMLMediaElement::setReadyState(%d) - current state is %d,", static_cast<int>(state), static_cast<int>(m_readyState)); @@ -1531,9 +1725,9 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) bool wasPotentiallyPlaying = potentiallyPlaying(); ReadyState oldState = m_readyState; - ReadyState newState = static_cast<ReadyState>(state); + ReadyState newState = state; - bool tracksAreReady = !RuntimeEnabledFeatures::videoTrackEnabled() || textTracksAreReady(); + bool tracksAreReady = textTracksAreReady(); if (newState == oldState && m_tracksAreReady == tracksAreReady) return; @@ -1576,11 +1770,20 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) } if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) { + createPlaceholderTracksIfNecessary(); + prepareMediaFragmentURI(); + + selectInitialTracksIfNecessary(); + + m_duration = duration(); scheduleEvent(EventTypeNames::durationchange); + + if (isHTMLVideoElement(*this)) + scheduleEvent(EventTypeNames::resize); scheduleEvent(EventTypeNames::loadedmetadata); if (hasMediaControls()) - mediaControls()->loadedMetadata(); + mediaControls()->reset(); if (renderer()) renderer()->updateFromElement(); } @@ -1604,154 +1807,33 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) } if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA && tracksAreReady) { - if (oldState <= HAVE_CURRENT_DATA) + if (oldState <= HAVE_CURRENT_DATA) { scheduleEvent(EventTypeNames::canplay); + if (isPotentiallyPlaying) + scheduleEvent(EventTypeNames::playing); + } - scheduleEvent(EventTypeNames::canplaythrough); - - if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA) - scheduleEvent(EventTypeNames::playing); - - if (m_autoplaying && m_paused && autoplay() && !document().isSandboxed(SandboxAutomaticFeatures) && !userGestureRequiredForRateChange()) { + if (m_autoplaying && m_paused && autoplay() && !document().isSandboxed(SandboxAutomaticFeatures) && !m_userGestureRequiredForPlay) { m_paused = false; invalidateCachedTime(); scheduleEvent(EventTypeNames::play); scheduleEvent(EventTypeNames::playing); } + scheduleEvent(EventTypeNames::canplaythrough); + shouldUpdateDisplayState = true; } if (shouldUpdateDisplayState) { updateDisplayState(); - if (hasMediaControls()) { + if (hasMediaControls()) mediaControls()->refreshClosedCaptionsButtonVisibility(); - mediaControls()->updateStatusDisplay(); - } } updatePlayState(); updateMediaController(); - if (RuntimeEnabledFeatures::videoTrackEnabled()) - updateActiveTextTrackCues(currentTime()); -} - -void HTMLMediaElement::mediaPlayerKeyAdded(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(EventTypeNames::webkitkeyadded, initializer); - event->setTarget(this); - m_asyncEventQueue->enqueueEvent(event.release()); -} - -void HTMLMediaElement::mediaPlayerKeyError(const String& keySystem, const String& sessionId, MediaPlayerClient::MediaKeyErrorCode errorCode, unsigned short systemCode) -{ - MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN; - switch (errorCode) { - case MediaPlayerClient::UnknownError: - mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN; - break; - case MediaPlayerClient::ClientError: - mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT; - break; - case MediaPlayerClient::ServiceError: - mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_SERVICE; - break; - case MediaPlayerClient::OutputError: - mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_OUTPUT; - break; - case MediaPlayerClient::HardwareChangeError: - mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_HARDWARECHANGE; - break; - case MediaPlayerClient::DomainError: - mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_DOMAIN; - break; - } - - 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(EventTypeNames::webkitkeyerror, initializer); - event->setTarget(this); - m_asyncEventQueue->enqueueEvent(event.release()); -} - -void HTMLMediaElement::mediaPlayerKeyMessage(const String& keySystem, const String& sessionId, const unsigned char* message, unsigned messageLength, const KURL& 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(EventTypeNames::webkitkeymessage, initializer); - event->setTarget(this); - m_asyncEventQueue->enqueueEvent(event.release()); -} - -bool HTMLMediaElement::mediaPlayerKeyNeeded(const String& keySystem, const String& sessionId, const unsigned char* initData, unsigned initDataLength) -{ - if (!hasEventListeners(EventTypeNames::webkitneedkey)) { - m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED); - scheduleEvent(EventTypeNames::error); - 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(EventTypeNames::webkitneedkey, initializer); - event->setTarget(this); - m_asyncEventQueue->enqueueEvent(event.release()); - return true; -} - -bool HTMLMediaElement::mediaPlayerKeyNeeded(Uint8Array* initData) -{ - if (!hasEventListeners("webkitneedkey")) { - m_error = MediaError::create(MediaError::MEDIA_ERR_ENCRYPTED); - scheduleEvent(EventTypeNames::error); - return false; - } - - MediaKeyNeededEventInit initializer; - initializer.initData = initData; - initializer.bubbles = false; - initializer.cancelable = false; - - RefPtr<Event> event = MediaKeyNeededEvent::create(EventTypeNames::webkitneedkey, initializer); - event->setTarget(this); - m_asyncEventQueue->enqueueEvent(event.release()); - - return true; -} - -void HTMLMediaElement::setMediaKeys(MediaKeys* mediaKeys) -{ - if (m_mediaKeys == mediaKeys) - return; - - if (m_mediaKeys) - m_mediaKeys->setMediaElement(0); - m_mediaKeys = mediaKeys; - if (m_mediaKeys) - m_mediaKeys->setMediaElement(this); + updateActiveTextTrackCues(currentTime()); } void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*) @@ -1769,8 +1851,6 @@ void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*) m_sentStalledEvent = false; if (renderer()) renderer()->updateFromElement(); - if (hasMediaControls()) - mediaControls()->bufferingProgressed(); } else if (timedelta > 3.0 && !m_sentStalledEvent) { scheduleEvent(EventTypeNames::stalled); m_sentStalledEvent = true; @@ -1797,7 +1877,9 @@ void HTMLMediaElement::prepareToPlay() if (m_havePreparedToPlay) return; m_havePreparedToPlay = true; - m_player->prepareToPlay(); + + if (loadIsDeferred()) + startDeferredLoad(); } void HTMLMediaElement::seek(double time, ExceptionState& exceptionState) @@ -1808,7 +1890,7 @@ void HTMLMediaElement::seek(double time, ExceptionState& exceptionState) // 1 - If the media element's readyState is HAVE_NOTHING, then raise an InvalidStateError exception. if (m_readyState == HAVE_NOTHING || !m_player) { - exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); + exceptionState.throwDOMException(InvalidStateError, "The element's readyState is HAVE_NOTHING."); return; } @@ -1827,14 +1909,15 @@ void HTMLMediaElement::seek(double time, ExceptionState& exceptionState) // 3 - Set the seeking IDL attribute to true. // The flag will be cleared when the engine tells us the time has actually changed. + bool previousSeekStillPending = m_seeking; m_seeking = true; // 5 - 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, duration()); // 6 - If the new playback position is less than the earliest possible position, let it be that position instead. - time = max(time, 0.0); + time = std::max(time, 0.0); // 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 @@ -1859,14 +1942,11 @@ void HTMLMediaElement::seek(double time, ExceptionState& exceptionState) // cancel poster display. bool noSeekRequired = !seekableRanges->length() || (time == now && displayMode() != Poster); - // Always notify the media engine of a seek if the source is not closed. This ensures that the source is - // always in a flushed state when the 'seeking' event fires. - if (m_mediaSource && m_mediaSource->isClosed()) - noSeekRequired = false; - if (noSeekRequired) { if (time == now) { scheduleEvent(EventTypeNames::seeking); + if (previousSeekStillPending) + return; // FIXME: There must be a stable state before timeupdate+seeked are dispatched and seeking // is reset to false. See http://crbug.com/266631 scheduleTimeupdateEvent(false); @@ -1918,7 +1998,7 @@ HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const bool HTMLMediaElement::hasAudio() const { - return m_player ? m_player->hasAudio() : false; + return webMediaPlayer() && webMediaPlayer()->hasAudio(); } bool HTMLMediaElement::seeking() const @@ -1977,7 +2057,7 @@ double HTMLMediaElement::currentTime() const void HTMLMediaElement::setCurrentTime(double time, ExceptionState& exceptionState) { if (m_mediaController) { - exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); + exceptionState.throwDOMException(InvalidStateError, "The element is slaved to a MediaController."); return; } seek(time, exceptionState); @@ -1986,12 +2066,12 @@ void HTMLMediaElement::setCurrentTime(double time, ExceptionState& exceptionStat double HTMLMediaElement::duration() const { if (!m_player || m_readyState < HAVE_METADATA) - return numeric_limits<double>::quiet_NaN(); + return std::numeric_limits<double>::quiet_NaN(); // FIXME: Refactor so m_duration is kept current (in both MSE and // non-MSE cases) once we have transitioned from HAVE_NOTHING -> // HAVE_METADATA. Currently, m_duration may be out of date for at least MSE - // case because MediaSourceBase and SourceBuffer do not notify the element + // case because MediaSource and SourceBuffer do not notify the element // directly upon duration changes caused by endOfStream, remove, or append // operations; rather the notification is triggered by the WebMediaPlayer // implementation observing that the underlying engine has updated duration @@ -2016,10 +2096,11 @@ double HTMLMediaElement::defaultPlaybackRate() const void HTMLMediaElement::setDefaultPlaybackRate(double rate) { - if (m_defaultPlaybackRate != rate) { - m_defaultPlaybackRate = rate; - scheduleEvent(EventTypeNames::ratechange); - } + if (m_defaultPlaybackRate == rate) + return; + + m_defaultPlaybackRate = rate; + scheduleEvent(EventTypeNames::ratechange); } double HTMLMediaElement::playbackRate() const @@ -2037,13 +2118,22 @@ void HTMLMediaElement::setPlaybackRate(double rate) scheduleEvent(EventTypeNames::ratechange); } - if (m_player && potentiallyPlaying() && m_player->rate() != rate && !m_mediaController) - m_player->setRate(rate); + updatePlaybackRate(); +} + +double HTMLMediaElement::effectivePlaybackRate() const +{ + return m_mediaController ? m_mediaController->playbackRate() : m_playbackRate; +} + +HTMLMediaElement::DirectionOfPlayback HTMLMediaElement::directionOfPlayback() const +{ + return m_playbackRate >= 0 ? Forward : Backward; } void HTMLMediaElement::updatePlaybackRate() { - double effectiveRate = m_mediaController ? m_mediaController->playbackRate() : m_playbackRate; + double effectiveRate = effectivePlaybackRate(); if (m_player && potentiallyPlaying() && m_player->rate() != effectiveRate) m_player->setRate(effectiveRate); } @@ -2053,7 +2143,7 @@ bool HTMLMediaElement::ended() const // 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() && directionOfPlayback() == Forward; } bool HTMLMediaElement::autoplay() const @@ -2079,7 +2169,7 @@ String HTMLMediaElement::preload() const return String(); } -void HTMLMediaElement::setPreload(const String& preload) +void HTMLMediaElement::setPreload(const AtomicString& preload) { WTF_LOG(Media, "HTMLMediaElement::setPreload(%s)", preload.utf8().data()); setAttribute(preloadAttr, preload); @@ -2089,10 +2179,10 @@ void HTMLMediaElement::play() { WTF_LOG(Media, "HTMLMediaElement::play()"); - if (userGestureRequiredForRateChange() && !UserGestureIndicator::processingUserGesture()) + if (m_userGestureRequiredForPlay && !UserGestureIndicator::processingUserGesture()) return; if (UserGestureIndicator::processingUserGesture()) - removeBehaviorsRestrictionsAfterFirstUserGesture(); + m_userGestureRequiredForPlay = false; playInternal(); } @@ -2131,18 +2221,6 @@ void HTMLMediaElement::pause() { WTF_LOG(Media, "HTMLMediaElement::pause()"); - if (userGestureRequiredForRateChange() && !UserGestureIndicator::processingUserGesture()) - return; - - pauseInternal(); -} - - -void HTMLMediaElement::pauseInternal() -{ - WTF_LOG(Media, "HTMLMediaElement::pauseInternal"); - - // 4.8.10.9. Playing the media resource if (!m_player || m_networkState == NETWORK_EMPTY) scheduleDelayedAction(LoadMediaResource); @@ -2163,89 +2241,7 @@ void HTMLMediaElement::closeMediaSource() return; m_mediaSource->close(); - m_mediaSource = 0; -} - -void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, PassRefPtr<Uint8Array> initData, ExceptionState& exceptionState) -{ - if (keySystem.isEmpty()) { - exceptionState.throwUninformativeAndGenericDOMException(SyntaxError); - return; - } - - if (!m_player) { - exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); - return; - } - - const unsigned char* initDataPointer = 0; - unsigned initDataLength = 0; - if (initData) { - initDataPointer = initData->data(); - initDataLength = initData->length(); - } - - MediaPlayer::MediaKeyException result = m_player->generateKeyRequest(keySystem, initDataPointer, initDataLength); - throwExceptionForMediaKeyException(result, exceptionState); -} - -void HTMLMediaElement::webkitGenerateKeyRequest(const String& keySystem, ExceptionState& exceptionState) -{ - webkitGenerateKeyRequest(keySystem, Uint8Array::create(0), exceptionState); -} - -void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, PassRefPtr<Uint8Array> initData, const String& sessionId, ExceptionState& exceptionState) -{ - if (keySystem.isEmpty()) { - exceptionState.throwUninformativeAndGenericDOMException(SyntaxError); - return; - } - - if (!key) { - exceptionState.throwUninformativeAndGenericDOMException(SyntaxError); - return; - } - - if (!key->length()) { - exceptionState.throwUninformativeAndGenericDOMException(TypeMismatchError); - return; - } - - if (!m_player) { - exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); - return; - } - - const unsigned char* initDataPointer = 0; - unsigned initDataLength = 0; - if (initData) { - initDataPointer = initData->data(); - initDataLength = initData->length(); - } - - MediaPlayer::MediaKeyException result = m_player->addKey(keySystem, key->data(), key->length(), initDataPointer, initDataLength, sessionId); - throwExceptionForMediaKeyException(result, exceptionState); -} - -void HTMLMediaElement::webkitAddKey(const String& keySystem, PassRefPtr<Uint8Array> key, ExceptionState& exceptionState) -{ - webkitAddKey(keySystem, key, Uint8Array::create(0), String(), exceptionState); -} - -void HTMLMediaElement::webkitCancelKeyRequest(const String& keySystem, const String& sessionId, ExceptionState& exceptionState) -{ - if (keySystem.isEmpty()) { - exceptionState.throwUninformativeAndGenericDOMException(SyntaxError); - return; - } - - if (!m_player) { - exceptionState.throwUninformativeAndGenericDOMException(InvalidStateError); - return; - } - - MediaPlayer::MediaKeyException result = m_player->cancelKeyRequest(keySystem, sessionId); - throwExceptionForMediaKeyException(result, exceptionState); + m_mediaSource = nullptr; } bool HTMLMediaElement::loop() const @@ -2261,7 +2257,7 @@ void HTMLMediaElement::setLoop(bool b) bool HTMLMediaElement::controls() const { - Frame* frame = document().frame(); + LocalFrame* frame = document().frame(); // always show controls when scripting is disabled if (frame && !frame->script().canExecuteScripts(NotAboutToExecuteScript)) @@ -2289,16 +2285,17 @@ void HTMLMediaElement::setVolume(double vol, ExceptionState& exceptionState) { WTF_LOG(Media, "HTMLMediaElement::setVolume(%f)", vol); + if (m_volume == vol) + return; + if (vol < 0.0f || vol > 1.0f) { - exceptionState.throwUninformativeAndGenericDOMException(IndexSizeError); + exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexOutsideRange("volume", vol, 0.0, ExceptionMessages::InclusiveBound, 1.0, ExceptionMessages::InclusiveBound)); return; } - if (m_volume != vol) { - m_volume = vol; - updateVolume(); - scheduleEvent(EventTypeNames::volumechange); - } + m_volume = vol; + updateVolume(); + scheduleEvent(EventTypeNames::volumechange); } bool HTMLMediaElement::muted() const @@ -2310,55 +2307,14 @@ void HTMLMediaElement::setMuted(bool muted) { WTF_LOG(Media, "HTMLMediaElement::setMuted(%s)", boolString(muted)); - if (m_muted != muted) { - m_muted = muted; - if (m_player) { - m_player->setMuted(m_muted); - if (hasMediaControls()) - mediaControls()->changedMute(); - } - scheduleEvent(EventTypeNames::volumechange); - } -} - -void HTMLMediaElement::togglePlayState() -{ - WTF_LOG(Media, "HTMLMediaElement::togglePlayState - canPlay() is %s", 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 - if (canPlay()) { - updatePlaybackRate(); - playInternal(); - } else - pauseInternal(); -} - -void HTMLMediaElement::beginScrubbing() -{ - WTF_LOG(Media, "HTMLMediaElement::beginScrubbing - paused() is %s", boolString(paused())); + if (m_muted == muted) + return; - if (!paused()) { - if (ended()) { - // Because a media element stays in non-paused state when it reaches end, playback resumes - // when the slider is dragged from the end to another position unless we pause first. Do - // a "hard pause" so an event is generated, since we want to stay paused after scrubbing finishes. - pause(); - } else { - // Not at the end but we still want to pause playback so the media engine doesn't try to - // continue playing during scrubbing. Pause without generating an event as we will - // unpause after scrubbing finishes. - setPausedInternal(true); - } - } -} + m_muted = muted; -void HTMLMediaElement::endScrubbing() -{ - WTF_LOG(Media, "HTMLMediaElement::endScrubbing - m_pausedInternal is %s", boolString(m_pausedInternal)); + updateVolume(); - if (m_pausedInternal) - setPausedInternal(false); + scheduleEvent(EventTypeNames::volumechange); } // The spec says to fire periodic timeupdate events (those sent while playing) every @@ -2371,32 +2327,32 @@ void HTMLMediaElement::startPlaybackProgressTimer() return; m_previousProgressTime = WTF::currentTime(); - m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency); + m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency, FROM_HERE); } void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*) { ASSERT(m_player); - if (m_fragmentEndTime != MediaPlayer::invalidTime() && currentTime() >= m_fragmentEndTime && m_playbackRate > 0) { + if (m_fragmentEndTime != MediaPlayer::invalidTime() && currentTime() >= m_fragmentEndTime && directionOfPlayback() == Forward) { m_fragmentEndTime = MediaPlayer::invalidTime(); if (!m_mediaController && !m_paused) { + UseCounter::count(document(), UseCounter::HTMLMediaElementPauseAtFragmentEnd); // changes paused to true and fires a simple event named pause at the media element. - pauseInternal(); + pause(); } } if (!m_seeking) scheduleTimeupdateEvent(true); - if (!m_playbackRate) + if (!effectivePlaybackRate()) return; if (!m_paused && hasMediaControls()) mediaControls()->playbackProgressed(); - if (RuntimeEnabledFeatures::videoTrackEnabled()) - updateActiveTextTrackCues(currentTime()); + updateActiveTextTrackCues(currentTime()); } void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent) @@ -2418,38 +2374,139 @@ void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent) } } -bool HTMLMediaElement::canPlay() const +bool HTMLMediaElement::togglePlayStateWillPlay() const { - return paused() || ended() || m_readyState < HAVE_METADATA; + if (m_mediaController) + return m_mediaController->paused() || m_mediaController->isRestrained(); + return paused(); } -double HTMLMediaElement::percentLoaded() const +void HTMLMediaElement::togglePlayState() { - if (!m_player) + if (m_mediaController) { + if (m_mediaController->isRestrained()) + m_mediaController->play(); + else if (m_mediaController->paused()) + m_mediaController->unpause(); + else + m_mediaController->pause(); + } else { + if (paused()) + play(); + else + pause(); + } +} + +AudioTrackList& HTMLMediaElement::audioTracks() +{ + ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled()); + return *m_audioTracks; +} + +void HTMLMediaElement::audioTrackChanged() +{ + WTF_LOG(Media, "HTMLMediaElement::audioTrackChanged()"); + ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled()); + + audioTracks().scheduleChangeEvent(); + + // FIXME: Add call on m_mediaSource to notify it of track changes once the SourceBuffer.audioTracks attribute is added. + + if (!m_audioTracksTimer.isActive()) + m_audioTracksTimer.startOneShot(0, FROM_HERE); +} + +void HTMLMediaElement::audioTracksTimerFired(Timer<HTMLMediaElement>*) +{ + Vector<WebMediaPlayer::TrackId> enabledTrackIds; + for (unsigned i = 0; i < audioTracks().length(); ++i) { + AudioTrack* track = audioTracks().anonymousIndexedGetter(i); + if (track->enabled()) + enabledTrackIds.append(track->trackId()); + } + + webMediaPlayer()->enabledAudioTracksChanged(enabledTrackIds); +} + +WebMediaPlayer::TrackId HTMLMediaElement::addAudioTrack(const String& id, blink::WebMediaPlayerClient::AudioTrackKind kind, const AtomicString& label, const AtomicString& language, bool enabled) +{ + AtomicString kindString = AudioKindToString(kind); + WTF_LOG(Media, "HTMLMediaElement::addAudioTrack('%s', '%s', '%s', '%s', %d)", + id.ascii().data(), kindString.ascii().data(), label.ascii().data(), language.ascii().data(), enabled); + + if (!RuntimeEnabledFeatures::audioVideoTracksEnabled()) return 0; - double duration = m_player->duration(); - if (!duration || std::isinf(duration)) + RefPtrWillBeRawPtr<AudioTrack> audioTrack = AudioTrack::create(id, kindString, label, language, enabled); + audioTracks().add(audioTrack); + + return audioTrack->trackId(); +} + +void HTMLMediaElement::removeAudioTrack(WebMediaPlayer::TrackId trackId) +{ + WTF_LOG(Media, "HTMLMediaElement::removeAudioTrack()"); + + if (!RuntimeEnabledFeatures::audioVideoTracksEnabled()) + return; + + audioTracks().remove(trackId); +} + +VideoTrackList& HTMLMediaElement::videoTracks() +{ + ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled()); + return *m_videoTracks; +} + +void HTMLMediaElement::selectedVideoTrackChanged(WebMediaPlayer::TrackId* selectedTrackId) +{ + WTF_LOG(Media, "HTMLMediaElement::selectedVideoTrackChanged()"); + ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled()); + + if (selectedTrackId) + videoTracks().trackSelected(*selectedTrackId); + + // FIXME: Add call on m_mediaSource to notify it of track changes once the SourceBuffer.videoTracks attribute is added. + + webMediaPlayer()->selectedVideoTrackChanged(selectedTrackId); +} + +WebMediaPlayer::TrackId HTMLMediaElement::addVideoTrack(const String& id, blink::WebMediaPlayerClient::VideoTrackKind kind, const AtomicString& label, const AtomicString& language, bool selected) +{ + AtomicString kindString = VideoKindToString(kind); + WTF_LOG(Media, "HTMLMediaElement::addVideoTrack('%s', '%s', '%s', '%s', %d)", + id.ascii().data(), kindString.ascii().data(), label.ascii().data(), language.ascii().data(), selected); + + if (!RuntimeEnabledFeatures::audioVideoTracksEnabled()) return 0; - double buffered = 0; - RefPtr<TimeRanges> 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); - buffered += end - start; - } - return buffered / duration; + // If another track was selected (potentially by the user), leave it selected. + if (selected && videoTracks().selectedIndex() != -1) + selected = false; + + RefPtrWillBeRawPtr<VideoTrack> videoTrack = VideoTrack::create(id, kindString, label, language, selected); + videoTracks().add(videoTrack); + + return videoTrack->trackId(); } -void HTMLMediaElement::mediaPlayerDidAddTrack(WebInbandTextTrack* webTrack) +void HTMLMediaElement::removeVideoTrack(WebMediaPlayer::TrackId trackId) { - if (!RuntimeEnabledFeatures::videoTrackEnabled()) + WTF_LOG(Media, "HTMLMediaElement::removeVideoTrack()"); + + if (!RuntimeEnabledFeatures::audioVideoTracksEnabled()) return; + videoTracks().remove(trackId); +} + +void HTMLMediaElement::mediaPlayerDidAddTextTrack(WebInbandTextTrack* webTrack) +{ // 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(document(), this, webTrack); + RefPtrWillBeRawPtr<InbandTextTrack> textTrack = InbandTextTrack::create(document(), webTrack); // 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 @@ -2474,24 +2531,21 @@ void HTMLMediaElement::mediaPlayerDidAddTrack(WebInbandTextTrack* webTrack) // 9. Fire an event with the name addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent // interface, with the track attribute initialized to the text track's TextTrack object, at the media element's // textTracks attribute's TextTrackList object. - addTrack(textTrack.get()); + addTextTrack(textTrack.get()); } -void HTMLMediaElement::mediaPlayerDidRemoveTrack(WebInbandTextTrack* webTrack) +void HTMLMediaElement::mediaPlayerDidRemoveTextTrack(WebInbandTextTrack* webTrack) { - if (!RuntimeEnabledFeatures::videoTrackEnabled()) - return; - if (!m_textTracks) return; // This cast is safe because we created the InbandTextTrack with the WebInbandTextTrack - // passed to mediaPlayerDidAddTrack. - RefPtr<InbandTextTrack> textTrack = static_cast<InbandTextTrack*>(webTrack->client()); + // passed to mediaPlayerDidAddTextTrack. + RefPtrWillBeRawPtr<InbandTextTrack> textTrack = static_cast<InbandTextTrack*>(webTrack->client()); if (!textTrack) return; - removeTrack(textTrack.get()); + removeTextTrack(textTrack.get()); } void HTMLMediaElement::closeCaptionTracksChanged() @@ -2500,49 +2554,47 @@ void HTMLMediaElement::closeCaptionTracksChanged() mediaControls()->closedCaptionTracksChanged(); } -void HTMLMediaElement::addTrack(TextTrack* track) +void HTMLMediaElement::addTextTrack(TextTrack* track) { textTracks()->append(track); closeCaptionTracksChanged(); } -void HTMLMediaElement::removeTrack(TextTrack* track) +void HTMLMediaElement::removeTextTrack(TextTrack* track) { TrackDisplayUpdateScope scope(this); - TextTrackCueList* cues = track->cues(); - if (cues) - textTrackRemoveCues(track, cues); m_textTracks->remove(track); closeCaptionTracksChanged(); } -void HTMLMediaElement::removeAllInbandTracks() +void HTMLMediaElement::forgetResourceSpecificTracks() { - if (!m_textTracks) - return; + // Implements the "forget the media element's media-resource-specific tracks" algorithm. + // The order is explicitly specified as text, then audio, and finally video. Also + // 'removetrack' events should not be fired. + if (m_textTracks) { + TrackDisplayUpdateScope scope(this); + m_textTracks->removeAllInbandTracks(); + closeCaptionTracksChanged(); + } - TrackDisplayUpdateScope scope(this); - for (int i = m_textTracks->length() - 1; i >= 0; --i) { - TextTrack* track = m_textTracks->item(i); + m_audioTracks->removeAll(); + m_videoTracks->removeAll(); - if (track->trackType() == TextTrack::InBand) - removeTrack(track); - } + m_audioTracksTimer.stop(); } -PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const String& kind, const String& label, const String& language, ExceptionState& exceptionState) +PassRefPtrWillBeRawPtr<TextTrack> HTMLMediaElement::addTextTrack(const AtomicString& kind, const AtomicString& label, const AtomicString& language, ExceptionState& exceptionState) { - ASSERT(RuntimeEnabledFeatures::videoTrackEnabled()); - // 4.8.10.12.4 Text track API // The addTextTrack(kind, label, language) method of media elements, when invoked, must run the following steps: // 1. If kind is not one of the following strings, then throw a SyntaxError exception and abort these steps if (!TextTrack::isValidKindKeyword(kind)) { - exceptionState.throwUninformativeAndGenericDOMException(SyntaxError); - return 0; + exceptionState.throwDOMException(SyntaxError, "The 'kind' provided ('" + kind + "') is invalid."); + return nullptr; } // 2. If the label argument was omitted, let label be the empty string. @@ -2551,13 +2603,13 @@ 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(document(), this, kind, label, language); + RefPtrWillBeRawPtr<TextTrack> textTrack = TextTrack::create(document(), kind, label, language); // Note, due to side effects when changing track parameters, we have to // first append the track to the text track list. // 6. Add the new text track to the media element's list of text tracks. - addTrack(textTrack.get()); + addTextTrack(textTrack.get()); // ... its text track readiness state to the text track loaded state ... textTrack->setReadinessState(TextTrack::Loaded); @@ -2570,55 +2622,41 @@ PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const String& kind, const S TextTrackList* HTMLMediaElement::textTracks() { - ASSERT(RuntimeEnabledFeatures::videoTrackEnabled()); - if (!m_textTracks) m_textTracks = TextTrackList::create(this); return m_textTracks.get(); } -void HTMLMediaElement::didAddTrack(HTMLTrackElement* trackElement) +void HTMLMediaElement::didAddTrackElement(HTMLTrackElement* trackElement) { - ASSERT(trackElement->hasTagName(trackTag)); - - if (!RuntimeEnabledFeatures::videoTrackEnabled()) - return; - // 4.8.10.12.3 Sourcing out-of-band text tracks // When a track element's parent element changes and the new parent is a media element, // then the user agent must add the track element's corresponding text track to the // media element's list of text tracks ... [continues in TextTrackList::append] - RefPtr<TextTrack> textTrack = trackElement->track(); + RefPtrWillBeRawPtr<TextTrack> textTrack = trackElement->track(); if (!textTrack) return; - addTrack(textTrack.get()); + addTextTrack(textTrack.get()); // Do not schedule the track loading until parsing finishes so we don't start before all tracks // in the markup have been added. - if (!m_parsingInProgress) + if (isFinishedParsingChildren()) scheduleDelayedAction(LoadTextTrackResource); if (hasMediaControls()) mediaControls()->closedCaptionTracksChanged(); } -void HTMLMediaElement::didRemoveTrack(HTMLTrackElement* trackElement) +void HTMLMediaElement::didRemoveTrackElement(HTMLTrackElement* trackElement) { - ASSERT(trackElement->hasTagName(trackTag)); - - if (!RuntimeEnabledFeatures::videoTrackEnabled()) - return; - #if !LOG_DISABLED - if (trackElement->hasTagName(trackTag)) { - KURL url = trackElement->getNonEmptyURLAttribute(srcAttr); - WTF_LOG(Media, "HTMLMediaElement::didRemoveTrack - 'src' is %s", urlForLoggingMedia(url).utf8().data()); - } + KURL url = trackElement->getNonEmptyURLAttribute(srcAttr); + WTF_LOG(Media, "HTMLMediaElement::didRemoveTrackElement - 'src' is %s", urlForLoggingMedia(url).utf8().data()); #endif - RefPtr<TextTrack> textTrack = trackElement->track(); + RefPtrWillBeRawPtr<TextTrack> textTrack = trackElement->track(); if (!textTrack) return; @@ -2631,7 +2669,7 @@ void HTMLMediaElement::didRemoveTrack(HTMLTrackElement* trackElement) // When a track element's parent element changes and the old parent was a media element, // then the user agent must remove the track element's corresponding text track from the // media element's list of text tracks. - removeTrack(textTrack.get()); + removeTextTrack(textTrack.get()); size_t index = m_textTracksWhenResourceSelectionBegan.find(textTrack.get()); if (index != kNotFound) @@ -2643,32 +2681,20 @@ static int textTrackLanguageSelectionScore(const TextTrack& track) if (track.language().isEmpty()) return 0; - Vector<String> languages = userPreferredLanguages(); + Vector<AtomicString> languages = userPreferredLanguages(); size_t languageMatchIndex = indexOfBestMatchingLanguageInList(track.language(), languages); if (languageMatchIndex >= languages.size()) return 0; - // Matching a track language is more important than matching track type, so this multiplier must be - // greater than the maximum value returned by textTrackSelectionScore. - return (languages.size() - languageMatchIndex) * 10; + return languages.size() - languageMatchIndex; } -static int textTrackSelectionScore(const TextTrack& track, Settings* settings) +static int textTrackSelectionScore(const TextTrack& track) { - int trackScore = 0; - - if (!settings) - return trackScore; - if (track.kind() != TextTrack::captionsKeyword() && track.kind() != TextTrack::subtitlesKeyword()) - return trackScore; - - if (track.kind() == TextTrack::subtitlesKeyword() && settings->shouldDisplaySubtitles()) - trackScore = 1; - else if (track.kind() == TextTrack::captionsKeyword() && settings->shouldDisplayCaptions()) - trackScore = 1; + return 0; - return trackScore + textTrackLanguageSelectionScore(track); + return textTrackLanguageSelectionScore(track); } void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group) @@ -2677,21 +2703,19 @@ void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group) WTF_LOG(Media, "HTMLMediaElement::configureTextTrackGroup(%d)", group.kind); - Settings* settings = document().settings(); - // First, find the track in the group that should be enabled (if any). - Vector<RefPtr<TextTrack> > currentlyEnabledTracks; - RefPtr<TextTrack> trackToEnable; - RefPtr<TextTrack> defaultTrack; - RefPtr<TextTrack> fallbackTrack; + WillBeHeapVector<RefPtrWillBeMember<TextTrack> > currentlyEnabledTracks; + RefPtrWillBeRawPtr<TextTrack> trackToEnable = nullptr; + RefPtrWillBeRawPtr<TextTrack> defaultTrack = nullptr; + RefPtrWillBeRawPtr<TextTrack> fallbackTrack = nullptr; int highestTrackScore = 0; for (size_t i = 0; i < group.tracks.size(); ++i) { - RefPtr<TextTrack> textTrack = group.tracks[i]; + RefPtrWillBeRawPtr<TextTrack> textTrack = group.tracks[i]; if (m_processingPreferenceChange && textTrack->mode() == TextTrack::showingKeyword()) currentlyEnabledTracks.append(textTrack); - int trackScore = textTrackSelectionScore(*textTrack, settings); + int trackScore = textTrackSelectionScore(*textTrack); if (trackScore) { // * If the text track kind is { [subtitles or captions] [descriptions] } and the user has indicated an interest in having a // track with this text track kind, text track language, and text track label enabled, and there is no @@ -2732,7 +2756,7 @@ void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group) if (currentlyEnabledTracks.size()) { for (size_t i = 0; i < currentlyEnabledTracks.size(); ++i) { - RefPtr<TextTrack> textTrack = currentlyEnabledTracks[i]; + RefPtrWillBeRawPtr<TextTrack> textTrack = currentlyEnabledTracks[i]; if (textTrack != trackToEnable) textTrack->setMode(TextTrack::disabledKeyword()); } @@ -2754,7 +2778,7 @@ void HTMLMediaElement::configureTextTracks() return; for (size_t i = 0; i < m_textTracks->length(); ++i) { - RefPtr<TextTrack> textTrack = m_textTracks->item(i); + RefPtrWillBeRawPtr<TextTrack> textTrack = m_textTracks->item(i); if (!textTrack) continue; @@ -2809,8 +2833,8 @@ bool HTMLMediaElement::havePotentialSourceChild() { // Stash the current <source> node and next nodes so we can restore them after checking // to see there is another potential. - RefPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode; - RefPtr<Node> nextNode = m_nextChildNodeToConsider; + RefPtrWillBeRawPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode; + RefPtrWillBeRawPtr<Node> nextNode = m_nextChildNodeToConsider; KURL nextURL = selectNextSourceChild(0, 0, DoNothing); @@ -2844,7 +2868,6 @@ KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* k String system; bool lookingForStartNode = m_nextChildNodeToConsider; bool canUseSourceElement = false; - bool okToLoadSourceURL; NodeVector potentialSourceNodes; getChildNodes(*this, potentialSourceNodes); @@ -2855,12 +2878,11 @@ KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* k continue; lookingForStartNode = false; - if (!node->hasTagName(sourceTag)) + if (!isHTMLSourceElement(*node)) continue; if (node->parentNode() != this) continue; - UseCounter::count(document(), UseCounter::SourceElementCandidate); source = toHTMLSourceElement(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 @@ -2872,19 +2894,6 @@ KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* k if (mediaURL.isEmpty()) goto check_again; - if (source->fastHasAttribute(mediaAttr)) { - MediaQueryEvaluator screenEval("screen", document().frame(), renderer() ? renderer()->style() : 0); - RefPtr<MediaQuerySet> media = MediaQuerySet::create(source->media()); -#if !LOG_DISABLED - if (shouldLog) - WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'media' is %s", source->media().string().utf8().data()); -#endif - if (!screenEval.eval(media.get())) { - UseCounter::count(document(), UseCounter::SourceElementNonMatchingMedia); - goto check_again; - } - } - type = source->type(); // FIXME(82965): Add support for keySystem in <source> and set system from source. if (type.isEmpty() && mediaURL.protocolIsData()) @@ -2899,16 +2908,7 @@ KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* k } // Is it safe to load this url? - 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) { - WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild : 'beforeload' removed current element"); - source = 0; - goto check_again; - } - - if (!okToLoadSourceURL) + if (!isSafeToLoadURL(mediaURL, actionIfInvalid)) goto check_again; // Making it this far means the <source> looks reasonable. @@ -2927,8 +2927,8 @@ 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 @@ -2943,10 +2943,8 @@ void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source) WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded(%p)", source); #if !LOG_DISABLED - if (source->hasTagName(sourceTag)) { - KURL url = source->getNonEmptyURLAttribute(srcAttr); - WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded - 'src' is %s", urlForLoggingMedia(url).utf8().data()); - } + KURL url = source->getNonEmptyURLAttribute(srcAttr); + WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded - 'src' is %s", urlForLoggingMedia(url).utf8().data()); #endif // We should only consider a <source> element when there is not src attribute at all. @@ -2991,10 +2989,8 @@ void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement* source) WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p)", source); #if !LOG_DISABLED - if (source->hasTagName(sourceTag)) { - KURL url = source->getNonEmptyURLAttribute(srcAttr); - WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved - 'src' is %s", urlForLoggingMedia(url).utf8().data()); - } + KURL url = source->getNonEmptyURLAttribute(srcAttr); + WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved - 'src' is %s", urlForLoggingMedia(url).utf8().data()); #endif if (source != m_currentSourceNode && source != m_nextChildNodeToConsider) @@ -3008,7 +3004,7 @@ void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement* source) // 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; + m_currentSourceNode = nullptr; WTF_LOG(Media, "HTMLMediaElement::sourceRemoved - m_currentSourceNode set to 0"); } } @@ -3017,8 +3013,7 @@ void HTMLMediaElement::mediaPlayerTimeChanged() { WTF_LOG(Media, "HTMLMediaElement::mediaPlayerTimeChanged"); - if (RuntimeEnabledFeatures::videoTrackEnabled()) - updateActiveTextTrackCues(currentTime()); + updateActiveTextTrackCues(currentTime()); invalidateCachedTime(); @@ -3036,7 +3031,7 @@ void HTMLMediaElement::mediaPlayerTimeChanged() // 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) { + if (!std::isnan(dur) && dur && now >= dur && directionOfPlayback() == Forward) { // If the media element has a loop attribute specified and does not have a current media controller, if (loop() && !m_mediaController) { m_sentEndEvent = false; @@ -3070,17 +3065,22 @@ void HTMLMediaElement::mediaPlayerTimeChanged() void HTMLMediaElement::mediaPlayerDurationChanged() { WTF_LOG(Media, "HTMLMediaElement::mediaPlayerDurationChanged"); - durationChanged(duration()); + // FIXME: Change MediaPlayerClient & WebMediaPlayer to convey + // the currentTime when the duration change occured. The current + // WebMediaPlayer implementations always clamp currentTime() to + // duration() so the requestSeek condition here is always false. + durationChanged(duration(), currentTime() > duration()); } -void HTMLMediaElement::durationChanged(double duration) +void HTMLMediaElement::durationChanged(double duration, bool requestSeek) { - WTF_LOG(Media, "HTMLMediaElement::durationChanged(%f)", duration); + WTF_LOG(Media, "HTMLMediaElement::durationChanged(%f, %d)", duration, requestSeek); // Abort if duration unchanged. if (m_duration == duration) return; + WTF_LOG(Media, "HTMLMediaElement::durationChanged : %f -> %f", m_duration, duration); m_duration = duration; scheduleEvent(EventTypeNames::durationchange); @@ -3089,7 +3089,7 @@ void HTMLMediaElement::durationChanged(double duration) if (renderer()) renderer()->updateFromElement(); - if (currentTime() > duration) + if (requestSeek) seek(duration, IGNORE_EXCEPTION); } @@ -3101,7 +3101,7 @@ void HTMLMediaElement::mediaPlayerPlaybackStateChanged() return; if (m_player->paused()) - pauseInternal(); + pause(); else playInternal(); } @@ -3109,6 +3109,11 @@ void HTMLMediaElement::mediaPlayerPlaybackStateChanged() void HTMLMediaElement::mediaPlayerRequestFullscreen() { WTF_LOG(Media, "HTMLMediaElement::mediaPlayerRequestFullscreen"); + + // The player is responsible for only invoking this callback in response to + // user interaction or when it is technically required to play the video. + UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture); + enterFullscreen(); } @@ -3130,14 +3135,15 @@ void HTMLMediaElement::mediaPlayerRepaint() updateDisplayState(); if (renderer()) - renderer()->repaint(); + renderer()->paintInvalidationForWholeRenderer(); } void HTMLMediaElement::mediaPlayerSizeChanged() { WTF_LOG(Media, "HTMLMediaElement::mediaPlayerSizeChanged"); - if (m_readyState > HAVE_NOTHING) + ASSERT(hasVideo()); // "resize" makes no sense absent video. + if (m_readyState > HAVE_NOTHING && isHTMLVideoElement(*this)) scheduleEvent(EventTypeNames::resize); if (renderer()) @@ -3190,7 +3196,7 @@ bool HTMLMediaElement::potentiallyPlaying() const bool HTMLMediaElement::couldPlayIfEnoughData() const { - return !paused() && !endedPlayback() && !stoppedDueToErrors() && !pausedForUserInteraction(); + return !paused() && !endedPlayback() && !stoppedDueToErrors(); } bool HTMLMediaElement::endedPlayback() const @@ -3210,15 +3216,13 @@ bool HTMLMediaElement::endedPlayback() const // 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) + if (directionOfPlayback() == Forward) return dur > 0 && 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; - - return false; + ASSERT(directionOfPlayback() == Backward); + return now <= 0; } bool HTMLMediaElement::stoppedDueToErrors() const @@ -3232,17 +3236,17 @@ bool HTMLMediaElement::stoppedDueToErrors() const return false; } -bool HTMLMediaElement::pausedForUserInteraction() const +void HTMLMediaElement::updateVolume() { -// return !paused() && m_readyState >= HAVE_FUTURE_DATA && [UA requires a decitions from the user] - return false; + if (webMediaPlayer()) + webMediaPlayer()->setVolume(playerVolume()); + + if (hasMediaControls()) + mediaControls()->updateVolume(); } -void HTMLMediaElement::updateVolume() +double HTMLMediaElement::playerVolume() const { - if (!m_player) - return; - double volumeMultiplier = 1; bool shouldMute = m_muted; @@ -3251,11 +3255,7 @@ void HTMLMediaElement::updateVolume() shouldMute = m_mediaController->muted(); } - m_player->setMuted(shouldMute); - m_player->setVolume(m_volume * volumeMultiplier); - - if (hasMediaControls()) - mediaControls()->changedVolume(); + return shouldMute ? 0 : m_volume * volumeMultiplier; } void HTMLMediaElement::updatePlayState() @@ -3286,8 +3286,8 @@ void HTMLMediaElement::updatePlayState() if (playerPaused) { // 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(effectivePlaybackRate()); + updateVolume(); m_player->play(); } @@ -3368,40 +3368,42 @@ 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 (RuntimeEnabledFeatures::videoTrackEnabled()) - updateActiveTextTrackCues(0); + updateActiveTextTrackCues(0); } -void HTMLMediaElement::clearMediaPlayerAndAudioSourceProviderClient() +void HTMLMediaElement::clearMediaPlayerAndAudioSourceProviderClientWithoutLocking() { #if ENABLE(WEB_AUDIO) - if (m_audioSourceNode) - m_audioSourceNode->lock(); - if (audioSourceProvider()) audioSourceProvider()->setClient(0); #endif - m_player.clear(); - -#if ENABLE(WEB_AUDIO) - if (m_audioSourceNode) - m_audioSourceNode->unlock(); -#endif } void HTMLMediaElement::clearMediaPlayer(int flags) { - removeAllInbandTracks(); + forgetResourceSpecificTracks(); closeMediaSource(); - clearMediaPlayerAndAudioSourceProviderClient(); + cancelDeferredLoad(); + +#if ENABLE(WEB_AUDIO) + if (m_audioSourceNode) + m_audioSourceNode->lock(); +#endif + + clearMediaPlayerAndAudioSourceProviderClientWithoutLocking(); + +#if ENABLE(WEB_AUDIO) + if (m_audioSourceNode) + m_audioSourceNode->unlock(); +#endif stopPeriodicTimers(); m_loadTimer.stop(); @@ -3440,8 +3442,12 @@ bool HTMLMediaElement::hasPendingActivity() const void HTMLMediaElement::contextDestroyed() { + // With Oilpan the ExecutionContext is weakly referenced from the media + // controller and so it will clear itself on destruction. +#if !ENABLE(OILPAN) if (m_mediaController) m_mediaController->clearExecutionContext(); +#endif ActiveDOMObject::contextDestroyed(); } @@ -3454,32 +3460,30 @@ void HTMLMediaElement::enterFullscreen() { WTF_LOG(Media, "HTMLMediaElement::enterFullscreen"); - if (document().settings() && document().settings()->fullScreenEnabled()) - FullscreenElementStack::from(&document())->requestFullScreenForElement(this, 0, FullscreenElementStack::ExemptIFrameAllowFullScreenRequirement); + FullscreenElementStack::from(document()).requestFullScreenForElement(this, 0, FullscreenElementStack::ExemptIFrameAllowFullScreenRequirement); } void HTMLMediaElement::exitFullscreen() { WTF_LOG(Media, "HTMLMediaElement::exitFullscreen"); - if (document().settings() && document().settings()->fullScreenEnabled() && isFullscreen()) - FullscreenElementStack::from(&document())->webkitCancelFullScreen(); + FullscreenElementStack::from(document()).webkitCancelFullScreen(); } void HTMLMediaElement::didBecomeFullscreenElement() { if (hasMediaControls()) mediaControls()->enteredFullscreen(); - if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isVideo()) - document().renderView()->compositor()->setCompositingLayersNeedRebuild(true); + if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isHTMLVideoElement(*this)) + document().renderView()->compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree); } void HTMLMediaElement::willStopBeingFullscreenElement() { if (hasMediaControls()) mediaControls()->exitedFullscreen(); - if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isVideo()) - document().renderView()->compositor()->setCompositingLayersNeedRebuild(true); + if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled() && isHTMLVideoElement(*this)) + document().renderView()->compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree); } blink::WebLayer* HTMLMediaElement::platformLayer() const @@ -3489,7 +3493,7 @@ blink::WebLayer* HTMLMediaElement::platformLayer() const bool HTMLMediaElement::hasClosedCaptions() const { - if (RuntimeEnabledFeatures::videoTrackEnabled() && m_textTracks) { + if (m_textTracks) { for (unsigned i = 0; i < m_textTracks->length(); ++i) { if (m_textTracks->item(i)->readinessState() == TextTrack::FailedToLoad) continue; @@ -3526,27 +3530,25 @@ void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible) m_closedCaptionsVisible = closedCaptionVisible; - if (RuntimeEnabledFeatures::videoTrackEnabled()) { - m_processingPreferenceChange = true; - markCaptionAndSubtitleTracksAsUnconfigured(); - m_processingPreferenceChange = false; + m_processingPreferenceChange = true; + markCaptionAndSubtitleTracksAsUnconfigured(); + m_processingPreferenceChange = false; - updateTextTrackDisplay(); - } + updateTextTrackDisplay(); } unsigned HTMLMediaElement::webkitAudioDecodedByteCount() const { - if (!m_player) + if (!webMediaPlayer()) return 0; - return m_player->audioDecodedByteCount(); + return webMediaPlayer()->audioDecodedByteCount(); } unsigned HTMLMediaElement::webkitVideoDecodedByteCount() const { - if (!m_player) + if (!webMediaPlayer()) return 0; - return m_player->videoDecodedByteCount(); + return webMediaPlayer()->videoDecodedByteCount(); } bool HTMLMediaElement::isURLAttribute(const Attribute& attribute) const @@ -3590,11 +3592,10 @@ bool HTMLMediaElement::createMediaControls() if (hasMediaControls()) return true; - RefPtr<MediaControls> mediaControls = MediaControls::create(document()); + RefPtrWillBeRawPtr<MediaControls> mediaControls = MediaControls::create(*this); if (!mediaControls) return false; - mediaControls->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this)); mediaControls->reset(); if (isFullscreen()) mediaControls->enteredFullscreen(); @@ -3618,6 +3619,7 @@ void HTMLMediaElement::configureMediaControls() if (!hasMediaControls() && !createMediaControls()) return; + mediaControls()->reset(); mediaControls()->show(); } @@ -3652,10 +3654,8 @@ void HTMLMediaElement::configureTextTrackDisplay(VisibilityChangeAssumption assu mediaControls()->changedClosedCaptionsVisibility(); - if (RuntimeEnabledFeatures::videoTrackEnabled()) { - updateActiveTextTrackCues(currentTime()); - updateTextTrackDisplay(); - } + updateActiveTextTrackCues(currentTime()); + updateTextTrackDisplay(); } void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured() @@ -3669,7 +3669,7 @@ void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured() // captions and non-default tracks should be displayed based on language // preferences if the user has turned captions on). for (unsigned i = 0; i < m_textTracks->length(); ++i) { - RefPtr<TextTrack> textTrack = m_textTracks->item(i); + RefPtrWillBeRawPtr<TextTrack> textTrack = m_textTracks->item(i); String kind = textTrack->kind(); if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword()) @@ -3678,7 +3678,6 @@ void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured() configureTextTracks(); } - void* HTMLMediaElement::preDispatchEventHandler(Event* event) { if (event && event->type() == EventTypeNames::webkitfullscreenchange) @@ -3694,14 +3693,13 @@ void HTMLMediaElement::createMediaPlayer() m_audioSourceNode->lock(); #endif - if (m_mediaSource) - closeMediaSource(); + closeMediaSource(); m_player = MediaPlayer::create(this); #if ENABLE(WEB_AUDIO) if (m_audioSourceNode) { - // When creating the player, make sure its AudioSourceProvider knows about the MediaElementAudioSourceNode. + // When creating the player, make sure its AudioSourceProvider knows about the client. if (audioSourceProvider()) audioSourceProvider()->setClient(m_audioSourceNode); @@ -3711,7 +3709,7 @@ void HTMLMediaElement::createMediaPlayer() } #if ENABLE(WEB_AUDIO) -void HTMLMediaElement::setAudioSourceNode(MediaElementAudioSourceNode* sourceNode) +void HTMLMediaElement::setAudioSourceNode(AudioSourceProviderClient* sourceNode) { m_audioSourceNode = sourceNode; @@ -3745,7 +3743,7 @@ void HTMLMediaElement::setMediaGroup(const AtomicString& group) // attribute is set, changed, or removed, the user agent must run the following steps: // 1. Let m [this] be the media element in question. // 2. Let m have no current media controller, if it currently has one. - setControllerInternal(0); + setControllerInternal(nullptr); // 3. If m's mediagroup attribute is being removed, then abort these steps. if (group.isNull() || group.isEmpty()) @@ -3753,8 +3751,8 @@ void HTMLMediaElement::setMediaGroup(const AtomicString& 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) { + WeakMediaElementSet elements = documentToElementSetMap().get(&document()); + for (WeakMediaElementSet::iterator i = elements.begin(); i != elements.end(); ++i) { if (*i == this) continue; @@ -3776,7 +3774,7 @@ MediaController* HTMLMediaElement::controller() const return m_mediaController.get(); } -void HTMLMediaElement::setController(PassRefPtr<MediaController> controller) +void HTMLMediaElement::setController(PassRefPtrWillBeRawPtr<MediaController> controller) { // 4.8.10.11.2 Media controllers: controller attribute. // On setting, it must first remove the element's mediagroup attribute, if any, @@ -3785,7 +3783,7 @@ void HTMLMediaElement::setController(PassRefPtr<MediaController> controller) setControllerInternal(controller); } -void HTMLMediaElement::setControllerInternal(PassRefPtr<MediaController> controller) +void HTMLMediaElement::setControllerInternal(PassRefPtrWillBeRawPtr<MediaController> controller) { if (m_mediaController) m_mediaController->removeMediaElement(this); @@ -3794,9 +3792,6 @@ void HTMLMediaElement::setControllerInternal(PassRefPtr<MediaController> control if (m_mediaController) m_mediaController->addMediaElement(this); - - if (hasMediaControls()) - mediaControls()->setMediaController(m_mediaController ? m_mediaController.get() : static_cast<MediaControllerInterface*>(this)); } void HTMLMediaElement::updateMediaController() @@ -3809,11 +3804,11 @@ bool HTMLMediaElement::isBlocked() const { // A media element is a blocked media element if its readyState attribute is in the // HAVE_NOTHING state, the HAVE_METADATA state, or the HAVE_CURRENT_DATA state, + // or if the element has paused for user interaction or paused for in-band content. if (m_readyState <= HAVE_CURRENT_DATA) return true; - // or if the element has paused for user interaction. - return pausedForUserInteraction(); + return false; } bool HTMLMediaElement::isBlockedOnMediaController() const @@ -3857,6 +3852,8 @@ void HTMLMediaElement::prepareMediaFragmentURI() } else m_fragmentEndTime = MediaPlayer::invalidTime(); + // FIXME: Add support for selecting tracks by ID with the Media Fragments track dimension. + if (m_fragmentStartTime != MediaPlayer::invalidTime() && m_readyState < HAVE_FUTURE_DATA) prepareToPlay(); } @@ -3865,22 +3862,19 @@ void HTMLMediaElement::applyMediaFragmentURI() { if (m_fragmentStartTime != MediaPlayer::invalidTime()) { m_sentEndEvent = false; + UseCounter::count(document(), UseCounter::HTMLMediaElementSeekToFragmentStart); seek(m_fragmentStartTime, IGNORE_EXCEPTION); } } -MediaPlayerClient::CORSMode HTMLMediaElement::mediaPlayerCORSMode() const +WebMediaPlayer::CORSMode HTMLMediaElement::corsMode() const { - if (!fastHasAttribute(crossoriginAttr)) - return Unspecified; - if (equalIgnoringCase(fastGetAttribute(crossoriginAttr), "use-credentials")) - return UseCredentials; - return Anonymous; -} - -void HTMLMediaElement::removeBehaviorsRestrictionsAfterFirstUserGesture() -{ - m_restrictions = NoRestrictions; + const AtomicString& crossOriginMode = fastGetAttribute(crossoriginAttr); + if (crossOriginMode.isNull()) + return WebMediaPlayer::CORSModeUnspecified; + if (equalIgnoringCase(crossOriginMode, "use-credentials")) + return WebMediaPlayer::CORSModeUseCredentials; + return WebMediaPlayer::CORSModeAnonymous; } void HTMLMediaElement::mediaPlayerSetWebLayer(blink::WebLayer* webLayer) @@ -3890,22 +3884,19 @@ void HTMLMediaElement::mediaPlayerSetWebLayer(blink::WebLayer* webLayer) // If either of the layers is null we need to enable or disable compositing. This is done by triggering a style recalc. if (!m_webLayer || !webLayer) - scheduleLayerUpdate(); + setNeedsCompositingUpdate(); if (m_webLayer) GraphicsLayer::unregisterContentsLayer(m_webLayer); m_webLayer = webLayer; if (m_webLayer) { - m_webLayer->setOpaque(m_opaque); GraphicsLayer::registerContentsLayer(m_webLayer); } } -void HTMLMediaElement::mediaPlayerSetOpaque(bool opaque) +void HTMLMediaElement::mediaPlayerMediaSourceOpened(blink::WebMediaSource* webMediaSource) { - m_opaque = opaque; - if (m_webLayer) - m_webLayer->setOpaque(m_opaque); + m_mediaSource->setWebMediaSourceAndOpen(adoptPtr(webMediaSource)); } bool HTMLMediaElement::isInteractiveContent() const @@ -3913,4 +3904,67 @@ bool HTMLMediaElement::isInteractiveContent() const return fastHasAttribute(controlsAttr); } +void HTMLMediaElement::defaultEventHandler(Event* event) +{ + if (event->type() == EventTypeNames::focusin) { + if (hasMediaControls()) + mediaControls()->mediaElementFocused(); + } + HTMLElement::defaultEventHandler(event); +} + +void HTMLMediaElement::trace(Visitor* visitor) +{ + visitor->trace(m_asyncEventQueue); + visitor->trace(m_error); + visitor->trace(m_currentSourceNode); + visitor->trace(m_nextChildNodeToConsider); + visitor->trace(m_audioTracks); + visitor->trace(m_videoTracks); + visitor->trace(m_textTracks); + visitor->trace(m_textTracksWhenResourceSelectionBegan); + visitor->trace(m_mediaController); +#if ENABLE(WEB_AUDIO) + visitor->registerWeakMembers<HTMLMediaElement, &HTMLMediaElement::clearWeakMembers>(this); +#endif + WillBeHeapSupplementable<HTMLMediaElement>::trace(visitor); + HTMLElement::trace(visitor); +} + +void HTMLMediaElement::createPlaceholderTracksIfNecessary() +{ + if (!RuntimeEnabledFeatures::audioVideoTracksEnabled()) + return; + + // Create a placeholder audio track if the player says it has audio but it didn't explicitly announce the tracks. + if (hasAudio() && !audioTracks().length()) + addAudioTrack("audio", WebMediaPlayerClient::AudioTrackKindMain, "Audio Track", "", true); + + // Create a placeholder video track if the player says it has video but it didn't explicitly announce the tracks. + if (webMediaPlayer() && webMediaPlayer()->hasVideo() && !videoTracks().length()) + addVideoTrack("video", WebMediaPlayerClient::VideoTrackKindMain, "Video Track", "", true); +} + +void HTMLMediaElement::selectInitialTracksIfNecessary() +{ + if (!RuntimeEnabledFeatures::audioVideoTracksEnabled()) + return; + + // Enable the first audio track if an audio track hasn't been enabled yet. + if (audioTracks().length() > 0 && !audioTracks().hasEnabledTrack()) + audioTracks().anonymousIndexedGetter(0)->setEnabled(true); + + // Select the first video track if a video track hasn't been selected yet. + if (videoTracks().length() > 0 && videoTracks().selectedIndex() == -1) + videoTracks().anonymousIndexedGetter(0)->setSelected(true); +} + +#if ENABLE(WEB_AUDIO) +void HTMLMediaElement::clearWeakMembers(Visitor* visitor) +{ + if (!visitor->isAlive(m_audioSourceNode) && audioSourceProvider()) + audioSourceProvider()->setClient(0); +} +#endif + } |