WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
RESOLVED FIXED
299853
REGRESSION (Safari 26): Live HLS ID3 timed metadata cues mapped with endTime = Infinity cause TextTrack.activeCues to include all past cues indefinitely
https://bugs.webkit.org/show_bug.cgi?id=299853
Summary
REGRESSION (Safari 26): Live HLS ID3 timed metadata cues mapped with endTime ...
baehongjun
Reported
2025-09-29 21:44:48 PDT
**Product/Component** * Product: WebKit * Component: Media → TextTracks / Timed Metadata * Version/Build: Safari 26.0 (20622.1.22), 26.0.1 on macOS 26 / iOS 26 **Summary** Since Safari 26, ID3 timed metadata (in-band HLS) seems to be mapped to `DataCue` objects whose `endTime` is `Infinity`. As a result, `textTrack.activeCues` no longer represents “cues overlapping the current time” but **keeps all cues inserted so far**. Our ad logic reads the latest **NMSS timestamp** (`info = "com.timestamp"`) from `activeCues`. On Safari 26, `activeCues[0]` sticks to the very first cue, so the parsed `current-utc` value **never updates**. Earlier Safari versions and other browsers behave as expected. **Context / Potential cause** * Safari 26 release notes mention *“Added support for in-band tracks in MSE.”* Our path does not use MSE, but there may have been broader changes in TextTrack/timed-metadata plumbing. ([Apple Developer][1]) * The HTML spec defines **unbounded cues** as those with `endTime = +Infinity`, which **cannot become inactive** during normal playback. Safari 26 appears to map ID3 cues as unbounded now. ([html.spec.whatwg.org][2]) * Past discussions proposed using `Infinity` to represent end-of-media, and the Media Timed Events TF discussed **bounding the previous unbounded cue when a new event arrives** to keep a meaningful active set. Safari 26 seems to keep **all** previous ID3 cues unbounded. ([GitHub][3]) **Actual Results** * `textTrack.activeCues` permanently includes **all NMSS timestamp cues since insertion** → `activeCues[0]` holds a stale `current-utc` * Mid-roll ad polling logic stops (no “latest timestamp” detected), so no subsequent ad requests fire **Expected Results** * Either keep `activeCues` limited to cues that overlap the current playback time (as before), **or** * If adopting unbounded cues, **bound the previous cue’s `endTime` when a new event arrives**, so the active set remains meaningful around “now” **Workaround (production-safe)** On Safari 26, avoid `activeCues` and **search by `startTime` around the current time** (binary search for performance): ```ts function isSafari26() { const ua = navigator.userAgent; const isSafari = !!ua.match(/Version\/[\d.]+.*Safari/) && navigator.vendor === 'Apple Computer, Inc.'; if (!isSafari) return false; const m = ua.match(/Version\/(\d+)/); return m && parseInt(m[1], 10) === 26; } function getNmssTimestampCue(player: HTMLVideoElement, tolerance: number): DataCue | null { for (const track of Array.from(player.textTracks)) { if (track.kind !== 'metadata') continue; if (!track.cues) { if (track.mode === 'disabled') track.mode = 'hidden'; continue; } let hasNmss = false; for (let i = 0; i < track.cues.length; i++) { const { value } = track.cues[i] as any; if (value && value.info === 'com.timestamp') { hasNmss = true; break; } } if (!hasNmss) continue; const active = isSafari26() ? getAdjacentCues(track, player.currentTime, tolerance) : (track.activeCues ? Array.from(track.activeCues) : []); if (active.length === 0) continue; return active[0] as DataCue; } return null; } function getAdjacentCues(track: TextTrack, currentTime: number, tolerance: number): TextTrackCue[] { const result: TextTrackCue[] = []; const cues = track.cues; if (!cues || cues.length === 0) return result; let l = 0, r = cues.length - 1, last = -1; while (l <= r) { const mid = (l + r) >> 1; if (cues[mid].startTime <= currentTime) { last = mid; l = mid + 1; } else { r = mid - 1; } } if (last === -1) return result; for (let i = last; i >= 0; i--) { const cue = cues[i]; if (currentTime - cue.startTime > tolerance) break; result.unshift(cue); } return result; } ``` **Regression?** * Yes. Works on earlier Safari and other engines; breaks on Safari 26.0/26.0.1 (macOS/iOS). **Questions / Requests** 1. Was mapping ID3 cues to **unbounded** (`endTime = Infinity`) an intentional change in Safari 26? 2. If intentional, could WebKit consider **bounding the previous unbounded cue** when a new event arrives (per MTE TF discussions), or provide guidance on how to fetch the *latest* metadata event without scanning? ([W3C][4]) 3. If this is expected by spec, please update release notes/docs to call out the web-compat impact for apps that rely on `activeCues` semantics. Current public notes don’t mention this detail. ([Apple Developer][1]) **References** * Safari 26 Release Notes (26.0 / 20622.1.22; 26.1 beta list) ([Apple Developer][1]) * WebKit Features in Safari 26.0 (in-band tracks in MSE) ([WebKit][5]) * HTML Standard: unbounded text track cue (`endTime = +Infinity`) & “cannot become inactive” ([html.spec.whatwg.org][2]) * WHATWG: proposal to use `Infinity` for end-of-media ([GitHub][3]) * W3C Media Timed Events TF minutes: bounding previous unbounded cue when a new event arrives ([W3C][4]) * Apple HLS docs (timed metadata overview) ([Apple Developer][6]) --- [1]:
https://developer.apple.com/documentation/safari-release-notes
[2]:
https://html.spec.whatwg.org/dev/media.html
[3]:
https://github.com/whatwg/html/issues/5297
[4]:
https://www.w3.org/2021/04/19-me-minutes.html
" [5]:
https://webkit.org/blog/17333/webkit-features-in-safari-26-0
[6]:
https://developer.apple.com/documentation/http-live-streaming/links-to-additional-specifications-and-videos
[7]:
https://docs.aws.amazon.com/medialive/latest/ug/insert-timed-metadata.html
Attachments
Add attachment
proposed patch, testcase, etc.
baehongjun
Comment 1
2025-09-29 23:22:06 PDT
# Reproduction Steps Test on Safari 26.0 or 26.0.1 (macOS 26 / iOS 26) Open Developer Tools (Inspector) → Console tab Run the following snippet: const video = document.querySelector('video'); for (const track of video.textTracks) { if (track.kind === 'metadata') { track.mode = 'hidden'; // ensure cues are exposed console.log('metadata track:', track); console.log('activeCues:', Array.from(track.activeCues || [])); } } - On Safari 26, track.activeCues does not include only the latest cue, but instead retains all past NMSS timestamp cues - On Safari 25 or earlier, and on Chrome/Firefox, activeCues correctly returns only the cues that overlap the current playback position
Radar WebKit Bug Importer
Comment 2
2025-09-30 15:35:49 PDT
<
rdar://problem/161687917
>
demur_petty.5t
Comment 3
2025-10-17 04:51:09 PDT
issue can be reproduced on
https://chzzk.naver.com/
while some streams are region-locked, most can be played.
demur_petty.5t
Comment 4
2025-10-17 05:03:01 PDT
reproduced on iOS 18 and later, macOS Sequoia, and macOS Tahoe. Similar to
bug 255499
.
Eric Carlson
Comment 5
2025-10-29 08:25:30 PDT
Pull request:
https://github.com/WebKit/WebKit/pull/53144
EWS
Comment 6
2025-10-31 11:47:30 PDT
Committed
302395@main
(01952a527b50): <
https://commits.webkit.org/302395@main
> Reviewed commits have been landed. Closing PR #53144 and removing active labels.
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug