5.29.0 - Container chapters, opt-in video Now-Playing, second-open HLS reroute
Two external contributions from natedogg058, both merged and then hardened here, plus a routing fix reported by qoli. No breaking changes.
Added
-
Container chapters as
mediaChapters. Matroska and MP4 chapters are read off the probe demuxer at load and published as@Published mediaChapters: [ChapterInfo], so a host can bind a chapter picker for ordinary files the way it already can for discs. Empty for disc sources, which keep publishingdiscChapters, so exactly one of the two is ever populated. The two differ in time axis: a disc chapter is title-relative and is seeked throughselectChapter(id:), which adds the backend's content-start base, whereas a container chapter'sstartSecondsis already a timestamp on theseek(to:)axis and a host passes it straight through.selectChapter(id:)stays disc-only and no-ops for a container chapter id. Ids are sequential in start order, so they stay usable as list indices, and untitled entries are numbered "Chapter N". Contributed by natedogg058 (#179).A chapter's duration runs to the next chapter's start rather than to its declared end, because muxers routinely write
end == start; the last entry falls back to its declared end and then to the container duration, since the next-start rule cannot cover it. -
System Now-Playing on the native video path, opt-in. A host with its own transport can let the engine own an
MPNowPlayingSessionbound to the native player: setownsVideoNowPlayingSessionbeforeload(), register transport commands on the publishedvideoNowPlayingSession, and stage identity metadata withsetVideoNowPlayingInfo(_:). The staged dictionary is replayed onto every freshAVPlayerItem, so the readiness-gate master reload, the media fallback and an in-place PiP swap keep the system card instead of dropping it mid-session. The session lives on the native host and therefore survives native->native reloads with it, which is what keeps Now-Playing ownership across a background pause. The session auto-publishes elapsed time, rate and duration from the player, so nothing writesMPNowPlayingInfoCenter.default().nowPlayingInfo. Contributed by natedogg058 (#180).It is off by default and must stay off under
AVPlayerViewController. AVKit owns Now-Playing itself there, through its own registration, and WWDC22's guidance is not to bring a session when using AVKit. Two owners produce the half-working state: an empty identity card displaces AVKit's, and remote commands route into a session with no handlers on it.docs/formats.mdpreviously told custom-transport hosts to hold their own session againstcurrentAVPlayer; it now points at this API.
Fixed
-
A remote HLS VOD source died when the load-time probe failed for an unrelated reason. The AE#154 reroute onto the native remote-HLS bypass keys on the reader's typed
hlsPlaylistOnVODPathclassification, and that classification only reached it when the load-time probe was the open that read the playlist body. If the probe failed transiently first,probeFailurewas inconclusive, the load fell through to the loopback path with no preopened demuxer, andHLSVideoEngine.start()reopened the URL. That second open is then the first one to see the body, so it produced the classification, which was interpolated intoopenFailed(reason: "\(error)")and lost its domain one frame before anything could act on it. The result was a terminalHLS playlist supplied to the VOD loopback pathfor a source AVPlayer plays fine, while a manual retry whose first probe happened to see the body routed natively and reachedreadyToPlay. Reported by qoli (#246).The fallback open now rethrows both HLS classifications verbatim (
hlsPlaylistOnRawLivePathrides along, so the AE#140 fail-closed contract stays typed at every open too), andload()answers one from either origin with the same reroute. It is taken as a fullload()restart, the shape the #168 reroute already uses, because the half-built loopback session has to be cleared; the bypass runs no probe, so it cannot bounce back a second time. NoisLiveguard is needed: the reader only classifies ashlsPlaylistOnVODPathon the non-live path. -
The remote-HLS bypass ignored the resume position.
LoadOptions.nativeRemoteHLSroutes before the probe, and that branch never forwardedstartPosition, so a VOD playlist restarted at zero whether a host requested the bypass directly or arrived on it through a reroute. Without this the reroute above would have preserved URL and headers and then quietly dropped the anchor. VOD honors it now; live keeps its no-initial-seek contract even when a host passes one.
Hardening applied to the merged contributions
- The container-chapter wipe sat in
stopInternalwith a snapshot/restore pair around the reload helper, butteardownVideoForBackgroundand the #127 expiry callstopInternaloutside that helper, so an iOS background roundtrip emptied the list for the rest of the session. Chapters belong to the source URL rather than to the pipeline, which is the rulefontAttachmentsalready follows, so the wipe moved tostop()and both reload hunks came out. tvOS was never affected. - A zero time-base numerator is rejected rather than accepted; it scales every chapter timestamp to zero.
- The Now-Playing identity write and its per-item replay are gated on ownership, so an AVKit host cannot have a second, host-authored identity fed into AVKit's own publication.
becomeActiveNowPlaying()re-asserts on the load path, matching the audio path. A host preserved across a reload never re-runsinit, so a session another app took over was never claimed back.pendingVideoNowPlayingInfoclears instop()next topendingExternalMetadata, while still survivingstopInternalso a reload keeps the card through the seam.
Tests
Issue246SecondOpenRerouteTests covers the open-failure mapping and the reroute decision on the error it now preserves, including the regression direction: a wrapped classification must not reroute, which is why it must not be wrapped. ContainerChapterMappingTests covers ordering, ids, naming and all three duration rules over the pure mapping function. VideoNowPlayingOptInTests covers the opt-in gate and the staging semantics. Suite is 1272 tests, green, with -strict-concurrency=complete clean and the tvOS Simulator build succeeding.