1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 19:57:35 +00:00

VideoPlayer/LibVideo: Implement the UI functionality for seeking

With these changes, the seek bar can be used, but only to seek to the
start of the file. Seeking to anywhere else in the file will cause an
error in the demuxer.

The timestamp label that was previously invisible now has its text set
according to either the playback or seek slider's position.
This commit is contained in:
Zaggy1024 2022-11-11 22:26:19 -06:00 committed by Andreas Kling
parent e216d1a65f
commit f31621b3f2
7 changed files with 84 additions and 13 deletions

View file

@ -38,6 +38,17 @@ VideoPlayerWidget::VideoPlayerWidget(GUI::Window& window)
m_seek_slider = player_controls_widget.add<GUI::HorizontalSlider>(); m_seek_slider = player_controls_widget.add<GUI::HorizontalSlider>();
m_seek_slider->set_fixed_height(20); m_seek_slider->set_fixed_height(20);
m_seek_slider->set_enabled(false); m_seek_slider->set_enabled(false);
m_seek_slider->on_change = [&](int value) {
if (!m_playback_manager)
return;
update_seek_slider_max();
auto progress = value / static_cast<double>(m_seek_slider->max());
auto duration = m_playback_manager->duration().to_milliseconds();
Time timestamp = Time::from_milliseconds(static_cast<i64>(round(progress * static_cast<double>(duration))));
set_current_timestamp(timestamp);
m_playback_manager->seek_to_timestamp(timestamp);
};
m_seek_slider->set_jump_to_cursor(true);
auto& toolbar_container = player_controls_widget.add<GUI::ToolbarContainer>(); auto& toolbar_container = player_controls_widget.add<GUI::ToolbarContainer>();
m_toolbar = toolbar_container.add<GUI::Toolbar>(); m_toolbar = toolbar_container.add<GUI::Toolbar>();
@ -56,7 +67,7 @@ VideoPlayerWidget::VideoPlayerWidget(GUI::Window& window)
m_toolbar->add_action(*m_play_pause_action); m_toolbar->add_action(*m_play_pause_action);
m_toolbar->add<GUI::VerticalSeparator>(); m_toolbar->add<GUI::VerticalSeparator>();
m_timestamp_label = m_toolbar->add<GUI::Label>(); m_timestamp_label = m_toolbar->add<GUI::Label>();
m_timestamp_label->set_fixed_width(50); m_timestamp_label->set_autosize(true);
m_toolbar->add<GUI::Widget>(); // Filler widget m_toolbar->add<GUI::Widget>(); // Filler widget
@ -89,6 +100,7 @@ void VideoPlayerWidget::open_file(StringView filename)
close_file(); close_file();
m_playback_manager = load_file_result.release_value(); m_playback_manager = load_file_result.release_value();
update_seek_slider_max();
resume_playback(); resume_playback();
} }
@ -162,6 +174,46 @@ void VideoPlayerWidget::on_decoding_error(Video::DecoderError const& error)
GUI::MessageBox::show(&m_window, String::formatted(text_format, error.string_literal()), "Video Player encountered an error"sv); GUI::MessageBox::show(&m_window, String::formatted(text_format, error.string_literal()), "Video Player encountered an error"sv);
} }
void VideoPlayerWidget::update_seek_slider_max()
{
if (!m_playback_manager) {
m_seek_slider->set_enabled(false);
return;
}
m_seek_slider->set_max(static_cast<int>(min(m_playback_manager->duration().to_milliseconds(), NumericLimits<int>::max())));
m_seek_slider->set_enabled(true);
}
void VideoPlayerWidget::set_current_timestamp(Time timestamp)
{
set_time_label(timestamp);
if (!m_playback_manager)
return;
auto progress = static_cast<double>(timestamp.to_milliseconds()) / static_cast<double>(m_playback_manager->duration().to_milliseconds());
m_seek_slider->set_value(static_cast<int>(round(progress * m_seek_slider->max())), GUI::AllowCallback::No);
}
void VideoPlayerWidget::set_time_label(Time timestamp)
{
StringBuilder string_builder;
auto append_time = [&](Time time) {
auto seconds = time.to_seconds();
string_builder.appendff("{:02}:{:02}:{:02}", seconds / 3600, seconds / 60, seconds % 60);
};
append_time(timestamp);
if (m_playback_manager) {
string_builder.append(" / "sv);
append_time(m_playback_manager->duration());
} else {
string_builder.append(" / --:--:--.---"sv);
}
m_timestamp_label->set_text(string_builder.string_view());
}
void VideoPlayerWidget::event(Core::Event& event) void VideoPlayerWidget::event(Core::Event& event)
{ {
if (event.type() == Video::EventType::DecoderErrorOccurred) { if (event.type() == Video::EventType::DecoderErrorOccurred) {
@ -174,9 +226,8 @@ void VideoPlayerWidget::event(Core::Event& event)
m_video_display->set_bitmap(frame_event.frame()); m_video_display->set_bitmap(frame_event.frame());
m_video_display->repaint(); m_video_display->repaint();
m_seek_slider->set_max(m_playback_manager->duration().to_milliseconds()); update_seek_slider_max();
m_seek_slider->set_value(m_playback_manager->current_playback_time().to_milliseconds()); set_current_timestamp(m_playback_manager->current_playback_time());
m_seek_slider->set_enabled(true);
frame_event.accept(); frame_event.accept();
} else if (event.type() == Video::EventType::PlaybackStatusChange) { } else if (event.type() == Video::EventType::PlaybackStatusChange) {

View file

@ -36,6 +36,9 @@ private:
VideoPlayerWidget(GUI::Window&); VideoPlayerWidget(GUI::Window&);
void update_play_pause_icon(); void update_play_pause_icon();
void update_seek_slider_max();
void set_current_timestamp(Time);
void set_time_label(Time);
void on_decoding_error(Video::DecoderError const&); void on_decoding_error(Video::DecoderError const&);
void display_next_frame(); void display_next_frame();

View file

@ -28,7 +28,7 @@ public:
return sample.release_nonnull<VideoSample>(); return sample.release_nonnull<VideoSample>();
} }
virtual DecoderErrorOr<void> seek_to_most_recent_keyframe(Track track, size_t timestamp) = 0; virtual DecoderErrorOr<void> seek_to_most_recent_keyframe(Track track, Time timestamp) = 0;
virtual DecoderErrorOr<Time> duration() = 0; virtual DecoderErrorOr<Time> duration() = 0;

View file

@ -53,9 +53,9 @@ DecoderErrorOr<MatroskaDemuxer::TrackStatus*> MatroskaDemuxer::get_track_status(
return &m_track_statuses.get(track).release_value(); return &m_track_statuses.get(track).release_value();
} }
DecoderErrorOr<void> MatroskaDemuxer::seek_to_most_recent_keyframe(Track track, size_t timestamp) DecoderErrorOr<void> MatroskaDemuxer::seek_to_most_recent_keyframe(Track track, Time timestamp)
{ {
if (timestamp == 0) { if (timestamp.is_zero()) {
// Removing the track status will cause us to start from the beginning. // Removing the track status will cause us to start from the beginning.
m_track_statuses.remove(track); m_track_statuses.remove(track);
return {}; return {};

View file

@ -27,7 +27,7 @@ public:
DecoderErrorOr<Vector<Track>> get_tracks_for_type(TrackType type) override; DecoderErrorOr<Vector<Track>> get_tracks_for_type(TrackType type) override;
DecoderErrorOr<void> seek_to_most_recent_keyframe(Track track, size_t timestamp) override; DecoderErrorOr<void> seek_to_most_recent_keyframe(Track track, Time timestamp) override;
DecoderErrorOr<Time> duration() override; DecoderErrorOr<Time> duration() override;

View file

@ -55,8 +55,6 @@ void PlaybackManager::set_playback_status(PlaybackStatus status)
if (status == PlaybackStatus::Playing) { if (status == PlaybackStatus::Playing) {
if (old_status == PlaybackStatus::Stopped || old_status == PlaybackStatus::Corrupted) { if (old_status == PlaybackStatus::Stopped || old_status == PlaybackStatus::Corrupted) {
restart_playback(); restart_playback();
m_frame_queue->clear();
m_skipped_frames = 0;
} }
m_last_present_in_real_time = Time::now_monotonic(); m_last_present_in_real_time = Time::now_monotonic();
m_present_timer->start(0); m_present_timer->start(0);
@ -94,6 +92,8 @@ bool PlaybackManager::prepare_next_frame()
Time PlaybackManager::current_playback_time() Time PlaybackManager::current_playback_time()
{ {
if (m_last_present_in_media_time.is_negative())
return Time::zero();
if (is_playing()) if (is_playing())
return m_last_present_in_media_time + (Time::now_monotonic() - m_last_present_in_real_time); return m_last_present_in_media_time + (Time::now_monotonic() - m_last_present_in_real_time);
return m_last_present_in_media_time; return m_last_present_in_media_time;
@ -165,6 +165,11 @@ void PlaybackManager::update_presented_frame()
return; return;
} }
if (m_last_present_in_media_time.is_negative()) {
m_last_present_in_media_time = m_next_frame->timestamp();
dbgln_if(PLAYBACK_MANAGER_DEBUG, "We've seeked, set last media time to the next frame {}ms", m_last_present_in_media_time.to_milliseconds());
}
auto frame_time_ms = (m_next_frame->timestamp() - current_playback_time()).to_milliseconds(); auto frame_time_ms = (m_next_frame->timestamp() - current_playback_time()).to_milliseconds();
VERIFY(frame_time_ms <= NumericLimits<int>::max()); VERIFY(frame_time_ms <= NumericLimits<int>::max());
dbgln_if(PLAYBACK_MANAGER_DEBUG, "Time until next frame is {}ms", frame_time_ms); dbgln_if(PLAYBACK_MANAGER_DEBUG, "Time until next frame is {}ms", frame_time_ms);
@ -176,15 +181,26 @@ void PlaybackManager::update_presented_frame()
m_decode_timer->start(0); m_decode_timer->start(0);
} }
void PlaybackManager::restart_playback() void PlaybackManager::seek_to_timestamp(Time timestamp)
{ {
m_last_present_in_media_time = Time::zero(); dbgln_if(PLAYBACK_MANAGER_DEBUG, "Seeking to {}ms", timestamp.to_milliseconds());
m_last_present_in_media_time = Time::min();
m_last_present_in_real_time = Time::zero(); m_last_present_in_real_time = Time::zero();
auto result = m_demuxer->seek_to_most_recent_keyframe(m_selected_video_track, 0); m_frame_queue->clear();
m_next_frame.clear();
m_skipped_frames = 0;
// FIXME: When the demuxer is getting samples off the main thread in the future, this needs to
// mutex so that seeking can't happen while that thread is getting a sample.
auto result = m_demuxer->seek_to_most_recent_keyframe(m_selected_video_track, timestamp);
if (result.is_error()) if (result.is_error())
on_decoder_error(result.release_error()); on_decoder_error(result.release_error());
} }
void PlaybackManager::restart_playback()
{
seek_to_timestamp(Time::zero());
}
void PlaybackManager::post_decoder_error(DecoderError error) void PlaybackManager::post_decoder_error(DecoderError error)
{ {
m_main_loop.post_event(m_event_handler, make<DecoderErrorEvent>(error)); m_main_loop.post_event(m_event_handler, make<DecoderErrorEvent>(error));

View file

@ -101,6 +101,7 @@ public:
void resume_playback(); void resume_playback();
void pause_playback(); void pause_playback();
void restart_playback(); void restart_playback();
void seek_to_timestamp(Time);
bool is_playing() const { return m_status == PlaybackStatus::Playing; } bool is_playing() const { return m_status == PlaybackStatus::Playing; }
bool is_buffering() const { return m_status == PlaybackStatus::Buffering; } bool is_buffering() const { return m_status == PlaybackStatus::Buffering; }
bool is_stopped() const { return m_status == PlaybackStatus::Stopped; } bool is_stopped() const { return m_status == PlaybackStatus::Stopped; }