113 Commits
Author SHA1 Message Date
ponzischeme89andClaude Opus 5 1ae07b93ec 0.3.17
Fix the crash on every cold start of a signed-in television.

MembyIcons' currentIconPack and DesignTokens' activePalette are process-wide
mutableStateOf. The first thing in the process to touch either was ThemeSync,
applying the cached pack and palette from an IO thread within milliseconds of
the settings store's first emission -- reliably after MainActivity had taken
its composition snapshot. A state object created in a newer snapshot than the
composition reading it makes that read throw, so HomeNavigation's first
MembyIcon.mark read died with "Reading a state that was created after the
snapshot was taken".

MembyApp.onCreate now creates both on the main thread before anything else can.
Both calls are value-level no-ops; what matters is the thread and the ordering.

It was invisible on a fresh install because first-run and sign-in draw no marks,
which is why clearing app data appeared to fix it -- and it looked like an update
bug only because replacing the APK is what forces a cold start with the launcher
as the first screen. A reboot did it too.

Also: AppRoot filtered neither read of the persisted requiredUpdateVersion
through requiredUpdateSatisfied, so a set that had just installed the demanded
version opened on the retired-build screen it was updating to escape, until a
/v1/update round trip completed. Both reads now apply the rule the in-memory
signal and markRequiredUpdate already did.

Verified on 10.0.0.238: three cold starts, no crash, launcher fully drawn.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HoEWidWhZu9kZzNURatmcT
2026-08-24 09:58:09 +12:00
ponzischeme89 067395a362 0.3.16 2026-08-24 09:16:19 +12:00
ponzischeme89 30155e1c5f 0.3.15 2026-08-24 08:16:55 +12:00
ponzischeme89 72cb3c376e 0.3.14 2026-08-24 07:43:17 +12:00
ponzischeme89 f1d2c2f2eb 0.3.14 2026-08-24 07:29:23 +12:00
ponzischeme89 6ba8728dce 0.3.13 2026-08-23 20:25:20 +12:00
ponzischeme89 5107c4b0fc 0.3.12 2026-08-23 18:55:04 +12:00
ponzischeme89 aa8f16f21b 0.3.11 2026-08-23 18:21:18 +12:00
ponzischeme89 c809127996 0.3.10 2026-08-23 17:45:46 +12:00
ponzischeme89 d4e1cb2fbf 0.3.09 2026-08-23 16:38:15 +12:00
ponzischeme89 63c3df11b2 0.3.08 2026-08-23 14:16:03 +12:00
ponzischeme89 89ebe21201 0.3.07 2026-08-23 13:20:54 +12:00
ponzischeme89 766cea2199 0.3.06 2026-08-23 09:40:45 +12:00
ponzischeme89 9a1f44da46 0.3.05 2026-08-22 21:47:54 +12:00
ponzischeme89 d2f7f84cbf 0.3.04 2026-08-22 20:01:49 +12:00
ponzischeme89 7d77700c7b 0.3.03 2026-08-22 18:40:50 +12:00
ponzischeme89 9858e44710 0.3.02 2026-08-22 15:32:51 +12:00
ponzischeme89 b3e3f62c54 0.3.01 2026-08-22 12:38:26 +12:00
ponzischeme89 f982d391b9 0.3.01 2026-08-22 11:32:50 +12:00
ponzischeme89 b131932a4e 0.3.00 2026-08-22 08:26:10 +12:00
ponzischeme89 3ae8ffad64 0.2.99 2026-08-22 07:15:10 +12:00
ponzischeme89 5df4a1aaa3 0.2.99 2026-08-22 07:15:03 +12:00
ponzischeme89 4fd690a127 0.2.98 2026-08-22 06:29:34 +12:00
ponzischeme89 ac4cf32746 0.2.98 2026-08-22 06:29:31 +12:00
ponzischeme89 8b3d80a0a6 0.2.97 2026-08-21 23:15:42 +12:00
ponzischeme89 c85fab4459 0.2.96 2026-08-21 22:40:31 +12:00
ponzischeme89 32019f2e20 0.2.95 - Refactor 2026-08-21 22:07:02 +12:00
ponzischeme89 02247a6287 0.2.94 2026-08-21 21:04:57 +12:00
ponzischeme89 9c7dec32a8 0.2.93 2026-08-21 17:16:47 +12:00
ponzischeme89 57edfaa60b 0.2.93 2026-08-21 16:30:29 +12:00
ponzischeme89 891d3dfedc 0.2.92 2026-08-21 16:02:25 +12:00
ponzischeme89 4439277c86 0.2.91 2026-08-21 15:24:22 +12:00
ponzischeme89 4c48ef8b19 0.2.90 2026-08-21 14:06:36 +12:00
ponzischeme89 fb97980775 0.2.89 2026-08-21 13:22:04 +12:00
ponzischeme89 243b7c8b90 0.2.88 2026-08-21 12:12:03 +12:00
ponzischeme89 48043dbae2 0.2.87 2026-08-21 11:36:22 +12:00
ponzischeme89 aac8438764 0.2.86 2026-08-21 11:19:10 +12:00
ponzischeme89 af1ca4370c 0.2.85 2026-08-21 10:38:41 +12:00
ponzischeme89 5467fba0eb 0.2.84 2026-08-21 09:54:44 +12:00
ponzischeme89 f1164db2c5 0.2.82 2026-08-20 15:06:00 +12:00
ponzischeme89 549f9c5eed Untrack .tmp-go-cache 2026-08-20 10:05:51 +12:00
ponzischeme89 434371e9cf 0.2.81 2026-08-20 07:54:03 +12:00
ponzischeme89 769fe01c84 0.2.80 2026-08-19 21:30:44 +12:00
ponzischeme89 0782545013 0.2.79 - Slow api fixes 2026-08-19 18:08:00 +12:00
ponzischeme89 590e069366 0.2.78 2026-08-19 14:25:44 +12:00
ponzischeme89 2b43b9ef12 0.2.77 2026-08-19 06:57:59 +12:00
ponzischeme89 8c847c59b8 0.2.76 - Icon Packs 2026-08-18 14:59:29 +12:00
ponzischeme89 36d171e51b 0.2.75 2026-08-18 08:41:48 +12:00
ponzischeme89 1da91e40a1 0.1.52 Gateway 2026-08-17 19:09:17 +12:00
ponzischeme89 d5a8d3ad88 0.2.74 - Genres brwoser 2026-08-17 14:56:13 +12:00
ponzischeme89 43ab18854c 0.2.73 2026-08-17 13:13:10 +12:00
ponzischeme89 12b27c77c3 0.2.72 - Magic button, Next up fixes 2026-08-17 11:41:36 +12:00
ponzischeme89 36cb1324fd 0.2.71 2026-08-17 07:34:23 +12:00
ponzischeme89 93fb0fd728 0.2.70 - Cold start improvements 2026-08-16 22:08:43 +12:00
ponzischeme89 845fa349e4 0.2.69 - Homepage loading improvements pass 2026-08-16 21:20:55 +12:00
ponzischeme89 b9374baaf1 0.2.68 2026-08-16 12:13:51 +12:00
ponzischeme89 bd3732fba5 0.2.67 - Detail pages improvements 2026-08-16 09:02:17 +12:00
ponzischeme89 4bc4b075ec 0.2.66 - End Credits improvements / Gateway: 0.1.47 - End credits redesign 2026-08-15 22:26:17 +12:00
ponzischeme89 e528d04b43 0.2.65 - End Credits wiring / Gateway: 0.1.45 - Credits 2026-08-15 21:11:43 +12:00
ponzischeme89 d5d47473a2 0.2.64 update 2026-08-15 09:23:26 +12:00
ponzischeme89 a2ca7e8061 Fix release history ordering 2026-08-14 13:41:43 +12:00
ponzischeme89 9775041c30 Merge remote-tracking branch 'github/main'
# Conflicts:
#	CHANGELOG.md
#	app/build.gradle.kts
2026-08-14 13:33:05 +12:00
ponzischeme89 05d95f51cd Release Memby 0.2.64 2026-08-14 13:32:14 +12:00
ponzischeme89 ad0c74f880 0.2.63 update 2026-08-14 11:49:07 +12:00
ponzischeme89 06b6490ac9 0.2.63 update 2026-08-14 11:47:32 +12:00
ponzischeme89 bd5cd8a4b1 Bump Memby app to 0.2.63 2026-08-14 11:46:36 +12:00
ponzischeme89 9a8ecbdceb Bump gateway to 0.1.42 2026-08-14 11:06:47 +12:00
ponzischeme89 48c2809041 Bump gateway to 0.1.41 2026-08-14 10:48:12 +12:00
ponzischeme89 19f08e6293 Fix Sonarr TV request policy and bump gateway to 0.1.40 2026-08-14 10:30:25 +12:00
ponzischeme89 5e2ed3d12e 0.1.38 gateway 2026-08-14 09:40:03 +12:00
ponzischeme89andClaude Opus 5 abc392d30b 0.2.62 - Roll back the custom playback engine
0.2.61 shipped the groundwork for Memby playing video itself and caused
playback problems in the field, so it is withdrawn. This is 0.2.60's code
released as 0.2.62: Android refuses an APK whose versionCode went
backwards, so a set already on 0.2.61 (20061) could not install a 0.2.60
build (20060).

The 0.2.61 work is parked on parked/0.2.61 and tagged broken-0.2.61.

Also corrects the changelog headings the reset left behind: the top block
was labelled 0.2.59 while holding 0.2.60's notes, and 0.2.59's own two
entries were folded into it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 19:28:10 +12:00
ponzischeme89 a0b84dfc37 0.2.60 - Improvments to Requests, placeholders 2026-08-12 16:54:41 +12:00
ponzischeme89 4fb68f4bbc 0.2.59 - Settings save fixes 2026-08-12 15:39:08 +12:00
ponzischeme89 613f203cf9 0.2.58 - Requests module 2026-08-12 14:13:19 +12:00
ponzischeme89 64f19aeef2 0.2.57 - Traliers bug fixes 2026-08-12 13:08:53 +12:00
ponzischeme89 f2d052dbf6 0.2.56 - Reliable trailer playback 2026-08-12 11:05:07 +12:00
ponzischeme89 9777eb0952 0.2.55 - Remote config/Request fixes 2026-08-12 09:57:56 +12:00
ponzischeme89 4b31946635 0.2.54 2026-08-12 08:25:15 +12:00
ponzischeme89 e30962245e 0.2.53 2026-08-12 07:33:24 +12:00
ponzischeme89 5fed3360c2 0.2.52 2026-08-11 23:41:10 +12:00
ponzischeme89 0e183cf560 0.2.51 - update checker fixes 2026-08-11 17:26:48 +12:00
ponzischeme89 226136b4e7 0.2.51 2026-08-11 17:18:04 +12:00
ponzischeme89 5e5849f985 0.2.50 2026-08-11 15:18:26 +12:00
ponzischeme89 594159c0ae Release 0.2.49 2026-08-11 13:46:53 +12:00
ponzischeme89 f46d6596c5 0.2.48 - Tv calender fixes 2026-08-11 12:08:51 +12:00
ponzischeme89 dd2dd497f2 0.2.45 - Server side git commits 2026-08-10 20:54:00 +12:00
ponzischeme89 4c47a5f8a0 0.2.45 - Server side git commits 2026-08-10 20:39:26 +12:00
ponzischeme89 56c1167382 0.2.45 - Advanced analytics, logout old versions 2026-08-10 20:24:22 +12:00
ponzischeme89 63f0768507 Release v0.2.43 2026-08-10 13:09:38 +12:00
ponzischeme89 12b519762b 0.2.42 - DTS surround sound support 2026-08-10 12:24:25 +12:00
ponzischeme89 f41db66ac6 0.2.42 - DTS surround sound support 2026-08-10 12:23:23 +12:00
ponzischeme89 32c0054c44 0.2.41 - Preroll fixes, theme fixes 2026-08-10 09:39:09 +12:00
ponzischeme89 e1bb687df4 0.2.40 & server 0.1.30 2026-08-10 08:54:23 +12:00
ponzischeme89 78d26effbf 0.2.40 2026-08-10 08:37:08 +12:00
ponzischeme89 d2f2eb62be Improve playback, preroll and TV experience 2026-08-10 07:11:14 +12:00
ponzischeme89 598a4f5c75 Release v0.2.39
Publish Memby release / release (push) Canceled after 0s
2026-08-09 16:57:14 +12:00
ponzischeme89 52e167ea59 Release v0.2.38
Publish Memby release / release (push) Canceled after 0s
2026-08-09 16:04:40 +12:00
ponzischeme89 0dffd59440 Release v0.2.37 2026-08-09 13:10:55 +12:00
ponzischeme89 574826dc03 Release v0.2.36 2026-08-09 12:53:25 +12:00
ponzischeme89 add8aa831c Release v0.2.35 2026-08-09 09:36:35 +12:00
ponzischeme89 0c623bbcc0 Complete genre browsing for v0.2.34 2026-08-09 08:56:42 +12:00
ponzischeme89 fdd9e6cab2 Release v0.2.34 2026-08-09 08:25:50 +12:00
ponzischeme89 b1128bcce2 Release v0.2.33 2026-08-08 16:56:18 +12:00
ponzischeme89andClaude Opus 5 80c304d86b App v0.2.27 and gateway 0.1.23
Skip Intro from Emby's own chapter markers, trickplay seek previews from
BIF files, a server-composed home hero ranked on Radarr/Sonarr dates and
review scores, and My Alerts as its own page behind the user picker.

Related titles now degrade at every step instead of returning empty, and
the "+" is back on Manage users so a second viewer can be added from the
launcher.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 10:44:17 +12:00
ponzischeme89andClaude Opus 5 4a4df7a73c App v0.2.26 and gateway 0.1.20
Client: seek controls, Bazarr subtitle download and cast panel in the
player; MDBList ratings strip; episode and schedule detail pages; series
pace estimate; what's new panel; install-permission onboarding step;
synced per-profile preferences; Emby outage banner.

Gateway: rebuilt admin console (one fragment per page), preference
history and restore, merged Continue Watching, Emby health probe,
subtitle selection and Bazarr download, structured request logging with
per-request identity, and embedded build version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 22:33:56 +12:00
ponzischeme89 2675e6d82b Add Memby account management 2026-08-03 10:16:44 +12:00
ponzischeme89 666da9c5d3 Add optional MDBList movie ratings 2026-08-03 08:55:52 +12:00
ponzischeme89 b6b2a9c25a Merge GitHub repository history 2026-08-02 22:11:04 +12:00
ponzischeme89 1ed180c739 Publish current app and server 2026-08-02 22:10:19 +12:00
Matt f12042ad24 Initial commit 2026-08-02 22:01:18 +12:00
ponzischeme89 a265636139 Big changes 2026-07-29 15:26:55 +12:00
ponzischeme89 70914400b4 Big changes 2026-07-29 15:26:27 +12:00
ponzischeme89 8d6cf2f5a1 Server changes/Sonarr 2026-07-27 21:06:51 +12:00
3557 changed files with 1261173 additions and 4532 deletions
+167 -10
View File
@@ -5,28 +5,185 @@ MEMBY_EMBY_URL=https://molise.bounceme.net
# What TVs are told to stream from. Only set this when it differs from the address # What TVs are told to stream from. Only set this when it differs from the address
# above — video goes device -> Emby directly, never through the gateway. # above — video goes device -> Emby directly, never through the gateway.
#MEMBY_EMBY_PUBLIC_URL=https://molise.bounceme.net MEMBY_EMBY_PUBLIC_URL=https://molise.bounceme.net
# Where the gateway reads media *bytes* from, which is a different question from the two
# addresses above and matters for exactly one subsystem.
#
# Credits detection is the only server-side thing that opens a media file, and it does so as
# ranged reads over the last few minutes of it. The gateway runs on the NAS (10.0.0.213) and
# the media and Emby live on the HTPC (10.0.0.2), so with this unset every one of those
# ranged reads takes MEMBY_EMBY_URL: out to the DDNS name, through TLS and the reverse proxy,
# and back in — and a proxy that buffers turns the ranged read into a whole-file read, which
# is the one thing the scan design exists to avoid.
#
# Unset, it falls back to MEMBY_EMBY_URL and behaves exactly as before. Confirm the port your
# Emby listens on over plain HTTP on the LAN before uncommenting.
MEMBY_EMBY_MEDIA_URL=http://10.0.0.2:8096
# Postgres password for the memby role. Generate one, e.g. # Postgres password for the memby role. Generate one, e.g.
# openssl rand -base64 24 # openssl rand -base64 24
POSTGRES_PASSWORD=change-me POSTGRES_PASSWORD=7dfc3eb07108013c7bea9787457397551b5eb9059adac51aea4741f4ec649be2
# Host port the gateway listens on. # Fixed NAS host port used by the mserver.sublogue.com reverse proxy.
MEMBY_PORT=8080 MEMBY_PORT=32768
# INFO is recommended. DEBUG adds diagnostic request and playback detail; TRACE adds the
# deepest request and playback-negotiation trace. Sensitive structured values are redacted.
MEMBY_LOG_LEVEL=INFO
MEMBY_LOG_BUFFER_CAPACITY=5000
# The Compose default is a named-volume-backed JSONL archive restored by the admin log.
MEMBY_LOG_HISTORY_PATH=/data/logs/events.jsonl
# console (default) is one aligned line per event, timestamp first and no `time=` key.
# logfmt restores slog's own key=value line; json is for a log collector.
MEMBY_LOG_FORMAT=console
MEMBY_GOMEMLIMIT=384MiB
MEMBY_SERVER_MEMORY_LIMIT=512m
# Used for local Sonarr air times and Radarr digital-release day boundaries.
MEMBY_TIMEZONE=Pacific/Auckland
# How long a cached home payload stays warm. # How long a cached home payload stays warm.
MEMBY_HOME_TTL=60s MEMBY_HOME_TTL=60s
MEMBY_RECOMMEND_TTL=24h
# Optional JSON overlay on the explainable weighted-ranking defaults.
# MEMBY_RECOMMENDATION_WEIGHTS={"ExplorationRate":0.08,"MinimumEvidence":2,"ImpressionPenalty":0.12}
MEMBY_RECOMMENDATION_WEIGHTS=
# Admin interface at http://<host>:8080/admin/ — library imports, the maintenance # Optional complete Remote Config document. Leave blank for bundled-equivalent defaults.
# switch, and row analytics. Leave blank to disable /admin entirely. Generate with # Every change must increase configVersion; see server/REMOTE_CONFIG.md.
MEMBY_REMOTE_CONFIG_JSON=
# Maximum number of distinct Memby TVs one Emby user may keep signed in.
# Admin interface at https://mserver.sublogue.com/admin/ — library imports, the
# maintenance switch, and row analytics. Leave blank to disable /admin entirely. Generate with
# openssl rand -hex 32 # openssl rand -hex 32
MEMBY_ADMIN_TOKEN= MEMBY_ADMIN_TOKEN=4fad67d508558efee5cc5ae05694105421d4c79d35ee2333a817b3791235cd8c
# Library import. Hourly incremental keeps up with episodes added through the day. # Public gateway address. Release publishing uses the file-backed secret in
# MEMBY_SECRETS_DIR, never a value in this environment file.
MEMBY_PUBLIC_URL=https://mserver.sublogue.com
MEMBY_SECRETS_DIR=/share/Docker/Memby-secrets
# Catalogue sweep. With both *arr webhooks below configured this is reconciliation for
# media Sonarr and Radarr do not manage — a file dropped in by hand, a title edited in
# Emby, a notification that never arrived — so it can be lengthened to 6h. Without them
# it is the only way a new title is ever found and must stay frequent. The console can
# override it at Settings > Catalogue sweep without a redeployment.
MEMBY_SYNC_INTERVAL=1h MEMBY_SYNC_INTERVAL=1h
MEMBY_SYNC_ON_START=false MEMBY_SYNC_ON_START=false
# Optional Emby service account for imports. Without it the gateway borrows the most # Optional Emby service account for imports. Without it the gateway borrows the most
# recently active TV session, which works but stops if that user is removed. # recently active TV session, which works but stops if that user is removed.
#MEMBY_SYNC_USER_ID= MEMBY_SYNC_USER_ID=23bf90fc1fe049ce898db7adda1ed7bf
#MEMBY_SYNC_API_KEY= MEMBY_SYNC_API_KEY=56775917938841e7ac1b6a233d4d5075
# Optional Sonarr calendar integration. Use the URL reachable from this container,
# not necessarily the address entered in a browser. The API key is in Sonarr under
# Settings > General > Security.
MEMBY_SONARR_URL=http://10.0.0.2:8989
MEMBY_SONARR_API_KEY=6b608b051ee24582925773bd5dfbe37a
MEMBY_SONARR_TTL=5m
# How long an episode banner keeps being offered — both the "aired, coming soon" one and
# the "new episode added" one a finished scan produces. 0 turns both off and leaves the
# airing-today row alone.
MEMBY_SONARR_ALERT_WINDOW=3h
# Shared secret for Sonarr's webhook, which is how the catalogue learns that an episode
# landed rather than waiting for the next sweep. In Sonarr: Settings > Connect > + >
# Webhook, with On Import, On Upgrade, On Rename, On Episode File Delete and On Series
# Delete ticked, URL https://<public gateway>/hooks/sonarr?token=<this value>. Empty
# makes the hook 404.
MEMBY_SONARR_WEBHOOK_TOKEN=09f091f0f2ee8af3cba1aac29557e344
# How long after a webhook the gateway first looks for the file in Emby. Sonarr and
# Radarr fire the moment they have moved the file into place and Emby has not scanned it
# yet, so asking immediately spends a request to learn nothing.
MEMBY_ARR_INGEST_SETTLE=1m
# Optional Radarr calendar integration. Upcoming movies cover the coming month, ordered by
# Radarr's digital release date. A film with no digital date yet is estimated at its cinema
# date plus 30 days and says so on the card; physical dates are ignored.
MEMBY_RADARR_URL=http://10.0.0.2:7878
MEMBY_RADARR_API_KEY=d393acb157a44dc2b0e2aede96278ad5
MEMBY_RADARR_TTL=5m
# Shared secret for Radarr's webhook, which is how the catalogue learns that a film landed
# rather than waiting for the next sweep. In Radarr: Settings > Connect > + > Webhook, with
# On Import, On Upgrade, On Rename, On Movie File Delete and On Movie Delete ticked, URL
# https://<public gateway>/hooks/radarr?token=<this value>. Empty makes the hook 404.
# The catalogue reads all five; the banner is announced once the film has actually scanned
# in, and only for an import — an upgrade replaced a film that was already there.
MEMBY_RADARR_WEBHOOK_TOKEN=bfa059594adeadf9105c27481a5fd758
# How long a newly scanned film keeps being announced, so a TV switched on shortly after
# the import still hears about it. 0 turns the banners off.
MEMBY_RADARR_ALERT_WINDOW=3h
# Optional Bazarr integration, one of the two providers a viewer can fetch a missing
# subtitle from. Bazarr writes the file beside the media file, so Emby serves the result
# and Memby stores nothing — leaving these unset simply means Bazarr is never offered. The
# API key is in Bazarr under Settings > General.
#
# Whether viewers may actually use it is a switch on the admin console's Subtitles page,
# not an environment variable: the address is deployment configuration and belongs here,
# but turning the provider on and off is an operator's decision that should not need a
# redeployment. The second provider, OpenSubtitles, is configured entirely on that page —
# it needs only an API key, and the credentials live in the database rather than in this
# file.
#MEMBY_BAZARR_URL=http://10.0.0.2:6767
#MEMBY_BAZARR_API_KEY=e079ab79c1e32b5cf648d079f4421e11
# How long Bazarr's movie/series/episode listings are cached. They exist only to turn an
# Emby item into the Radarr/Sonarr id Bazarr keys on.
#MEMBY_BAZARR_TTL=5m
# A manual search queries live subtitle providers and is legitimately slow, so it gets a
# longer timeout than the rest of the upstreams. Too short reads to a viewer as "no
# subtitles found" rather than as a timeout.
MEMBY_BAZARR_TIMEOUT=45s
# How often the gateway checks that Emby is answering. A run of failures raises the
# "server not responding" banner on every TV, including mid-playback, and recovery
# raises the matching "back online" one. 0 turns the probe and both banners off.
MEMBY_EMBY_HEALTH_INTERVAL=60s
# How slow a request has to be before its log line carries a breakdown of where the time
# went: Emby, Postgres, Redis, each *arr, and the gateway's own row assembly and encoding.
# Fast requests carry none deliberately — a breakdown on every /v1/status poll would bury
# the one it exists to reveal. Lower it while chasing one slow screen. The gateway's
# settings page overrides it without a redeployment.
MEMBY_SLOW_REQUEST_THRESHOLD=500ms
# Optional Tracearr-powered For You signals. Create a read-only public API key in
# Tracearr Settings. Server ID is optional unless Tracearr monitors multiple servers.
MEMBY_TRACEARR_URL=https://tracearr.sublogue.com/
MEMBY_TRACEARR_API_KEY=trr_pub_WKSdiZFGZ_10d-zgQ2Wx0H4Ym4NAiIeLBSvf8a6rvZ0
MEMBY_TRACEARR_SERVER_ID=6964b9ed-3a21-4f51-b94b-cd344ec42c1b
MEMBY_TRACEARR_SYNC_INTERVAL=5m
MEMBY_TRACEARR_FULL_INTERVAL=24h
# Credits detection: where an episode's closing credits begin, discovered for the few
# episodes the household is actually about to watch. Tracearr is what says which those are,
# so this never scans the library — it prepares a sliding window a few episodes ahead of
# each viewer and stops. An accepted marker is scanned once per media version; inconclusive
# attempts observe the retry delay configured in the admin console.
#
# It is the only thing in the gateway that reads media bytes, which is why it is off by
# default. With ffmpeg absent it still runs, writing markers from where viewers actually
# stopped — which on a well-watched show is the better signal anyway.
#
# Where those bytes are read FROM is MEMBY_EMBY_MEDIA_URL, near the top of this file beside
# the other Emby addresses. It is the setting that decides whether a scan crosses the LAN to
# the HTPC or goes out to the DDNS name and back through the reverse proxy, so it is worth
# setting before turning this on.
MEMBY_CREDITS_ENABLED=true
# Leave blank to find ffmpeg on the path, which is where the image puts it.
MEMBY_CREDITS_FFMPEG=
# Look-ahead for an ordinary viewer. A binge expands it, a slow viewer contracts it, and
# MAX_PREFETCH is the ceiling nothing exceeds however fast somebody watches.
MEMBY_CREDITS_PREFETCH_EPISODES=3
MEMBY_CREDITS_MAX_PREFETCH=5
# Pending candidates. Past this, low-priority speculation is discarded rather than queued:
# a backlog of episodes nobody reached has stopped describing demand. These three values
# are first-run defaults; Admin Console → Credits detection can override them durably.
MEMBY_CREDITS_QUEUE_LIMIT=20
MEMBY_FOR_YOU_MIN_REBUILD_AGE=24h
MEMBY_FOR_YOU_REFRESH_INTERVAL=24h
MEMBY_FOR_YOU_REBUILD_HOUR=4
+81
View File
@@ -0,0 +1,81 @@
name: Publish Memby release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: read
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Check out source
uses: actions/checkout@v4
- name: Set up Java 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- name: Set up Android SDK
uses: android-actions/setup-android@v3
- name: Install Android 35 build tools
shell: bash
run: sdkmanager "platforms;android-35" "build-tools;35.0.0"
- name: Read version from tag
shell: bash
run: |
VERSION="${GITEA_REF_NAME#v}"
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Tag must look like v0.1.54" >&2
exit 1
fi
echo "VERSION=$VERSION" >> "$GITEA_ENV"
- name: Restore release keystore
shell: bash
env:
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
run: |
printf '%s' "$KEYSTORE_BASE64" | base64 --decode > "$GITEA_WORKSPACE/memby-release.jks"
- name: Test and build signed APK
shell: bash
env:
MEMBY_KEYSTORE: ${{ gitea.workspace }}/memby-release.jks
MEMBY_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
MEMBY_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
MEMBY_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
chmod +x ./gradlew
./gradlew --console=plain testDebugUnitTest assembleRelease \
-Pmemby.versionName="$VERSION"
- name: Verify signed APK
shell: bash
run: |
"$ANDROID_HOME/build-tools/35.0.0/apksigner" verify --verbose --print-certs \
app/build/outputs/apk/release/app-release.apk
echo "APK_SHA256=$(sha256sum app/build/outputs/apk/release/app-release.apk | awk '{print $1}')" \
>> "$GITEA_ENV"
- name: Publish to Memby gateway
shell: bash
env:
RELEASE_TOKEN: ${{ secrets.MEMBY_RELEASE_PUBLISH_TOKEN }}
run: |
NOTES="$(git log -1 --pretty=%B)"
curl --fail-with-body --show-error --silent \
-X POST \
-H "Authorization: Bearer $RELEASE_TOKEN" \
-F "version=$VERSION" \
-F "sha256=$APK_SHA256" \
-F "notes=$NOTES" \
-F "apk=@app/build/outputs/apk/release/app-release.apk;type=application/vnd.android.package-archive" \
https://mserver.sublogue.com/admin/api/release
+16
View File
@@ -18,9 +18,25 @@ local.properties
# Holds the Postgres password, admin token and Emby address. # Holds the Postgres password, admin token and Emby address.
.env .env
/server/bin/ /server/bin/
/server/.tmp-go-cache/
# Release artefacts and signing material. The keystore must never be committed: # Release artefacts and signing material. The keystore must never be committed:
# whoever holds it can publish an update that installs over Memby. # whoever holds it can publish an update that installs over Memby.
/dist/out/ /dist/out/
*.apk
*.jks *.jks
*.keystore *.keystore
*.p12
*.pfx
*.password
*.secret
/secrets/
# Local environment variants may contain host paths or release settings. The
# checked-in example contains names/placeholders only.
.env.*
!.env.example
# The installed APK a deploy script pulls off a television to compare against.
/.tmp-memby-installed-base.apk
.tmp-go-cache
@@ -0,0 +1 @@
e76e2be01f9477bf3dae4d4f5582546f
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,15 @@
import java.io.File;
import java.net.URI;
import org.gradle.wrapper.PathAssembler;
import org.gradle.wrapper.WrapperConfiguration;
public class WrapperProbe {
public static void main(String[] args) throws Exception {
WrapperConfiguration config = new WrapperConfiguration();
config.setDistribution(URI.create(args[0]));
PathAssembler assembler = new PathAssembler(new File(args[1]), new File(args[1]));
PathAssembler.LocalDistribution local = assembler.getDistribution(config);
System.out.println(local.getDistributionDir().getAbsolutePath());
System.out.println(local.getZipFile().getAbsolutePath());
}
}
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Implementation-Title: Gradle Wrapper
Binary file not shown.
+59
View File
@@ -0,0 +1,59 @@
# AGENTS.md
Guidance for coding agents working in this repository. **`CLAUDE.md` is the full brief** —
what Memby is, how it is built and deployed, and why the architecture is the shape it is.
Read it first. This file carries the rules that are easiest to break without noticing.
## Language: New Zealand English
**Everything a person reads is New Zealand English. No American spellings.**
- `-ise` / `-isation`, never `-ize` / `-ization` — personalise, synchronisation, organise,
recognise, prioritise.
- `-our`, never `-or` — colour, favourite, behaviour, honour, neighbour.
- `-re`, never `-er` — centre, theatre, metre.
- **licence** the noun, *license* the verb. **programme** for a broadcast, *program* for
software.
- grey, catalogue, cancelled, labelled, travelling, dialogue, defence, mould.
This covers on-screen copy in Kotlin and `app/src/main/res/values/strings.xml`, the admin
console at `server/internal/api/admin.html`, the release landing page in `dist/template/`,
every string the gateway sends the client to display — row titles,
alert `label`s, the preference and feature catalogues, error messages — and this
repository's own prose and comments.
### What is exempt, and why
**Identifiers and anything on a wire keep the spelling they already have.** Renaming them
breaks the wire or the build:
- Emby's API — `favorites`, `IsFavorite`, `SearchTerm`.
- Android and Compose — `Color`, `fontSize`, `TheaterComedy`, `RecognizerIntent`.
- Go and Kotlin — `synchronized`, and the literal `"request canceled"` in `images.go`,
which is matched against `net/http`'s own error text.
- SPDX and licence names — "GNU General Public License", `GPL-2.0-only`.
- CSS properties — `color`, `text-transform`, `box-sizing`.
The rule is about words a person reads, never tokens a machine matches.
### Where the two meet
The boundary sits at the render. The favourites row is the worked example: the id and the
row `kind` stay `favorites` on both sides of the wire, and `personalisedFavouritesTitle`
puts **"Favourites"** on the screen. When a new string is both a key and a label, spell the
display half and leave the key alone.
A quick sweep before finishing a change that touches copy:
```bash
grep -rnEio '\b([a-z]{3,}iz(e|es|ed|ing|ation)|behaviors?|colors?|favorites?|honor|theater|canceled|labeled|catalog|gray)\b' \
app/src/main server/internal dist
```
Expect hits — most are the exemptions above. Read each one and ask whether a viewer or an
operator ever sees it.
## Version bumps
Every version bump is a release change: commit and push the complete release to GitHub. Do
not leave a bumped version only in the local working tree.
+424
View File
@@ -0,0 +1,424 @@
## 0.3.17 - 2026-08-24
- Fixed: Crash on every cold start of a signed-in television. `MembyIcons`' icon-pack state and `DesignTokens`' palette state are process-wide `mutableStateOf`, and the first thing to touch either was `ThemeSync` applying the cached pack from an IO thread while the launcher was composing — so the state object was created in a snapshot newer than the composition reading it, and the navigation rail's first `MembyIcon.mark` read threw "Reading a state that was created after the snapshot was taken". `MembyApp.onCreate` now creates both on the main thread before anything else in the process can. Invisible on a fresh install because first-run and sign-in draw no marks, which is why clearing app data appeared to fix it.
- Fixed: A television that had just installed the version the gateway demanded opened on the retired-build screen it was updating to escape. `AppRoot` now filters the persisted `requiredUpdateVersion` through `requiredUpdateSatisfied`, the same rule the in-memory signal and `markRequiredUpdate` already apply.
## 0.2.98 - 2026-08-22
- v298 closes the following issues: 116 (Feature: Integrate Memby with Android TV Home Screen), 117 (Improve Row Navigation & Add “View All” Actions) and 115 (Add ratings strip to movie continue watching).
- Added: Continue Watching support on the Google TV launcher homescreen (116)
- Added: Ratings strip to continue watching cards (115)
- Added: Buttons to rows which can take you back to main genre (117).
- Improved: Cleaned up genres chips row on search page.
- Improved: Switched cards to list cards for Search.
- Fixed: RequeststCards.kt shares its component with Search.
- Improved: Bug fixes & improvements to ResumeableMediaCard.kt, ContinueWatchingCard.kt
- Updated: Server updated to v0.1.64
- Publishes partially watched movies/episodes and Next Up episodes through Android TVs native Watch Next API.
- Updates launcher progress from existing playback reports.
- Removes completed, stale, deleted, duplicate, signed-out, or previous-user entries.
- Launcher cards deep-link through a validated activity directly into PlayerActivity, resuming from current Emby/Memby state.
- Uses appropriate episode, series, season, runtime, description, and backdrop/poster metadata.
- Keeps provider failures and offline reconciliation completely supplementary to playback.
- Added an isolated tvhome architecture:
- app/src/main/java/com/ponzischeme89/memby/tvhome/TvHomeIntegrationService.kt
- app/src/main/java/com/ponzischeme89/memby/tvhome/WatchNextPublisher.kt
- app/src/main/java/com/ponzischeme89/memby/tvhome/TvDeepLinkActivity.kt
- app/src/main/java/com/ponzischeme89/memby/tvhome/TvDeepLinks.kt
- app/src/main/java/com/ponzischeme89/memby/tvhome/HomeChannelPublisher.kt
## 0.2.97 - 2026-08-21
- Improved: Modularised MembyButtons, ProfileChooser, SettingsSheet, ViewerControls
- Added: Traliers no longer show controls that are outside of our control (e.g. Youtube)
- Improved: Simplified the Emby exit screen prompt. Moved to EmbyMembyScreen.kt
## 0.2.96 - 2026-08-21
- Improved: Refactor EmbyRepo
- Improved: HomeNavigation.kt
- Added: ServiceLocator.kt
## 0.2.95 - 2026-08-21
- Improved: MetadataHero.kt
- Added: MetadataHero.kt opts to Preferences
## 0.2.94 - 2026-08-21
- Improved: MetadataHero.kt
## 0.2.93 - 2026-08-21
- Fixed: Bugs with HomeOverlay
## 0.2.92 - 2026-08-21
- Improved: App design / layout tweaks.
- Improved: Many behind the scenes tweaks, bug fixes to make the app run smoother.
- Improved: Gateway updated to version 0.1.63.
## 0.2.91 - 2026-08-21
- Fixed: Bugs with HomeComponents/HomeScreen
- Fixed: Bugs with FocusScaleContainer
- Improved: LandscapeCard, ContinueWatchingCard, MediaBadges, MediaCardMetadata, MediaCardSizing, MediaLoading, PosterCard
## 0.2.90 - 2026-08-21
- Fixed: Padding fixes with HomeComponents.kt
## 0.2.89 - 2026-08-21
- Fixed: Responsive row sizing fixes
## 0.2.88 - 2026-08-21
- Fixed: More bug fixes to HomeComponents.kt, HomeScreen.kt.
- Tested: Tests passed with HomeMovieHeroTest.
## 0.2.87 - 2026-08-21
- Fixed: Bugs with HomeComponents.kt
- Fixed: Bugs with HomeOverlays.kt
- Fixed: Bugs with HomeScreen.kt
## 0.2.86 - 2026-08-21
- Fixed: Bugs with HomeComponents
## 0.2.85 - 2026-08-21
- Fixed: Bug with LaunchPreroll
- Fixed: Bug fixes to PrerollPreLoader
- Fixed: Bug which prevented ResumableMediaCardTest from firing
## 0.2.84 - 2026-08-21
- Improved: Loading and performance of the continue watching row.
## 0.2.83 - 2026-08-20
- Fixed: Turning off "Play the next episode" now turns off the next-episode recap or preview with it. With auto-play off but previews on, an episode still cut away two minutes before the end to play a preview and then rolled into the next episode anyway.
- Fixed: "Who's watching?" now appears as soon as your server has viewers switched on, so the first person can actually be added. It was hidden until a second viewer existed, which nothing on the TV could create.
- Improved: The user menu now lists the people on this account, so switching to somebody else is one press instead of a row leading to a separate screen. Your Emby sign-ins moved to "Switch account" just below them, and adding a viewer is a row in the same list.
- Improved: The account chooser says plainly that it adds a separate Emby sign-in, so it is no longer mistaken for the way to add somebody to the account you are already using.
- Improved: "Whos watching?" is centred, evenly spaced, and the profile under focus is obvious from across the room. Names now line up along one baseline whichever one is selected.
- Improved: Removing a user is a small delete mark that appears on the profile you are on — press Down to reach it — rather than a "Remove user" button parked under every profile. It still asks before it removes anything, and cancelling puts you back where you were.
## 0.2.82 - 2026-08-20
- New: Each Memby account can have multiple "profiles" per TV.
## 0.2.81 - 2026-08-19
- Improved: Moved poster media type icon to subtitle on Genres page.
- Fixed: Homepage heroes now update as soon as an administrator changes them.
- Fixed: Show/movie logos were not cached correctly.
- Fixed: Genres list in Genres page sorted by user activity.
## 0.2.80 - 2026-08-19
- New: Hold down on the user switcher to get a "clear all" option for notifications.
- Fixed: Improved navigation behaviour on the TV Shows page.
- Fixed: Design tweaks/fixes with Genres page.
- Removed: Recent Searches feature.
## 0.2.79 - 2026-08-19
- Bug fixes
## 0.2.78 - 2026-08-19
- Removed: Genres row from the TV Series and Movies pages. Replaced by Genres page.
- Improved: Added Mark as Watched button to Series detail pages.
- Improved: In the backend, Intergrations has it's own tab.
## 0.2.77 - 2026-08-19
- New: Films on the "Upcoming Movie releases" shelf now open a page of their own.
- New: A trailer can be played from an upcoming film's page.
- Improved: Backend of notifications now go through one service.
- Improved: Design of Notifications.
- Fixed: A bug where the short indent overlapping at the start of the show.
- Fixed: Bug with Continue Watching not remembering playstate, in some cases.
- Fixed: Backend server fixes.
## 0.2.76 - 2026-08-18
- Fixed: Backend server fixes.
## 0.2.75 - 2026-08-18
- Improved: Upgrade player version.
- Fixed: Backend server fixes.
## 0.2.74 — 2026-08-17
- Fixed: Genre shelves were showing films only. A programme whose details came from TMDb carries its genre as one label — "Sci-Fi & Fantasy", "Action & Adventure", "War & Politics" — which the shelves did not recognise, so every show was missing from Sci-Fi & Fantasy, Action & Adventure and War & History. Shows now appear on them beside the films.
- Improved: Genre shelves recognise more ways of spelling the same genre, including the hyphenated names IMDb uses, so fewer titles are missed. Sport finds more than it did, though a film your server has not tagged with a sport genre at all still cannot appear there.
- Improved: The genre grid is now five titles across.
- New: While browsing genres, each poster carries a small mark in its top corner saying whether it is a film or a TV show, so you can tell them apart where a shelf holds both.
- Improved: The genre you are looking at is picked out in dark green in the genre list, so it stays clear which shelf is on screen once you move down into the posters.
## 0.2.73 — 2026-08-17
- Fixed: Reintroduced Genres.
## 0.2.72 — 2026-08-17
- New: The player now has a Next episode button, so you can move on to the next episode at any point without going back to the programme's page.
- New: A "Play something else" button on the player picks another film from your library and starts it, based on what you have been watching.
- Fixed: The next episode is now found and offered whether or not you have automatic play-next switched on. With it off, the closing-credits panel and the Next episode banner appear as they should and wait for you to press Play, instead of never appearing at all.
- Fixed: Starting the next episode no longer occasionally fails to connect. The stream is now re-established before it is needed rather than reusing one negotiated when the episode began.
- Fixed: Reaching the closing credits, a progress update and a button press arriving together can no longer start the next episode more than once.
- Fixed: Removed the obsolete Back item from the Settings rail now that it no longer exits Settings.
## 0.2.71 — 2026-08-17
- Fixed: Leaving playback now saves the final position immediately and prevents an older progress update from moving the saved playhead backwards.
- Fixed: Playback loading, pre-roll and first-frame screens now keep the selected title's artwork, series logo and episode details correct through previews, auto-advance and screen recreation.
- Fixed: D-pad navigation now moves predictably through the navigation rail and profile menu, with Profiles kept at the top and Settings available from the profile menu.
- Fixed: The TV calendar now displays New Zealand's Monday-first week order.
- Technical fix: Playback reports are serialised, successful stop reports cancel their exact fallback job, and stale stop retries expire before they can overwrite progress made on another device.
- Technical fix: Added configurable daily gateway quiet time, pausing television requests and background work while leaving the admin console and health controls available.
## 0.2.70 - 2026-08-16
- Improvements: Playback "cold start" improvements.
## 0.2.69 - 2026-08-16
- Fixed: Cached recommendation shelves remain visible while fresh recommendations rebuild, avoiding row flicker and recomposition.
- Fixed: “For You” content now loads only when opened, rather than competing with homepage requests.
- Fixed: Detail, related-title, episode and trailer prefetching waits until focus settles, preventing request storms during D-pad navigation.
- Fixed: Homepage artwork preloading is smaller and lifecycle-aware.
- Fixed: My Shows and notifications refresh concurrently.
- Fixed: Diagnostic logging avoids unnecessary production-path work.
- Fixed: Added time-to-interactive and improved frame/jank measurements, including a warm-start benchmark.
## 0.2.67 — 2026-08-16
- Improved: Movie and show pages.
## 0.2.66 - 2026-08-15
- Improved: Skip end credits.
## 0.2.65 - 2026-08-15
- Added: Server now detects end credits more accurately.
## 0.2.64 — 2026-08-14
- Improved: General app bug fixes & polish.
- Fixed: Backend improvements.
## 0.2.63 — 2026-08-14
- Improved: General app bug fixes & polish.
- Improved: Backend.
## 0.2.62 — 2026-08-13
- Fixed: Rolled back version 0.2.60.
## 0.2.60 — 2026-08-12
- Added: Voice search on the Requests page, beside the query, the same as Search.
- Fixed: Remote navigation on the Requests search page. Up through the keyboard now moves a row at a time instead of jumping to the tabs, coming back from the results lands on the key you were last on, and Back steps results → keyboard → tabs → out, one press at a time.
- Added: A USB or phone keyboard types into the Requests search the same way it does into Search.
- Fixed: The navigation rail is now on the Requests page, so Left goes somewhere and you can move straight to Home, Search or Settings without leaving it first.
- Fixed: "Good morning" now greets you while the featured card is up, instead of appearing only once you had pressed Down past it.
- Improved: Notifications shows a bell that quietly pulses while it is listening when you have nothing waiting, and a still grey one when notifications are switched off.
- Changed: Requests no longer searches as you type. Type a name and press Search, so one title costs one look-up instead of one per letter — and two characters is now enough, so short titles work again. Dictating a title searches straight away.
## 0.2.59 — 2026-08-12
- Fixed: Moving up through the on-screen keyboard on the Requests page no longer jumps straight to the tabs — only the top row of letters leaves for them.
- Improved: Requests now waits for five letters, and for a short pause in typing, before looking anything up.
- Fixed: The featured cards at the top of Home no longer lead with a film you have already watched. Series are unchanged, so a new season of something you are up to date with still features.
- Fixed: Saving a setting could fail repeatedly with a server error.
## 0.2.58 — 2026-08-12
- Added: Improved Requests option to the user selector menu.
## 0.2.57 — 2026-08-12
- Fixed: Traliers show the logo from the show in the on screen player vs text.
- Added: Smart search for next episode recap/preview episode from YouTube that plays 2 mins before show end
- Added: Optional setting for next episode recap/preview.
- Fixed: The source IP address for each tralier play should come from the Client's real IP address, not from the server.
## 0.2.56 — 2026-08-12
- Added: Films, series and supported seasons can now play trailers with one press.
- Improved: Memby prefers official Apple and YouTube trailers, plays them natively and automatically tries another source when one is unavailable, blocked or fails during playback.
- Fixed: Trailer playback no longer needs a second Play press, opens a broken web player or leaves the viewer on a generic playback error.
- Fixed: Back now returns cleanly to the detail page after a trailer, and Memby returns there automatically when no playable source remains.
- Improved: Successful trailer sources are remembered for faster repeat playback without unnecessary provider requests.
- Added: Selected labels, feature switches and safe presentation settings can now be managed by the server, while complete bundled defaults keep the app working normally when the server is slow or unavailable.
- Improved: Search now has a dedicated Request action for finding the exact film or series to add, with clearer in-library and already-requested states.
- Fixed: Film and series requests now use the intended monitored request workflow and provide clearer results when a title cannot be requested.
## 0.2.53 — 2026-08-11
- Fixed: Removed the home-screen message asking viewers to open the For You page.
- Improved: The play button used by the mini hero cards now appears over every focused poster.
- Improved: Reduced the primary homepage hero logo so its supporting details remain visible.
- Improved: The profile rail now shows the name of the user signed in on the television.
## 0.2.52 — 2026-08-11
- Fixed: The last refreshed date in the Devices list used the incorrect date format.
- Fixed: The show or film logo was missing from the primary home-page hero.
- Fixed: My Alerts did not include an alert when Memby had been updated.
- Fixed: The release date in version history used the incorrect date format.
- Improved: Added soundbar and passthrough settings to the on-screen display.
- Improved: Slimmed down the subtitles menu and made the Get subtitles button easier to see.
- Improved: Added country flags to the subtitles search and menu.
## 0.2.51 — 2026-08-11
- Improved: For You now begins loading after Home arrives, so its personalised rows are usually ready before you open it.
- Fixed: Frequent screen updates can no longer repeat network requests, navigation, analytics, timers or other background work.
- Fixed: Cancelled Home, For You, Search, detail and screensaver requests can no longer replace newer results or show a stale error.
- Fixed: App updates now keep one download and installer attempt across screen and activity recreation instead of starting again.
- Fixed: Settings choices are saved even when you leave Settings immediately after making them.
- Fixed: Delayed D-pad focus, long-press, pagination, retry and permission actions now use the current screen state rather than an older callback.
- Improved: Screen data is now collected with the active lifecycle, avoiding unnecessary work while a screen is stopped.
## 0.2.50 — 2026-08-11
- Fixed: Playback reports are now serialised, and final stop positions are protected from out-of-order requests.
- Fixed: Home, preferences, themes and detail screens now recover automatically with bounded retries.
- Fixed: Retries can no longer cause request storms, duplicate media requests or repeated alert actions.
- Fixed: Useful cached content remains visible when refreshes or later result pages fail.
- Fixed: Network requests now have explicit loading, success, empty and failure states, with retry actions where appropriate.
- Fixed: HTTP 429 responses now respect the server's Retry-After delay.
- Fixed: Malformed and partially populated objects are safely filtered before reaching the interface.
- Fixed: Rate limits, partial payloads and durable playback stops now have regression coverage.
- Fixed: Resuming playback now cancels delayed background stop work before reporting the session as active again.
- Fixed: Refreshed streams and subtitle-driven stream changes now close superseded playback sessions and start their replacements in order.
- Fixed: Cancelled or stale stream, retry and subtitle requests can no longer overwrite a newer playback attempt.
- Fixed: Enabling burned-in subtitles no longer lets the hidden video continue and then jumps back to an earlier position when the stream is ready.
- Fixed: Playback now waits for a slow next-episode lookup, and auto-advance resets subtitle, preview, intro, credits, artwork and session state for the new episode.
- Fixed: Player construction and synchronous media preparation failures now show recoverable Retry and Exit actions instead of closing the player.
- Fixed: The pre-roll player remains idle while the main programme is playing, avoiding a second prepared decoder after hand-off.
- Fixed: Repeated screensaver Play presses are gated, and a new single-task player intent now deliberately replaces the current playback.
- Fixed: Playback position, stream identity, selected tracks and episode details now survive activity and process recreation.
- Fixed: Natural playback completion now returns to the screen that launched the player instead of clearing the task back to Home.
## 0.2.49 — 2026-08-11
- Fixed: D-pad focus now stays visible and predictable across My Shows, Settings, profile management, the TV calendar, detail pages and changing home rows.
## 0.2.48 — 2026-08-11
- Improved: The TV calendar is now a modern weekly guide with artwork, title logos and clear season-premiere and finale labels.
- Added: Home-screen metadata now shows the sound profile, including mono, stereo, 5.1 and 7.1.
## 0.2.47 — 2026-08-11
- Fixed: A television signed out because its version has been retired now stays on the update screen — across a restart, and while the server is slow to answer — instead of falling back to a welcome and sign-in it could never get past.
## 0.2.46 — 2026-08-11
- Added: A TV calendar on the navigation rail — the month ahead as a grid, with the focused day's episodes beside it, so you can see when a show comes back.
- Added: A television signed out because its version has been retired now goes straight to the update screen, instead of showing a sign-in that could never succeed.
- Improved: In Settings, pressing Up at the top of a page returns to the page list rather than doing nothing.
- Fixed: The home screen could close itself when a row or a card arrived twice from the server. The same fault could close the cast list, search results, similar titles and episode lists.
- Fixed: Pressing down through the rows on Home and TV Shows could stop part way and refuse to go any further until you left the page.
- Fixed: Play could stop responding for the rest of the session if starting a title failed early, with nothing on screen to say why.
- Fixed: A title the server could not supply a stream for now says so, instead of flickering and returning to where you were.
- Fixed: Back now leaves the "starting playback" screen, rather than waiting out a server that has stopped answering.
- Fixed: Dismissing one of your alerts takes effect immediately, so a slow connection no longer invites a second press.
- Improved: Genre and search shelves no longer stop loading further pages when the server repeats a title across a page boundary.
- Improved: Memby uses less memory on a television left on for weeks at a time.
## 0.2.45 - 2026-08-10
- Bug fixes
## 0.2.43 — 2026-08-10
- Fixed: App no longer crashes.
- Fixed: Manual surround-sound choices now reliably override the automatically detected audio output.
## 0.2.42 — 2026-08-10
- Added: Memby's own opening clip now plays while the app is starting up.
- Added: Surround sound passthrough for Dolby Digital, Dolby Digital Plus, Dolby Atmos, DTS, DTS-HD and TrueHD, with automatic detection and manual receiver overrides.
- Improved: Unsupported surround tracks are decoded to PCM on the TV, and audio conversion no longer makes Emby re-encode otherwise compatible video.
- Fixed: Colour schemes now repaint the whole app. The home screen, Settings, Search and the opening screen only followed a theme in part.
- Improved: A far wider range of welcome lines and opening messages, so the same one comes round much less often.
- Improved: The opening screen is no longer rebuilt as Memby moves between start-up steps.
## 0.2.40 — 2026-08-10
- Improved: Video playback recovers more reliably from decoder failures and prolonged buffering.
- Improved: Memby's short pre-roll is prepared in advance for faster playback starts.
- Added: A libmpv compatibility fallback for videos Media3 cannot decode or read.
- Fixed: Shows opened from the upcoming schedule now resume from the viewer's latest episode progress.
## 0.2.39 — 2026-08-09
- Improved: HEVC playback performance
- Added: Cast panels now mark deceased performers and open biographies and filmographies.
- Added: An optional confirmation before the Back button closes Memby.
- Improved: Reviewed D-pad navigation across the Android TV experience and documented follow-up work.
- Fixed: Continue Watching now keeps its saved position and shows Resume instead of Play.
## 0.2.38 — 2026-08-09
- Improved: Ratings are larger and easier to read.
- Improved: Genre cards now use the same poster format as home-screen rows.
- Improved: The Memby rail logo now animates when opening every section.
- Improved: The user switcher now shows each profile's initials.
- Added: The About screen now shows the connected Emby gateway version.
- Changed: The genre browser is now controlled by the server and is off by default while its layout is refined.
- Fixed: The automatic home-screen artwork setting no longer reverts to posters.
- Fixed: Watched changes now appear on posters immediately, just like favourite changes.
## 0.2.37 — 2026-08-09
- Improved: Browse films and TV shows from colourful genre cards with clearer, consistent categories.
- Improved: Genre pages now keep their place and show Favourite changes immediately after returning from a title.
- Improved: App updates now show a brief confirmation instead of a full-screen release-notes panel.
- Fixed: Episodes without their own cast information now use the series cast in the player.
## 0.2.36 — 2026-08-09
- Improved: Browse all movies or TV shows alongside individual genres, with clearer navigation and paging.
## 0.2.35 — 2026-08-09
- General bug fixes and improvements.
## 0.2.34 — 2026-08-09
- General bug fixes and improvements.
## 0.2.33 — 2026-08-08
- General bug fixes and improvements.
## 0.2.32 — 2026-08-08
- General bug fixes and improvements.
- Backend improvements.
## 0.2.31 — 2026-08-08
- General bug fixes and improvements.
## 0.2.30 — 2026-08-08
- General bug fixes and improvements.
## 0.2.29 — 2026-08-07
- Fixed: A small bug with new seek previews.
- Fixed: Skip Intro and Outro Credits were not appearing in some cases.
## 0.2.28 — 2026-08-07
- Added: Skip outro credits
- Added: Themes for Halloween, Christmas and Easter.
## 0.2.27 — 2026-08-06
- Added: Skip Intro button
- Fixed: Player improvements & bug fixes
- Fixed: Gateway bug fixes & improvements
- Fixed: Homepage hero relevancy
- Fixed: Related titles showing up empty often
- Fixed: You can add another user to the television again, from Manage users
## 0.2.26 — 2026-08-05
- Improved: Ratings are more accurate and easier to read.
- Fixed: Continue Watching now keeps the right order as you watch.
- Fixed: Film and show pages no longer display incorrectly.
- Fixed: The loading screen no longer appears when you skip forward or back.
## 0.2.25 — 2026-08-05
- Added: Left and right on the remote skip back and forward while you watch.
- Added: You can choose whether one press skips 10, 20 or 30 seconds.
- Added: You can search for and add subtitles without leaving what you are watching.
- Added: A cast button in the player shows who is in the scene.
- Added: Episode lists now show when each episode aired.
- Improved: Ratings now show the IMDb, Rotten Tomatoes, Metacritic, Letterboxd and TMDb logos.
## 0.2.24 — 2026-08-05
- Fixed: The centre button on the remote pauses straight away instead of taking two presses.
- Fixed: Reinstalling Memby on a television replaces it in your devices list instead of adding another one.
- Fixed: Removing a television from Devices now clears it from Emby as well.
- Improved: The subtitle menu is quicker to read and easier to move around.
## 0.2.23 — 2026-08-04
- Added: Turning subtitles off, and the language you pick, are saved to your account.
- Added: Tags showing whether a show is continuing, has ended, or is still in cinemas.
- Improved: Settings is simpler, darker and easier to read.
- Fixed: The Play button on the Home screen is no longer squashed by long titles.
## 0.2.22 — 2026-08-04
- Fixed: Updates no longer fail with a warning about an untrusted release key.
- Fixed: Updates now install on televisions where they previously did nothing.
- Added: Memby asks for permission to install updates while you are setting up.
- Added: Instructions for televisions that have no install permission screen.
- Improved: You are now told clearly when an update fails.
## 0.2.20 — 2026-08-04
- Added: A warning appears when Memby cannot reach your Emby server.
- Added: Your settings are saved to your account and restored on any television you sign into.
- Added: Title logos, auto-play and the ten-minute reminder are saved for each person.
## 0.2.17 — 2026-08-03
- Added: Episodes in Continue Watching now open their own page.
- Improved: Resume buttons show which episode you are up to.
## 0.2.16 — 2026-08-03
- Added: You can rename or sign out televisions from Settings → Devices.
- Added: Optional ratings on film pages.
- Added: A version history in About, so you can see what changed.
- Improved: The navigation rail stays visible while you are in Settings.
## 0.2.0 — 2026-05-01
- Added: Instant search with an on-screen keyboard.
- Added: Alerts for newly added titles, library updates and server outages.
- Added: Personalised "Why you might enjoy it" suggestions on film and show pages.
- Improved: Film and series pages now share one layout.
- Improved: Home screen rows can change without updating the app.
+3372 -26
View File
File diff suppressed because it is too large Load Diff
+340
View File
@@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
+79
View File
@@ -0,0 +1,79 @@
Memby
Copyright (C) 2026 Memby contributors
Memby is free software licensed under the GNU General Public License,
version 2. A copy of that licence is included in the LICENSE file.
Source code:
https://g.sublogue.com/admin/memby
Wholphin
--------
Portions of Memby's Android playback capability probing, server-side
device-profile generation, and ExoPlayer construction (constant-bitrate
seeking and sourcing media over the application's own OkHttp client, in
app/src/main/java/com/ponzischeme89/memby/ui/player/PlayerEngine.kt), and the
design of its seek preview thumbnails
(app/src/main/java/com/ponzischeme89/memby/ui/player/TrickplayPreview.kt), are
adapted from Wholphin, an Android TV client licensed under GNU GPL version 2:
https://github.com/damontecres/Wholphin
Wholphin's profiling implementation is itself derived from Jellyfin Android TV:
https://github.com/jellyfin/jellyfin-androidtv
The adapted Memby files identify both upstream projects, identify Memby's 2026
modifications, and remain under GPLv2. Copyright in the upstream work remains
with its respective contributors.
Memby and Wholphin are both licensed under GPL version 2, so adapting source
from Wholphin into Memby is permitted provided the combined work stays under
that licence and attribution is preserved — which is what this file does. No
Wholphin source is copied verbatim into Memby; the files listed above reproduce
architectural techniques, and where an approach was reproduced independently
rather than adapted it is recorded as such in PERFORMANCE.md.
Moonfin
-------
Memby's surround-audio capability probe, Auto/manual per-codec passthrough
rules, Media3 audio-sink configuration, and software-decode fallback are adapted
from Moonfin's Android TV playback backend, licensed under GNU GPL version 2:
https://github.com/Moonfin-Client/Moonfin-Core
The adapted files identify Moonfin, identify Memby's 2026 modifications, and
remain under GPLv2. Copyright in the upstream work remains with the Moonfin
contributors.
Third-party software
--------------------
Inter
-----
Memby bundles the Inter font family, copyright 2016 The Inter Project Authors
(https://github.com/rsms/inter), under the SIL Open Font License, Version 1.1.
The complete font licence is packaged at assets/licenses/inter_ofl.txt.
The Android application uses AndroidX, Jetpack Compose, Media3, Coil,
Retrofit, OkHttp, kotlinx.serialization, and other open-source libraries.
Their copyright notices and licence metadata remain in their respective
source distributions and packaged dependency metadata. Those components
remain governed by their own licences; GPLv2 applies to Memby's original
and combined application code as required by the licence.
The Media3 software audio fallback uses Jellyfin's FFmpeg decoder extension:
https://github.com/jellyfin/jellyfin-androidx-media
Memby is an independent project and is not affiliated with or endorsed by
Emby LLC. Emby is a trademark of Emby LLC.
The marks shown beside each score in the ratings strip
(app/src/main/res/drawable-nodpi/ic_rating_*.png) identify the rating provider
a score came from. IMDb, Rotten Tomatoes, Metacritic, Letterboxd and TMDb are
trademarks of their respective owners, used here only to attribute those
scores. Memby is not affiliated with, endorsed by, or certified by any of them.
+236 -100
View File
@@ -1,53 +1,129 @@
# Memby (Android TV) <div align="center">
An independent Android TV client for Emby, by **ponzischeme89**. Sign in to browse your <img src="memby-icon.png" alt="Memby" width="128" height="128">
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. # 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.
[![Licence: GPL v2](https://img.shields.io/badge/licence-GPL--2.0-green.svg)](LICENSE)
[![Platform](https://img.shields.io/badge/platform-Android%20TV-green.svg)](#build--install)
[![Min SDK](https://img.shields.io/badge/minSdk-23-green.svg)](#build--install)
[![Kotlin](https://img.shields.io/badge/Kotlin-Compose%20for%20TV-green.svg)](#tech-stack)
[![Gateway](https://img.shields.io/badge/gateway-Go%20%2B%20Postgres%20%2B%20Redis-green.svg)](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 ## Features
- **System screensaver** via `DreamService` — auto-starts on idle once selected in the ### Browse and discover
TV's Screensaver settings. Also previewable from the app's home screen.
- Random, cross-fading **backdrops** of Movies and Series that have backdrop images. - Personalised, server-composed Home rows with a cached instant-start launcher.
- **Interactive**: OK reveals Play / Favorite actions; ◄ ► change the backdrop; Back exits. - A cinematic featured hero that rotates through ranked films and series premieres.
- **In-app playback** with Media3/ExoPlayer (movies play directly; a series plays its - Continue Watching merged with Next Up, ordered by the viewer's recent activity.
next-up / first episode). - Favourites, latest films, recommendations, curated shelves and schedule rows.
- **Favorites** managed through the Emby API and shown on the home screen. - 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 ## Tech stack
- Kotlin + Jetpack **Compose for TV** (`androidx.tv:tv-material`) | Layer | Built with |
- **Media3 / ExoPlayer** for playback | --- | --- |
- Retrofit + OkHttp + kotlinx.serialization for the Emby REST API | UI | Kotlin + Jetpack **Compose for TV** (`androidx.tv:tv-material3`) |
- DataStore for persisted connection/session | Playback | **Media3 / ExoPlayer** |
- Coil for backdrop image loading | Networking | Retrofit + OkHttp + kotlinx.serialization, over one shared HTTP stack |
| Storage | DataStore for profiles, settings and cached launcher state |
## Project layout | Images | Coil |
| Gateway | Go, with Postgres and Redis |
```
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 ## Build & install
@@ -62,7 +138,9 @@ terminal:
"sdk.dir=C:\\Users\\<you>\\AppData\\Local\\Android\\Sdk" | Out-File -Encoding ascii local.properties "sdk.dir=C:\\Users\\<you>\\AppData\\Local\\Android\\Sdk" | Out-File -Encoding ascii local.properties
.\gradlew.bat assembleDebug # build the APK .\gradlew.bat assembleDebug # build the APK
.\gradlew.bat test # JVM unit tests
.\gradlew.bat installDebug # install to a connected Android TV / emulator .\gradlew.bat installDebug # install to a connected Android TV / emulator
# Preferred for a TV already showing the Dream: clears Memby, installs, then reopens # 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 # it so the old render surface cannot remain black. Pass -Serial when more than one
# device is attached. # device is attached.
@@ -71,28 +149,6 @@ terminal:
The APK lands in `app/build/outputs/apk/debug/app-debug.apk`. 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 ## Server address
Memby is built for one Emby server, so the address is baked into the APK instead of being Memby is built for one Emby server, so the address is baked into the APK instead of being
@@ -105,11 +161,11 @@ 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 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`. build: `.\gradlew.bat assembleDebug -Pmemby.serverUrl=http://192.168.1.10:8096`.
The value becomes `BuildConfig.EMBY_SERVER_URL`, read through The value becomes `BuildConfig.EMBY_SERVER_URL`, read through `data/ServerConfig.kt`. When
`data/ServerConfig.kt`. When it is set, the setup screen only asks for a username and it is set, the setup screen only asks for a username and password, and the address wins
password, and the address wins over whatever a saved session recorded — so moving the over whatever a saved session recorded — so moving the server is a property change plus a
server is a property change plus a reinstall, with no user action. Leaving the property reinstall, with no user action. Leaving the property **blank** restores the original
**blank** restores the original behaviour: users type the address themselves. behaviour: users type the address themselves.
## First run ## First run
@@ -119,16 +175,60 @@ server is a property change plus a reinstall, with no user action. Leaving the p
3. **Preview screensaver** to test it, or **Set as system screensaver** to open the TV's 3. **Preview screensaver** to test it, or **Set as system screensaver** to open the TV's
screensaver settings and choose "Memby Screensaver". 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
```
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)
admin-ui/ The operations console (React, served by nginx)
server/ The Memby gateway (Go) — see server/README.md
benchmark/ Macrobenchmarks and baseline-profile generation
```
## Distributing builds ## Distributing builds
Memby is handed out as an APK from your own web server or NAS, and updates itself from Memby is handed out as an APK from your own web server or NAS, and updates itself from the
the same folder. same folder.
### One-time: a signing key ### One-time: a signing key
Android identifies an app by `applicationId` **plus signing key**. Every update must be 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 signed with the *same* key, or the TV rejects it as a different app. Lose the key and every
every user has to uninstall and reinstall. user has to uninstall and reinstall.
```powershell ```powershell
keytool -genkeypair -v -keystore memby-release.jks -alias memby ` keytool -genkeypair -v -keystore memby-release.jks -alias memby `
@@ -145,22 +245,42 @@ memby.keyAlias=memby
memby.keyPassword= memby.keyPassword=
``` ```
Without these, `assembleRelease` still builds but the APK is unsigned and will not Without these, `assembleRelease` still builds but the APK is unsigned and will not install.
install. The build prints a warning saying so. The build prints a warning saying so.
### Each release ### NAS deployment release (recommended)
`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`. 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)
```powershell ```powershell
.\release.ps1 -Version 0.1.54 -Notes "Faster home screen" -BaseUrl https://nas.example.com/memby .\release.ps1 -Version 0.1.54 -Notes "Faster home screen" `
-BaseUrl https://nas.example.com/memby `
-SourceUrl https://g.sublogue.com/admin/memby
``` ```
That bumps `versionName`/`versionCode`, runs the tests, builds a signed APK, and fills That workflow bumps `versionName`/`versionCode`, runs the tests, builds a signed APK, and
`dist/out/` with: fills `dist/out/` with:
``` ```
index.html the page people are sent to index.html the page people are sent to
latest.json the update manifest the app polls latest.json the update manifest the app polls
memby-0.1.54.apk the build 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` Copy those to the folder the NAS serves. Old APKs can stay alongside — only `latest.json`
@@ -168,13 +288,11 @@ decides what the app offers, so rolling back is editing one file.
### How TVs update themselves ### How TVs update themselves
In Memby's Settings, set the update URL to `https://nas.example.com/memby/latest.json`. The gateway checks the running app version on launch and returns an optional or mandatory
**Check for updates** then downloads and installs on the TV, no computer involved. A URL update with its download address. Memby downloads the APK, verifies its checksum, package
ending in `.json` is read as a static manifest; anything else is treated as a Gitea host and release signature, then commits it through Android's package installer. Update policy
(`/api/v1/repos/{owner}/{repo}/releases/latest`), so either source works. and the download address are managed in the gateway's admin console; viewers do not need to
configure an update source on the television.
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 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. landing page is the usual route, and the page explains it.
@@ -188,17 +306,35 @@ different app, so on every TV:
1. Install v0.1.53 — it appears as a **second** Memby entry in the launcher. 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. 2. Sign in again; the previous session does not carry over.
3. Uninstall the old app: `adb uninstall com.mattcohen.embyclientsname`. 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 4. Re-select Memby in the TV's Screensaver settings — the Dream's component name changed as
as well, so the old selection no longer resolves. well, so the old selection no longer resolves.
## Notes & limitations ## Notes & limitations
- Playback uses Emby's direct stream (`/Videos/{id}/stream?static=true`). This direct-plays - Playback negotiates through Emby's `PlaybackInfo` endpoint, then streams directly from
containers/codecs ExoPlayer supports (most MP4/H.264, many MKV). Server-side transcoding Emby. The Wholphin/Jellyfin-derived capability engine reports Android's H.264 and HEVC
is not requested; unusual codecs may need it — a future enhancement is to call Emby's profiles, maximum levels and resolutions rather than assuming every decoder handles every
`PlaybackInfo` endpoint and use the returned HLS transcode URL (the `media3-exoplayer-hls` file. The Moonfin-derived audio path reports formats the TV can decode or bitstream,
dependency is already included). offers automatic receiver detection and manual per-codec passthrough overrides, and
- Cleartext HTTP is enabled so local `http://` servers work out of the box. For an HTTPS-only decodes the rest to PCM through Media3's FFmpeg renderer. Emby can preserve a supported
server this is unnecessary but harmless. 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. - 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.
+5
View File
@@ -0,0 +1,5 @@
# The Dockerfile copies only the manifest and source files it needs. Keeping these
# out of the build context avoids sending a local development install or stale bundle
# to the daemon before Node creates its clean, reproducible build-stage install.
node_modules
dist
+33
View File
@@ -0,0 +1,33 @@
# syntax=docker/dockerfile:1
# The Memby operations console.
#
# Two stages: Node builds the static bundle, nginx serves it. Nothing from Node survives
# into the running image — the console is a directory of files, and the container's only
# job is to hand them over on the compose network. It is never published: the gateway
# reverse-proxies /admin to it, so the console shares the gateway's origin, cookie and
# single ingress.
FROM node:22-alpine AS build
WORKDIR /ui
# Dependencies first so an edit to the source does not re-resolve the whole tree.
COPY package.json package-lock.json* ./
# `npm ci` when there is a lockfile — reproducible, and the only reason a lockfile is
# checked in — falling back to `npm install` so a first build works without one.
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
COPY tsconfig.json vite.config.ts index.html ./
COPY src ./src
# The type check is part of the build rather than a separate step: a console that compiles
# to JavaScript but reads a field the gateway does not send is exactly the failure the
# hand-written wire types exist to catch, and it must not reach a deployment.
RUN npm run build
FROM nginx:1.27-alpine
# Read-only, non-root and no default site: the image serves one directory and does nothing
# else. nginx's own temp paths are the only writable thing it needs, and they are declared
# as tmpfs in docker-compose.yml.
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /ui/dist /usr/share/nginx/html
EXPOSE 80
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
import"./router-D9WH5XEU.js";
+23
View File
@@ -0,0 +1,23 @@
<!doctype html>
<html lang="en-NZ">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<!-- The console is behind an admin token and is nobody's search result. -->
<meta name="robots" content="noindex, nofollow" />
<meta name="color-scheme" content="dark" />
<meta name="theme-color" content="#010409" />
<title>Memby admin</title>
<!-- An SVG favicon, inline, because the console must fetch nothing from anywhere. -->
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='16' fill='%2352b54b'/%3E%3Ctext x='16' y='23' font-family='system-ui,sans-serif' font-size='19' font-weight='800' text-anchor='middle' fill='%2306240a'%3EM%3C/text%3E%3C/svg%3E"
/>
<script type="module" crossorigin src="/admin/assets/index-D2yWw-VA.js"></script>
<link rel="modulepreload" crossorigin href="/admin/assets/router-D9WH5XEU.js">
<link rel="stylesheet" crossorigin href="/admin/assets/index-BIMcejkS.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
+21
View File
@@ -0,0 +1,21 @@
<!doctype html>
<html lang="en-NZ">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<!-- The console is behind an admin token and is nobody's search result. -->
<meta name="robots" content="noindex, nofollow" />
<meta name="color-scheme" content="dark" />
<meta name="theme-color" content="#010409" />
<title>Memby admin</title>
<!-- An SVG favicon, inline, because the console must fetch nothing from anywhere. -->
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='16' fill='%2352b54b'/%3E%3Ctext x='16' y='23' font-family='system-ui,sans-serif' font-size='19' font-weight='800' text-anchor='middle' fill='%2306240a'%3EM%3C/text%3E%3C/svg%3E"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+75
View File
@@ -0,0 +1,75 @@
# The console's own server. It sees only requests the gateway has already authenticated
# and forwarded, so there is no auth here and there must not be, or there would be two
# places deciding who may read the console.
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
# The gateway proxies /admin/* through untouched, so the paths arriving here carry that
# prefix. Vite is built with base=/admin/, so the asset URLs in index.html match.
location /admin/assets/ {
alias /usr/share/nginx/html/assets/;
# Hashed filenames: a changed file is a changed name, so this can be cached hard
# and a deployment is picked up the moment the shell is re-fetched.
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Canonicalise the console root even though the gateway normally performs this
# redirect before proxying. It keeps the static container correct if it is ever
# reached directly during diagnosis or through a future internal proxy change.
location = /admin {
return 308 /admin/;
}
# Every other /admin path is a console route /admin/logins, /admin/devices/tv-1, a
# deep link, a refresh, the Back button and none of them exist as files. They all
# answer with the shell, which is what makes client-side routing work on a reload.
location /admin {
try_files $uri @console_shell;
# The shell must never be cached: it carries the asset hashes, so a stale copy
# points at JavaScript a deployment has already replaced. The gateway sets this too
# on the way past; it is stated in both places because either alone would be enough
# to get wrong.
add_header Cache-Control "no-store" always;
}
# `try_files ... /index.html` would internally re-match the fallback against the
# catch-all `location /` below, which deliberately returns 404. A named location
# retains this server's static root and serves the shell without exposing /index.html
# as a second public route.
location @console_shell {
try_files /index.html =404;
add_header Cache-Control "no-store" always;
}
# A liveness probe for docker-compose. Deliberately outside /admin so it cannot be
# confused with a console route, and it answers without touching the filesystem.
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
# Nothing else is served. The console is the only thing in this image.
location / {
return 404;
}
# The console fetches nothing from anywhere: no CDN, no fonts, no analytics. Saying so
# in a policy means a dependency that tried to would fail loudly during development
# rather than quietly phone home from an operator's browser.
#
# 'unsafe-inline' for styles is Vite's injected critical CSS; connect-src 'self' covers
# the API and the notification stream, both of which are same-origin by construction.
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
}
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../baseline-browser-mapping/dist/cli.cjs" "$@"
else
exec node "$basedir/../baseline-browser-mapping/dist/cli.cjs" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\baseline-browser-mapping\dist\cli.cjs" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
} else {
& "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
} else {
& "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../browserslist/cli.js" "$@"
else
exec node "$basedir/../browserslist/cli.js" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\browserslist\cli.js" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../browserslist/cli.js" $args
} else {
& "node$exe" "$basedir/../browserslist/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../esbuild/bin/esbuild" "$@"
else
exec node "$basedir/../esbuild/bin/esbuild" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esbuild\bin\esbuild" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args
} else {
& "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../esbuild/bin/esbuild" $args
} else {
& "node$exe" "$basedir/../esbuild/bin/esbuild" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../jsesc/bin/jsesc" "$@"
else
exec node "$basedir/../jsesc/bin/jsesc" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\jsesc\bin\jsesc" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../jsesc/bin/jsesc" $args
} else {
& "$basedir/node$exe" "$basedir/../jsesc/bin/jsesc" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../jsesc/bin/jsesc" $args
} else {
& "node$exe" "$basedir/../jsesc/bin/jsesc" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../json5/lib/cli.js" "$@"
else
exec node "$basedir/../json5/lib/cli.js" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\json5\lib\cli.js" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../json5/lib/cli.js" $args
} else {
& "node$exe" "$basedir/../json5/lib/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../loose-envify/cli.js" "$@"
else
exec node "$basedir/../loose-envify/cli.js" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\loose-envify\cli.js" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../loose-envify/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../loose-envify/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../loose-envify/cli.js" $args
} else {
& "node$exe" "$basedir/../loose-envify/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../nanoid/bin/nanoid.cjs" "$@"
else
exec node "$basedir/../nanoid/bin/nanoid.cjs" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nanoid\bin\nanoid.cjs" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
} else {
& "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
} else {
& "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../@babel/parser/bin/babel-parser.js" "$@"
else
exec node "$basedir/../@babel/parser/bin/babel-parser.js" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\@babel\parser\bin\babel-parser.js" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
} else {
& "$basedir/node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
} else {
& "node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../rollup/dist/bin/rollup" "$@"
else
exec node "$basedir/../rollup/dist/bin/rollup" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\rollup\dist\bin\rollup" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../rollup/dist/bin/rollup" $args
} else {
& "$basedir/node$exe" "$basedir/../rollup/dist/bin/rollup" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../rollup/dist/bin/rollup" $args
} else {
& "node$exe" "$basedir/../rollup/dist/bin/rollup" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../semver/bin/semver.js" "$@"
else
exec node "$basedir/../semver/bin/semver.js" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\semver\bin\semver.js" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
} else {
& "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../semver/bin/semver.js" $args
} else {
& "node$exe" "$basedir/../semver/bin/semver.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
else
exec node "$basedir/../typescript/bin/tsc" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsc" %*

Some files were not shown because too many files have changed in this diff Show More