@groovenet/player 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MPRIS.md +97 -0
- package/OWNTONE.md +145 -0
- package/README.md +17 -0
- package/SERVER_PLAYERS.md +348 -0
- package/build/daemon.d.ts.map +1 -1
- package/build/daemon.js +3 -0
- package/build/daemon.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/mpris.d.ts +11 -0
- package/build/mpris.d.ts.map +1 -0
- package/build/mpris.js +145 -0
- package/build/mpris.js.map +1 -0
- package/build/mprisBin.d.ts +3 -0
- package/build/mprisBin.d.ts.map +1 -0
- package/build/mprisBin.js +26 -0
- package/build/mprisBin.js.map +1 -0
- package/build/mprisMapping.d.ts +12 -0
- package/build/mprisMapping.d.ts.map +1 -0
- package/build/mprisMapping.js +35 -0
- package/build/mprisMapping.js.map +1 -0
- package/build/mpvBackend.d.ts.map +1 -1
- package/build/mpvBackend.js +2 -1
- package/build/mpvBackend.js.map +1 -1
- package/build/ownToneBackend.d.ts +62 -0
- package/build/ownToneBackend.d.ts.map +1 -0
- package/build/ownToneBackend.js +349 -0
- package/build/ownToneBackend.js.map +1 -0
- package/build/player.d.ts +1 -0
- package/build/player.d.ts.map +1 -1
- package/build/player.js +11 -0
- package/build/player.js.map +1 -1
- package/build/protocol.d.ts.map +1 -1
- package/build/protocol.js +1 -0
- package/build/protocol.js.map +1 -1
- package/build/resolver.d.ts +3 -1
- package/build/resolver.d.ts.map +1 -1
- package/build/resolver.js +30 -1
- package/build/resolver.js.map +1 -1
- package/build/server/bin.d.ts +3 -0
- package/build/server/bin.d.ts.map +1 -0
- package/build/server/bin.js +7 -0
- package/build/server/bin.js.map +1 -0
- package/build/server/http.d.ts +5 -0
- package/build/server/http.d.ts.map +1 -0
- package/build/server/http.js +129 -0
- package/build/server/http.js.map +1 -0
- package/build/server/manager.d.ts +61 -0
- package/build/server/manager.d.ts.map +1 -0
- package/build/server/manager.js +555 -0
- package/build/server/manager.js.map +1 -0
- package/build/server/observer.d.ts +10 -0
- package/build/server/observer.d.ts.map +1 -0
- package/build/server/observer.js +47 -0
- package/build/server/observer.js.map +1 -0
- package/build/server/runtime.d.ts +18 -0
- package/build/server/runtime.d.ts.map +1 -0
- package/build/server/runtime.js +101 -0
- package/build/server/runtime.js.map +1 -0
- package/build/server/store.d.ts +47 -0
- package/build/server/store.d.ts.map +1 -0
- package/build/server/store.js +120 -0
- package/build/server/store.js.map +1 -0
- package/build/types.d.ts +3 -1
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/owntone/Dockerfile +27 -0
- package/owntone/smoke.mjs +135 -0
- package/owntone/terminal-v1.patch +96 -0
- package/package.json +24 -4
- package/scripts/mpris-smoke.mjs +54 -0
package/MPRIS.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Desktop controls — issue #650 design and smoke tests
|
|
2
|
+
|
|
3
|
+
## Linux design
|
|
4
|
+
|
|
5
|
+
`groovenet-mpris` is an opt-in foreground companion to an already running
|
|
6
|
+
`groovenet player start --detach` daemon. Install `@groovenet/player` with optional
|
|
7
|
+
dependencies enabled (the CLI also depends on this package). From a checkout,
|
|
8
|
+
build contracts → client → player, then run:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
node packages/groovenet-player/build/mprisBin.js
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The bridge uses `dbus-next` 0.10.2 directly, with native JavaScript BigInt for
|
|
15
|
+
MPRIS's signed 64-bit microsecond values. The dependency is optional and dynamically
|
|
16
|
+
loaded only on Linux with `DBUS_SESSION_BUS_ADDRESS`. No display is needed for
|
|
17
|
+
MPRIS; a user session bus is sufficient. The library's optional native Unix FD
|
|
18
|
+
addon is unnecessary for these interfaces; no audio, file descriptors or credentials
|
|
19
|
+
are sent over D-Bus. A missing dependency or unavailable bus reports a bridge error
|
|
20
|
+
and never changes daemon lifetime. Installing with `--omit=optional` leaves the
|
|
21
|
+
headless daemon usable. Nothing starts this bridge automatically.
|
|
22
|
+
|
|
23
|
+
The service owns `org.mpris.MediaPlayer2.groovenet` and exports the root and
|
|
24
|
+
Player interfaces at `/org/mpris/MediaPlayer2`. A second bridge refuses to take
|
|
25
|
+
over that name. It takes an initial `status` snapshot over the existing private
|
|
26
|
+
Unix socket, then subscribes to `state` events. Those already contain both
|
|
27
|
+
track-change and position information, so consuming their separate notifications
|
|
28
|
+
would duplicate updates. Metadata uses the queue-entry ID (hex encoded into a
|
|
29
|
+
valid object path), title, artist, album and known duration. It never publishes
|
|
30
|
+
authenticated stream URLs. Normal position updates do not emit PropertiesChanged.
|
|
31
|
+
|
|
32
|
+
Play, pause, toggle, next, previous and seeks forward to daemon RPCs. Absolute
|
|
33
|
+
seeks reject stale track IDs; relative seeks clamp to zero or skip beyond the
|
|
34
|
+
known duration. The additive `stop` RPC pauses the backend, clears current playback
|
|
35
|
+
state and retains the queue and daemon. It differs from `shutdown` and the CLI's
|
|
36
|
+
`player stop`, which still shuts down the daemon. A later play reloads the stopped
|
|
37
|
+
queue entry. Transport errors return through D-Bus rather than becoming unhandled
|
|
38
|
+
promise rejections. SIGINT/SIGTERM close the bridge connection and bus. Loss of the
|
|
39
|
+
daemon or bus closes the bridge; restart it after restarting the daemon.
|
|
40
|
+
|
|
41
|
+
References: [dbus-next](https://github.com/dbusjs/node-dbus-next),
|
|
42
|
+
[MPRIS Player specification](https://specifications.freedesktop.org/mpris/latest/Player_Interface.html).
|
|
43
|
+
|
|
44
|
+
## macOS decision and scope
|
|
45
|
+
|
|
46
|
+
macOS is deferred. Apple's
|
|
47
|
+
[MPNowPlayingInfoCenter](https://developer.apple.com/documentation/mediaplayer/mpnowplayinginfocenter)
|
|
48
|
+
and [MPRemoteCommandCenter](https://developer.apple.com/documentation/mediaplayer/mpremotecommandcenter)
|
|
49
|
+
are native MediaPlayer framework APIs. Node's standard library does not expose
|
|
50
|
+
them. Supporting them here would require a native binding or a separately built
|
|
51
|
+
Swift/Objective-C helper with its own run loop, packaging and lifecycle. That
|
|
52
|
+
exceeds the issue's lightweight integration constraint. The socket-subscriber
|
|
53
|
+
boundary allows such a helper later without changing the playback core. Running
|
|
54
|
+
this bridge on macOS prints that it is disabled and exits successfully.
|
|
55
|
+
|
|
56
|
+
Also out of scope: artwork fetching, graphical UI raising, URI loading, queue
|
|
57
|
+
editing over D-Bus, shuffle/repeat, volume control, playback rate changes,
|
|
58
|
+
automatic bridge startup/reconnection, and desktop hotkey binding installation.
|
|
59
|
+
Desktop environments usually route media keys to MPRIS; minimal window managers
|
|
60
|
+
can bind them to the `playerctl` commands below.
|
|
61
|
+
|
|
62
|
+
## Manual acceptance smoke test
|
|
63
|
+
|
|
64
|
+
In a Linux desktop session, with mpv, dbus and playerctl installed:
|
|
65
|
+
|
|
66
|
+
1. Start `groovenet player start --detach`, add two downloaded tracks with
|
|
67
|
+
`groovenet player queue add '<query>'`, and run `groovenet-mpris` in another
|
|
68
|
+
terminal (or the checkout command above).
|
|
69
|
+
2. `playerctl --player=groovenet play`; `playerctl --player=groovenet status`
|
|
70
|
+
should print `Playing`.
|
|
71
|
+
3. `playerctl --player=groovenet metadata` should show `xesam:title`,
|
|
72
|
+
`xesam:artist`, `xesam:album` and a valid `mpris:trackid`.
|
|
73
|
+
4. `playerctl --player=groovenet play-pause` should pause, then resume on a
|
|
74
|
+
second call. Verify the desktop's play/pause media key does the same.
|
|
75
|
+
5. `playerctl --player=groovenet next` and `previous` should update metadata
|
|
76
|
+
and audible playback. Verify the desktop's next/previous media keys.
|
|
77
|
+
6. `playerctl --player=groovenet position` reports seconds;
|
|
78
|
+
`position 12.5` seeks absolutely and `position 2+` seeks relatively.
|
|
79
|
+
7. `playerctl --player=groovenet stop` reports Stopped; `play` restarts the
|
|
80
|
+
retained entry. Ctrl-C the bridge; the daemon should keep playing.
|
|
81
|
+
8. Stop the daemon while the bridge is running: the bridge should exit and
|
|
82
|
+
release its bus name. Start two bridges: the second should fail cleanly.
|
|
83
|
+
9. `env -u DBUS_SESSION_BUS_ADDRESS groovenet-mpris` should exit successfully
|
|
84
|
+
with a disabled message. Repeat daemon startup without a desktop/session bus
|
|
85
|
+
and with optional npm dependencies omitted: playback must still work.
|
|
86
|
+
|
|
87
|
+
An automated Linux wire smoke test uses an in-memory audio backend and the real
|
|
88
|
+
D-Bus daemon/playerctl, requiring no server or audio device:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
dbus-run-session -- node packages/groovenet-player/scripts/mpris-smoke.mjs
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This checks status, play/pause, metadata, next/previous, absolute/relative position,
|
|
95
|
+
stop, and that closing the bridge leaves playback usable. Unit tests cover mapping,
|
|
96
|
+
property notifications, stale/invalid seeks, failures and cleanup; use
|
|
97
|
+
`npm run test:coverage --workspace @groovenet/player`.
|
package/OWNTONE.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# OwnTone backend (single output)
|
|
2
|
+
|
|
3
|
+
`OwnToneBackend` controls a **dedicated** OwnTone engine. Groovenet owns the
|
|
4
|
+
queue; OwnTone holds one indexed track at a time. mpv remains the default.
|
|
5
|
+
Persistent named players, server APIs and independent listening sessions are
|
|
6
|
+
#693; remote TUI control is #694. Synchronized speaker groups are deferred.
|
|
7
|
+
|
|
8
|
+
## Engine requirements
|
|
9
|
+
|
|
10
|
+
Stock OwnTone 29.3 does not expose a completion reason through its JSON API.
|
|
11
|
+
The backend requires the additive `groovenet_terminal` v1 extension in
|
|
12
|
+
[`owntone/terminal-v1.patch`](owntone/terminal-v1.patch). It refuses an unpatched
|
|
13
|
+
engine with `ENGINE_INCOMPATIBLE` before changing outputs or playback.
|
|
14
|
+
|
|
15
|
+
This introduces a maintained engine patch, rather than depending only on the
|
|
16
|
+
upstream container. The Dockerfile pins upstream 29.3 commit
|
|
17
|
+
`d6fb3edf5831de38134ebd92fcf09a730ddd37aa` and verifies its source archive hash.
|
|
18
|
+
The upstream license is GPL-2.0. The image includes the corresponding source,
|
|
19
|
+
license and patch at `/usr/src/owntone`; retain these when distributing it.
|
|
20
|
+
Alpine packages are resolved at build time; this is not a bit-reproducible image.
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
# From the repository root:
|
|
24
|
+
docker build -t groovenet-owntone:692-test packages/groovenet-player/owntone
|
|
25
|
+
npm run build --workspace @groovenet/contracts
|
|
26
|
+
npm run build --workspace @groovenet/client
|
|
27
|
+
npm run build --workspace @groovenet/player
|
|
28
|
+
node packages/groovenet-player/owntone/smoke.mjs
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The smoke test creates and removes a network-isolated container with generated
|
|
32
|
+
files and a dummy output. It publishes no ports and never uses real speakers.
|
|
33
|
+
The engine image does not start Avahi/D-Bus itself: a deployment must supply
|
|
34
|
+
those services. The smoke script shows an isolated startup sequence, **not** a
|
|
35
|
+
production Compose service. Production worker/Compose integration is #693.
|
|
36
|
+
|
|
37
|
+
For AirPlay discovery on Debian, use the LAN network/interface with Avahi and
|
|
38
|
+
D-Bus available. `bind_address = "127.0.0.1"` also restricts discovery to
|
|
39
|
+
loopback, so it is suitable only for the isolated test. Restrict engine access
|
|
40
|
+
with firewall/trusted networks and an admin password when it binds to the LAN;
|
|
41
|
+
do not expose an unauthenticated control API. The CLI's `insecure_tls` setting
|
|
42
|
+
applies to the Groovenet client/mpv, not the OwnTone connection.
|
|
43
|
+
|
|
44
|
+
Configure `speaker_autoselect = no`. The patch disables automatic speaker
|
|
45
|
+
resurrection after a failed streaming connection; only an explicit play or
|
|
46
|
+
output-selection request can select a receiver again. This does not prevent
|
|
47
|
+
another application from taking over an AirPlay receiver. Busy/auth status is
|
|
48
|
+
best-effort engine information, not a cross-application speaker reservation.
|
|
49
|
+
|
|
50
|
+
## Shared audio delivery
|
|
51
|
+
|
|
52
|
+
Mount Groovenet's audio volume **read-only** into OwnTone and configure that
|
|
53
|
+
path in its `library.directories`. Set `owntone_library` to the absolute Linux
|
|
54
|
+
path **inside the engine**, for example `/srv/media`. The CLI does not read
|
|
55
|
+
that local path. It maps each track's `local_audio_url` to an engine-side file
|
|
56
|
+
URL, then looks up the exact indexed path using `/api/library/files`.
|
|
57
|
+
Directory contents are read afresh on each load; no stale track-ID cache or
|
|
58
|
+
whole-library scan is required by the adapter.
|
|
59
|
+
|
|
60
|
+
OwnTone must finish indexing before a file can play; a missing/unindexed or
|
|
61
|
+
ambiguous file is refused. The backend does not force a library rescan. It
|
|
62
|
+
accepts finite indexed files with a known positive duration, not Internet radio
|
|
63
|
+
or HTTP streams. Standard relative names, `/audio/` and `/app/audio/` prefixes,
|
|
64
|
+
and legacy app audio URLs are supported. Traversal, backslashes, NULs and
|
|
65
|
+
ambiguous path segments are refused. There is no basename fallback.
|
|
66
|
+
|
|
67
|
+
Groovenet credentials remain on Groovenet API requests. Indexed file delivery
|
|
68
|
+
needs no bearer token; sources containing HTTP headers or non-file URLs are
|
|
69
|
+
explicitly rejected, never forwarded with authentication silently removed.
|
|
70
|
+
|
|
71
|
+
## CLI setup
|
|
72
|
+
|
|
73
|
+
Run the daemon on the host that should control OwnTone. These settings apply
|
|
74
|
+
when a daemon starts; stop an existing daemon before changing backends.
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
groovenet config set owntone_url http://127.0.0.1:3689/api
|
|
78
|
+
groovenet config set owntone_library /srv/media
|
|
79
|
+
groovenet config set player_backend owntone
|
|
80
|
+
groovenet player start --detach
|
|
81
|
+
groovenet player devices list
|
|
82
|
+
groovenet player devices select <output-id>
|
|
83
|
+
groovenet player queue add "miles davis"
|
|
84
|
+
groovenet player play
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Alternatively keep `player_backend` unset and pass `--backend owntone` to
|
|
88
|
+
`player` or `player start`. Set `owntone_output` to remember one stable engine
|
|
89
|
+
output ID for subsequent starts. `owntone_password` supplies HTTP Basic admin
|
|
90
|
+
authentication; keep the CLI config private. Human config output masks it;
|
|
91
|
+
`config show --json`, like the existing API-key behavior, includes credentials.
|
|
92
|
+
`--backend mpv` overrides the persisted choice. Artwork is an mpv-only setting.
|
|
93
|
+
|
|
94
|
+
The Unix socket and its v1 protocol are unchanged. A TUI connects to an existing
|
|
95
|
+
daemon regardless of the requested backend; restart it to change engines.
|
|
96
|
+
This is still a local daemon, not the remote server-player API from #693.
|
|
97
|
+
|
|
98
|
+
## Events, interruption and cleanup
|
|
99
|
+
|
|
100
|
+
The patch snapshots a random per-process instance ID, sequence, queue item ID
|
|
101
|
+
and terminal reason on the engine player thread. EOF is recorded only at the
|
|
102
|
+
playback boundary; input failures and failed/lost output callbacks mark the
|
|
103
|
+
source as failed, suppressing successful EOF. Stop and skip are not inferred
|
|
104
|
+
from elapsed time, play counts or an empty engine queue. The adapter emits
|
|
105
|
+
`ended` only for a new EOF of its owned item in the same engine instance, with
|
|
106
|
+
no foreign replacement queue. Duplicate/stale events cannot advance twice.
|
|
107
|
+
|
|
108
|
+
All adapter commands and polls are serialized. API outages trigger read-only
|
|
109
|
+
reconciliation: no load, resume or output selection is retried. A still-owned
|
|
110
|
+
active item can be controlled explicitly after recovery. The core retains its
|
|
111
|
+
error state until an explicit playback command. A completion observed across
|
|
112
|
+
a connection gap is treated as uncertain and **does not advance**. A restarted
|
|
113
|
+
engine, replaced queue or terminal interruption drops ownership and requires
|
|
114
|
+
an explicit new play. This favors preserving the queue over guessing EOF.
|
|
115
|
+
|
|
116
|
+
`close()` stops/clears/releases only a still-owned or completed session (or an
|
|
117
|
+
idle explicit output selection), and leaves a foreign session/output alone.
|
|
118
|
+
After ownership is lost or the API remains unreachable, cleanup cannot promise
|
|
119
|
+
to stop the engine; use the engine controls if necessary. The engine must not
|
|
120
|
+
be shared with another queue controller. Separate engines are needed for
|
|
121
|
+
independent player queues; concurrent instances remain unvalidated.
|
|
122
|
+
|
|
123
|
+
Typed errors include `ENGINE_UNAVAILABLE`, `ENGINE_AUTH`, `ENGINE_TIMEOUT`,
|
|
124
|
+
`ENGINE_API`, `ENGINE_INCOMPATIBLE`, `DEVICE_UNAVAILABLE`, `DEVICE_BUSY`,
|
|
125
|
+
`DEVICE_AUTH`, `NO_AUDIO`, `STREAM_FAILED` and `PLAYBACK_INTERRUPTED`.
|
|
126
|
+
Authentication of AirPlay receivers is managed in OwnTone, outside Groovenet.
|
|
127
|
+
|
|
128
|
+
## Volume and verification limits
|
|
129
|
+
|
|
130
|
+
`setVolume` sends 0–100 to the selected engine output. It cannot guarantee an
|
|
131
|
+
acoustic level. Lounge Room Audio uses Shairport's
|
|
132
|
+
`ignore_volume_control = "yes"`, so sender volume does **not** attenuate its
|
|
133
|
+
USB output. The snapshot volume is the last requested Groovenet value, not a
|
|
134
|
+
measured receiver level; OwnTone may report -1 when no output is selected.
|
|
135
|
+
Generate attenuated audio for hardware tests, rather than relying on volume 5.
|
|
136
|
+
|
|
137
|
+
The #691 prototype confirmed audible single-speaker AirPlay on Lounge Room
|
|
138
|
+
Audio, plus engine pause/seek/resume and USB PCM activity. That used the stock
|
|
139
|
+
29.3 image, not this patched build. The new adapter/patch has isolated dummy
|
|
140
|
+
output validation for natural EOF, external stop and queue replacement,
|
|
141
|
+
pause/resume/seek/volume API acceptance, and engine restart handling. Fake API
|
|
142
|
+
tests cover transport/auth/timeouts, ownership, stale events and reconciliation.
|
|
143
|
+
Patched-engine AirPlay takeover, receiver disconnect, effective volume and
|
|
144
|
+
audible seek accuracy still need hardware verification. No synchronized or
|
|
145
|
+
concurrent-engine playback is claimed.
|
package/README.md
CHANGED
|
@@ -45,6 +45,9 @@ and `isPlay` are the pure rules: a track of 30 seconds or more counts after
|
|
|
45
45
|
`min(50% of its duration, 240 s)` of accumulated play time, where only forward
|
|
46
46
|
progress while playing is counted. The CLI wires it to `POST /api/spins` with
|
|
47
47
|
`medium: "digital"` (`player_log_plays` / `--log-plays`).
|
|
48
|
+
Persistent server players apply the same threshold per queue entry and deliver
|
|
49
|
+
owner-library plays through the app's `/api/spins` endpoint. Pending deliveries
|
|
50
|
+
are stored in Postgres for retries across worker restarts.
|
|
48
51
|
|
|
49
52
|
`MpvBackend` does not install mpv. Install it separately and make it available
|
|
50
53
|
on `PATH`, or pass an executable path in the constructor. `close()` stops the
|
|
@@ -55,3 +58,17 @@ implementation for consumers that need to exercise the core without audio.
|
|
|
55
58
|
|
|
56
59
|
Build with `npm run build --workspace=@groovenet/player` after building
|
|
57
60
|
`@groovenet/client`.
|
|
61
|
+
|
|
62
|
+
Linux desktop media controls are available through the optional `groovenet-mpris`
|
|
63
|
+
companion process. It subscribes to an existing daemon over its socket and exposes
|
|
64
|
+
MPRIS for `playerctl` and desktop media keys. It is off unless explicitly started,
|
|
65
|
+
and exits disabled without a Linux session bus. See [MPRIS.md](MPRIS.md) for setup,
|
|
66
|
+
the platform design (including the macOS deferral), and smoke tests.
|
|
67
|
+
|
|
68
|
+
Persistent Debian server players with independent queues and a shared control
|
|
69
|
+
API are documented in [SERVER_PLAYERS.md](SERVER_PLAYERS.md).
|
|
70
|
+
|
|
71
|
+
An opt-in `OwnToneBackend` supports one selected AirPlay output through a
|
|
72
|
+
dedicated patched OwnTone engine. See [OWNTONE.md](OWNTONE.md) for the pinned
|
|
73
|
+
engine build, shared read-only audio mapping, CLI setup, completion semantics
|
|
74
|
+
and verification limits. mpv remains the default.
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# Persistent server players
|
|
2
|
+
|
|
3
|
+
Server players run on the Debian Docker host. Closing a laptop, TUI or event
|
|
4
|
+
subscription leaves playback running. Each named player has its own persistent
|
|
5
|
+
queue and one dedicated OwnTone engine. Different players can use different
|
|
6
|
+
speakers concurrently; synchronized groups are deferred. This is the backend
|
|
7
|
+
for #693; remote TUI controls are tracked in #694. The web UI can control these sessions through an optional household browser
|
|
8
|
+
session; see [web controls](../../my-collection-search/docs/server-player-web.md).
|
|
9
|
+
There is no iOS UI yet. Web authorization can be provisioned from 1Password with
|
|
10
|
+
`just player-web-setup`; existing hosts remain opted out until that explicit setup.
|
|
11
|
+
|
|
12
|
+
## Deployment
|
|
13
|
+
|
|
14
|
+
Use the repository's existing Debian Compose stack and apply migrations first
|
|
15
|
+
(`just migrate-up` with the stack's normal environment). Do not start a second
|
|
16
|
+
stack against the production database volume. Add `docker-compose.players.yml`
|
|
17
|
+
after your usual Compose files and enable profile `players`. This extension is
|
|
18
|
+
optional; the ordinary stack does not start any player engines.
|
|
19
|
+
|
|
20
|
+
For production, put the following player settings in a private root `.env.players`
|
|
21
|
+
(`chmod 600 .env.players`), using `.env.example` as the reference. Its presence
|
|
22
|
+
opts the production deploy script into the player Compose overlay/profile and
|
|
23
|
+
builds the two local images before starting services. It survives the normal
|
|
24
|
+
`.env` rendering step. Keep the existing database/collection secrets in `.env`.
|
|
25
|
+
Player secrets and private worktree files are excluded from Docker build contexts.
|
|
26
|
+
|
|
27
|
+
The optional `.env.players.tpl` uses the existing `GROOVENET_OP_VAULT` /
|
|
28
|
+
`GROOVENET_OP_ITEM` selection (defaults `Homelab` / `groovenet`). Add four fields
|
|
29
|
+
to that 1Password item: `PLAYER_API_KEY`, `PLAYER_LIBRARY_FRIEND_ID`,
|
|
30
|
+
`PLAYER_ENGINE_A_PASSWORD`, and `PLAYER_ENGINE_B_PASSWORD`. The friend ID is the
|
|
31
|
+
positive `friends.id` of the library owner. API keys must be at least 32
|
|
32
|
+
characters on one line; engine passwords must be distinct hex/base64url values.
|
|
33
|
+
To render locally:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
mkdir -p .worktree
|
|
37
|
+
op run --env-file .env.players.tpl -- node my-collection-search/scripts/render-player-env.mjs .worktree/players.env
|
|
38
|
+
# Copy to the production checkout as .env.players with mode 0600.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The renderer validates credentials before replacing any existing file, writes
|
|
42
|
+
atomically with private permissions, and constructs the engine JSON from the same
|
|
43
|
+
passwords. It quotes literal dollar signs/backslashes so Compose preserves secret
|
|
44
|
+
values. Never commit the rendered file. `just deploy` refreshes `.env.players`
|
|
45
|
+
from 1Password only on a host that already has that file; other hosts stay opted
|
|
46
|
+
out. The ordinary collection credential template is unchanged.
|
|
47
|
+
|
|
48
|
+
For the laptop CLI, load only the API key (never the engine passwords):
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
PLAYER_API_KEY='op://Homelab/groovenet/PLAYER_API_KEY' op run -- groovenet player list
|
|
52
|
+
PLAYER_API_KEY='op://Homelab/groovenet/PLAYER_API_KEY' op run --no-masking -- groovenet player --remote lounge
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Use the installed executable or the built Node entrypoint inside `op run`; shell
|
|
56
|
+
functions such as `gn` are not executables that `op run` can invoke directly.
|
|
57
|
+
Existing CLI collection credentials remain independent. For the interactive TUI,
|
|
58
|
+
`op run` needs `--no-masking`: its default output-masking pipe makes stdout cease
|
|
59
|
+
to be a TTY. This option passes terminal output through directly; the player UI
|
|
60
|
+
does not print credentials. Keep default masking for noninteractive commands.
|
|
61
|
+
|
|
62
|
+
Configure player settings:
|
|
63
|
+
|
|
64
|
+
* Generate `PLAYER_API_KEY` with `openssl rand -hex 32`. It is a **shared
|
|
65
|
+
household/admin credential**, with access to every player and friend’s queued
|
|
66
|
+
track references. All reads, commands and SSE require it. Existing browser
|
|
67
|
+
cookies and the ordinary app API key do not grant player access. The opt-in
|
|
68
|
+
web bridge issues a separate player-only session after web-passphrase
|
|
69
|
+
authorization; it never shares this worker key with browsers. Give it only
|
|
70
|
+
to trusted controllers and use HTTPS when reaching the app across a network.
|
|
71
|
+
* Generate distinct hex/base64url `PLAYER_ENGINE_A_PASSWORD` and
|
|
72
|
+
`PLAYER_ENGINE_B_PASSWORD` values (`openssl rand -hex 24`).
|
|
73
|
+
* Set `PLAYER_LAN_INTERFACE` to the Debian host's actual speaker-facing interface.
|
|
74
|
+
Engines and the shared clock use host networking for discovery/AirPlay.
|
|
75
|
+
Reserve API ports 13689/13690, WebSocket ports 13688/13691 and PTP UDP 319/320.
|
|
76
|
+
Restrict engine APIs to trusted host/network access; never expose them publicly.
|
|
77
|
+
* Set `PLAYER_AUDIO_MOUNT=music_data` with the production overlay: this logical
|
|
78
|
+
Compose volume maps to the existing `PROD_MUSIC_DATA_VOLUME`. Alternatively use
|
|
79
|
+
an absolute directory visible to Docker. Do not supply an undeclared Docker
|
|
80
|
+
volume name. The mount is read-only. OwnTone must finish indexing before play.
|
|
81
|
+
* Set `PLAYER_ENGINES_JSON` to the following single-line JSON, replacing passwords
|
|
82
|
+
to match the engine environment variables. These credentials stay server-side.
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{"primary":{"apiUrl":"http://host.docker.internal:13689/api","password":"A_PASSWORD","libraryRoot":"/srv/media"},"secondary":{"apiUrl":"http://host.docker.internal:13690/api","password":"B_PASSWORD","libraryRoot":"/srv/media"}}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
For example, with the existing production environment loaded:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
docker compose --env-file .env --env-file .env.players -p dj-playlist -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.players.yml --profile players build player-clock player-worker
|
|
92
|
+
docker compose --env-file .env --env-file .env.players -p dj-playlist -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.players.yml --profile players up -d app player-clock player-engine-a player-engine-b player-worker
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The worker has no published port: the app proxies to `http://player-worker:13700`.
|
|
96
|
+
The two engine cache databases use separate persistent volumes. Both engines
|
|
97
|
+
must share the clock's IPC namespace (`ipc: service:player-clock`): AirPlay 2
|
|
98
|
+
requires `/dev/shm/airptp_shm`, as well as network access to the clock. The engine
|
|
99
|
+
entrypoint refuses startup without it. Run only one such shared clock per host.
|
|
100
|
+
|
|
101
|
+
Outside Compose, build packages with `just build-packages`, then run
|
|
102
|
+
`groovenet-player-server` with `DATABASE_URL`, `PLAYER_API_KEY`,
|
|
103
|
+
`PLAYER_LIBRARY_FRIEND_ID`, `PLAYER_ENGINES_JSON` and `PLAYER_APP_URL` (the app
|
|
104
|
+
origin, default `http://app:3000`). Set the app's `PLAYER_WORKER_URL` to that
|
|
105
|
+
private worker.
|
|
106
|
+
The standalone worker binds `0.0.0.0:13700` (override `PLAYER_WORKER_PORT`), so
|
|
107
|
+
restrict access at the host/network boundary.
|
|
108
|
+
|
|
109
|
+
## Control API
|
|
110
|
+
|
|
111
|
+
All public paths start with `/api/players`. Supply
|
|
112
|
+
`Authorization: Bearer <PLAYER_API_KEY>` on every request.
|
|
113
|
+
|
|
114
|
+
| Method and path | Purpose |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `GET /api/players` | List persistent players |
|
|
117
|
+
| `GET /api/players/engines` | List configured engine IDs |
|
|
118
|
+
| `POST /api/players` | Create `{ "id": "lounge", "name": "Lounge", "engine_id": "primary" }` |
|
|
119
|
+
| `GET /api/players/lounge` | Get full state and revision |
|
|
120
|
+
| `GET /api/players/lounge/devices` | Discovered outputs, assignments and observations |
|
|
121
|
+
| `POST /api/players/lounge/commands` | Apply a command |
|
|
122
|
+
| `GET /api/players/lounge/events` | SSE snapshots and lightweight position updates |
|
|
123
|
+
|
|
124
|
+
Each engine can belong to only one named player. Names can be changed; player
|
|
125
|
+
IDs and engine assignments are fixed. Engine configuration is an operator
|
|
126
|
+
allowlist, never a client-provided URL. Player deletion is not implemented.
|
|
127
|
+
|
|
128
|
+
Commands use last-write-wins behavior by default. Optionally include
|
|
129
|
+
`expected_revision` from a snapshot to require optimistic concurrency: a stale
|
|
130
|
+
value returns `REVISION_CONFLICT`. Position ticks do not change the revision.
|
|
131
|
+
Each player has a persisted `availability_policy`, initially `ask`. Set it with
|
|
132
|
+
the bearer credential:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
groovenet player set lounge --availability allow_unknown
|
|
136
|
+
groovenet player set lounge --availability always
|
|
137
|
+
groovenet player set lounge --availability ask
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The equivalent command is
|
|
141
|
+
`{"type":"availability.set","availability_policy":"allow_unknown","expected_revision":3}`.
|
|
142
|
+
`ask` requires the existing per-command acknowledgements. `allow_unknown`
|
|
143
|
+
connects when HA activity is unknown without `allow_unknown: true`, but still
|
|
144
|
+
requires `takeover: true` for observed occupied activity. `always` also permits
|
|
145
|
+
observed occupied activity without a takeover flag. Both policies apply to fresh
|
|
146
|
+
connection and reconnection checks; neither bypasses another Groovenet player's
|
|
147
|
+
assignment. Use `output.transfer` explicitly to move that assignment. Browser
|
|
148
|
+
sessions cannot change this policy.
|
|
149
|
+
For example:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{"type":"queue.add","tracks":[{"track_id":"TRACK_ID","friend_id":1}]}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Scripts that need optimistic concurrency can use the returned player revision
|
|
156
|
+
for their next command:
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{"type":"output.select","expected_revision":1,"output_id":"OUTPUT_ID"}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{"type":"play","allow_unknown":true}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Selecting an output remembers intent; it does not connect or reserve the
|
|
167
|
+
speaker. Playback requires indexed local audio, resolved afresh from Postgres.
|
|
168
|
+
Queue entries get stable UUIDs, including repeated copies of the same track.
|
|
169
|
+
Use those entry IDs for `queue.remove`, `queue.move` and an explicit `play`.
|
|
170
|
+
If an entry has disappeared, these commands return `ENTRY_NOT_FOUND`.
|
|
171
|
+
Other commands are `rename`, `queue.clear`, `pause`, `resume`, `stop`, `next`,
|
|
172
|
+
`previous`, `seek` (seconds) and `volume` (0–100). Stop clears the current entry
|
|
173
|
+
and position while preserving the queue. Natural EOF advances only that player's
|
|
174
|
+
queue; interruption/error preserves the current entry and requires a command.
|
|
175
|
+
|
|
176
|
+
### Atomic queue replacement
|
|
177
|
+
|
|
178
|
+
`queue.replace` resolves every track before changing the queue or playback. An
|
|
179
|
+
unresolved track rejects the whole command. Entries accept optional UUIDs;
|
|
180
|
+
provided IDs are preserved and new entries receive generated IDs. Duplicate IDs
|
|
181
|
+
and reusing a current queue ID for another track/friend are rejected. Titles and
|
|
182
|
+
artists are resolved on the server, never accepted from the caller.
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{"type":"queue.replace","expected_revision":3,"entries":[{"id":"11111111-1111-4111-8111-111111111111","track_id":"TRACK_ID","friend_id":1}],"current_entry_id":"11111111-1111-4111-8111-111111111111","position":42,"play":false}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Omitting `current_entry_id` retains the current entry if present; otherwise it
|
|
189
|
+
selects the first replacement entry only when `play: true`. Explicit `null`
|
|
190
|
+
clears the current entry. A non-null ID must belong to the replacement queue.
|
|
191
|
+
Omitting `position` preserves the retained entry's position, or starts a new
|
|
192
|
+
entry at zero. An explicit position seeks a retained live entry, or saves a
|
|
193
|
+
checkpoint for a later play. Removing/changing the current entry stops playback
|
|
194
|
+
unless `play: true`. Retaining it keeps its existing backend and playing/paused
|
|
195
|
+
state; `play: true` resumes it. An empty replacement clears the queue and cannot
|
|
196
|
+
be played. Playback is opt-in and uses the usual output availability/assignment
|
|
197
|
+
checks; rejected preflight checks leave the old queue intact. Engine failures
|
|
198
|
+
after playback effects begin are reported as playback errors, not a rollback of
|
|
199
|
+
already-performed audio operations.
|
|
200
|
+
|
|
201
|
+
The typed client's `commandServerPlayer` accepts this command. From the CLI:
|
|
202
|
+
|
|
203
|
+
```sh
|
|
204
|
+
groovenet player --remote lounge queue replace '{"entries":[{"track_id":"TRACK_ID","friend_id":1}],"play":true}' --json
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Use `--allow-unknown` or `--takeover` only when deliberately authorizing the
|
|
208
|
+
corresponding speaker policy. A replacement with retained UUIDs can restore an
|
|
209
|
+
Undo snapshot, including its current entry and position. Revisions still guard
|
|
210
|
+
against overwriting changes made after that snapshot.
|
|
211
|
+
|
|
212
|
+
SSE begins with a complete `snapshot`. `position` events include player ID,
|
|
213
|
+
revision, current entry ID and observation time; apply them only to a matching
|
|
214
|
+
snapshot revision/entry. Each event also carries worker instance and sequence.
|
|
215
|
+
Reconnect for a fresh snapshot; there is no event replay. Slow clients are
|
|
216
|
+
disconnected, and client disconnection never stops audio. The SDK exposes the
|
|
217
|
+
list/create/get/command/devices/engines requests; SSE can be consumed directly.
|
|
218
|
+
|
|
219
|
+
## Speaker activity and transfers
|
|
220
|
+
|
|
221
|
+
Configure `PLAYER_HA_URL`, `PLAYER_HA_TOKEN` and `PLAYER_HA_ENTITIES_JSON`
|
|
222
|
+
with exact OwnTone output IDs or names as keys. ID mappings take precedence.
|
|
223
|
+
Names are case-sensitive. A name mapping is used only when that name is unique
|
|
224
|
+
among the engine's discovered outputs; duplicate names remain unmapped unless
|
|
225
|
+
each output has an ID mapping. ID mappings always take precedence. Name mapping
|
|
226
|
+
still appears in device diagnostics when HA credentials are missing, so the
|
|
227
|
+
mapping can be checked separately from HA connectivity.
|
|
228
|
+
For example:
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{"aswitch":"media_player.aswitch_streamer","Lounge Room Audio":"media_player.lounge_streamer","132116595608304":"media_player.bedroom_sonos"}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Read-only inspection on beelink on 2026-10-25 confirmed both engines discover:
|
|
235
|
+
|
|
236
|
+
| OwnTone output ID | Name | HA entity from the installed registry (#691) |
|
|
237
|
+
| --- | --- | --- |
|
|
238
|
+
| `202481599519404` | aswitch | `media_player.aswitch_streamer` |
|
|
239
|
+
| `202481586338774` | Lounge Room Audio | `media_player.lounge_streamer` |
|
|
240
|
+
| `132116595608304` | Bedroom | `media_player.bedroom_sonos` |
|
|
241
|
+
|
|
242
|
+
The running worker had an empty HA URL/token and `{}` mappings. HA live states
|
|
243
|
+
could not be queried through that worker. These settings must be supplied to
|
|
244
|
+
`.env.players` and retained in the environment used to render it on deployment.
|
|
245
|
+
The optional `.env.players.tpl` does not supply HA credentials by default.
|
|
246
|
+
|
|
247
|
+
`groovenet player devices --remote lounge` (or the devices API) shows
|
|
248
|
+
`observed.entity_id`, availability, source, observation time and sender identity.
|
|
249
|
+
`entity=unmapped` exposes missing mappings; an entity with `source=unconfigured`
|
|
250
|
+
means the URL/token is missing. A configured but inaccessible entity reports
|
|
251
|
+
`source=home_assistant`, unknown activity and no observation timestamp.
|
|
252
|
+
|
|
253
|
+
The observer reads HA with a 2-second timeout. Device listings cache observations
|
|
254
|
+
for 5 seconds; connection/reconnection decisions always fetch fresh state.
|
|
255
|
+
`idle`, `off`, `standby` mean observed idle; active/paused states mean occupied;
|
|
256
|
+
unavailable, missing configuration and failed observations mean unknown.
|
|
257
|
+
Observations can race another app.
|
|
258
|
+
|
|
259
|
+
To recognise an engine, HA must expose receiver **session sender attributes**.
|
|
260
|
+
Configure exact attribute/value matches keyed by engine ID, for example:
|
|
261
|
+
|
|
262
|
+
```sh
|
|
263
|
+
PLAYER_HA_SENDERS_JSON='{"primary":{"client_name":"Groovenet-primary","client_ip":"192.168.2.151"},"secondary":{"client_name":"Groovenet-secondary","client_ip":"192.168.2.151"}}'
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
These attribute names/values are illustrative: inspect the actual HA entity
|
|
267
|
+
attributes before configuring them. All attributes for an engine must match;
|
|
268
|
+
exactly one engine must match. Matching only the beelink IP cannot distinguish
|
|
269
|
+
its two engines or another app on that host. Use a unique sender identifier,
|
|
270
|
+
never track metadata, a room source label or a shared host IP. Duplicate matches
|
|
271
|
+
remain unknown identity and occupied activity. Sender attributes must describe
|
|
272
|
+
the current session and be cleared/replaced on takeover. Idle/unavailable states
|
|
273
|
+
never identify a sender, even if attributes linger.
|
|
274
|
+
|
|
275
|
+
The installed `shairport_sync` 1.4.0 integration was inspected read-only: it
|
|
276
|
+
subscribes to playback and track metadata but exposes no client name/IP. It needs
|
|
277
|
+
an HA integration/receiver metadata update before this sender configuration can
|
|
278
|
+
work. Shairport's D-Bus client address is available on the Lounge receiver, but
|
|
279
|
+
its client name was empty in #691 and the address persists after disconnect.
|
|
280
|
+
Do not assume an engine library name appears as `client_name`. No receiver or HA
|
|
281
|
+
configuration was changed as part of the inspection.
|
|
282
|
+
|
|
283
|
+
An active matching sender reports `sender_identity=groovenet` and
|
|
284
|
+
`sender_engine_id`. Its own player may connect/resume without takeover; other
|
|
285
|
+
engines still need takeover. Nonmatching sender attributes report `foreign`;
|
|
286
|
+
missing/ambiguous attributes report `unknown`. Both retain occupancy protection.
|
|
287
|
+
|
|
288
|
+
Connecting to unknown activity requires explicit `allow_unknown: true`.
|
|
289
|
+
Occupied activity requires `takeover: true` unless its sender identifies this
|
|
290
|
+
player's dedicated engine. A paused engine may release its AirPlay transport;
|
|
291
|
+
reconnection checks apply the same sender rules.
|
|
292
|
+
Neither acknowledgement can steal another Groovenet player's assignment.
|
|
293
|
+
For that, send `output.transfer` to the destination player:
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
{"type":"output.transfer","from_player_id":"lounge","from_revision":12,"output_id":"OUTPUT_ID","play":true,"allow_unknown":true}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Transfer detaches the source first and keeps each player's own queue and
|
|
300
|
+
checkpoint. `play: false` only changes the destination's desired output.
|
|
301
|
+
`from_revision` is required even when `expected_revision` is omitted; it fences
|
|
302
|
+
the source player whose output is being transferred.
|
|
303
|
+
Inspect `transfer.detached`, `attached` and `error` even when HTTP status is 200:
|
|
304
|
+
the source can be detached while destination playback fails. It is never
|
|
305
|
+
automatically reconnected to hide that partial result.
|
|
306
|
+
|
|
307
|
+
## Restarts and recovery
|
|
308
|
+
|
|
309
|
+
Postgres holds names, independent queues, current entry, volume, desired output
|
|
310
|
+
and position checkpoints (at most once per five seconds, plus command/shutdown
|
|
311
|
+
writes). Only one worker holds the database lifecycle lease. Commands and engine
|
|
312
|
+
events share a serial lane; a slow engine can delay commands to other players.
|
|
313
|
+
|
|
314
|
+
A worker restart restores **stopped checkpoints**, clears transient assignments
|
|
315
|
+
and creates no engine connections. It never resumes or reclaims a speaker
|
|
316
|
+
automatically. `checkpoint: true` means saved position, not observed live audio.
|
|
317
|
+
|
|
318
|
+
Server players log digital plays after the shared `playThreshold` listen time.
|
|
319
|
+
Only tracks belonging to `PLAYER_LIBRARY_FRIEND_ID` are eligible. Each queue
|
|
320
|
+
entry has its own durable delivery record; failed app API requests are retried
|
|
321
|
+
every 30 seconds and after a worker restart. An API conflict for an already
|
|
322
|
+
logged digital play completes the delivery. The worker must be able to reach
|
|
323
|
+
`POST /api/spins` at `PLAYER_APP_URL`.
|
|
324
|
+
|
|
325
|
+
An abrupt crash can leave an old engine playing; stop that engine explicitly
|
|
326
|
+
before issuing new playback if its ownership cannot be established. Do not use
|
|
327
|
+
the dedicated engines from other controller apps.
|
|
328
|
+
|
|
329
|
+
When a receiver or input fails, the worker stops, clears and releases only a
|
|
330
|
+
verified failed engine session. It rechecks the engine instance, queue version,
|
|
331
|
+
item and terminal failure sequence, and refuses to clean up a resumed/replaced
|
|
332
|
+
session or another selected output. The named player's queue/current entry and
|
|
333
|
+
checkpoint remain intact; it never automatically resumes or advances after a
|
|
334
|
+
failure. Once the receiver is ready, explicitly play again (with any required
|
|
335
|
+
availability acknowledgement). Queue and settings edits preserve the playback
|
|
336
|
+
error until a successful transport action leaves the error state.
|
|
337
|
+
|
|
338
|
+
If engine cleanup fails, an uncertain assignment remains reserved and commands
|
|
339
|
+
return `RELEASE_UNCERTAIN`. Stop/restart the affected engine, then restart the
|
|
340
|
+
worker to restore stopped checkpoints and clear the claim. Investigate DB/engine
|
|
341
|
+
connectivity before retrying. Do not run a second worker to bypass a lease error.
|
|
342
|
+
|
|
343
|
+
Some receivers ignore AirPlay volume. The saved volume is the requested sender
|
|
344
|
+
value, not a measured loudness guarantee. The implementation does not promise
|
|
345
|
+
synchronization, automatic recovery after receiver outages, or Sonos validation.
|
|
346
|
+
|
|
347
|
+
Validation and the successful two-room hardware results are recorded in
|
|
348
|
+
[evaluations/693](../../../evaluations/693/README.md).
|
package/build/daemon.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,YAAY,EAAe,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAGpF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAI9C,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,iBAAiB,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,YAAY,CAAC;IACnC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3C;AAED,6EAA6E;AAC7E,qBAAa,YAAY;IAuBX,OAAO,CAAC,QAAQ,CAAC,OAAO;IAtBpC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;IACzD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2B;IAC1D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAO9B;IACF,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA6D;IACjG,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA4D;IAC9F,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAGjC;IAEF,YAA6B,OAAO,EAAE,mBAAmB,EAMxD;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAiB3B;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAqB1B;YAEa,iBAAiB;YAkBjB,cAAc;IAiB5B,OAAO,CAAC,MAAM;YAeA,UAAU;YAuCV,QAAQ;
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,YAAY,EAAe,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAGpF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAI9C,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,iBAAiB,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,YAAY,CAAC;IACnC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3C;AAED,6EAA6E;AAC7E,qBAAa,YAAY;IAuBX,OAAO,CAAC,QAAQ,CAAC,OAAO;IAtBpC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;IACzD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2B;IAC1D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAO9B;IACF,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA6D;IACjG,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA4D;IAC9F,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAGjC;IAEF,YAA6B,OAAO,EAAE,mBAAmB,EAMxD;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAiB3B;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAqB1B;YAEa,iBAAiB;YAkBjB,cAAc;IAiB5B,OAAO,CAAC,MAAM;YAeA,UAAU;YAuCV,QAAQ;IAiDtB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,SAAS;CAIlB"}
|
package/build/daemon.js
CHANGED