commit 6bc5f33ade36f71c360721f04c77744d662ace1b
parent 6f366e2d4f99e96991661219882560c14479ac2f
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:
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;