RESOLVED FIXED299853
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
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
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
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.