Branch: refs/heads/main
Home: https://github.com/WebKit/WebKit
Commit: 612b528ca58713f979bb25ef03fac63f236db932
https://github.com/WebKit/WebKit/commit/612b528ca58713f979bb25ef03fac63f236db932
Author: Jean-Yves Avenard <[email protected]>
Date: 2026-05-13 (Wed, 13 May 2026)
Changed paths:
M Source/WebCore/html/HTMLMediaElement.cpp
M Source/WebCore/html/HTMLMediaElement.h
M
Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm
Log Message:
-----------
[MSE] timeupdate event can fire during seek operation, before the seek
completes.
https://bugs.webkit.org/show_bug.cgi?id=314624
rdar://problem/176861767
Reviewed by Youenn Fablet.
In the WPT mediasource-duration.html test the three "truncated
duration seek" subtests ("seek starts on duration truncation below
currentTime", "appendBuffer completes previous seek to truncated
duration", "endOfStream completes previous seek to truncated
duration") failed intermittently under --repeat=N stress with
assert_equals: Event types match. expected "seeking" but got "timeupdate"
The common pattern in all three was: after a seek completes, the
subtest calls sourceBuffer.remove(truncatedDuration, Infinity),
waits for the resulting 'updateend', then sets
mediaSource.duration = truncatedDuration and expects the next
mediaElement event to be 'seeking'. A 'timeupdate' was instead
landing between the remove's updateend and the seek's seeking,
failing the assertion.
Two distinct sources of the stray timeupdate were identified.
1. MediaPlayerPrivateMediaSourceAVFObjC::stall() unconditionally
called timeChanged() when shouldBePlaying() was still true.
The remove()'s bufferedChanged runs on the main thread with
hasFutureTime(currentTime) = false (the range now ends before
currentTime), which calls stall() synchronously: the renderer
stall dispatch is asynchronous so shouldBePlaying() still
returns true, and timeChanged() queues a scheduleTimeupdateEvent
task at the current (stalled) movieTime. That task was then
dispatched ahead of the subtest handler's setDuration -> seek.
2. HTMLMediaElement::playbackProgressTimerFired fires
scheduleTimeupdateEvent(periodic=true) every 250 ms. If the
timer tick lands in the brief window between the remove's
'updateend' dispatching to JS and the subtest handler calling
setCurrentTime / setDuration, it queues a timeupdate task that
then dispatches ahead of the seeking event. This is visible in
a non-MSE form too — during any in-flight seek, the seek
algorithm in the spec owns the event ordering
("in the other cases, such as explicit seeks, relevant events
get fired as part of the overall process of changing the
current playback position").
Fix, in two coordinated parts.
=== Stall path ===
* MediaPlayerPrivateMediaSourceAVFObjC::stall(): drop the
if (shouldBePlaying()) timeChanged() call.
=== Periodic timeupdate cancellation ===
Add m_periodicTimeupdateCancellationGroup to HTMLMediaElement.
Route tasks queued by scheduleTimeupdateEvent(periodic=true)
through it, and cancel it at the top of seek() before queuing
the seeking event. This drops any playbackProgressTimer-queued
timeupdate task that is sitting in the queue just before the
seek starts, so the seek algorithm owns the event ordering as
the spec prescribes. Non-periodic timeupdate calls (finishSeek,
updateReadyState, mediaPlayerTimeChanged's discontinuity path)
continue to use the general async-events group and are not
cancelled.
Also add an early-return in scheduleTimeupdateEvent(periodicEvent)
when m_seeking is true. This covers the complementary case where
a periodic timer tick happens during an already-in-flight seek:
per spec, during an explicit seek the seek algorithm's own
events (seeking -> timeupdate -> seeked via finishSeek) are what
the page sees; the periodic timer contributes nothing useful.
* Source/WebCore/html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::seekTask):
(WebCore::HTMLMediaElement::scheduleTimeupdateEvent): early return
if (periodicEvent && m_seeking); for periodic events queue the
task through m_periodicTimeupdateCancellationGroup.
* Source/WebCore/html/HTMLMediaElement.h:
*
Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm:
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::bufferedChanged):
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::updateStateFromReadyState):
drop timeChanged() in the shouldBePlaying branch.
Canonical link: https://commits.webkit.org/313165@main
To unsubscribe from these emails, change your notification settings at
https://github.com/WebKit/WebKit/settings/notifications