diff options
Diffstat (limited to 'src/3rdparty/webkit/WebCore/html/HTMLMediaElement.cpp')
-rw-r--r-- | src/3rdparty/webkit/WebCore/html/HTMLMediaElement.cpp | 1418 |
1 files changed, 949 insertions, 469 deletions
diff --git a/src/3rdparty/webkit/WebCore/html/HTMLMediaElement.cpp b/src/3rdparty/webkit/WebCore/html/HTMLMediaElement.cpp index df38f014fd..6ad0653d1d 100644 --- a/src/3rdparty/webkit/WebCore/html/HTMLMediaElement.cpp +++ b/src/3rdparty/webkit/WebCore/html/HTMLMediaElement.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2007, 2008, 2009 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -31,25 +31,37 @@ #include "CSSHelper.h" #include "CSSPropertyNames.h" #include "CSSValueKeywords.h" +#include "ContentType.h" +#include "DocLoader.h" #include "Event.h" #include "EventNames.h" #include "ExceptionCode.h" +#include "Frame.h" +#include "FrameLoader.h" #include "HTMLDocument.h" #include "HTMLNames.h" #include "HTMLSourceElement.h" #include "HTMLVideoElement.h" -#include <limits> +#include "MIMETypeRegistry.h" +#include "MappedAttribute.h" +#include "MediaDocument.h" #include "MediaError.h" #include "MediaList.h" -#include "MediaQueryEvaluator.h" -#include "MIMETypeRegistry.h" #include "MediaPlayer.h" +#include "MediaQueryEvaluator.h" #include "Page.h" +#include "ProgressEvent.h" #include "RenderVideo.h" -#include "SystemTime.h" #include "TimeRanges.h" +#include <limits> +#include <wtf/CurrentTime.h> #include <wtf/MathExtras.h> +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) +#include "RenderPartObject.h" +#include "Widget.h" +#endif + using namespace std; namespace WebCore { @@ -61,27 +73,39 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* doc) , m_loadTimer(this, &HTMLMediaElement::loadTimerFired) , m_asyncEventTimer(this, &HTMLMediaElement::asyncEventTimerFired) , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired) + , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired) + , m_playedTimeRanges() + , m_playbackRate(1.0f) , m_defaultPlaybackRate(1.0f) - , m_networkState(EMPTY) - , m_readyState(DATA_UNAVAILABLE) - , m_begun(false) - , m_loadedFirstFrame(false) - , m_autoplaying(true) - , m_currentLoop(0) + , m_networkState(NETWORK_EMPTY) + , m_readyState(HAVE_NOTHING) , m_volume(1.0f) + , m_lastSeekTime(0) + , m_previousProgress(0) + , m_previousProgressTime(numeric_limits<double>::max()) + , m_lastTimeUpdateEventWallTime(0) + , m_lastTimeUpdateEventMovieTime(numeric_limits<float>::max()) + , m_loadState(WaitingForSource) + , m_currentSourceNode(0) + , m_player(0) + , m_restrictions(NoRestrictions) + , m_playing(false) + , m_processingMediaPlayerCallback(0) + , m_processingLoad(false) + , m_delayingTheLoadEvent(false) + , m_haveFiredLoadedData(false) + , m_inActiveDocument(true) + , m_autoplaying(true) , m_muted(false) , m_paused(true) , m_seeking(false) - , m_currentTimeDuringSeek(0) - , m_previousProgress(0) - , m_previousProgressTime(numeric_limits<double>::max()) , m_sentStalledEvent(false) - , m_bufferingRate(0) - , m_loadNestingLevel(0) - , m_terminateLoadBelowNestingLevel(0) + , m_sentEndEvent(false) , m_pausedInternal(false) - , m_inActiveDocument(true) - , m_player(0) + , m_sendProgressEvents(true) +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + , m_needWidgetUpdate(false) +#endif { document()->registerForDocumentActivationCallbacks(this); document()->registerForMediaVolumeCallbacks(this); @@ -104,11 +128,12 @@ void HTMLMediaElement::attributeChanged(Attribute* attr, bool preserveDecls) const QualifiedName& attrName = attr->name(); if (attrName == srcAttr) { - // 3.14.9.2. - // change to src attribute triggers load() - if (inDocument() && m_networkState == EMPTY) + // don't have a src or any <source> children, trigger load + if (inDocument() && m_loadState == WaitingForSource) scheduleLoad(); - } if (attrName == controlsAttr) { + } +#if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) + else if (attrName == controlsAttr) { if (!isVideo() && attached() && (controls() != (renderer() != 0))) { detach(); attach(); @@ -116,16 +141,39 @@ void HTMLMediaElement::attributeChanged(Attribute* attr, bool preserveDecls) if (renderer()) renderer()->updateFromElement(); } +#endif } - + +void HTMLMediaElement::parseMappedAttribute(MappedAttribute *attr) +{ + if (attr->name() == autobufferAttr) { + if (m_player) + m_player->setAutobuffer(!attr->isNull()); + } else + HTMLElement::parseMappedAttribute(attr); +} + bool HTMLMediaElement::rendererIsNeeded(RenderStyle* style) { +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + UNUSED_PARAM(style); + Frame* frame = document()->frame(); + if (!frame) + return false; + + return true; +#else return controls() ? HTMLElement::rendererIsNeeded(style) : false; +#endif } RenderObject* HTMLMediaElement::createRenderer(RenderArena* arena, RenderStyle*) { +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + return new (arena) RenderPartObject(this); +#else return new (arena) RenderMedia(this); +#endif } void HTMLMediaElement::insertedIntoDocument() @@ -137,10 +185,8 @@ void HTMLMediaElement::insertedIntoDocument() void HTMLMediaElement::removedFromDocument() { - // FIXME: pause() may invoke load() which seem like a strange thing to do as a side effect - // of removing an element. This might need to be fixed in the spec. - ExceptionCode ec; - pause(ec); + if (m_networkState > NETWORK_EMPTY) + pause(); HTMLElement::removedFromDocument(); } @@ -148,6 +194,10 @@ void HTMLMediaElement::attach() { ASSERT(!attached()); +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + m_needWidgetUpdate = true; +#endif + HTMLElement::attach(); if (renderer()) @@ -167,39 +217,56 @@ void HTMLMediaElement::scheduleLoad() m_loadTimer.startOneShot(0); } -void HTMLMediaElement::initAndDispatchProgressEvent(const AtomicString& eventName) +void HTMLMediaElement::scheduleProgressEvent(const AtomicString& eventName) { + if (!m_sendProgressEvents) + return; + + // FIXME: don't schedule timeupdate or progress events unless there are registered listeners + bool totalKnown = m_player && m_player->totalBytesKnown(); unsigned loaded = m_player ? m_player->bytesLoaded() : 0; unsigned total = m_player ? m_player->totalBytes() : 0; - dispatchProgressEvent(eventName, totalKnown, loaded, total); + + RefPtr<ProgressEvent> evt = ProgressEvent::create(eventName, totalKnown, loaded, total); + enqueueEvent(evt); + if (renderer()) renderer()->updateFromElement(); } -void HTMLMediaElement::dispatchEventAsync(const AtomicString& eventName) +void HTMLMediaElement::scheduleEvent(const AtomicString& eventName) { - m_asyncEventsToDispatch.append(eventName); - if (!m_asyncEventTimer.isActive()) - m_asyncEventTimer.startOneShot(0); + enqueueEvent(Event::create(eventName, false, true)); } -void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*) +void HTMLMediaElement::enqueueEvent(RefPtr<Event> event) { - ExceptionCode ec; - load(ec); + m_pendingEvents.append(event); + if (!m_asyncEventTimer.isActive()) + m_asyncEventTimer.startOneShot(0); } void HTMLMediaElement::asyncEventTimerFired(Timer<HTMLMediaElement>*) { - Vector<AtomicString> asyncEventsToDispatch; - m_asyncEventsToDispatch.swap(asyncEventsToDispatch); - unsigned count = asyncEventsToDispatch.size(); - for (unsigned n = 0; n < count; ++n) - dispatchEventForType(asyncEventsToDispatch[n], false, true); + Vector<RefPtr<Event> > pendingEvents; + ExceptionCode ec = 0; + + m_pendingEvents.swap(pendingEvents); + unsigned count = pendingEvents.size(); + for (unsigned ndx = 0; ndx < count; ++ndx) + dispatchEvent(pendingEvents[ndx].release(), ec); } -String serializeTimeOffset(float time) +void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*) +{ + if (m_loadState == LoadingFromSourceElement) + loadNextSourceChild(); + else + loadInternal(); +} + +static String serializeTimeOffset(float time) { String timeString = String::number(time); // FIXME serialize time offset values properly (format not specified yet) @@ -207,7 +274,7 @@ String serializeTimeOffset(float time) return timeString; } -float parseTimeOffset(const String& timeString, bool* ok = 0) +static float parseTimeOffset(const String& timeString, bool* ok = 0) { const UChar* characters = timeString.characters(); unsigned length = timeString.length(); @@ -260,252 +327,456 @@ HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const return m_networkState; } -float HTMLMediaElement::bufferingRate() +String HTMLMediaElement::canPlayType(const String& mimeType) const { - if (!m_player) - return 0; - return m_bufferingRate; - //return m_player->dataRate(); + MediaPlayer::SupportsType support = MediaPlayer::supportsType(ContentType(mimeType)); + String canPlay; + + // 4.8.10.3 + switch (support) + { + case MediaPlayer::IsNotSupported: + canPlay = "no"; + break; + case MediaPlayer::MayBeSupported: + canPlay = "maybe"; + break; + case MediaPlayer::IsSupported: + canPlay = "probably"; + break; + } + + return canPlay; } void HTMLMediaElement::load(ExceptionCode& ec) { - String mediaSrc; - - // 3.14.9.4. Loading the media resource - // 1 - // if an event generated during load() ends up re-entering load(), terminate previous instances - m_loadNestingLevel++; - m_terminateLoadBelowNestingLevel = m_loadNestingLevel; - - m_progressEventTimer.stop(); - m_sentStalledEvent = false; - m_bufferingRate = 0; + if (m_restrictions & RequireUserGestureForLoadRestriction && !processingUserGesture()) + ec = INVALID_STATE_ERR; + else + loadInternal(); +} + +void HTMLMediaElement::loadInternal() +{ + // 1 - If the load() method for this element is already being invoked, then abort these steps. + if (m_processingLoad) + return; + m_processingLoad = true; + stopPeriodicTimers(); m_loadTimer.stop(); + m_sentStalledEvent = false; + m_haveFiredLoadedData = false; + + // 2 - Abort any already-running instance of the resource selection algorithm for this element. + m_currentSourceNode = 0; + + // 3 - 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(); - // 2 - if (m_begun) { - m_begun = false; + // 4 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, set the + // error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED, + // and fire a progress event called abort at the media element. + if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE) { m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED); - initAndDispatchProgressEvent(eventNames().abortEvent); - if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel) - goto end; + + // fire synchronous 'abort' + bool totalKnown = m_player && m_player->totalBytesKnown(); + unsigned loaded = m_player ? m_player->bytesLoaded() : 0; + unsigned total = m_player ? m_player->totalBytes() : 0; + dispatchProgressEvent(eventNames().abortEvent, totalKnown, loaded, total); } - // 3 + // 5 m_error = 0; - m_loadedFirstFrame = false; m_autoplaying = true; + m_playedTimeRanges = TimeRanges::create(); + m_lastSeekTime = 0; + + // 6 + setPlaybackRate(defaultPlaybackRate()); - // 4 - setPlaybackRate(defaultPlaybackRate(), ec); - - // 5 - if (networkState() != EMPTY) { - m_networkState = EMPTY; - m_readyState = DATA_UNAVAILABLE; + // 7 + if (m_networkState != NETWORK_EMPTY) { + m_networkState = NETWORK_EMPTY; + m_readyState = HAVE_NOTHING; m_paused = true; m_seeking = false; if (m_player) { m_player->pause(); + m_playing = false; m_player->seek(0); } - m_currentLoop = 0; - dispatchEventForType(eventNames().emptiedEvent, false, true); - if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel) - goto end; + dispatchEvent(eventNames().emptiedEvent, false, true); } - // 6 - mediaSrc = pickMedia(); - if (mediaSrc.isEmpty()) { - ec = INVALID_STATE_ERR; - goto end; + selectMediaResource(); + m_processingLoad = false; +} + +void HTMLMediaElement::selectMediaResource() +{ + // 1 - If the media element has neither a src attribute nor any source element children, run these substeps + String mediaSrc = getAttribute(srcAttr); + if (!mediaSrc && !havePotentialSourceChild()) { + m_loadState = WaitingForSource; + + // 1 - Set the networkState to NETWORK_NO_SOURCE + m_networkState = NETWORK_NO_SOURCE; + + // 2 - While the media element has neither a src attribute nor any source element children, + // wait. (This steps might wait forever.) + + m_delayingTheLoadEvent = false; + return; } - - // 7 - m_networkState = LOADING; - - // 8 - m_currentSrc = mediaSrc; - - // 9 - m_begun = true; - dispatchProgressEvent(eventNames().loadstartEvent, false, 0, 0); - if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel) - goto end; - - // 10, 11, 12, 13 + + // 2 + m_delayingTheLoadEvent = true; + + // 3 + m_networkState = NETWORK_LOADING; + + // 4 + scheduleProgressEvent(eventNames().loadstartEvent); + + // 5 - If the media element has a src attribute, then run these substeps + ContentType contentType(""); + if (!mediaSrc.isEmpty()) { + KURL mediaURL = document()->completeURL(mediaSrc); + if (isSafeToLoadURL(mediaURL, Complain)) { + m_loadState = LoadingFromSrcAttr; + loadResource(mediaURL, contentType); + } else + noneSupported(); + + return; + } + + // Otherwise, the source elements will be used + m_currentSourceNode = 0; + loadNextSourceChild(); +} + +void HTMLMediaElement::loadNextSourceChild() +{ + ContentType contentType(""); + KURL mediaURL = selectNextSourceChild(&contentType, Complain); + if (!mediaURL.isValid()) { + // It seems wrong to fail silently when we give up because no suitable <source> + // element can be found and set the error attribute if the element's 'src' attribute + // fails, but that is what the spec says. + return; + } + + m_loadState = LoadingFromSourceElement; + loadResource(mediaURL, contentType); +} + +void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType) +{ + ASSERT(isSafeToLoadURL(url, Complain)); + + // The resource fetch algorithm + m_networkState = NETWORK_LOADING; + + m_currentSrc = url; + + if (m_sendProgressEvents) + startProgressEventTimer(); + +#if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) m_player.clear(); m_player.set(new MediaPlayer(this)); +#else + if (!m_player) + m_player.set(new MediaPlayer(this)); +#endif + updateVolume(); - m_player->load(m_currentSrc); - if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel) - goto end; + + m_player->load(m_currentSrc, contentType); if (renderer()) renderer()->updateFromElement(); +} + +bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidSourceAction actionIfInvalid) +{ + Frame* frame = document()->frame(); + FrameLoader* loader = frame ? frame->loader() : 0; + + // don't allow remote to local urls + if (!loader || !loader->canLoad(url, String(), document())) { + if (actionIfInvalid == Complain) + FrameLoader::reportLocalLoadFailed(frame, url.string()); + return false; + } - // 14 - m_previousProgressTime = WebCore::currentTime(); + return true; +} + +void HTMLMediaElement::startProgressEventTimer() +{ + if (m_progressEventTimer.isActive()) + return; + + m_previousProgressTime = WTF::currentTime(); m_previousProgress = 0; - if (m_begun) - // 350ms is not magic, it is in the spec! - m_progressEventTimer.startRepeating(0.350); -end: - ASSERT(m_loadNestingLevel); - m_loadNestingLevel--; + // 350ms is not magic, it is in the spec! + m_progressEventTimer.startRepeating(0.350); +} + +void HTMLMediaElement::noneSupported() +{ + stopPeriodicTimers(); + m_loadState = WaitingForSource; + m_currentSourceNode = 0; + + // 3 - Reaching this step indicates that either the URL failed to resolve, or the media + // resource failed to load. 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); + + // 4- Set the element's networkState attribute to the NETWORK_NO_SOURCE value. + m_networkState = NETWORK_NO_SOURCE; + + // 5 - Queue a task to fire a progress event called error at the media element. + scheduleProgressEvent(eventNames().errorEvent); + + // 6 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. + m_delayingTheLoadEvent = false; + + // Abort these steps. Until the load() method is invoked, the element won't attempt to load another resource. + + if (isVideo()) + static_cast<HTMLVideoElement*>(this)->updatePosterImage(); + if (renderer()) + renderer()->updateFromElement(); +} + +void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err) +{ + // 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 progress event called error at the media element. + scheduleProgressEvent(eventNames().errorEvent); + + // 3 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a + // task to fire a simple event called emptied at the element. + m_networkState = NETWORK_EMPTY; + scheduleEvent(eventNames().emptiedEvent); + + // 4 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. + m_delayingTheLoadEvent = false; + + // 5 - Abort the overall resource selection algorithm. + m_currentSourceNode = 0; + +} + +void HTMLMediaElement::cancelPendingEventsAndCallbacks() +{ + m_pendingEvents.clear(); + + for (Node* node = firstChild(); node; node = node->nextSibling()) { + if (node->hasTagName(sourceTag)) + static_cast<HTMLSourceElement*>(node)->cancelPendingErrorEvent(); + } } void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*) { - if (!m_begun || m_networkState == EMPTY) + beginProcessingMediaPlayerCallback(); + setNetworkState(m_player->networkState()); + endProcessingMediaPlayerCallback(); +} + +void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state) +{ + if (state == MediaPlayer::Empty) { + // just update the cached state and leave, we can't do anything + m_networkState = NETWORK_EMPTY; return; - - m_terminateLoadBelowNestingLevel = m_loadNestingLevel; + } - MediaPlayer::NetworkState state = m_player->networkState(); - - // 3.14.9.4. Loading the media resource - // 14 - if (state == MediaPlayer::LoadFailed) { - //delete m_player; - //m_player = 0; - // FIXME better error handling - m_error = MediaError::create(MediaError::MEDIA_ERR_NETWORK); - m_begun = false; - m_progressEventTimer.stop(); - m_bufferingRate = 0; - - initAndDispatchProgressEvent(eventNames().errorEvent); - if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel) + if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) { + 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) { + m_currentSourceNode->scheduleErrorEvent(); + if (havePotentialSourceChild()) + scheduleLoad(); return; - - m_networkState = EMPTY; - + } + + if (state == MediaPlayer::NetworkError) + mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK)); + else if (state == MediaPlayer::DecodeError) + mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE)); + else if (state == MediaPlayer::FormatError && m_loadState == LoadingFromSrcAttr) + noneSupported(); + if (isVideo()) static_cast<HTMLVideoElement*>(this)->updatePosterImage(); - dispatchEventForType(eventNames().emptiedEvent, false, true); return; } - - if (state >= MediaPlayer::Loading && m_networkState < LOADING) - m_networkState = LOADING; - - if (state >= MediaPlayer::LoadedMetaData && m_networkState < LOADED_METADATA) { - m_player->seek(effectiveStart()); - m_networkState = LOADED_METADATA; - - dispatchEventForType(eventNames().durationchangeEvent, false, true); - if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel) - return; - - dispatchEventForType(eventNames().loadedmetadataEvent, false, true); - if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel) - return; - } - - if (state >= MediaPlayer::LoadedFirstFrame && m_networkState < LOADED_FIRST_FRAME) { - m_networkState = LOADED_FIRST_FRAME; - - setReadyState(CAN_SHOW_CURRENT_FRAME); - - if (isVideo()) - static_cast<HTMLVideoElement*>(this)->updatePosterImage(); - - if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel) - return; - - m_loadedFirstFrame = true; - if (renderer()) { - ASSERT(!renderer()->isImage()); - static_cast<RenderVideo*>(renderer())->videoSizeChanged(); + + if (state == MediaPlayer::Idle) { + ASSERT(static_cast<ReadyState>(m_player->readyState()) < HAVE_ENOUGH_DATA); + if (m_networkState > NETWORK_IDLE) { + stopPeriodicTimers(); + scheduleProgressEvent(eventNames().suspendEvent); } - - dispatchEventForType(eventNames().loadedfirstframeEvent, false, true); - if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel) - return; - - dispatchEventForType(eventNames().canshowcurrentframeEvent, false, true); - if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel) - return; + m_networkState = NETWORK_IDLE; } - - // 15 - if (state == MediaPlayer::Loaded && m_networkState < LOADED) { - m_begun = false; - m_networkState = LOADED; - m_progressEventTimer.stop(); - m_bufferingRate = 0; - initAndDispatchProgressEvent(eventNames().loadEvent); + + if (state == MediaPlayer::Loading) { + if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE) + startProgressEventTimer(); + m_networkState = NETWORK_LOADING; + } + + if (state == MediaPlayer::Loaded) { + NetworkState oldState = m_networkState; + + m_networkState = NETWORK_LOADED; + if (oldState < NETWORK_LOADED || oldState == NETWORK_NO_SOURCE) { + m_progressEventTimer.stop(); + + // Check to see if readyState changes need to be dealt with before sending the + // 'load' event so we report 'canplaythrough' first. This is necessary because a + // media engine reports readyState and networkState changes separately + MediaPlayer::ReadyState currentState = m_player->readyState(); + if (static_cast<ReadyState>(currentState) != m_readyState) + setReadyState(currentState); + + scheduleProgressEvent(eventNames().loadEvent); + } } } void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*) { - MediaPlayer::ReadyState state = m_player->readyState(); - setReadyState((ReadyState)state); + beginProcessingMediaPlayerCallback(); + + setReadyState(m_player->readyState()); + + endProcessingMediaPlayerCallback(); } -void HTMLMediaElement::setReadyState(ReadyState state) +void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) { - // 3.14.9.6. The ready states - if (m_readyState == state) + // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it + bool wasPotentiallyPlaying = potentiallyPlaying(); + + ReadyState oldState = m_readyState; + m_readyState = static_cast<ReadyState>(state); + + if (m_readyState == oldState) return; - bool wasActivelyPlaying = activelyPlaying(); - m_readyState = state; - - if (state >= CAN_PLAY) + if (m_readyState >= HAVE_CURRENT_DATA) m_seeking = false; - if (networkState() == EMPTY) + if (m_networkState == NETWORK_EMPTY) return; - - if (state == DATA_UNAVAILABLE) { - dispatchEventForType(eventNames().dataunavailableEvent, false, true); - if (wasActivelyPlaying) { - dispatchEventForType(eventNames().timeupdateEvent, false, true); - dispatchEventForType(eventNames().waitingEvent, false, true); - } - } else if (state == CAN_SHOW_CURRENT_FRAME) { - if (m_loadedFirstFrame) - dispatchEventForType(eventNames().canshowcurrentframeEvent, false, true); - if (wasActivelyPlaying) { - dispatchEventForType(eventNames().timeupdateEvent, false, true); - dispatchEventForType(eventNames().waitingEvent, false, true); + + if (m_seeking && m_readyState < HAVE_CURRENT_DATA) { + // 4.8.10.10, step 9 + scheduleEvent(eventNames().seekingEvent); + m_seeking = false; + } + + if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) { + // 4.8.10.9 + scheduleTimeupdateEvent(false); + scheduleEvent(eventNames().waitingEvent); + } + + if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) { + scheduleEvent(eventNames().durationchangeEvent); + scheduleEvent(eventNames().loadedmetadataEvent); + +#if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) + if (renderer() && !renderer()->isImage()) { + static_cast<RenderVideo*>(renderer())->videoSizeChanged(); } - } else if (state == CAN_PLAY) { - dispatchEventForType(eventNames().canplayEvent, false, true); - } else if (state == CAN_PLAY_THROUGH) { - dispatchEventForType(eventNames().canplaythroughEvent, false, true); +#endif + m_delayingTheLoadEvent = false; + m_player->seek(0); + } + + // 4.8.10.7 says loadeddata is sent only when the new state *is* HAVE_CURRENT_DATA: "If the + // previous ready state was HAVE_METADATA and the new ready state is HAVE_CURRENT_DATA", + // but the event table at the end of the spec says it is sent when: "readyState newly + // increased to HAVE_CURRENT_DATA or greater for the first time" + // We go with the later because it seems useful to count on getting this event + if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) { + m_haveFiredLoadedData = true; + scheduleEvent(eventNames().loadeddataEvent); + } + + bool isPotentiallyPlaying = potentiallyPlaying(); + if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA) { + scheduleEvent(eventNames().canplayEvent); + if (isPotentiallyPlaying) + scheduleEvent(eventNames().playingEvent); + + if (isVideo()) + static_cast<HTMLVideoElement*>(this)->updatePosterImage(); + } + + if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA) { + if (oldState <= HAVE_CURRENT_DATA) + scheduleEvent(eventNames().canplayEvent); + + scheduleEvent(eventNames().canplaythroughEvent); + + if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA) + scheduleEvent(eventNames().playingEvent); + if (m_autoplaying && m_paused && autoplay()) { m_paused = false; - dispatchEventForType(eventNames().playEvent, false, true); + scheduleEvent(eventNames().playEvent); + scheduleEvent(eventNames().playingEvent); } + + if (isVideo()) + static_cast<HTMLVideoElement*>(this)->updatePosterImage(); } + updatePlayState(); } void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*) { ASSERT(m_player); + if (m_networkState == NETWORK_EMPTY || m_networkState >= NETWORK_LOADED) + return; + unsigned progress = m_player->bytesLoaded(); - double time = WebCore::currentTime(); + double time = WTF::currentTime(); double timedelta = time - m_previousProgressTime; - if (timedelta) - m_bufferingRate = (float)(0.8 * m_bufferingRate + 0.2 * ((float)(progress - m_previousProgress)) / timedelta); - + if (progress == m_previousProgress) { if (timedelta > 3.0 && !m_sentStalledEvent) { - m_bufferingRate = 0; - initAndDispatchProgressEvent(eventNames().stalledEvent); + scheduleProgressEvent(eventNames().stalledEvent); m_sentStalledEvent = true; } } else { - initAndDispatchProgressEvent(eventNames().progressEvent); + scheduleProgressEvent(eventNames().progressEvent); m_previousProgress = progress; m_previousProgressTime = time; m_sentStalledEvent = false; @@ -514,52 +785,49 @@ void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*) void HTMLMediaElement::seek(float time, ExceptionCode& ec) { - // 3.14.9.8. Seeking + // 4.8.10.10. Seeking // 1 - if (networkState() < LOADED_METADATA) { + if (m_readyState == HAVE_NOTHING || !m_player) { ec = INVALID_STATE_ERR; return; } - + // 2 - float minTime; - if (currentLoop() == 0) - minTime = effectiveStart(); - else - minTime = effectiveLoopStart(); - + time = min(time, duration()); + // 3 - float maxTime = currentLoop() == playCount() - 1 ? effectiveEnd() : effectiveLoopEnd(); - + time = max(time, 0.0f); + // 4 - time = min(time, maxTime); - - // 5 - time = max(time, minTime); - - // 6 RefPtr<TimeRanges> seekableRanges = seekable(); if (!seekableRanges->contain(time)) { ec = INDEX_SIZE_ERR; return; } - // 7 - m_currentTimeDuringSeek = time; + // avoid generating events when the time won't actually change + float now = currentTime(); + if (time == now) + return; + + // 5 + if (m_playing) { + if (m_lastSeekTime < now) + m_playedTimeRanges->add(m_lastSeekTime, now); + } + m_lastSeekTime = time; - // 8 + // 6 - set the seeking flag, it will be cleared when the engine tells is the time has actually changed m_seeking = true; - - // 9 - dispatchEventForType(eventNames().timeupdateEvent, false, true); - + + // 7 + scheduleTimeupdateEvent(false); + + // 8 - this is covered, if necessary, when the engine signals a readystate change + // 10 - // As soon as the user agent has established whether or not the media data for the new playback position is available, - // and, if it is, decoded enough data to play back that position, the seeking DOM attribute must be set to false. - if (m_player) { - m_player->setEndTime(maxTime); - m_player->seek(time); - } + m_player->seek(time); + m_sentEndEvent = false; } HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const @@ -578,7 +846,7 @@ float HTMLMediaElement::currentTime() const if (!m_player) return 0; if (m_seeking) - return m_currentTimeDuringSeek; + return m_lastSeekTime; return m_player->currentTime(); } @@ -587,9 +855,19 @@ void HTMLMediaElement::setCurrentTime(float time, ExceptionCode& ec) seek(time, ec); } +float HTMLMediaElement::startTime() const +{ + if (!m_player) + return 0; + return m_player->startTime(); +} + float HTMLMediaElement::duration() const { - return m_player ? m_player->duration() : 0; + if (m_readyState >= HAVE_METADATA) + return m_player->duration(); + + return numeric_limits<float>::quiet_NaN(); } bool HTMLMediaElement::paused() const @@ -602,15 +880,11 @@ float HTMLMediaElement::defaultPlaybackRate() const return m_defaultPlaybackRate; } -void HTMLMediaElement::setDefaultPlaybackRate(float rate, ExceptionCode& ec) +void HTMLMediaElement::setDefaultPlaybackRate(float rate) { - if (rate == 0.0f) { - ec = NOT_SUPPORTED_ERR; - return; - } if (m_defaultPlaybackRate != rate) { m_defaultPlaybackRate = rate; - dispatchEventAsync(eventNames().ratechangeEvent); + scheduleEvent(eventNames().ratechangeEvent); } } @@ -619,16 +893,14 @@ float HTMLMediaElement::playbackRate() const return m_player ? m_player->rate() : 0; } -void HTMLMediaElement::setPlaybackRate(float rate, ExceptionCode& ec) +void HTMLMediaElement::setPlaybackRate(float rate) { - if (rate == 0.0f) { - ec = NOT_SUPPORTED_ERR; - return; + if (m_playbackRate != rate) { + m_playbackRate = rate; + scheduleEvent(eventNames().ratechangeEvent); } - if (m_player && m_player->rate() != rate) { + if (m_player && potentiallyPlaying() && m_player->rate() != rate) m_player->setRate(rate); - dispatchEventAsync(eventNames().ratechangeEvent); - } } bool HTMLMediaElement::ended() const @@ -646,126 +918,95 @@ void HTMLMediaElement::setAutoplay(bool b) setBooleanAttribute(autoplayAttr, b); } -void HTMLMediaElement::play(ExceptionCode& ec) +bool HTMLMediaElement::autobuffer() const { - // 3.14.9.7. Playing the media resource - if (!m_player || networkState() == EMPTY) { - ec = 0; - load(ec); - if (ec) - return; - } - ExceptionCode unused; - if (endedPlayback()) { - m_currentLoop = 0; - seek(effectiveStart(), unused); - } - setPlaybackRate(defaultPlaybackRate(), unused); - - if (m_paused) { - m_paused = false; - dispatchEventAsync(eventNames().playEvent); - } - - m_autoplaying = false; - - updatePlayState(); + return hasAttribute(autobufferAttr); } -void HTMLMediaElement::pause(ExceptionCode& ec) +void HTMLMediaElement::setAutobuffer(bool b) { - // 3.14.9.7. Playing the media resource - if (!m_player || networkState() == EMPTY) { - ec = 0; - load(ec); - if (ec) - return; - } - - if (!m_paused) { - m_paused = true; - dispatchEventAsync(eventNames().timeupdateEvent); - dispatchEventAsync(eventNames().pauseEvent); - } - - m_autoplaying = false; - - updatePlayState(); + setBooleanAttribute(autobufferAttr, b); } -unsigned HTMLMediaElement::playCount() const +void HTMLMediaElement::play() { - bool ok; - unsigned count = getAttribute(playcountAttr).string().toUInt(&ok); - return (count > 0 && ok) ? count : 1; + if (m_restrictions & RequireUserGestureForRateChangeRestriction && !processingUserGesture()) + return; + + playInternal(); } -void HTMLMediaElement::setPlayCount(unsigned count, ExceptionCode& ec) +void HTMLMediaElement::playInternal() { - if (!count) { - ec = INDEX_SIZE_ERR; - return; + // 4.8.10.9. Playing the media resource + if (!m_player || m_networkState == NETWORK_EMPTY) + scheduleLoad(); + + if (endedPlayback()) { + ExceptionCode unused; + seek(0, unused); } - setAttribute(playcountAttr, String::number(count)); - checkIfSeekNeeded(); -} + + setPlaybackRate(defaultPlaybackRate()); + + if (m_paused) { + m_paused = false; + scheduleEvent(eventNames().playEvent); -float HTMLMediaElement::start() const -{ - return getTimeOffsetAttribute(startAttr, 0); -} + if (m_readyState <= HAVE_CURRENT_DATA) + scheduleEvent(eventNames().waitingEvent); + else if (m_readyState >= HAVE_FUTURE_DATA) + scheduleEvent(eventNames().playingEvent); + } + m_autoplaying = false; -void HTMLMediaElement::setStart(float time) -{ - setTimeOffsetAttribute(startAttr, time); - checkIfSeekNeeded(); + updatePlayState(); } -float HTMLMediaElement::end() const -{ - return getTimeOffsetAttribute(endAttr, std::numeric_limits<float>::infinity()); -} +void HTMLMediaElement::pause() +{ + if (m_restrictions & RequireUserGestureForRateChangeRestriction && !processingUserGesture()) + return; -void HTMLMediaElement::setEnd(float time) -{ - setTimeOffsetAttribute(endAttr, time); - checkIfSeekNeeded(); + pauseInternal(); } -float HTMLMediaElement::loopStart() const -{ - return getTimeOffsetAttribute(loopstartAttr, start()); -} -void HTMLMediaElement::setLoopStart(float time) +void HTMLMediaElement::pauseInternal() { - setTimeOffsetAttribute(loopstartAttr, time); - checkIfSeekNeeded(); -} + // 4.8.10.9. Playing the media resource + if (!m_player || m_networkState == NETWORK_EMPTY) + scheduleLoad(); -float HTMLMediaElement::loopEnd() const -{ - return getTimeOffsetAttribute(loopendAttr, end()); -} + m_autoplaying = false; + + if (!m_paused) { + m_paused = true; + scheduleTimeupdateEvent(false); + scheduleEvent(eventNames().pauseEvent); + } -void HTMLMediaElement::setLoopEnd(float time) -{ - setTimeOffsetAttribute(loopendAttr, time); - checkIfSeekNeeded(); + updatePlayState(); } -unsigned HTMLMediaElement::currentLoop() const +bool HTMLMediaElement::loop() const { - return m_currentLoop; + return hasAttribute(loopAttr); } -void HTMLMediaElement::setCurrentLoop(unsigned currentLoop) +void HTMLMediaElement::setLoop(bool b) { - m_currentLoop = currentLoop; + setBooleanAttribute(loopAttr, b); } bool HTMLMediaElement::controls() const { + Frame* frame = document()->frame(); + + // always show controls when scripting is disabled + if (frame && !frame->script()->isEnabled()) + return true; + return hasAttribute(controlsAttr); } @@ -789,7 +1030,7 @@ void HTMLMediaElement::setVolume(float vol, ExceptionCode& ec) if (m_volume != vol) { m_volume = vol; updateVolume(); - dispatchEventAsync(eventNames().volumechangeEvent); + scheduleEvent(eventNames().volumechangeEvent); } } @@ -803,110 +1044,245 @@ void HTMLMediaElement::setMuted(bool muted) if (m_muted != muted) { m_muted = muted; updateVolume(); - dispatchEventAsync(eventNames().volumechangeEvent); + scheduleEvent(eventNames().volumechangeEvent); } } -bool HTMLMediaElement::canPlay() const +void HTMLMediaElement::togglePlayState() { - return paused() || ended() || networkState() < LOADED_METADATA; + // 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()) + playInternal(); + else + pauseInternal(); } -String HTMLMediaElement::pickMedia() +void HTMLMediaElement::beginScrubbing() { - // 3.14.9.2. Location of the media resource - String mediaSrc = getAttribute(srcAttr); - if (mediaSrc.isEmpty()) { - for (Node* n = firstChild(); n; n = n->nextSibling()) { - if (n->hasTagName(sourceTag)) { - HTMLSourceElement* source = static_cast<HTMLSourceElement*>(n); - if (!source->hasAttribute(srcAttr)) - continue; - if (source->hasAttribute(mediaAttr)) { - MediaQueryEvaluator screenEval("screen", document()->frame(), renderer() ? renderer()->style() : 0); - RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(source->media()); - if (!screenEval.eval(media.get())) - continue; - } - if (source->hasAttribute(typeAttr)) { - String type = source->type().stripWhiteSpace(); - - // "type" can have parameters after a semi-colon, strip them before checking with the type registry - int semi = type.find(';'); - if (semi != -1) - type = type.left(semi).stripWhiteSpace(); - - if (!MIMETypeRegistry::isSupportedMediaMIMEType(type)) - continue; - } - mediaSrc = source->src().string(); - break; - } + 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); } } - if (!mediaSrc.isEmpty()) - mediaSrc = document()->completeURL(mediaSrc).string(); - return mediaSrc; } -void HTMLMediaElement::checkIfSeekNeeded() +void HTMLMediaElement::endScrubbing() { - // 3.14.9.5. Offsets into the media resource - // 1 - if (playCount() <= m_currentLoop) - m_currentLoop = playCount() - 1; - - // 2 - if (networkState() <= LOADING) + if (m_pausedInternal) + setPausedInternal(false); +} + +// 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_previousProgress = 0; + m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency); +} + +void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*) +{ + ASSERT(m_player); + if (!m_playbackRate) + return; + + scheduleTimeupdateEvent(true); - // 3 - ExceptionCode ec; - float time = currentTime(); - if (!m_currentLoop && time < effectiveStart()) - seek(effectiveStart(), ec); + // FIXME: deal with cue ranges here +} - // 4 - if (m_currentLoop && time < effectiveLoopStart()) - seek(effectiveLoopStart(), ec); - - // 5 - if (m_currentLoop < playCount() - 1 && time > effectiveLoopEnd()) { - seek(effectiveLoopStart(), ec); - m_currentLoop++; +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 + float movieTime = m_player ? m_player->currentTime() : 0; + if (movieTime != m_lastTimeUpdateEventMovieTime) { + scheduleEvent(eventNames().timeupdateEvent); + m_lastTimeUpdateEventWallTime = now; + m_lastTimeUpdateEventMovieTime = movieTime; } - - // 6 - if (m_currentLoop == playCount() - 1 && time > effectiveEnd()) - seek(effectiveEnd(), ec); +} - updatePlayState(); +bool HTMLMediaElement::canPlay() const +{ + return paused() || ended() || m_readyState < HAVE_METADATA; +} + +bool HTMLMediaElement::havePotentialSourceChild() +{ + // Stash the current <source> node so we can restore it after checking + // to see there is another potential + HTMLSourceElement* currentSourceNode = m_currentSourceNode; + KURL nextURL = selectNextSourceChild(0, DoNothing); + m_currentSourceNode = currentSourceNode; + + return nextURL.isValid(); +} + +KURL HTMLMediaElement::selectNextSourceChild(ContentType *contentType, InvalidSourceAction actionIfInvalid) +{ + KURL mediaURL; + Node* node; + bool lookingForPreviousNode = m_currentSourceNode; + bool canUse = false; + + for (node = firstChild(); !canUse && node; node = node->nextSibling()) { + if (!node->hasTagName(sourceTag)) + continue; + + if (lookingForPreviousNode) { + if (m_currentSourceNode == static_cast<HTMLSourceElement*>(node)) + lookingForPreviousNode = false; + continue; + } + + HTMLSourceElement* source = static_cast<HTMLSourceElement*>(node); + if (!source->hasAttribute(srcAttr)) + goto check_again; + + if (source->hasAttribute(mediaAttr)) { + MediaQueryEvaluator screenEval("screen", document()->frame(), renderer() ? renderer()->style() : 0); + RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(source->media()); + if (!screenEval.eval(media.get())) + goto check_again; + } + + if (source->hasAttribute(typeAttr)) { + if (!MediaPlayer::supportsType(ContentType(source->type()))) + goto check_again; + } + + // Is it safe to load this url? + mediaURL = source->src(); + if (!mediaURL.isValid() || !isSafeToLoadURL(mediaURL, actionIfInvalid)) + goto check_again; + + // Making it this far means the <source> looks reasonable + canUse = true; + if (contentType) + *contentType = ContentType(source->type()); + +check_again: + if (!canUse && actionIfInvalid == Complain) + source->scheduleErrorEvent(); + m_currentSourceNode = static_cast<HTMLSourceElement*>(node); + } + + if (!canUse) + m_currentSourceNode = 0; + return canUse ? mediaURL : KURL(); } void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*) { - if (readyState() >= CAN_PLAY) + beginProcessingMediaPlayerCallback(); + + if (m_readyState >= HAVE_CURRENT_DATA && m_seeking) { + scheduleEvent(eventNames().seekedEvent); m_seeking = false; - - if (m_currentLoop < playCount() - 1 && currentTime() >= effectiveLoopEnd()) { - ExceptionCode ec; - seek(effectiveLoopStart(), ec); - m_currentLoop++; - dispatchEventForType(eventNames().timeupdateEvent, false, true); } - if (m_currentLoop == playCount() - 1 && currentTime() >= effectiveEnd()) { - dispatchEventForType(eventNames().timeupdateEvent, false, true); - dispatchEventForType(eventNames().endedEvent, false, true); + float now = currentTime(); + float dur = duration(); + if (!isnan(dur) && dur && now >= dur) { + if (loop()) { + ExceptionCode ignoredException; + m_sentEndEvent = false; + seek(0, ignoredException); + } else { + if (!m_sentEndEvent) { + m_sentEndEvent = true; + scheduleTimeupdateEvent(false); + scheduleEvent(eventNames().endedEvent); + } + } } + else + m_sentEndEvent = false; updatePlayState(); + endProcessingMediaPlayerCallback(); } void HTMLMediaElement::mediaPlayerRepaint(MediaPlayer*) { + beginProcessingMediaPlayerCallback(); if (renderer()) renderer()->repaint(); + endProcessingMediaPlayerCallback(); +} + +void HTMLMediaElement::mediaPlayerVolumeChanged(MediaPlayer*) +{ + beginProcessingMediaPlayerCallback(); + updateVolume(); + endProcessingMediaPlayerCallback(); +} + +void HTMLMediaElement::mediaPlayerDurationChanged(MediaPlayer*) +{ + beginProcessingMediaPlayerCallback(); + scheduleEvent(eventNames().durationchangeEvent); +#if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) + if (renderer()) { + renderer()->updateFromElement(); + if (!renderer()->isImage()) + static_cast<RenderVideo*>(renderer())->videoSizeChanged(); + } +#endif + endProcessingMediaPlayerCallback(); +} + +void HTMLMediaElement::mediaPlayerRateChanged(MediaPlayer*) +{ + beginProcessingMediaPlayerCallback(); + // Stash the rate in case the one we tried to set isn't what the engine is + // using (eg. it can't handle the rate we set) + m_playbackRate = m_player->rate(); + endProcessingMediaPlayerCallback(); +} + +void HTMLMediaElement::mediaPlayerSizeChanged(MediaPlayer*) +{ + beginProcessingMediaPlayerCallback(); +#if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) + if (renderer() && !renderer()->isImage()) + static_cast<RenderVideo*>(renderer())->videoSizeChanged(); +#endif + endProcessingMediaPlayerCallback(); +} + +void HTMLMediaElement::mediaPlayerSawUnsupportedTracks(MediaPlayer*) +{ + // The MediaPlayer came across content it cannot completely handle. + // This is normally acceptable except when we are in a standalone + // MediaDocument. If so, tell the document what has happened. + if (ownerDocument()->isMediaDocument()) { + MediaDocument* mediaDocument = static_cast<MediaDocument*>(ownerDocument()); + mediaDocument->mediaElementSawUnsupportedTracks(); + } } PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const @@ -919,8 +1295,16 @@ PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const PassRefPtr<TimeRanges> HTMLMediaElement::played() const { - // FIXME track played - return TimeRanges::create(); + if (!m_playedTimeRanges) { + // We are not yet loaded + return TimeRanges::create(); + } + if (m_playing) { + float time = currentTime(); + if (m_lastSeekTime < time) + m_playedTimeRanges->add(m_lastSeekTime, time); + } + return m_playedTimeRanges->copy(); } PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const @@ -931,53 +1315,49 @@ PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const return TimeRanges::create(0, m_player->maxTimeSeekable()); } -float HTMLMediaElement::effectiveStart() const -{ - if (!m_player) - return 0; - return min(start(), m_player->duration()); -} - -float HTMLMediaElement::effectiveEnd() const +bool HTMLMediaElement::potentiallyPlaying() const { - if (!m_player) - return 0; - return min(max(end(), max(start(), loopStart())), m_player->duration()); + return !paused() && m_readyState >= HAVE_FUTURE_DATA && !endedPlayback() && !stoppedDueToErrors() && !pausedForUserInteraction(); } -float HTMLMediaElement::effectiveLoopStart() const +bool HTMLMediaElement::endedPlayback() const { - if (!m_player) - return 0; - return min(loopStart(), m_player->duration()); + if (!m_player || m_readyState < HAVE_METADATA) + return false; + + float dur = duration(); + return !isnan(dur) && currentTime() >= dur && !loop(); } -float HTMLMediaElement::effectiveLoopEnd() const +bool HTMLMediaElement::stoppedDueToErrors() const { - if (!m_player) - return 0; - return min(max(start(), max(loopStart(), loopEnd())), m_player->duration()); + if (m_readyState >= HAVE_METADATA && m_error) { + RefPtr<TimeRanges> seekableRanges = seekable(); + if (!seekableRanges->contain(currentTime())) + return true; + } + + return false; } -bool HTMLMediaElement::activelyPlaying() const +bool HTMLMediaElement::pausedForUserInteraction() const { - return !paused() && readyState() >= CAN_PLAY && !endedPlayback(); // && !stoppedDueToErrors() && !pausedForUserInteraction(); +// return !paused() && m_readyState >= HAVE_FUTURE_DATA && [UA requires a decitions from the user] + return false; } -bool HTMLMediaElement::endedPlayback() const -{ - return networkState() >= LOADED_METADATA && currentTime() >= effectiveEnd() && currentLoop() == playCount() - 1; -} - void HTMLMediaElement::updateVolume() { if (!m_player) return; - Page* page = document()->page(); - float volumeMultiplier = page ? page->mediaVolume() : 1; - - m_player->setVolume(m_muted ? 0 : m_volume * volumeMultiplier); + // Avoid recursion when the player reports volume changes. + if (!processingMediaPlayerCallback()) { + Page* page = document()->page(); + float volumeMultiplier = page ? page->mediaVolume() : 1; + + m_player->setVolume(m_muted ? 0 : m_volume * volumeMultiplier); + } if (renderer()) renderer()->updateFromElement(); @@ -987,20 +1367,31 @@ void HTMLMediaElement::updatePlayState() { if (!m_player) return; - + if (m_pausedInternal) { if (!m_player->paused()) m_player->pause(); + m_playbackProgressTimer.stop(); return; } - m_player->setEndTime(currentLoop() == playCount() - 1 ? effectiveEnd() : effectiveLoopEnd()); - - bool shouldBePlaying = activelyPlaying() && currentTime() < effectiveEnd(); - if (shouldBePlaying && m_player->paused()) + bool shouldBePlaying = potentiallyPlaying(); + bool playerPaused = m_player->paused(); + if (shouldBePlaying && playerPaused) { + // Set rate before calling play in case the rate was set before the media engine wasn't setup. + // The media engine should just stash the rate since it isn't already playing. + m_player->setRate(m_playbackRate); m_player->play(); - else if (!shouldBePlaying && !m_player->paused()) + startPlaybackProgressTimer(); + m_playing = true; + } else if (!shouldBePlaying && !playerPaused) { m_player->pause(); + m_playbackProgressTimer.stop(); + m_playing = false; + float time = currentTime(); + if (m_lastSeekTime < time) + m_playedTimeRanges->add(m_lastSeekTime, time); + } if (renderer()) renderer()->updateFromElement(); @@ -1012,25 +1403,50 @@ void HTMLMediaElement::setPausedInternal(bool b) updatePlayState(); } -void HTMLMediaElement::documentWillBecomeInactive() +void HTMLMediaElement::stopPeriodicTimers() { - // 3.14.9.4. Loading the media resource - // 14 - if (m_begun) { - // For simplicity cancel the incomplete load by deleting the player + m_progressEventTimer.stop(); + m_playbackProgressTimer.stop(); +} + +void HTMLMediaElement::userCancelledLoad() +{ + if (m_networkState != NETWORK_EMPTY) { + + // If the media data fetching process is aborted by the user: + + // 1 - The user agent should cancel the fetching process. +#if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) m_player.clear(); - m_progressEventTimer.stop(); +#endif + stopPeriodicTimers(); + // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORT. m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED); - m_begun = false; - initAndDispatchProgressEvent(eventNames().abortEvent); - if (m_networkState >= LOADING) { - m_networkState = EMPTY; - m_readyState = DATA_UNAVAILABLE; - dispatchEventForType(eventNames().emptiedEvent, false, true); + + // 3 - Queue a task to fire a progress event called abort at the media element. + scheduleProgressEvent(eventNames().abortEvent); + + // 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 called emptied at the element. Otherwise, set set the element's networkState + // attribute to the NETWORK_IDLE value. + if (m_networkState >= NETWORK_LOADING) { + m_networkState = NETWORK_EMPTY; + m_readyState = HAVE_NOTHING; + scheduleEvent(eventNames().emptiedEvent); } + + // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. + m_delayingTheLoadEvent = false; } +} + +void HTMLMediaElement::documentWillBecomeInactive() +{ m_inActiveDocument = false; + userCancelledLoad(); + // Stop the playback without generating events setPausedInternal(true); @@ -1045,6 +1461,8 @@ void HTMLMediaElement::documentDidBecomeActive() if (m_error && m_error->code() == MediaError::MEDIA_ERR_ABORTED) { // Restart the load if it was aborted in the middle by moving the document to the page cache. + // m_error is only left at MEDIA_ERR_ABORTED when the document becomes inactive (it is set to + // MEDIA_ERR_ABORTED while the abortEvent is being sent, but cleared immediately afterwards). // This behavior is not specified but it seems like a sensible thing to do. ExceptionCode ec; load(ec); @@ -1061,13 +1479,75 @@ void HTMLMediaElement::mediaVolumeDidChange() void HTMLMediaElement::defaultEventHandler(Event* event) { +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + RenderObject* r = renderer(); + if (!r || !r->isWidget()) + return; + + Widget* widget = static_cast<RenderWidget*>(r)->widget(); + if (widget) + widget->handleEvent(event); +#else if (renderer() && renderer()->isMedia()) static_cast<RenderMedia*>(renderer())->forwardEvent(event); if (event->defaultHandled()) return; HTMLElement::defaultEventHandler(event); +#endif +} + +bool HTMLMediaElement::processingUserGesture() const +{ + Frame* frame = document()->frame(); + FrameLoader* loader = frame ? frame->loader() : 0; + + // return 'true' for safety if we don't know the answer + return loader ? loader->userGestureHint() : true; } +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) +void HTMLMediaElement::deliverNotification(MediaPlayerProxyNotificationType notification) +{ + if (notification == MediaPlayerNotificationPlayPauseButtonPressed) { + ExceptionCode ec; + togglePlayState(ec); + return; + } + + if (m_player) + m_player->deliverNotification(notification); +} + +void HTMLMediaElement::setMediaPlayerProxy(WebMediaPlayerProxy* proxy) +{ + if (m_player) + m_player->setMediaPlayerProxy(proxy); +} + +String HTMLMediaElement::initialURL() +{ + KURL initialSrc = document()->completeURL(getAttribute(srcAttr)); + + if (!initialSrc.isValid()) + initialSrc = selectNextSourceChild(0, DoNothing).string(); + + m_currentSrc = initialSrc.string(); + + return initialSrc; +} + +void HTMLMediaElement::finishParsingChildren() +{ + HTMLElement::finishParsingChildren(); + if (!m_player) + m_player.set(new MediaPlayer(this)); + + document()->updateStyleIfNeeded(); + if (m_needWidgetUpdate && renderer()) + static_cast<RenderPartObject*>(renderer())->updateWidget(true); +} +#endif + } #endif |