Branch: refs/heads/main
Home: https://github.com/WebKit/WebKit
Commit: 5407a3b0b154ea17c86d82f01d05a3b7df7c1508
https://github.com/WebKit/WebKit/commit/5407a3b0b154ea17c86d82f01d05a3b7df7c1508
Author: Jean-Yves Avenard <[email protected]>
Date: 2026-10-13 (Tue, 13 Oct 2026)
Changed paths:
M Source/WebCore/platform/graphics/avfoundation/AudioVideoRendererAVFObjC.h
M Source/WebCore/platform/graphics/avfoundation/AudioVideoRendererAVFObjC.mm
M Source/WebCore/platform/graphics/cocoa/VideoMediaSampleRenderer.h
M Source/WebCore/platform/graphics/cocoa/VideoMediaSampleRenderer.mm
Log Message:
-----------
AudioVideoRendererAVFObjC busy-loops the main thread in
requestMediaDataWhenReady while a seek or flush is pending
https://bugs.webkit.org/show_bug.cgi?id=326950
rdar://188769771
Reviewed by Youenn Fablet.
AVFoundation keeps invoking a block given to
requestMediaDataWhenReadyOnQueue:usingBlock: for as long as the renderer
wants more data, until stopRequestingMediaData is called. Two blocks did not
always call it:
1) In AudioVideoRendererAVFObjC::requestMediaDataWhenReady(), the video and
audio blocks returned early, without stopping, while
m_readyToRequestVideoData / readyToRequestAudioData was false.
prepareToSeek(),
stageVideoRenderer() when a flush was required, and
notifyRequiresFlushToResume() cleared those flags, and only a flush from
the client set them again. The main thread spun until then, indefinitely
if the WebContent process was suspended or, on iOS, while the application
was inactive.
2) VideoMediaSampleRenderer::flush() always called
resetReadyForMoreMediaData(), which installed a main-queue block even when
no request was outstanding. That block had nothing to call and never
stopped.
Calling stopRequestingMediaData at the start of each block removes both
loops, but a request stopped while not ready must then be resumed, and the
renderer could lose or drop requests in other ways:
3) Nothing would resolve an audio request held across a seek:
flushAudio() and flushAudioTrack() only set the flag. MediaPlayerPrivateWebM
does not issue a new request while one is outstanding, so its audio track
would stall.
4) When no flush was required, stageVideoRenderer() set
m_readyToRequestVideoData directly, so a video request held across a seek
would never be resumed.
5) VideoMediaSampleRenderer::changeRenderer() stopped the previous
renderer's request without installing it on the new renderer. With a
decompression session it also left m_waitingForMoreMediaData set, so
maybeBecomeReadyForMoreMediaData() never installed a request on the new
renderer.
6) The main-queue block returned without calling anything when the renderer
was not ready, which drops the request once the block has stopped it.
7) With a decompression session, VideoMediaSampleRenderer::shutdown() did not
stop the request installed on the AVSampleBufferVideoRenderer. Releasing it
without that call is undefined behaviour per the
AVQueuedSampleBufferRendering documentation.
8) m_requestVideoPromise was a single slot not tied to a track. addTrack()
for a video track and destroyVideoTrack() left the request from the
previously enabled track outstanding: it was never settled, or was
rejected by the next request. Once the ready setters resolve an
outstanding request, it would be resolved for a track that is no longer
enabled, and the client would enqueue samples after the video renderer
was gone.
VideoMediaSampleRenderer now owns its request. requestMediaDataWhenReady()
returns a GenericPromise backed by an AutoRejectProducer; a new call
replaces the outstanding one, and resetReadyForMoreMediaData() installs a
block only while a request is outstanding (2). Every block passed to
requestMediaDataWhenReadyOnQueue:usingBlock: holds its renderer through a
WeakObjCPtr and calls stopRequestingMediaData on entry, even when its owner
is gone, then resolves the request (1, 6). changeRenderer() installs the
outstanding request on the new renderer and resets m_waitingForMoreMediaData
(5). shutdown() rejects the request and stops the renderer on the work queue
(7).
AudioVideoRendererAVFObjC no longer calls requestMediaDataWhenReady or
stopRequestingMediaData on the video renderer; it only deals with promises.
It resolves a request straight away when isReadyForMoreSamples() is true,
holds it while a seek or required flush is pending, and otherwise waits on
VideoMediaSampleRenderer's promise or installs the audio block.
setReadyToRequestVideoData() and setReadyToRequestAudioData(), called from
the flushes and from stageVideoRenderer()'s completion, resolve a held
request or ask the renderer again (3, 4). They check the renderer's current
readiness, so samples enqueued while a request was held, such as key-blocked
samples released when a key arrives mid-seek, need no special handling.
addTrack() for a video track and destroyVideoTrack() reject the outstanding
video request and stop VideoMediaSampleRenderer's request (8).
* Source/WebCore/platform/graphics/avfoundation/AudioVideoRendererAVFObjC.h:
* Source/WebCore/platform/graphics/avfoundation/AudioVideoRendererAVFObjC.mm:
(WebCore::AudioVideoRendererAVFObjC::addTrack): Cancel and reject the
outstanding video request.
(WebCore::AudioVideoRendererAVFObjC::requestMediaDataWhenReady): Resolve, hold,
or ask the renderer.
(WebCore::AudioVideoRendererAVFObjC::requestVideoDataWhenReady): Added. Wait on
VideoMediaSampleRenderer's promise.
(WebCore::AudioVideoRendererAVFObjC::cancelVideoDataRequest): Added. Disconnect
the pending callback and stop the renderer's request.
(WebCore::AudioVideoRendererAVFObjC::clearReadyToRequestVideoData): Added.
Clear the flag and cancel the pending callback.
(WebCore::AudioVideoRendererAVFObjC::requestAudioDataWhenReady): Added. Stop
the renderer on entry.
(WebCore::AudioVideoRendererAVFObjC::setReadyToRequestVideoData): Added.
Resolve a held request or ask the renderer again.
(WebCore::AudioVideoRendererAVFObjC::setReadyToRequestAudioData): Added. Ditto.
(WebCore::AudioVideoRendererAVFObjC::prepareToSeek): Use
clearReadyToRequestVideoData().
(WebCore::AudioVideoRendererAVFObjC::stageVideoRenderer): Use
setReadyToRequestVideoData() when no flush is required.
(WebCore::AudioVideoRendererAVFObjC::destroyVideoTrack): Cancel and reject the
outstanding video request.
(WebCore::AudioVideoRendererAVFObjC::flushVideo): Use
setReadyToRequestVideoData().
(WebCore::AudioVideoRendererAVFObjC::flushAudio): Use
setReadyToRequestAudioData().
(WebCore::AudioVideoRendererAVFObjC::flushAudioTrack): Ditto.
(WebCore::AudioVideoRendererAVFObjC::notifyRequiresFlushToResume): Use
clearReadyToRequestVideoData().
* Source/WebCore/platform/graphics/cocoa/VideoMediaSampleRenderer.h:
* Source/WebCore/platform/graphics/cocoa/VideoMediaSampleRenderer.mm:
(WebCore::VideoMediaSampleRenderer::changeRenderer): Install the outstanding
request on the new renderer; reset m_waitingForMoreMediaData.
(WebCore::VideoMediaSampleRenderer::maybeBecomeReadyForMoreMediaData): Stop the
renderer on entry; resolve the request.
(WebCore::VideoMediaSampleRenderer::resolveRequestMediaDataIfNeeded): Added.
(WebCore::VideoMediaSampleRenderer::stopRequestingMediaData): Reject the
outstanding request.
(WebCore::VideoMediaSampleRenderer::shutdown): Reject the outstanding request
and stop the renderer on the work queue.
(WebCore::VideoMediaSampleRenderer::requestMediaDataWhenReady): Return a
GenericPromise.
(WebCore::VideoMediaSampleRenderer::resetReadyForMoreMediaData): Install only
while a request is outstanding; stop the renderer on entry.
Canonical link: https://commits.webkit.org/323669@main
To unsubscribe from these emails, change your notification settings at
https://github.com/WebKit/WebKit/settings/notifications