95 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
3452 changed files with 1191969 additions and 15993 deletions
+91 -18
View File
@@ -7,6 +7,20 @@ MEMBY_EMBY_URL=https://molise.bounceme.net
# above — video goes device -> Emby directly, never through the gateway.
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.
# openssl rand -base64 24
POSTGRES_PASSWORD=7dfc3eb07108013c7bea9787457397551b5eb9059adac51aea4741f4ec649be2
@@ -14,10 +28,12 @@ POSTGRES_PASSWORD=7dfc3eb07108013c7bea9787457397551b5eb9059adac51aea4741f4ec649b
# Fixed NAS host port used by the mserver.sublogue.com reverse proxy.
MEMBY_PORT=32768
# INFO is recommended. DEBUG also logs successful health, status and artwork requests,
# playback progress reports and search terms.
# 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
@@ -34,6 +50,10 @@ MEMBY_RECOMMEND_TTL=24h
# MEMBY_RECOMMENDATION_WEIGHTS={"ExplorationRate":0.08,"MinimumEvidence":2,"ImpressionPenalty":0.12}
MEMBY_RECOMMENDATION_WEIGHTS=
# Optional complete Remote Config document. Leave blank for bundled-equivalent defaults.
# 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
@@ -41,12 +61,16 @@ MEMBY_RECOMMENDATION_WEIGHTS=
# openssl rand -hex 32
MEMBY_ADMIN_TOKEN=4fad67d508558efee5cc5ae05694105421d4c79d35ee2333a817b3791235cd8c
# Public gateway address and a dedicated token used only by the Gitea release workflow.
# Generate the token with: openssl rand -hex 32
# 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_RELEASE_PUBLISH_TOKEN=4b0a891382f056d6ab2ddb43ad08fbbadda0a5a7db9371c7d90fd57b38d083d2
MEMBY_SECRETS_DIR=/share/Docker/Memby-secrets
# Library import. Hourly incremental keeps up with episodes added through the day.
# 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_ON_START=false
@@ -61,21 +85,36 @@ MEMBY_SYNC_API_KEY=56775917938841e7ac1b6a233d4d5075
MEMBY_SONARR_URL=http://10.0.0.2:8989
MEMBY_SONARR_API_KEY=6b608b051ee24582925773bd5dfbe37a
MEMBY_SONARR_TTL=5m
# How long after an episode airs the "aired, coming soon" banner keeps being offered.
# 0 turns the banners off and leaves the airing-today row alone.
# 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 are selected strictly from
# Radarr's digital release date; theatrical and physical dates are ignored.
# 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 "On Import" webhook, which announces a newly added film on
# every TV that is awake. In Radarr: Settings > Connect > + > Webhook, On Import only,
# URL https://<public gateway>/hooks/radarr?token=<this value>. Empty makes the hook 404.
# 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 an imported film keeps being announced, so a TV switched on shortly after the
# import still hears about it. 0 turns the banners off.
# 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
@@ -89,11 +128,11 @@ MEMBY_RADARR_ALERT_WINDOW=3h
# 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
#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
#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.
@@ -104,6 +143,13 @@ MEMBY_BAZARR_TIMEOUT=45s
# 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/
@@ -111,6 +157,33 @@ 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
+9
View File
@@ -57,6 +57,14 @@ jobs:
./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:
@@ -67,6 +75,7 @@ jobs:
-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
+12
View File
@@ -23,8 +23,20 @@ local.properties
# Release artefacts and signing material. The keystore must never be committed:
# whoever holds it can publish an update that installs over Memby.
/dist/out/
*.apk
*.jks
*.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.
+6 -8
View File
@@ -16,10 +16,9 @@ Read it first. This file carries the rules that are easiest to break without not
software.
- grey, catalogue, cancelled, labelled, travelling, dialogue, defence, mould.
This covers on-screen copy in Kotlin and `app/src/main/res/values/strings.xml`,
`CHANGELOG.md` (the TV renders it twice — Settings → About, and the what's-new panel after
an update), 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,
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.
@@ -48,7 +47,7 @@ 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 CHANGELOG.md
app/src/main server/internal dist
```
Expect hits — most are the exemptions above. Read each one and ask whether a viewer or an
@@ -56,6 +55,5 @@ operator ever sees it.
## Version bumps
Every version bump is a release change: update the version and changelog together, then
commit and push the complete release to GitHub. Do not leave a bumped version only in the
local working tree.
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.
+312
View File
@@ -1,6 +1,318 @@
## 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.
+1718 -152
View File
File diff suppressed because it is too large Load Diff
-216
View File
@@ -1,216 +0,0 @@
# Design fixes — home screen and detail pages
Audit of 2026-08-01. Findings only; no product code was changed. The three items under
"Confirmed layout bugs" were reproduced by rendering the real composables at TV 1080p
(`w960dp-h540dp-television-xhdpi`) through the existing Roborazzi harness — everything
else is read from the source.
**All of it is implemented.** The findings below are left as written — they are the
diagnosis, and each one says why the fix is shaped the way it is. What was done:
| # | Fixed in |
|---|----------|
| 1 | `HomeMovieHero.kt` — column padding 28→22dp, and a wrapped title stands the synopsis down (`onTextLayout` line count) so Play is never what gets cut. Captured as `df_home-movie-hero-long-title.png`. |
| 2 | `DetailPageComponents.detailPaneHeight()` — the slot is derived from the viewport (250420dp) instead of a hard 250dp. Every technical spec now renders; `df_detail-pane-cast-details.png`. Studio, which both columns claimed, is dropped from the technical column. |
| 3 | `DetailFoldPeek` (34dp) holds the tab strip off the bottom edge. |
| 4 | The hero's private `heroFacts`/runtime formatter is gone; it calls `detail/DetailFacts.kt`. `HomeComponents.formatTvRuntime` too — one formatter left in the app. |
| 5 | `MembyScore` token, and every rating goes through `ratingLabel` (`Locale.US`). The home metadata panel renders the score as its own run of text so it can carry the same gold. |
| 6 | "No favourite shows yet" / "Mark a series as a favourite…". |
| 7 | `FactSeparator` between facts, `ValueSeparator` inside a fact that holds a list. |
| 8 | `ui/theme/DesignTokens.kt`; `HomeComponents` and `DetailPageComponents` colours are aliases of it, and `Theme.kt` uses the same near-blacks. |
| 9 | Same — the detail page picked up the raised TV neutrals. |
| 10 | Three radii: `MembyChipCorner` 8, `MembyCardCorner` 10, `MembyPanelCorner` 14. |
| 11 | `ui/MembyButtons.kt``MembyPlayButton`, `MembyPlayChip`, `MembyChoiceChip`. The hero chip, the detail Play button, the metadata panel's "▶ Resume" and the For You time budget all use them. |
| 12 | `DetailHero` honours `Settings.showTitleLogo` (new `EmbyRepository.showTitleLogo`) and shares `useTextTitleForLogo` with the screensaver (`ui/TitleLogo.kt`). |
| 13 | One `UHD_MIN_WIDTH` (3800) for the badge and the `(4K)` suffix. Unit-tested. |
| 14 | `mediaBadges` reads `dynamicRangeLabel`, so HDR10+ stays HDR10+. Unit-tested. |
| 15 | One `FocusRequester` per pane in both overlays; none is attached to two live nodes. |
| 16 | A series passes `mediaBadges(item)`, and `DetailFactRow` takes 4 badges so the airing badge is not squeezed out. |
| 17 | `HomeRowHeaderIcon` / `HomeRowHeaderIconGap` / `HomeRowHeaderSpacing`, used by `MediaRow`, `MyShowsStrip` and `RecentSearchesRow` — which also gained the vertical padding its focus-scaled chips needed. |
| 18 | `HomeHeroPick` carries the row a title was drawn from; the caption is no longer the card's slot. Unit-tested. |
| 19 | Deleted (≈260 lines: `HomeHero`, `HomeRow`, `ContentCard`, `HomeRowData`, `HomeRowSkeleton` and the two runtime formatters only they used). |
| 20 | The peek under the strip plus a chevron at its end. |
| Docs | `CLAUDE.md`'s detail-page section rewritten to describe this code, with the token, button and header conventions above it. |
Screenshots of the result are `app/build/screenshots/df_*.png`
(`.\gradlew.bat :app:testDebugUnitTest --tests "*ScreenshotTest"`).
---
## Confirmed layout bugs
These clip real content on a real TV. Fix these first.
### 1. The featured home hero drops its Play button when the title wraps to two lines
`ui/HomeMovieHero.kt:171-236`, `ui/MainActivity.kt:137` (`homeHeaderHeight`)
The card's content column measures ~229dp with a one-line title and ~261dp with two.
`homeHeaderHeight(540dp, showHero = true)` yields 248dp, minus the hero row's 16dp top and
10dp bottom padding, so the card gets **222dp** — and `FocusScaleContainer` clips it to a
14dp rounded rect. Rendered with a two-line title, the kicker, title, fact line and
synopsis draw and the green Play chip is **gone entirely**. A one-line title is already
7dp over budget; it only survives because the part cut off is the chip's shadow.
Fix direction: the column is `align(Alignment.CenterStart)` inside a fixed-height box, so
overflow is split top and bottom and the button is always the first thing lost. Either
give the hero a height derived from its content, cap the title at one line, or drop the
synopsis when the title wraps.
### 2. The Cast & Details tab silently discards every technical spec
`ui/DetailPageComponents.kt:258-278` (the 250dp pane), `:563-580` (`DetailFocusablePane`),
`:613-643` (`DetailCastAndDetailsPane`)
The tab content slot is a hard `.height(250.dp)` and `DetailFocusablePane` applies
`.clip(RoundedCornerShape(10.dp))`. `releaseAndTechnical` builds Released / Certificate /
Runtime and then `addAll(specs)` — Video, Codec, Audio, Subtitles, Studio. Rendered at the
real slot geometry, only the first three rows survive; the entire output of
`technicalSpecs()` is clipped below the fold of a pane that cannot scroll. That is the
whole reason the tab exists.
`DetailOverviewPane` shares the ceiling: a five-line synopsis plus the series "Up next"
supporting line pushes its credit rows past the same boundary.
Fix direction: the pane needs a height budget that accounts for its worst case, or the
two-column meta block needs to page/scroll. Note the pane deliberately does not scroll
(one screen per tab), so the honest fix is probably fewer rows per column, not a scroller.
### 3. The detail tab strip sits flush against the bottom screen edge
`ui/DetailPageComponents.kt:209` (`heroHeight = maxHeight - DetailTabHeight`), `:494-550`
The selection underline is cut in half at y=1080 in both `detail-movie-more-like-this.png`
and `detail-series-cast-details.png`. On a TV with overscan the underline and part of the
labels are off-screen. This is the only element in the app with zero safe-area inset —
gutters are 36-58dp and the home clock keeps 18dp.
---
## Copy and formatting
### 4. The home hero formats runtime differently from everywhere else
`ui/HomeMovieHero.kt:351` is a private `heroFacts` shadowing `ui/detail/DetailFacts.kt:62`.
Different field order (year · certificate · runtime vs year · runtime · certificate) and
`"${it}m"` instead of `formatRuntime`. The checked-in `home-movie-hero.png` shows
**"2026 • M • 124m"** in the hero and **"2026 • 2h 4m"** on the card directly beneath it.
There are three runtime formatters in the app: `DetailFacts.formatRuntime`,
`HomeComponents.formatTvRuntime` (private, identical) and this one.
### 5. The community score changes colour and locale by screen
Gold `0xFFF5C518` on detail (`DetailPageComponents.kt:484`), grey `MutedText` on home
(`HomeComponents.kt:1025`). `HomeMovieHero.kt:355` uses `"★ %.1f".format(it)` with the
default locale while every other rating goes through `Locale.US` — a comma decimal in a
non-US locale.
### 6. American spelling in two user-facing strings
`ui/HomeComponents.kt:1549` "No **favorite** shows yet" and `:1555` "Mark a series as a
**favorite** and it'll be waiting here." The rail says Favourites, the quick menu says
"Add to favourites", the detail hero action says "Add to Favourites".
### 7. Four separator styles for the same kind of fact list
`" • "` (home metadata), `" • "` (detail fact row, schedule metadata, home hero),
`" · "` (home genres), `", "` (credit rows).
---
## Design-token drift
### 8. Four near-blacks
Theme `background 0xFF0B0E11` and `surface 0xFF101418` (`ui/theme/Theme.kt`, effectively
unused), home `0xFF090B0D`, detail `0xFF080A0C`. The accent green is duplicated four ways:
`EmbyGreen`, `DetailAccent`, and the hero's `0xFF69C762` / `0xFF7BD574`.
### 9. Secondary-text contrast diverged between the two screens
`ui/HomeComponents.kt:129-132` carries a comment about raising the neutrals for TV distance
(`MutedText 0xFFD0D6DB`, `QuietText 0xFFAEB7BF`). The detail page still uses the pre-fix
values (`DetailMutedText 0xFFB6BDC3`, `DetailQuietText 0xFF8C959D`). The two sit side by
side the moment a detail page is opened from a row.
### 10. Corner radii are ad hoc
9dp home cards, 8dp related posters, 8/10dp cast cards, 14dp featured hero, 11dp mini hero,
12dp overlays, 7dp chips, 999dp search chips.
### 11. Three button languages
The hand-rolled `DetailPlayButton` (23/12dp padding, 16sp), the hero's hand-rolled play chip
(12/7dp, 13sp — same look, different metrics), and raw `androidx.tv.material3.Button` with
glyph text in `MediaMetadataPanel` ("▶ Resume") and `ForYouTimeBudget` ("✓ 30 min"), which
picks up theme colours nothing else in the app uses.
---
## Logic and behaviour
### 12. `showTitleLogo` is ignored by the detail pages
The Settings copy promises "shows each title's logo artwork from Emby instead of plain
text", but only `ui/screensaver/ScreensaverContent.kt:773` honours it;
`ui/DetailPageComponents.kt:322` always fetches the logo. The screensaver also has
`useTextTitleForLogo`, a fallback for logos too dark to read — the detail hero has no
equivalent, so a dark logo is invisible on the near-black scrim.
### 13. Two different 4K thresholds
The badge fires at video width ≥ 3800 (`HomeComponents.kt:1184`); the `(4K)` suffix at
≥ 3400 (`DetailFacts.kt:190`). A 3600-wide file is 4K in the spec row and not on the badge.
### 14. HDR10+ is named in `dynamicRangeLabel` but collapses to plain "HDR" in `mediaBadges`
### 15. One `FocusRequester` attached to two live nodes
`informationPane` is attached by the Overview pane, the Cast & Details pane and the Episodes
empty states (`ui/MediaDetailsOverlay.kt:202-216`, `ui/SeriesDetailsOverlay.kt:291-329`).
`AnimatedContent`'s 80ms fade-out keeps the outgoing pane composed, so a Down press landing
in that window can request focus on a pane that is disappearing.
### 16. A series can never show format badges
`ui/SeriesDetailsOverlay.kt:248` passes `badges = emptyList()` where movies pass real ones.
Related: `DetailFactRow` does `badges.take(3)`, so on a 4K/HDR/HEVC movie the airing badge
appended in `ui/MediaDetailsOverlay.kt:147` is silently dropped.
### 17. Home row headers do not align
`MediaRow` and `MyShowsStrip` lead with a 28dp icon chip plus 10dp; `RecentSearchesRow`
(`ui/MainActivity.kt:2084`) has no chip, so its title starts 38dp further left. It also uses
9dp header spacing against everyone else's 6dp and gives its `LazyRow` no vertical padding,
so focus-scaled chips have no room to grow.
### 18. The mini hero labels are positional fiction
`ui/HomeMovieHero.kt:124`: `listOf("POPULAR", "NEW RELEASE", "TRENDING")[index]`. But
`selectHomeHeroMovies` interleaves new releases and popular picks and then falls back to
every movie in the response. In the current screenshot a 2025 title is labelled NEW RELEASE
and a 2026 one POPULAR.
### 19. A dead second home implementation
`ui/MainActivity.kt:2752-2900`: `HomeHero`, `HomeRow`, `ContentCard`, `HomeRowData` and
`HomeRowSkeleton` are unreferenced (only the gateway *model* named `HomeRow` is in use).
They carry a competing 48dp gutter and SemiBold header style — a live-looking template for
the wrong conventions.
### 20. Tab content sits entirely below the fold with no affordance
By design per the code comments, but nothing on screen tells the viewer that Down reveals
anything.
---
## Documentation
`CLAUDE.md`'s detail-page section no longer describes this code. It documents a poster-left
layout with Play hanging off the poster's bottom-right corner, "nothing scrolls vertically",
a tab list of "Overview, Episodes, Cast, Details", and a `DetailReasonStrip` of several
short phrases. The code is a full-bleed scrolling hero with Overview / Episodes / More Like
This / Cast & Details and a single reason line (`ui/DetailPageComponents.kt:375-385`).
Worth correcting before it misleads the next change.
+31
View File
@@ -28,9 +28,36 @@ 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
@@ -38,6 +65,10 @@ 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.
+184 -116
View File
@@ -1,11 +1,68 @@
# Memby (Android TV)
<div align="center">
An independent Android TV client for Emby, by **ponzischeme89**. Memby combines a
personalised television launcher, full media player, system screensaver and an optional
self-hosted gateway that shapes the experience for every viewer in the household.
<img src="memby-icon.png" alt="Memby" width="128" height="128">
# Memby
**An independent Android TV client for Emby.**
A personalised television launcher, a full media player, a system screensaver and an
optional self-hosted gateway that shapes the experience for every viewer in the house.
[![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
### Browse and discover
@@ -59,12 +116,79 @@ Source: [g.sublogue.com/admin/memby](https://g.sublogue.com/admin/memby)
## Tech stack
- Kotlin + Jetpack **Compose for TV** (`androidx.tv:tv-material3`)
- **Media3 / ExoPlayer** for playback
- Retrofit + OkHttp + kotlinx.serialization for the Emby REST API
- DataStore for persisted profiles, settings and cached launcher state
- Coil for backdrop image loading
- Go gateway with Postgres and Redis
| Layer | Built with |
| --- | --- |
| UI | Kotlin + Jetpack **Compose for TV** (`androidx.tv:tv-material3`) |
| Playback | **Media3 / ExoPlayer** |
| Networking | Retrofit + OkHttp + kotlinx.serialization, over one shared HTTP stack |
| Storage | DataStore for profiles, settings and cached launcher state |
| Images | Coil |
| Gateway | Go, with Postgres and Redis |
## Build & install
You need **JDK 17** and the **Android SDK** (Android Studio bundles both).
Open the folder in Android Studio (Giraffe/Koala or newer) and let it sync, **or** from a
terminal:
```powershell
# Android Studio writes local.properties automatically. If building from the CLI,
# point it at your SDK first:
"sdk.dir=C:\\Users\\<you>\\AppData\\Local\\Android\\Sdk" | Out-File -Encoding ascii local.properties
.\gradlew.bat assembleDebug # build the APK
.\gradlew.bat test # JVM unit tests
.\gradlew.bat installDebug # install to a connected Android TV / emulator
# Preferred for a TV already showing the Dream: clears Memby, installs, then reopens
# it so the old render surface cannot remain black. Pass -Serial when more than one
# device is attached.
.\deploy-debug.ps1 -Serial 192.168.20.3:41479
```
The APK lands in `app/build/outputs/apk/debug/app-debug.apk`.
## Server address
Memby is built for one Emby server, so the address is baked into the APK instead of being
typed on a TV remote. Set it in `gradle.properties`:
```properties
memby.serverUrl=http://192.168.1.10:8096
```
It can also come from `~/.gradle/gradle.properties` (keeps it out of the repo) or a single
build: `.\gradlew.bat assembleDebug -Pmemby.serverUrl=http://192.168.1.10:8096`.
The value becomes `BuildConfig.EMBY_SERVER_URL`, read through `data/ServerConfig.kt`. When
it is set, the setup screen only asks for a username and password, and the address wins
over whatever a saved session recorded — so moving the server is a property change plus a
reinstall, with no user action. Leaving the property **blank** restores the original
behaviour: users type the address themselves.
## First run
1. Launch **Memby** from the Android TV launcher.
2. Sign in with your Emby username and password. (If the build has no hardwired server,
enter its address first, e.g. `http://192.168.1.10:8096`.)
3. **Preview screensaver** to test it, or **Set as system screensaver** to open the TV's
screensaver settings and choose "Memby Screensaver".
## Running the gateway
See [`server/README.md`](server/README.md) to run the container. In short, the gateway
imports Emby's catalogue into Postgres (once manually, then hourly for new episodes),
composes the home rows — including "Recommended from your watching history" — and serves an
admin console at `/admin/` for imports, an offline switch, and per-row engagement.
For Tracearr-powered recommendations, create a read-only public API key in Tracearr and set
`MEMBY_TRACEARR_URL` plus `MEMBY_TRACEARR_API_KEY` on the gateway (and optionally
`MEMBY_TRACEARR_SERVER_ID` when Tracearr monitors several servers). Tracearr credentials
remain server-side; the Android app only receives ranked Emby items and short reasons. The
gateway imports compact session signals into Postgres every five minutes and prepares an
over-provisioned per-user pool, so opening For You normally performs one indexed database
read. The live on-demand path remains available for cold starts and failed rebuilds.
## Project layout
@@ -90,98 +214,21 @@ app/src/main/java/com/ponzischeme89/memby/
player/PlayerActivity.kt Media3 playback
screensaver/MembyDreamService.kt System screensaver (hosts ScreensaverContent)
admin-ui/ The operations console (React, served by nginx)
server/ The Memby gateway (Go) — see server/README.md
benchmark/ Macrobenchmarks and baseline-profile generation
```
## Build & install
You need **JDK 17** and the **Android SDK** (Android Studio bundles both).
Open the folder in Android Studio (Giraffe/Koala or newer) and let it sync, **or** from a
terminal:
```powershell
# Android Studio writes local.properties automatically. If building from the CLI,
# point it at your SDK first:
"sdk.dir=C:\\Users\\<you>\\AppData\\Local\\Android\\Sdk" | Out-File -Encoding ascii local.properties
.\gradlew.bat assembleDebug # build the APK
.\gradlew.bat installDebug # install to a connected Android TV / emulator
# Preferred for a TV already showing the Dream: clears Memby, installs, then reopens
# it so the old render surface cannot remain black. Pass -Serial when more than one
# device is attached.
.\deploy-debug.ps1 -Serial 192.168.20.3:41479
```
The APK lands in `app/build/outputs/apk/debug/app-debug.apk`.
## Two ways to run
The client can talk to Emby directly, or through the **Memby gateway** — a Go service in
`server/` that runs in Docker alongside Postgres and Redis and owns auth, caching, search
and the shaping of TV screens. With a gateway the launcher is one request instead of four,
and the TV holds a revocable gateway token rather than a live Emby token.
```
direct: TV ──────────────────────────────► Emby
gateway: TV ──► Memby gateway ──► Emby (metadata + artwork)
TV ─────────────────────► Emby (video stream; never proxied by the gateway)
```
Which one a build uses is decided by `memby.gatewayUrl` in `gradle.properties`: set it and
the app is a thin client; leave it blank and nothing changes from the direct path below.
See [`server/README.md`](server/README.md) to run the container.
The gateway also imports Emby's catalogue into Postgres (once manually, then hourly for
new episodes), composes the home rows — including "Recommended from your watching
history" — and has an admin page at `/admin/` for imports, an offline switch, and
per-row engagement.
For Tracearr-powered recommendations, create a read-only public API key in Tracearr and
set `MEMBY_TRACEARR_URL` plus `MEMBY_TRACEARR_API_KEY` on the gateway (and optionally
`MEMBY_TRACEARR_SERVER_ID` when Tracearr monitors several servers). Tracearr credentials
remain server-side; the Android app only receives ranked Emby items and short reasons.
The gateway imports compact session signals into Postgres every five minutes and prepares
an over-provisioned per-user pool, so opening For You normally performs one indexed
database read. The live on-demand path remains available for cold starts and failed
rebuilds.
## Server address
Memby is built for one Emby server, so the address is baked into the APK instead of being
typed on a TV remote. Set it in `gradle.properties`:
```properties
memby.serverUrl=http://192.168.1.10:8096
```
It can also come from `~/.gradle/gradle.properties` (keeps it out of the repo) or a single
build: `.\gradlew.bat assembleDebug -Pmemby.serverUrl=http://192.168.1.10:8096`.
The value becomes `BuildConfig.EMBY_SERVER_URL`, read through
`data/ServerConfig.kt`. When it is set, the setup screen only asks for a username and
password, and the address wins over whatever a saved session recorded — so moving the
server is a property change plus a reinstall, with no user action. Leaving the property
**blank** restores the original behaviour: users type the address themselves.
## First run
1. Launch **Memby** from the Android TV launcher.
2. Sign in with your Emby username and password. (If the build has no hardwired server,
enter its address first, e.g. `http://192.168.1.10:8096`.)
3. **Preview screensaver** to test it, or **Set as system screensaver** to open the TV's
screensaver settings and choose "Memby Screensaver".
## Distributing builds
Memby is handed out as an APK from your own web server or NAS, and updates itself from
the same folder.
Memby is handed out as an APK from your own web server or NAS, and updates itself from the
same folder.
### One-time: a signing key
Android identifies an app by `applicationId` **plus signing key**. Every update must be
signed with the *same* key, or the TV rejects it as a different app. Lose the key and
every user has to uninstall and reinstall.
signed with the *same* key, or the TV rejects it as a different app. Lose the key and every
user has to uninstall and reinstall.
```powershell
keytool -genkeypair -v -keystore memby-release.jks -alias memby `
@@ -198,10 +245,25 @@ memby.keyAlias=memby
memby.keyPassword=
```
Without these, `assembleRelease` still builds but the APK is unsigned and will not
install. The build prints a warning saying so.
Without these, `assembleRelease` still builds but the APK is unsigned and will not install.
The build prints a warning saying so.
### 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
.\release.ps1 -Version 0.1.54 -Notes "Faster home screen" `
@@ -209,15 +271,16 @@ install. The build prints a warning saying so.
-SourceUrl https://g.sublogue.com/admin/memby
```
That bumps `versionName`/`versionCode`, runs the tests, builds a signed APK, and fills
`dist/out/` with:
That workflow bumps `versionName`/`versionCode`, runs the tests, builds a signed APK, and
fills `dist/out/` with:
```
index.html the page people are sent to
latest.json the update manifest the app polls
memby-0.1.54.apk the build
LICENSE GNU GPL v2 terms
NOTICE copyright and third-party acknowledgements
index.html the page people are sent to
latest.json the update manifest the app polls
memby-0.1.54.apk the build
memby-0.1.54.apk.sha256 the SHA-256 checksum
LICENSE GNU GPL v2 terms
NOTICE copyright and third-party acknowledgements
```
Copy those to the folder the NAS serves. Old APKs can stay alongside — only `latest.json`
@@ -228,8 +291,8 @@ decides what the app offers, so rolling back is editing one file.
The gateway checks the running app version on launch and returns an optional or mandatory
update with its download address. Memby downloads the APK, verifies its checksum, package
and release signature, then commits it through Android's package installer. Update policy
and the download address are managed in the gateway's admin console; viewers do not need
to configure an update source on the television.
and the download address are managed in the gateway's admin console; viewers do not need to
configure an update source on the television.
First install on each TV still has to be manual — the **Downloader** app pointed at the
landing page is the usual route, and the page explains it.
@@ -243,30 +306,35 @@ different app, so on every TV:
1. Install v0.1.53 — it appears as a **second** Memby entry in the launcher.
2. Sign in again; the previous session does not carry over.
3. Uninstall the old app: `adb uninstall com.mattcohen.embyclientsname`.
4. Re-select Memby in the TV's Screensaver settings — the Dream's component name changed
as well, so the old selection no longer resolves.
4. Re-select Memby in the TV's Screensaver settings — the Dream's component name changed as
well, so the old selection no longer resolves.
## Notes & limitations
- Playback negotiates through Emby's `PlaybackInfo` endpoint, then streams directly from
Emby. The Wholphin/Jellyfin-derived capability engine reports Android's H.264 and HEVC
profiles, maximum levels and resolutions rather than assuming every decoder handles every
file. Emby can preserve a supported video stream while converting only incompatible audio
or subtitles. If a vendor decoder still fails, Media3 tries another decoder and Memby
ultimately requests an H.264 HLS transcode instead of abandoning playback.
- Cleartext HTTP is enabled so local `http://` servers work out of the box. For an HTTPS-only
server this is unnecessary but harmless.
file. The Moonfin-derived audio path reports formats the TV can decode or bitstream,
offers automatic receiver detection and manual per-codec passthrough overrides, and
decodes the rest to PCM through Media3's FFmpeg renderer. Emby can preserve a supported
video stream while converting only incompatible audio or subtitles. If a vendor decoder
still fails, Media3 tries another decoder and Memby ultimately requests an H.264 HLS
transcode instead of abandoning playback.
- Cleartext HTTP is enabled so local `http://` servers work out of the box. For an
HTTPS-only server this is unnecessary but harmless.
- The device is remembered across sign-outs (stable `DeviceId`); credentials are cleared.
## Licence
Memby is free software licensed under the [GNU General Public License v2](LICENSE).
Copyright and third-party acknowledgements are recorded in [NOTICE](NOTICE). Distributed
APKs and server binaries must be accompanied by the corresponding source in accordance
with GPLv2. The Android TV app also exposes the source link, notices, and complete licence
under **Settings → About / Licences**.
APKs and server binaries must be accompanied by the corresponding source in accordance with
GPLv2. The Android TV app also exposes the source link, notices, and complete licence under
**Settings → About / Licences**.
Playback capability probing and device-profile generation contain GPLv2 adaptations from
[Wholphin](https://github.com/damontecres/Wholphin), itself derived in part from
[Jellyfin Android TV](https://github.com/jellyfin/jellyfin-androidtv). Attribution and
[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