Skip to content

Live TV: watch the server's channels, and a TV tab of channel covers - #993

Draft
Dr-Amp wants to merge 1 commit into
GLinnik21:mainfrom
Dr-Amp:contrib/opus-live-tv
Draft

Dr-Amp wants to merge 1 commit into
GLinnik21:mainfrom
Dr-Amp:contrib/opus-live-tv

Conversation

@Dr-Amp

@Dr-Amp Dr-Amp commented Oct 24, 2026 •

Copy link
Copy Markdown

Watch a server's Live TV channels from the app: a channel row on Home and a TV tab whose page is a grid of channel covers (the guide's logos, fetched through the server's photo transcoder) with what is on now. OK tunes the channel and direct-plays the server's recording of it; leaving the player, or the app going to the background, sends the stopped that frees the server's tuner.

Built and used against PMS 1.43.3 with one DVR backed by ErsatzTV (HDHomeRun protocol) and an XMLTV guide. Opening as a draft: one gate below needs your decision.

What it does

  • Reads (plex/src/plex/livetv.rs, docs/pms-api.md §10): /livetv/dvrs, the DVR's lineup and the guide grid bounded to now. Only DVRs with a tuner the server last saw alive and a well-formed provider id get a row; the tune path accepts one DVR and one channel segment, nothing else.
  • Watching a channel follows the official LG client's sequence, read from the server log (§10): POST …/tune with the playback session id → universal decision for the session (hasMDE=1, Generic + an HLS/MPEG-TS target, so PMS answers direct play) → the session's own media playlist at the edge (offset=-1). The timeline reports under the live session; stopped&continuing=0 ends the recording. A tune whose plan is abandoned or refused still stops its session.
  • HLS reader for a live recording (media/src/ff.rs): the entry may be a media playlist with no master; segments are cuts of one stream, so a segment without SPS/PPS or an IDR continues from the previous one of the same playlist (scoped to its directory); playback starts 3 s behind the edge and polls every 500 ms. Library HLS keeps its behaviour (live: false). Seeks are refused on a live channel.
  • UI: screens/src/livetv.rs (6-column grid of 2:3 covers, OK tunes, BACK → Home), a TV tab on the shared strip (the longer label overflowed GLASS_TRACK_MAX and lost the glass), HomeTab::LiveTv / AppArg::LiveTv, locale strings.

One signed-off commit on current main (squashed from the working history, same tree).

Why draft

  1. fxlanes (check-deps). The page's requests leave as AppFx::LiveTv and the bridge parks them on a side queue drained by content::livetv_requests — the Watchlist page's pattern, written before Sans-IO baseline: make check ledgers that freeze IO in logic #971 froze that ledger. The gate correctly refuses the new site. Which machine should own these (tune, tab, account, back) on the ordered FIFO? I'll move them once you say how you want it; I did not want to guess a design for the Sans-IO series.

  2. Replay fixtures — done. The Live TV screen moved the state shape (SCREEN_SHAPES_PIN now 0x54a0_99c6_78cc_98c9). 1-boot-home-chip-grid, 6-settings-family and 12-filmography-detail-return were re-recorded on a Mac (120 Hz display, 350 ms key interval) and installed with plxnative-rec rerecord, anchors retained; the closed alphabet gains the strip's TV label. See tests/fixtures/replay/README.md.

  3. Binary size budget. Live TV adds 131,120 bytes to the stripped ARM binary (15,275,256 against main's 15,144,136; binary_bytes limit 15,180,000). I'll raise it in this PR with the justification once the fxlanes shape is settled, since that changes the code being measured, or rebase onto a re-baseline if you'd rather do that on main.

Verification

Host: CachyOS x86_64, pinned toolchain 1.99.1, on 17dc0a22 (main a2b7bb33 merged in):

  • make check-nodef: pass. check-cargo-lint (clippy): pass.
  • check-cargo-unit-default and check-cargo-unit-hostsim: every crate passes except plx_media's two curlio::keymode_tests (media_key_mode_never_reuses_a_kept_alive_connection, media_seeks_and_reopens_in_key_mode_each_do_their_own_handshake), which also fail on this host (libcurl 8.22) on unmodified main e843b99f and pass in CI.
  • ci/check-deps.sh: one failed gate, fxlanes (above).
  • macOS 26.6.2 (M1 Pro): make sim-macos, then tests/replay_fixtures.py --window 960x540: 3 fixtures, 6 replays, 0 failures, verdict=SAME with every counter zero, on three consecutive runs.
  • Not run here: the ARM cross-build of this merge.

Device: LG C5, webOS 10.3, PMS 1.43.3 + ErsatzTV, on the pre-merge branch (builds of 2026-10-21): tune → direct play of the session with picture and sound (a 42 s log held 950–1050‰ after the start), an abandoned plan's stop freed the tuner (livetv: abandoned session stopped ok=1), and the TV tab showed the channel covers. Not verified on the panel: the stop on going to the background (needs the Home button), and anything after merging main.

Not in scope: DVR recording, the guide grid, channel changing inside the player.

Signed-off-by: Dr-Amp dr_amp@users.noreply.github.com

@Dr-Amp

Dr-Amp commented Oct 25, 2026

Copy link
Copy Markdown
Author

Merged current main into this branch. The #992 squash is now an ancestor, so the diff against main is Live TV only.

Two Russian strings were needed for the new Live TV keys, or the build fails: browse.chrome.live_tv = "ТВ" and browse.home.live_tv = "Прямой эфир" (locales/ru/browse.json). They are AI-drafted and no native speaker has reviewed them yet, per docs/localization.md.

The replay fixtures are not re-recorded yet and the fxlanes question is still open, so the PR stays a draft.

@Dr-Amp

Dr-Amp commented Oct 26, 2026

Copy link
Copy Markdown
Author

Merged current main and re-recorded the three replay anchors on a Mac (1-boot-home-chip-grid, 6-settings-family, 12-filmography-detail-return; installed with rerecord, anchors kept; TV added to the closed alphabet). Locally, tests/replay_fixtures.py replays all three SAME in Targets and Resolve, three runs in a row.

That should leave fxlanes as the only red. It is more than a two-line change: livetv_requests checks the route and the input owner, navigates (nav_tab, the account chip) and starts the player, and none of that is reachable from Bridge::deliver_inner. Pushing Fx::Deliver(..) from the arm would only move the side queue somewhere else, which is what the gate exists to stop.

How would you like it: a page-request lane the Dispatcher drains for Live TV, or should Live TV wait for the migration that takes the Watchlist page off its ledger line? I'll follow whichever you prefer.

@Dr-Amp

Dr-Amp commented Oct 28, 2026

Copy link
Copy Markdown
Author

Merged current main (a329aad) into the branch and pushed (db066a9).

  • Conflict in route/plan.rs: the Live TV early return now sits at the top of the new thread scope in build_stream, before the direct-play check. The new refused-preview test helper gets live: None.
  • main added Polish, and the build rejects a catalog that lacks a key. I added two Live TV strings to locales/pl/browse.json: TV and Telewizja na żywo. These are my draft; no native speaker has reviewed them yet.
  • Local gates: lint, type checks, localization and the unit suites pass apart from the two known libcurl tests on this host.
  • CI on db066a91: lint, type checks, host unit tests (default and hostsim), both simulator jobs (replays 1/6/12) and the Windows launcher pass. Still red: the fxlanes check and its 14 harness tests (unchanged, open question above), and the armv7 binary budget: binary_bytes 15,275,256 > limit 15,180,000 (main at a329aad measures 15,144,136). I have not changed ci/build-budgets.json.

A channel row on Home and a TV tab whose page is a grid of channel covers
(the guide's logos through the server's photo transcoder) with what is on
now. OK tunes the channel and direct-plays the server's recording of it;
leaving the player, or the app going to the background, sends the
`stopped` that frees the server's tuner.

- Reads (plex/src/plex/livetv.rs, docs/pms-api.md §10): /livetv/dvrs, the
  DVR's lineup and the guide grid bounded to now.
- Watching follows the official LG client's sequence: tune with the
  playback session id, universal decision (hasMDE=1, HLS/MPEG-TS target),
  the session's media playlist at the edge; stopped&continuing=0 ends it.
- HLS reader for a live recording (media/src/ff.rs): segments are cuts of
  one stream; playback starts 3 s behind the edge. Library HLS unchanged.
- UI: screens/src/livetv.rs, a TV tab on the shared strip, locale strings
  (ru, pl drafted without a native reviewer).
- Replay anchors 1, 6 and 12 re-recorded on a Mac for the new screen shape.

Squashed from the contrib/opus-live-tv history onto current main; the
tree is identical to the reviewed branch head.

Signed-off-by: Dr-Amp <dr_amp@users.noreply.github.com>
@Dr-Amp
Dr-Amp force-pushed the contrib/opus-live-tv branch from db066a9 to 0819d50 Compare October 28, 2026 16:08
@Dr-Amp

Dr-Amp commented Oct 28, 2026

Copy link
Copy Markdown
Author

@GLinnik21 this one has been waiting on a single call from you since Monday. Could you pick one?

  1. A page-request lane the Dispatcher drains, added in this PR with Live TV as its first user.
  2. Ride the Watchlist migration: Live TV keeps the Watchlist page's shape and leaves the side queue in the same step that takes AppFx::Watchlist off the fxlanes ledger (W-1 landed today in Watchlist W-1: the stores take the seated profile and its identity from the Bridge (#1107) #1108). If that's the plan, roughly when?
  3. No new arm: the page emits the existing Home/Content requests (tab, account, back; the tune as a new PlayIntent), so the ledger doesn't grow, at the cost of more traffic on two ledgered queues.

A one-word answer is enough; I'll do the work. If I don't hear otherwise by Friday, I'll go ahead with 1 so you have something concrete to review.

Everything else on my side is done: the branch is now one signed-off commit on current main (same tree), and replays 1/6/12 pass on both simulators. After fxlanes, the only red is the ARM size budget, which I'll raise here with the numbers once the shape is settled (details in the description).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant