217 lines
9.7 KiB
Markdown
217 lines
9.7 KiB
Markdown
# Memby (Android TV)
|
|
|
|
An independent Android TV client for Emby, by **ponzischeme89**. Sign in to browse your
|
|
personalised home screen, resume titles, play recent movies, and choose audio or subtitle
|
|
tracks during playback. The screensaver (Dream) code remains isolated from the client work.
|
|
|
|
## Features
|
|
|
|
- **System screensaver** via `DreamService` — auto-starts on idle once selected in the
|
|
TV's Screensaver settings. Also previewable from the app's home screen.
|
|
- Random, cross-fading **backdrops** of Movies and Series that have backdrop images.
|
|
- **Interactive**: OK reveals Play / Favorite actions; ◄ ► change the backdrop; Back exits.
|
|
- **In-app playback** with Media3/ExoPlayer (movies play directly; a series plays its
|
|
next-up / first episode).
|
|
- **Favorites** managed through the Emby API and shown on the home screen.
|
|
- A dedicated **For You** destination, shaped by Tracearr completion/device history,
|
|
Memby browsing signals and a viewer-selected 30/60/120-minute time window. Every pick
|
|
says why it was chosen and whether it has direct-play evidence on that television.
|
|
|
|
## Tech stack
|
|
|
|
- Kotlin + Jetpack **Compose for TV** (`androidx.tv:tv-material`)
|
|
- **Media3 / ExoPlayer** for playback
|
|
- Retrofit + OkHttp + kotlinx.serialization for the Emby REST API
|
|
- DataStore for persisted connection/session
|
|
- Coil for backdrop image loading
|
|
|
|
## 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, favorites, 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, always direct-play)
|
|
```
|
|
|
|
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.
|
|
|
|
### Each release
|
|
|
|
```powershell
|
|
.\release.ps1 -Version 0.1.54 -Notes "Faster home screen" -BaseUrl https://nas.example.com/memby
|
|
```
|
|
|
|
That 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
|
|
```
|
|
|
|
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
|
|
|
|
In Memby's Settings, set the update URL to `https://nas.example.com/memby/latest.json`.
|
|
**Check for updates** then downloads and installs on the TV, no computer involved. A URL
|
|
ending in `.json` is read as a static manifest; anything else is treated as a Gitea host
|
|
(`/api/v1/repos/{owner}/{repo}/releases/latest`), so either source works.
|
|
|
|
The manifest's `apkUrl` may be relative (`memby-0.1.54.apk`) and is resolved against the
|
|
manifest's own URL, so the folder keeps working if the NAS is reached by another name.
|
|
|
|
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 uses Emby's direct stream (`/Videos/{id}/stream?static=true`). This direct-plays
|
|
containers/codecs ExoPlayer supports (most MP4/H.264, many MKV). Server-side transcoding
|
|
is not requested; unusual codecs may need it — a future enhancement is to call Emby's
|
|
`PlaybackInfo` endpoint and use the returned HLS transcode URL (the `media3-exoplayer-hls`
|
|
dependency is already included).
|
|
- 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.
|
|
```
|