Compare commits
95
Commits
v0.2.39
...
1ae07b93ec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ae07b93ec | ||
|
|
067395a362 | ||
|
|
30155e1c5f | ||
|
|
72cb3c376e | ||
|
|
f1d2c2f2eb | ||
|
|
6ba8728dce | ||
|
|
5107c4b0fc | ||
|
|
aa8f16f21b | ||
|
|
c809127996 | ||
|
|
d4e1cb2fbf | ||
|
|
63c3df11b2 | ||
|
|
89ebe21201 | ||
|
|
766cea2199 | ||
|
|
9a1f44da46 | ||
|
|
d2f7f84cbf | ||
|
|
7d77700c7b | ||
|
|
9858e44710 | ||
|
|
b3e3f62c54 | ||
|
|
f982d391b9 | ||
|
|
b131932a4e | ||
|
|
3ae8ffad64 | ||
|
|
5df4a1aaa3 | ||
|
|
4fd690a127 | ||
|
|
ac4cf32746 | ||
|
|
8b3d80a0a6 | ||
|
|
c85fab4459 | ||
|
|
32019f2e20 | ||
|
|
02247a6287 | ||
|
|
9c7dec32a8 | ||
|
|
57edfaa60b | ||
|
|
891d3dfedc | ||
|
|
4439277c86 | ||
|
|
4c48ef8b19 | ||
|
|
fb97980775 | ||
|
|
243b7c8b90 | ||
|
|
48043dbae2 | ||
|
|
aac8438764 | ||
|
|
af1ca4370c | ||
|
|
5467fba0eb | ||
|
|
f1164db2c5 | ||
|
|
549f9c5eed | ||
|
|
434371e9cf | ||
|
|
769fe01c84 | ||
|
|
0782545013 | ||
|
|
590e069366 | ||
|
|
2b43b9ef12 | ||
|
|
8c847c59b8 | ||
|
|
36d171e51b | ||
|
|
1da91e40a1 | ||
|
|
d5a8d3ad88 | ||
|
|
43ab18854c | ||
|
|
12b27c77c3 | ||
|
|
36cb1324fd | ||
|
|
93fb0fd728 | ||
|
|
845fa349e4 | ||
|
|
b9374baaf1 | ||
|
|
bd3732fba5 | ||
|
|
4bc4b075ec | ||
|
|
e528d04b43 | ||
|
|
d5d47473a2 | ||
|
|
a2ca7e8061 | ||
|
|
9775041c30 | ||
|
|
05d95f51cd | ||
|
|
ad0c74f880 | ||
|
|
06b6490ac9 | ||
|
|
bd5cd8a4b1 | ||
|
|
9a8ecbdceb | ||
|
|
48c2809041 | ||
|
|
19f08e6293 | ||
|
|
5e2ed3d12e | ||
|
|
abc392d30b | ||
|
|
a0b84dfc37 | ||
|
|
4fb68f4bbc | ||
|
|
613f203cf9 | ||
|
|
64f19aeef2 | ||
|
|
f2d052dbf6 | ||
|
|
9777eb0952 | ||
|
|
4b31946635 | ||
|
|
e30962245e | ||
|
|
5fed3360c2 | ||
|
|
0e183cf560 | ||
|
|
226136b4e7 | ||
|
|
5e5849f985 | ||
|
|
594159c0ae | ||
|
|
f46d6596c5 | ||
|
|
dd2dd497f2 | ||
|
|
4c47a5f8a0 | ||
|
|
56c1167382 | ||
|
|
63f0768507 | ||
|
|
12b519762b | ||
|
|
f41db66ac6 | ||
|
|
32c0054c44 | ||
|
|
e1bb687df4 | ||
|
|
78d26effbf | ||
|
|
d2f2eb62be |
+91
-18
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
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
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -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
@@ -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 TV’s 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: "Who’s 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.
|
||||
|
||||
-216
@@ -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 (250–420dp) 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.
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
[](LICENSE)
|
||||
[](#build--install)
|
||||
[](#build--install)
|
||||
[](#tech-stack)
|
||||
[](server/README.md)
|
||||
|
||||
Source: [g.sublogue.com/admin/memby](https://g.sublogue.com/admin/memby)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Contents
|
||||
|
||||
- [How it works](#how-it-works)
|
||||
- [Features](#features)
|
||||
- [Browse and discover](#browse-and-discover)
|
||||
- [Playback](#playback)
|
||||
- [Profiles, televisions and administration](#profiles-televisions-and-administration)
|
||||
- [Screensaver and updates](#screensaver-and-updates)
|
||||
- [Tech stack](#tech-stack)
|
||||
- [Build & install](#build--install)
|
||||
- [Server address](#server-address)
|
||||
- [First run](#first-run)
|
||||
- [Running the gateway](#running-the-gateway)
|
||||
- [Project layout](#project-layout)
|
||||
- [Distributing builds](#distributing-builds)
|
||||
- [One-time: a signing key](#one-time-a-signing-key)
|
||||
- [NAS deployment release (recommended)](#nas-deployment-release-recommended)
|
||||
- [Local PowerShell release (compatibility path)](#local-powershell-release-compatibility-path)
|
||||
- [How TVs update themselves](#how-tvs-update-themselves)
|
||||
- [Upgrading to v0.1.53](#upgrading-to-v0153)
|
||||
- [Notes & limitations](#notes--limitations)
|
||||
- [Licence](#licence)
|
||||
|
||||
---
|
||||
|
||||
## How it works
|
||||
|
||||
The client can talk to Emby directly, or through the **Memby gateway** — a Go service in
|
||||
`server/` that runs in Docker alongside Postgres and Redis and owns auth, caching, search
|
||||
and the shaping of TV screens. With a gateway the launcher is one request instead of four,
|
||||
and the TV holds a revocable gateway token rather than a live Emby token.
|
||||
|
||||
```
|
||||
direct: TV ──────────────────────────────► Emby
|
||||
gateway: TV ──► Memby gateway ──► Emby (metadata + artwork)
|
||||
TV ─────────────────────► Emby (video stream; never proxied by the gateway)
|
||||
```
|
||||
|
||||
Which one a build uses is decided by `memby.gatewayUrl` in `gradle.properties`: set it and
|
||||
the app is a thin client; leave it blank and the app talks to Emby directly. Both paths are
|
||||
maintained — the direct path is the fallback when the container is down.
|
||||
|
||||
## Features
|
||||
|
||||
### Browse and discover
|
||||
@@ -59,12 +116,79 @@ Source: [g.sublogue.com/admin/memby](https://g.sublogue.com/admin/memby)
|
||||
|
||||
## Tech stack
|
||||
|
||||
- Kotlin + Jetpack **Compose for TV** (`androidx.tv:tv-material3`)
|
||||
- **Media3 / ExoPlayer** for playback
|
||||
- Retrofit + OkHttp + kotlinx.serialization for the Emby REST API
|
||||
- DataStore for persisted profiles, settings and cached launcher state
|
||||
- Coil for backdrop image loading
|
||||
- Go gateway with Postgres and Redis
|
||||
| Layer | Built with |
|
||||
| --- | --- |
|
||||
| UI | Kotlin + Jetpack **Compose for TV** (`androidx.tv:tv-material3`) |
|
||||
| Playback | **Media3 / ExoPlayer** |
|
||||
| Networking | Retrofit + OkHttp + kotlinx.serialization, over one shared HTTP stack |
|
||||
| Storage | DataStore for profiles, settings and cached launcher state |
|
||||
| Images | Coil |
|
||||
| Gateway | Go, with Postgres and Redis |
|
||||
|
||||
## Build & install
|
||||
|
||||
You need **JDK 17** and the **Android SDK** (Android Studio bundles both).
|
||||
|
||||
Open the folder in Android Studio (Giraffe/Koala or newer) and let it sync, **or** from a
|
||||
terminal:
|
||||
|
||||
```powershell
|
||||
# Android Studio writes local.properties automatically. If building from the CLI,
|
||||
# point it at your SDK first:
|
||||
"sdk.dir=C:\\Users\\<you>\\AppData\\Local\\Android\\Sdk" | Out-File -Encoding ascii local.properties
|
||||
|
||||
.\gradlew.bat assembleDebug # build the APK
|
||||
.\gradlew.bat test # JVM unit tests
|
||||
.\gradlew.bat installDebug # install to a connected Android TV / emulator
|
||||
|
||||
# Preferred for a TV already showing the Dream: clears Memby, installs, then reopens
|
||||
# it so the old render surface cannot remain black. Pass -Serial when more than one
|
||||
# device is attached.
|
||||
.\deploy-debug.ps1 -Serial 192.168.20.3:41479
|
||||
```
|
||||
|
||||
The APK lands in `app/build/outputs/apk/debug/app-debug.apk`.
|
||||
|
||||
## Server address
|
||||
|
||||
Memby is built for one Emby server, so the address is baked into the APK instead of being
|
||||
typed on a TV remote. Set it in `gradle.properties`:
|
||||
|
||||
```properties
|
||||
memby.serverUrl=http://192.168.1.10:8096
|
||||
```
|
||||
|
||||
It can also come from `~/.gradle/gradle.properties` (keeps it out of the repo) or a single
|
||||
build: `.\gradlew.bat assembleDebug -Pmemby.serverUrl=http://192.168.1.10:8096`.
|
||||
|
||||
The value becomes `BuildConfig.EMBY_SERVER_URL`, read through `data/ServerConfig.kt`. When
|
||||
it is set, the setup screen only asks for a username and password, and the address wins
|
||||
over whatever a saved session recorded — so moving the server is a property change plus a
|
||||
reinstall, with no user action. Leaving the property **blank** restores the original
|
||||
behaviour: users type the address themselves.
|
||||
|
||||
## First run
|
||||
|
||||
1. Launch **Memby** from the Android TV launcher.
|
||||
2. Sign in with your Emby username and password. (If the build has no hardwired server,
|
||||
enter its address first, e.g. `http://192.168.1.10:8096`.)
|
||||
3. **Preview screensaver** to test it, or **Set as system screensaver** to open the TV's
|
||||
screensaver settings and choose "Memby Screensaver".
|
||||
|
||||
## Running the gateway
|
||||
|
||||
See [`server/README.md`](server/README.md) to run the container. In short, the gateway
|
||||
imports Emby's catalogue into Postgres (once manually, then hourly for new episodes),
|
||||
composes the home rows — including "Recommended from your watching history" — and serves an
|
||||
admin console at `/admin/` for imports, an offline switch, and per-row engagement.
|
||||
|
||||
For Tracearr-powered recommendations, create a read-only public API key in Tracearr and set
|
||||
`MEMBY_TRACEARR_URL` plus `MEMBY_TRACEARR_API_KEY` on the gateway (and optionally
|
||||
`MEMBY_TRACEARR_SERVER_ID` when Tracearr monitors several servers). Tracearr credentials
|
||||
remain server-side; the Android app only receives ranked Emby items and short reasons. The
|
||||
gateway imports compact session signals into Postgres every five minutes and prepares an
|
||||
over-provisioned per-user pool, so opening For You normally performs one indexed database
|
||||
read. The live on-demand path remains available for cold starts and failed rebuilds.
|
||||
|
||||
## Project layout
|
||||
|
||||
@@ -90,98 +214,21 @@ app/src/main/java/com/ponzischeme89/memby/
|
||||
player/PlayerActivity.kt Media3 playback
|
||||
screensaver/MembyDreamService.kt System screensaver (hosts ScreensaverContent)
|
||||
|
||||
admin-ui/ The operations console (React, served by nginx)
|
||||
server/ The Memby gateway (Go) — see server/README.md
|
||||
benchmark/ Macrobenchmarks and baseline-profile generation
|
||||
```
|
||||
|
||||
## Build & install
|
||||
|
||||
You need **JDK 17** and the **Android SDK** (Android Studio bundles both).
|
||||
|
||||
Open the folder in Android Studio (Giraffe/Koala or newer) and let it sync, **or** from a
|
||||
terminal:
|
||||
|
||||
```powershell
|
||||
# Android Studio writes local.properties automatically. If building from the CLI,
|
||||
# point it at your SDK first:
|
||||
"sdk.dir=C:\\Users\\<you>\\AppData\\Local\\Android\\Sdk" | Out-File -Encoding ascii local.properties
|
||||
|
||||
.\gradlew.bat assembleDebug # build the APK
|
||||
.\gradlew.bat installDebug # install to a connected Android TV / emulator
|
||||
# Preferred for a TV already showing the Dream: clears Memby, installs, then reopens
|
||||
# it so the old render surface cannot remain black. Pass -Serial when more than one
|
||||
# device is attached.
|
||||
.\deploy-debug.ps1 -Serial 192.168.20.3:41479
|
||||
```
|
||||
|
||||
The APK lands in `app/build/outputs/apk/debug/app-debug.apk`.
|
||||
|
||||
## Two ways to run
|
||||
|
||||
The client can talk to Emby directly, or through the **Memby gateway** — a Go service in
|
||||
`server/` that runs in Docker alongside Postgres and Redis and owns auth, caching, search
|
||||
and the shaping of TV screens. With a gateway the launcher is one request instead of four,
|
||||
and the TV holds a revocable gateway token rather than a live Emby token.
|
||||
|
||||
```
|
||||
direct: TV ──────────────────────────────► Emby
|
||||
gateway: TV ──► Memby gateway ──► Emby (metadata + artwork)
|
||||
TV ─────────────────────► Emby (video stream; never proxied by the gateway)
|
||||
```
|
||||
|
||||
Which one a build uses is decided by `memby.gatewayUrl` in `gradle.properties`: set it and
|
||||
the app is a thin client; leave it blank and nothing changes from the direct path below.
|
||||
See [`server/README.md`](server/README.md) to run the container.
|
||||
|
||||
The gateway also imports Emby's catalogue into Postgres (once manually, then hourly for
|
||||
new episodes), composes the home rows — including "Recommended from your watching
|
||||
history" — and has an admin page at `/admin/` for imports, an offline switch, and
|
||||
per-row engagement.
|
||||
|
||||
For Tracearr-powered recommendations, create a read-only public API key in Tracearr and
|
||||
set `MEMBY_TRACEARR_URL` plus `MEMBY_TRACEARR_API_KEY` on the gateway (and optionally
|
||||
`MEMBY_TRACEARR_SERVER_ID` when Tracearr monitors several servers). Tracearr credentials
|
||||
remain server-side; the Android app only receives ranked Emby items and short reasons.
|
||||
The gateway imports compact session signals into Postgres every five minutes and prepares
|
||||
an over-provisioned per-user pool, so opening For You normally performs one indexed
|
||||
database read. The live on-demand path remains available for cold starts and failed
|
||||
rebuilds.
|
||||
|
||||
## Server address
|
||||
|
||||
Memby is built for one Emby server, so the address is baked into the APK instead of being
|
||||
typed on a TV remote. Set it in `gradle.properties`:
|
||||
|
||||
```properties
|
||||
memby.serverUrl=http://192.168.1.10:8096
|
||||
```
|
||||
|
||||
It can also come from `~/.gradle/gradle.properties` (keeps it out of the repo) or a single
|
||||
build: `.\gradlew.bat assembleDebug -Pmemby.serverUrl=http://192.168.1.10:8096`.
|
||||
|
||||
The value becomes `BuildConfig.EMBY_SERVER_URL`, read through
|
||||
`data/ServerConfig.kt`. When it is set, the setup screen only asks for a username and
|
||||
password, and the address wins over whatever a saved session recorded — so moving the
|
||||
server is a property change plus a reinstall, with no user action. Leaving the property
|
||||
**blank** restores the original behaviour: users type the address themselves.
|
||||
|
||||
## First run
|
||||
|
||||
1. Launch **Memby** from the Android TV launcher.
|
||||
2. Sign in with your Emby username and password. (If the build has no hardwired server,
|
||||
enter its address first, e.g. `http://192.168.1.10:8096`.)
|
||||
3. **Preview screensaver** to test it, or **Set as system screensaver** to open the TV's
|
||||
screensaver settings and choose "Memby Screensaver".
|
||||
|
||||
## Distributing builds
|
||||
|
||||
Memby is handed out as an APK from your own web server or NAS, and updates itself from
|
||||
the same folder.
|
||||
Memby is handed out as an APK from your own web server or NAS, and updates itself from the
|
||||
same folder.
|
||||
|
||||
### One-time: a signing key
|
||||
|
||||
Android identifies an app by `applicationId` **plus signing key**. Every update must be
|
||||
signed with the *same* key, or the TV rejects it as a different app. Lose the key and
|
||||
every user has to uninstall and reinstall.
|
||||
signed with the *same* key, or the TV rejects it as a different app. Lose the key and every
|
||||
user has to uninstall and reinstall.
|
||||
|
||||
```powershell
|
||||
keytool -genkeypair -v -keystore memby-release.jks -alias memby `
|
||||
@@ -198,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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+11
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.
Binary file not shown.
+59
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
import"./router-D9WH5XEU.js";
|
||||
Vendored
+23
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user