Skip to content

Live TV subtitles/audio, IPTV guide and playlist fixes - #806

Merged
ProdigyV21 merged 9 commits into
ProdigyV21:mainfrom
silentbil:fix/live-tv-subtitles
Oct 15, 2026
Merged

ProdigyV21 merged 9 commits into
ProdigyV21:mainfrom
silentbil:fix/live-tv-subtitles

Conversation

@silentbil

@silentbil silentbil commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Live TV subtitles and audio, plus IPTV fixes found while testing them on real playlists.

Live TV: subtitle and audio tracks

  • Live channels often carry DVB bitmap subtitles inside the stream and more than one audio language. The Live TV player never chose a subtitle track, so subtitles only showed when the device's system captions were on in the same language — on some TVs and not on others.
  • Channels now start on the profile's Default subtitle and Default audio language, and the full-screen controls gain Subtitles and Audio buttons (shown only when there is a choice). A pick is remembered per channel for the session.
  • The Home channel preview also uses the default subtitle language (trailers are unchanged).

Muxed subtitles were invisible on some channels

  • With chunkless HLS preparation the track list comes from the master playlist's CODECS, which names only video and audio, so subtitles muxed into the TS segments never appeared. Media3 falls back to reading a segment when a variant lists two audio codecs, so channels of one provider had subtitles or not depending on their audio count.
  • IPTV HLS (Live TV, Home preview, TvScreen) is now prepared from its first segment. Measured tune-to-playing time is unchanged (~0.5 s).

Every DVB subtitle language on IPTV HLS channels

  • Broadcast channels often carry several DVB subtitle streams (e.g. two languages). In HLS mode Media3's TsExtractor keys tracks by stream type and keeps only the lowest PID, so only the first subtitle language ever appeared — picked by stream order, not by the user's language.
  • IPTV HLS now uses a vendored copy of the media3 1.9.0 TS extractor (src/main/iptvts-java, same pattern as the vendored Matroska extractor) whose only change gives each DVB subtitle PID its own HLS track id. IptvHlsExtractorFactory routes MPEG-TS segments to it and every other segment type to Media3's DefaultHlsExtractorFactory, configured exactly as before.

Stalled live streams

  • A stream whose buffer stops moving while buffering used to spin until Media3's 10-minute stuck-buffering timeout. It now errors after 12 s and is restarted at the live edge, at most once a minute and outside the existing retry budget.

Full-screen controls

  • The action buttons (Guide, Choose source, Subtitles, Audio, Replay) no longer overlap the centred playback controls; the controls stay centred and slide aside only when needed.
  • Closing the Subtitles/Audio list returns focus to the button that opened it, so left/right move between the controls again — also when the list stayed open past the HUD's 5 s timeout (the HUD hiding used to hand focus back to playback, so Down zapped channels).

Guide missing for playlists that declare their own XMLTV

A plain M3U whose guide is only named in its #EXTM3U url-tvg="..." line showed "No guide data matched" on every channel:

  • The saved guide address was only restored from a cache that already held guide data — never true before the first guide load, so the guide could never load. The store-based startup path did not restore it at all.
  • Only a typed or Xtream guide counted as an XMLTV source.
  • The visible XMLTV fallback downloaded the whole guide but kept only the visible rows, and the same URL may not be fetched again for two minutes, so only the first row ever got a guide.

The saved address is now restored on those paths; when none was saved the playlist header is read once per session; a declared guide counts as an XMLTV source; and for lists up to 10,000 channels one download fills the whole guide.

Pasted addresses with invisible characters

  • A pasted playlist address was stored with a trailing NUL character, so every request went to .../list.m3u%00 and the server answered HTTP 400. Playlist, guide and Stalker portal/MAC input now drop control and invisible format characters before use, which also repairs addresses already saved.

Favorite TV row after switching playlists

  • Switching which playlist is enabled left the Home Favorite TV row on the old playlist's channels until a restart. Home now rebuilds the row once the new channel list can be read (or removes it when the new sources have no favourites).

Notes

  • A second audio language in the same stream is still collapsed by Media3's HLS audio mapping (by type, not id). Out of scope here.

Testing

  • ./gradlew :app:testSideloadDebugUnitTest passes. New tests: LiveSubtitlesTest, IptvTsSubtitleTracksTest (two DVB languages become two tracks; stock extractor gives one), HudControlBarTest, IptvDeclaredGuideRestoreTest, IptvInputSanitizingTest, additions to LiveTvRetryPolicyTest, LiveTvGuideSourcesTest and FavoriteTvGuideRefreshTest.
  • Android TV emulator (API 30):
    • subtitles shown and switchable on channels with one and with two audio tracks; Home preview subtitles;
    • a channel with two DVB subtitle languages lists both, and the second one displays;
    • stall restart observed in the log;
    • HUD layout, and focus after closing the list by OK, by Back and after the HUD timed out;
    • guide: reproduced the empty guide on main, then with this branch a clean state (no saved guide data, nothing typed in settings) filled the guide for every channel from one download, and it survived a restart;
    • a playlist saved with a trailing NUL loads;
    • Favorite TV follows a playlist switch without a restart;
    • an Xtream login still loads and plays.

Live channels often carry their subtitles inside the stream (DVB bitmap
subtitles in MPEG-TS) and more than one audio language. The Live TV
player never chose a subtitle track, so subtitles only showed when the
device's system captions were on in the same language - on some TVs and
not on others - and the audio was whatever the stream listed first.

Live TV now starts each channel on the profile's default subtitle
language and default audio language (when not "Off" / "Auto"), and the
full-screen controls gain Subtitles and Audio buttons that list the
channel's tracks. A choice is remembered per channel for the session.
… focus

IPTV HLS channels often carry DVB subtitles inside the TS segments while
the master playlist's CODECS lists only video and audio. With chunkless
preparation the player built its track list from CODECS alone, so those
subtitles never appeared. Media3 already reads a segment when a variant
lists two audio codecs, which is why channels of the same provider showed
subtitles or not depending on their audio count. IPTV HLS is now always
prepared from its first segment (Live TV, the Home preview and TvScreen);
measured startup is unchanged.

- Home preview: channel previews use the profile's default subtitle
  language; trailers keep the no-subtitle default.
- A live stream whose buffer stops moving now raises an error after 12s
  (Media3's default is 10 minutes) and is restarted at the live edge, at
  most once a minute and outside the existing retry budget.
- Full-screen controls: the action buttons no longer overlap the centred
  playback controls; the controls slide aside only when needed.
- Closing the Subtitles or Audio list returns focus to the button that
  opened it, so left/right move between the controls again.
- The track list is shared by Subtitles and Audio, so it is renamed to
  LiveTrackPicker / LiveTrackOption.
@github-actions github-actions Bot added the area: android Changes to the Android app or Gradle build label Oct 13, 2026
@silentbil
silentbil requested a review from ProdigyV21 October 13, 2026 19:04
@silentbil
silentbil marked this pull request as draft October 14, 2026 06:18
…dresses

A plain M3U whose guide is only named in its `#EXTM3U url-tvg="..."` line
showed no guide at all once its channels were kept in the SQLite store:

- readCache() returned before restoring the saved guide address when the
  cache held no channels and no guide yet - always the case before the
  first guide load, so the guide could never load.
- The store startup path (getPagedStartupSnapshotOrNull) never restored it.
- Only a typed or Xtream guide counted as an XMLTV source.

The saved address is now restored on those paths; when none was saved the
playlist header is read once per session; a declared guide counts as an
XMLTV source. The visible XMLTV fallback downloads the whole file, and the
same file may not be fetched again for two minutes, so for lists up to
10,000 channels it now keeps every channel's programmes instead of only the
visible rows (the 304 check still uses the requested channels).

Pasted addresses can carry characters trim() keeps, e.g. a trailing NUL
that sent every playlist request to `list.m3u%00` (HTTP 400). Playlist,
guide and Stalker portal/MAC input now drop control and invisible format
characters before use, which also repairs addresses already saved.
Switching which playlist is enabled left the Home Favorite TV row showing
the old playlist's channels until the app restarted: the home load that
follows finds the new channel list still loading, reports the row as
unavailable and keeps it, and nothing rebuilt it once the list was ready.

Home now watches the enabled playlists and portals. When they change it
rebuilds the row as soon as the new list can be read - replaced, or
removed when the new sources have no favourites - and drops a stale row
that still cannot be rebuilt after about a minute. Renames and other
settings do not trigger it.
@silentbil
silentbil marked this pull request as ready for review October 14, 2026 07:05
With the subtitle or audio list open longer than the HUD's 5 s timeout,
the HUD hiding handed focus back to the playback surface. Down then
zapped channels and OK/Back no longer reached the list. The hand-back now
waits while either list is open.
@silentbil silentbil changed the title Live TV: subtitle and audio tracks, stall recovery, HUD fixes Live TV subtitles/audio, IPTV guide and playlist fixes Oct 14, 2026
Broadcast IPTV channels often carry several DVB subtitle streams (for
example Hebrew and Russian). In HLS mode Media3's TsExtractor keys tracks
by stream type and keeps only the lowest PID of each, so only the first
subtitle language ever reached the player - chosen by stream order, not
by the user's language.

IPTV HLS now uses a vendored copy of the media3 1.9.0 TS extractor
(src/main/iptvts-java, same pattern as the vendored Matroska extractor)
whose only change gives each DVB subtitle PID its own HLS track id. HLS
maps text tracks by id, so all languages appear in the Subtitles list.
IptvHlsExtractorFactory routes MPEG-TS segments to it and every other
segment type to Media3's DefaultHlsExtractorFactory, configured exactly
as before. A second audio language is still collapsed by Media3's HLS
audio mapping; out of scope here.
# Conflicts:
#	app/src/main/kotlin/com/arflix/tv/ui/screens/tv/live/LiveTvScreen.kt
@ProdigyV21

Copy link
Copy Markdown
Owner

Thanks for this, it's a useful Live TV improvement, and the playlist/guide fixes plus emulator testing are appreciated. CI is green and the branch merges cleanly, but I found a few cases to address before merging:

  1. Keep DVB subtitle IDs stable across HLS quality switches/discontinuities. In TsExtractor.java:809, the ID now includes the elementary PID. When another rendition carries the same subtitle service on a different PID, it becomes a new track ID. Media3's HLS wrapper discards unknown text IDs after endTracks, so subtitles can stop after the switch. Please use a stable subtitle-service identity and add a test switching between renditions with different PIDs; the current single-segment extractor test doesn't cover this.

  2. Honour the profile defaults fully. resetLiveSubtitleChoice always enables text tracks. With Default subtitle = Off, it also leaves any existing preferred text language intact, so system-caption preferences or forced tracks can still enable subtitles. Please distinguish Off from automatic/Forced selection. The language resolver also returns null for the existing Portuguese (Brazil) option and regional tags such as pt-BR; reuse/support the app's language mapping and cover these defaults in tests.

  3. Scroll the selected picker row into view before requesting focus. LiveTrackPicker.kt:59 attaches the requester only to the selected row but starts the LazyColumn at the top. If that selection is below the initial viewport, its row isn't attached, the one focus request fails silently, and focus can stay behind the overlay. Please initialise/scroll to that row and request focus once it is placed; test a longer track list.

  4. Handle controls that cannot fit on one line. hudCenterGroupX returns minX when maxX < minX, which still overlaps the action group. With the new buttons, narrow/mobile layouts need a compact, wrapping or overflow layout rather than only shifting the playback controls. Please test with Audio, Subtitles, Choose source and Guide all visible, including catch-up mode.

These are code-review findings; I haven't independently tested this build on a physical TV. The feature is worth keeping, but these cases need coverage before calling it fully ready.

- DVB subtitle track ids in HLS no longer come from the PID. The id is
  derived from the subtitle service itself (language, subtitling type,
  composition/ancillary page ids from its descriptor; its position among
  the program's subtitle streams when it has none), so a rendition switch
  or discontinuity that carries the same service on another PID keeps the
  same track id instead of being discarded by the HLS sample-stream wrapper.
- The profile's Default subtitle is applied fully: Off disables text
  tracks, Forced selects only forced tracks, a language selects that
  language. Every case sets the text preferences explicitly, so the
  device caption settings (language and caption roles) no longer turn
  subtitles on or pick another language. The Home channel preview uses
  the same rules; trailers get the player's own defaults back.
- Languages resolve through the app's mapping, now shared with the VOD
  player (util/LanguageCodes.kt, moved unchanged from PlayerViewModel),
  so "Portuguese (Brazil)" and regional tags such as pt-BR work.
- The track picker opens scrolled to the selected row and requests focus
  once that row is laid out; a selection below the first screen used to
  leave focus behind the overlay.
- When time, playback controls and actions cannot share one line, the
  actions take a line of their own above the controls (wrapping further
  if needed) instead of being drawn over them.

Tests: rendition switch with different PIDs, repeated program tables,
Off/Forced/language/pt-BR defaults against device caption settings, and
control-bar placement (unit); a long track list and every HUD button
shown in catch-up mode at narrow and TV widths (device).
@silentbil

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review. All four points are fixed in b5e5084 (the branch is also merged with current main).

  1. Stable DVB subtitle IDs. HLS track IDs for DVB subtitles no longer use the PID. They're derived from the subtitle service: language, subtitling type, and the composition/ancillary page IDs from its descriptor. A service without a descriptor falls back to its position among the program's subtitle streams. A rendition that carries the same service on a different PID now keeps the same track ID. Tests in IptvTsSubtitleTracksTest:

    • two renditions with the subtitles on different PIDs and in a different order produce identical IDs per language;
    • repeated PMTs don't add tracks.
  2. Profile defaults. LiveSubtitleDefault now distinguishes Off (text disabled), Forced (only forced tracks; the default selection flag is ignored) and a language. Every case sets the text preferences explicitly, including clearing the caption role flags, so captioning-manager settings can no longer switch subtitles on or pick another language. The language resolver now uses the app's mapping, moved unchanged from PlayerViewModel to util/LanguageCodes.kt, so "Portuguese (Brazil)" and tags like pt-BR resolve. The Home channel preview uses the same rules; trailers get the player's defaults back. Covered in LiveSubtitlesTest, including starting from captioning-style parameters.

  3. Picker focus. The list starts scrolled to the selected row and requests focus once that row is laid out. Device test: 25 tracks with [FEATURE REQUEST] Trakt Watchlist #20 selected. It fails on the previous code: nothing gets focus.

  4. Controls that don't fit. When time, playback controls and actions can't share a line, the actions move to their own line above the controls, and the action group wraps further if needed. Device tests with Audio, Subtitles, Choose source, Guide and catch-up LIVE all visible check that no two controls intersect, at 480dp and at TV width. The narrow case fails on the previous code: Audio is drawn over Rewind. The placement logic is unit-tested in HudControlBarTest.

Unit tests pass; the new device tests were run on an Android TV emulator (API 30). I also checked on-device: subtitle selection on a channel carrying two DVB subtitle languages, and the TV-width control bar on a real channel.

@ProdigyV21
ProdigyV21 merged commit dd38d5d into ProdigyV21:main Oct 15, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: android Changes to the Android app or Gradle build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants