feat(player): opt-in stream information caption with playlist name - #210
Open
bilipp wants to merge 1 commit into
Open
feat(player): opt-in stream information caption with playlist name#210bilipp wants to merge 1 commit into
bilipp wants to merge 1 commit into
Conversation
Adds an in-player stream-information caption to iOS, iPadOS, macOS and visionOS, and puts the owning playlist's name into it. Until now the only information surface in the app was the tvOS player chrome — every file behind it is wrapped in `#if os(tvOS)` — so this builds one shared leaf rather than flipping a gate. The caption rides the existing controls overlay and inherits its auto-hide: no new button, panel or gesture competing with the tap-to-toggle, the scrubber drag and the interactive dismiss already on the video surface. It is off by default and free. A two-level Simple/Advanced preset decides how much it spells out. Simple shows the playlist, category and what's on now; Advanced adds quality, codec, frame rate and the active engine. tvOS defaults to Advanced, so its caption is unchanged apart from the added playlist name, and it gains the same picker in its player settings pane. The playlist is the one that *owns* the playing item, recovered from the content id's UUID prefix with a single predicated fetch — not the globally selected playlist, which would be wrong for cross-playlist search, Downloads, Continue Watching and deep links. No schema change, and no new field on the Codable `PlayableMedia` that macOS window restore and the Live Activity's `lume://resume` persist. Two pre-existing bugs fixed on the way: - KSPlayer, the default engine, had no `videoInfo` off tvOS — its only producer lived inside the tvOS-gated `KSTVPlaybackEngine`. Lifted verbatim into an ungated `KSVideoInfo` that both platforms call, so the Advanced readout is not permanently blank for most users. - `PlayerVideoInfo.captionParts` hardcoded `"… fps"` and a `%.2f` period decimal separator, which is untranslated and wrong in de/fr/es/pt/it. Elements with no value collapse rather than emitting a dangling separator — the common case, since AVPlayer reports no codec or frame rate and a live channel with no matched XMLTV has no programme. Preferences are device-local `UserDefaults` read directly, with `@AppStorage` only in the leaf; hoisting it would re-render the whole player tree on every toggle. The per-stream resolve hangs off the same enabled gate, so a viewer with the caption off pays for no fetch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Brings an in-player stream information caption to iOS, iPadOS, macOS and visionOS, shows the owning playlist's name in it, and adds a Simple / Advanced detail-level preference.
Until now the only information surface in the app was the tvOS player chrome —
TVPlayerControlsOverlay.titleBlockand theInfopanel, every file behind them wrapped in#if os(tvOS). The four non-tvOS engine overlays rendered only title, subtitle and a LIVE dot. So this builds one shared leaf rather than flipping a gate.Simple
Example · NEWSAdvanced
Example · NEWS · 1080p · H264 · 24 fps · KSPlayerSettings (Player section, free, no paywall)
Design decisions
fullScreenCoverdismiss and macOS.onContinuousHoveralready compete on that surface. (tvOS's "show it briefly on channel change" has no iOS equivalent:switchLiveChannel/LiveChannelNavigatorexist only inside#if os(tvOS).)PlayerEpisodeOverlaysalready is — never four inline edits to four near-identical title blocks. tvOS and non-tvOS both derive from the samePlayerInfoSnapshot, so they cannot drift.fetchLimit = 1fetch. Not the globally selected playlist — that would be actively wrong for cross-playlist search, the Other Sources rail, Downloads, Continue Watching andlume://playdeep links. No?? playlists.firstfallback: a miss collapses the row.PlayableMedia— it is Codable, is the macOSWindowGroupvalue and is JSON-persisted byPlaybackResumeStorefor the Live Activity'slume://resume.Two pre-existing bugs fixed on the way
videoInfooff tvOS. Its only producer lived in the tvOS-gatedKSTVPlaybackEngine.refreshVideoInfo(). Lifted verbatim into an ungatedKSVideoInfothat both platforms now call — otherwise the Advanced readout would be permanently blank for most users.PlayerVideoInfo.captionPartshardcoded"… fps"andString(format: "%.2f")— untranslated, and a period decimal separator that is wrong in de/fr/es/pt/it. Now localized and locale-aware, with the English rendering unchanged.Graceful degradation
Elements with no value collapse rather than emitting a dangling separator — the common case, since AVPlayer reports
fps: 0, codec: nil(and an active AirPlay route silently forces AVPlayer), and a live channel with no matched XMLTV has no programme. Covered by tests.Performance
Preferences are device-local
UserDefaultsread directly, with@AppStorageonly in the leaf — hoisting it into an engine view orFullScreenPlayerViewwould re-render the whole player tree on every toggle. The per-stream resolve hangs off the same enabled gate, so a viewer with the caption off pays for no fetch at all. EPG comes fromChannelEPGLoaderoff-main, never a@Query<EPGListing>.Verification
--strictexit 0check-translations.swift→ OKAlso verified by hand on an iPhone 17 Pro simulator against the example server: both detail levels render, the playlist name is correct, and a channel with no EPG collapses cleanly.
Reviewer notes
#if !os(tvOS)gate ships it there and no visionOS-specific API is involved, so the risk is layout rather than compilation, but it has not been compiled..captionwithminimumScaleFactor(0.7), riding the control column above the transport; with every Advanced element the engine name gets clipped. Fine in landscape and on iPad/Mac. Worth deciding separately whether Advanced should drop the engine name on compact widths.accessibility1— above that the caption crowds the title and scrubber off the video. VoiceOver still reads the full expanded label ("24 frames per second", not "24 f p s") as a single element regardless of rendered size..gitignoregains a bareExampleDataline; the existingExampleData/does not match the symlink a worktree needs.