LibMedia: Sync AudioMixingSink::set_time onto the stream thread

With the previous setup setting the time directly on the main thread,
the following could occur:

- HTMLMediaElement temporarily pauses playback to begin a seek.
- AudioMixingSink starts an audio task to drain audio and suspend.
- HTMLMediaElement starts PlaybackManager seeking to a new position.
- AudioDataProvider completes the seek to the new position.
- The PlaybackManager tells AudioMixingSink to set the media time,
  which it does so synchronously on the main thread.
- At this point, the time provider corresponds to the new position.
- The pause completes, and a deferred invocation sets media time to
  its old position again.

This would result in the timeline showing the wrong position after a
seek on rare occasions.

Instead, always queue up a drain and suspend when setting the sink's
media time. This ensures that updating the time always occurs after the
pause has completed.

Also, since setting the time is asynchronous now, we need to store the
target time until the seeking drain completes. Otherwise, we still
briefly see the previous playback position after a seek.
This commit is contained in:
Zaggy1024 2025-10-07 18:06:46 -05:00 committed by Jelle Raaijmakers
parent 49f088275e
commit 7745d9209d
2 changed files with 12 additions and 15 deletions

View File

@ -218,6 +218,8 @@ void AudioMixingSink::create_playback_stream(u32 sample_rate, u32 channel_count)
AK::Duration AudioMixingSink::current_time() const
{
if (m_temporary_time.has_value())
return m_temporary_time.value();
if (!m_playback_stream)
return m_last_media_time;
@ -279,18 +281,7 @@ void AudioMixingSink::pause()
void AudioMixingSink::set_time(AK::Duration time)
{
if (!m_playing || !m_playback_stream) {
Threading::MutexLocker mixing_locker { m_mutex };
m_last_media_time = time;
m_next_sample_to_write = duration_to_sample(time, m_playback_stream_sample_rate);
for (auto& [track, track_data] : m_track_mixing_datas) {
track_data.current_block.clear();
track_data.current_block_first_sample_offset = 0;
}
return;
}
m_temporary_time = time;
m_playback_stream->drain_buffer_and_suspend()
->when_resolved([weak_self = m_weak_self, &playback_stream = *m_playback_stream, time]() {
auto self = weak_self->take_strong();
@ -303,10 +294,14 @@ void AudioMixingSink::set_time(AK::Duration time)
self->m_main_thread_event_loop.deferred_invoke([self, new_stream_time, time]() {
{
Threading::MutexLocker mixing_locker { self->m_mutex };
self->m_last_stream_time = new_stream_time;
self->m_last_media_time = time;
self->m_next_sample_to_write = duration_to_sample(time, self->m_playback_stream_sample_rate);
self->m_temporary_time = {};
{
Threading::MutexLocker mixing_locker { self->m_mutex };
self->m_next_sample_to_write = duration_to_sample(time, self->m_playback_stream_sample_rate);
}
for (auto& [track, track_data] : self->m_track_mixing_datas) {
track_data.current_block.clear();
@ -314,7 +309,8 @@ void AudioMixingSink::set_time(AK::Duration time)
}
}
self->resume();
if (self->m_playing)
self->resume();
});
})
.when_rejected([](auto&& error) {

View File

@ -95,6 +95,7 @@ private:
AK::Duration m_last_stream_time;
AK::Duration m_last_media_time;
Optional<AK::Duration> m_temporary_time;
};
}