Skip to content

3.0.0: Playback clock split, probe API for custom readers, raw ASS

Choose a tag to compare

@superuser404notfound superuser404notfound released this 10 Jun 16:05
· 190 commits to main since this release

A breaking release centered on one API redesign, plus the probe / custom-IO extensions and the subtitle memory fix from this week's issue work.

Breaking: the playback clock moved to engine.clock

currentTime ticked at ~10 Hz as @Published on the engine itself, so every SwiftUI view observing the engine re-rendered per tick no matter what it read. On tvOS that re-render tears down native Menu dropdowns mid-interaction, which is the flicker reported in #29.

The continuously ticking values (currentTime, sourceTime, progress, liveEdgeTime, seekableLiveRange, isAtLiveEdge, behindLiveSeconds) now live on PlaybackClock, a separate ObservableObject exposed as engine.clock. The engine's objectWillChange no longer fires on clock ticks. This mirrors AVFoundation's own shape: AVPlayer exposes time via addPeriodicTimeObserver, not as observable state.

Migration

  • Plain reads compile unchanged: engine.currentTime etc. remain as read-only forwarders.
  • Combine subscriptions move: engine.$currentTime becomes engine.clock.$currentTime (same for sourceTime, progress, and the live-edge fields).
  • Time-driven UI (transport bar, time labels) should observe engine.clock in the leaf view that renders time; everything else observes the engine and stays quiet during playback.

Added

  • probe(source:) accepts a MediaSource, so custom IOReader sources can be probed like URLs. The caller keeps reader ownership; the probe never calls close(). Closes #27.
  • load() returns SourceProbe (@discardableResult, existing callers compile unchanged): video size, codec, duration, tracks, and container tags in one shot, without a second probe round-trip. sourceVideoWidth / sourceVideoHeight are also public read-only now. Closes #28.
  • Opt-in raw ASS event lines. LoadOptions.preserveASSMarkup emits ASS / SSA cues as the raw event line (override tags, style references, escapes intact), and TrackInfo.assHeader carries the track's script header so hosts can resolve style references. Default off. Part of #30; full libass rendering stays open there.
  • Live DVR scrub thumbnails via liveScrubThumbnail, decoded straight from the DVR segment cache.
  • DataIOReader, a ready-made in-memory IOReader over an immutable Data buffer.
  • Native remote-HLS path (LoadOptions.nativeRemoteHLS): play a server-provided HLS URL directly with AVPlayer, bypassing the demux / remux / loopback pipeline.
  • SW-path deinterlacing through a persistent bwdif / yadif graph (DVD-rip MPEG-2, SD broadcast).
  • HE-AAC / LATM bridging: LATM/LOAS AAC live audio bridges instead of dropping; plain ADTS-AAC stream-copies into fMP4.

Fixed

  • Embedded-subtitle side demuxer no longer races to EOF. It paces against the playhead (90 s read-ahead) and TCP backpressure throttles its connection to playback rate. Previously it re-downloaded the entire remaining file alongside playback and pinned every future PGS bitmap cue in memory, which on 50-80 GB UHD remuxes ran the app into jetsam. Subtitle part of #31; the no-subtitle memory spike reported there is still under investigation.
  • Live hardening batch: server-side stream-replay detection after reconnect, program-boundary timeline rebase, A/V-sync rebase pairing, source-loss auto-reopen with backoff, deterministic pause/resume, LL-HLS blocking playlist reload, fast give-up on dead tuners, abortable in-flight probes on stop / channel zap.
  • VOD robustness batch: muxer-wedge exit, audio-bridge EOF / restart flush, Range-ignored (200-at-offset) guard, cache-gated backward restart, paused-seek clock anchor, corrupt-source-audio resilience.

Thanks

@ohjey for the precise root-cause analysis behind the clock split (#29), @strangeliu for the probe / load API proposals (#27, #28) and the raw-ASS suggestion (#30), @edde746 for the libass request (#30), @RomanLiberda for the large-file memory report (#31), and @DrHurt for review input across the board.

Full Changelog: 2.5.0...3.0.0