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

Reply via email to