summaryrefslogtreecommitdiff
path: root/Source/WebCore/html/MediaController.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/html/MediaController.cpp')
-rw-r--r--Source/WebCore/html/MediaController.cpp595
1 files changed, 595 insertions, 0 deletions
diff --git a/Source/WebCore/html/MediaController.cpp b/Source/WebCore/html/MediaController.cpp
new file mode 100644
index 000000000..806af55b9
--- /dev/null
+++ b/Source/WebCore/html/MediaController.cpp
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 2011 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"
+
+#if ENABLE(VIDEO)
+#include "MediaController.h"
+
+#include "Clock.h"
+#include "ExceptionCode.h"
+#include "HTMLMediaElement.h"
+#include "TimeRanges.h"
+#include <wtf/StdLibExtras.h>
+#include <wtf/text/AtomicString.h>
+
+using namespace WebCore;
+using namespace std;
+
+PassRefPtr<MediaController> MediaController::create(ScriptExecutionContext* context)
+{
+ return adoptRef(new MediaController(context));
+}
+
+MediaController::MediaController(ScriptExecutionContext* context)
+ : m_paused(false)
+ , m_defaultPlaybackRate(1)
+ , m_volume(1)
+ , m_muted(false)
+ , m_readyState(HAVE_NOTHING)
+ , m_playbackState(WAITING)
+ , m_asyncEventTimer(this, &MediaController::asyncEventTimerFired)
+ , m_closedCaptionsVisible(false)
+ , m_clock(Clock::create())
+ , m_scriptExecutionContext(context)
+{
+}
+
+MediaController::~MediaController()
+{
+}
+
+void MediaController::addMediaElement(HTMLMediaElement* element)
+{
+ ASSERT(element);
+ ASSERT(!m_mediaElements.contains(element));
+
+ m_mediaElements.append(element);
+ bringElementUpToSpeed(element);
+}
+
+void MediaController::removeMediaElement(HTMLMediaElement* element)
+{
+ ASSERT(element);
+ ASSERT(m_mediaElements.contains(element));
+ m_mediaElements.remove(m_mediaElements.find(element));
+}
+
+bool MediaController::containsMediaElement(HTMLMediaElement* element) const
+{
+ return m_mediaElements.contains(element);
+}
+
+PassRefPtr<TimeRanges> MediaController::buffered() const
+{
+ if (m_mediaElements.isEmpty())
+ return TimeRanges::create();
+
+ // The buffered attribute must return a new static normalized TimeRanges object that represents
+ // the intersection of the ranges of the media resources of the slaved media elements that the
+ // user agent has buffered, at the time the attribute is evaluated.
+ RefPtr<TimeRanges> bufferedRanges = m_mediaElements.first()->buffered();
+ for (size_t index = 1; index < m_mediaElements.size(); ++index)
+ bufferedRanges->intersectWith(m_mediaElements[index]->buffered().get());
+ return bufferedRanges;
+}
+
+PassRefPtr<TimeRanges> MediaController::seekable() const
+{
+ if (m_mediaElements.isEmpty())
+ return TimeRanges::create();
+
+ // The seekable attribute must return a new static normalized TimeRanges object that represents
+ // the intersection of the ranges of the media resources of the slaved media elements that the
+ // user agent is able to seek to, at the time the attribute is evaluated.
+ RefPtr<TimeRanges> seekableRanges = m_mediaElements.first()->seekable();
+ for (size_t index = 1; index < m_mediaElements.size(); ++index)
+ seekableRanges->intersectWith(m_mediaElements[index]->seekable().get());
+ return seekableRanges;
+}
+
+PassRefPtr<TimeRanges> MediaController::played()
+{
+ if (m_mediaElements.isEmpty())
+ return TimeRanges::create();
+
+ // The played attribute must return a new static normalized TimeRanges object that represents
+ // the union of the ranges of the media resources of the slaved media elements that the
+ // user agent has so far rendered, at the time the attribute is evaluated.
+ RefPtr<TimeRanges> playedRanges = m_mediaElements.first()->played();
+ for (size_t index = 1; index < m_mediaElements.size(); ++index)
+ playedRanges->unionWith(m_mediaElements[index]->played().get());
+ return playedRanges;
+}
+
+float MediaController::duration() const
+{
+ // FIXME: Investigate caching the maximum duration and only updating the cached value
+ // when the slaved media elements' durations change.
+ float maxDuration = 0;
+ for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+ float duration = m_mediaElements[index]->duration();
+ if (isnan(duration))
+ continue;
+ maxDuration = max(maxDuration, duration);
+ }
+ return maxDuration;
+}
+
+float MediaController::currentTime() const
+{
+ if (m_mediaElements.isEmpty())
+ return 0;
+
+ return m_clock->currentTime();
+}
+
+void MediaController::setCurrentTime(float time, ExceptionCode& code)
+{
+ // When the user agent is to seek the media controller to a particular new playback position,
+ // it must follow these steps:
+ // If the new playback position is less than zero, then set it to zero.
+ time = max(0.0f, time);
+
+ // If the new playback position is greater than the media controller duration, then set it
+ // to the media controller duration.
+ time = min(time, duration());
+
+ // Set the media controller position to the new playback position.
+ m_clock->setCurrentTime(time);
+
+ // Seek each slaved media element to the new playback position relative to the media element timeline.
+ for (size_t index = 0; index < m_mediaElements.size(); ++index)
+ m_mediaElements[index]->seek(time, code);
+}
+
+void MediaController::play()
+{
+ // When the play() method is invoked, if the MediaController is a paused media controller,
+ if (!m_paused)
+ return;
+
+ // the user agent must change the MediaController into a playing media controller,
+ m_paused = false;
+ // queue a task to fire a simple event named play at the MediaController,
+ scheduleEvent(eventNames().playEvent);
+ // and then report the controller state of the MediaController.
+ reportControllerState();
+}
+
+void MediaController::pause()
+{
+ // When the pause() method is invoked, if the MediaController is a playing media controller,
+ if (m_paused)
+ return;
+
+ // then the user agent must change the MediaController into a paused media controller,
+ m_paused = true;
+ // queue a task to fire a simple event named pause at the MediaController,
+ scheduleEvent(eventNames().pauseEvent);
+ // and then report the controller state of the MediaController.
+ reportControllerState();
+}
+
+void MediaController::setDefaultPlaybackRate(float rate)
+{
+ if (m_defaultPlaybackRate == rate)
+ return;
+
+ // The defaultPlaybackRate attribute, on setting, must set the MediaController's media controller
+ // default playback rate to the new value,
+ m_defaultPlaybackRate = rate;
+
+ // then queue a task to fire a simple event named ratechange at the MediaController.
+ scheduleEvent(eventNames().ratechangeEvent);
+}
+
+float MediaController::playbackRate() const
+{
+ return m_clock->playRate();
+}
+
+void MediaController::setPlaybackRate(float rate)
+{
+ if (m_clock->playRate() == rate)
+ return;
+
+ // The playbackRate attribute, on setting, must set the MediaController's media controller
+ // playback rate to the new value,
+ m_clock->setPlayRate(rate);
+
+ for (size_t index = 0; index < m_mediaElements.size(); ++index)
+ m_mediaElements[index]->updatePlaybackRate();
+
+ // then queue a task to fire a simple event named ratechange at the MediaController.
+ scheduleEvent(eventNames().ratechangeEvent);
+}
+
+void MediaController::setVolume(float level, ExceptionCode& code)
+{
+ if (m_volume == level)
+ return;
+
+ // If the new value is outside the range 0.0 to 1.0 inclusive, then, on setting, an
+ // IndexSizeError exception must be raised instead.
+ if (level < 0 || level > 1) {
+ code = INDEX_SIZE_ERR;
+ return;
+ }
+
+ // The volume attribute, on setting, if the new value is in the range 0.0 to 1.0 inclusive,
+ // must set the MediaController's media controller volume multiplier to the new value
+ m_volume = level;
+
+ // and queue a task to fire a simple event named volumechange at the MediaController.
+ scheduleEvent(eventNames().volumechangeEvent);
+
+ for (size_t index = 0; index < m_mediaElements.size(); ++index)
+ m_mediaElements[index]->updateVolume();
+}
+
+void MediaController::setMuted(bool flag)
+{
+ if (m_muted == flag)
+ return;
+
+ // The muted attribute, on setting, must set the MediaController's media controller mute override
+ // to the new value
+ m_muted = flag;
+
+ // and queue a task to fire a simple event named volumechange at the MediaController.
+ scheduleEvent(eventNames().volumechangeEvent);
+
+ for (size_t index = 0; index < m_mediaElements.size(); ++index)
+ m_mediaElements[index]->updateVolume();
+}
+
+void MediaController::reportControllerState()
+{
+ updateReadyState();
+ updatePlaybackState();
+}
+
+static AtomicString eventNameForReadyState(MediaControllerInterface::ReadyState state)
+{
+ switch (state) {
+ case MediaControllerInterface::HAVE_NOTHING:
+ return eventNames().emptiedEvent;
+ case MediaControllerInterface::HAVE_METADATA:
+ return eventNames().loadedmetadataEvent;
+ case MediaControllerInterface::HAVE_CURRENT_DATA:
+ return eventNames().loadeddataEvent;
+ case MediaControllerInterface::HAVE_FUTURE_DATA:
+ return eventNames().canplayEvent;
+ case MediaControllerInterface::HAVE_ENOUGH_DATA:
+ return eventNames().canplaythroughEvent;
+ default:
+ ASSERT_NOT_REACHED();
+ return nullAtom;
+ }
+}
+
+void MediaController::updateReadyState()
+{
+ ReadyState oldReadyState = m_readyState;
+ ReadyState newReadyState;
+
+ if (m_mediaElements.isEmpty()) {
+ // If the MediaController has no slaved media elements, let new readiness state be 0.
+ newReadyState = HAVE_NOTHING;
+ } else {
+ // Otherwise, let it have the lowest value of the readyState IDL attributes of all of its
+ // slaved media elements.
+ newReadyState = m_mediaElements.first()->readyState();
+ for (size_t index = 1; index < m_mediaElements.size(); ++index)
+ newReadyState = min(newReadyState, m_mediaElements[index]->readyState());
+ }
+
+ if (newReadyState == oldReadyState)
+ return;
+
+ // If the MediaController's most recently reported readiness state is greater than new readiness
+ // state then queue a task to fire a simple event at the MediaController object, whose name is the
+ // event name corresponding to the value of new readiness state given in the table below. [omitted]
+ if (oldReadyState > newReadyState) {
+ scheduleEvent(eventNameForReadyState(newReadyState));
+ return;
+ }
+
+ // If the MediaController's most recently reported readiness state is less than the new readiness
+ // state, then run these substeps:
+ // 1. Let next state be the MediaController's most recently reported readiness state.
+ ReadyState nextState = oldReadyState;
+ do {
+ // 2. Loop: Increment next state by one.
+ nextState = static_cast<ReadyState>(nextState + 1);
+ // 3. Queue a task to fire a simple event at the MediaController object, whose name is the
+ // event name corresponding to the value of next state given in the table below. [omitted]
+ scheduleEvent(eventNameForReadyState(nextState));
+ // If next state is less than new readiness state, then return to the step labeled loop
+ } while (nextState < newReadyState);
+
+ // Let the MediaController's most recently reported readiness state be new readiness state.
+ m_readyState = newReadyState;
+}
+
+void MediaController::updatePlaybackState()
+{
+ PlaybackState oldPlaybackState = m_playbackState;
+ PlaybackState newPlaybackState;
+
+ // Initialize new playback state by setting it to the state given for the first matching
+ // condition from the following list:
+ if (m_mediaElements.isEmpty()) {
+ // If the MediaController has no slaved media elements
+ // Let new playback state be waiting.
+ newPlaybackState = WAITING;
+ } else if (hasEnded()) {
+ // If all of the MediaController's slaved media elements have ended playback and the media
+ // controller playback rate is positive or zero
+ // Let new playback state be ended.
+ newPlaybackState = ENDED;
+ } else if (isBlocked()) {
+ // If the MediaController is a blocked media controller
+ // Let new playback state be waiting.
+ newPlaybackState = WAITING;
+ } else {
+ // Otherwise
+ // Let new playback state be playing.
+ newPlaybackState = PLAYING;
+ }
+
+ // If the MediaController's most recently reported playback state is not equal to new playback state
+ if (newPlaybackState == oldPlaybackState)
+ return;
+
+ // and the new playback state is ended,
+ if (newPlaybackState == ENDED) {
+ // then queue a task that, if the MediaController object is a playing media controller, and
+ // all of the MediaController's slaved media elements have still ended playback, and the
+ // media controller playback rate is still positive or zero,
+ if (!m_paused && hasEnded()) {
+ // changes the MediaController object to a paused media controller
+ m_paused = true;
+
+ // and then fires a simple event named pause at the MediaController object.
+ scheduleEvent(eventNames().pauseEvent);
+ }
+ }
+
+ // If the MediaController's most recently reported playback state is not equal to new playback state
+ // then queue a task to fire a simple event at the MediaController object, whose name is playing
+ // if new playback state is playing, ended if new playback state is ended, and waiting otherwise.
+ AtomicString eventName;
+ switch (newPlaybackState) {
+ case WAITING:
+ eventName = eventNames().waitingEvent;
+ m_clock->stop();
+ break;
+ case ENDED:
+ eventName = eventNames().endedEvent;
+ m_clock->stop();
+ break;
+ case PLAYING:
+ eventName = eventNames().playingEvent;
+ m_clock->start();
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ scheduleEvent(eventName);
+
+ // Let the MediaController's most recently reported playback state be new playback state.
+ m_playbackState = newPlaybackState;
+
+ updateMediaElements();
+}
+
+void MediaController::updateMediaElements()
+{
+ for (size_t index = 0; index < m_mediaElements.size(); ++index)
+ m_mediaElements[index]->updatePlayState();
+}
+
+void MediaController::bringElementUpToSpeed(HTMLMediaElement* element)
+{
+ ASSERT(element);
+ ASSERT(m_mediaElements.contains(element));
+
+ // When the user agent is to bring a media element up to speed with its new media controller,
+ // it must seek that media element to the MediaController's media controller position relative
+ // to the media element's timeline.
+ ExceptionCode ignoredCode = 0;
+ element->seek(currentTime(), ignoredCode);
+}
+
+bool MediaController::isBlocked() const
+{
+ // A MediaController is a blocked media controller if the MediaController is a paused media
+ // controller,
+ if (m_paused)
+ return true;
+
+ if (m_mediaElements.isEmpty())
+ return false;
+
+ bool allPaused = true;
+ for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+ HTMLMediaElement* element = m_mediaElements[index];
+ // or if any of its slaved media elements are blocked media elements,
+ if (element->isBlocked())
+ return true;
+
+ // or if any of its slaved media elements whose autoplaying flag is true still have their
+ // paused attribute set to true,
+ if (element->isAutoplaying() && element->paused())
+ return true;
+
+ if (!element->paused())
+ allPaused = false;
+ }
+
+ // or if all of its slaved media elements have their paused attribute set to true.
+ return allPaused;
+}
+
+bool MediaController::hasEnded() const
+{
+ // If the ... media controller playback rate is positive or zero
+ if (m_clock->playRate() < 0)
+ return false;
+
+ // [and] all of the MediaController's slaved media elements have ended playback ... let new
+ // playback state be ended.
+ if (m_mediaElements.isEmpty())
+ return false;
+
+ bool allHaveEnded = true;
+ for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+ if (!m_mediaElements[index]->ended())
+ allHaveEnded = false;
+ }
+ return allHaveEnded;
+}
+
+void MediaController::scheduleEvent(const AtomicString& eventName)
+{
+ m_pendingEvents.append(Event::create(eventName, false, true));
+ if (!m_asyncEventTimer.isActive())
+ m_asyncEventTimer.startOneShot(0);
+}
+
+void MediaController::asyncEventTimerFired(Timer<MediaController>*)
+{
+ Vector<RefPtr<Event> > pendingEvents;
+ ExceptionCode ec = 0;
+
+ m_pendingEvents.swap(pendingEvents);
+ size_t count = pendingEvents.size();
+ for (size_t index = 0; index < count; ++index)
+ dispatchEvent(pendingEvents[index].release(), ec);
+}
+
+bool MediaController::hasAudio() const
+{
+ for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+ if (m_mediaElements[index]->hasAudio())
+ return true;
+ }
+ return false;
+}
+
+bool MediaController::hasVideo() const
+{
+ for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+ if (m_mediaElements[index]->hasVideo())
+ return true;
+ }
+ return false;
+}
+
+bool MediaController::hasClosedCaptions() const
+{
+ for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+ if (m_mediaElements[index]->hasClosedCaptions())
+ return true;
+ }
+ return false;
+}
+
+void MediaController::setClosedCaptionsVisible(bool visible)
+{
+ m_closedCaptionsVisible = visible;
+ for (size_t index = 0; index < m_mediaElements.size(); ++index)
+ m_mediaElements[index]->setClosedCaptionsVisible(visible);
+}
+
+bool MediaController::supportsScanning() const
+{
+ for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+ if (!m_mediaElements[index]->supportsScanning())
+ return false;
+ }
+ return true;
+}
+
+void MediaController::beginScrubbing()
+{
+ for (size_t index = 0; index < m_mediaElements.size(); ++index)
+ m_mediaElements[index]->beginScrubbing();
+ if (m_playbackState == PLAYING)
+ m_clock->stop();
+}
+
+void MediaController::endScrubbing()
+{
+ for (size_t index = 0; index < m_mediaElements.size(); ++index)
+ m_mediaElements[index]->endScrubbing();
+ if (m_playbackState == PLAYING)
+ m_clock->start();
+}
+
+bool MediaController::canPlay() const
+{
+ if (m_paused)
+ return true;
+
+ for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+ if (!m_mediaElements[index]->canPlay())
+ return false;
+ }
+ return true;
+}
+
+bool MediaController::isLiveStream() const
+{
+ for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+ if (!m_mediaElements[index]->isLiveStream())
+ return false;
+ }
+ return true;
+}
+
+bool MediaController::hasCurrentSrc() const
+{
+ for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+ if (!m_mediaElements[index]->hasCurrentSrc())
+ return false;
+ }
+ return true;
+}
+
+void MediaController::returnToRealtime()
+{
+ for (size_t index = 0; index < m_mediaElements.size(); ++index)
+ m_mediaElements[index]->returnToRealtime();
+}
+
+const AtomicString& MediaController::interfaceName() const
+{
+ return eventNames().interfaceForMediaController;
+}
+
+#endif