tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit bd5396922f1bf4fba56cc63a393f1f4bbb28538e
parent 75c1a65bd7c8fddd7aa0f0102d64afd1440f31ee
Author: Andreas Pehrson <apehrson@mozilla.com>
Date:   Tue, 11 Nov 2025 08:21:03 +0000

Bug 1931328 - Interpolate DecodedStream position based on current system time. r=padenot,webidl,smaug

Differential Revision: https://phabricator.services.mozilla.com/D238750

Diffstat:
Mdom/media/mediasink/AudioDecoderInputTrack.cpp | 2+-
Mdom/media/mediasink/AudioDecoderInputTrack.h | 8++++----
Mdom/media/mediasink/DecodedStream.cpp | 59+++++++++++++++++++++++++++++++++++++++++++----------------
Mdom/media/mediasink/DecodedStream.h | 7+++++--
Mdom/webidl/MediaDebugInfo.webidl | 1+
5 files changed, 54 insertions(+), 23 deletions(-)

diff --git a/dom/media/mediasink/AudioDecoderInputTrack.cpp b/dom/media/mediasink/AudioDecoderInputTrack.cpp @@ -597,7 +597,7 @@ void AudioDecoderInputTrack::NotifyInTheEndOfProcessInput( LOG("Notify, fill=%" PRId64 ", total written=%" PRId64 ", ended=%d", aFillDuration, mWrittenFrames, Ended()); if (aFillDuration > 0) { - mOnOutput.Notify(mWrittenFrames); + mOnOutput.Notify(mWrittenFrames, AwakeTimeStamp::Now()); } if (Ended()) { mOnEnd.Notify(); diff --git a/dom/media/mediasink/AudioDecoderInputTrack.h b/dom/media/mediasink/AudioDecoderInputTrack.h @@ -12,7 +12,6 @@ #include "MediaTrackGraph.h" #include "TimeUnits.h" #include "mozilla/SPSCQueue.h" -#include "mozilla/StateMirroring.h" #include "mozilla/TimeStamp.h" #include "nsISerialEventTarget.h" @@ -101,7 +100,7 @@ class AudioDecoderInputTrack final : public ProcessedMediaTrack { void Close(); bool HasBatchedData() const; - MediaEventSource<int64_t>& OnOutput() { return mOnOutput; } + MediaEventSource<int64_t, AwakeTimeStamp>& OnOutput() { return mOnOutput; } MediaEventSource<void>& OnEnd() { return mOnEnd; } // Graph Thread API @@ -176,8 +175,9 @@ class AudioDecoderInputTrack final : public ProcessedMediaTrack { const RefPtr<nsISerialEventTarget> mDecoderThread; - // Notify the amount of audio frames which have been sent to the track. - MediaEventProducer<int64_t> mOnOutput; + // Notify the amount of audio frames which have been sent to the track, + // sampled by the system time they were sent. + MediaEventProducer<int64_t, AwakeTimeStamp> mOnOutput; // Notify when the track is ended. MediaEventProducer<void> mOnEnd; diff --git a/dom/media/mediasink/DecodedStream.cpp b/dom/media/mediasink/DecodedStream.cpp @@ -104,9 +104,9 @@ class DecodedStreamGraphListener { void RegisterListeners() { if (mAudioTrack) { mOnAudioOutput = mAudioTrack->OnOutput().Connect( - mDecoderThread, - [self = RefPtr<DecodedStreamGraphListener>(this)](TrackTime aTime) { - self->NotifyOutput(MediaSegment::AUDIO, aTime); + mDecoderThread, [self = RefPtr<DecodedStreamGraphListener>(this)]( + TrackTime aTime, AwakeTimeStamp aSystemTime) { + self->NotifyOutput(MediaSegment::AUDIO, aTime, aSystemTime); }); mOnAudioEnd = mAudioTrack->OnEnd().Connect( mDecoderThread, [self = RefPtr<DecodedStreamGraphListener>(this)]() { @@ -147,7 +147,8 @@ class DecodedStreamGraphListener { mOnAudioEnd.DisconnectIfExists(); } - void NotifyOutput(MediaSegment::Type aType, TrackTime aCurrentTrackTime) { + void NotifyOutput(MediaSegment::Type aType, TrackTime aCurrentTrackTime, + AwakeTimeStamp aCurrentSystemTime) { AssertOnDecoderThread(); if (aType == MediaSegment::AUDIO) { mAudioOutputFrames = aCurrentTrackTime; @@ -181,7 +182,8 @@ class DecodedStreamGraphListener { const MediaTrack* track = aType == MediaSegment::VIDEO ? static_cast<MediaTrack*>(mVideoTrack) : static_cast<MediaTrack*>(mAudioTrack); - mOnOutput.Notify(track->TrackTimeToMicroseconds(aCurrentTrackTime)); + mOnOutput.Notify(track->TrackTimeToMicroseconds(aCurrentTrackTime), + aCurrentSystemTime); } void NotifyEnded(MediaSegment::Type aType) { @@ -237,7 +239,7 @@ class DecodedStreamGraphListener { return mAudioOutputFrames; } - MediaEventSource<int64_t>& OnOutput() { return mOnOutput; } + MediaEventSource<int64_t, AwakeTimeStamp>& OnOutput() { return mOnOutput; } private: ~DecodedStreamGraphListener() { @@ -252,7 +254,7 @@ class DecodedStreamGraphListener { const RefPtr<nsISerialEventTarget> mDecoderThread; // Accessible on any thread, but only notify on the decoder thread. - MediaEventProducer<int64_t> mOnOutput; + MediaEventProducer<int64_t, AwakeTimeStamp> mOnOutput; RefPtr<SourceVideoTrackListener> mVideoTrackListener; @@ -299,9 +301,10 @@ void SourceVideoTrackListener::NotifyOutput(MediaTrackGraph* aGraph, mLastVideoOutputTime = aCurrentTrackTime; mDecoderThread->Dispatch(NS_NewRunnableFunction( "SourceVideoTrackListener::NotifyOutput", - [self = RefPtr<SourceVideoTrackListener>(this), aCurrentTrackTime]() { - self->mGraphListener->NotifyOutput(MediaSegment::VIDEO, - aCurrentTrackTime); + [self = RefPtr<SourceVideoTrackListener>(this), aCurrentTrackTime, + currentSystemTime = AwakeTimeStamp::Now()]() { + self->mGraphListener->NotifyOutput( + MediaSegment::VIDEO, aCurrentTrackTime, currentSystemTime); })); } @@ -333,7 +336,7 @@ class DecodedStreamData final { float aPlaybackRate, float aVolume, bool aPreservesPitch, nsISerialEventTarget* aDecoderThread); ~DecodedStreamData(); - MediaEventSource<int64_t>& OnOutput(); + MediaEventSource<int64_t, AwakeTimeStamp>& OnOutput(); // This is used to mark track as closed and should be called before Forget(). // Decoder thread only. void Close(); @@ -443,7 +446,7 @@ DecodedStreamData::~DecodedStreamData() { } } -MediaEventSource<int64_t>& DecodedStreamData::OnOutput() { +MediaEventSource<int64_t, AwakeTimeStamp>& DecodedStreamData::OnOutput() { return mListener->OnOutput(); } @@ -508,6 +511,7 @@ nsresult DecodedStream::Start(const TimeUnit& aStartTime, const MediaInfo& aInfo) { AssertOwnerThread(); MOZ_ASSERT(mStartTime.isNothing(), "playback already started."); + MOZ_ASSERT(mLastReportedPosition.isNothing()); AUTO_PROFILER_LABEL(FUNCTION_SIGNATURE, MEDIA_PLAYBACK); if (profiler_thread_is_being_profiled_for_markers()) { @@ -520,6 +524,7 @@ nsresult DecodedStream::Start(const TimeUnit& aStartTime, mStartTime.emplace(aStartTime); mLastOutputTime = TimeUnit::Zero(); + mLastOutputSystemTime = Nothing(); mInfo = aInfo; mPlaying = true; mPrincipalHandle.Connect(mCanonicalOutputPrincipal); @@ -635,6 +640,7 @@ void DecodedStream::Stop() { ResetVideo(mPrincipalHandle); ResetAudio(); mStartTime.reset(); + mLastReportedPosition = Nothing(); mAudioEndedPromise = nullptr; mVideoEndedPromise = nullptr; @@ -1103,13 +1109,28 @@ TimeUnit DecodedStream::GetPosition(TimeStamp* aTimeStamp) { // This is only called after MDSM starts playback. So mStartTime is // guaranteed to be something. MOZ_ASSERT(mStartTime.isSome()); + TimeStamp now = TimeStamp::Now(); + AwakeTimeStamp awakeNow = AwakeTimeStamp::Now(); if (aTimeStamp) { - *aTimeStamp = TimeStamp::Now(); - } - return mStartTime.ref() + mLastOutputTime; + *aTimeStamp = now; + } + AwakeTimeDuration timeSinceLastOutput; + if (mLastOutputSystemTime) { + MOZ_ASSERT(awakeNow >= *mLastOutputSystemTime); + timeSinceLastOutput = awakeNow - *mLastOutputSystemTime; + } + TimeUnit position = mStartTime.ref() + mLastOutputTime + + TimeUnit::FromSeconds(timeSinceLastOutput.ToSeconds()); + if (mLastReportedPosition && position < *mLastReportedPosition) { + // There's a theoretical risk of time going backwards because of the + // interpolation based on mLastOutputSystemTime. Prevent that here. + position = *mLastReportedPosition; + } + mLastReportedPosition = Some(position); + return position; } -void DecodedStream::NotifyOutput(int64_t aTime) { +void DecodedStream::NotifyOutput(int64_t aTime, AwakeTimeStamp aSystemTime) { AssertOwnerThread(); TimeUnit time = TimeUnit::FromMicroseconds(aTime); if (time == mLastOutputTime) { @@ -1117,6 +1138,8 @@ void DecodedStream::NotifyOutput(int64_t aTime) { } MOZ_ASSERT(mLastOutputTime < time); mLastOutputTime = time; + MOZ_ASSERT_IF(mLastOutputSystemTime, *mLastOutputSystemTime < aSystemTime); + mLastOutputSystemTime = Some(aSystemTime); auto currentTime = GetPosition(); if (profiler_thread_is_being_profiled_for_markers()) { @@ -1179,6 +1202,10 @@ void DecodedStream::GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) { NS_ConvertUTF8toUTF16(nsPrintfCString("%p", this)); aInfo.mDecodedStream.mStartTime = startTime; aInfo.mDecodedStream.mLastOutputTime = mLastOutputTime.ToMicroseconds(); + aInfo.mDecodedStream.mLastReportedPosition = + mLastReportedPosition + .map([](const auto& aT) { return aT.ToMicroseconds(); }) + .valueOr(0); aInfo.mDecodedStream.mPlaying = mPlaying.Ref(); auto lastAudio = mAudioQueue.PeekBack(); aInfo.mDecodedStream.mLastAudio = diff --git a/dom/media/mediasink/DecodedStream.h b/dom/media/mediasink/DecodedStream.h @@ -13,10 +13,12 @@ #include "MediaSegment.h" #include "MediaSink.h" #include "mozilla/AbstractThread.h" +#include "mozilla/AwakeTimeStamp.h" #include "mozilla/Maybe.h" #include "mozilla/MozPromise.h" #include "mozilla/RefPtr.h" #include "mozilla/StateMirroring.h" +#include "mozilla/TimeStamp.h" #include "mozilla/UniquePtr.h" namespace mozilla { @@ -28,7 +30,6 @@ class VideoData; struct PlaybackInfoInit; class ProcessedMediaTrack; struct SharedDummyTrack; -class TimeStamp; template <class T> class MediaQueue; @@ -85,7 +86,7 @@ class DecodedStream : public MediaSink { void ResetAudio(); void ResetVideo(const PrincipalHandle& aPrincipalHandle); void SendData(); - void NotifyOutput(int64_t aTime); + void NotifyOutput(int64_t aTime, AwakeTimeStamp aSystemTime); void CheckIsDataAudible(const AudioData* aData); void AssertOwnerThread() const { @@ -128,6 +129,8 @@ class DecodedStream : public MediaSink { media::NullableTimeUnit mStartTime; media::TimeUnit mLastOutputTime; + Maybe<AwakeTimeStamp> mLastOutputSystemTime; + Maybe<media::TimeUnit> mLastReportedPosition; MediaInfo mInfo; // True when stream is producing audible sound, false when stream is silent. bool mIsAudioDataAudible = false; diff --git a/dom/webidl/MediaDebugInfo.webidl b/dom/webidl/MediaDebugInfo.webidl @@ -117,6 +117,7 @@ dictionary DecodedStreamDebugInfo { DOMString instance = ""; long long startTime = 0; long long lastOutputTime = 0; + long long lastReportedPosition = 0; long playing = 0; long long lastAudio = 0; boolean audioQueueFinished = false;