MediaController.cpp [plain text]
#include "config.h"
#if ENABLE(VIDEO)
#include "MediaController.h"
#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();
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();
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();
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
{
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()) {
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)
{
time = std::max(0.0, time);
time = std::min(time, duration());
m_clock->setCurrentTime(time);
for (auto& mediaElement : m_mediaElements)
mediaElement->seek(MediaTime::createWithDouble(time));
scheduleTimeupdateEvent();
m_resetCurrentTimeInNextPlay = false;
}
void MediaController::unpause()
{
if (!m_paused)
return;
m_paused = false;
scheduleEvent(eventNames().playEvent);
reportControllerState();
}
void MediaController::play()
{
for (auto& mediaElement : m_mediaElements)
mediaElement->play();
unpause();
}
void MediaController::pause()
{
if (m_paused)
return;
m_paused = true;
scheduleEvent(eventNames().pauseEvent);
reportControllerState();
}
void MediaController::setDefaultPlaybackRate(double rate)
{
if (m_defaultPlaybackRate == rate)
return;
m_defaultPlaybackRate = rate;
scheduleEvent(eventNames().ratechangeEvent);
}
double MediaController::playbackRate() const
{
return m_clock->playRate();
}
void MediaController::setPlaybackRate(double rate)
{
if (m_clock->playRate() == rate)
return;
m_clock->setPlayRate(rate);
for (auto& mediaElement : m_mediaElements)
mediaElement->updatePlaybackRate();
scheduleEvent(eventNames().ratechangeEvent);
}
ExceptionOr<void> MediaController::setVolume(double level)
{
if (m_volume == level)
return { };
if (!(level >= 0 && level <= 1))
return Exception { IndexSizeError };
m_volume = level;
scheduleEvent(eventNames().volumechangeEvent);
for (auto& mediaElement : m_mediaElements)
mediaElement->updateVolume();
return { };
}
void MediaController::setMuted(bool flag)
{
if (m_muted == flag)
return;
m_muted = flag;
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()) {
newReadyState = HAVE_NOTHING;
} else {
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 (oldReadyState > newReadyState) {
scheduleEvent(eventNameForReadyState(newReadyState));
return;
}
ReadyState nextState = oldReadyState;
do {
nextState = static_cast<ReadyState>(nextState + 1);
scheduleEvent(eventNameForReadyState(nextState));
} while (nextState < newReadyState);
m_readyState = newReadyState;
}
void MediaController::updatePlaybackState()
{
PlaybackState oldPlaybackState = m_playbackState;
PlaybackState newPlaybackState;
if (m_mediaElements.isEmpty()) {
newPlaybackState = WAITING;
} else if (hasEnded()) {
newPlaybackState = ENDED;
} else if (isBlocked()) {
newPlaybackState = WAITING;
} else {
newPlaybackState = PLAYING;
}
if (newPlaybackState == oldPlaybackState)
return;
if (newPlaybackState == ENDED) {
if (!m_paused && hasEnded()) {
m_paused = true;
scheduleEvent(eventNames().pauseEvent);
}
}
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);
m_playbackState = newPlaybackState;
updateMediaElements();
}
void MediaController::updateMediaElements()
{
for (auto& mediaElement : m_mediaElements)
mediaElement->updatePlayState();
}
void MediaController::bringElementUpToSpeed(HTMLMediaElement& element)
{
ASSERT(m_mediaElements.contains(&element));
element.seekInternal(MediaTime::createWithDouble(currentTime()));
}
bool MediaController::isBlocked() const
{
if (m_paused)
return true;
if (m_mediaElements.isEmpty())
return false;
bool allPaused = true;
for (auto& element : m_mediaElements) {
if (element->isBlocked())
return true;
if (element->isAutoplaying() && element->paused())
return true;
if (!element->paused())
allPaused = false;
}
return allPaused;
}
bool MediaController::hasEnded() const
{
if (m_clock->playRate() < 0)
return false;
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();
}
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;
}
}
#endif