diff --git a/Userland/Applications/VideoPlayer/VideoPlayerWidget.cpp b/Userland/Applications/VideoPlayer/VideoPlayerWidget.cpp index 95695c7..20e9571 100644 --- a/Userland/Applications/VideoPlayer/VideoPlayerWidget.cpp +++ b/Userland/Applications/VideoPlayer/VideoPlayerWidget.cpp @@ -57,11 +57,16 @@ m_play_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/play.png"sv)); m_pause_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/pause.png"sv)); + m_loop_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/undo.png"sv)); m_play_pause_action = GUI::Action::create("Play", { Key_Space }, m_play_icon, [&](auto&) { toggle_pause(); }); + m_loop_action = GUI::Action::create("Loop", { }, m_loop_icon, [&](auto&) { + toggle_loop_mode(); + }); + m_cycle_sizing_modes_action = GUI::Action::create( "Sizing", TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/fit-image-to-view.png"sv)), [&](auto&) { cycle_sizing_modes(); @@ -74,6 +79,7 @@ m_timestamp_label = find_descendant_of_type_named("timestamp"); m_volume_slider = find_descendant_of_type_named("volume_slider"); find_descendant_of_type_named("playback")->set_action(*m_play_pause_action); + find_descendant_of_type_named("loop")->set_action(*m_loop_action); find_descendant_of_type_named("sizing")->set_action(*m_cycle_sizing_modes_action); find_descendant_of_type_named("fullscreen")->set_action(*m_toggle_fullscreen_action); @@ -147,6 +153,18 @@ } }; + m_playback_manager->on_finished_playing = [this]() { + if (loop_mode()) { + resume_playback(); + } else { + update_play_pause_icon(); + // If we are seeking, do not set the timestamp, as that will override the seek position. + if (!m_was_playing_before_seek && m_playback_manager->get_state() != Media::PlaybackState::Seeking) { + set_current_timestamp(m_playback_manager->current_playback_time()); + } + } + }; + m_playback_manager->on_decoder_error = [this](auto error) { on_decoding_error(error); }; @@ -203,6 +221,15 @@ resume_playback(); } +void VideoPlayerWidget::toggle_loop_mode() +{ + if (!m_loop_mode) { + m_loop_mode = true; + } else { + m_loop_mode = false; + } +} + void VideoPlayerWidget::on_decoding_error(Media::DecoderError const& error) { StringView text_format; diff --git a/Userland/Applications/VideoPlayer/VideoPlayerWidget.gml b/Userland/Applications/VideoPlayer/VideoPlayerWidget.gml index 86c1977..98d826d 100644 --- a/Userland/Applications/VideoPlayer/VideoPlayerWidget.gml +++ b/Userland/Applications/VideoPlayer/VideoPlayerWidget.gml @@ -30,6 +30,13 @@ button_style: "Coolbar" } + @GUI::Button { + name: "loop" + icon_from_path: "/res/icons/16x16/undo.png" + fixed_width: 24 + button_style: "Coolbar" + } + @GUI::VerticalSeparator {} @GUI::Label { diff --git a/Userland/Applications/VideoPlayer/VideoPlayerWidget.h b/Userland/Applications/VideoPlayer/VideoPlayerWidget.h index 67fd78f..9bb34df 100644 --- a/Userland/Applications/VideoPlayer/VideoPlayerWidget.h +++ b/Userland/Applications/VideoPlayer/VideoPlayerWidget.h @@ -28,10 +28,13 @@ ErrorOr initialize(); virtual ~VideoPlayerWidget() override = default; void close_file(); + void reopen_file(); void open_file(FileSystemAccessClient::File filename); void resume_playback(); void pause_playback(); void toggle_pause(); + void toggle_loop_mode(); + bool loop_mode() const { return m_loop_mode; } void update_title(); @@ -63,8 +66,10 @@ RefPtr m_play_icon; RefPtr m_pause_icon; + RefPtr m_loop_icon; RefPtr m_play_pause_action; + RefPtr m_loop_action; RefPtr m_timestamp_label; RefPtr m_cycle_sizing_modes_action; RefPtr m_volume_slider; @@ -81,6 +86,7 @@ OwnPtr m_playback_manager; + bool m_loop_mode { false }; bool m_was_playing_before_seek { false }; }; diff --git a/Userland/Libraries/LibMedia/PlaybackManager.cpp b/Userland/Libraries/LibMedia/PlaybackManager.cpp index c8ace1d..6a2220c 100644 --- a/Userland/Libraries/LibMedia/PlaybackManager.cpp +++ b/Userland/Libraries/LibMedia/PlaybackManager.cpp @@ -153,6 +153,14 @@ { if (on_playback_state_change) on_playback_state_change(); + +} + +void PlaybackManager::dispatch_finished_playing() +{ + if (on_finished_playing) + on_finished_playing(); + } void PlaybackManager::timer_callback() @@ -337,7 +345,7 @@ dbgln("Changing state from {} to {}", temp_handler->name(), m_manager.m_playback_handler->name()); #endif TRY(m_manager.m_playback_handler->on_enter()); - m_manager.dispatch_state_change(); + m_manager.dispatch_finished_playing(); return {}; } diff --git a/Userland/Libraries/LibMedia/PlaybackManager.h b/Userland/Libraries/LibMedia/PlaybackManager.h index 08a049b..b6e3d11 100644 --- a/Userland/Libraries/LibMedia/PlaybackManager.h +++ b/Userland/Libraries/LibMedia/PlaybackManager.h @@ -142,6 +142,7 @@ Function on_playback_state_change; Function on_decoder_error; Function on_fatal_playback_error; + Function on_finished_playing; Track const& selected_video_track() const { return m_selected_video_track; } @@ -171,6 +172,7 @@ // Returns whether we changed playback states. If so, any PlaybackStateHandler processing must cease. [[nodiscard]] bool dispatch_frame_queue_item(FrameQueueItem&&); void dispatch_state_change(); + void dispatch_finished_playing(); void dispatch_fatal_error(Error); Duration m_last_present_in_media_time = Duration::zero();