LibMedia: Use a combined status to exit the seeking/buffering states
Instead of tracking in-flight seeks across all the sinks in the seeking state handler, move the logic to PlaybackManager to determine the overall status and then notify the state of that status to potentially trigger resumption. The buffering state handler can then share essentially the same logic instead of having the playback manager specifically track the blocked tracks for it.
This commit is contained in:
parent
042b458f75
commit
5a120bff33
13 changed files with 116 additions and 98 deletions
|
|
@ -21,6 +21,8 @@ set(SOURCES
|
|||
GenericTimeProvider.cpp
|
||||
IncrementallyPopulatedStream.cpp
|
||||
PlaybackManager.cpp
|
||||
PlaybackStates/BufferingStateHandler.cpp
|
||||
PlaybackStates/PlayingStateHandler.cpp
|
||||
PlaybackStates/StartingStateHandler.cpp
|
||||
PlaybackStates/PausedStateHandler.cpp
|
||||
PlaybackStates/PlaybackStateHandler.cpp
|
||||
|
|
|
|||
|
|
@ -251,33 +251,46 @@ AK::Duration PlaybackManager::current_time() const
|
|||
|
||||
void PlaybackManager::on_audio_sink_state_changed(PipelineStatus status)
|
||||
{
|
||||
m_audio_buffering = status == PipelineStatus::Blocked;
|
||||
update_buffering_state();
|
||||
m_handler->on_audio_sink_state_changed(status);
|
||||
m_audio_sink_status = status;
|
||||
update_pipeline_state();
|
||||
}
|
||||
|
||||
void PlaybackManager::on_video_sink_state_changed(Track const& track, PipelineStatus status)
|
||||
{
|
||||
if (status == PipelineStatus::Blocked) {
|
||||
if (m_video_tracks_buffering.set(track) == HashSetResult::InsertedNewEntry)
|
||||
update_buffering_state();
|
||||
} else {
|
||||
if (m_video_tracks_buffering.remove(track))
|
||||
update_buffering_state();
|
||||
}
|
||||
m_handler->on_video_sink_state_changed(track, status);
|
||||
auto& track_data = get_video_data_for_track(track);
|
||||
track_data.sink_status = status;
|
||||
update_pipeline_state();
|
||||
}
|
||||
|
||||
void PlaybackManager::update_buffering_state()
|
||||
PipelineStatus PlaybackManager::combined_pipeline_status() const
|
||||
{
|
||||
auto is_buffering = m_audio_buffering || !m_video_tracks_buffering.is_empty();
|
||||
if (is_buffering == m_was_buffering)
|
||||
return;
|
||||
m_was_buffering = is_buffering;
|
||||
if (is_buffering)
|
||||
m_handler->enter_buffering();
|
||||
else
|
||||
m_handler->exit_buffering();
|
||||
auto status = PipelineStatus::EndOfStream;
|
||||
|
||||
if (m_audio_sink != nullptr)
|
||||
status = select_combined_pipeline_status(status, m_audio_sink_status);
|
||||
|
||||
for (auto const& track_data : m_video_track_datas) {
|
||||
if (track_data.display == nullptr)
|
||||
continue;
|
||||
status = select_combined_pipeline_status(status, track_data.sink_status);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void PlaybackManager::update_pipeline_state()
|
||||
{
|
||||
m_handler->on_pipeline_status_changed(combined_pipeline_status());
|
||||
}
|
||||
|
||||
void PlaybackManager::reset_pipeline_state()
|
||||
{
|
||||
for (auto& track_data : m_video_track_datas) {
|
||||
if (track_data.display == nullptr)
|
||||
continue;
|
||||
track_data.sink_status = PipelineStatus::Pending;
|
||||
}
|
||||
m_audio_sink_status = m_audio_sink != nullptr ? PipelineStatus::Pending : PipelineStatus::HaveData;
|
||||
}
|
||||
|
||||
void PlaybackManager::check_for_duration_change(AK::Duration duration)
|
||||
|
|
@ -317,7 +330,6 @@ void PlaybackManager::set_time_provider(NonnullRefPtr<MediaTimeProvider> const&
|
|||
|
||||
void PlaybackManager::disable_audio()
|
||||
{
|
||||
m_audio_buffering = false;
|
||||
m_audio_mixer = nullptr;
|
||||
m_audio_time_stretch_processor = nullptr;
|
||||
m_audio_sink = nullptr;
|
||||
|
|
@ -329,6 +341,7 @@ NonnullRefPtr<DisplayingVideoSink> PlaybackManager::get_or_create_the_displaying
|
|||
{
|
||||
auto& track_data = get_video_data_for_track(track);
|
||||
if (track_data.display == nullptr) {
|
||||
track_data.sink_status = PipelineStatus::HaveData;
|
||||
auto display = MUST(Media::DisplayingVideoSink::try_create(m_time_provider,
|
||||
[self = weak(), track](PipelineStatus status) {
|
||||
if (!self)
|
||||
|
|
@ -337,6 +350,7 @@ NonnullRefPtr<DisplayingVideoSink> PlaybackManager::get_or_create_the_displaying
|
|||
}));
|
||||
MUST(display->connect_input(track_data.producer));
|
||||
track_data.display = move(display);
|
||||
update_pipeline_state();
|
||||
}
|
||||
return *track_data.display;
|
||||
}
|
||||
|
|
@ -347,29 +361,34 @@ void PlaybackManager::remove_the_displaying_video_sink_for_track(Track const& tr
|
|||
VERIFY(track_data.display);
|
||||
track_data.display->disconnect_input(track_data.producer);
|
||||
track_data.display = nullptr;
|
||||
on_video_sink_state_changed(track, PipelineStatus::EndOfStream);
|
||||
track_data.sink_status = PipelineStatus::HaveData;
|
||||
update_pipeline_state();
|
||||
}
|
||||
|
||||
void PlaybackManager::enable_an_audio_track(Track const& track)
|
||||
{
|
||||
auto& track_data = get_audio_data_for_track(track);
|
||||
VERIFY(!track_data.enabled);
|
||||
m_audio_sink_status = PipelineStatus::HaveData;
|
||||
if (m_audio_mixer) {
|
||||
m_audio_mixer->seek(current_time());
|
||||
MUST(m_audio_mixer->connect_input(track_data.producer));
|
||||
}
|
||||
track_data.enabled = true;
|
||||
update_pipeline_state();
|
||||
}
|
||||
|
||||
void PlaybackManager::disable_an_audio_track(Track const& track)
|
||||
{
|
||||
auto& track_data = get_audio_data_for_track(track);
|
||||
VERIFY(track_data.enabled);
|
||||
m_audio_sink_status = PipelineStatus::HaveData;
|
||||
if (m_audio_mixer) {
|
||||
m_audio_mixer->seek(current_time());
|
||||
m_audio_mixer->disconnect_input(track_data.producer);
|
||||
}
|
||||
track_data.enabled = false;
|
||||
update_pipeline_state();
|
||||
}
|
||||
|
||||
bool PlaybackManager::track_is_enabled(Track const& track) const
|
||||
|
|
@ -401,8 +420,10 @@ void PlaybackManager::pause()
|
|||
|
||||
void PlaybackManager::seek(AK::Duration timestamp, SeekMode mode)
|
||||
{
|
||||
reset_pipeline_state();
|
||||
m_handler->seek(timestamp, mode);
|
||||
m_is_in_error_state = false;
|
||||
update_pipeline_state();
|
||||
}
|
||||
|
||||
bool PlaybackManager::is_playing()
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ private:
|
|||
Track track;
|
||||
NonnullRefPtr<DecodedVideoProducer> producer;
|
||||
RefPtr<DisplayingVideoSink> display;
|
||||
PipelineStatus sink_status { PipelineStatus::HaveData };
|
||||
};
|
||||
using VideoTrackDatas = Vector<VideoTrackData, EXPECTED_VIDEO_TRACK_COUNT>;
|
||||
|
||||
|
|
@ -126,7 +127,9 @@ private:
|
|||
void set_up_producers();
|
||||
void on_audio_sink_state_changed(PipelineStatus);
|
||||
void on_video_sink_state_changed(Track const&, PipelineStatus);
|
||||
void update_buffering_state();
|
||||
void update_pipeline_state();
|
||||
void reset_pipeline_state();
|
||||
PipelineStatus combined_pipeline_status() const;
|
||||
void check_for_duration_change(AK::Duration);
|
||||
void dispatch_error(DecoderError&&);
|
||||
|
||||
|
|
@ -182,9 +185,7 @@ private:
|
|||
AK::Duration m_duration;
|
||||
Optional<AK::UnixDateTime> m_start_time_realtime;
|
||||
|
||||
bool m_audio_buffering { false };
|
||||
HashTable<Track> m_video_tracks_buffering;
|
||||
bool m_was_buffering { false };
|
||||
PipelineStatus m_audio_sink_status { PipelineStatus::HaveData };
|
||||
|
||||
bool m_is_in_error_state { false };
|
||||
};
|
||||
|
|
|
|||
19
Libraries/LibMedia/PlaybackStates/BufferingStateHandler.cpp
Normal file
19
Libraries/LibMedia/PlaybackStates/BufferingStateHandler.cpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2025-2026, Gregory Bertilson <gregory@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "BufferingStateHandler.h"
|
||||
|
||||
#include <LibMedia/PlaybackManager.h>
|
||||
|
||||
namespace Media {
|
||||
|
||||
void BufferingStateHandler::on_pipeline_status_changed(PipelineStatus status)
|
||||
{
|
||||
if (status != PipelineStatus::Blocked)
|
||||
resume();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -30,14 +30,7 @@ public:
|
|||
return AvailableData::Current;
|
||||
}
|
||||
|
||||
virtual void enter_buffering() override
|
||||
{
|
||||
}
|
||||
|
||||
virtual void exit_buffering() override
|
||||
{
|
||||
resume();
|
||||
}
|
||||
virtual void on_pipeline_status_changed(PipelineStatus) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,9 +37,6 @@ public:
|
|||
{
|
||||
return AvailableData::Future;
|
||||
}
|
||||
|
||||
virtual void enter_buffering() override { }
|
||||
virtual void exit_buffering() override { }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,4 +21,9 @@ void PlaybackStateHandler::seek(AK::Duration timestamp, SeekMode mode)
|
|||
manager().replace_state_handler<SeekingStateHandler>(manager().is_playing(), timestamp, mode);
|
||||
}
|
||||
|
||||
void PlaybackStateHandler::on_pipeline_status_changed(PipelineStatus status)
|
||||
{
|
||||
(void)status;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,11 +37,7 @@ public:
|
|||
virtual PlaybackState state() = 0;
|
||||
virtual AvailableData available_data() = 0;
|
||||
|
||||
virtual void enter_buffering() = 0;
|
||||
virtual void exit_buffering() = 0;
|
||||
|
||||
virtual void on_audio_sink_state_changed(PipelineStatus) { }
|
||||
virtual void on_video_sink_state_changed(Track const&, PipelineStatus) { }
|
||||
virtual void on_pipeline_status_changed(PipelineStatus);
|
||||
|
||||
protected:
|
||||
PlaybackManager& manager() const { return m_manager; }
|
||||
|
|
|
|||
26
Libraries/LibMedia/PlaybackStates/PlayingStateHandler.cpp
Normal file
26
Libraries/LibMedia/PlaybackStates/PlayingStateHandler.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2025-2026, Gregory Bertilson <gregory@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "PlayingStateHandler.h"
|
||||
|
||||
#include <LibMedia/PlaybackManager.h>
|
||||
#include <LibMedia/PlaybackStates/BufferingStateHandler.h>
|
||||
#include <LibMedia/PlaybackStates/PausedStateHandler.h>
|
||||
|
||||
namespace Media {
|
||||
|
||||
void PlayingStateHandler::pause()
|
||||
{
|
||||
manager().replace_state_handler<PausedStateHandler>();
|
||||
}
|
||||
|
||||
void PlayingStateHandler::on_pipeline_status_changed(PipelineStatus status)
|
||||
{
|
||||
if (status == PipelineStatus::Blocked)
|
||||
manager().replace_state_handler<BufferingStateHandler>(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,9 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibMedia/PlaybackManager.h>
|
||||
#include <LibMedia/PlaybackStates/BufferingStateHandler.h>
|
||||
#include <LibMedia/PlaybackStates/Forward.h>
|
||||
#include <LibMedia/PlaybackStates/PausedStateHandler.h>
|
||||
|
||||
namespace Media {
|
||||
|
||||
|
|
@ -31,10 +29,7 @@ public:
|
|||
}
|
||||
|
||||
virtual void play() override { }
|
||||
virtual void pause() override
|
||||
{
|
||||
manager().replace_state_handler<PausedStateHandler>();
|
||||
}
|
||||
virtual void pause() override;
|
||||
|
||||
virtual bool is_playing() override
|
||||
{
|
||||
|
|
@ -49,11 +44,7 @@ public:
|
|||
return AvailableData::Future;
|
||||
}
|
||||
|
||||
virtual void enter_buffering() override
|
||||
{
|
||||
manager().replace_state_handler<BufferingStateHandler>(true);
|
||||
}
|
||||
virtual void exit_buffering() override { }
|
||||
virtual void on_pipeline_status_changed(PipelineStatus) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibMedia/PipelineStatus.h>
|
||||
#include <LibMedia/PlaybackManager.h>
|
||||
|
|
@ -36,11 +35,7 @@ public:
|
|||
begin_seek();
|
||||
}
|
||||
|
||||
virtual void on_exit() override
|
||||
{
|
||||
VERIFY(m_video_seeks_pending.is_empty());
|
||||
VERIFY(!m_audio_seek_pending);
|
||||
}
|
||||
virtual void on_exit() override { }
|
||||
|
||||
virtual AK::Duration current_time() const override { return m_chosen_timestamp; }
|
||||
|
||||
|
|
@ -60,23 +55,12 @@ public:
|
|||
return AvailableData::Current;
|
||||
}
|
||||
|
||||
virtual void enter_buffering() override { }
|
||||
virtual void exit_buffering() override { }
|
||||
|
||||
virtual void on_audio_sink_state_changed(PipelineStatus status) override
|
||||
virtual void on_pipeline_status_changed(PipelineStatus status) override
|
||||
{
|
||||
if (!resolves_seek(status))
|
||||
return;
|
||||
m_audio_seek_pending = false;
|
||||
possibly_complete_seek();
|
||||
}
|
||||
|
||||
virtual void on_video_sink_state_changed(Track const& track, PipelineStatus status) override
|
||||
{
|
||||
if (!resolves_seek(status))
|
||||
return;
|
||||
m_video_seeks_pending.remove(track);
|
||||
possibly_complete_seek();
|
||||
resume();
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
@ -98,40 +82,22 @@ private:
|
|||
void begin_seek()
|
||||
{
|
||||
m_chosen_timestamp = choose_timestamp();
|
||||
m_video_seeks_pending.clear();
|
||||
m_audio_seek_pending = false;
|
||||
|
||||
for (auto& video_track_data : manager().m_video_track_datas) {
|
||||
if (video_track_data.display == nullptr)
|
||||
continue;
|
||||
m_video_seeks_pending.set(video_track_data.track);
|
||||
video_track_data.display->seek(m_chosen_timestamp);
|
||||
}
|
||||
|
||||
if (manager().m_audio_sink) {
|
||||
m_audio_seek_pending = true;
|
||||
if (manager().m_audio_sink)
|
||||
manager().m_audio_sink->seek(m_chosen_timestamp);
|
||||
} else {
|
||||
else
|
||||
manager().m_time_provider->seek(m_chosen_timestamp);
|
||||
}
|
||||
|
||||
possibly_complete_seek();
|
||||
}
|
||||
|
||||
void possibly_complete_seek()
|
||||
{
|
||||
if (m_audio_seek_pending)
|
||||
return;
|
||||
if (!m_video_seeks_pending.is_empty())
|
||||
return;
|
||||
resume();
|
||||
}
|
||||
|
||||
AK::Duration m_target_timestamp;
|
||||
SeekMode m_mode { SeekMode::Accurate };
|
||||
AK::Duration m_chosen_timestamp { AK::Duration::zero() };
|
||||
HashTable<Track> m_video_seeks_pending;
|
||||
bool m_audio_seek_pending { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
#include "StartingStateHandler.h"
|
||||
|
||||
#include <LibMedia/PlaybackManager.h>
|
||||
#include <LibMedia/PlaybackStates/BufferingStateHandler.h>
|
||||
|
||||
namespace Media {
|
||||
|
||||
|
|
@ -15,13 +14,15 @@ void StartingStateHandler::start()
|
|||
{
|
||||
m_started = true;
|
||||
|
||||
if (!manager().m_audio_buffering && manager().m_video_tracks_buffering.is_empty())
|
||||
if (!m_pipeline_blocked)
|
||||
resume();
|
||||
}
|
||||
|
||||
void StartingStateHandler::exit_buffering()
|
||||
void StartingStateHandler::on_pipeline_status_changed(PipelineStatus status)
|
||||
{
|
||||
if (m_started)
|
||||
m_pipeline_blocked = status == PipelineStatus::Blocked;
|
||||
|
||||
if (m_started && !m_pipeline_blocked)
|
||||
resume();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ public:
|
|||
return AvailableData::None;
|
||||
}
|
||||
|
||||
virtual void enter_buffering() override { }
|
||||
virtual void exit_buffering() override;
|
||||
virtual void on_pipeline_status_changed(PipelineStatus) override;
|
||||
|
||||
private:
|
||||
bool m_started { false };
|
||||
bool m_pipeline_blocked { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue