Skip to content

Live TV: AI subtitles (Gemini, held-back playback) - #875

Open
silentbil wants to merge 8 commits into
ProdigyV21:mainfrom
silentbil:claude/sharp-feynman-u0adtc
Open

silentbil wants to merge 8 commits into
ProdigyV21:mainfrom
silentbil:claude/sharp-feynman-u0adtc

Conversation

@silentbil

Copy link
Copy Markdown
Collaborator

What

AI subtitles for Live TV: translated subtitles for live channels in the profile's subtitle language, using the user's own Gemini key.

Live TV has no "ahead" to read from, so playback is held back a few seconds. The app hears the broadcast before the viewer does, so each line can be translated and shown on time.

How it works

  • Look-ahead hold: a LoadControl wrapper (SubtitleLookaheadLoadControl) keeps playback from starting until enough is buffered. The hold covers one speech segment plus the measured AI answer time plus a margin, clamped to 6–15 s ("Auto"). While it waits, a short "Preparing subtitles… N s" note shows.
  • Tap at extraction: HLS and TS extractors are wrapped to copy audio and text samples as they are loaded, ahead of the playhead. Playback is unchanged.
  • Channel subtitles first: if the channel carries text subtitles (CEA-608 closed captions, or WebVTT and other text renditions), those sentences are translated as text. Picture subtitles (DVB, PGS, VobSub) are never used, including ones Media3 parses into cues during extraction (original type in Format.codecs).
  • Audio otherwise: Silero VAD (already in the app for audio sync) cuts speech into segments of 3.5–5 s. Each segment goes to gemini-3.5-flash-lite as a WAV in one generateContent call, which transcribes and translates. JSON out, minimal thinking.
  • Timing: cues are kept in media time and shown by player position, never by when the answer arrived. Late lines are dropped. Long lines are split.
  • Errors: invalid key, quota (backoff), offline and unsupported-audio states each show a toast; subtitles resume on their own.

Settings (Settings → AI subtitles)

  • AI subtitles for Live TV: main switch. The key is checked when it's switched on, and it uses the same Gemini key as the player's AI subtitles.
  • Auto-translate when a channel starts: otherwise pick "AI subtitles" per channel in the player's subtitle menu.
  • No auto-start on a channel that already has subtitles in the profile language.
  • Both settings sync through the cloud profile like the other AI subtitle settings.

Effect on existing code

The regular (VOD) player behaves as before. Shared code only gained optional parameters with the old defaults:

  • SpeechSegmenter: segment-length parameters.
  • AudioSyncDecoder: thread priority.
  • AudioTapExtractorOutput / AudioTapTrackOutput / JSON helpers in SubtitleTranslationService: made internal so Live TV can reuse them, logic unchanged.

Measured on a Mi Box 4 (English news channel → Hebrew)

  • AI answer time: p50 ~1.7 s, p95 ~2.1 s. No failures and no late lines over several minutes.
  • About 19 requests and ~7k tokens per minute of speech-heavy news, roughly $0.005/min at paid Flash-Lite prices (free tier: no charge).
  • Tap and VAD: about 20–25% of one core.

Tests

LiveCueSchedulerTest, GeminiLiveProtocolTest, Cea608TextAssemblerTest and LiveAiSubtitlesOptionTest are new. All Live TV unit tests pass.

Copies the channel's compressed audio at the extractor (progressive via
AudioSyncExtractorsFactory, HLS via a chunk-extractor wrapper), decodes it on
a side thread and cuts it into speech segments with Silero, logging segment
media times, look-ahead and player period position under tag LiveAiSubs.

Adds SubtitleLookaheadLoadControl, which holds playback until D seconds are
buffered, only while required. Everything is inert unless switched on over
adb in a debug build (debug.arvio.liveaisubs*). No UI and no AI calls yet.

Also makes the tap's output wrappers internal and the segmenter's maximum
segment length configurable.
… playback

Live channels get AI subtitles in the viewer's language that appear on time:
the channel's audio is copied as it downloads (extractor tap), cut into
speech segments with Silero, and each segment is sent to Gemini, which
transcribes and translates it in one call. Lines are timed from the
segment's own media time and shown by the player position, while
SubtitleLookaheadLoadControl holds playback back so answers arrive first.

- Pipeline skips its backlog when analysis falls behind playback (busy boxes)
- Segments end at speech pauses after 3.5 s, hard limit 5 s
- Up to 2 requests in flight, applied in order; 429 backoff, invalid key,
  offline and unsupported-audio states with toasts
- Delay Auto (default, from measured latency) / 6 / 10 / 15 s; "Preparing
  subtitles" while held; pause-to-build when turned on mid-channel
- Settings (AI subtitles section): enable with key check, delay,
  activation per channel / always, target language, own Gemini key when
  the AI subtitles model is not Gemini; cloud synced
- Subtitle picker gets an "AI subtitles" entry, remembered per channel
- Overlay uses the profile's subtitle style; hidden debug panel via
  debug.arvio.liveaisubs.overlay
- Strings in en, iw, de, es, pt, pt-BR; unit tests for scheduler, delay
  math, Gemini JSON parsing and the picker option
Picking "AI subtitles" turns them on for that channel even when the
settings switch is off; the switch now only makes channels start with them
(together with Always on).
…m the start

- Settings are now just the feature switch and "auto-translate when a
  channel starts"; the delay is always measured, the language is the
  profile's subtitle language and the key is the AI subtitles Gemini key.
- Switching to AI subtitles while a channel plays restarts the stream, so
  the audio that was already buffered is subtitled too; "Preparing
  subtitles" shows during the hold and until the first line arrives.
- Audio is captured whenever AI subtitles are on for the channel (mini
  player included), so the lines for the start-up hold are not lost.
Once playback is held back, every coming line is translated in time, so the
extra note after the hold only misled during quiet stretches.
…udio as fallback

- Tap text tracks next to audio: CEA-608 closed captions and parsed text renditions
  (WebVTT etc.) become sentences that are translated as text; audio is only sent
  for stretches the captions do not cover.
- Picture subtitles (DVB, PGS, VobSub) are never used, including ones Media3 parses
  into cues during extraction (original type in Format.codecs).
- Auto-translate does not start on a channel that already has subtitles in the
  profile language.
The hidden look-ahead/latency panel (debug.arvio.liveaisubs.overlay) and its counters
were for measuring on a device; the measured AI latency still drives the Auto delay.
@github-actions github-actions Bot added the area: android Changes to the Android app or Gradle build label Nov 6, 2026
…t run, keep catch-up position

- Tag audio and caption work with the channel source it was captured from and drop
  stale work before sending, so an earlier channel's lines never reach the next one.
- When AI cannot run (no or rejected key, undecodable audio) skip the look-ahead hold
  and show the channel's own subtitles; during a quota or offline pause show them too.
- Do not restart the stream when AI is switched on during catch-up.
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.

1 participant