LibMedia+LibWeb: Add an initial Starting state to PlaybackManager

This state will indicate to the media element that it's not guaranteed
to have a frame yet, for the purposes of determining the ready state.
JavaScript should be sure that video elements with a ready state of
HAVE_CURRENT_DATA or greater represent the current video frame already.

To allow the state to be exited if audio is disabled, audio tracks are
now only added to the buffering set on enable if the audio sink exists,
since without the sink starting the data provider, it will never be
removed.

This is a step towards making video ref tests.
This commit is contained in:
Zaggy1024
2026-04-18 05:33:02 -05:00
committed by Gregory Bertilson
parent e1e752cc28
commit 08faa83340
Notes: github-actions[bot] 2026-04-22 00:13:12 +00:00
13 changed files with 98 additions and 20 deletions

View File

@@ -8,6 +8,7 @@ set(SOURCES
Containers/Matroska/Reader.cpp
IncrementallyPopulatedStream.cpp
PlaybackManager.cpp
PlaybackStates/StartingStateHandler.cpp
PlaybackStates/PausedStateHandler.cpp
PlaybackStates/PlaybackStateHandler.cpp
PlaybackStates/ResumingStateHandler.cpp

View File

@@ -7,7 +7,7 @@
#include <LibMedia/Containers/Matroska/MatroskaDemuxer.h>
#include <LibMedia/Demuxer.h>
#include <LibMedia/FFmpeg/FFmpegDemuxer.h>
#include <LibMedia/PlaybackStates/PausedStateHandler.h>
#include <LibMedia/PlaybackStates/StartingStateHandler.h>
#include <LibMedia/Providers/AudioDataProvider.h>
#include <LibMedia/Providers/GenericTimeProvider.h>
#include <LibMedia/Providers/VideoDataProvider.h>
@@ -146,7 +146,7 @@ DecoderErrorOr<void> PlaybackManager::prepare_playback_from_demuxer(WeakPlayback
NonnullOwnPtr<PlaybackManager> PlaybackManager::create()
{
auto playback_manager = adopt_own(*new (nothrow) PlaybackManager());
playback_manager->m_handler = make<PausedStateHandler>(*playback_manager, RESUMING_SUSPEND_TIMEOUT_MS);
playback_manager->m_handler = make<StartingStateHandler>(*playback_manager);
playback_manager->m_handler->on_enter();
return playback_manager;
}
@@ -346,12 +346,12 @@ void PlaybackManager::enable_an_audio_track(Track const& track)
{
auto& track_data = get_audio_data_for_track(track);
VERIFY(!track_data.enabled);
track_data.enabled = true;
if (m_audio_sink) {
VERIFY(m_audio_sink->provider(track) == nullptr);
m_audio_sink->set_provider(track, track_data.provider);
m_tracks_still_buffering.set(track);
}
track_data.enabled = true;
m_tracks_still_buffering.set(track);
m_handler->on_track_enabled(track);
}
@@ -384,6 +384,11 @@ bool PlaybackManager::track_is_enabled(Track const& track) const
return true;
}
void PlaybackManager::start()
{
m_handler->start();
}
void PlaybackManager::play()
{
m_handler->play();

View File

@@ -78,6 +78,7 @@ public:
bool track_is_enabled(Track const&) const;
void start();
void play();
void pause();
void seek(AK::Duration timestamp, SeekMode);

View File

@@ -11,6 +11,7 @@
namespace Media {
enum class AvailableData : u8 {
None,
Current,
Future,
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Gregory Bertilson <zaggy1024@gmail.com>
* Copyright (c) 2025-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -9,6 +9,7 @@
#include <LibMedia/PlaybackStates/PlaybackStateHandler.h>
#define ENUMERATE_PLAYBACK_STATE_HANDLERS(X) \
X(StartingStateHandler) \
X(BufferingStateHandler) \
X(PlaybackStateHandler) \
X(PlayingStateHandler) \

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org>
* Copyright (c) 2025-2026, Gregory Bertilson <gregory@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -11,6 +11,7 @@
namespace Media {
enum class PlaybackState : u8 {
Starting,
Buffering,
Playing,
Paused,

View File

@@ -25,6 +25,7 @@ public:
virtual void on_enter() = 0;
virtual void on_exit() = 0;
virtual void start() { }
virtual void play() = 0;
virtual void pause() = 0;
virtual void seek(AK::Duration timestamp, SeekMode);

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "StartingStateHandler.h"
#include <LibMedia/PlaybackManager.h>
#include <LibMedia/PlaybackStates/BufferingStateHandler.h>
namespace Media {
void StartingStateHandler::start()
{
m_started = true;
if (manager().m_tracks_still_buffering.is_empty())
resume();
}
void StartingStateHandler::exit_buffering()
{
if (m_started)
resume();
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibMedia/PlaybackStates/ResumingStateHandler.h>
namespace Media {
class StartingStateHandler final : public ResumingStateHandler {
public:
StartingStateHandler(PlaybackManager& manager)
: ResumingStateHandler(manager, false)
{
}
virtual ~StartingStateHandler() override = default;
virtual void start() override;
virtual PlaybackState state() override
{
return PlaybackState::Starting;
}
virtual AvailableData available_data() override
{
return AvailableData::None;
}
virtual void enter_buffering() override { }
virtual void exit_buffering() override;
private:
bool m_started { false };
};
}

View File

@@ -1743,7 +1743,9 @@ void HTMLMediaElement::on_metadata_parsed()
});
}
// AD-HOC: If we've already got buffered data, we need to upgrade the readyState further than HAVE_METADATA.
// AD-HOC: Now that we've enabled one of each available track type, the playback manager can be started. If this
// causes the playback manager to exit the initial state, the ready state should change.
m_playback_manager->start();
update_ready_state();
}
@@ -2127,7 +2129,8 @@ void HTMLMediaElement::update_ready_state()
auto current_range = ranges.range_at_or_after(current_time);
auto available_data = m_playback_manager->available_data();
if (available_data == Media::AvailableData::Current && !current_range.has_value()) {
if (available_data == Media::AvailableData::None
|| (available_data == Media::AvailableData::Current && !current_range.has_value())) {
// 1. Set the HTMLMediaElement's readyState attribute to HAVE_METADATA.
set_ready_state(ReadyState::HaveMetadata);
// 2. Abort these steps.

View File

@@ -1,15 +1,12 @@
loadstart: readyState=0
durationchange: readyState=4 duration=0
loadedmetadata: readyState=4
loadeddata: readyState=4
canplay: readyState=4
canplaythrough: readyState=4
error: readyState=4 code=3
durationchange: readyState=1 duration=0
loadedmetadata: readyState=1
error: readyState=1 code=3
abort: readyState=0
emptied: readyState=0
loadstart: readyState=0
durationchange: readyState=4 duration=12.043
loadedmetadata: readyState=4
durationchange: readyState=1 duration=12.043
loadedmetadata: readyState=1
loadeddata: readyState=4
canplay: readyState=4
canplaythrough: readyState=4

View File

@@ -5,8 +5,8 @@ Events:
error handler: src setter returned: readyState=0
emptied: readyState=0
loadstart: readyState=0
durationchange: readyState=4 duration=12.043
loadedmetadata: readyState=4
durationchange: readyState=1 duration=12.043
loadedmetadata: readyState=1
loadeddata: readyState=4
canplay: readyState=4
canplaythrough: readyState=4

View File

@@ -1,6 +1,6 @@
loadstart: readyState=0
durationchange: readyState=4 duration=12.043
loadedmetadata: readyState=4
durationchange: readyState=1 duration=12.043
loadedmetadata: readyState=1
loadeddata: readyState=4
canplay: readyState=4
canplaythrough: readyState=4