| /* |
| * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "core/html/HTMLMediaElement.h" |
| |
| #include "bindings/core/v8/ExceptionMessages.h" |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "bindings/core/v8/ExceptionStatePlaceholder.h" |
| #include "bindings/core/v8/ScriptController.h" |
| #include "core/HTMLNames.h" |
| #include "core/css/MediaList.h" |
| #include "core/dom/Attribute.h" |
| #include "core/dom/ElementTraversal.h" |
| #include "core/dom/ExceptionCode.h" |
| #include "core/events/Event.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/Settings.h" |
| #include "core/frame/UseCounter.h" |
| #include "core/html/HTMLMediaSource.h" |
| #include "core/html/HTMLSourceElement.h" |
| #include "core/html/MediaError.h" |
| #include "core/html/MediaFragmentURIParser.h" |
| #include "core/html/TimeRanges.h" |
| #include "core/rendering/RenderVideo.h" |
| #include "core/rendering/RenderView.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 "public/platform/Platform.h" |
| #include "wtf/CurrentTime.h" |
| #include "wtf/MathExtras.h" |
| #include "wtf/NonCopyingSort.h" |
| #include "wtf/Uint8Array.h" |
| #include "wtf/text/CString.h" |
| #include <limits> |
| |
| using blink::WebMediaPlayer; |
| using blink::WebMimeRegistry; |
| using blink::WebMediaPlayerClient; |
| |
| namespace blink { |
| |
| #if !LOG_DISABLED |
| static String urlForLoggingMedia(const KURL& url) |
| { |
| static const unsigned maximumURLLengthForLogging = 128; |
| |
| if (url.string().length() < maximumURLLengthForLogging) |
| return url.string(); |
| return url.string().substring(0, maximumURLLengthForLogging) + "..."; |
| } |
| |
| static const char* boolString(bool val) |
| { |
| return val ? "true" : "false"; |
| } |
| #endif |
| |
| #ifndef LOG_MEDIA_EVENTS |
| // Default to not logging events because so many are generated they can overwhelm the rest of |
| // the logging. |
| #define LOG_MEDIA_EVENTS 0 |
| #endif |
| |
| #ifndef LOG_CACHED_TIME_WARNINGS |
| // Default to not logging warnings about excessive drift in the cached media time because it adds a |
| // fair amount of overhead and logging. |
| #define LOG_CACHED_TIME_WARNINGS 0 |
| #endif |
| |
| typedef WillBeHeapHashSet<RawPtrWillBeWeakMember<HTMLMediaElement> > WeakMediaElementSet; |
| typedef WillBeHeapHashMap<RawPtrWillBeWeakMember<Document>, WeakMediaElementSet> DocumentElementSetMap; |
| static DocumentElementSetMap& documentToElementSetMap() |
| { |
| DEFINE_STATIC_LOCAL(OwnPtrWillBePersistent<DocumentElementSetMap>, map, (adoptPtrWillBeNoop(new DocumentElementSetMap()))); |
| return *map; |
| } |
| |
| static void addElementToDocumentMap(HTMLMediaElement* element, Document* document) |
| { |
| DocumentElementSetMap& map = documentToElementSetMap(); |
| WeakMediaElementSet set = map.take(document); |
| set.add(element); |
| map.add(document, set); |
| } |
| |
| static void removeElementFromDocumentMap(HTMLMediaElement* element, Document* document) |
| { |
| DocumentElementSetMap& map = documentToElementSetMap(); |
| WeakMediaElementSet set = map.take(document); |
| set.remove(element); |
| if (!set.isEmpty()) |
| map.add(document, set); |
| } |
| |
| static bool canLoadURL(const KURL& url, const ContentType& contentType, const String& keySystem) |
| { |
| DEFINE_STATIC_LOCAL(const String, codecs, ("codecs")); |
| |
| String contentMIMEType = contentType.type().lower(); |
| String contentTypeCodecs = contentType.parameter(codecs); |
| |
| // If the MIME type is missing or is not meaningful, try to figure it out from the URL. |
| if (contentMIMEType.isEmpty() || contentMIMEType == "application/octet-stream" || contentMIMEType == "text/plain") { |
| if (url.protocolIsData()) |
| contentMIMEType = mimeTypeFromDataURL(url.string()); |
| } |
| |
| // If no MIME type is specified, always attempt to load. |
| if (contentMIMEType.isEmpty()) |
| return true; |
| |
| // 4.8.10.3 MIME types - In the absence of a specification to the contrary, the MIME type "application/octet-stream" |
| // when used with parameters, e.g. "application/octet-stream;codecs=theora", is a type that the user agent knows |
| // it cannot render. |
| if (contentMIMEType != "application/octet-stream" || contentTypeCodecs.isEmpty()) { |
| WebMimeRegistry::SupportsType supported = blink::Platform::current()->mimeRegistry()->supportsMediaMIMEType(contentMIMEType, contentTypeCodecs, keySystem.lower()); |
| return supported > WebMimeRegistry::IsNotSupported; |
| } |
| |
| return false; |
| } |
| |
| WebMimeRegistry::SupportsType HTMLMediaElement::supportsType(const ContentType& contentType, const String& keySystem) |
| { |
| DEFINE_STATIC_LOCAL(const String, codecs, ("codecs")); |
| |
| if (!RuntimeEnabledFeatures::mediaEnabled()) |
| return WebMimeRegistry::IsNotSupported; |
| |
| String type = contentType.type().lower(); |
| // The codecs string is not lower-cased because MP4 values are case sensitive |
| // per http://tools.ietf.org/html/rfc4281#page-7. |
| String typeCodecs = contentType.parameter(codecs); |
| String system = keySystem.lower(); |
| |
| if (type.isEmpty()) |
| return WebMimeRegistry::IsNotSupported; |
| |
| // 4.8.10.3 MIME types - The canPlayType(type) method must return the empty string if type is a type that the |
| // user agent knows it cannot render or is the type "application/octet-stream" |
| if (type == "application/octet-stream") |
| return WebMimeRegistry::IsNotSupported; |
| |
| return blink::Platform::current()->mimeRegistry()->supportsMediaMIMEType(type, typeCodecs, system); |
| } |
| |
| 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_playedTimeRanges() |
| , m_asyncEventQueue(GenericEventQueue::create(this)) |
| , m_playbackRate(1.0f) |
| , m_defaultPlaybackRate(1.0f) |
| , m_networkState(NETWORK_EMPTY) |
| , m_readyState(HAVE_NOTHING) |
| , m_readyStateMaximum(HAVE_NOTHING) |
| , m_volume(1.0f) |
| , m_lastSeekTime(0) |
| , m_previousProgressTime(std::numeric_limits<double>::max()) |
| , m_duration(std::numeric_limits<double>::quiet_NaN()) |
| , m_lastTimeUpdateEventWallTime(0) |
| , m_lastTimeUpdateEventMovieTime(std::numeric_limits<double>::max()) |
| , m_loadState(WaitingForSource) |
| , m_deferredLoadState(NotDeferred) |
| , m_deferredLoadTimer(this, &HTMLMediaElement::deferredLoadTimerFired) |
| , m_webLayer(0) |
| , m_preload(MediaPlayer::Auto) |
| , m_displayMode(Unknown) |
| , m_cachedTime(MediaPlayer::invalidTime()) |
| , m_fragmentStartTime(MediaPlayer::invalidTime()) |
| , m_fragmentEndTime(MediaPlayer::invalidTime()) |
| , m_pendingActionFlags(0) |
| , m_userGestureRequiredForPlay(false) |
| , m_playing(false) |
| , m_shouldDelayLoadEvent(false) |
| , m_haveFiredLoadedData(false) |
| , m_active(true) |
| , m_autoplaying(true) |
| , m_muted(false) |
| , m_paused(true) |
| , m_seeking(false) |
| , m_sentStalledEvent(false) |
| , m_sentEndEvent(false) |
| , m_pausedInternal(false) |
| , m_closedCaptionsVisible(false) |
| , m_completelyLoaded(false) |
| , m_havePreparedToPlay(false) |
| , m_processingPreferenceChange(false) |
| #if ENABLE(OILPAN) |
| , m_isFinalizing(false) |
| , m_closeMediaSourceWhenFinalizing(false) |
| #endif |
| { |
| ASSERT(RuntimeEnabledFeatures::mediaEnabled()); |
| |
| WTF_LOG(Media, "HTMLMediaElement::HTMLMediaElement"); |
| ScriptWrappable::init(this); |
| |
| 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); |
| #endif |
| |
| #if ENABLE(OILPAN) |
| if (m_closeMediaSourceWhenFinalizing) |
| closeMediaSource(); |
| #else |
| closeMediaSource(); |
| |
| removeElementFromDocumentMap(this, &document()); |
| #endif |
| |
| // Destroying the player may cause a resource load to be canceled, |
| // which could result in userCancelledLoad() being called back. |
| // Setting m_completelyLoaded ensures that such a call will not cause |
| // us to dispatch an abort event, which would result in a crash. |
| // 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 |
| // FrameLoader::loadDone(). To prevent load event dispatching during |
| // 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 |
| |
| clearMediaPlayerAndAudioSourceProviderClientWithoutLocking(); |
| |
| #if !ENABLE(OILPAN) |
| document().decrementLoadEventDelayCount(); |
| #endif |
| } |
| |
| #if ENABLE(OILPAN) |
| void HTMLMediaElement::setCloseMediaSourceWhenFinalizing() |
| { |
| ASSERT(!m_closeMediaSourceWhenFinalizing); |
| m_closeMediaSourceWhenFinalizing = true; |
| } |
| #endif |
| |
| void HTMLMediaElement::didMoveToNewDocument(Document& oldDocument) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::didMoveToNewDocument"); |
| |
| if (m_shouldDelayLoadEvent) { |
| document().incrementLoadEventDelayCount(); |
| // Note: Keeping the load event delay count increment on oldDocument that was added |
| // when m_shouldDelayLoadEvent was set so that destruction of m_player can not |
| // cause load event dispatching in oldDocument. |
| } else { |
| // Incrementing the load event delay count so that destruction of m_player can not |
| // cause load event dispatching in oldDocument. |
| oldDocument.incrementLoadEventDelayCount(); |
| } |
| |
| removeElementFromDocumentMap(this, &oldDocument); |
| addElementToDocumentMap(this, &document()); |
| |
| // FIXME: This is a temporary fix to prevent this object from causing 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 LocalFrame and FrameLoader references on |
| // document changes so that playback can be resumed properly. |
| userCancelledLoad(); |
| |
| // Decrement the load event delay count on oldDocument now that m_player has been destroyed |
| // and there is no risk of dispatching a load event from within the destructor. |
| oldDocument.decrementLoadEventDelayCount(); |
| |
| ActiveDOMObject::didMoveToNewExecutionContext(&document()); |
| HTMLElement::didMoveToNewDocument(oldDocument); |
| } |
| |
| bool HTMLMediaElement::isMouseFocusable() const |
| { |
| return false; |
| } |
| |
| void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value) |
| { |
| if (name == HTMLNames::srcAttr) { |
| // Trigger a reload, as long as the 'src' attribute is present. |
| if (!value.isNull()) { |
| clearMediaPlayer(LoadMediaResource); |
| scheduleDelayedAction(LoadMediaResource); |
| } |
| } else if (name == HTMLNames::preloadAttr) { |
| if (equalIgnoringCase(value, "none")) |
| m_preload = MediaPlayer::None; |
| else if (equalIgnoringCase(value, "metadata")) |
| m_preload = MediaPlayer::MetaData; |
| else { |
| // The spec does not define an "invalid value default" but "auto" is suggested as the |
| // "missing value default", so use it for everything except "none" and "metadata" |
| m_preload = MediaPlayer::Auto; |
| } |
| |
| // The attribute must be ignored if the autoplay attribute is present |
| if (!autoplay() && m_player) |
| setPlayerPreload(); |
| } |
| |
| HTMLElement::parseAttribute(name, value); |
| } |
| |
| void HTMLMediaElement::finishParsingChildren() |
| { |
| HTMLElement::finishParsingChildren(); |
| } |
| |
| bool HTMLMediaElement::rendererIsNeeded(const RenderStyle& style) |
| { |
| // FIXME(sky): Can we delete this method? |
| return false; |
| } |
| |
| RenderObject* HTMLMediaElement::createRenderer(RenderStyle*) |
| { |
| return new RenderMedia(this); |
| } |
| |
| Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode* insertionPoint) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::insertedInto"); |
| |
| HTMLElement::insertedInto(insertionPoint); |
| if (insertionPoint->inDocument()) { |
| m_active = true; |
| |
| if (!getAttribute(HTMLNames::srcAttr).isEmpty() && m_networkState == NETWORK_EMPTY) |
| scheduleDelayedAction(LoadMediaResource); |
| } |
| |
| return InsertionShouldCallDidNotifySubtreeInsertions; |
| } |
| |
| void HTMLMediaElement::didNotifySubtreeInsertionsToDocument() |
| { |
| } |
| |
| void HTMLMediaElement::removedFrom(ContainerNode* insertionPoint) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::removedFrom"); |
| |
| m_active = false; |
| if (insertionPoint->inDocument() && insertionPoint->document().isActive()) { |
| if (m_networkState > NETWORK_EMPTY) |
| pause(); |
| } |
| |
| HTMLElement::removedFrom(insertionPoint); |
| } |
| |
| void HTMLMediaElement::attach(const AttachContext& context) |
| { |
| HTMLElement::attach(context); |
| |
| if (renderer()) |
| renderer()->updateFromElement(); |
| } |
| |
| void HTMLMediaElement::didRecalcStyle(StyleRecalcChange) |
| { |
| if (renderer()) |
| renderer()->updateFromElement(); |
| } |
| |
| void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::scheduleDelayedAction"); |
| |
| if ((actionType & LoadMediaResource) && !(m_pendingActionFlags & LoadMediaResource)) { |
| prepareForLoad(); |
| m_pendingActionFlags |= LoadMediaResource; |
| } |
| |
| if (!m_loadTimer.isActive()) |
| 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, 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'", event->type().ascii().data()); |
| #endif |
| m_asyncEventQueue->enqueueEvent(event); |
| } |
| |
| void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*) |
| { |
| if (m_pendingActionFlags & LoadMediaResource) { |
| if (m_loadState == LoadingFromSourceElement) |
| loadNextSourceChild(); |
| else |
| loadInternal(); |
| } |
| |
| m_pendingActionFlags = 0; |
| } |
| |
| PassRefPtrWillBeRawPtr<MediaError> HTMLMediaElement::error() const |
| { |
| return m_error; |
| } |
| |
| void HTMLMediaElement::setSrc(const AtomicString& url) |
| { |
| setAttribute(HTMLNames::srcAttr, url); |
| } |
| |
| HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const |
| { |
| return m_networkState; |
| } |
| |
| 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; |
| |
| // 4.8.10.3 |
| switch (support) |
| { |
| case WebMimeRegistry::IsNotSupported: |
| canPlay = emptyString(); |
| break; |
| case WebMimeRegistry::MayBeSupported: |
| canPlay = "maybe"; |
| break; |
| case WebMimeRegistry::IsSupported: |
| canPlay = "probably"; |
| break; |
| } |
| |
| WTF_LOG(Media, "HTMLMediaElement::canPlayType(%s, %s) -> %s", mimeType.utf8().data(), keySystem.utf8().data(), canPlay.utf8().data()); |
| |
| return canPlay; |
| } |
| |
| void HTMLMediaElement::load() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::load()"); |
| |
| if (UserGestureIndicator::processingUserGesture()) |
| m_userGestureRequiredForPlay = false; |
| |
| prepareForLoad(); |
| loadInternal(); |
| prepareToPlay(); |
| } |
| |
| void HTMLMediaElement::prepareForLoad() |
| { |
| WTF_LOG(Media, "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; |
| m_completelyLoaded = false; |
| m_havePreparedToPlay = false; |
| m_displayMode = Unknown; |
| |
| // 1 - Abort any already-running instance of the resource selection algorithm for this element. |
| m_loadState = WaitingForSource; |
| 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. |
| cancelPendingEventsAndCallbacks(); |
| |
| // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue |
| // a task to fire a simple event named abort at the media element. |
| if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE) |
| scheduleEvent(EventTypeNames::abort); |
| |
| 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. |
| // FIXME(sky): We have no tracks. |
| |
| // 4.4 - If readyState is not set to HAVE_NOTHING, then set it to that state. |
| m_readyState = HAVE_NOTHING; |
| m_readyStateMaximum = HAVE_NOTHING; |
| |
| // 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 the ready state is HAVE_NOTHING. |
| invalidateCachedTime(); |
| |
| // 4.9 - Set the timeline offset to Not-a-Number (NaN). |
| // 4.10 - Update the duration attribute to Not-a-Number (NaN). |
| } |
| |
| // 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 = nullptr; |
| m_autoplaying = true; |
| |
| // 7 - Invoke the media element's resource selection algorithm. |
| |
| // 8 - Note: Playback of any previously playing media resource for this element stops. |
| |
| // The resource selection algorithm |
| // 1 - Set the networkState to NETWORK_NO_SOURCE |
| m_networkState = NETWORK_NO_SOURCE; |
| |
| // 2 - Asynchronously await a stable state. |
| |
| 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 = 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 |
| // event may have already fired by then. |
| setShouldDelayLoadEvent(true); |
| } |
| |
| void HTMLMediaElement::loadInternal() |
| { |
| selectMediaResource(); |
| } |
| |
| void HTMLMediaElement::selectMediaResource() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::selectMediaResource"); |
| |
| enum Mode { attribute, children }; |
| |
| // 3 - If the media element has a src attribute, then let mode be attribute. |
| Mode mode = attribute; |
| if (!hasAttribute(HTMLNames::srcAttr)) { |
| // Otherwise, if the media element does not have a src attribute but has a source |
| // element child, then let mode be children and let candidate be the first such |
| // source element child in tree order. |
| if (HTMLSourceElement* element = Traversal<HTMLSourceElement>::firstChild(*this)) { |
| mode = children; |
| 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 |
| // synchronous section ends. |
| m_loadState = WaitingForSource; |
| setShouldDelayLoadEvent(false); |
| m_networkState = NETWORK_EMPTY; |
| |
| WTF_LOG(Media, "HTMLMediaElement::selectMediaResource, nothing to load"); |
| return; |
| } |
| } |
| |
| // 4 - Set the media element's delaying-the-load-event flag to true (this delays the load event), |
| // and set its networkState to NETWORK_LOADING. |
| setShouldDelayLoadEvent(true); |
| m_networkState = NETWORK_LOADING; |
| |
| // 5 - Queue a task to fire a simple event named loadstart at the media element. |
| scheduleEvent(EventTypeNames::loadstart); |
| |
| // 6 - If mode is attribute, then run these substeps |
| if (mode == attribute) { |
| m_loadState = LoadingFromSrcAttr; |
| |
| // If the src attribute's value is the empty string ... jump down to the failed step below |
| KURL mediaURL = getNonEmptyURLAttribute(HTMLNames::srcAttr); |
| if (mediaURL.isEmpty()) { |
| mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError); |
| WTF_LOG(Media, "HTMLMediaElement::selectMediaResource, empty 'src'"); |
| return; |
| } |
| |
| if (!isSafeToLoadURL(mediaURL, Complain)) { |
| mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError); |
| return; |
| } |
| |
| // No type or key system information is available when the url comes |
| // from the 'src' attribute so MediaPlayer |
| // will have to pick a media engine based on the file extension. |
| ContentType contentType((String())); |
| loadResource(mediaURL, contentType, String()); |
| WTF_LOG(Media, "HTMLMediaElement::selectMediaResource, using 'src' attribute url"); |
| return; |
| } |
| |
| // Otherwise, the source elements will be used |
| loadNextSourceChild(); |
| } |
| |
| void HTMLMediaElement::loadNextSourceChild() |
| { |
| ContentType contentType((String())); |
| String keySystem; |
| KURL mediaURL = selectNextSourceChild(&contentType, &keySystem, Complain); |
| if (!mediaURL.isValid()) { |
| waitForSourceChange(); |
| return; |
| } |
| |
| // Recreate the media player for the new url |
| createMediaPlayer(); |
| |
| m_loadState = LoadingFromSourceElement; |
| loadResource(mediaURL, contentType, keySystem); |
| } |
| |
| void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType, const String& keySystem) |
| { |
| ASSERT(isSafeToLoadURL(url, Complain)); |
| |
| WTF_LOG(Media, "HTMLMediaElement::loadResource(%s, %s, %s)", urlForLoggingMedia(url).utf8().data(), contentType.raw().utf8().data(), keySystem.utf8().data()); |
| |
| LocalFrame* frame = document().frame(); |
| if (!frame) { |
| mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError); |
| return; |
| } |
| |
| // The resource fetch algorithm |
| m_networkState = NETWORK_LOADING; |
| |
| // Set m_currentSrc *before* changing to the cache url, the fact that we are loading from the app |
| // cache is an internal detail not exposed through the media element API. |
| m_currentSrc = url; |
| |
| WTF_LOG(Media, "HTMLMediaElement::loadResource - m_currentSrc -> %s", urlForLoggingMedia(m_currentSrc).utf8().data()); |
| |
| startProgressEventTimer(); |
| |
| // Reset display mode to force a recalculation of what to show because we are resetting the player. |
| setDisplayMode(Unknown); |
| |
| if (!autoplay()) |
| setPlayerPreload(); |
| |
| if (hasAttribute(HTMLNames::mutedAttr)) |
| m_muted = true; |
| updateVolume(); |
| |
| ASSERT(!m_mediaSource); |
| |
| bool attemptLoad = true; |
| |
| 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 { |
| mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError); |
| } |
| |
| // If there is no poster to display, allow the media engine to render video frames as soon as |
| // they are available. |
| updateDisplayState(); |
| |
| if (renderer()) |
| renderer()->updateFromElement(); |
| } |
| |
| 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; |
| |
| return WebMediaPlayer::LoadTypeURL; |
| } |
| |
| bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidURLAction actionIfInvalid) |
| { |
| if (!url.isValid()) { |
| WTF_LOG(Media, "HTMLMediaElement::isSafeToLoadURL(%s) -> FALSE because url is invalid", urlForLoggingMedia(url).utf8().data()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void HTMLMediaElement::startProgressEventTimer() |
| { |
| if (m_progressEventTimer.isActive()) |
| return; |
| |
| m_previousProgressTime = WTF::currentTime(); |
| // 350ms is not magic, it is in the spec! |
| m_progressEventTimer.startRepeating(0.350, FROM_HERE); |
| } |
| |
| void HTMLMediaElement::waitForSourceChange() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::waitForSourceChange"); |
| |
| stopPeriodicTimers(); |
| m_loadState = WaitingForSource; |
| |
| // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value |
| m_networkState = NETWORK_NO_SOURCE; |
| |
| // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. |
| setShouldDelayLoadEvent(false); |
| |
| updateDisplayState(); |
| |
| if (renderer()) |
| renderer()->updateFromElement(); |
| } |
| |
| void HTMLMediaElement::noneSupported() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::noneSupported"); |
| |
| stopPeriodicTimers(); |
| m_loadState = WaitingForSource; |
| m_currentSourceNode = nullptr; |
| |
| // 4.8.10.5 |
| // 6 - Reaching this step indicates that the media resource failed to load or that the given |
| // URL could not be resolved. In one atomic operation, run the following steps: |
| |
| // 6.1 - Set the error attribute to a new MediaError object whose code attribute is set to |
| // MEDIA_ERR_SRC_NOT_SUPPORTED. |
| m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED); |
| |
| // 6.2 - Forget the media element's media-resource-specific text tracks. |
| // FIXME(sky): We have no tracks. |
| |
| // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value. |
| m_networkState = NETWORK_NO_SOURCE; |
| |
| // 7 - Queue a task to fire a simple event named error at the media element. |
| scheduleEvent(EventTypeNames::error); |
| |
| closeMediaSource(); |
| |
| // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. |
| setShouldDelayLoadEvent(false); |
| |
| // 9 - Abort these steps. Until the load() method is invoked or the src attribute is changed, |
| // the element won't attempt to load another resource. |
| |
| updateDisplayState(); |
| |
| if (renderer()) |
| renderer()->updateFromElement(); |
| } |
| |
| void HTMLMediaElement::mediaEngineError(PassRefPtrWillBeRawPtr<MediaError> err) |
| { |
| ASSERT(m_readyState >= HAVE_METADATA); |
| WTF_LOG(Media, "HTMLMediaElement::mediaEngineError(%d)", static_cast<int>(err->code())); |
| |
| // 1 - The user agent should cancel the fetching process. |
| stopPeriodicTimers(); |
| m_loadState = WaitingForSource; |
| |
| // 2 - Set the error attribute to a new MediaError object whose code attribute is |
| // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE. |
| m_error = err; |
| |
| // 3 - Queue a task to fire a simple event named error at the media element. |
| scheduleEvent(EventTypeNames::error); |
| |
| // 4 - Set the element's networkState attribute to the NETWORK_IDLE value. |
| m_networkState = NETWORK_IDLE; |
| |
| // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. |
| setShouldDelayLoadEvent(false); |
| |
| // 6 - Abort the overall resource selection algorithm. |
| m_currentSourceNode = nullptr; |
| } |
| |
| void HTMLMediaElement::cancelPendingEventsAndCallbacks() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::cancelPendingEventsAndCallbacks"); |
| m_asyncEventQueue->cancelAllEvents(); |
| |
| for (HTMLSourceElement* source = Traversal<HTMLSourceElement>::firstChild(*this); source; source = Traversal<HTMLSourceElement>::nextSibling(*source)) |
| source->cancelPendingErrorEvent(); |
| } |
| |
| void HTMLMediaElement::mediaPlayerNetworkStateChanged() |
| { |
| setNetworkState(webMediaPlayer()->networkState()); |
| } |
| |
| void HTMLMediaElement::mediaLoadingFailed(WebMediaPlayer::NetworkState error) |
| { |
| stopPeriodicTimers(); |
| |
| // If we failed while trying to load a <source> element, the movie was never parsed, and there are more |
| // <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. |
| // FIXME(sky): We have no tracks. |
| |
| if (havePotentialSourceChild()) { |
| WTF_LOG(Media, "HTMLMediaElement::setNetworkState - scheduling next <source>"); |
| scheduleNextSourceChild(); |
| } else { |
| WTF_LOG(Media, "HTMLMediaElement::setNetworkState - no more <source> elements, waiting"); |
| waitForSourceChange(); |
| } |
| |
| return; |
| } |
| |
| if (error == WebMediaPlayer::NetworkStateNetworkError && m_readyState >= HAVE_METADATA) |
| mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK)); |
| else if (error == WebMediaPlayer::NetworkStateDecodeError) |
| mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE)); |
| else if ((error == WebMediaPlayer::NetworkStateFormatError |
| || error == WebMediaPlayer::NetworkStateNetworkError) |
| && m_loadState == LoadingFromSrcAttr) |
| noneSupported(); |
| |
| updateDisplayState(); |
| } |
| |
| void HTMLMediaElement::setNetworkState(WebMediaPlayer::NetworkState state) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::setNetworkState(%d) - current state is %d", static_cast<int>(state), static_cast<int>(m_networkState)); |
| |
| if (state == WebMediaPlayer::NetworkStateEmpty) { |
| // Just update the cached state and leave, we can't do anything. |
| m_networkState = NETWORK_EMPTY; |
| return; |
| } |
| |
| if (state == WebMediaPlayer::NetworkStateFormatError |
| || state == WebMediaPlayer::NetworkStateNetworkError |
| || state == WebMediaPlayer::NetworkStateDecodeError) { |
| mediaLoadingFailed(state); |
| return; |
| } |
| |
| if (state == WebMediaPlayer::NetworkStateIdle) { |
| if (m_networkState > NETWORK_IDLE) { |
| changeNetworkStateFromLoadingToIdle(); |
| setShouldDelayLoadEvent(false); |
| } else { |
| m_networkState = NETWORK_IDLE; |
| } |
| } |
| |
| if (state == WebMediaPlayer::NetworkStateLoading) { |
| if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE) |
| startProgressEventTimer(); |
| m_networkState = NETWORK_LOADING; |
| } |
| |
| if (state == WebMediaPlayer::NetworkStateLoaded) { |
| if (m_networkState != NETWORK_IDLE) |
| changeNetworkStateFromLoadingToIdle(); |
| m_completelyLoaded = true; |
| } |
| } |
| |
| void HTMLMediaElement::changeNetworkStateFromLoadingToIdle() |
| { |
| ASSERT(m_player); |
| m_progressEventTimer.stop(); |
| |
| // Schedule one last progress event so we guarantee that at least one is fired |
| // for files that load very quickly. |
| if (webMediaPlayer() && webMediaPlayer()->didLoadingProgress()) |
| scheduleEvent(EventTypeNames::progress); |
| scheduleEvent(EventTypeNames::suspend); |
| m_networkState = NETWORK_IDLE; |
| } |
| |
| void HTMLMediaElement::mediaPlayerReadyStateChanged() |
| { |
| setReadyState(static_cast<ReadyState>(webMediaPlayer()->readyState())); |
| } |
| |
| void HTMLMediaElement::setReadyState(ReadyState state) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::setReadyState(%d) - current state is %d,", static_cast<int>(state), static_cast<int>(m_readyState)); |
| |
| // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it |
| bool wasPotentiallyPlaying = potentiallyPlaying(); |
| |
| ReadyState oldState = m_readyState; |
| ReadyState newState = state; |
| |
| if (newState == oldState) |
| return; |
| |
| m_readyState = newState; |
| |
| if (oldState > m_readyStateMaximum) |
| m_readyStateMaximum = oldState; |
| |
| if (m_networkState == NETWORK_EMPTY) |
| return; |
| |
| if (m_seeking) { |
| // 4.8.10.9, step 9 note: If the media element was potentially playing immediately before |
| // it started seeking, but seeking caused its readyState attribute to change to a value |
| // lower than HAVE_FUTURE_DATA, then a waiting will be fired at the element. |
| if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) |
| scheduleEvent(EventTypeNames::waiting); |
| |
| // 4.8.10.9 steps 12-14 |
| if (m_readyState >= HAVE_CURRENT_DATA) |
| finishSeek(); |
| } else { |
| if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) { |
| // 4.8.10.8 |
| scheduleTimeupdateEvent(false); |
| scheduleEvent(EventTypeNames::waiting); |
| } |
| } |
| |
| if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) { |
| prepareMediaFragmentURI(); |
| |
| m_duration = duration(); |
| scheduleEvent(EventTypeNames::durationchange); |
| |
| if (isHTMLVideoElement()) |
| scheduleEvent(EventTypeNames::resize); |
| scheduleEvent(EventTypeNames::loadedmetadata); |
| if (renderer()) |
| renderer()->updateFromElement(); |
| } |
| |
| bool shouldUpdateDisplayState = false; |
| |
| if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) { |
| m_haveFiredLoadedData = true; |
| shouldUpdateDisplayState = true; |
| scheduleEvent(EventTypeNames::loadeddata); |
| setShouldDelayLoadEvent(false); |
| applyMediaFragmentURI(); |
| } |
| |
| bool isPotentiallyPlaying = potentiallyPlaying(); |
| if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA) { |
| scheduleEvent(EventTypeNames::canplay); |
| if (isPotentiallyPlaying) |
| scheduleEvent(EventTypeNames::playing); |
| shouldUpdateDisplayState = true; |
| } |
| |
| if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA) { |
| if (oldState <= HAVE_CURRENT_DATA) { |
| scheduleEvent(EventTypeNames::canplay); |
| if (isPotentiallyPlaying) |
| scheduleEvent(EventTypeNames::playing); |
| } |
| |
| if (m_autoplaying && m_paused && autoplay() && !m_userGestureRequiredForPlay) { |
| m_paused = false; |
| invalidateCachedTime(); |
| scheduleEvent(EventTypeNames::play); |
| scheduleEvent(EventTypeNames::playing); |
| } |
| |
| scheduleEvent(EventTypeNames::canplaythrough); |
| |
| shouldUpdateDisplayState = true; |
| } |
| |
| if (shouldUpdateDisplayState) |
| updateDisplayState(); |
| |
| updatePlayState(); |
| } |
| |
| void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*) |
| { |
| ASSERT(m_player); |
| if (m_networkState != NETWORK_LOADING) |
| return; |
| |
| double time = WTF::currentTime(); |
| double timedelta = time - m_previousProgressTime; |
| |
| if (webMediaPlayer() && webMediaPlayer()->didLoadingProgress()) { |
| scheduleEvent(EventTypeNames::progress); |
| m_previousProgressTime = time; |
| m_sentStalledEvent = false; |
| if (renderer()) |
| renderer()->updateFromElement(); |
| } else if (timedelta > 3.0 && !m_sentStalledEvent) { |
| scheduleEvent(EventTypeNames::stalled); |
| m_sentStalledEvent = true; |
| setShouldDelayLoadEvent(false); |
| } |
| } |
| |
| void HTMLMediaElement::addPlayedRange(double start, double end) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::addPlayedRange(%f, %f)", start, end); |
| if (!m_playedTimeRanges) |
| m_playedTimeRanges = TimeRanges::create(); |
| m_playedTimeRanges->add(start, end); |
| } |
| |
| bool HTMLMediaElement::supportsSave() const |
| { |
| return webMediaPlayer() && webMediaPlayer()->supportsSave(); |
| } |
| |
| void HTMLMediaElement::prepareToPlay() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::prepareToPlay(%p)", this); |
| if (m_havePreparedToPlay) |
| return; |
| m_havePreparedToPlay = true; |
| |
| if (loadIsDeferred()) |
| startDeferredLoad(); |
| } |
| |
| void HTMLMediaElement::seek(double time, ExceptionState& exceptionState) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::seek(%f)", time); |
| |
| // 4.8.10.9 Seeking |
| |
| // 1 - If the media element's readyState is HAVE_NOTHING, then raise an InvalidStateError exception. |
| if (m_readyState == HAVE_NOTHING) { |
| exceptionState.throwDOMException(InvalidStateError, "The element's readyState is HAVE_NOTHING."); |
| return; |
| } |
| |
| // If the media engine has been told to postpone loading data, let it go ahead now. |
| if (m_preload < MediaPlayer::Auto && m_readyState < HAVE_FUTURE_DATA) |
| prepareToPlay(); |
| |
| // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set. |
| refreshCachedTime(); |
| double now = currentTime(); |
| |
| // 2 - If the element's seeking IDL attribute is true, then another instance of this algorithm is |
| // already running. Abort that other instance of the algorithm without waiting for the step that |
| // it is running to complete. |
| // Nothing specific to be done here. |
| |
| // 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 = std::min(time, duration()); |
| |
| // 6 - If the new playback position is less than the earliest possible position, let it be that position instead. |
| 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 |
| // time scale, we will ask the media engine to "seek" to the current movie time, which may be a noop and |
| // not generate a timechanged callback. This means m_seeking will never be cleared and we will never |
| // fire a 'seeked' event. |
| double mediaTime = webMediaPlayer()->mediaTimeForTimeValue(time); |
| if (time != mediaTime) { |
| WTF_LOG(Media, "HTMLMediaElement::seek(%f) - media timeline equivalent is %f", time, mediaTime); |
| time = mediaTime; |
| } |
| |
| // 7 - If the (possibly now changed) new playback position is not in one of the ranges given in the |
| // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute |
| // that is the nearest to the new playback position. ... If there are no ranges given in the seekable |
| // attribute then set the seeking IDL attribute to false and abort these steps. |
| RefPtrWillBeRawPtr<TimeRanges> seekableRanges = seekable(); |
| |
| // Short circuit seeking to the current time by just firing the events if no seek is required. |
| // Don't skip calling the media engine if we are in poster mode because a seek should always |
| // cancel poster display. |
| bool noSeekRequired = !seekableRanges->length() || (time == now && displayMode() != Poster); |
| |
| 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); |
| scheduleEvent(EventTypeNames::seeked); |
| } |
| m_seeking = false; |
| return; |
| } |
| time = seekableRanges->nearest(time); |
| |
| if (m_playing) { |
| if (m_lastSeekTime < now) |
| addPlayedRange(m_lastSeekTime, now); |
| } |
| m_lastSeekTime = time; |
| m_sentEndEvent = false; |
| |
| // 8 - Queue a task to fire a simple event named seeking at the element. |
| scheduleEvent(EventTypeNames::seeking); |
| |
| // 9 - Set the current playback position to the given new playback position |
| webMediaPlayer()->seek(time); |
| |
| // 10-14 are handled, if necessary, when the engine signals a readystate change or otherwise |
| // satisfies seek completion and signals a time change. |
| } |
| |
| void HTMLMediaElement::finishSeek() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::finishSeek"); |
| |
| // 4.8.10.9 Seeking completion |
| // 12 - Set the seeking IDL attribute to false. |
| m_seeking = false; |
| |
| // 13 - Queue a task to fire a simple event named timeupdate at the element. |
| scheduleTimeupdateEvent(false); |
| |
| // 14 - Queue a task to fire a simple event named seeked at the element. |
| scheduleEvent(EventTypeNames::seeked); |
| |
| setDisplayMode(Video); |
| } |
| |
| HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const |
| { |
| return m_readyState; |
| } |
| |
| bool HTMLMediaElement::hasAudio() const |
| { |
| return webMediaPlayer() && webMediaPlayer()->hasAudio(); |
| } |
| |
| bool HTMLMediaElement::seeking() const |
| { |
| return m_seeking; |
| } |
| |
| void HTMLMediaElement::refreshCachedTime() const |
| { |
| if (!webMediaPlayer() || m_readyState < HAVE_METADATA) |
| return; |
| |
| m_cachedTime = webMediaPlayer()->currentTime(); |
| } |
| |
| void HTMLMediaElement::invalidateCachedTime() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::invalidateCachedTime"); |
| m_cachedTime = MediaPlayer::invalidTime(); |
| } |
| |
| // playback state |
| double HTMLMediaElement::currentTime() const |
| { |
| if (m_readyState == HAVE_NOTHING) |
| return 0; |
| |
| if (m_seeking) { |
| WTF_LOG(Media, "HTMLMediaElement::currentTime - seeking, returning %f", m_lastSeekTime); |
| return m_lastSeekTime; |
| } |
| |
| if (m_cachedTime != MediaPlayer::invalidTime() && m_paused) { |
| #if LOG_CACHED_TIME_WARNINGS |
| static const double minCachedDeltaForWarning = 0.01; |
| double delta = m_cachedTime - webMediaPlayer()->currentTime(); |
| if (delta > minCachedDeltaForWarning) |
| WTF_LOG(Media, "HTMLMediaElement::currentTime - WARNING, cached time is %f seconds off of media time when paused", delta); |
| #endif |
| return m_cachedTime; |
| } |
| |
| refreshCachedTime(); |
| |
| return m_cachedTime; |
| } |
| |
| void HTMLMediaElement::setCurrentTime(double time, ExceptionState& exceptionState) |
| { |
| seek(time, exceptionState); |
| } |
| |
| double HTMLMediaElement::duration() const |
| { |
| // FIXME: remove m_player check once we figure out how m_player is going |
| // out of sync with readystate. m_player is cleared but readystate is not set |
| // to HAVE_NOTHING |
| if (!m_player || m_readyState < HAVE_METADATA) |
| 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 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 |
| // and notifying the element to consult its MediaSource for current |
| // duration. See http://crbug.com/266644 |
| |
| if (m_mediaSource) |
| return m_mediaSource->duration(); |
| |
| return webMediaPlayer()->duration(); |
| } |
| |
| bool HTMLMediaElement::paused() const |
| { |
| return m_paused; |
| } |
| |
| double HTMLMediaElement::defaultPlaybackRate() const |
| { |
| return m_defaultPlaybackRate; |
| } |
| |
| void HTMLMediaElement::setDefaultPlaybackRate(double rate) |
| { |
| if (m_defaultPlaybackRate == rate) |
| return; |
| |
| m_defaultPlaybackRate = rate; |
| scheduleEvent(EventTypeNames::ratechange); |
| } |
| |
| double HTMLMediaElement::playbackRate() const |
| { |
| return m_playbackRate; |
| } |
| |
| void HTMLMediaElement::setPlaybackRate(double rate) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::setPlaybackRate(%f)", rate); |
| |
| if (m_playbackRate != rate) { |
| m_playbackRate = rate; |
| invalidateCachedTime(); |
| scheduleEvent(EventTypeNames::ratechange); |
| } |
| |
| updatePlaybackRate(); |
| } |
| |
| double HTMLMediaElement::effectivePlaybackRate() const |
| { |
| return m_playbackRate; |
| } |
| |
| HTMLMediaElement::DirectionOfPlayback HTMLMediaElement::directionOfPlayback() const |
| { |
| return m_playbackRate >= 0 ? Forward : Backward; |
| } |
| |
| void HTMLMediaElement::updatePlaybackRate() |
| { |
| double effectiveRate = effectivePlaybackRate(); |
| if (m_player && potentiallyPlaying()) |
| webMediaPlayer()->setRate(effectiveRate); |
| } |
| |
| 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() && directionOfPlayback() == Forward; |
| } |
| |
| bool HTMLMediaElement::autoplay() const |
| { |
| return hasAttribute(HTMLNames::autoplayAttr); |
| } |
| |
| String HTMLMediaElement::preload() const |
| { |
| switch (m_preload) { |
| case MediaPlayer::None: |
| return "none"; |
| break; |
| case MediaPlayer::MetaData: |
| return "metadata"; |
| break; |
| case MediaPlayer::Auto: |
| return "auto"; |
| break; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return String(); |
| } |
| |
| void HTMLMediaElement::setPreload(const AtomicString& preload) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::setPreload(%s)", preload.utf8().data()); |
| setAttribute(HTMLNames::preloadAttr, preload); |
| } |
| |
| void HTMLMediaElement::play() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::play()"); |
| |
| if (m_userGestureRequiredForPlay && !UserGestureIndicator::processingUserGesture()) |
| return; |
| if (UserGestureIndicator::processingUserGesture()) |
| m_userGestureRequiredForPlay = false; |
| |
| playInternal(); |
| } |
| |
| void HTMLMediaElement::playInternal() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::playInternal"); |
| |
| // 4.8.10.9. Playing the media resource |
| if (!m_player || m_networkState == NETWORK_EMPTY) |
| scheduleDelayedAction(LoadMediaResource); |
| |
| if (endedPlayback()) |
| seek(0, IGNORE_EXCEPTION); |
| |
| if (m_paused) { |
| m_paused = false; |
| invalidateCachedTime(); |
| scheduleEvent(EventTypeNames::play); |
| |
| if (m_readyState <= HAVE_CURRENT_DATA) |
| scheduleEvent(EventTypeNames::waiting); |
| else if (m_readyState >= HAVE_FUTURE_DATA) |
| scheduleEvent(EventTypeNames::playing); |
| } |
| m_autoplaying = false; |
| |
| updatePlayState(); |
| } |
| |
| void HTMLMediaElement::pause() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::pause()"); |
| |
| if (!m_player || m_networkState == NETWORK_EMPTY) |
| scheduleDelayedAction(LoadMediaResource); |
| |
| m_autoplaying = false; |
| |
| if (!m_paused) { |
| m_paused = true; |
| scheduleTimeupdateEvent(false); |
| scheduleEvent(EventTypeNames::pause); |
| } |
| |
| updatePlayState(); |
| } |
| |
| void HTMLMediaElement::closeMediaSource() |
| { |
| if (!m_mediaSource) |
| return; |
| |
| m_mediaSource->close(); |
| m_mediaSource = nullptr; |
| } |
| |
| bool HTMLMediaElement::loop() const |
| { |
| return hasAttribute(HTMLNames::loopAttr); |
| } |
| |
| void HTMLMediaElement::setLoop(bool b) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::setLoop(%s)", boolString(b)); |
| setBooleanAttribute(HTMLNames::loopAttr, b); |
| } |
| |
| double HTMLMediaElement::volume() const |
| { |
| return m_volume; |
| } |
| |
| 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.throwDOMException(IndexSizeError, ExceptionMessages::indexOutsideRange("volume", vol, 0.0, ExceptionMessages::InclusiveBound, 1.0, ExceptionMessages::InclusiveBound)); |
| return; |
| } |
| |
| m_volume = vol; |
| updateVolume(); |
| scheduleEvent(EventTypeNames::volumechange); |
| } |
| |
| bool HTMLMediaElement::muted() const |
| { |
| return m_muted; |
| } |
| |
| void HTMLMediaElement::setMuted(bool muted) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::setMuted(%s)", boolString(muted)); |
| |
| if (m_muted == muted) |
| return; |
| |
| m_muted = muted; |
| |
| updateVolume(); |
| |
| scheduleEvent(EventTypeNames::volumechange); |
| } |
| |
| void HTMLMediaElement::updateVolume() |
| { |
| if (webMediaPlayer()) |
| webMediaPlayer()->setVolume(effectiveMediaVolume()); |
| } |
| |
| double HTMLMediaElement::effectiveMediaVolume() const |
| { |
| if (m_muted) |
| return 0; |
| |
| return m_volume; |
| } |
| |
| // The spec says to fire periodic timeupdate events (those sent while playing) every |
| // "15 to 250ms", we choose the slowest frequency |
| static const double maxTimeupdateEventFrequency = 0.25; |
| |
| void HTMLMediaElement::startPlaybackProgressTimer() |
| { |
| if (m_playbackProgressTimer.isActive()) |
| return; |
| |
| m_previousProgressTime = WTF::currentTime(); |
| m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency, FROM_HERE); |
| } |
| |
| void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*) |
| { |
| ASSERT(m_player); |
| |
| if (m_fragmentEndTime != MediaPlayer::invalidTime() && currentTime() >= m_fragmentEndTime && directionOfPlayback() == Forward) { |
| m_fragmentEndTime = MediaPlayer::invalidTime(); |
| if (!m_paused) { |
| UseCounter::count(document(), UseCounter::HTMLMediaElementPauseAtFragmentEnd); |
| // changes paused to true and fires a simple event named pause at the media element. |
| pause(); |
| } |
| } |
| |
| if (!m_seeking) |
| scheduleTimeupdateEvent(true); |
| } |
| |
| void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent) |
| { |
| double now = WTF::currentTime(); |
| double timedelta = now - m_lastTimeUpdateEventWallTime; |
| |
| // throttle the periodic events |
| if (periodicEvent && timedelta < maxTimeupdateEventFrequency) |
| return; |
| |
| // Some media engines make multiple "time changed" callbacks at the same time, but we only want one |
| // event at a given time so filter here |
| double movieTime = currentTime(); |
| if (movieTime != m_lastTimeUpdateEventMovieTime) { |
| scheduleEvent(EventTypeNames::timeupdate); |
| m_lastTimeUpdateEventWallTime = now; |
| m_lastTimeUpdateEventMovieTime = movieTime; |
| } |
| } |
| |
| bool HTMLMediaElement::togglePlayStateWillPlay() const |
| { |
| return paused(); |
| } |
| |
| void HTMLMediaElement::togglePlayState() |
| { |
| if (paused()) |
| play(); |
| else |
| pause(); |
| } |
| |
| bool HTMLMediaElement::havePotentialSourceChild() |
| { |
| // Stash the current <source> node and next nodes so we can restore them after checking |
| // to see there is another potential. |
| RefPtrWillBeRawPtr<HTMLSourceElement> currentSourceNode = m_currentSourceNode; |
| RefPtrWillBeRawPtr<Node> nextNode = m_nextChildNodeToConsider; |
| |
| KURL nextURL = selectNextSourceChild(0, 0, DoNothing); |
| |
| m_currentSourceNode = currentSourceNode; |
| m_nextChildNodeToConsider = nextNode; |
| |
| return nextURL.isValid(); |
| } |
| |
| KURL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* keySystem, InvalidURLAction actionIfInvalid) |
| { |
| #if !LOG_DISABLED |
| // Don't log if this was just called to find out if there are any valid <source> elements. |
| bool shouldLog = actionIfInvalid != DoNothing; |
| if (shouldLog) |
| WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild"); |
| #endif |
| |
| if (!m_nextChildNodeToConsider) { |
| #if !LOG_DISABLED |
| if (shouldLog) |
| WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild -> 0x0000, \"\""); |
| #endif |
| return KURL(); |
| } |
| |
| KURL mediaURL; |
| Node* node; |
| HTMLSourceElement* source = 0; |
| String type; |
| String system; |
| bool lookingForStartNode = m_nextChildNodeToConsider; |
| bool canUseSourceElement = false; |
| |
| NodeVector potentialSourceNodes; |
| getChildNodes(*this, potentialSourceNodes); |
| |
| for (unsigned i = 0; !canUseSourceElement && i < potentialSourceNodes.size(); ++i) { |
| node = potentialSourceNodes[i].get(); |
| if (lookingForStartNode && m_nextChildNodeToConsider != node) |
| continue; |
| lookingForStartNode = false; |
| |
| if (!isHTMLSourceElement(*node)) |
| continue; |
| if (node->parentNode() != this) |
| continue; |
| |
| 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 |
| mediaURL = source->getNonEmptyURLAttribute(HTMLNames::srcAttr); |
| #if !LOG_DISABLED |
| if (shouldLog) |
| WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'src' is %s", urlForLoggingMedia(mediaURL).utf8().data()); |
| #endif |
| if (mediaURL.isEmpty()) |
| goto check_again; |
| |
| type = source->type(); |
| // FIXME(82965): Add support for keySystem in <source> and set system from source. |
| if (type.isEmpty() && mediaURL.protocolIsData()) |
| type = mimeTypeFromDataURL(mediaURL); |
| if (!type.isEmpty() || !system.isEmpty()) { |
| #if !LOG_DISABLED |
| if (shouldLog) |
| WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild - 'type' is '%s' - key system is '%s'", type.utf8().data(), system.utf8().data()); |
| #endif |
| if (!supportsType(ContentType(type), system)) |
| goto check_again; |
| } |
| |
| // Is it safe to load this url? |
| if (!isSafeToLoadURL(mediaURL, actionIfInvalid)) |
| goto check_again; |
| |
| // Making it this far means the <source> looks reasonable. |
| canUseSourceElement = true; |
| |
| check_again: |
| if (!canUseSourceElement && actionIfInvalid == Complain && source) |
| source->scheduleErrorEvent(); |
| } |
| |
| if (canUseSourceElement) { |
| if (contentType) |
| *contentType = ContentType(type); |
| if (keySystem) |
| *keySystem = system; |
| m_currentSourceNode = source; |
| m_nextChildNodeToConsider = source->nextSibling(); |
| } else { |
| m_currentSourceNode = nullptr; |
| m_nextChildNodeToConsider = nullptr; |
| } |
| |
| #if !LOG_DISABLED |
| if (shouldLog) |
| WTF_LOG(Media, "HTMLMediaElement::selectNextSourceChild -> %p, %s", m_currentSourceNode.get(), canUseSourceElement ? urlForLoggingMedia(mediaURL).utf8().data() : ""); |
| #endif |
| return canUseSourceElement ? mediaURL : KURL(); |
| } |
| |
| void HTMLMediaElement::sourceWasAdded(HTMLSourceElement* source) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded(%p)", source); |
| |
| #if !LOG_DISABLED |
| KURL url = source->getNonEmptyURLAttribute(HTMLNames::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. |
| if (hasAttribute(HTMLNames::srcAttr)) |
| return; |
| |
| // 4.8.8 - If a source element is inserted as a child of a media element that has no src |
| // attribute and whose networkState has the value NETWORK_EMPTY, the user agent must invoke |
| // the media element's resource selection algorithm. |
| if (networkState() == HTMLMediaElement::NETWORK_EMPTY) { |
| scheduleDelayedAction(LoadMediaResource); |
| m_nextChildNodeToConsider = source; |
| return; |
| } |
| |
| if (m_currentSourceNode && source == m_currentSourceNode->nextSibling()) { |
| WTF_LOG(Media, "HTMLMediaElement::sourceWasAdded - <source> inserted immediately after current source"); |
| m_nextChildNodeToConsider = source; |
| return; |
| } |
| |
| if (m_nextChildNodeToConsider) |
| return; |
| |
| if (m_loadState != WaitingForSource) |
| return; |
| |
| // 4.8.9.5, resource selection algorithm, source elements section: |
| // 21. Wait until the node after pointer is a node other than the end of the list. (This step might wait forever.) |
| // 22. Asynchronously await a stable state... |
| // 23. 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); |
| |
| // 24. Set the networkState back to NETWORK_LOADING. |
| m_networkState = NETWORK_LOADING; |
| |
| // 25. Jump back to the find next candidate step above. |
| m_nextChildNodeToConsider = source; |
| scheduleNextSourceChild(); |
| } |
| |
| void HTMLMediaElement::sourceWasRemoved(HTMLSourceElement* source) |
| { |
| WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved(%p)", source); |
| |
| #if !LOG_DISABLED |
| KURL url = source->getNonEmptyURLAttribute(HTMLNames::srcAttr); |
| WTF_LOG(Media, "HTMLMediaElement::sourceWasRemoved - 'src' is %s", urlForLoggingMedia(url).utf8().data()); |
| #endif |
| |
| if (source != m_currentSourceNode && source != m_nextChildNodeToConsider) |
| return; |
| |
| if (source == m_nextChildNodeToConsider) { |
| if (m_currentSourceNode) |
| m_nextChildNodeToConsider = m_currentSourceNode->nextSibling(); |
| WTF_LOG(Media, "HTMLMediaElement::sourceRemoved - m_nextChildNodeToConsider set to %p", m_nextChildNodeToConsider.get()); |
| } else if (source == m_currentSourceNode) { |
| // Clear the current source node pointer, but don't change the movie as the spec says: |
| // 4.8.8 - Dynamically modifying a source element and its attribute when the element is already |
| // inserted in a video or audio element will have no effect. |
| m_currentSourceNode = nullptr; |
| WTF_LOG(Media, "HTMLMediaElement::sourceRemoved - m_currentSourceNode set to 0"); |
| } |
| } |
| |
| void HTMLMediaElement::mediaPlayerTimeChanged() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::mediaPlayerTimeChanged"); |
| |
| invalidateCachedTime(); |
| |
| // 4.8.10.9 steps 12-14. Needed if no ReadyState change is associated with the seek. |
| if (m_seeking && m_readyState >= HAVE_CURRENT_DATA && !webMediaPlayer()->seeking()) |
| finishSeek(); |
| |
| // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity, |
| // it will only queue a 'timeupdate' event if we haven't already posted one at the current |
| // movie time. |
| scheduleTimeupdateEvent(false); |
| |
| double now = currentTime(); |
| double dur = duration(); |
| |
| // When the current playback position reaches the end of the media resource when the direction of |
| // playback is forwards, then the user agent must follow these steps: |
| if (!std::isnan(dur) && dur && now >= dur && directionOfPlayback() == Forward) { |
| // If the media element has a loop attribute specified and does not have a current media controller, |
| if (loop()) { |
| m_sentEndEvent = false; |
| // then seek to the earliest possible position of the media resource and abort these steps. |
| seek(0, IGNORE_EXCEPTION); |
| } else { |
| // If the media element does not have a current media controller, and the media element |
| // has still ended playback, and the direction of playback is still forwards, and paused |
| // is false, |
| if (!m_paused) { |
| // changes paused to true and fires a simple event named pause at the media element. |
| m_paused = true; |
| scheduleEvent(EventTypeNames::pause); |
| } |
| // Queue a task to fire a simple event named ended at the media element. |
| if (!m_sentEndEvent) { |
| m_sentEndEvent = true; |
| scheduleEvent(EventTypeNames::ended); |
| } |
| } |
| } |
| else |
| m_sentEndEvent = false; |
| |
| updatePlayState(); |
| } |
| |
| void HTMLMediaElement::mediaPlayerDurationChanged() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::mediaPlayerDurationChanged"); |
| // 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, bool requestSeek) |
| { |
| 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); |
| |
| if (renderer()) |
| renderer()->updateFromElement(); |
| |
| if (requestSeek) |
| seek(duration, IGNORE_EXCEPTION); |
| } |
| |
| void HTMLMediaElement::mediaPlayerPlaybackStateChanged() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::mediaPlayerPlaybackStateChanged"); |
| |
| if (!m_player || m_pausedInternal) |
| return; |
| |
| if (webMediaPlayer()->paused()) |
| pause(); |
| else |
| playInternal(); |
| } |
| |
| void HTMLMediaElement::mediaPlayerRequestFullscreen() |
| { |
| // FIXME(sky): How do we go full screen now? |
| } |
| |
| void HTMLMediaElement::mediaPlayerRequestSeek(double time) |
| { |
| setCurrentTime(time, IGNORE_EXCEPTION); |
| } |
| |
| // MediaPlayerPresentation methods |
| void HTMLMediaElement::mediaPlayerRepaint() |
| { |
| if (m_webLayer) |
| m_webLayer->invalidate(); |
| |
| updateDisplayState(); |
| if (renderer()) |
| renderer()->setShouldDoFullPaintInvalidation(true); |
| } |
| |
| void HTMLMediaElement::mediaPlayerSizeChanged() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::mediaPlayerSizeChanged"); |
| |
| ASSERT(hasVideo()); // "resize" makes no sense absent video. |
| if (m_readyState > HAVE_NOTHING && isHTMLVideoElement()) |
| scheduleEvent(EventTypeNames::resize); |
| |
| if (renderer()) |
| renderer()->updateFromElement(); |
| } |
| |
| PassRefPtrWillBeRawPtr<TimeRanges> HTMLMediaElement::buffered() const |
| { |
| if (m_mediaSource) |
| return m_mediaSource->buffered(); |
| |
| if (!webMediaPlayer()) |
| return TimeRanges::create(); |
| |
| return TimeRanges::create(webMediaPlayer()->buffered()); |
| } |
| |
| PassRefPtrWillBeRawPtr<TimeRanges> HTMLMediaElement::played() |
| { |
| if (m_playing) { |
| double time = currentTime(); |
| if (time > m_lastSeekTime) |
| addPlayedRange(m_lastSeekTime, time); |
| } |
| |
| if (!m_playedTimeRanges) |
| m_playedTimeRanges = TimeRanges::create(); |
| |
| return m_playedTimeRanges->copy(); |
| } |
| |
| PassRefPtrWillBeRawPtr<TimeRanges> HTMLMediaElement::seekable() const |
| { |
| if (webMediaPlayer()) { |
| double maxTimeSeekable = webMediaPlayer()->maxTimeSeekable(); |
| if (maxTimeSeekable) |
| return TimeRanges::create(0, maxTimeSeekable); |
| } |
| return TimeRanges::create(); |
| } |
| |
| bool HTMLMediaElement::potentiallyPlaying() const |
| { |
| // "pausedToBuffer" means the media engine's rate is 0, but only because it had to stop playing |
| // when it ran out of buffered data. A movie is this state is "potentially playing", modulo the |
| // checks in couldPlayIfEnoughData(). |
| bool pausedToBuffer = m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA; |
| return (pausedToBuffer || m_readyState >= HAVE_FUTURE_DATA) && couldPlayIfEnoughData(); |
| } |
| |
| bool HTMLMediaElement::couldPlayIfEnoughData() const |
| { |
| return !paused() && !endedPlayback() && !stoppedDueToErrors(); |
| } |
| |
| bool HTMLMediaElement::endedPlayback() const |
| { |
| double dur = duration(); |
| if (!m_player || std::isnan(dur)) |
| return false; |
| |
| // 4.8.10.8 Playing the media resource |
| |
| // A media element is said to have ended playback when the element's |
| // readyState attribute is HAVE_METADATA or greater, |
| if (m_readyState < HAVE_METADATA) |
| return false; |
| |
| // and the current playback position is the end of the media resource and the direction |
| // of playback is forwards, Either the media element does not have a loop attribute specified, |
| // or the media element has a current media controller. |
| double now = currentTime(); |
| if (directionOfPlayback() == Forward) |
| return dur > 0 && now >= dur && !loop(); |
| |
| // or the current playback position is the earliest possible position and the direction |
| // of playback is backwards |
| ASSERT(directionOfPlayback() == Backward); |
| return now <= 0; |
| } |
| |
| bool HTMLMediaElement::stoppedDueToErrors() const |
| { |
| if (m_readyState >= HAVE_METADATA && m_error) { |
| RefPtrWillBeRawPtr<TimeRanges> seekableRanges = seekable(); |
| if (!seekableRanges->contain(currentTime())) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void HTMLMediaElement::updatePlayState() |
| { |
| if (!m_player) |
| return; |
| |
| bool isPlaying = webMediaPlayer() && !webMediaPlayer()->paused(); |
| if (m_pausedInternal) { |
| if (isPlaying) |
| webMediaPlayer()->pause(); |
| refreshCachedTime(); |
| m_playbackProgressTimer.stop(); |
| return; |
| } |
| |
| bool shouldBePlaying = potentiallyPlaying(); |
| |
| WTF_LOG(Media, "HTMLMediaElement::updatePlayState - shouldBePlaying = %s, isPlaying = %s", |
| boolString(shouldBePlaying), boolString(isPlaying)); |
| |
| if (shouldBePlaying) { |
| setDisplayMode(Video); |
| invalidateCachedTime(); |
| |
| if (!isPlaying) { |
| // 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. |
| webMediaPlayer()->setRate(effectivePlaybackRate()); |
| updateVolume(); |
| webMediaPlayer()->play(); |
| } |
| |
| startPlaybackProgressTimer(); |
| m_playing = true; |
| |
| } else { // Should not be playing right now |
| if (isPlaying) |
| webMediaPlayer()->pause(); |
| refreshCachedTime(); |
| |
| m_playbackProgressTimer.stop(); |
| m_playing = false; |
| double time = currentTime(); |
| if (time > m_lastSeekTime) |
| addPlayedRange(m_lastSeekTime, time); |
| |
| if (couldPlayIfEnoughData()) |
| prepareToPlay(); |
| } |
| |
| if (renderer()) |
| renderer()->updateFromElement(); |
| } |
| |
| void HTMLMediaElement::setPausedInternal(bool b) |
| { |
| m_pausedInternal = b; |
| updatePlayState(); |
| } |
| |
| void HTMLMediaElement::stopPeriodicTimers() |
| { |
| m_progressEventTimer.stop(); |
| m_playbackProgressTimer.stop(); |
| } |
| |
| void HTMLMediaElement::userCancelledLoad() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::userCancelledLoad"); |
| |
| // If the media data fetching process is aborted by the user: |
| |
| // 1 - The user agent should cancel the fetching process. |
| clearMediaPlayer(-1); |
| |
| if (m_networkState == NETWORK_EMPTY || m_completelyLoaded) |
| return; |
| |
| // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED. |
| m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED); |
| |
| // 3 - Queue a task to fire a simple event named error at the media element. |
| scheduleEvent(EventTypeNames::abort); |
| |
| closeMediaSource(); |
| |
| // 4 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the |
| // element's networkState attribute to the NETWORK_EMPTY value and queue a task to fire a |
| // simple event named emptied at the element. Otherwise, set the element's networkState |
| // attribute to the NETWORK_IDLE value. |
| if (m_readyState == HAVE_NOTHING) { |
| m_networkState = NETWORK_EMPTY; |
| scheduleEvent(EventTypeNames::emptied); |
| } |
| else |
| m_networkState = NETWORK_IDLE; |
| |
| // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. |
| setShouldDelayLoadEvent(false); |
| |
| // 6 - Abort the overall resource selection algorithm. |
| m_currentSourceNode = nullptr; |
| |
| // Reset m_readyState since m_player is gone. |
| m_readyState = HAVE_NOTHING; |
| invalidateCachedTime(); |
| } |
| |
| void HTMLMediaElement::clearMediaPlayerAndAudioSourceProviderClientWithoutLocking() |
| { |
| m_player.clear(); |
| } |
| |
| void HTMLMediaElement::clearMediaPlayer(int flags) |
| { |
| closeMediaSource(); |
| |
| cancelDeferredLoad(); |
| clearMediaPlayerAndAudioSourceProviderClientWithoutLocking(); |
| |
| stopPeriodicTimers(); |
| m_loadTimer.stop(); |
| |
| m_pendingActionFlags &= ~flags; |
| m_loadState = WaitingForSource; |
| } |
| |
| void HTMLMediaElement::stop() |
| { |
| WTF_LOG(Media, "HTMLMediaElement::stop"); |
| |
| m_active = false; |
| userCancelledLoad(); |
| |
| // Stop the playback without generating events |
| m_playing = false; |
| setPausedInternal(true); |
| |
| if (renderer()) |
| renderer()->updateFromElement(); |
| |
| stopPeriodicTimers(); |
| cancelPendingEventsAndCallbacks(); |
| |
| m_asyncEventQueue->close(); |
| } |
| |
| bool HTMLMediaElement::hasPendingActivity() const |
| { |
| return (hasAudio() && isPlaying()) || m_asyncEventQueue->hasPendingEvents(); |
| } |
| |
| void HTMLMediaElement::contextDestroyed() |
| { |
| ActiveDOMObject::contextDestroyed(); |
| } |
| |
| bool HTMLMediaElement::isFullscreen() const |
| { |
| // FIXME(sky): How does video go full screen now? |
| return false; |
| } |
| |
| void HTMLMediaElement::enterFullscreen() |
| { |
| } |
| |
| void HTMLMediaElement::exitFullscreen() |
| { |
| } |
| |
| blink::WebLayer* HTMLMediaElement::platformLayer() const |
| { |
| return m_webLayer; |
| } |
| |
| bool HTMLMediaElement::isURLAttribute(const Attribute& attribute) const |
| { |
| return attribute.name() == HTMLNames::srcAttr || HTMLElement::isURLAttribute(attribute); |
| } |
| |
| void HTMLMediaElement::setShouldDelayLoadEvent(bool shouldDelay) |
| { |
| if (m_shouldDelayLoadEvent == shouldDelay) |
| return; |
| |
| WTF_LOG(Media, "HTMLMediaElement::setShouldDelayLoadEvent(%s)", boolString(shouldDelay)); |
| |
| m_shouldDelayLoadEvent = shouldDelay; |
| if (shouldDelay) |
| document().incrementLoadEventDelayCount(); |
| else |
| document().decrementLoadEventDelayCount(); |
| } |
| |
| void* HTMLMediaElement::preDispatchEventHandler(Event* event) |
| { |
| return 0; |
| } |
| |
| void HTMLMediaElement::createMediaPlayer() |
| { |
| closeMediaSource(); |
| m_player = MediaPlayer::create(this); |
| } |
| |
| const AtomicString& HTMLMediaElement::mediaGroup() const |
| { |
| return getAttribute(HTMLNames::mediagroupAttr); |
| } |
| |
| 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; |
| |
| return false; |
| } |
| |
| void HTMLMediaElement::prepareMediaFragmentURI() |
| { |
| MediaFragmentURIParser fragmentParser(m_currentSrc); |
| double dur = duration(); |
| |
| double start = fragmentParser.startTime(); |
| if (start != MediaFragmentURIParser::invalidTimeValue() && start > 0) { |
| m_fragmentStartTime = start; |
| if (m_fragmentStartTime > dur) |
| m_fragmentStartTime = dur; |
| } else |
| m_fragmentStartTime = MediaPlayer::invalidTime(); |
| |
| double end = fragmentParser.endTime(); |
| if (end != MediaFragmentURIParser::invalidTimeValue() && end > 0 && end > m_fragmentStartTime) { |
| m_fragmentEndTime = end; |
| if (m_fragmentEndTime > dur) |
| m_fragmentEndTime = dur; |
| } else |
| m_fragmentEndTime = MediaPlayer::invalidTime(); |
| |
| if (m_fragmentStartTime != MediaPlayer::invalidTime() && m_readyState < HAVE_FUTURE_DATA) |
| prepareToPlay(); |
| } |
| |
| void HTMLMediaElement::applyMediaFragmentURI() |
| { |
| if (m_fragmentStartTime != MediaPlayer::invalidTime()) { |
| m_sentEndEvent = false; |
| UseCounter::count(document(), UseCounter::HTMLMediaElementSeekToFragmentStart); |
| seek(m_fragmentStartTime, IGNORE_EXCEPTION); |
| } |
| } |
| |
| WebMediaPlayer::CORSMode HTMLMediaElement::corsMode() const |
| { |
| const AtomicString& crossOriginMode = getAttribute(HTMLNames::crossoriginAttr); |
| if (crossOriginMode.isNull()) |
| return WebMediaPlayer::CORSModeUnspecified; |
| if (equalIgnoringCase(crossOriginMode, "use-credentials")) |
| return WebMediaPlayer::CORSModeUseCredentials; |
| return WebMediaPlayer::CORSModeAnonymous; |
| } |
| |
| void HTMLMediaElement::mediaPlayerSetWebLayer(blink::WebLayer* webLayer) |
| { |
| if (webLayer == m_webLayer) |
| return; |
| |
| // 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) |
| setNeedsCompositingUpdate(); |
| |
| if (m_webLayer) |
| GraphicsLayer::unregisterContentsLayer(m_webLayer); |
| m_webLayer = webLayer; |
| if (m_webLayer) { |
| GraphicsLayer::registerContentsLayer(m_webLayer); |
| } |
| } |
| |
| void HTMLMediaElement::mediaPlayerMediaSourceOpened(blink::WebMediaSource* webMediaSource) |
| { |
| m_mediaSource->setWebMediaSourceAndOpen(adoptPtr(webMediaSource)); |
| } |
| |
| bool HTMLMediaElement::isInteractiveContent() const |
| { |
| return hasAttribute(HTMLNames::controlsAttr); |
| } |
| |
| void HTMLMediaElement::defaultEventHandler(Event* event) |
| { |
| HTMLElement::defaultEventHandler(event); |
| } |
| |
| void HTMLMediaElement::trace(Visitor* visitor) |
| { |
| visitor->trace(m_playedTimeRanges); |
| visitor->trace(m_asyncEventQueue); |
| visitor->trace(m_error); |
| visitor->trace(m_currentSourceNode); |
| visitor->trace(m_nextChildNodeToConsider); |
| visitor->trace(m_mediaSource); |
| WillBeHeapSupplementable<HTMLMediaElement>::trace(visitor); |
| HTMLElement::trace(visitor); |
| } |
| |
| } |