tor-browser

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

commit e4d5b2294c324f34022e2727b932e551997c3fd0
parent b7d5495e2cfe76ed6307941419eee56ac3cf5b47
Author: Alastor Wu <alwu@mozilla.com>
Date:   Fri, 17 Oct 2025 18:28:12 +0000

Bug 1967475 - part2 : check the first sync sample in MP4 if a key frame can't be found from the closest sync sample. r=media-playback-reviewers,jolin

Due to D268192, now only IDR frame will be marked as keyframe on MacOS.

That means other type of I frames will be ignored, in this situation,
seeking from the closet sync sample might not be able to find a key frame,
because the sync sample might be other types of I frames.

Therefore, we should try to search from the first sync sample in a GOP, which
is usually a IDR frame, in order to find a valid key frame.

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

Diffstat:
Mdom/media/mp4/MP4Demuxer.cpp | 28+++++++++++++++++++++++++++-
Mdom/media/mp4/SampleIterator.cpp | 5++++-
Mdom/media/mp4/SampleIterator.h | 9++++++++-
3 files changed, 39 insertions(+), 3 deletions(-)

diff --git a/dom/media/mp4/MP4Demuxer.cpp b/dom/media/mp4/MP4Demuxer.cpp @@ -379,11 +379,34 @@ RefPtr<MP4TrackDemuxer::SeekPromise> MP4TrackDemuxer::Seek( mIterator->Seek(seekTime); +#ifdef MOZ_APPLEMEDIA + bool hasSeenValidSamples = false, seekingFromFirstSyncSample = false; +#endif // Check what time we actually seeked to. do { auto next = GetNextSample(); if (next.isErr()) { - return SeekPromise::CreateAndReject(next.unwrapErr(), __func__); + auto error = next.unwrapErr(); +#ifdef MOZ_APPLEMEDIA + // On macOS, only IDR frames are marked as key frames. This means some + // sync samples (e.g., Recovery SEI or I-slices) are not treated as key + // frames, as they may not be supported by Apple’s VideoToolbox H.264 + // decoder. If we have seen samples but found no key frame, it indicates + // we need to look further back for an earlier sample where an IDR frame + // should exist per spec. In that case, we retry from the first sync + // sample in the GOP, which is typically an IDR frame. This situation + // suggests a poorly muxed file that wastes seek time, but unfortunately + // such cases do occur in real-world content. + if (mType == kH264 && error == NS_ERROR_DOM_MEDIA_END_OF_STREAM && + hasSeenValidSamples && !seekingFromFirstSyncSample) { + LOG("Can not find a key frame from the closet sync sample, try again " + "from the first sync sample"); + seekingFromFirstSyncSample = true; + mIterator->Seek(seekTime, SampleIterator::SyncSampleMode::First); + continue; + } +#endif + return SeekPromise::CreateAndReject(error, __func__); } RefPtr<MediaRawData> sample = next.unwrap(); if (!sample->Size()) { @@ -395,6 +418,9 @@ RefPtr<MP4TrackDemuxer::SeekPromise> MP4TrackDemuxer::Seek( mQueuedSample = sample; seekTime = mQueuedSample->mTime; } +#ifdef MOZ_APPLEMEDIA + hasSeenValidSamples = true; +#endif } while (!mQueuedSample); SetNextKeyFrameTime(); diff --git a/dom/media/mp4/SampleIterator.cpp b/dom/media/mp4/SampleIterator.cpp @@ -431,7 +431,7 @@ Result<Sample*, nsresult> SampleIterator::Get() { void SampleIterator::Next() { ++mCurrentSample; } -void SampleIterator::Seek(const TimeUnit& aTime) { +void SampleIterator::Seek(const TimeUnit& aTime, SyncSampleMode aMode) { size_t syncMoof = 0; size_t syncSample = 0; mCurrentMoof = 0; @@ -443,6 +443,9 @@ void SampleIterator::Seek(const TimeUnit& aTime) { if (sample->mSync) { syncMoof = mCurrentMoof; syncSample = mCurrentSample; + if (aMode == SyncSampleMode::First) { + break; + } } if (sample->mCompositionRange.start == aTime) { break; diff --git a/dom/media/mp4/SampleIterator.h b/dom/media/mp4/SampleIterator.h @@ -28,7 +28,14 @@ class SampleIterator { bool HasNext(); already_AddRefed<mozilla::MediaRawData> GetNextHeader(); Result<already_AddRefed<mozilla::MediaRawData>, MediaResult> GetNext(); - void Seek(const media::TimeUnit& aTime); + + // The default seek mode finds the closest sync sample at or before the target + // time. Setting the mode to `First` allows seeking to the earliest sync + // sample instead, which is only used in a specific case. + enum class SyncSampleMode { Closest, First }; + void Seek(const media::TimeUnit& aTime, + SyncSampleMode aMode = SyncSampleMode::Closest); + media::TimeUnit GetNextKeyframeTime(); private: