No description
  • Kotlin 96.3%
  • Java 1.8%
  • Python 0.7%
  • C 0.5%
  • C++ 0.5%
  • Other 0.2%
Find a file
Adam Rosenbaum df943aae7f fix(sync): resolve post-merge compile errors from the upstream/dev sync
- MainActivity: our email-first onboarding adapted to upstream's if/else-if/else
  startup-routing (set startupDestination = Setup, drop the old return@Surface).
- MetaDetailsViewModel: drop our release-date enrichment block (upstream removed the
  settings.useReleaseDates toggle); the #DUPE# TheTVDB year is now preserved by default
  since nothing overwrites releaseInfo.
- StreamScreenViewModel: progressive-poll getStreamsFromAddon now takes upstream's
  `addon: Addon` param instead of `baseUrl`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-11-03 08:12:13 -07:00
.github Merge remote-tracking branch 'upstream/dev' into sync/upstream-20261102-213255 2026-11-03 08:11:20 -07:00
.idea Merge upstream/dev (0.5.6) into feature/collections 2026-04-02 10:47:10 -07:00
.kotlin/errors modern layout refact 2026-02-25 03:35:43 +05:30
app fix(sync): resolve post-merge compile errors from the upstream/dev sync 2026-11-03 08:12:13 -07:00
assets logo update 2026-02-15 13:44:43 +05:30
baselineprofile docs(benchmark): document the profile regeneration recipe 2026-11-02 15:01:35 -03:00
docs feat(library): support mdblist watchlist and static lists 2026-09-06 22:25:35 +05:30
DV7/libdovi feat: DV7 Profile 7 to 8.1 conversion and custom playback buffer engine 2026-05-28 08:40:26 -04:00
ffmpeg-decoder-downmix Add LFE level option for FFmpeg downmix 2026-10-29 13:56:37 +00:00
gradle feat(network): connect to realtime for watch parties 2026-10-13 14:53:21 +03:00
libmpv-android@f48aa82b26 feat: adding 3h ttl cache for yt trailers 2026-03-27 18:05:07 +05:30
org/libtorrent4j/swig Fix cache being cleared on exit by correcting DataStore fallback from true to false 2026-04-03 13:09:26 +02:00
scripts Merge remote-tracking branch 'upstream/dev' into sync/upstream-20261102-213255 2026-11-03 08:11:20 -07:00
.gitignore Update .gitignore 2026-09-23 09:19:21 +02:00
ACCESSIBILITY_HANDOVER.md docs: record the October 4 accessibility handover 2026-10-17 14:39:57 -05:00
build.gradle.kts Add Sentry and remove QR anonymous auth 2026-07-03 23:59:14 +05:30
compose_stability_config.conf Perf improvements including offloading IO ops to IO coroutines, enhanced scrolling awareness 2026-05-01 14:44:11 +01:00
CONTRIBUTING.md update CONTRIBUTING.md 2026-09-20 13:43:15 +05:30
gradle.properties chore: drop non-accessibility changes from the a11y PR 2026-10-18 17:53:54 -05:00
gradlew init 2026-01-29 13:03:50 +05:30
gradlew.bat init 2026-01-29 13:03:50 +05:30
LICENSE added license 2026-02-05 18:12:29 +05:30
local.example.properties Merge branch 'dev' into feat/mdblist 2026-09-21 12:28:25 +05:30
README.md Merge upstream/dev into custom (479 commits) — keep our fixes 2026-08-30 19:18:46 -06:00
release_test_cases.csv Test cases csv file 2026-02-05 18:00:44 +05:30
settings.gradle.kts Use nuvio-libass AAR from NuvioMedia/nuvio-libass v0.0.1 2026-10-21 19:28:07 +02:00

Nuvio

A free, open-source media app for your phone, your desktop, and the TV you already own.
Bring your own sources. Nuvio turns them into a library with artwork, ratings, subtitles, and your place saved on every screen.

Website · GitHub releases · Support Nuvio

Get Nuvio TV

It acts as a client-side playback interface that can integrate with the Stremio addon ecosystem for content discovery and source resolution through user-installed extensions.

Built with Kotlin and optimized for a TV-first viewing experience.

Ecosystem

This custom fork is the on-screen client for a self-hosted stack built to run together:

Project Role
NuvioTV (this repo, custom) Android TV client. Cinemeta #DUPE# reconstruction, episode/season/series scope search, a live-ticking EPG panel, and player fixes (DV/MKV, CEA-608 captions, live-HLS recovery).
AIOStreams On-demand movie/series aggregator — torrents (Torrentio/Comet) + a self-hosted Prowlarr → NZBGeek usenet pipeline, all through TorBox. Its fork patch resolves #DUPE# server-side (the search-side complement to this app's display-side reconstruction).
usa-tv-next Live US TV addon (~293 channels, merged multi-source EPG) that emits the epgSchedule field this app's guide panel renders.
Comet (fork) Torrent-scraper addon (currently paused).

Install AIOStreams + usa-tv-next as addons in this client to get the full stack (on-demand + live TV). Both #DUPE# layers are intentionally symmetric: this app fixes the displayed title/art/episodes, AIOStreams fixes the search query. Supporting infra (Prowlarr + NZBGeek, TorBox, Caddy) runs on a single VPS.

This fork

This repository is a fork of tapframe/NuvioTV, maintained to pair with the usa-tv-next live-TV Stremio addon (293 US channels; a merged multi-source EPG — Schedules Direct → epg.pw → epgshare01/i.mjh.nz).

Branches

Branch Purpose
custom All our modifications, on top of upstream tapframe/NuvioTV dev. Build releases from here.

Syncing upstream (merge, not rebase — custom is pushed):

One command does the whole dance below — fetch upstream, merge onto a throwaway sync/upstream-<date> branch, list any conflicts, and compile-check a clean merge (it never touches custom until you fast-forward it yourself):

scripts/sync-upstream.sh            # JAVA_HOME must point at a JDK 17–21 for the compile check
# then, once green:
git switch custom && git merge --ff-only sync/upstream-<date> && git push origin custom

A Upstream sync check GitHub Action (.github/workflows/upstream-sync.yml) also opens/updates a single tracking issue whenever upstream/dev gets ahead, with the new-commit list and a clean/conflict probe — so a sync never gets missed. (GitHub only fires schedule from the default branch; set custom as default, or trigger the workflow manually.)

Or do it by hand:

git remote add upstream https://github.com/tapframe/NuvioTV.git   # one-time
git fetch upstream
git switch custom && git switch -c sync-dev      # throwaway test branch
git merge upstream/dev                           # resolve any conflicts, then build/test
git switch custom && git merge --ff-only sync-dev && git push origin custom

Use merge (not rebase) so the pushed custom history isn't rewritten. The conflict surface is small — our changes are localized to the files listed below (the last sync of 52 upstream commits merged with zero conflicts).

custom modifications

  • Focus-reactive, live EPG guide panel on the stream-selection screen. As focus moves between a channel's feeds, the left panel shows that stream's program guide (defaults to the first stream). It recomputes NOW/NEXT on a 30s clock from a new epgSchedule field (absolute-time window emitted by usa-tv-next) so it stays live and self-corrects even from a cached response; it falls back to the epg now/next string, then description. Focus reads are deferred into the panel composable so hovering recomposes only the panel.
  • Live channel detail screen. HeroSection recomputes NOW PLAYING / UP NEXT / today's schedule from meta.epgSchedule on the same 30s clock, formatted identically regardless of EPG source. Both screens share one formatter — core/util/EpgGuide.kt (the single source of truth).
  • CEA-608/708 closed captions for HLS. Re-streamed live channels carry captions muxed in-band with no declared rendition; PlayerMediaSourceFactory routes HLS through HlsMediaSource.Factory with DefaultHlsExtractorFactory(exposeCea608WhenMissingDeclarations=true), and PlayerRuntimeControllerTracks labels them "Closed Captions [CCn]" so they're selectable.
  • Email/password sign-in re-enabled on TV (AuthSignInScreen rebuilt via AccountViewModel), with first launch defaulting to the email screen + a QR toggle (MainActivity, NuvioNavHost, Settings→Sign-in route).
  • Keyless TheTVDB metadata layer + Cinemeta #DUPE# reconstruction. Cinemeta de-dupes shows that have multiple regional IMDb entries (e.g. Mayday / Air Crash Investigation / Air Disasters) by renaming the duplicate to the literal #DUPE# (slug <type>/dupe-<id>) and leaving a half-broken entry whose own IMDb id is dead at TMDB/IMDb — so it appears nameless with the canonical sibling's data leaking in (wrong title "Mayday", year 2003, country "Canada", 404'd metahub episode thumbnails). core/tvdb/TvdbMetadataService recovers the rightful entry with no API key by scraping thetvdb.com (the dupe's Cinemeta meta still carries a tvdb_id): name + year + current poster/background + country/language/status/genres/runtime from the series page, and the full season/episode tree (with screenshots) from the all-seasons page. On both the catalog tile (CatalogRepositoryImpl) and the detail screen (MetaDetailsViewModel.enrichMeta) the entry is rebuilt from TheTVDB — re-fetching from Cinemeta when another meta addon masks the dupe — and the TMDB enrichment is guarded so it can't re-clobber the regional facts; episode thumbnails fall back to the series backdrop where TheTVDB has none. A metahub clearlogo fallback (by IMDb id) also restores stylized hero title-treatments when an addon serves no logo. Threaded through a new tvdb_id field on MetaResponseDto → Meta → MetaMapper.
  • Full episode-description overlay. Episode-card descriptions truncate with …; the episode long-press menu now has a View full description action that opens the untruncated text in a D-pad-scrollable overlay. Extracted the hero's scrollable-synopsis pattern into a reusable ui/components/ScrollableDescriptionDialog.
  • "Exit behavior" settings (fully close the app). Settings → Layout → Exit behavior adds two toggles (both off by default): Fully close on Back and Close when Home is pressed. When on, the app removes its task and kills its process (MainActivity.fullyExit()) instead of staying cached — Home is hooked via onUserLeaveHint(), guarded against external-player handoffs. Backed by LayoutPreferenceDataStore (exitAppOnBack / exitAppOnHome).
  • Files touched: StreamScreen.kt, HeroSection.kt, Stream.kt/Meta.kt, the *Dtos + mappers, core/util/EpgGuide.kt, PlayerMediaSourceFactory.kt, PlayerRuntimeControllerTracks.kt, AuthSignInScreen.kt, MainActivity.kt, NuvioNavHost.kt, core/tvdb/TvdbMetadataService.kt, core/tmdb/DupeTitleResolver.kt, data/remote/api/TvdbWebApi.kt, CatalogRepositoryImpl.kt, MetaDetailsViewModel.kt, EpisodesSection.kt, ui/components/ScrollableDescriptionDialog.kt, core/di/NetworkModule.kt.

Installation

Android TV

Download the latest APK from GitHub Releases and install on your Android TV device.

Development

Prerequisites

  • Android Studio (latest version)
  • JDK 17–21 (the bundled Android Studio JBR 21 at …/Android Studio/jbr works; JDK 26 is not yet supported by this Gradle/AGP and will crash the build). Point JAVA_HOME/Gradle JDK at a 17–21 JDK.
  • Android SDK (API 29+)
  • Gradle 8.13 / AGP 8.13.2 / Kotlin 2.3.0 (via the wrapper)

Setup

# This fork (build from the `custom` branch)
git clone https://github.com/ConfidentlyIncorrect/NuvioTV.git
cd NuvioTV
git checkout custom

# Optional: track upstream for syncing
git remote add upstream https://github.com/tapframe/NuvioTV.git

Local config (gitignored — never commit)

File Keys Notes
local.properties sdk.dir, TRAKT_CLIENT_ID, TRAKT_CLIENT_SECRET Trakt creds bake into BuildConfig at build time. Create a Trakt API app at https://trakt.tv/oauth/applications/new with Redirect URI urn:ietf:wg:oauth:2.0:oob (leave JavaScript/CORS origins blank — device-code flow). Empty = "missing client id/secret".
local.dev.properties SUPABASE_URL, SUPABASE_ANON_KEY, TV_LOGIN_WEB_BASE_URL Supabase auth config used by the email/browser sign-in path.
nuviotv.jks — Signing keystore (the debug build uses the release signing config). Generate a self-signed key matching the baked-in alias/passwords if missing.

Full Debug Build

./gradlew :app:compileFullDebugKotlin
./gradlew :app:assembleFullDebug --no-watch-fs   # produces app/build/outputs/apk/full/debug/*.apk

Build note: packaging occasionally fails with a flaky IncrementalSplitterRunnable error. The Kotlin/codegen still compiled — just re-run with filesystem watching disabled: ./gradlew :app:packageFullDebug --no-watch-fs. APK splits are produced per-ABI (app-full-arm64-v8a-debug.apk for most Android TV boxes) plus a universal APK.

Running on Emulator or Device

License

GNU General Public License v3.0