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:
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: