145 lines
6.7 KiB
Markdown
145 lines
6.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.
|
||
|
|
|
||
|
|
## 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.
|
||
|
|
|
||
|
|
## 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".
|
||
|
|
|
||
|
|
## 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.
|
||
|
|
```
|