0.2.73
This commit is contained in:
@@ -1,11 +1,68 @@
|
||||
# Memby (Android TV)
|
||||
<div align="center">
|
||||
|
||||
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 using your library.
|
||||
<img src="memby-icon.png" alt="Memby" width="128" height="128">
|
||||
|
||||
# Memby
|
||||
|
||||
**An independent Android TV client for Emby.**
|
||||
|
||||
A personalised television launcher, a full media player, a system screensaver and an
|
||||
optional self-hosted gateway that shapes the experience for every viewer in the house.
|
||||
|
||||
[](LICENSE)
|
||||
[](#build--install)
|
||||
[](#build--install)
|
||||
[](#tech-stack)
|
||||
[](server/README.md)
|
||||
|
||||
Source: [g.sublogue.com/admin/memby](https://g.sublogue.com/admin/memby)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Contents
|
||||
|
||||
- [How it works](#how-it-works)
|
||||
- [Features](#features)
|
||||
- [Browse and discover](#browse-and-discover)
|
||||
- [Playback](#playback)
|
||||
- [Profiles, televisions and administration](#profiles-televisions-and-administration)
|
||||
- [Screensaver and updates](#screensaver-and-updates)
|
||||
- [Tech stack](#tech-stack)
|
||||
- [Build & install](#build--install)
|
||||
- [Server address](#server-address)
|
||||
- [First run](#first-run)
|
||||
- [Running the gateway](#running-the-gateway)
|
||||
- [Project layout](#project-layout)
|
||||
- [Distributing builds](#distributing-builds)
|
||||
- [One-time: a signing key](#one-time-a-signing-key)
|
||||
- [NAS deployment release (recommended)](#nas-deployment-release-recommended)
|
||||
- [Local PowerShell release (compatibility path)](#local-powershell-release-compatibility-path)
|
||||
- [How TVs update themselves](#how-tvs-update-themselves)
|
||||
- [Upgrading to v0.1.53](#upgrading-to-v0153)
|
||||
- [Notes & limitations](#notes--limitations)
|
||||
- [Licence](#licence)
|
||||
|
||||
---
|
||||
|
||||
## How it works
|
||||
|
||||
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 the app talks to Emby directly. Both paths are
|
||||
maintained — the direct path is the fallback when the container is down.
|
||||
|
||||
## Features
|
||||
|
||||
### Browse and discover
|
||||
@@ -59,12 +116,79 @@ Source: [g.sublogue.com/admin/memby](https://g.sublogue.com/admin/memby)
|
||||
|
||||
## 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
|
||||
| Layer | Built with |
|
||||
| --- | --- |
|
||||
| UI | Kotlin + Jetpack **Compose for TV** (`androidx.tv:tv-material3`) |
|
||||
| Playback | **Media3 / ExoPlayer** |
|
||||
| Networking | Retrofit + OkHttp + kotlinx.serialization, over one shared HTTP stack |
|
||||
| Storage | DataStore for profiles, settings and cached launcher state |
|
||||
| Images | Coil |
|
||||
| Gateway | Go, with Postgres and Redis |
|
||||
|
||||
## 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 test # JVM unit tests
|
||||
.\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`.
|
||||
|
||||
## 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".
|
||||
|
||||
## Running the gateway
|
||||
|
||||
See [`server/README.md`](server/README.md) to run the container. In short, the gateway
|
||||
imports Emby's catalogue into Postgres (once manually, then hourly for new episodes),
|
||||
composes the home rows — including "Recommended from your watching history" — and serves an
|
||||
admin console 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.
|
||||
|
||||
## Project layout
|
||||
|
||||
@@ -90,98 +214,21 @@ app/src/main/java/com/ponzischeme89/memby/
|
||||
player/PlayerActivity.kt Media3 playback
|
||||
screensaver/MembyDreamService.kt System screensaver (hosts ScreensaverContent)
|
||||
|
||||
admin-ui/ The operations console (React, served by nginx)
|
||||
server/ The Memby gateway (Go) — see server/README.md
|
||||
benchmark/ Macrobenchmarks and baseline-profile generation
|
||||
```
|
||||
|
||||
## 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.
|
||||
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.
|
||||
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 `
|
||||
@@ -198,30 +245,23 @@ 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.
|
||||
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)
|
||||
### NAS deployment release (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
|
||||
```
|
||||
`deploy-server.ps1` builds and publishes the TV app in the same operation that replaces the
|
||||
gateway: it signs with the existing keystore, verifies the APK and publishes it through
|
||||
`POST /admin/api/release` once the new gateway is healthy. A pushed semantic tag does the
|
||||
same thing through `.gitea/workflows/release.yml`. The gateway builds nothing itself.
|
||||
|
||||
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.
|
||||
`/data/releases/memby-<version>.apk` and `/data/releases/memby-<version>.apk.sha256`. The
|
||||
release-publish credential is a read-only Compose secret under `/run/secrets` and is never
|
||||
copied into the image or repository.
|
||||
|
||||
Admin Console → App updates sets the update policy — latest version, APK URL, release
|
||||
notes, and the required/destructive toggles.
|
||||
|
||||
### Local PowerShell release (compatibility path)
|
||||
|
||||
@@ -231,17 +271,16 @@ under `/run/secrets` and are never copied into the image or repository.
|
||||
-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:
|
||||
That workflow 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
|
||||
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
|
||||
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`
|
||||
@@ -252,8 +291,8 @@ decides what the app offers, so rolling back is editing one file.
|
||||
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.
|
||||
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.
|
||||
@@ -267,31 +306,31 @@ 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.
|
||||
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.
|
||||
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**.
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user