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

Reply via email to