302 lines
14 KiB
Markdown
302 lines
14 KiB
Markdown
# Memby (Android TV)
|
||
|
||
An independent Android TV client for Emby, by **ponzischeme89**. Memby combines a
|
||
personalised television launcher, full media player, system screensaver and an optional
|
||
self-hosted gateway that shapes the experience for every viewer in the household.
|
||
|
||
Source: [g.sublogue.com/admin/memby](https://g.sublogue.com/admin/memby)
|
||
|
||
## Features
|
||
|
||
### Browse and discover
|
||
|
||
- Personalised, server-composed Home rows with a cached instant-start launcher.
|
||
- A cinematic featured hero that rotates through ranked films and series premieres.
|
||
- Continue Watching merged with Next Up, ordered by the viewer's recent activity.
|
||
- Favourites, latest films, recommendations, curated shelves and schedule rows.
|
||
- Dedicated Movies, TV Shows, Favourites and For You destinations.
|
||
- Instant search with an on-screen keyboard, voice input and session result caching.
|
||
- Optional, server-controlled genre browsing with paged film and series catalogues.
|
||
- A dedicated For You page combining viewing history, browsing signals, available time
|
||
and direct-play evidence; every recommendation explains why it was selected.
|
||
- More Like This, cast and technical details on film and series pages.
|
||
- Per-title IMDb, Rotten Tomatoes, Metacritic, Letterboxd and TMDb ratings where available.
|
||
- My Shows tracking, upcoming episodes, lifecycle badges and personal return alerts.
|
||
- Themes, seasonal colour schemes and optional launcher decorations.
|
||
|
||
### Playback
|
||
|
||
- Media3/ExoPlayer playback with direct play, compatible-stream remuxing and HLS fallback.
|
||
- Resume playback, progress reporting, watched state and immediate poster badge updates.
|
||
- Audio and subtitle track selection with per-viewer language preferences.
|
||
- Missing-subtitle search and download through Bazarr or OpenSubtitles when configured.
|
||
- Configurable Left/Right seeking with trickplay thumbnail previews.
|
||
- Skip-intro prompts or automatic skipping from Emby's chapter markers.
|
||
- Automatic next-episode playback with an on-screen countdown.
|
||
- Closing-credits presentation with the next episode alongside and adaptive 2× playback.
|
||
- Episode pages with season navigation, watched markers and estimated completion dates.
|
||
- In-player cast lookup, pause artwork, time-remaining reminders and service-status banners.
|
||
|
||
### Profiles, televisions and administration
|
||
|
||
- Multiple saved viewer profiles with quick switching from the navigation rail.
|
||
- Per-viewer settings synchronised through the gateway across every signed-in television.
|
||
- Device management, session revocation and stable television identity.
|
||
- Gateway-controlled feature flags, maintenance mode, forced app updates and live themes.
|
||
- Service alerts for new library items, aired episodes, deployments and Emby outages.
|
||
- A dependency-free web console for accounts, devices, settings history, features,
|
||
updates, library imports, recommendations, searches, row engagement and logs.
|
||
- Postgres catalogue import, Redis caching, full-text search and background recommendation
|
||
and ratings warming.
|
||
|
||
### Screensaver and updates
|
||
|
||
- A system `DreamService` screensaver with cross-fading library backdrops.
|
||
- Interactive screensaver controls for playback, favourites and artwork navigation.
|
||
- An in-app screensaver preview using the same rendering surface as the system Dream.
|
||
- Self-hosted signed APK updates with SHA-256, package and signing-key verification.
|
||
- Mandatory and optional update policy controlled by the gateway.
|
||
|
||
## Tech stack
|
||
|
||
- Kotlin + Jetpack **Compose for TV** (`androidx.tv:tv-material3`)
|
||
- **Media3 / ExoPlayer** for playback
|
||
- Retrofit + OkHttp + kotlinx.serialization for the Emby REST API
|
||
- DataStore for persisted profiles, settings and cached launcher state
|
||
- Coil for backdrop image loading
|
||
- Go gateway with Postgres and Redis
|
||
|
||
## Project layout
|
||
|
||
```
|
||
app/src/main/java/com/ponzischeme89/memby/
|
||
MembyApp.kt Application; initialises the ServiceLocator
|
||
ServiceLocator.kt Manual DI (settings + repository)
|
||
data/
|
||
SettingsStore.kt DataStore-backed connection/session state
|
||
ServerConfig.kt Which backend this build talks to
|
||
EmbyRepository.kt Content, favourites, playback; dual gateway/direct paths
|
||
analytics/RowAnalytics.kt Row engagement buffering
|
||
model/ Emby + gateway DTOs (kotlinx.serialization)
|
||
remote/EmbyApi.kt Retrofit interface for Emby
|
||
remote/GatewayApi.kt Retrofit interface for the Memby gateway
|
||
ui/
|
||
MainActivity.kt Setup, profiles, home
|
||
HomeViewModel.kt Home state, row analytics, refresh
|
||
HomeComponents.kt Navigation rail, rows, cards
|
||
MaintenanceScreen.kt Full-screen offline state
|
||
settings/SettingsSheet.kt Settings panel
|
||
screensaver/ Shared slideshow + in-app preview host
|
||
player/PlayerActivity.kt Media3 playback
|
||
screensaver/MembyDreamService.kt System screensaver (hosts ScreensaverContent)
|
||
|
||
server/ The Memby gateway (Go) — see server/README.md
|
||
```
|
||
|
||
## Build & install
|
||
|
||
You need **JDK 17** and the **Android SDK** (Android Studio bundles both).
|
||
|
||
Open the folder in Android Studio (Giraffe/Koala or newer) and let it sync, **or** from a
|
||
terminal:
|
||
|
||
```powershell
|
||
# Android Studio writes local.properties automatically. If building from the CLI,
|
||
# point it at your SDK first:
|
||
"sdk.dir=C:\\Users\\<you>\\AppData\\Local\\Android\\Sdk" | Out-File -Encoding ascii local.properties
|
||
|
||
.\gradlew.bat assembleDebug # build the APK
|
||
.\gradlew.bat installDebug # install to a connected Android TV / emulator
|
||
# Preferred for a TV already showing the Dream: clears Memby, installs, then reopens
|
||
# it so the old render surface cannot remain black. Pass -Serial when more than one
|
||
# device is attached.
|
||
.\deploy-debug.ps1 -Serial 192.168.20.3:41479
|
||
```
|
||
|
||
The APK lands in `app/build/outputs/apk/debug/app-debug.apk`.
|
||
|
||
## Two ways to run
|
||
|
||
The client can talk to Emby directly, or through the **Memby gateway** — a Go service in
|
||
`server/` that runs in Docker alongside Postgres and Redis and owns auth, caching, search
|
||
and the shaping of TV screens. With a gateway the launcher is one request instead of four,
|
||
and the TV holds a revocable gateway token rather than a live Emby token.
|
||
|
||
```
|
||
direct: TV ──────────────────────────────► Emby
|
||
gateway: TV ──► Memby gateway ──► Emby (metadata + artwork)
|
||
TV ─────────────────────► Emby (video stream; never proxied by the gateway)
|
||
```
|
||
|
||
Which one a build uses is decided by `memby.gatewayUrl` in `gradle.properties`: set it and
|
||
the app is a thin client; leave it blank and nothing changes from the direct path below.
|
||
See [`server/README.md`](server/README.md) to run the container.
|
||
|
||
The gateway also imports Emby's catalogue into Postgres (once manually, then hourly for
|
||
new episodes), composes the home rows — including "Recommended from your watching
|
||
history" — and has an admin page at `/admin/` for imports, an offline switch, and
|
||
per-row engagement.
|
||
|
||
For Tracearr-powered recommendations, create a read-only public API key in Tracearr and
|
||
set `MEMBY_TRACEARR_URL` plus `MEMBY_TRACEARR_API_KEY` on the gateway (and optionally
|
||
`MEMBY_TRACEARR_SERVER_ID` when Tracearr monitors several servers). Tracearr credentials
|
||
remain server-side; the Android app only receives ranked Emby items and short reasons.
|
||
The gateway imports compact session signals into Postgres every five minutes and prepares
|
||
an over-provisioned per-user pool, so opening For You normally performs one indexed
|
||
database read. The live on-demand path remains available for cold starts and failed
|
||
rebuilds.
|
||
|
||
## Server address
|
||
|
||
Memby is built for one Emby server, so the address is baked into the APK instead of being
|
||
typed on a TV remote. Set it in `gradle.properties`:
|
||
|
||
```properties
|
||
memby.serverUrl=http://192.168.1.10:8096
|
||
```
|
||
|
||
It can also come from `~/.gradle/gradle.properties` (keeps it out of the repo) or a single
|
||
build: `.\gradlew.bat assembleDebug -Pmemby.serverUrl=http://192.168.1.10:8096`.
|
||
|
||
The value becomes `BuildConfig.EMBY_SERVER_URL`, read through
|
||
`data/ServerConfig.kt`. When it is set, the setup screen only asks for a username and
|
||
password, and the address wins over whatever a saved session recorded — so moving the
|
||
server is a property change plus a reinstall, with no user action. Leaving the property
|
||
**blank** restores the original behaviour: users type the address themselves.
|
||
|
||
## First run
|
||
|
||
1. Launch **Memby** from the Android TV launcher.
|
||
2. Sign in with your Emby username and password. (If the build has no hardwired server,
|
||
enter its address first, e.g. `http://192.168.1.10:8096`.)
|
||
3. **Preview screensaver** to test it, or **Set as system screensaver** to open the TV's
|
||
screensaver settings and choose "Memby Screensaver".
|
||
|
||
## Distributing builds
|
||
|
||
Memby is handed out as an APK from your own web server or NAS, and updates itself from
|
||
the same folder.
|
||
|
||
### One-time: a signing key
|
||
|
||
Android identifies an app by `applicationId` **plus signing key**. Every update must be
|
||
signed with the *same* key, or the TV rejects it as a different app. Lose the key and
|
||
every user has to uninstall and reinstall.
|
||
|
||
```powershell
|
||
keytool -genkeypair -v -keystore memby-release.jks -alias memby `
|
||
-keyalg RSA -keysize 4096 -validity 10000
|
||
```
|
||
|
||
Keep the `.jks` somewhere backed up and **outside the repo** (`*.jks` is gitignored), then
|
||
point `local.properties` at it — also gitignored:
|
||
|
||
```properties
|
||
memby.keystore=C:/keys/memby-release.jks
|
||
memby.keystorePassword=…
|
||
memby.keyAlias=memby
|
||
memby.keyPassword=…
|
||
```
|
||
|
||
Without these, `assembleRelease` still builds but the APK is unsigned and will not
|
||
install. The build prints a warning saying so.
|
||
|
||
### Docker release builder (recommended)
|
||
|
||
The backend Compose project includes an isolated `memby-builder` service. It keeps the
|
||
Android SDK out of the gateway image, fetches the newest semantic GitHub tag, builds with
|
||
the tagged source's Gradle wrapper, signs with the existing externally mounted keystore,
|
||
verifies the APK and publishes it into the gateway's existing release volume and update
|
||
policy. Admin Console → App updates provides a **Build latest release** button, optional
|
||
exact-tag fallback, required-update confirmation, live status and build output.
|
||
|
||
After the one-time secret-file setup in [`builder/README.md`](builder/README.md), use the
|
||
Admin Console button. The Linux/NAS command-line fallback remains:
|
||
|
||
```sh
|
||
docker compose run --rm --build memby-builder release
|
||
```
|
||
|
||
The signed APK and its checksum are stored in the `memby-releases` volume at
|
||
`/data/releases/memby-<version>.apk` and
|
||
`/data/releases/memby-<version>.apk.sha256`. Gradle downloads are retained in the
|
||
`memby-gradle-cache` volume. The keystore and credentials remain read-only Compose secrets
|
||
under `/run/secrets` and are never copied into the image or repository.
|
||
|
||
### Local PowerShell release (compatibility path)
|
||
|
||
```powershell
|
||
.\release.ps1 -Version 0.1.54 -Notes "Faster home screen" `
|
||
-BaseUrl https://nas.example.com/memby `
|
||
-SourceUrl https://g.sublogue.com/admin/memby
|
||
```
|
||
|
||
That existing workflow still bumps `versionName`/`versionCode`, runs the tests, builds a
|
||
signed APK, and fills
|
||
`dist/out/` with:
|
||
|
||
```
|
||
index.html the page people are sent to
|
||
latest.json the update manifest the app polls
|
||
memby-0.1.54.apk the build
|
||
memby-0.1.54.apk.sha256 the SHA-256 checksum
|
||
LICENSE GNU GPL v2 terms
|
||
NOTICE copyright and third-party acknowledgements
|
||
```
|
||
|
||
Copy those to the folder the NAS serves. Old APKs can stay alongside — only `latest.json`
|
||
decides what the app offers, so rolling back is editing one file.
|
||
|
||
### How TVs update themselves
|
||
|
||
The gateway checks the running app version on launch and returns an optional or mandatory
|
||
update with its download address. Memby downloads the APK, verifies its checksum, package
|
||
and release signature, then commits it through Android's package installer. Update policy
|
||
and the download address are managed in the gateway's admin console; viewers do not need
|
||
to configure an update source on the television.
|
||
|
||
First install on each TV still has to be manual — the **Downloader** app pointed at the
|
||
landing page is the usual route, and the page explains it.
|
||
|
||
## Upgrading to v0.1.53
|
||
|
||
The package and install identity both became `com.ponzischeme89.memby` in this release
|
||
(previously `com.mattcohen.embyclientsname`). Android treats a new `applicationId` as a
|
||
different app, so on every TV:
|
||
|
||
1. Install v0.1.53 — it appears as a **second** Memby entry in the launcher.
|
||
2. Sign in again; the previous session does not carry over.
|
||
3. Uninstall the old app: `adb uninstall com.mattcohen.embyclientsname`.
|
||
4. Re-select Memby in the TV's Screensaver settings — the Dream's component name changed
|
||
as well, so the old selection no longer resolves.
|
||
|
||
## Notes & limitations
|
||
|
||
- Playback negotiates through Emby's `PlaybackInfo` endpoint, then streams directly from
|
||
Emby. The Wholphin/Jellyfin-derived capability engine reports Android's H.264 and HEVC
|
||
profiles, maximum levels and resolutions rather than assuming every decoder handles every
|
||
file. The Moonfin-derived audio path reports formats the TV can decode or bitstream, offers
|
||
automatic receiver detection and manual per-codec passthrough overrides, and decodes the
|
||
rest to PCM through Media3's FFmpeg renderer. Emby can preserve a supported video stream
|
||
while converting only incompatible audio or subtitles. If a vendor decoder still fails,
|
||
Media3 tries another decoder and Memby ultimately requests an H.264 HLS transcode instead
|
||
of abandoning playback.
|
||
- Cleartext HTTP is enabled so local `http://` servers work out of the box. For an HTTPS-only
|
||
server this is unnecessary but harmless.
|
||
- The device is remembered across sign-outs (stable `DeviceId`); credentials are cleared.
|
||
|
||
## Licence
|
||
|
||
Memby is free software licensed under the [GNU General Public License v2](LICENSE).
|
||
Copyright and third-party acknowledgements are recorded in [NOTICE](NOTICE). Distributed
|
||
APKs and server binaries must be accompanied by the corresponding source in accordance
|
||
with GPLv2. The Android TV app also exposes the source link, notices, and complete licence
|
||
under **Settings → About / Licences**.
|
||
|
||
Playback capability probing and device-profile generation contain GPLv2 adaptations from
|
||
[Wholphin](https://github.com/damontecres/Wholphin), itself derived in part from
|
||
[Jellyfin Android TV](https://github.com/jellyfin/jellyfin-androidtv). Surround-audio
|
||
capability detection, passthrough controls and software fallback are adapted from
|
||
[Moonfin](https://github.com/Moonfin-Client/Moonfin-Core) under GPLv2. Attribution and
|
||
modification notices are preserved in the adapted source files.
|