@basmilius/apple-devices 0.9.11 → 0.9.12

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/dist/index.d.mts CHANGED
@@ -5,11 +5,12 @@ import { EventEmitter } from "node:events";
5
5
  import { AccessoryCredentials, AudioSource, DiscoveryResult, TimingServer } from "@basmilius/apple-common";
6
6
  import { AttentionState, ButtonPressType, HidCommandKey, LaunchableApp, MediaControlCommandKey, Protocol as Protocol$1, UserAccount } from "@basmilius/apple-companion-link";
7
7
 
8
- //#region src/airplay/client.d.ts
9
- declare class Client {
8
+ //#region src/airplay/player.d.ts
9
+ declare class Player {
10
10
  #private;
11
- get bundleIdentifier(): string;
11
+ get identifier(): string;
12
12
  get displayName(): string;
13
+ get isDefaultPlayer(): boolean;
13
14
  get nowPlayingInfo(): Proto.NowPlayingInfo | null;
14
15
  get playbackQueue(): Proto.PlaybackQueue | null;
15
16
  get playbackState(): Proto.PlaybackState_Enum;
@@ -35,7 +36,7 @@ declare class Client {
35
36
  get currentItemArtwork(): Uint8Array | null;
36
37
  get currentItemArtworkUrl(): string | null;
37
38
  get currentItemLyrics(): Proto.LyricsItem | null;
38
- constructor(bundleIdentifier: string, displayName: string);
39
+ constructor(identifier: string, displayName: string);
39
40
  isCommandSupported(command: Proto.Command): boolean;
40
41
  setNowPlayingInfo(nowPlayingInfo: Proto.NowPlayingInfo): void;
41
42
  setPlaybackQueue(playbackQueue: Proto.PlaybackQueue): void;
@@ -44,6 +45,46 @@ declare class Client {
44
45
  updateContentItem(item: Proto.ContentItem): void;
45
46
  }
46
47
  //#endregion
48
+ //#region src/airplay/client.d.ts
49
+ declare class Client {
50
+ #private;
51
+ get bundleIdentifier(): string;
52
+ get displayName(): string;
53
+ get players(): Map<string, Player>;
54
+ get activePlayer(): Player | null;
55
+ get nowPlayingInfo(): Proto.NowPlayingInfo | null;
56
+ get playbackQueue(): Proto.PlaybackQueue | null;
57
+ get playbackState(): Proto.PlaybackState_Enum;
58
+ get playbackStateTimestamp(): number;
59
+ get supportedCommands(): Proto.CommandInfo[];
60
+ get title(): string;
61
+ get artist(): string;
62
+ get album(): string;
63
+ get genre(): string;
64
+ get seriesName(): string;
65
+ get seasonNumber(): number;
66
+ get episodeNumber(): number;
67
+ get mediaType(): Proto.ContentItemMetadata_MediaType;
68
+ get contentIdentifier(): string;
69
+ get duration(): number;
70
+ get playbackRate(): number;
71
+ get isPlaying(): boolean;
72
+ get shuffleMode(): Proto.ShuffleMode_Enum;
73
+ get repeatMode(): Proto.RepeatMode_Enum;
74
+ get elapsedTime(): number;
75
+ get currentItem(): Proto.ContentItem | null;
76
+ get currentItemMetadata(): Proto.ContentItemMetadata | null;
77
+ get currentItemArtwork(): Uint8Array | null;
78
+ get currentItemArtworkUrl(): string | null;
79
+ get currentItemLyrics(): Proto.LyricsItem | null;
80
+ constructor(bundleIdentifier: string, displayName: string);
81
+ getOrCreatePlayer(identifier: string, displayName?: string): Player;
82
+ setActivePlayer(identifier: string): void;
83
+ removePlayer(identifier: string): void;
84
+ setDefaultSupportedCommands(supportedCommands: Proto.CommandInfo[]): void;
85
+ isCommandSupported(command: Proto.Command): boolean;
86
+ }
87
+ //#endregion
47
88
  //#region src/airplay/const.d.ts
48
89
  declare const PROTOCOL: unique symbol;
49
90
  declare const STATE_SUBSCRIBE_SYMBOL: unique symbol;
@@ -102,6 +143,7 @@ type EventMap$4 = {
102
143
  readonly originClientProperties: [Proto.OriginClientPropertiesMessage];
103
144
  readonly playerClientProperties: [Proto.PlayerClientPropertiesMessage];
104
145
  readonly removeClient: [Proto.RemoveClientMessage];
146
+ readonly removePlayer: [Proto.RemovePlayerMessage];
105
147
  readonly sendCommandResult: [Proto.SendCommandResultMessage];
106
148
  readonly setArtwork: [Proto.SetArtworkMessage];
107
149
  readonly setDefaultSupportedCommands: [Proto.SetDefaultSupportedCommandsMessage];
@@ -144,6 +186,7 @@ declare class export_default$7 extends EventEmitter<EventMap$4> {
144
186
  onUpdateContentItem(message: Proto.UpdateContentItemMessage): void;
145
187
  onUpdateContentItemArtwork(message: Proto.UpdateContentItemArtworkMessage): void;
146
188
  onUpdatePlayer(message: Proto.UpdatePlayerMessage): void;
189
+ onRemovePlayer(message: Proto.RemovePlayerMessage): void;
147
190
  onUpdateClient(message: Proto.UpdateClientMessage): void;
148
191
  onUpdateOutputDevice(message: Proto.UpdateOutputDeviceMessage): void;
149
192
  onVolumeControlAvailability(message: Proto.VolumeControlAvailabilityMessage): void;
package/dist/index.mjs CHANGED
@@ -4,21 +4,25 @@ import { EventEmitter } from "node:events";
4
4
  import { waitFor } from "@basmilius/apple-common";
5
5
  import { Protocol as Protocol$1, convertAttentionState } from "@basmilius/apple-companion-link";
6
6
 
7
- //#region src/airplay/client.ts
7
+ //#region src/airplay/player.ts
8
8
  const COCOA_EPOCH_OFFSET = 978307200;
9
+ const DEFAULT_PLAYER_ID = "MediaRemote-DefaultPlayer";
9
10
  const extrapolateElapsed = (elapsed, cocoaTimestamp, rate) => {
10
11
  if (!rate) return elapsed;
11
12
  const timestampUnix = cocoaTimestamp + COCOA_EPOCH_OFFSET;
12
13
  const delta = (Date.now() / 1e3 - timestampUnix) * rate;
13
14
  return Math.max(0, elapsed + delta);
14
15
  };
15
- var Client = class {
16
- get bundleIdentifier() {
17
- return this.#bundleIdentifier;
16
+ var Player = class {
17
+ get identifier() {
18
+ return this.#identifier;
18
19
  }
19
20
  get displayName() {
20
21
  return this.#displayName;
21
22
  }
23
+ get isDefaultPlayer() {
24
+ return this.#identifier === DEFAULT_PLAYER_ID;
25
+ }
22
26
  get nowPlayingInfo() {
23
27
  return this.#nowPlayingInfo;
24
28
  }
@@ -79,8 +83,14 @@ var Client = class {
79
83
  get elapsedTime() {
80
84
  const npi = this.#nowPlayingInfo;
81
85
  const meta = this.currentItemMetadata;
82
- if (npi?.elapsedTime != null && npi.timestamp) return extrapolateElapsed(npi.elapsedTime, npi.timestamp, npi.playbackRate);
83
- if (meta?.elapsedTime != null && meta.elapsedTimeTimestamp) return extrapolateElapsed(meta.elapsedTime, meta.elapsedTimeTimestamp, meta.playbackRate);
86
+ const npiValid = npi?.elapsedTime != null && npi.timestamp;
87
+ const metaValid = meta?.elapsedTime != null && meta.elapsedTimeTimestamp;
88
+ if (npiValid && metaValid) {
89
+ if (meta.elapsedTimeTimestamp > npi.timestamp) return extrapolateElapsed(meta.elapsedTime, meta.elapsedTimeTimestamp, meta.playbackRate);
90
+ return extrapolateElapsed(npi.elapsedTime, npi.timestamp, npi.playbackRate);
91
+ }
92
+ if (npiValid) return extrapolateElapsed(npi.elapsedTime, npi.timestamp, npi.playbackRate);
93
+ if (metaValid) return extrapolateElapsed(meta.elapsedTime, meta.elapsedTimeTimestamp, meta.playbackRate);
84
94
  return npi?.elapsedTime || meta?.elapsedTime || 0;
85
95
  }
86
96
  get currentItem() {
@@ -107,18 +117,17 @@ var Client = class {
107
117
  get currentItemLyrics() {
108
118
  return this.currentItem?.lyrics ?? null;
109
119
  }
110
- #bundleIdentifier;
120
+ #identifier;
111
121
  #displayName;
112
122
  #nowPlayingInfo = null;
113
123
  #playbackQueue = null;
114
124
  #playbackState;
115
- #playbackStateTimestamp;
125
+ #playbackStateTimestamp = 0;
116
126
  #supportedCommands = [];
117
- constructor(bundleIdentifier, displayName) {
118
- this.#bundleIdentifier = bundleIdentifier;
127
+ constructor(identifier, displayName) {
128
+ this.#identifier = identifier;
119
129
  this.#displayName = displayName;
120
130
  this.#playbackState = Proto.PlaybackState_Enum.Unknown;
121
- this.#supportedCommands = [];
122
131
  }
123
132
  isCommandSupported(command) {
124
133
  return this.#supportedCommands.some((c) => c.command === command);
@@ -150,6 +159,129 @@ var Client = class {
150
159
  }
151
160
  };
152
161
 
162
+ //#endregion
163
+ //#region src/airplay/client.ts
164
+ var Client = class {
165
+ get bundleIdentifier() {
166
+ return this.#bundleIdentifier;
167
+ }
168
+ get displayName() {
169
+ return this.#displayName;
170
+ }
171
+ get players() {
172
+ return this.#players;
173
+ }
174
+ get activePlayer() {
175
+ if (this.#activePlayerId) return this.#players.get(this.#activePlayerId) ?? null;
176
+ return this.#players.get(DEFAULT_PLAYER_ID) ?? null;
177
+ }
178
+ get nowPlayingInfo() {
179
+ return this.activePlayer?.nowPlayingInfo ?? null;
180
+ }
181
+ get playbackQueue() {
182
+ return this.activePlayer?.playbackQueue ?? null;
183
+ }
184
+ get playbackState() {
185
+ return this.activePlayer?.playbackState ?? Proto.PlaybackState_Enum.Unknown;
186
+ }
187
+ get playbackStateTimestamp() {
188
+ return this.activePlayer?.playbackStateTimestamp ?? -1;
189
+ }
190
+ get supportedCommands() {
191
+ return this.activePlayer?.supportedCommands ?? this.#defaultSupportedCommands;
192
+ }
193
+ get title() {
194
+ return this.activePlayer?.title ?? "";
195
+ }
196
+ get artist() {
197
+ return this.activePlayer?.artist ?? "";
198
+ }
199
+ get album() {
200
+ return this.activePlayer?.album ?? "";
201
+ }
202
+ get genre() {
203
+ return this.activePlayer?.genre ?? "";
204
+ }
205
+ get seriesName() {
206
+ return this.activePlayer?.seriesName ?? "";
207
+ }
208
+ get seasonNumber() {
209
+ return this.activePlayer?.seasonNumber ?? 0;
210
+ }
211
+ get episodeNumber() {
212
+ return this.activePlayer?.episodeNumber ?? 0;
213
+ }
214
+ get mediaType() {
215
+ return this.activePlayer?.mediaType ?? Proto.ContentItemMetadata_MediaType.UnknownMediaType;
216
+ }
217
+ get contentIdentifier() {
218
+ return this.activePlayer?.contentIdentifier ?? "";
219
+ }
220
+ get duration() {
221
+ return this.activePlayer?.duration ?? 0;
222
+ }
223
+ get playbackRate() {
224
+ return this.activePlayer?.playbackRate ?? 0;
225
+ }
226
+ get isPlaying() {
227
+ return this.activePlayer?.isPlaying ?? false;
228
+ }
229
+ get shuffleMode() {
230
+ return this.activePlayer?.shuffleMode ?? Proto.ShuffleMode_Enum.Unknown;
231
+ }
232
+ get repeatMode() {
233
+ return this.activePlayer?.repeatMode ?? Proto.RepeatMode_Enum.Unknown;
234
+ }
235
+ get elapsedTime() {
236
+ return this.activePlayer?.elapsedTime ?? 0;
237
+ }
238
+ get currentItem() {
239
+ return this.activePlayer?.currentItem ?? null;
240
+ }
241
+ get currentItemMetadata() {
242
+ return this.activePlayer?.currentItemMetadata ?? null;
243
+ }
244
+ get currentItemArtwork() {
245
+ return this.activePlayer?.currentItemArtwork ?? null;
246
+ }
247
+ get currentItemArtworkUrl() {
248
+ return this.activePlayer?.currentItemArtworkUrl ?? null;
249
+ }
250
+ get currentItemLyrics() {
251
+ return this.activePlayer?.currentItemLyrics ?? null;
252
+ }
253
+ #bundleIdentifier;
254
+ #displayName;
255
+ #players = /* @__PURE__ */ new Map();
256
+ #activePlayerId = null;
257
+ #defaultSupportedCommands = [];
258
+ constructor(bundleIdentifier, displayName) {
259
+ this.#bundleIdentifier = bundleIdentifier;
260
+ this.#displayName = displayName;
261
+ }
262
+ getOrCreatePlayer(identifier, displayName) {
263
+ let player = this.#players.get(identifier);
264
+ if (!player) {
265
+ player = new Player(identifier, displayName ?? identifier);
266
+ this.#players.set(identifier, player);
267
+ }
268
+ return player;
269
+ }
270
+ setActivePlayer(identifier) {
271
+ this.#activePlayerId = identifier;
272
+ }
273
+ removePlayer(identifier) {
274
+ this.#players.delete(identifier);
275
+ if (this.#activePlayerId === identifier) this.#activePlayerId = null;
276
+ }
277
+ setDefaultSupportedCommands(supportedCommands) {
278
+ this.#defaultSupportedCommands = supportedCommands;
279
+ }
280
+ isCommandSupported(command) {
281
+ return this.activePlayer?.isCommandSupported(command) ?? false;
282
+ }
283
+ };
284
+
153
285
  //#endregion
154
286
  //#region src/airplay/const.ts
155
287
  const FEEDBACK_INTERVAL = 2e3;
@@ -364,6 +496,7 @@ var state_default = class extends EventEmitter {
364
496
  this.onOriginClientProperties = this.onOriginClientProperties.bind(this);
365
497
  this.onPlayerClientProperties = this.onPlayerClientProperties.bind(this);
366
498
  this.onRemoveClient = this.onRemoveClient.bind(this);
499
+ this.onRemovePlayer = this.onRemovePlayer.bind(this);
367
500
  this.onSendCommandResult = this.onSendCommandResult.bind(this);
368
501
  this.onSetArtwork = this.onSetArtwork.bind(this);
369
502
  this.onSetDefaultSupportedCommands = this.onSetDefaultSupportedCommands.bind(this);
@@ -385,6 +518,7 @@ var state_default = class extends EventEmitter {
385
518
  this.#dataStream.on("originClientProperties", this.onOriginClientProperties);
386
519
  this.#dataStream.on("playerClientProperties", this.onPlayerClientProperties);
387
520
  this.#dataStream.on("removeClient", this.onRemoveClient);
521
+ this.#dataStream.on("removePlayer", this.onRemovePlayer);
388
522
  this.#dataStream.on("sendCommandResult", this.onSendCommandResult);
389
523
  this.#dataStream.on("setArtwork", this.onSetArtwork);
390
524
  this.#dataStream.on("setDefaultSupportedCommands", this.onSetDefaultSupportedCommands);
@@ -408,6 +542,7 @@ var state_default = class extends EventEmitter {
408
542
  dataStream.off("originClientProperties", this.onOriginClientProperties);
409
543
  dataStream.off("playerClientProperties", this.onPlayerClientProperties);
410
544
  dataStream.off("removeClient", this.onRemoveClient);
545
+ dataStream.off("removePlayer", this.onRemovePlayer);
411
546
  dataStream.off("sendCommandResult", this.onSendCommandResult);
412
547
  dataStream.off("setArtwork", this.onSetArtwork);
413
548
  dataStream.off("setDefaultSupportedCommands", this.onSetDefaultSupportedCommands);
@@ -453,6 +588,8 @@ var state_default = class extends EventEmitter {
453
588
  onRemoveClient(message) {
454
589
  if (!(message.client.bundleIdentifier in this.#clients)) return;
455
590
  delete this.#clients[message.client.bundleIdentifier];
591
+ if (this.#nowPlayingClientBundleIdentifier === message.client.bundleIdentifier) this.#nowPlayingClientBundleIdentifier = null;
592
+ this.emit("removeClient", message);
456
593
  this.emit("clients", this.#clients);
457
594
  }
458
595
  onSendCommandResult(message) {
@@ -462,6 +599,7 @@ var state_default = class extends EventEmitter {
462
599
  this.emit("setArtwork", message);
463
600
  }
464
601
  onSetDefaultSupportedCommands(message) {
602
+ if (message.playerPath?.client?.bundleIdentifier && message.supportedCommands) this.#client(message.playerPath.client.bundleIdentifier, message.playerPath.client.displayName).setDefaultSupportedCommands(message.supportedCommands.supportedCommands);
465
603
  this.emit("setDefaultSupportedCommands", message);
466
604
  }
467
605
  onSetNowPlayingClient(message) {
@@ -469,28 +607,44 @@ var state_default = class extends EventEmitter {
469
607
  this.emit("setNowPlayingClient", message);
470
608
  }
471
609
  onSetNowPlayingPlayer(message) {
610
+ if (message.playerPath?.client?.bundleIdentifier && message.playerPath?.player?.identifier) {
611
+ const client = this.#client(message.playerPath.client.bundleIdentifier, message.playerPath.client.displayName);
612
+ client.getOrCreatePlayer(message.playerPath.player.identifier, message.playerPath.player.displayName);
613
+ client.setActivePlayer(message.playerPath.player.identifier);
614
+ }
472
615
  this.emit("setNowPlayingPlayer", message);
473
616
  }
474
617
  onSetState(message) {
475
618
  const client = this.#client(message.playerPath.client.bundleIdentifier, message.displayName);
476
- if (message.nowPlayingInfo) client.setNowPlayingInfo(message.nowPlayingInfo);
477
- if (message.playbackState) client.setPlaybackState(message.playbackState, message.playbackStateTimestamp);
478
- if (message.supportedCommands) client.setSupportedCommands(message.supportedCommands.supportedCommands);
479
- if (message.playbackQueue) client.setPlaybackQueue(message.playbackQueue);
619
+ const playerIdentifier = message.playerPath?.player?.identifier || "MediaRemote-DefaultPlayer";
620
+ const player = client.getOrCreatePlayer(playerIdentifier, message.playerPath?.player?.displayName);
621
+ if (message.nowPlayingInfo) player.setNowPlayingInfo(message.nowPlayingInfo);
622
+ if (message.playbackState) player.setPlaybackState(message.playbackState, message.playbackStateTimestamp);
623
+ if (message.supportedCommands) player.setSupportedCommands(message.supportedCommands.supportedCommands);
624
+ if (message.playbackQueue) player.setPlaybackQueue(message.playbackQueue);
480
625
  this.emit("setState", message);
481
626
  }
482
627
  onUpdateContentItem(message) {
483
628
  const client = this.#client(message.playerPath.client.bundleIdentifier, message.playerPath.client.displayName);
484
- if (!client) return;
485
- for (const item of message.contentItems) client.updateContentItem(item);
629
+ const playerIdentifier = message.playerPath?.player?.identifier || "MediaRemote-DefaultPlayer";
630
+ const player = client.getOrCreatePlayer(playerIdentifier, message.playerPath?.player?.displayName);
631
+ for (const item of message.contentItems) player.updateContentItem(item);
486
632
  this.emit("updateContentItem", message);
487
633
  }
488
634
  onUpdateContentItemArtwork(message) {
489
635
  this.emit("updateContentItemArtwork", message);
490
636
  }
491
637
  onUpdatePlayer(message) {
638
+ if (message.playerPath?.client?.bundleIdentifier && message.playerPath?.player?.identifier) this.#client(message.playerPath.client.bundleIdentifier, message.playerPath.client.displayName).getOrCreatePlayer(message.playerPath.player.identifier, message.playerPath.player.displayName);
492
639
  this.emit("updatePlayer", message);
493
640
  }
641
+ onRemovePlayer(message) {
642
+ if (message.playerPath?.client?.bundleIdentifier && message.playerPath?.player?.identifier) {
643
+ const client = this.#clients[message.playerPath.client.bundleIdentifier];
644
+ if (client) client.removePlayer(message.playerPath.player.identifier);
645
+ }
646
+ this.emit("removePlayer", message);
647
+ }
494
648
  onUpdateClient(message) {
495
649
  this.#client(message.client.bundleIdentifier, message.client.displayName);
496
650
  this.emit("clients", this.#clients);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@basmilius/apple-devices",
3
3
  "description": "Exposes various Apple devices to connect with either AirPlay or Companion Link.",
4
- "version": "0.9.11",
4
+ "version": "0.9.12",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": {
@@ -49,10 +49,10 @@
49
49
  }
50
50
  },
51
51
  "dependencies": {
52
- "@basmilius/apple-airplay": "0.9.11",
53
- "@basmilius/apple-common": "0.9.11",
54
- "@basmilius/apple-companion-link": "0.9.11",
55
- "@basmilius/apple-encoding": "0.9.11"
52
+ "@basmilius/apple-airplay": "0.9.12",
53
+ "@basmilius/apple-common": "0.9.12",
54
+ "@basmilius/apple-companion-link": "0.9.12",
55
+ "@basmilius/apple-encoding": "0.9.12"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/bun": "^1.3.9",