691 lines
22 KiB
C++
691 lines
22 KiB
C++
/*
|
|
* 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 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 INC. OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "MediaController.h"
|
|
|
|
#if ENABLE(VIDEO)
|
|
|
|
#include "EventNames.h"
|
|
#include "HTMLMediaElement.h"
|
|
#include "TimeRanges.h"
|
|
#include <pal/system/Clock.h>
|
|
#include <wtf/IsoMallocInlines.h>
|
|
#include <wtf/NeverDestroyed.h>
|
|
#include <wtf/StdLibExtras.h>
|
|
#include <wtf/text/AtomString.h>
|
|
|
|
namespace WebCore {
|
|
|
|
WTF_MAKE_ISO_ALLOCATED_IMPL(MediaController);
|
|
|
|
Ref<MediaController> MediaController::create(ScriptExecutionContext& context)
|
|
{
|
|
return adoptRef(*new MediaController(context));
|
|
}
|
|
|
|
MediaController::MediaController(ScriptExecutionContext& context)
|
|
: m_paused(false)
|
|
, m_defaultPlaybackRate(1)
|
|
, m_volume(1)
|
|
, m_position(MediaPlayer::invalidTime())
|
|
, m_muted(false)
|
|
, m_readyState(HAVE_NOTHING)
|
|
, m_playbackState(WAITING)
|
|
, m_asyncEventTimer(*this, &MediaController::asyncEventTimerFired)
|
|
, m_clearPositionTimer(*this, &MediaController::clearPositionTimerFired)
|
|
, m_closedCaptionsVisible(false)
|
|
, m_clock(PAL::Clock::create())
|
|
, m_scriptExecutionContext(context)
|
|
, m_timeupdateTimer(*this, &MediaController::scheduleTimeupdateEvent)
|
|
{
|
|
}
|
|
|
|
MediaController::~MediaController() = default;
|
|
|
|
void MediaController::addMediaElement(HTMLMediaElement& element)
|
|
{
|
|
ASSERT(!m_mediaElements.contains(&element));
|
|
|
|
m_mediaElements.append(&element);
|
|
bringElementUpToSpeed(element);
|
|
}
|
|
|
|
void MediaController::removeMediaElement(HTMLMediaElement& element)
|
|
{
|
|
ASSERT(m_mediaElements.contains(&element));
|
|
m_mediaElements.remove(m_mediaElements.find(&element));
|
|
}
|
|
|
|
bool MediaController::containsMediaElement(HTMLMediaElement& element) const
|
|
{
|
|
return m_mediaElements.contains(&element);
|
|
}
|
|
|
|
Ref<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 mediagroup elements that the
|
|
// user agent has buffered, at the time the attribute is evaluated.
|
|
Ref<TimeRanges> bufferedRanges = m_mediaElements.first()->buffered();
|
|
for (size_t index = 1; index < m_mediaElements.size(); ++index)
|
|
bufferedRanges->intersectWith(m_mediaElements[index]->buffered());
|
|
return bufferedRanges;
|
|
}
|
|
|
|
Ref<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 mediagroup elements that the
|
|
// user agent is able to seek to, at the time the attribute is evaluated.
|
|
Ref<TimeRanges> seekableRanges = m_mediaElements.first()->seekable();
|
|
for (size_t index = 1; index < m_mediaElements.size(); ++index)
|
|
seekableRanges->intersectWith(m_mediaElements[index]->seekable());
|
|
return seekableRanges;
|
|
}
|
|
|
|
Ref<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 mediagroup elements that the
|
|
// user agent has so far rendered, at the time the attribute is evaluated.
|
|
Ref<TimeRanges> playedRanges = m_mediaElements.first()->played();
|
|
for (size_t index = 1; index < m_mediaElements.size(); ++index)
|
|
playedRanges->unionWith(m_mediaElements[index]->played());
|
|
return playedRanges;
|
|
}
|
|
|
|
double MediaController::duration() const
|
|
{
|
|
// FIXME: Investigate caching the maximum duration and only updating the cached value
|
|
// when the mediagroup elements' durations change.
|
|
double maxDuration = 0;
|
|
for (auto& mediaElement : m_mediaElements) {
|
|
double duration = mediaElement->duration();
|
|
if (std::isnan(duration))
|
|
continue;
|
|
maxDuration = std::max(maxDuration, duration);
|
|
}
|
|
return maxDuration;
|
|
}
|
|
|
|
double MediaController::currentTime() const
|
|
{
|
|
if (m_mediaElements.isEmpty())
|
|
return 0;
|
|
|
|
if (m_position == MediaPlayer::invalidTime()) {
|
|
// Some clocks may return times outside the range of [0..duration].
|
|
m_position = std::max<double>(0, std::min(duration(), m_clock->currentTime()));
|
|
m_clearPositionTimer.startOneShot(0_s);
|
|
}
|
|
|
|
return m_position;
|
|
}
|
|
|
|
void MediaController::setCurrentTime(double time)
|
|
{
|
|
// 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 = std::max(0.0, time);
|
|
|
|
// If the new playback position is greater than the media controller duration, then set it
|
|
// to the media controller duration.
|
|
time = std::min(time, duration());
|
|
|
|
// Set the media controller position to the new playback position.
|
|
m_clock->setCurrentTime(time);
|
|
|
|
// Seek each mediagroup element to the new playback position relative to the media element timeline.
|
|
for (auto& mediaElement : m_mediaElements)
|
|
mediaElement->seek(MediaTime::createWithDouble(time));
|
|
|
|
scheduleTimeupdateEvent();
|
|
m_resetCurrentTimeInNextPlay = false;
|
|
}
|
|
|
|
void MediaController::unpause()
|
|
{
|
|
// When the unpause() 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::play()
|
|
{
|
|
// When the play() method is invoked, the user agent must invoke the play method of each
|
|
// mediagroup element in turn,
|
|
for (auto& mediaElement : m_mediaElements)
|
|
mediaElement->play();
|
|
|
|
// and then invoke the unpause method of the MediaController.
|
|
unpause();
|
|
}
|
|
|
|
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(double 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);
|
|
}
|
|
|
|
double MediaController::playbackRate() const
|
|
{
|
|
return m_clock->playRate();
|
|
}
|
|
|
|
void MediaController::setPlaybackRate(double 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 (auto& mediaElement : m_mediaElements)
|
|
mediaElement->updatePlaybackRate();
|
|
|
|
// then queue a task to fire a simple event named ratechange at the MediaController.
|
|
scheduleEvent(eventNames().ratechangeEvent);
|
|
}
|
|
|
|
ExceptionOr<void> MediaController::setVolume(double level)
|
|
{
|
|
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))
|
|
return Exception { IndexSizeError };
|
|
|
|
// 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 (auto& mediaElement : m_mediaElements)
|
|
mediaElement->updateVolume();
|
|
|
|
return { };
|
|
}
|
|
|
|
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 (auto& mediaElement : m_mediaElements)
|
|
mediaElement->updateVolume();
|
|
}
|
|
|
|
static const AtomString& playbackStateWaiting()
|
|
{
|
|
static MainThreadNeverDestroyed<const AtomString> waiting("waiting", AtomString::ConstructFromLiteral);
|
|
return waiting;
|
|
}
|
|
|
|
static const AtomString& playbackStatePlaying()
|
|
{
|
|
static MainThreadNeverDestroyed<const AtomString> playing("playing", AtomString::ConstructFromLiteral);
|
|
return playing;
|
|
}
|
|
|
|
static const AtomString& playbackStateEnded()
|
|
{
|
|
static MainThreadNeverDestroyed<const AtomString> ended("ended", AtomString::ConstructFromLiteral);
|
|
return ended;
|
|
}
|
|
|
|
const AtomString& MediaController::playbackState() const
|
|
{
|
|
switch (m_playbackState) {
|
|
case WAITING:
|
|
return playbackStateWaiting();
|
|
case PLAYING:
|
|
return playbackStatePlaying();
|
|
case ENDED:
|
|
return playbackStateEnded();
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
return nullAtom();
|
|
}
|
|
}
|
|
|
|
void MediaController::reportControllerState()
|
|
{
|
|
updateReadyState();
|
|
updatePlaybackState();
|
|
}
|
|
|
|
static AtomString 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 mediagroup 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
|
|
// mediagroup elements.
|
|
newReadyState = m_mediaElements.first()->readyState();
|
|
for (size_t index = 1; index < m_mediaElements.size(); ++index)
|
|
newReadyState = std::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 mediagroup elements
|
|
// Let new playback state be waiting.
|
|
newPlaybackState = WAITING;
|
|
} else if (hasEnded()) {
|
|
// If all of the MediaController's mediagroup 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 mediagroup 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.
|
|
AtomString eventName;
|
|
switch (newPlaybackState) {
|
|
case WAITING:
|
|
eventName = eventNames().waitingEvent;
|
|
m_clock->stop();
|
|
m_timeupdateTimer.stop();
|
|
break;
|
|
case ENDED:
|
|
eventName = eventNames().endedEvent;
|
|
m_resetCurrentTimeInNextPlay = true;
|
|
m_clock->stop();
|
|
m_timeupdateTimer.stop();
|
|
break;
|
|
case PLAYING:
|
|
if (m_resetCurrentTimeInNextPlay) {
|
|
m_resetCurrentTimeInNextPlay = false;
|
|
m_clock->setCurrentTime(0);
|
|
}
|
|
eventName = eventNames().playingEvent;
|
|
m_clock->start();
|
|
startTimeupdateTimer();
|
|
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 (auto& mediaElement : m_mediaElements)
|
|
mediaElement->updatePlayState();
|
|
}
|
|
|
|
void MediaController::bringElementUpToSpeed(HTMLMediaElement& 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.
|
|
element.seekInternal(MediaTime::createWithDouble(currentTime()));
|
|
}
|
|
|
|
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 (auto& element : m_mediaElements) {
|
|
// or if any of its mediagroup elements are blocked media elements,
|
|
if (element->isBlocked())
|
|
return true;
|
|
|
|
// or if any of its mediagroup 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 mediagroup 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 mediagroup elements have ended playback ... let new
|
|
// playback state be ended.
|
|
if (m_mediaElements.isEmpty())
|
|
return false;
|
|
|
|
bool allHaveEnded = true;
|
|
for (auto& mediaElement : m_mediaElements) {
|
|
if (!mediaElement->ended())
|
|
allHaveEnded = false;
|
|
}
|
|
return allHaveEnded;
|
|
}
|
|
|
|
void MediaController::scheduleEvent(const AtomString& eventName)
|
|
{
|
|
m_pendingEvents.append(Event::create(eventName, Event::CanBubble::No, Event::IsCancelable::Yes));
|
|
if (!m_asyncEventTimer.isActive())
|
|
m_asyncEventTimer.startOneShot(0_s);
|
|
}
|
|
|
|
void MediaController::asyncEventTimerFired()
|
|
{
|
|
Vector<Ref<Event>> pendingEvents;
|
|
|
|
m_pendingEvents.swap(pendingEvents);
|
|
for (auto& pendingEvent : pendingEvents)
|
|
dispatchEvent(pendingEvent);
|
|
}
|
|
|
|
void MediaController::clearPositionTimerFired()
|
|
{
|
|
m_position = MediaPlayer::invalidTime();
|
|
}
|
|
|
|
bool MediaController::hasAudio() const
|
|
{
|
|
for (auto& mediaElement : m_mediaElements) {
|
|
if (mediaElement->hasAudio())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MediaController::hasVideo() const
|
|
{
|
|
for (auto& mediaElement : m_mediaElements) {
|
|
if (mediaElement->hasVideo())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MediaController::hasClosedCaptions() const
|
|
{
|
|
for (auto& mediaElement : m_mediaElements) {
|
|
if (mediaElement->hasClosedCaptions())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MediaController::setClosedCaptionsVisible(bool visible)
|
|
{
|
|
m_closedCaptionsVisible = visible;
|
|
for (auto& mediaElement : m_mediaElements)
|
|
mediaElement->setClosedCaptionsVisible(visible);
|
|
}
|
|
|
|
bool MediaController::supportsScanning() const
|
|
{
|
|
for (auto& mediaElement : m_mediaElements) {
|
|
if (!mediaElement->supportsScanning())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void MediaController::beginScrubbing()
|
|
{
|
|
for (auto& mediaElement : m_mediaElements)
|
|
mediaElement->beginScrubbing();
|
|
if (m_playbackState == PLAYING)
|
|
m_clock->stop();
|
|
}
|
|
|
|
void MediaController::endScrubbing()
|
|
{
|
|
for (auto& mediaElement : m_mediaElements)
|
|
mediaElement->endScrubbing();
|
|
if (m_playbackState == PLAYING)
|
|
m_clock->start();
|
|
}
|
|
|
|
void MediaController::beginScanning(ScanDirection direction)
|
|
{
|
|
for (auto& mediaElement : m_mediaElements)
|
|
mediaElement->beginScanning(direction);
|
|
}
|
|
|
|
void MediaController::endScanning()
|
|
{
|
|
for (auto& mediaElement : m_mediaElements)
|
|
mediaElement->endScanning();
|
|
}
|
|
|
|
bool MediaController::canPlay() const
|
|
{
|
|
if (m_paused)
|
|
return true;
|
|
|
|
for (auto& mediaElement : m_mediaElements) {
|
|
if (!mediaElement->canPlay())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MediaController::isLiveStream() const
|
|
{
|
|
for (auto& mediaElement : m_mediaElements) {
|
|
if (!mediaElement->isLiveStream())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MediaController::hasCurrentSrc() const
|
|
{
|
|
for (auto& mediaElement : m_mediaElements) {
|
|
if (!mediaElement->hasCurrentSrc())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void MediaController::returnToRealtime()
|
|
{
|
|
for (auto& mediaElement : m_mediaElements)
|
|
mediaElement->returnToRealtime();
|
|
}
|
|
|
|
// The spec says to fire periodic timeupdate events (those sent while playing) every
|
|
// "15 to 250ms", we choose the slowest frequency
|
|
static const Seconds maxTimeupdateEventFrequency { 250_ms };
|
|
|
|
void MediaController::startTimeupdateTimer()
|
|
{
|
|
if (m_timeupdateTimer.isActive())
|
|
return;
|
|
|
|
m_timeupdateTimer.startRepeating(maxTimeupdateEventFrequency);
|
|
}
|
|
|
|
void MediaController::scheduleTimeupdateEvent()
|
|
{
|
|
MonotonicTime now = MonotonicTime::now();
|
|
Seconds timedelta = now - m_previousTimeupdateTime;
|
|
|
|
if (timedelta < maxTimeupdateEventFrequency)
|
|
return;
|
|
|
|
scheduleEvent(eventNames().timeupdateEvent);
|
|
m_previousTimeupdateTime = now;
|
|
}
|
|
|
|
} // namespace WebCore
|
|
|
|
#endif
|