@homebridge-plugins/homebridge-homepod-radio 3.2.7 → 3.2.9

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/README.md CHANGED
@@ -161,6 +161,37 @@ For Whom The Bell Tolls.mp3
161
161
  > [!NOTE]
162
162
  > Comments starting with `#` and empty lines are ignored.
163
163
 
164
+ ### Low-latency audio buttons
165
+
166
+ By default, every audio-file button press starts a fresh `python3` helper that
167
+ imports pyatv and re-establishes the AirPlay connection before any sound plays.
168
+ For short sound effects (door chimes, alerts, ringtones) that startup cost
169
+ dominates the perceived delay.
170
+
171
+ To avoid it, the plugin keeps a single, long-lived pyatv worker running. The
172
+ worker imports pyatv once and holds the connection open, so button presses skip
173
+ the cold start and play noticeably sooner. This is **on by default** and is only
174
+ started when at least one `audioFiles` entry is configured, so installs without
175
+ audio buttons consume no extra resources.
176
+
177
+ If you ever want to disable it (and fall back to the original spawn-per-press
178
+ behavior), set `keepConnectionWarm` to `false` in your config:
179
+
180
+ ```
181
+ "keepConnectionWarm": false
182
+ ```
183
+
184
+ Notes:
185
+
186
+ - The warm path applies to the HomeKit **audio-file switch accessories**. The
187
+ webhook (`/play/...`) endpoint is unaffected and continues to use the standard
188
+ path.
189
+ - If the warm worker is unavailable for any reason, playback automatically falls
190
+ back to the standard spawn path, so a worker failure never silently breaks a
191
+ button.
192
+ - The first press after restart may still be slightly slower while the worker
193
+ finishes its initial warm-up.
194
+
164
195
  ### Webhook for audio file playback
165
196
 
166
197
  You should use the Homebridge server name (default for Homebridge server is homebridge.local) or IP to invoke playback via URL
@@ -0,0 +1,208 @@
1
+ # !/usr/bin/python3
2
+ """Resident pyatv worker for homebridge-homepod-radio (warm-connection mode).
3
+
4
+ Spawned once by the plugin when ``keepConnectionWarm`` is enabled. It imports
5
+ pyatv a single time, scans for and connects to the HomePod once, and then holds
6
+ that connection warm, reusing it for every audio-button press. This eliminates
7
+ the ~5 s per-press cost (``import pyatv`` cold start + device scan + connect)
8
+ that ``stream.py`` pays each time it is spawned fresh.
9
+
10
+ Protocol (newline-delimited JSON; one object per line):
11
+
12
+ stdin {"id": "<id>", "cmd": "play", "file": "<abs path>",
13
+ "volume": <0-100>, "title": "<text>"}
14
+ {"id": "<id>", "cmd": "ping"}
15
+
16
+ stdout {"event": "ready"} (once, after startup)
17
+ {"id": "<id>", "ok": true}
18
+ {"id": "<id>", "ok": false, "error": "..."}
19
+
20
+ All human-readable logging goes to **stderr** so that stdout stays a clean
21
+ protocol channel. ``volume`` of 0 (or missing) means "do not change volume",
22
+ matching the existing ``stream.py`` semantics.
23
+ """
24
+ import argparse
25
+ import asyncio
26
+ import json
27
+ import logging
28
+ import os
29
+ import re
30
+ import sys
31
+
32
+ import pyatv
33
+ from pyatv.const import Protocol
34
+ from pyatv.interface import MediaMetadata
35
+
36
+
37
+ _LOGGER = logging.getLogger("warm-worker")
38
+
39
+
40
+ def _out(obj) -> None:
41
+ """Write one protocol message to stdout and flush immediately."""
42
+ sys.stdout.write(json.dumps(obj) + "\n")
43
+ sys.stdout.flush()
44
+
45
+
46
+ class WarmConnection:
47
+ """Owns a single, reused pyatv connection to the target device."""
48
+
49
+ def __init__(self, identifier: str, loop: asyncio.AbstractEventLoop) -> None:
50
+ self.identifier = identifier
51
+ self.loop = loop
52
+ self.atv = None
53
+ self._lock = asyncio.Lock()
54
+
55
+ async def _scan(self):
56
+ # Mirror stream.py: scan by 12-hex id / MAC first, else by RAOP name.
57
+ ident_regex = re.compile(r"^[0-9A-Fa-f]{12}$")
58
+ mac_regex = re.compile(r"^[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){5}$")
59
+ atvs = []
60
+ if (len(self.identifier) == 12 and ident_regex.match(self.identifier)) or mac_regex.match(
61
+ self.identifier
62
+ ):
63
+ _LOGGER.info("scanning by id/MAC: %s", self.identifier)
64
+ atvs = await pyatv.scan(self.loop, identifier=self.identifier, timeout=5)
65
+ if not atvs:
66
+ _LOGGER.info("scanning by name: %s", self.identifier)
67
+ found = await pyatv.scan(self.loop, protocol=Protocol.RAOP, timeout=5)
68
+ atvs = [a for a in found if a.name == self.identifier]
69
+ if not atvs:
70
+ raise RuntimeError("device not found: %s" % self.identifier)
71
+ return atvs[0]
72
+
73
+ async def ensure_connected(self):
74
+ if self.atv is not None:
75
+ return self.atv
76
+ conf = await self._scan()
77
+ _LOGGER.info("connecting to %s", conf.address)
78
+ self.atv = await pyatv.connect(conf, self.loop)
79
+ _LOGGER.info("connected; holding connection warm")
80
+ return self.atv
81
+
82
+ def close(self) -> None:
83
+ if self.atv is not None:
84
+ try:
85
+ self.atv.close()
86
+ except Exception: # noqa: BLE001 - best-effort teardown
87
+ pass
88
+ self.atv = None
89
+
90
+ async def _expand(self, file_path: str):
91
+ # Mirror stream.py m3u/m3u8 handling so warm playback matches spawn.
92
+ if file_path.endswith(".m3u") or file_path.endswith(".m3u8"):
93
+ folder = os.path.dirname(file_path)
94
+ with open(file_path, "r", encoding="UTF-8") as playlist:
95
+ lines = [ln.strip() for ln in playlist if ln.strip()]
96
+ return [
97
+ os.path.join(folder, ln)
98
+ for ln in lines
99
+ if re.match(r"^[A-Za-z0-9]", ln)
100
+ and not re.match(r"^[A-Za-z]:\\", ln)
101
+ and not re.match(r"^http[s]?://", ln)
102
+ ]
103
+ return [file_path]
104
+
105
+ async def play(self, file_path: str, volume, title: str) -> None:
106
+ """Stream a file (or m3u) on the warm connection, reconnecting once on error."""
107
+ async with self._lock:
108
+ last_err = None
109
+ for attempt in (1, 2):
110
+ try:
111
+ atv = await self.ensure_connected()
112
+ if volume and int(volume) > 0:
113
+ try:
114
+ await atv.audio.set_volume(float(volume))
115
+ except Exception as ex: # noqa: BLE001
116
+ _LOGGER.warning("set_volume(%s) failed: %s", volume, ex)
117
+ metadata = MediaMetadata(title=title, album=title, artist=None, artwork=None)
118
+ for song in await self._expand(file_path):
119
+ _LOGGER.info("streaming %s (attempt %d)", song, attempt)
120
+ await atv.stream.stream_file(song, metadata)
121
+ _LOGGER.info("finished streaming %s", file_path)
122
+ return
123
+ except Exception as ex: # noqa: BLE001
124
+ last_err = ex
125
+ _LOGGER.error("stream attempt %d failed: %s", attempt, ex)
126
+ self.close() # force a fresh connect on retry
127
+ raise last_err if last_err is not None else RuntimeError("play failed")
128
+
129
+
130
+ async def _read_line(loop: asyncio.AbstractEventLoop) -> str:
131
+ # Blocking stdin read offloaded to a thread so the asyncio loop stays free.
132
+ return await loop.run_in_executor(None, sys.stdin.readline)
133
+
134
+
135
+ async def main_async(identifier: str) -> None:
136
+ loop = asyncio.get_running_loop()
137
+ conn = WarmConnection(identifier, loop)
138
+
139
+ # Best-effort warmup; a failure here is non-fatal because the first play
140
+ # will retry the scan/connect itself.
141
+ try:
142
+ await conn.ensure_connected()
143
+ except Exception as ex: # noqa: BLE001
144
+ _LOGGER.warning("initial warmup failed (will retry on first play): %s", ex)
145
+
146
+ _out({"event": "ready"})
147
+
148
+ while True:
149
+ line = await _read_line(loop)
150
+ if line == "": # EOF: parent closed our stdin
151
+ _LOGGER.info("stdin closed, exiting")
152
+ break
153
+ line = line.strip()
154
+ if not line:
155
+ continue
156
+ try:
157
+ msg = json.loads(line)
158
+ except Exception: # noqa: BLE001
159
+ _LOGGER.warning("ignoring non-JSON line: %s", line)
160
+ continue
161
+
162
+ req_id = msg.get("id")
163
+ cmd = msg.get("cmd")
164
+
165
+ if cmd == "ping":
166
+ _out({"id": req_id, "ok": True, "pong": True})
167
+ continue
168
+
169
+ if cmd == "play":
170
+ file_path = msg.get("file")
171
+ volume = msg.get("volume", 0)
172
+ title = msg.get("title") or (os.path.basename(file_path) if file_path else "")
173
+ if not file_path:
174
+ _out({"id": req_id, "ok": False, "error": "missing file"})
175
+ continue
176
+ try:
177
+ await conn.play(file_path, volume, title)
178
+ _out({"id": req_id, "ok": True})
179
+ except Exception as ex: # noqa: BLE001
180
+ _out({"id": req_id, "ok": False, "error": str(ex)})
181
+ continue
182
+
183
+ _out({"id": req_id, "ok": False, "error": "unknown cmd: %s" % cmd})
184
+
185
+ conn.close()
186
+
187
+
188
+ def main() -> None:
189
+ parser = argparse.ArgumentParser()
190
+ parser.add_argument("-i", "--id", dest="id", required=True, help="device identifier")
191
+ parser.add_argument("-v", "--verbose", action="store_true", dest="verbose")
192
+ args = parser.parse_args()
193
+
194
+ logging.basicConfig(
195
+ level=logging.DEBUG if args.verbose else logging.INFO,
196
+ stream=sys.stderr,
197
+ datefmt="%Y-%m-%d %H:%M:%S",
198
+ format="%(asctime)s %(levelname)s [%(name)s]: %(message)s",
199
+ )
200
+
201
+ try:
202
+ asyncio.run(main_async(args.id))
203
+ except KeyboardInterrupt:
204
+ pass
205
+
206
+
207
+ if __name__ == "__main__":
208
+ main()
@@ -1,4 +1,5 @@
1
1
  import { Logger } from 'homebridge';
2
+ import { WarmPlayer } from './warmPlayer.js';
2
3
  /**
3
4
  * AirPlay device
4
5
  */
@@ -9,17 +10,19 @@ export declare class AirPlayDevice {
9
10
  private readonly streamerName;
10
11
  private readonly streamMetadataUrl?;
11
12
  private readonly streamArtworkUrl?;
13
+ private readonly warmPlayer?;
12
14
  private readonly STREAMING_RESTART_TIMEOUT;
13
15
  private readonly HEARTBEAT_TIMEOUT;
14
16
  private readonly LAST_SEEN_THRESHOLD_MS;
15
17
  private readonly DEFAULT_ARTWORK_URL;
16
18
  private streaming;
19
+ private warmPlaying;
17
20
  private lastSeen;
18
21
  private heartbeat;
19
22
  private streamingRetries;
20
23
  private readonly debug;
21
24
  private readonly pluginPath;
22
- constructor(homepodId: string, logger: Logger, verboseMode: boolean, streamerName: string, streamMetadataUrl?: string | undefined, streamArtworkUrl?: string | undefined);
25
+ constructor(homepodId: string, logger: Logger, verboseMode: boolean, streamerName: string, streamMetadataUrl?: string | undefined, streamArtworkUrl?: string | undefined, warmPlayer?: WarmPlayer | undefined);
23
26
  private killProcess;
24
27
  getPlaybackTitle(): Promise<string>;
25
28
  setVolume(volume: number): Promise<void>;
@@ -15,24 +15,27 @@ export class AirPlayDevice {
15
15
  streamerName;
16
16
  streamMetadataUrl;
17
17
  streamArtworkUrl;
18
+ warmPlayer;
18
19
  STREAMING_RESTART_TIMEOUT = 500;
19
20
  HEARTBEAT_TIMEOUT = 5000;
20
21
  LAST_SEEN_THRESHOLD_MS = 10000;
21
22
  DEFAULT_ARTWORK_URL = 'https://www.apple.com/v/apple-music/q/images/shared/og__ckjrh2mu8b2a_image.png';
22
23
  streaming;
24
+ warmPlaying = false;
23
25
  lastSeen;
24
26
  heartbeat;
25
27
  streamingRetries = 0;
26
28
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
29
  debug;
28
30
  pluginPath;
29
- constructor(homepodId, logger, verboseMode, streamerName, streamMetadataUrl, streamArtworkUrl) {
31
+ constructor(homepodId, logger, verboseMode, streamerName, streamMetadataUrl, streamArtworkUrl, warmPlayer) {
30
32
  this.homepodId = homepodId;
31
33
  this.logger = logger;
32
34
  this.verboseMode = verboseMode;
33
35
  this.streamerName = streamerName;
34
36
  this.streamMetadataUrl = streamMetadataUrl;
35
37
  this.streamArtworkUrl = streamArtworkUrl;
38
+ this.warmPlayer = warmPlayer;
36
39
  this.debug = this.verboseMode ? this.logger.info.bind(this.logger) : this.logger.debug.bind(this.logger);
37
40
  this.pluginPath = path.resolve(path.dirname(__filename), '..', '..');
38
41
  }
@@ -52,6 +55,25 @@ export class AirPlayDevice {
52
55
  await execAsync(setVolumeCmd);
53
56
  }
54
57
  async playFile(filePath, volume) {
58
+ // Prefer the shared warm worker (held pyatv connection) when available,
59
+ // falling back to the per-press spawn path below if it is down or fails.
60
+ if (this.warmPlayer && this.warmPlayer.isReady()) {
61
+ this.warmPlaying = true;
62
+ this.logger.info(`[${this.streamerName}] Started file streaming ${filePath} (warm)`);
63
+ try {
64
+ const ok = await this.warmPlayer.playFile(filePath, volume, this.streamerName);
65
+ this.warmPlaying = false;
66
+ if (ok) {
67
+ this.debug(`[${this.streamerName}] Finished file streaming ${filePath} (warm)`);
68
+ return true;
69
+ }
70
+ this.logger.warn(`[${this.streamerName}] Warm play failed, falling back to spawn`);
71
+ }
72
+ catch (err) {
73
+ this.warmPlaying = false;
74
+ this.logger.warn(`[${this.streamerName}] Warm play error, falling back to spawn: ${err}`);
75
+ }
76
+ }
55
77
  // create pipe for the command:
56
78
  const scriptPath = path.resolve(path.dirname(__filename), '..', 'stream.py');
57
79
  const streamParams = [
@@ -192,6 +214,7 @@ export class AirPlayDevice {
192
214
  }
193
215
  async endStreaming(streamExited = false) {
194
216
  try {
217
+ this.warmPlaying = false;
195
218
  if (this.streaming === undefined) {
196
219
  this.debug(`[${this.streamerName}] End streaming: streaming: ${this.streaming}`);
197
220
  return Promise.resolve(true);
@@ -221,7 +244,7 @@ export class AirPlayDevice {
221
244
  return true;
222
245
  }
223
246
  isPlaying() {
224
- return !!this.streaming;
247
+ return !!this.streaming || this.warmPlaying;
225
248
  }
226
249
  }
227
250
  //# sourceMappingURL=airplayDevice.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"airplayDevice.js","sourceRoot":"","sources":["../../src/lib/airplayDevice.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"airplayDevice.js","sourceRoot":"","sources":["../../src/lib/airplayDevice.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGtC,OAAO,KAAK,KAAK,MAAM,eAAe,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAExC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAElD;;GAEG;AAEH,MAAM,OAAO,aAAa;IAmBD;IACA;IACA;IACA;IACA;IACA;IACA;IAxBJ,yBAAyB,GAAG,GAAG,CAAC;IAChC,iBAAiB,GAAG,IAAI,CAAC;IACzB,sBAAsB,GAAG,KAAK,CAAC;IAE/B,mBAAmB,GAChC,gFAAgF,CAAC;IAE7E,SAAS,CAAiC;IAC1C,WAAW,GAAG,KAAK,CAAC;IACpB,QAAQ,CAAU;IAClB,SAAS,CAA6C;IACtD,gBAAgB,GAAG,CAAC,CAAC;IAE7B,8DAA8D;IAC7C,KAAK,CAAkD;IACvD,UAAU,CAAS;IAEpC,YACqB,SAAiB,EACjB,MAAc,EACd,WAAoB,EACpB,YAAoB,EACpB,iBAA0B,EAC1B,gBAAyB,EACzB,UAAuB;QANvB,cAAS,GAAT,SAAS,CAAQ;QACjB,WAAM,GAAN,MAAM,CAAQ;QACd,gBAAW,GAAX,WAAW,CAAS;QACpB,iBAAY,GAAZ,YAAY,CAAQ;QACpB,sBAAiB,GAAjB,iBAAiB,CAAS;QAC1B,qBAAgB,GAAhB,gBAAgB,CAAS;QACzB,eAAU,GAAV,UAAU,CAAa;QAExC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACzE,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,MAAc;QACpC,MAAM,GAAG,GAAG,WAAW,MAAM,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,gBAAgB,MAAM,aAAa,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjG,CAAC;IAEM,KAAK,CAAC,gBAAgB;QACzB,MAAM,eAAe,GAAG,kBAAkB,IAAI,CAAC,SAAS,QAAQ,CAAC;QACjE,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,eAAe,CAAC,CAAC;QAChD,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACnD,CAAC;IAEM,KAAK,CAAC,SAAS,CAAC,MAAc;QACjC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;QAC7E,MAAM,YAAY,GAAG,WAAW,UAAU,SAAS,IAAI,CAAC,SAAS,aAAa,MAAM,EAAE,CAAC;QACvF,MAAM,SAAS,CAAC,YAAY,CAAC,CAAC;IAClC,CAAC;IAEM,KAAK,CAAC,QAAQ,CAAC,QAAgB,EAAE,MAAc;QAClD,wEAAwE;QACxE,yEAAyE;QACzE,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;YAC/C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,4BAA4B,QAAQ,SAAS,CAAC,CAAC;YACrF,IAAI,CAAC;gBACD,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;gBAC/E,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;gBACzB,IAAI,EAAE,EAAE,CAAC;oBACL,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,6BAA6B,QAAQ,SAAS,CAAC,CAAC;oBAChF,OAAO,IAAI,CAAC;gBAChB,CAAC;gBACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,2CAA2C,CAAC,CAAC;YACvF,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;gBACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,6CAA6C,GAAG,EAAE,CAAC,CAAC;YAC9F,CAAC;QACL,CAAC;QAED,+BAA+B;QAC/B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;QAE7E,MAAM,YAAY,GAAa;YAC3B,UAAU;YACV,MAAM;YACN,IAAI,CAAC,SAAS;YACd,SAAS;YACT,IAAI,CAAC,YAAY;YACjB,SAAS;YACT,IAAI,CAAC,YAAY;YACjB,QAAQ;YACR,QAAQ;YACR,WAAW;YACX,UAAU;YACV,EAAE,GAAG,MAAM;SACd,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,oBAAoB,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAErF,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,KAAK,CACxB,SAAS,EACT,YAAY,EACZ,EAAE,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,CACpD,CAAC;QAEF,IAAI,CAAC,SAAS,CAAC,MAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACvC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,qBAAqB,IAAI,EAAE,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;YAC7C,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,0BAA0B,IAAI,WAAW,MAAM,EAAE,CAAC,CAAC;YACnF,MAAM,YAAY,GAAG,IAAI,CAAC;YAC1B,MAAM,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,CAAC,MAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACvC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,qBAAqB,IAAI,EAAE,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,sBAAsB,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QAExE,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,sBAAsB,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC;QAC5E,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,4BAA4B,QAAQ,EAAE,CAAC,CAAC;QAC9E,OAAO,IAAI,CAAC;IAChB,CAAC;IAEM,KAAK,CAAC,UAAU,CAAC,SAAiB,EAAE,UAAkB,EAAE,MAAc;QACzE,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC;QAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,MAAM,eAAe,GAAG,KAAK,IAAmB,EAAE;YAC9C,0CAA0C;YAC1C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC5C,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,2BAA2B,IAAI,CAAC,SAAS,YAAY,KAAK,EAAE,CAAC,CAAC;YAC9F,MAAM,gBAAgB,GAAG,KAAK,CAAC;YAC/B,IAAI,gBAAgB,EAAE,CAAC;gBACnB,6CAA6C;gBAC7C,MAAM,KAAK,CAAC,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC;gBACvE,MAAM,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;YACzF,CAAC;iBAAM,CAAC;gBACJ,0EAA0E;gBAC1E,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;gBAC1B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,yCAAyC,CAAC,CAAC;YACrF,CAAC;QACL,CAAC,CAAC;QAEF,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACnB,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;YAC1B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,+BAA+B,CAAC,CAAC;YACvE,OAAO,MAAM,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;QAChG,CAAC;aAAM,CAAC;YACJ,OAAO,MAAM,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;QAChG,CAAC;IACL,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,cAAc,CAAC,YAAoB,EAAE,eAAoC;QACnF,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,iCAAiC,YAAY,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CACZ,IAAI,IAAI,CAAC,YAAY,iCAAiC,YAAY,uBAAuB,CAC5F,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,sBAAsB,IAAI,CAAC,SAAS,sBAAsB,CAAC,CAAC;YAClG,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;YAC3B,OAAO;QACX,CAAC;QACD,IAAI,YAAY,KAAK,WAAW,EAAE,CAAC;YAC/B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACJ,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;YAC1C,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,+BAA+B,MAAM,IAAI,CAAC,CAAC;YAC3E,IAAI,MAAM,GAAG,IAAI,CAAC,sBAAsB,EAAE,CAAC;gBACvC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,6BAA6B,CAAC,CAAC;gBAC/D,MAAM,eAAe,EAAE,CAAC;YAC5B,CAAC;QACL,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,cAAc,CACxB,SAAiB,EACjB,UAAkB,EAClB,MAAc,EACd,SAAkF,EAClF,eAAoC;QAEpC,+BAA+B;QAC/B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;QAE7E,MAAM,YAAY,GAAa;YAC3B,UAAU;YACV,MAAM;YACN,IAAI,CAAC,SAAS;YACd,SAAS;YACT,UAAU;YACV,SAAS;YACT,IAAI,CAAC,YAAY;YACjB,cAAc;YACd,SAAS;YACT,kBAAkB;YAClB,IAAI;YACJ,mBAAmB;YACnB,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI;YACtD,kBAAkB;YAClB,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB;YACxE,UAAU;YACV,EAAE,GAAG,MAAM;YACX,WAAW;SACd,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,oBAAoB,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAErF,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,KAAK,CACxB,SAAS,EACT,YAAY,EACZ,EAAE,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,CACpD,CAAC;QAEF,IAAI,CAAC,SAAS,CAAC,MAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACvC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,qBAAqB,IAAI,EAAE,CAAC,CAAC;YAC7D,SAAS,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;YAC7C,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,0BAA0B,IAAI,WAAW,MAAM,EAAE,CAAC,CAAC;YACnF,MAAM,YAAY,GAAG,IAAI,CAAC;YAC1B,MAAM,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,CAAC,MAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACvC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,qBAAqB,IAAI,EAAE,CAAC,CAAC;YAC7D,SAAS,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,sBAAsB,IAAI,CAAC,SAAS,mBAAmB,CAAC,CAAC;YACzF,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC/B,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;YAC9B,SAAS,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;QAC5C,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAE3B,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,sBAAsB,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QAExE,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,sBAAsB,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC;QAC5E,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,uBAAuB,SAAS,EAAE,CAAC,CAAC;QAC1E,OAAO,IAAI,CAAC;IAChB,CAAC;IAEO,KAAK,CAAC,YAAY,CAAC,eAAwB,KAAK;QACpD,IAAI,CAAC;YACD,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;YACzB,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC/B,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,+BAA+B,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;gBACjF,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACjC,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,iCAAiC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC;YAEvF,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,sBAAsB,IAAI,CAAC,SAAS,mBAAmB,CAAC,CAAC;YACzF,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;YAE3B,IAAI,CAAC,YAAY,EAAE,CAAC;gBAChB,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,GAAI,CAAC,CAAC;YAChD,CAAC;YACD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,iCAAiC,GAAG,EAAE,CAAC,CAAC;YAC/E,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC/B,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IAEM,KAAK,CAAC,IAAI;QACb,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACpB,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,mCAAmC,CAAC,CAAC;YACrE,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,uCAAuC,CAAC,CAAC;QAC/E,OAAO,IAAI,CAAC;IAChB,CAAC;IAEM,SAAS;QACZ,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,WAAW,CAAC;IAChD,CAAC;CACJ"}
@@ -1,10 +1,12 @@
1
1
  import { promises as fs } from 'fs';
2
+ import * as path from 'path';
2
3
  export class Storage {
3
4
  filePath;
4
5
  constructor(filePath) {
5
6
  this.filePath = filePath;
6
7
  }
7
8
  async write(data) {
9
+ await fs.mkdir(path.dirname(this.filePath), { recursive: true });
8
10
  await fs.writeFile(this.filePath, JSON.stringify(data), 'utf8');
9
11
  }
10
12
  async read() {
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/lib/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AAEpC,MAAM,OAAO,OAAO;IACoB;IAApC,YAAoC,QAAgB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;IACpD,CAAC;IAEM,KAAK,CAAC,KAAK,CAAI,IAAuB;QACzC,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACpE,CAAC;IAEM,KAAK,CAAC,IAAI;QACb,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;CACJ"}
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/lib/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,MAAM,OAAO,OAAO;IACoB;IAApC,YAAoC,QAAgB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;IACpD,CAAC;IAEM,KAAK,CAAC,KAAK,CAAI,IAAuB;QACzC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACpE,CAAC;IAEM,KAAK,CAAC,IAAI;QACb,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;CACJ"}
@@ -0,0 +1,41 @@
1
+ import { Logger } from 'homebridge';
2
+ /**
3
+ * Supervises a single resident `warm-worker.py` process that holds one warm
4
+ * pyatv connection to the HomePod and replays audio files on it. Shared by all
5
+ * audio-button accessories for a given homepodId.
6
+ *
7
+ * The worker is spawned once and kept alive; if it crashes it is restarted with
8
+ * exponential backoff. Each play is a newline-delimited JSON request/response on
9
+ * the worker's stdin/stdout (logs come back on stderr). When the worker is not
10
+ * ready, `playFile()` resolves `false` so the caller can fall back to the
11
+ * original per-press spawn path — behavior degrades gracefully.
12
+ */
13
+ export declare class WarmPlayer {
14
+ private readonly homepodId;
15
+ private readonly logger;
16
+ private readonly verboseMode;
17
+ private worker;
18
+ private rl;
19
+ private ready;
20
+ private stopped;
21
+ private restartDelay;
22
+ private readonly MAX_RESTART_DELAY;
23
+ private readonly PLAY_TIMEOUT_MS;
24
+ private nextId;
25
+ private readonly pending;
26
+ private readonly scriptPath;
27
+ private readonly debug;
28
+ constructor(homepodId: string, logger: Logger, verboseMode: boolean);
29
+ start(): void;
30
+ private handleLine;
31
+ private scheduleRestart;
32
+ private failAllPending;
33
+ isReady(): boolean;
34
+ /**
35
+ * Ask the warm worker to play a file. Resolves `true` on success, or `false`
36
+ * if the worker is unavailable or the play failed, so the caller can fall
37
+ * back to spawning stream.py.
38
+ */
39
+ playFile(filePath: string, volume: number, title: string): Promise<boolean>;
40
+ stop(): void;
41
+ }
@@ -0,0 +1,173 @@
1
+ import * as child from 'child_process';
2
+ import * as path from 'path';
3
+ import * as readline from 'readline';
4
+ import { fileURLToPath } from 'url';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ /**
7
+ * Supervises a single resident `warm-worker.py` process that holds one warm
8
+ * pyatv connection to the HomePod and replays audio files on it. Shared by all
9
+ * audio-button accessories for a given homepodId.
10
+ *
11
+ * The worker is spawned once and kept alive; if it crashes it is restarted with
12
+ * exponential backoff. Each play is a newline-delimited JSON request/response on
13
+ * the worker's stdin/stdout (logs come back on stderr). When the worker is not
14
+ * ready, `playFile()` resolves `false` so the caller can fall back to the
15
+ * original per-press spawn path — behavior degrades gracefully.
16
+ */
17
+ export class WarmPlayer {
18
+ homepodId;
19
+ logger;
20
+ verboseMode;
21
+ worker;
22
+ rl;
23
+ ready = false;
24
+ stopped = false;
25
+ restartDelay = 1000;
26
+ MAX_RESTART_DELAY = 30000;
27
+ PLAY_TIMEOUT_MS = 60000;
28
+ nextId = 1;
29
+ pending = new Map();
30
+ scriptPath;
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ debug;
33
+ constructor(homepodId, logger, verboseMode) {
34
+ this.homepodId = homepodId;
35
+ this.logger = logger;
36
+ this.verboseMode = verboseMode;
37
+ this.debug = this.verboseMode ? this.logger.info.bind(this.logger) : this.logger.debug.bind(this.logger);
38
+ // dist/lib/warmPlayer.js -> ../warm-worker.py == dist/warm-worker.py
39
+ this.scriptPath = path.resolve(path.dirname(__filename), '..', 'warm-worker.py');
40
+ }
41
+ start() {
42
+ if (this.stopped || this.worker) {
43
+ return;
44
+ }
45
+ const args = ['-u', this.scriptPath, '--id', this.homepodId];
46
+ if (this.verboseMode) {
47
+ args.push('--verbose');
48
+ }
49
+ this.logger.info(`[warm] starting worker: python3 ${args.join(' ')}`);
50
+ this.worker = child.spawn('python3', args, { env: { ...process.env } });
51
+ this.rl = readline.createInterface({ input: this.worker.stdout });
52
+ this.rl.on('line', (line) => this.handleLine(line));
53
+ this.worker.stderr.on('data', (data) => {
54
+ this.debug(`[warm worker] ${data.toString().trim()}`);
55
+ });
56
+ this.worker.on('error', (err) => {
57
+ this.logger.error(`[warm] worker spawn error: ${err}`);
58
+ });
59
+ this.worker.on('exit', (code, signal) => {
60
+ this.logger.warn(`[warm] worker exited code=${code} signal=${signal}`);
61
+ this.ready = false;
62
+ this.rl?.close();
63
+ this.rl = undefined;
64
+ this.worker = undefined;
65
+ this.failAllPending();
66
+ this.scheduleRestart();
67
+ });
68
+ }
69
+ handleLine(line) {
70
+ const trimmed = line.trim();
71
+ if (!trimmed) {
72
+ return;
73
+ }
74
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
+ let msg;
76
+ try {
77
+ msg = JSON.parse(trimmed);
78
+ }
79
+ catch {
80
+ this.debug(`[warm] non-JSON from worker: ${trimmed}`);
81
+ return;
82
+ }
83
+ if (msg.event === 'ready') {
84
+ this.ready = true;
85
+ this.restartDelay = 1000; // healthy start resets backoff
86
+ this.logger.info('[warm] worker ready (connection held warm)');
87
+ return;
88
+ }
89
+ if (msg.id !== undefined && msg.id !== null) {
90
+ const key = String(msg.id);
91
+ const pending = this.pending.get(key);
92
+ if (pending) {
93
+ clearTimeout(pending.timer);
94
+ this.pending.delete(key);
95
+ if (msg.ok === false && msg.error) {
96
+ this.logger.warn(`[warm] play failed: ${msg.error}`);
97
+ }
98
+ pending.resolve(msg.ok === true);
99
+ }
100
+ }
101
+ }
102
+ scheduleRestart() {
103
+ if (this.stopped) {
104
+ return;
105
+ }
106
+ const delay = this.restartDelay;
107
+ this.restartDelay = Math.min(this.restartDelay * 2, this.MAX_RESTART_DELAY);
108
+ this.logger.info(`[warm] restarting worker in ${delay}ms`);
109
+ setTimeout(() => this.start(), delay);
110
+ }
111
+ failAllPending() {
112
+ for (const pending of this.pending.values()) {
113
+ clearTimeout(pending.timer);
114
+ pending.resolve(false);
115
+ }
116
+ this.pending.clear();
117
+ }
118
+ isReady() {
119
+ return this.ready && !!this.worker;
120
+ }
121
+ /**
122
+ * Ask the warm worker to play a file. Resolves `true` on success, or `false`
123
+ * if the worker is unavailable or the play failed, so the caller can fall
124
+ * back to spawning stream.py.
125
+ */
126
+ playFile(filePath, volume, title) {
127
+ if (!this.isReady()) {
128
+ return Promise.resolve(false);
129
+ }
130
+ const id = String(this.nextId++);
131
+ const payload = JSON.stringify({ id, cmd: 'play', file: filePath, volume, title }) + '\n';
132
+ return new Promise((resolve) => {
133
+ const timer = setTimeout(() => {
134
+ this.pending.delete(id);
135
+ this.logger.warn(`[warm] play timed out for ${filePath}`);
136
+ resolve(false);
137
+ }, this.PLAY_TIMEOUT_MS);
138
+ this.pending.set(id, { resolve, timer });
139
+ try {
140
+ this.worker.stdin.write(payload);
141
+ }
142
+ catch (err) {
143
+ clearTimeout(timer);
144
+ this.pending.delete(id);
145
+ this.logger.warn(`[warm] failed to write to worker: ${err}`);
146
+ resolve(false);
147
+ }
148
+ });
149
+ }
150
+ stop() {
151
+ this.stopped = true;
152
+ this.ready = false;
153
+ this.failAllPending();
154
+ if (this.worker) {
155
+ try {
156
+ this.worker.stdin?.end();
157
+ }
158
+ catch {
159
+ // ignore
160
+ }
161
+ try {
162
+ this.worker.kill('SIGTERM');
163
+ }
164
+ catch {
165
+ // ignore
166
+ }
167
+ this.worker = undefined;
168
+ }
169
+ this.rl?.close();
170
+ this.rl = undefined;
171
+ }
172
+ }
173
+ //# sourceMappingURL=warmPlayer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"warmPlayer.js","sourceRoot":"","sources":["../../src/lib/warmPlayer.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ export declare class HomepodRadioPlatform implements DynamicPlatformPlugin {
12
12
  private readonly playbackController;
13
13
  private readonly httpService;
14
14
  private readonly platformActions;
15
+ private warmPlayer;
15
16
  readonly Service: typeof Service;
16
17
  readonly Characteristic: typeof Characteristic;
17
18
  readonly platformConfig: HomepodRadioPlatformConfig;
package/dist/platform.js CHANGED
@@ -8,6 +8,7 @@ import { HomepodAudioSwitchAccessory } from './platformAudioSwitchAccessory.js';
8
8
  import { HomepodVolumeAccessory } from './platformHomepodVolumeAccessory.js';
9
9
  import { delay } from './lib/promises.js';
10
10
  import { HttpService } from './lib/httpService.js';
11
+ import { WarmPlayer } from './lib/warmPlayer.js';
11
12
  let hap;
12
13
  /**
13
14
  * Platform Accessory
@@ -21,6 +22,7 @@ export class HomepodRadioPlatform {
21
22
  playbackController = new PlaybackController();
22
23
  httpService;
23
24
  platformActions;
25
+ warmPlayer;
24
26
  Service;
25
27
  Characteristic;
26
28
  platformConfig;
@@ -38,6 +40,15 @@ export class HomepodRadioPlatform {
38
40
  this.logger.info(`Loaded ${loadedRadios.length} radios: ${loadedRadios}`);
39
41
  this.api.on('didFinishLaunching', async () => {
40
42
  this.logger.info('Finished initializing platform');
43
+ // Start one shared warm worker (held pyatv connection) before adding
44
+ // audio buttons, so the first press can already use it. On by
45
+ // default, but only when at least one audio button is configured so
46
+ // installs without audio buttons consume no extra resources.
47
+ if (this.platformConfig.keepConnectionWarm && this.platformConfig.audioFiles.length > 0) {
48
+ this.logger.info('Platform: keeping AirPlay connection warm for audio buttons');
49
+ this.warmPlayer = new WarmPlayer(this.platformConfig.homepodId, this.logger, this.platformConfig.verboseMode);
50
+ this.warmPlayer.start();
51
+ }
41
52
  this.platformConfig.radios.forEach((radio) => this.addRadioAccessory(radio));
42
53
  this.platformConfig.audioFiles.forEach((fileSwitch) => this.addFileSwitchAccessory(fileSwitch));
43
54
  await delay(1000, 0);
@@ -53,6 +64,7 @@ export class HomepodRadioPlatform {
53
64
  if (this.platformConfig.httpPort > 0) {
54
65
  this.httpService.stop();
55
66
  }
67
+ this.warmPlayer?.stop();
56
68
  });
57
69
  }
58
70
  /**
@@ -99,7 +111,7 @@ export class HomepodRadioPlatform {
99
111
  // Adding Categories.SPEAKER as the category.
100
112
  // @see https://github.com/homebridge/homebridge/issues/2553#issuecomment-623675893
101
113
  accessory.category = 26 /* Categories.SPEAKER */;
102
- new HomepodAudioSwitchAccessory(this, accessory, fileSwitch, this.playbackController);
114
+ new HomepodAudioSwitchAccessory(this, accessory, fileSwitch, this.playbackController, this.warmPlayer);
103
115
  // SmartSpeaker service must be added as an external accessory.
104
116
  // @see https://github.com/homebridge/homebridge/issues/2553#issuecomment-622961035
105
117
  // There a no collision issues when calling this multiple times on accessories that already exist.
@@ -1 +1 @@
1
- {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
@@ -2,15 +2,17 @@ import { AccessoryPlugin, Service, PlatformAccessory } from 'homebridge';
2
2
  import { HomepodRadioPlatform } from './platform.js';
3
3
  import { AudioConfig } from './platformConfig.js';
4
4
  import { PlaybackController, PlaybackStreamer } from './lib/playbackController.js';
5
+ import { WarmPlayer } from './lib/warmPlayer.js';
5
6
  export declare class HomepodAudioSwitchAccessory implements AccessoryPlugin, PlaybackStreamer {
6
7
  private readonly platform;
7
8
  private readonly accessory;
8
9
  private readonly audioConfig;
9
10
  private readonly playbackController;
11
+ private readonly warmPlayer?;
10
12
  private readonly device;
11
13
  private readonly service;
12
14
  private readonly informationService;
13
- constructor(platform: HomepodRadioPlatform, accessory: PlatformAccessory, audioConfig: AudioConfig, playbackController: PlaybackController);
15
+ constructor(platform: HomepodRadioPlatform, accessory: PlatformAccessory, audioConfig: AudioConfig, playbackController: PlaybackController, warmPlayer?: WarmPlayer | undefined);
14
16
  volumeUpdated(homepodId: string, volume: number): Promise<void>;
15
17
  stopRequested(source: PlaybackStreamer): Promise<void>;
16
18
  shutdownRequested(): Promise<void>;
@@ -7,15 +7,17 @@ export class HomepodAudioSwitchAccessory {
7
7
  accessory;
8
8
  audioConfig;
9
9
  playbackController;
10
+ warmPlayer;
10
11
  device;
11
12
  service;
12
13
  informationService;
13
- constructor(platform, accessory, audioConfig, playbackController) {
14
+ constructor(platform, accessory, audioConfig, playbackController, warmPlayer) {
14
15
  this.platform = platform;
15
16
  this.accessory = accessory;
16
17
  this.audioConfig = audioConfig;
17
18
  this.playbackController = playbackController;
18
- this.device = new AirPlayDevice(this.platform.platformConfig.homepodId, platform.logger, platform.platformConfig.verboseMode, this.streamerName(), '', '');
19
+ this.warmPlayer = warmPlayer;
20
+ this.device = new AirPlayDevice(this.platform.platformConfig.homepodId, platform.logger, platform.platformConfig.verboseMode, this.streamerName(), '', '', this.warmPlayer);
19
21
  this.service =
20
22
  this.accessory.getService(this.platform.Service.Switch) ||
21
23
  this.accessory.addService(this.platform.Service.Switch);
@@ -1 +1 @@
1
- {"version":3,"file":"platformAudioSwitchAccessory.js","sourceRoot":"","sources":["../src/platformAudioSwitchAccessory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platformAudioSwitchAccessory.js","sourceRoot":"","sources":["../src/platformAudioSwitchAccessory.ts"],"names":[],"mappings":"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"}
@@ -27,6 +27,7 @@ export declare class HomepodRadioPlatformConfig {
27
27
  readonly httpPort: number;
28
28
  readonly enableVolumeControl: boolean;
29
29
  readonly volume: number;
30
+ readonly keepConnectionWarm: boolean;
30
31
  constructor(config: PlatformConfig);
31
32
  private loadAudioConfigs;
32
33
  private loadRadioConfigs;
@@ -1,4 +1,4 @@
1
- import { PLUGIN_MODEL } from './platformConstants.js';
1
+ import { PLUGIN_MODEL, DEFAULT_KEEP_CONNECTION_WARM } from './platformConstants.js';
2
2
  export class HomepodRadioPlatformConfig {
3
3
  config;
4
4
  name;
@@ -11,6 +11,7 @@ export class HomepodRadioPlatformConfig {
11
11
  httpPort;
12
12
  enableVolumeControl;
13
13
  volume;
14
+ keepConnectionWarm;
14
15
  constructor(config) {
15
16
  this.config = config;
16
17
  this.name = config.name || 'HomePod Mini Radio';
@@ -26,6 +27,8 @@ export class HomepodRadioPlatformConfig {
26
27
  this.mediaPath = this.config.mediaPath || '';
27
28
  this.enableVolumeControl = (this.config.enableVolumeControl ??= false);
28
29
  this.volume = this.config.volume || 25;
30
+ // On by default; an explicit config value (true/false) still wins.
31
+ this.keepConnectionWarm = this.config.keepConnectionWarm ?? DEFAULT_KEEP_CONNECTION_WARM;
29
32
  this.loadRadioConfigs();
30
33
  this.loadAudioConfigs();
31
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"platformConfig.js","sourceRoot":"","sources":["../src/platformConfig.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platformConfig.js","sourceRoot":"","sources":["../src/platformConfig.ts"],"names":[],"mappings":"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"}
@@ -2,3 +2,4 @@ export declare const PLATFORM_NAME = "HomepodRadioPlatform";
2
2
  export declare const PLUGIN_NAME = "@homebridge-plugins/homebridge-homepod-radio";
3
3
  export declare const PLUGIN_MANUFACTURER = "petro-kushchak";
4
4
  export declare const PLUGIN_MODEL = "Homepod Radio";
5
+ export declare const DEFAULT_KEEP_CONNECTION_WARM = true;
@@ -2,4 +2,10 @@ export const PLATFORM_NAME = 'HomepodRadioPlatform';
2
2
  export const PLUGIN_NAME = '@homebridge-plugins/homebridge-homepod-radio';
3
3
  export const PLUGIN_MANUFACTURER = 'petro-kushchak';
4
4
  export const PLUGIN_MODEL = 'Homepod Radio';
5
+ // Internal default for the warm-connection feature. The feature is on by
6
+ // default and only consumes resources when at least one audio button is
7
+ // configured (see platform.ts). It can still be opted out per-install by
8
+ // setting "keepConnectionWarm": false in config.json; flip this constant (or
9
+ // re-expose the option in config.schema.json) to change the default behavior.
10
+ export const DEFAULT_KEEP_CONNECTION_WARM = true;
5
11
  //# sourceMappingURL=platformConstants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"platformConstants.js","sourceRoot":"","sources":["../src/platformConstants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,sBAAsB,CAAC;AACpD,MAAM,CAAC,MAAM,WAAW,GAAG,8CAA8C,CAAC;AAE1E,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAEpD,MAAM,CAAC,MAAM,YAAY,GAAG,eAAe,CAAC"}
1
+ {"version":3,"file":"platformConstants.js","sourceRoot":"","sources":["../src/platformConstants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,sBAAsB,CAAC;AACpD,MAAM,CAAC,MAAM,WAAW,GAAG,8CAA8C,CAAC;AAE1E,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAEpD,MAAM,CAAC,MAAM,YAAY,GAAG,eAAe,CAAC;AAE5C,yEAAyE;AACzE,wEAAwE;AACxE,yEAAyE;AACzE,6EAA6E;AAC7E,8EAA8E;AAC9E,MAAM,CAAC,MAAM,4BAA4B,GAAG,IAAI,CAAC"}
@@ -0,0 +1,208 @@
1
+ # !/usr/bin/python3
2
+ """Resident pyatv worker for homebridge-homepod-radio (warm-connection mode).
3
+
4
+ Spawned once by the plugin when ``keepConnectionWarm`` is enabled. It imports
5
+ pyatv a single time, scans for and connects to the HomePod once, and then holds
6
+ that connection warm, reusing it for every audio-button press. This eliminates
7
+ the ~5 s per-press cost (``import pyatv`` cold start + device scan + connect)
8
+ that ``stream.py`` pays each time it is spawned fresh.
9
+
10
+ Protocol (newline-delimited JSON; one object per line):
11
+
12
+ stdin {"id": "<id>", "cmd": "play", "file": "<abs path>",
13
+ "volume": <0-100>, "title": "<text>"}
14
+ {"id": "<id>", "cmd": "ping"}
15
+
16
+ stdout {"event": "ready"} (once, after startup)
17
+ {"id": "<id>", "ok": true}
18
+ {"id": "<id>", "ok": false, "error": "..."}
19
+
20
+ All human-readable logging goes to **stderr** so that stdout stays a clean
21
+ protocol channel. ``volume`` of 0 (or missing) means "do not change volume",
22
+ matching the existing ``stream.py`` semantics.
23
+ """
24
+ import argparse
25
+ import asyncio
26
+ import json
27
+ import logging
28
+ import os
29
+ import re
30
+ import sys
31
+
32
+ import pyatv
33
+ from pyatv.const import Protocol
34
+ from pyatv.interface import MediaMetadata
35
+
36
+
37
+ _LOGGER = logging.getLogger("warm-worker")
38
+
39
+
40
+ def _out(obj) -> None:
41
+ """Write one protocol message to stdout and flush immediately."""
42
+ sys.stdout.write(json.dumps(obj) + "\n")
43
+ sys.stdout.flush()
44
+
45
+
46
+ class WarmConnection:
47
+ """Owns a single, reused pyatv connection to the target device."""
48
+
49
+ def __init__(self, identifier: str, loop: asyncio.AbstractEventLoop) -> None:
50
+ self.identifier = identifier
51
+ self.loop = loop
52
+ self.atv = None
53
+ self._lock = asyncio.Lock()
54
+
55
+ async def _scan(self):
56
+ # Mirror stream.py: scan by 12-hex id / MAC first, else by RAOP name.
57
+ ident_regex = re.compile(r"^[0-9A-Fa-f]{12}$")
58
+ mac_regex = re.compile(r"^[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){5}$")
59
+ atvs = []
60
+ if (len(self.identifier) == 12 and ident_regex.match(self.identifier)) or mac_regex.match(
61
+ self.identifier
62
+ ):
63
+ _LOGGER.info("scanning by id/MAC: %s", self.identifier)
64
+ atvs = await pyatv.scan(self.loop, identifier=self.identifier, timeout=5)
65
+ if not atvs:
66
+ _LOGGER.info("scanning by name: %s", self.identifier)
67
+ found = await pyatv.scan(self.loop, protocol=Protocol.RAOP, timeout=5)
68
+ atvs = [a for a in found if a.name == self.identifier]
69
+ if not atvs:
70
+ raise RuntimeError("device not found: %s" % self.identifier)
71
+ return atvs[0]
72
+
73
+ async def ensure_connected(self):
74
+ if self.atv is not None:
75
+ return self.atv
76
+ conf = await self._scan()
77
+ _LOGGER.info("connecting to %s", conf.address)
78
+ self.atv = await pyatv.connect(conf, self.loop)
79
+ _LOGGER.info("connected; holding connection warm")
80
+ return self.atv
81
+
82
+ def close(self) -> None:
83
+ if self.atv is not None:
84
+ try:
85
+ self.atv.close()
86
+ except Exception: # noqa: BLE001 - best-effort teardown
87
+ pass
88
+ self.atv = None
89
+
90
+ async def _expand(self, file_path: str):
91
+ # Mirror stream.py m3u/m3u8 handling so warm playback matches spawn.
92
+ if file_path.endswith(".m3u") or file_path.endswith(".m3u8"):
93
+ folder = os.path.dirname(file_path)
94
+ with open(file_path, "r", encoding="UTF-8") as playlist:
95
+ lines = [ln.strip() for ln in playlist if ln.strip()]
96
+ return [
97
+ os.path.join(folder, ln)
98
+ for ln in lines
99
+ if re.match(r"^[A-Za-z0-9]", ln)
100
+ and not re.match(r"^[A-Za-z]:\\", ln)
101
+ and not re.match(r"^http[s]?://", ln)
102
+ ]
103
+ return [file_path]
104
+
105
+ async def play(self, file_path: str, volume, title: str) -> None:
106
+ """Stream a file (or m3u) on the warm connection, reconnecting once on error."""
107
+ async with self._lock:
108
+ last_err = None
109
+ for attempt in (1, 2):
110
+ try:
111
+ atv = await self.ensure_connected()
112
+ if volume and int(volume) > 0:
113
+ try:
114
+ await atv.audio.set_volume(float(volume))
115
+ except Exception as ex: # noqa: BLE001
116
+ _LOGGER.warning("set_volume(%s) failed: %s", volume, ex)
117
+ metadata = MediaMetadata(title=title, album=title, artist=None, artwork=None)
118
+ for song in await self._expand(file_path):
119
+ _LOGGER.info("streaming %s (attempt %d)", song, attempt)
120
+ await atv.stream.stream_file(song, metadata)
121
+ _LOGGER.info("finished streaming %s", file_path)
122
+ return
123
+ except Exception as ex: # noqa: BLE001
124
+ last_err = ex
125
+ _LOGGER.error("stream attempt %d failed: %s", attempt, ex)
126
+ self.close() # force a fresh connect on retry
127
+ raise last_err if last_err is not None else RuntimeError("play failed")
128
+
129
+
130
+ async def _read_line(loop: asyncio.AbstractEventLoop) -> str:
131
+ # Blocking stdin read offloaded to a thread so the asyncio loop stays free.
132
+ return await loop.run_in_executor(None, sys.stdin.readline)
133
+
134
+
135
+ async def main_async(identifier: str) -> None:
136
+ loop = asyncio.get_running_loop()
137
+ conn = WarmConnection(identifier, loop)
138
+
139
+ # Best-effort warmup; a failure here is non-fatal because the first play
140
+ # will retry the scan/connect itself.
141
+ try:
142
+ await conn.ensure_connected()
143
+ except Exception as ex: # noqa: BLE001
144
+ _LOGGER.warning("initial warmup failed (will retry on first play): %s", ex)
145
+
146
+ _out({"event": "ready"})
147
+
148
+ while True:
149
+ line = await _read_line(loop)
150
+ if line == "": # EOF: parent closed our stdin
151
+ _LOGGER.info("stdin closed, exiting")
152
+ break
153
+ line = line.strip()
154
+ if not line:
155
+ continue
156
+ try:
157
+ msg = json.loads(line)
158
+ except Exception: # noqa: BLE001
159
+ _LOGGER.warning("ignoring non-JSON line: %s", line)
160
+ continue
161
+
162
+ req_id = msg.get("id")
163
+ cmd = msg.get("cmd")
164
+
165
+ if cmd == "ping":
166
+ _out({"id": req_id, "ok": True, "pong": True})
167
+ continue
168
+
169
+ if cmd == "play":
170
+ file_path = msg.get("file")
171
+ volume = msg.get("volume", 0)
172
+ title = msg.get("title") or (os.path.basename(file_path) if file_path else "")
173
+ if not file_path:
174
+ _out({"id": req_id, "ok": False, "error": "missing file"})
175
+ continue
176
+ try:
177
+ await conn.play(file_path, volume, title)
178
+ _out({"id": req_id, "ok": True})
179
+ except Exception as ex: # noqa: BLE001
180
+ _out({"id": req_id, "ok": False, "error": str(ex)})
181
+ continue
182
+
183
+ _out({"id": req_id, "ok": False, "error": "unknown cmd: %s" % cmd})
184
+
185
+ conn.close()
186
+
187
+
188
+ def main() -> None:
189
+ parser = argparse.ArgumentParser()
190
+ parser.add_argument("-i", "--id", dest="id", required=True, help="device identifier")
191
+ parser.add_argument("-v", "--verbose", action="store_true", dest="verbose")
192
+ args = parser.parse_args()
193
+
194
+ logging.basicConfig(
195
+ level=logging.DEBUG if args.verbose else logging.INFO,
196
+ stream=sys.stderr,
197
+ datefmt="%Y-%m-%d %H:%M:%S",
198
+ format="%(asctime)s %(levelname)s [%(name)s]: %(message)s",
199
+ )
200
+
201
+ try:
202
+ asyncio.run(main_async(args.id))
203
+ except KeyboardInterrupt:
204
+ pass
205
+
206
+
207
+ if __name__ == "__main__":
208
+ main()
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@homebridge-plugins/homebridge-homepod-radio",
3
3
  "displayName": "Homepod Mini Radio",
4
4
  "type": "module",
5
- "version": "3.2.7",
5
+ "version": "3.2.9",
6
6
  "description": "Homebridge accessory for streaming radio to Homepod Mini and Apple TV",
7
7
  "author": "Petro Kushchak",
8
8
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "prepublishOnly": "npm run lint && npm run build"
44
44
  },
45
45
  "dependencies": {
46
- "homebridge-lib": "^7.1.4"
46
+ "homebridge-lib": "^8.0.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@eslint/js": "^9.21.0",
@@ -53,7 +53,7 @@
53
53
  "nodemon": "^3.1.9",
54
54
  "rimraf": "^6.0.1",
55
55
  "ts-node": "^10.9.2",
56
- "typescript": "^5.7.3",
56
+ "typescript": "^6.0.3",
57
57
  "typescript-eslint": "^8.24.1"
58
58
  }
59
59
  }