Favourites and stream profiles were refreshed inside a bare runCatching,
so when they failed nothing was logged and the app just showed a stale
cached list. An expired login now always raises the re-login screen, even
when cached channels could still be displayed, and the settings gained a
"Senderliste neu laden" row for a manual retry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolving crests from Wikipedia at runtime never produced a visible badge
on the TV and left nothing in the log to debug, so the shield fallback was
all anyone ever saw. They are now downloaded once at build time by
tools/fetch-crests.py into assets/crests (git-ignored, so no trademarked
artwork is committed) and loaded from there — instant, and independent of
the network.
CI: accepting SDK licences by piping "yes" into sdkmanager died of SIGPIPE
(exit 141) under pipefail; the licence hashes are written directly now.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The start-up check already ran, but its result only showed as a badge for
whoever happened to open the settings. A chip in the top bar now says
"Update <version>" and leads straight there, and the check repeats every
six hours because a TV keeps the same process alive for days.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Crests are resolved from the German Wikipedia page summary at runtime and
cached per device, so nothing copyrighted is stored in the repository. The
coloured shield remains as the fallback while the badge loads or when a
club has none.
The add dialog crashed for the same reason the rail did: it focused a row
that only exists when something is preselected, and adding a club has no
preselection. It now also scrolls, which 56 clubs rather need.
CI: this Gitea resolves bare action names against itself, so the workflow
now uses full GitHub URLs, and it runs the unit tests too.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The club shortcut now offers every club of the first three German
divisions instead of two hard-coded ones. Up to three can be active at
once; benjamin and tobiasb still start with Hansa and the VfB switched on.
Also fixes a crash on the left jump out of the channel list: the jump
targeted a FocusRequester bound to the selected rail row, and a row that
is scrolled out of a LazyColumn is not composed, so requesting focus on it
threw. Jumps are now attempted and fall back to normal focus movement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds an optional rail entry per club that lists every channel whose EPG
mentions the club within the next three hours, with the matching kick-off
instead of the usual Now/Next. Only sport and free-to-air groups are
scanned (NFL excluded), so the scan stays cheap and quiet.
Which clubs appear is a per-TV setting: the signed-in viewer's own club is
on by default, and every club can be switched on or off under Einstellungen
-> Vereinsmenüs, so a household can follow more than one.
Crests are trademarks, so the icon is a plain shield in the club colours.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Updater and README point at the new path. The old one redirects, but the
updater should not depend on that. Also drops a stale GitHub-specific
Accept header.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rail opens whatever gets focused, so when the player closed and focus
landed on "Alle Sender", the view silently switched away from the group
the viewer had been browsing. A restore flag now suppresses that
auto-select until focus has been placed on the channel that was watched.
Completes #13.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Control server security and robustness:
- Socket read timeout, handshake deadline and a client cap so an idle or
hostile connection can no longer pin threads forever (#1)
- Per-address rate limiting that counts every failed hello, Origin
checking on the upgrade, and a separate revocable session token for
code-authenticated clients so the guessable path no longer yields the
QR credential (#2)
- Credentials excluded from cloud backup and device transfer, constant
time comparisons, and a pairing reset in the settings (#3)
- Playlist fetches restricted to http(s), capped at 24 MB and bounded by
an overall transfer deadline (#4)
- Port conflicts and MediaSession id collisions no longer crash the app;
the remote degrades to unavailable with a plain-language note (#11)
Player:
- Seeking no longer collapses to position 0 when the duration is unknown
(#5)
- Pause acts on playWhenReady, so pausing during a stall works and
playback cannot resume in the background after leaving the app (#6)
- Playback failures stay on screen with a retry action instead of
silently dropping back to the list (#7)
- Reconnects are spaced 1s/3s/8s and re-entering the channel just closed
waits out a short grace period, which is what the provider needs to
release the previous session (#12)
Channel list and remote:
- Leaving playback returns to the channel the viewer came from (#13)
- The remote only rebuilds its list when the data changed, never
overwrites a focused input and carries indices instead of scanning (#8)
- Pairing retry reconnects properly, resets its backoff and validates the
code before spending an attempt (#9)
- M3U parsing keeps commas in names, strips a BOM and rejects payloads
that are not playlists, covered by unit tests under tests/ (#10)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Releases API for the version check; the APK itself is served from the
orphan 'apk' branch of the public repo (asset uploads need a token,
raw files do not). Transitional GitHub release carries this change so
existing installs hop over.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Rows carry the Dispatcharr channel logo in a small tile (initials as
fallback), sit on a surface instead of transparent black, and get the
accent focus border; the group subline is gone — the logo carries
channel identity.
- Focus scale disabled on settings rows, picker options, rail items and
channel rows: the grown surface got clipped by its parent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Quelle and Stream-Qualität move out of Konto into their own
Wiedergabe card; Konto is account + Abmelden only.
- Choices open a TV-style picker dialog (focus starts on the current
value, Back closes) with the current value + drawn caret shown at
the row's trailing edge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- SegmentedRow lays label above wrapping option chips instead of
squeezing them into the trailing slot (long profile names collapsed
the label and blew the pill up into a blob).
- Gear icon: thick ring + stubby teeth instead of sun rays.
- Player overlay filters EPG placeholders and next==now duplicates
like the channel list.
- Updater: startActivity from Dispatchers.Main — firing it from the IO
dispatcher stalled the first install attempt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Settings page follows the Google TV pattern: Konto / App / Handy-
Fernbedienung cards, each row a full-width focus target with label,
subtitle and trailing value.
- Quelle and Stream-Qualität are segmented controls switched with
left/right inside the focused row (click cycles).
- Account row with initial avatar, Abmelden as red row, update row
with version badge; Update installieren only when one is available.
- Top bar: drawn gear icon button replaces the text pill.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The key-down that opens the overlay is consumed by the activity, but its
key-up arrived after the play button grabbed focus and clicked it —
instant pause right after the overlay appeared. The overlay now swallows
select-releases whose press it never saw.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Number keys type the stable channel number (position in Alle Sender,
shown identically in every view) with a live feedback badge; commits
after a short pause or as soon as no longer number can match.
- EPG cell is a left-aligned column with a fixed leading edge: title +
time range, progress under the title, next line only when it differs;
placeholder programmes (kein Programm / no information) count as no
EPG data. Star hidden in the favorites view.
- Player overlay: no LIVE dot (reads as recording) and no status word
while playing; state text only for paused/buffering/error.
- Settings pill gets a resting surface so it reads as a target.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Groups live in a scrollable rail on the left (Alle Sender, Favoriten,
then Dispatcharr groups with counts); focusing one opens it like a
menu — filters no longer stack additively.
- Channel list sits right of the rail with Now/Next and progress;
group subline only shown in the Alle view.
- Top bar: wordmark plus one context pill (Einstellungen / Zurück zu
Live) instead of two text tabs; Back returns to Live instead of
quitting the app.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OK now opens the overlay like every TV player; its buttons are ordinary
focusable pills (play/pause, stop, audio-track cycle). Back closes the
overlay first, then stops. Auto-hide timer restarts on any interaction.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Overlay shows current programme (title, time range, EPG progress) and
what's next; resurfaces briefly on programme change, hides after 5s.
- Audio tracks: listed from the stream, switchable via D-pad left/right
on live streams (seek keeps left/right on seekable media), via chips
in the overlay and from the phone remote (set_audio message).
- Main UI font switches to Inter; Space Grotesk stays for the wordmark.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The live proxy resolves /proxy/ts/stream/<key> by channel uuid (or stream
hash) — integer ids 404, so every Dispatcharr channel failed to play.
Streams now use the uuid, channels use effective_name/effective_tvg_id and
skip hidden_from_output entries.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Compose for TV scaffold: D-pad app shell (Live/Einstellungen), channel list
with focus handling, fullscreen player with auto-hiding overlay; zapping via
channel/D-pad keys (closes#2)
- Generic source end-to-end: M3U + XMLTV configurable on the TV, Now/Next with
progress in the list, channel cache (closes#3)
- OIDC device-flow login: server URL is the only input, issuer/client id come
from the fork's status endpoint; QR + user code screen, silent refresh,
logout (closes#4)
- Dispatcharr source via Bearer API: channels, groups, EPG grid; switchable
against the generic source (closes#5)
- Per-user favorites: star via long-press, favorites filter, backend-synced
(closes#6)
- Stream profile selection (Standard/Passthrough/audiofix/720p from the
backend profile list), applied per stream URL (closes#7)
- Phone remote: Now/Next lines, favorites star + filter over the WebSocket
protocol (closes#8)
Device verification pending (TV currently in use).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ControlServer.Listener declared onPause()/onResume(); implementing them in
MainActivity overrode Activity.onPause/onResume without super calls, so the
first resume died with SuperNotCalledException. Renamed to onPauseCast/
onResumeCast (matching onStopCast). Also drop R8 optimize variant.
Verified on a Sony Bravia (Android TV 14). Version 0.1.1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Package/applicationId com.nodecast.tv -> dev.castarr.tv, all branding in app
and remote page renamed, remote localStorage key changed. Foojay toolchain
resolver + jvmToolchain(17) so hosts with only a JRE can build.
Closes#1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Imported the native TV app (Kotlin, ExoPlayer, embedded remote server, QR
pairing) plus CONTEXT.md and ADRs 0001-0005. Rename, Compose for TV UI and
the Dispatcharr integration follow as tracked issues.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>