Branch: refs/heads/main
Home: https://github.com/WebKit/WebKit
Commit: 09ea5e2ced877aeeea2d6e369e1aa953f33bcc8f
https://github.com/WebKit/WebKit/commit/09ea5e2ced877aeeea2d6e369e1aa953f33bcc8f
Author: Jean-Yves Avenard <[email protected]>
Date: 2026-04-24 (Fri, 24 Apr 2026)
Changed paths:
M Source/WebCore/platform/graphics/SourceBufferPrivate.cpp
M Source/WebCore/platform/graphics/SourceBufferPrivate.h
M Source/WebCore/platform/graphics/avfoundation/AudioVideoRendererAVFObjC.h
M Source/WebCore/platform/graphics/avfoundation/AudioVideoRendererAVFObjC.mm
M
Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h
M
Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm
Log Message:
-----------
media/media-source/media-managedmse-resume-after-stall.html is an
intermittent failure
https://bugs.webkit.org/show_bug.cgi?id=313098
rdar://175395328
Reviewed by Jer Noble.
The test intermittently failed when the AVSampleBufferRenderSynchronizer
and its associated AVSampleBufferAudioRenderer entered a state in which
the timebase will no longer progress despite being later flushed correctly
and samples being enqueued appropriately.
While the reason for this broken state to occur is yet unexplained,
several pre-condition symptoms were identified that trigger the ultimate
failure.
1- After setting the rate to the AVSampleBufferRenderSynchronizer, reading
the rate attribute may still return the old, stale value. This could cause
the AudioVideoRenderer to incorrectly assume time was progressing when it
wasn't.
2- The AudioVideoRendererAVFObjC was receiving commands in an unexpected order:
instead of doing play() -> stall() -> flush() -> enqueueSamples() -> play()
we would see instead: play() -> stall() -> play() -> flush() -> enqueueSamples()
To address 1, we stop reading the AVSampleBufferRenderSynchronizer's rate
attribute
and instead cache the rate last set. It achieves two things: it avoids the
internal sync dispatch the AVSampleBufferRenderSynchronizer makes to its
rate queue, and ensure the value read is always correct.
Condition 2 was due to several races between the
MediaPlayerPrivateMediaSourceAVFObjC
and SourceBufferPrivateAVFObjC.
The MediaPlayerPrivateMediaSourceAVFObjC sends commands to the
AudioVideoRenderer
on the main thread, while the SourceBuffer interacts with the renderer's track
on its dedicated queue. The could lead commands reaching the renderer out of
assumed order.
To pre-emptively avoice such race in the MediaPlayer, we now ensure that
any commands in the media player related to the renderer's data flow (seek,
flush, setRate, play, pause, stall)
only occurs on the MediaSourcePrivate/SourceBufferPrivate's queue.
The primary race and cause for the test to fail was
1- monitorSourceBuffers() ran first → synchronously fires the readyState
change → updateStateFromReadyState → play() at time T1
2- reenqueueMediaIfNeeded() dispatches the reenqueue lambda (which will call
flush) at time T2
The call to play() which should occur after the flush() could run before.
When a buffered GOP containing the currentTime was being overwritten,
we would mark the track with setNeedsReenqueueing(true) and then notify
the MediaSource that the buffered changed, which triggered a called to
monitorSourceBuffers and in turn call play() as data was now available.
At the same time, we had
```
// SourceBufferPrivate::reenqueueMediaIfNeeded body
if (trackBuffer.needsReenqueueing()) {
DEBUG_LOG_WITH_THIS(...);
buffer.reenqueueMediaForTime(trackBuffer, trackID, currentTime);
}
```
Resulting IPC order in the GPU process to become:
1. flushTrack (issued from didReceiveSample on m_dispatcher, before
appendInternal even returns)
2. …appendBuffer finishes; main thread reacts → updateStateFromReadyState
dispatches play() to the queue…
3. play() (issued from the queue after m_dispatcher finishes the rest of its
work)
we now enforce the serialization of the tasks by flushing the tracks early
before notifying the
MediaSource that the buffered had changed.
Covered by existing tests.
* Source/WebCore/platform/graphics/SourceBufferPrivate.cpp:
(WebCore::SourceBufferPrivate::reenqueueMediaIfNeeded):
(WebCore::SourceBufferPrivate::removeCodedFramesInternal):
(WebCore::SourceBufferPrivate::append):
(WebCore::SourceBufferPrivate::flushTracksThatNeedReenqueueing):
* Source/WebCore/platform/graphics/SourceBufferPrivate.h:
* Source/WebCore/platform/graphics/avfoundation/AudioVideoRendererAVFObjC.h:
(WebCore::AudioVideoRendererAVFObjC::synchronizerRate const):
* Source/WebCore/platform/graphics/avfoundation/AudioVideoRendererAVFObjC.mm:
(WebCore::AudioVideoRendererAVFObjC::timeIsProgressing const):
(WebCore::AudioVideoRendererAVFObjC::setRate):
(WebCore::AudioVideoRendererAVFObjC::notifyTimeReachedAndStall):
(WebCore::AudioVideoRendererAVFObjC::prepareToSeek):
(WebCore::AudioVideoRendererAVFObjC::updateAllRenderersHaveAvailableSamples):
(WebCore::AudioVideoRendererAVFObjC::setSynchronizerRate):
*
Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h:
*
Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm:
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::~MediaPlayerPrivateMediaSourceAVFObjC):
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::playInternal):
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::pauseInternal):
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::stall):
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::seekInternal):
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::continueSeek):
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::reenqueueMediaForTimeAndFinishSeek):
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::setRateDouble):
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::bufferedChanged):
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::dispatchToRendererQueue):
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::updateStateFromReadyState):
Canonical link: https://commits.webkit.org/311992@main
To unsubscribe from these emails, change your notification settings at
https://github.com/WebKit/WebKit/settings/notifications