diff options
Diffstat (limited to 'WebCore/html/HTMLMediaElement.cpp')
-rw-r--r-- | WebCore/html/HTMLMediaElement.cpp | 324 |
1 files changed, 229 insertions, 95 deletions
diff --git a/WebCore/html/HTMLMediaElement.cpp b/WebCore/html/HTMLMediaElement.cpp index f98200d..0136e08 100644 --- a/WebCore/html/HTMLMediaElement.cpp +++ b/WebCore/html/HTMLMediaElement.cpp @@ -28,6 +28,9 @@ #if ENABLE(VIDEO) #include "HTMLMediaElement.h" +#include "ClientRect.h" +#include "ClientRectList.h" +#include "ChromeClient.h" #include "CSSHelper.h" #include "CSSPropertyNames.h" #include "CSSValueKeywords.h" @@ -38,6 +41,8 @@ #include "ExceptionCode.h" #include "Frame.h" #include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "FrameView.h" #include "HTMLDocument.h" #include "HTMLNames.h" #include "HTMLSourceElement.h" @@ -52,6 +57,7 @@ #include "Page.h" #include "ProgressEvent.h" #include "RenderVideo.h" +#include "RenderView.h" #include "ScriptEventListener.h" #include "TimeRanges.h" #include <limits> @@ -110,6 +116,7 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* doc) , m_sentEndEvent(false) , m_pausedInternal(false) , m_sendProgressEvents(true) + , m_isFullscreen(false) #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) , m_needWidgetUpdate(false) #endif @@ -178,6 +185,8 @@ void HTMLMediaElement::parseMappedAttribute(MappedAttribute* attr) setAttributeEventListener(eventNames().loadeddataEvent, createAttributeEventListener(this, attr)); else if (attrName == onloadedmetadataAttr) setAttributeEventListener(eventNames().loadedmetadataEvent, createAttributeEventListener(this, attr)); + else if (attrName == onloadendAttr) + setAttributeEventListener(eventNames().loadendEvent, createAttributeEventListener(this, attr)); else if (attrName == onloadstartAttr) setAttributeEventListener(eventNames().loadstartEvent, createAttributeEventListener(this, attr)); else if (attrName == onpauseAttr) @@ -238,6 +247,12 @@ void HTMLMediaElement::insertedIntoDocument() scheduleLoad(); } +void HTMLMediaElement::willRemove() +{ + if (m_isFullscreen) + exitFullscreen(); + HTMLElement::willRemove(); +} void HTMLMediaElement::removedFromDocument() { if (m_networkState > NETWORK_EMPTY) @@ -269,6 +284,15 @@ void HTMLMediaElement::recalcStyle(StyleChange change) void HTMLMediaElement::scheduleLoad() { + if (m_loadTimer.isActive()) + return; + prepareForLoad(); + m_loadTimer.startOneShot(0); +} + +void HTMLMediaElement::scheduleNextSourceChild() +{ + // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad. m_loadTimer.startOneShot(0); } @@ -408,17 +432,15 @@ void HTMLMediaElement::load(ExceptionCode& ec) { if (m_restrictions & RequireUserGestureForLoadRestriction && !processingUserGesture()) ec = INVALID_STATE_ERR; - else + else { + prepareForLoad(); loadInternal(); + } } -void HTMLMediaElement::loadInternal() +void HTMLMediaElement::prepareForLoad() { - // 1 - If the load() method for this element is already being invoked, then abort these steps. - if (m_processingLoad) - return; - m_processingLoad = true; - + // Perform the cleanup required for the resource load algorithm to run. stopPeriodicTimers(); m_loadTimer.stop(); m_sentStalledEvent = false; @@ -430,21 +452,34 @@ void HTMLMediaElement::loadInternal() // 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(); +} + +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; + + // Steps 2 and 3 were done in prepareForLoad() - // 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. + // 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, fire a progress event called abort at the media element, in the + // context of the fetching process that is in progress for the element, and fire a progress + // event called loadend at the media element, in the context of the same fetching process. if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE) { m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED); - - // fire synchronous 'abort' + + // fire synchronous 'abort' and 'loadend' 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); + dispatchEvent(ProgressEvent::create(eventNames().abortEvent, totalKnown, loaded, total)); + dispatchEvent(ProgressEvent::create(eventNames().loadendEvent, totalKnown, loaded, total)); } - - // 5 + + // 5 m_error = 0; m_autoplaying = true; m_playedTimeRanges = TimeRanges::create(); @@ -452,7 +487,7 @@ void HTMLMediaElement::loadInternal() // 6 setPlaybackRate(defaultPlaybackRate()); - + // 7 if (m_networkState != NETWORK_EMPTY) { m_networkState = NETWORK_EMPTY; @@ -464,9 +499,9 @@ void HTMLMediaElement::loadInternal() m_playing = false; m_player->seek(0); } - dispatchEvent(eventNames().emptiedEvent, false, true); + dispatchEvent(Event::create(eventNames().emptiedEvent, false, true)); } - + selectMediaResource(); m_processingLoad = false; } @@ -530,9 +565,20 @@ void HTMLMediaElement::loadNextSourceChild() loadResource(mediaURL, contentType); } -void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType) +void HTMLMediaElement::loadResource(const KURL& initialURL, ContentType& contentType) { - ASSERT(isSafeToLoadURL(url, Complain)); + ASSERT(isSafeToLoadURL(initialURL, Complain)); + + Frame* frame = document()->frame(); + if (!frame) + return; + FrameLoader* loader = frame->loader(); + if (!loader) + return; + + KURL url(initialURL); + if (!loader->willLoadMediaElementURL(url)) + return; // The resource fetch algorithm m_networkState = NETWORK_LOADING; @@ -555,13 +601,11 @@ void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType) m_player->load(m_currentSrc, contentType); -#if PLATFORM(ANDROID) if (isVideo() && m_player->canLoadPoster()) { KURL posterUrl = static_cast<HTMLVideoElement*>(this)->poster(); if (!posterUrl.isEmpty()) m_player->setPoster(posterUrl); } -#endif if (renderer()) renderer()->updateFromElement(); @@ -572,8 +616,8 @@ bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidSourceAction acti Frame* frame = document()->frame(); FrameLoader* loader = frame ? frame->loader() : 0; - // don't allow remote to local urls - if (!loader || !loader->canLoad(url, String(), document())) { + // don't allow remote to local urls, and check with the frame loader client. + if (!loader || !SecurityOrigin::canLoad(url, String(), document())) { if (actionIfInvalid == Complain) FrameLoader::reportLocalLoadFailed(frame, url.string()); return false; @@ -599,22 +643,29 @@ void HTMLMediaElement::noneSupported() 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 + // 4 - 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. + // 5 - 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 - Queue a task to fire a progress event called error at the media element, in + // the context of the fetching process that was used to try to obtain the media + // resource in the resource fetch algorithm. + scheduleProgressEvent(eventNames().errorEvent); - // 6 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. + // 7 - Queue a task to fire a progress event called loadend at the media element, in + // the context of the fetching process that was used to try to obtain the media + // resource in the resource fetch algorithm. + scheduleProgressEvent(eventNames().loadendEvent); + + // 8 - 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. - + // 9 -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()) @@ -631,20 +682,24 @@ void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err) // 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 - Queue a task to fire a progress event called error at the media element, in + // the context of the fetching process started by this instance of this algorithm. + scheduleProgressEvent(eventNames().errorEvent); + + // 4 - Queue a task to fire a progress event called loadend at the media element, in + // the context of the fetching process started by this instance of this algorithm. + scheduleProgressEvent(eventNames().loadendEvent); - // 3 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a + // 5 - 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. + // 6 - 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. + // 7 - Abort the overall resource selection algorithm. m_currentSourceNode = 0; - } void HTMLMediaElement::cancelPendingEventsAndCallbacks() @@ -680,7 +735,7 @@ void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state) if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) { m_currentSourceNode->scheduleErrorEvent(); if (havePotentialSourceChild()) - scheduleLoad(); + scheduleNextSourceChild(); return; } @@ -718,6 +773,10 @@ void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state) if (oldState < NETWORK_LOADED || oldState == NETWORK_NO_SOURCE) { m_progressEventTimer.stop(); + // Schedule one last progress event so we guarantee that at least one is fired + // for files that load very quickly. + scheduleProgressEvent(eventNames().progressEvent); + // 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 @@ -725,7 +784,8 @@ void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state) if (static_cast<ReadyState>(currentState) != m_readyState) setReadyState(currentState); - scheduleProgressEvent(eventNames().loadEvent); + scheduleProgressEvent(eventNames().loadEvent); + scheduleProgressEvent(eventNames().loadendEvent); } } } @@ -750,21 +810,29 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) if (m_readyState == oldState) return; - if (m_readyState >= HAVE_CURRENT_DATA) - m_seeking = false; - if (m_networkState == NETWORK_EMPTY) return; - if (m_seeking && m_readyState < HAVE_CURRENT_DATA) { + if (m_seeking) { + // 4.8.10.10, step 8 + if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) + scheduleEvent(eventNames().waitingEvent); + // 4.8.10.10, step 9 - scheduleEvent(eventNames().seekingEvent); - } + if (m_readyState < HAVE_CURRENT_DATA) { + if (oldState >= HAVE_CURRENT_DATA) + scheduleEvent(eventNames().seekingEvent); + } else { + // 4.8.10.10 step 12 & 13. + finishSeek(); + } - if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) { - // 4.8.10.9 - scheduleTimeupdateEvent(false); - scheduleEvent(eventNames().waitingEvent); + } else { + if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) { + // 4.8.10.9 + scheduleTimeupdateEvent(false); + scheduleEvent(eventNames().waitingEvent); + } } if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) { @@ -857,6 +925,13 @@ void HTMLMediaElement::returnToRealtime() setCurrentTime(maxTimeSeekable(), e); } +void HTMLMediaElement::addPlayedRange(float start, float end) +{ + if (!m_playedTimeRanges) + m_playedTimeRanges = TimeRanges::create(); + m_playedTimeRanges->add(start, end); +} + bool HTMLMediaElement::supportsSave() const { return m_player ? m_player->supportsSave() : false; @@ -892,7 +967,7 @@ void HTMLMediaElement::seek(float time, ExceptionCode& ec) // 5 if (m_playing) { if (m_lastSeekTime < now) - m_playedTimeRanges->add(m_lastSeekTime, now); + addPlayedRange(m_lastSeekTime, now); } m_lastSeekTime = time; @@ -909,6 +984,15 @@ void HTMLMediaElement::seek(float time, ExceptionCode& ec) m_sentEndEvent = false; } +void HTMLMediaElement::finishSeek() +{ + // 4.8.10.10 Seeking step 12 + m_seeking = false; + + // 4.8.10.10 Seeking step 13 + scheduleEvent(eventNames().seekedEvent); +} + HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const { return m_readyState; @@ -919,6 +1003,11 @@ MediaPlayer::MovieLoadType HTMLMediaElement::movieLoadType() const return m_player ? m_player->movieLoadType() : MediaPlayer::Unknown; } +bool HTMLMediaElement::hasAudio() const +{ + return m_player ? m_player->hasAudio() : false; +} + bool HTMLMediaElement::seeking() const { return m_seeking; @@ -1234,7 +1323,19 @@ float HTMLMediaElement::percentLoaded() const if (!m_player) return 0; float duration = m_player->duration(); - return duration ? m_player->maxTimeBuffered() / duration : 0; + + if (!duration || isinf(duration)) + return 0; + + float buffered = 0; + RefPtr<TimeRanges> timeRanges = m_player->buffered(); + for (unsigned i = 0; i < timeRanges->length(); ++i) { + ExceptionCode ignoredException; + float start = timeRanges->start(i, ignoredException); + float end = timeRanges->end(i, ignoredException); + buffered += end - start; + } + return buffered / duration; } bool HTMLMediaElement::havePotentialSourceChild() @@ -1306,9 +1407,9 @@ void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*) { beginProcessingMediaPlayerCallback(); + // 4.8.10.10 step 12 & 13. Needed if no ReadyState change is associated with the seek. if (m_readyState >= HAVE_CURRENT_DATA && m_seeking) { - scheduleEvent(eventNames().seekedEvent); - m_seeking = false; + finishSeek(); } float now = currentTime(); @@ -1413,23 +1514,22 @@ GraphicsLayer* HTMLMediaElement::mediaPlayerGraphicsLayer(MediaPlayer*) PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const { - // FIXME real ranges support - if (!m_player || !m_player->maxTimeBuffered()) + if (!m_player) return TimeRanges::create(); - return TimeRanges::create(0, m_player->maxTimeBuffered()); + return m_player->buffered(); } -PassRefPtr<TimeRanges> HTMLMediaElement::played() const +PassRefPtr<TimeRanges> HTMLMediaElement::played() { - 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); + if (time > m_lastSeekTime) + addPlayedRange(m_lastSeekTime, time); } + + if (!m_playedTimeRanges) + m_playedTimeRanges = TimeRanges::create(); + return m_playedTimeRanges->copy(); } @@ -1443,15 +1543,13 @@ PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const bool HTMLMediaElement::potentiallyPlaying() const { - return !paused() && m_readyState >= HAVE_FUTURE_DATA && !endedPlayback() && !stoppedDueToErrors() && !pausedForUserInteraction(); + return m_readyState >= HAVE_FUTURE_DATA && couldPlayIfEnoughData(); } -#if PLATFORM(ANDROID) bool HTMLMediaElement::couldPlayIfEnoughData() const { return !paused() && !endedPlayback() && !stoppedDueToErrors() && !pausedForUserInteraction(); } -#endif bool HTMLMediaElement::endedPlayback() const { @@ -1532,13 +1630,10 @@ void HTMLMediaElement::updatePlayState() m_playbackProgressTimer.stop(); m_playing = false; float time = currentTime(); - if (m_lastSeekTime < time) - m_playedTimeRanges->add(m_lastSeekTime, time); -#if PLATFORM(ANDROID) - } else if (couldPlayIfEnoughData() && playerPaused) { + if (time > m_lastSeekTime) + addPlayedRange(m_lastSeekTime, time); + } else if (couldPlayIfEnoughData() && playerPaused) m_player->prepareToPlay(); -#endif - } if (renderer()) renderer()->updateFromElement(); @@ -1558,35 +1653,44 @@ void HTMLMediaElement::stopPeriodicTimers() void HTMLMediaElement::userCancelledLoad() { - if (m_networkState != NETWORK_EMPTY) { + if (m_networkState == NETWORK_EMPTY || m_networkState >= NETWORK_LOADED) + return; - // If the media data fetching process is aborted by the user: + // If the media data fetching process is aborted by the user: - // 1 - The user agent should cancel the fetching process. + // 1 - The user agent should cancel the fetching process. #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) - m_player.clear(); + m_player.clear(); #endif - stopPeriodicTimers(); + 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); + // 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); - // 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); - } + // 3 - Queue a task to fire a progress event called abort at the media element, in the context + // of the fetching process started by this instance of this algorithm. + scheduleProgressEvent(eventNames().abortEvent); - // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. - m_delayingTheLoadEvent = false; + // 4 - Queue a task to fire a progress event called loadend at the media element, in the context + // of the fetching process started by this instance of this algorithm. + scheduleProgressEvent(eventNames().loadendEvent); + + // 5 - 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_readyState == HAVE_NOTHING) { + m_networkState = NETWORK_EMPTY; + scheduleEvent(eventNames().emptiedEvent); } + else + m_networkState = NETWORK_IDLE; + + // 6 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. + m_delayingTheLoadEvent = false; + + // 7 - Abort the overall resource selection algorithm. + m_currentSourceNode = 0; } void HTMLMediaElement::documentWillBecomeInactive() @@ -1617,7 +1721,7 @@ void HTMLMediaElement::documentDidBecomeActive() ExceptionCode ec; load(ec); } - + if (renderer()) renderer()->updateFromElement(); } @@ -1627,6 +1731,14 @@ void HTMLMediaElement::mediaVolumeDidChange() updateVolume(); } +const IntRect HTMLMediaElement::screenRect() +{ + IntRect elementRect; + if (renderer()) + elementRect = renderer()->view()->frameView()->contentsToScreen(renderer()->absoluteBoundingBoxRect()); + return elementRect; +} + void HTMLMediaElement::defaultEventHandler(Event* event) { #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) @@ -1699,6 +1811,28 @@ void HTMLMediaElement::finishParsingChildren() #endif +void HTMLMediaElement::enterFullscreen() +{ + ASSERT(!m_isFullscreen); + if (!renderer()) + return; + if (document() && document()->page()) + document()->page()->chrome()->client()->enterFullscreenForNode(this); + m_isFullscreen = true; +} + +void HTMLMediaElement::exitFullscreen() +{ + ASSERT(m_isFullscreen); + if (document() && document()->page()) + document()->page()->chrome()->client()->exitFullscreenForNode(this); + m_isFullscreen = false; +} + +PlatformMedia HTMLMediaElement::platformMedia() const +{ + return m_player ? m_player->platformMedia() : NoPlatformMedia; +} } #endif |