diff --git a/Libraries/LibMedia/GenericTimeProvider.cpp b/Libraries/LibMedia/GenericTimeProvider.cpp index f198748ef1..7e1721cfb0 100644 --- a/Libraries/LibMedia/GenericTimeProvider.cpp +++ b/Libraries/LibMedia/GenericTimeProvider.cpp @@ -15,8 +15,12 @@ GenericTimeProvider::~GenericTimeProvider() = default; AK::Duration GenericTimeProvider::current_time() const { auto time = m_media_time; - if (m_monotonic_time_on_resume.has_value()) - time += MonotonicTime::now() - m_monotonic_time_on_resume.value(); + if (m_monotonic_time_on_resume.has_value()) { + auto elapsed = MonotonicTime::now() - m_monotonic_time_on_resume.value(); + if (m_playback_rate != 1.0f) + elapsed = AK::Duration::from_seconds_f64(elapsed.to_seconds_f64() * m_playback_rate); + time += elapsed; + } return time; } @@ -41,4 +45,16 @@ void GenericTimeProvider::seek(AK::Duration time) m_media_time = time; } +void GenericTimeProvider::set_playback_rate(float rate) +{ + VERIFY(rate >= 0); + if (m_playback_rate == rate) + return; + if (m_monotonic_time_on_resume.has_value()) { + m_media_time = current_time(); + m_monotonic_time_on_resume.emplace(MonotonicTime::now()); + } + m_playback_rate = rate; +} + } diff --git a/Libraries/LibMedia/GenericTimeProvider.h b/Libraries/LibMedia/GenericTimeProvider.h index c15682a902..57e30dd15f 100644 --- a/Libraries/LibMedia/GenericTimeProvider.h +++ b/Libraries/LibMedia/GenericTimeProvider.h @@ -19,10 +19,12 @@ public: virtual void resume() override; virtual void pause() override; virtual void seek(AK::Duration) override; + virtual void set_playback_rate(float) override; private: Optional m_monotonic_time_on_resume; AK::Duration m_media_time; + float m_playback_rate { 1.0f }; }; } diff --git a/Libraries/LibMedia/MediaTimeProvider.h b/Libraries/LibMedia/MediaTimeProvider.h index deab71c181..4cbfe3b867 100644 --- a/Libraries/LibMedia/MediaTimeProvider.h +++ b/Libraries/LibMedia/MediaTimeProvider.h @@ -19,6 +19,7 @@ public: virtual void resume() = 0; virtual void pause() = 0; virtual void seek(AK::Duration) = 0; + virtual void set_playback_rate(float) = 0; }; } diff --git a/Libraries/LibMedia/PlaybackManager.cpp b/Libraries/LibMedia/PlaybackManager.cpp index 2399c8507b..01a4cdc4fd 100644 --- a/Libraries/LibMedia/PlaybackManager.cpp +++ b/Libraries/LibMedia/PlaybackManager.cpp @@ -301,6 +301,7 @@ void PlaybackManager::set_time_provider(NonnullRefPtr const& continue; track_data.display->set_time_provider(provider); } + provider->set_playback_rate(m_playback_rate); if (is_playing()) provider->resume(); } @@ -432,4 +433,10 @@ void PlaybackManager::set_volume(double volume) m_audio_sink->set_volume(volume); } +void PlaybackManager::set_playback_rate(float rate) +{ + m_playback_rate = rate; + m_time_provider->set_playback_rate(rate); +} + } diff --git a/Libraries/LibMedia/PlaybackManager.h b/Libraries/LibMedia/PlaybackManager.h index 929d3ff770..9905d82942 100644 --- a/Libraries/LibMedia/PlaybackManager.h +++ b/Libraries/LibMedia/PlaybackManager.h @@ -89,6 +89,7 @@ public: TimeRanges buffered_time_ranges() const; void set_volume(double); + void set_playback_rate(float); Function on_metadata_parsed; Function on_unsupported_format_error; @@ -162,6 +163,7 @@ private: NonnullRefPtr m_weak_link; NonnullRefPtr m_time_provider; + float m_playback_rate { 1.0f }; bool m_audio_output_disabled { false }; diff --git a/Libraries/LibMedia/Processors/AudioMixer.cpp b/Libraries/LibMedia/Processors/AudioMixer.cpp index a7f7ecf7b1..6ab4872775 100644 --- a/Libraries/LibMedia/Processors/AudioMixer.cpp +++ b/Libraries/LibMedia/Processors/AudioMixer.cpp @@ -49,6 +49,7 @@ ErrorOr AudioMixer::connect_input(NonnullRefPtr const& inpu disconnect_input_while_locked(input); return result.release_error(); } + input->set_playback_rate(m_playback_rate); input->seek(mix_head_timestamp()); if (m_started) input->start(); @@ -118,6 +119,16 @@ Audio::SampleSpecification AudioMixer::sample_specification() const return m_sample_specification; } +void AudioMixer::set_playback_rate(float rate) +{ + Sync::MutexLocker locker { m_mutex }; + if (m_playback_rate == rate) + return; + for (auto& [input, input_data] : m_inputs) + input->set_playback_rate(rate); + m_playback_rate = rate; +} + AK::Duration AudioMixer::mix_head_timestamp() const { return AK::Duration::from_time_units(m_next_frame_to_write, 1, m_sample_specification.sample_rate()); diff --git a/Libraries/LibMedia/Processors/AudioMixer.h b/Libraries/LibMedia/Processors/AudioMixer.h index 3770613cc9..2505279c0f 100644 --- a/Libraries/LibMedia/Processors/AudioMixer.h +++ b/Libraries/LibMedia/Processors/AudioMixer.h @@ -33,6 +33,8 @@ public: virtual void seek(AK::Duration timestamp) override; + virtual void set_playback_rate(float) override; + virtual ErrorOr set_output_sample_specification(Audio::SampleSpecification) override; Audio::SampleSpecification sample_specification() const; @@ -60,6 +62,7 @@ private: Audio::SampleSpecification m_sample_specification; mutable HashMap, InputMixingData> m_inputs; i64 m_next_frame_to_write { 0 }; + float m_playback_rate { 1.0f }; bool m_started { false }; bool m_moved_position_pending { false }; mutable bool m_downstream_needs_wake { true }; diff --git a/Libraries/LibMedia/Producers/AudioProducer.h b/Libraries/LibMedia/Producers/AudioProducer.h index 3cf3baddf0..71ab609de8 100644 --- a/Libraries/LibMedia/Producers/AudioProducer.h +++ b/Libraries/LibMedia/Producers/AudioProducer.h @@ -26,6 +26,11 @@ public: virtual void set_wake_handler(PipelineWakeHandler) = 0; virtual void seek(AK::Duration timestamp) = 0; + + virtual void set_playback_rate(float rate) + { + VERIFY(rate == 1.0f); + } }; } diff --git a/Libraries/LibMedia/Sinks/AudioPlaybackSink.cpp b/Libraries/LibMedia/Sinks/AudioPlaybackSink.cpp index 962aaf612d..435012a5c7 100644 --- a/Libraries/LibMedia/Sinks/AudioPlaybackSink.cpp +++ b/Libraries/LibMedia/Sinks/AudioPlaybackSink.cpp @@ -49,6 +49,7 @@ public: size_t m_block_count { 0 }; i64 m_next_frame_to_play { 0 }; AudioBlockTimingRing m_block_timings; + float m_playback_rate { 1.0f }; PipelineStateChangeHandler m_on_state_changed; PipelineStatus m_last_pull_status { PipelineStatus::Pending }; @@ -196,6 +197,7 @@ ErrorOr AudioPlaybackSink::connect_input(NonnullRefPtr cons disconnect_input_while_locked(input); return result.release_error(); } + input->set_playback_rate(m_output_thread_data->m_playback_rate); input->seek(current_time()); input->start(); } @@ -467,4 +469,17 @@ void AudioPlaybackSink::set_volume(double volume) } } +void AudioPlaybackSink::set_playback_rate(float rate) +{ + VERIFY(rate >= 0); + RefPtr input; + { + Sync::MutexLocker locker { m_output_thread_data->m_output_mutex }; + input = m_output_thread_data->m_input; + m_output_thread_data->m_playback_rate = rate; + } + if (input != nullptr) + input->set_playback_rate(rate); +} + } diff --git a/Libraries/LibMedia/Sinks/AudioPlaybackSink.h b/Libraries/LibMedia/Sinks/AudioPlaybackSink.h index 4438e9dd13..618fecdf12 100644 --- a/Libraries/LibMedia/Sinks/AudioPlaybackSink.h +++ b/Libraries/LibMedia/Sinks/AudioPlaybackSink.h @@ -40,6 +40,8 @@ public: virtual void pause() override; virtual void seek(AK::Duration) override; + virtual void set_playback_rate(float) override; + void set_volume(double); Function on_audio_output_error; diff --git a/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Libraries/LibWeb/HTML/HTMLMediaElement.cpp index 7cfb894207..e78ad323cf 100644 --- a/Libraries/LibWeb/HTML/HTMLMediaElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLMediaElement.cpp @@ -1781,6 +1781,8 @@ void HTMLMediaElement::set_up_playback_manager_for_remote() m_playback_manager = Media::PlaybackManager::create(); m_playback_manager->set_audio_output_disabled(document().page().client().is_headless()); + m_playback_manager->set_playback_rate(static_cast(m_playback_rate)); + m_has_enabled_preferred_audio_track = false; m_has_selected_preferred_video_track = false; @@ -1856,6 +1858,8 @@ void HTMLMediaElement::set_up_playback_manager_for_local() m_playback_manager = Media::PlaybackManager::create(); m_playback_manager->set_audio_output_disabled(document().page().client().is_headless()); + m_playback_manager->set_playback_rate(static_cast(m_playback_rate)); + m_has_enabled_preferred_audio_track = false; m_has_selected_preferred_video_track = false; @@ -2602,9 +2606,11 @@ WebIDL::ExceptionOr HTMLMediaElement::set_playback_rate(double new_value) // 2. Set playbackRate to the new value, and if the element is potentially playing, change the playback speed. m_playback_rate = new_value; - if (potentially_playing()) { - // FIXME: Do this once playback speeds other than 1 are supported. - } + // AD-HOC: Set the playback rate even when not potentially playing. The spec mandates that the media time advances + // by playbackRate units of media time per unit time on the clock. There's no reason this shouldn't be set + // always. + if (m_playback_manager) + m_playback_manager->set_playback_rate(static_cast(new_value)); return {}; }