@basmilius/apple-devices 0.9.18 → 0.9.19
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 +34 -3
- package/dist/index.mjs +259 -31
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import * as AirPlay from "@basmilius/apple-airplay";
|
|
3
3
|
import { Proto, Protocol } from "@basmilius/apple-airplay";
|
|
4
4
|
import { EventEmitter } from "node:events";
|
|
5
|
-
import { AccessoryCredentials, AudioSource, DiscoveryResult, TimingServer } from "@basmilius/apple-common";
|
|
6
|
-
import { AttentionState, ButtonPressType, HidCommandKey, LaunchableApp, MediaControlCommandKey, Protocol as Protocol$1, UserAccount } from "@basmilius/apple-companion-link";
|
|
5
|
+
import { AccessoryCredentials, AudioSource, CommandError, DiscoveryResult, TimingServer } from "@basmilius/apple-common";
|
|
6
|
+
import { AttentionState, ButtonPressType, HidCommandKey, LaunchableApp, MediaControlCommandKey, Protocol as Protocol$1, TextInputState, UserAccount } from "@basmilius/apple-companion-link";
|
|
7
7
|
|
|
8
8
|
//#region src/airplay/player.d.ts
|
|
9
9
|
declare class Player {
|
|
@@ -99,7 +99,7 @@ declare const STATE_SUBSCRIBE_SYMBOL: unique symbol;
|
|
|
99
99
|
declare const STATE_UNSUBSCRIBE_SYMBOL: unique symbol;
|
|
100
100
|
//#endregion
|
|
101
101
|
//#region src/airplay/remote.d.ts
|
|
102
|
-
declare class SendCommandError extends
|
|
102
|
+
declare class SendCommandError extends CommandError {
|
|
103
103
|
readonly sendError: Proto.SendError_Enum;
|
|
104
104
|
readonly handlerReturnStatus: Proto.HandlerReturnStatus_Enum;
|
|
105
105
|
constructor(sendError: Proto.SendError_Enum, handlerReturnStatus: Proto.HandlerReturnStatus_Enum);
|
|
@@ -119,11 +119,15 @@ declare class export_default$1 {
|
|
|
119
119
|
play(): Promise<void>;
|
|
120
120
|
pause(): Promise<void>;
|
|
121
121
|
playPause(): Promise<void>;
|
|
122
|
+
stop(): Promise<void>;
|
|
122
123
|
next(): Promise<void>;
|
|
123
124
|
previous(): Promise<void>;
|
|
124
125
|
volumeUp(): Promise<void>;
|
|
125
126
|
volumeDown(): Promise<void>;
|
|
126
127
|
mute(): Promise<void>;
|
|
128
|
+
topMenu(): Promise<void>;
|
|
129
|
+
channelUp(): Promise<void>;
|
|
130
|
+
channelDown(): Promise<void>;
|
|
127
131
|
commandPlay(): Promise<void>;
|
|
128
132
|
commandPause(): Promise<void>;
|
|
129
133
|
commandTogglePlayPause(): Promise<void>;
|
|
@@ -148,6 +152,10 @@ declare class export_default$1 {
|
|
|
148
152
|
commandDislikeTrack(): Promise<void>;
|
|
149
153
|
commandBookmarkTrack(): Promise<void>;
|
|
150
154
|
commandAddNowPlayingItemToLibrary(): Promise<void>;
|
|
155
|
+
textSet(text: string): Promise<void>;
|
|
156
|
+
textAppend(text: string): Promise<void>;
|
|
157
|
+
textClear(): Promise<void>;
|
|
158
|
+
getKeyboardSession(): Promise<void>;
|
|
151
159
|
tap(x: number, y: number, finger?: number): Promise<void>;
|
|
152
160
|
swipeUp(duration?: number): Promise<void>;
|
|
153
161
|
swipeDown(duration?: number): Promise<void>;
|
|
@@ -163,6 +171,7 @@ type EventMap$4 = {
|
|
|
163
171
|
readonly clients: [Record<string, Client>];
|
|
164
172
|
readonly deviceInfo: [Proto.DeviceInfoMessage];
|
|
165
173
|
readonly deviceInfoUpdate: [Proto.DeviceInfoMessage];
|
|
174
|
+
readonly keyboard: [Proto.KeyboardMessage];
|
|
166
175
|
readonly nowPlayingChanged: [client: Client | null, player: Player | null];
|
|
167
176
|
readonly originClientProperties: [Proto.OriginClientPropertiesMessage];
|
|
168
177
|
readonly playerClientProperties: [Proto.PlayerClientPropertiesMessage];
|
|
@@ -186,9 +195,15 @@ type EventMap$4 = {
|
|
|
186
195
|
declare class export_default$2 extends EventEmitter<EventMap$4> {
|
|
187
196
|
#private;
|
|
188
197
|
get clients(): Record<string, Client>;
|
|
198
|
+
get isKeyboardActive(): boolean;
|
|
199
|
+
get keyboardAttributes(): Proto.TextEditingAttributes | null;
|
|
200
|
+
get keyboardState(): Proto.KeyboardState_Enum;
|
|
189
201
|
get nowPlayingClient(): Client | null;
|
|
190
202
|
get outputDeviceUID(): string | null;
|
|
191
203
|
get outputDevices(): Proto.AVOutputDeviceDescriptor[];
|
|
204
|
+
get clusterID(): string | null;
|
|
205
|
+
get clusterType(): number;
|
|
206
|
+
get isClusterAware(): boolean;
|
|
192
207
|
get volume(): number;
|
|
193
208
|
get volumeAvailable(): boolean;
|
|
194
209
|
get volumeCapabilities(): Proto.VolumeCapabilities_Enum;
|
|
@@ -196,6 +211,7 @@ declare class export_default$2 extends EventEmitter<EventMap$4> {
|
|
|
196
211
|
[STATE_SUBSCRIBE_SYMBOL](): void;
|
|
197
212
|
[STATE_UNSUBSCRIBE_SYMBOL](): void;
|
|
198
213
|
clear(): void;
|
|
214
|
+
onKeyboard(message: Proto.KeyboardMessage): void;
|
|
199
215
|
onDeviceInfo(message: Proto.DeviceInfoMessage): void;
|
|
200
216
|
onDeviceInfoUpdate(message: Proto.DeviceInfoMessage): void;
|
|
201
217
|
onOriginClientProperties(message: Proto.OriginClientPropertiesMessage): void;
|
|
@@ -251,6 +267,9 @@ declare class export_default extends EventEmitter<EventMap$3> {
|
|
|
251
267
|
addOutputDevices(deviceUIDs: string[]): Promise<void>;
|
|
252
268
|
removeOutputDevices(deviceUIDs: string[]): Promise<void>;
|
|
253
269
|
setOutputDevices(deviceUIDs: string[]): Promise<void>;
|
|
270
|
+
playUrl(url: string, position?: number): Promise<void>;
|
|
271
|
+
waitForPlaybackEnd(): Promise<void>;
|
|
272
|
+
stopPlayUrl(): void;
|
|
254
273
|
streamAudio(source: AudioSource): Promise<void>;
|
|
255
274
|
requestPlaybackQueue(length: number): Promise<void>;
|
|
256
275
|
sendCommand(command: Proto.Command, options?: Proto.CommandOptions): Promise<void>;
|
|
@@ -265,6 +284,7 @@ type EventMap$2 = {
|
|
|
265
284
|
connected: [];
|
|
266
285
|
disconnected: [unexpected: boolean];
|
|
267
286
|
power: [AttentionState];
|
|
287
|
+
textInput: [TextInputState];
|
|
268
288
|
};
|
|
269
289
|
declare class export_default$5 extends EventEmitter<EventMap$2> {
|
|
270
290
|
#private;
|
|
@@ -272,6 +292,7 @@ declare class export_default$5 extends EventEmitter<EventMap$2> {
|
|
|
272
292
|
get discoveryResult(): DiscoveryResult;
|
|
273
293
|
set discoveryResult(discoveryResult: DiscoveryResult);
|
|
274
294
|
get isConnected(): boolean;
|
|
295
|
+
get textInputState(): TextInputState;
|
|
275
296
|
constructor(discoveryResult: DiscoveryResult);
|
|
276
297
|
connect(): Promise<void>;
|
|
277
298
|
disconnect(): Promise<void>;
|
|
@@ -285,6 +306,9 @@ declare class export_default$5 extends EventEmitter<EventMap$2> {
|
|
|
285
306
|
mediaControlCommand(command: MediaControlCommandKey, content?: object): Promise<void>;
|
|
286
307
|
pressButton(command: HidCommandKey, type?: ButtonPressType, holdDelayMs?: number): Promise<void>;
|
|
287
308
|
switchUserAccount(accountId: string): Promise<void>;
|
|
309
|
+
textSet(text: string): Promise<void>;
|
|
310
|
+
textAppend(text: string): Promise<void>;
|
|
311
|
+
textClear(): Promise<void>;
|
|
288
312
|
onSystemStatus(data: {
|
|
289
313
|
readonly state: number;
|
|
290
314
|
}): Promise<void>;
|
|
@@ -298,6 +322,7 @@ type EventMap$1 = {
|
|
|
298
322
|
connected: [];
|
|
299
323
|
disconnected: [unexpected: boolean];
|
|
300
324
|
power: [AttentionState];
|
|
325
|
+
textInput: [TextInputState];
|
|
301
326
|
};
|
|
302
327
|
declare class export_default$4 extends EventEmitter<EventMap$1> {
|
|
303
328
|
#private;
|
|
@@ -338,6 +363,9 @@ declare class export_default$4 extends EventEmitter<EventMap$1> {
|
|
|
338
363
|
getUserAccounts(): Promise<UserAccount[]>;
|
|
339
364
|
launchApp(bundleId: string): Promise<void>;
|
|
340
365
|
switchUserAccount(accountId: string): Promise<void>;
|
|
366
|
+
textSet(text: string): Promise<void>;
|
|
367
|
+
textAppend(text: string): Promise<void>;
|
|
368
|
+
textClear(): Promise<void>;
|
|
341
369
|
getCommandInfo(command: AirPlay.Proto.Command): Promise<AirPlay.Proto.CommandInfo | null>;
|
|
342
370
|
isCommandSupported(command: AirPlay.Proto.Command): Promise<boolean>;
|
|
343
371
|
}
|
|
@@ -375,6 +403,9 @@ declare abstract class export_default$8 extends EventEmitter<EventMap> {
|
|
|
375
403
|
stop(): Promise<void>;
|
|
376
404
|
next(): Promise<void>;
|
|
377
405
|
previous(): Promise<void>;
|
|
406
|
+
playUrl(url: string, position?: number): Promise<void>;
|
|
407
|
+
waitForPlaybackEnd(): Promise<void>;
|
|
408
|
+
stopPlayUrl(): void;
|
|
378
409
|
streamAudio(source: AudioSource): Promise<void>;
|
|
379
410
|
getCommandInfo(command: AirPlay.Proto.Command): Promise<AirPlay.Proto.CommandInfo | null>;
|
|
380
411
|
isCommandSupported(command: AirPlay.Proto.Command): Promise<boolean>;
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as AirPlay from "@basmilius/apple-airplay";
|
|
2
2
|
import { DataStreamMessage, Proto, Protocol } from "@basmilius/apple-airplay";
|
|
3
3
|
import { EventEmitter } from "node:events";
|
|
4
|
-
import { waitFor } from "@basmilius/apple-common";
|
|
4
|
+
import { CommandError, CredentialsError, waitFor } from "@basmilius/apple-common";
|
|
5
5
|
import { Protocol as Protocol$1, convertAttentionState } from "@basmilius/apple-companion-link";
|
|
6
|
+
import { Plist } from "@basmilius/apple-encoding";
|
|
6
7
|
|
|
7
8
|
//#region src/airplay/player.ts
|
|
8
9
|
const COCOA_EPOCH_OFFSET = 978307200;
|
|
@@ -334,7 +335,7 @@ const STATE_UNSUBSCRIBE_SYMBOL = Symbol("com.basmilius.airplay:unsubscribe");
|
|
|
334
335
|
|
|
335
336
|
//#endregion
|
|
336
337
|
//#region src/airplay/remote.ts
|
|
337
|
-
var SendCommandError = class extends
|
|
338
|
+
var SendCommandError = class extends CommandError {
|
|
338
339
|
sendError;
|
|
339
340
|
handlerReturnStatus;
|
|
340
341
|
constructor(sendError, handlerReturnStatus) {
|
|
@@ -392,6 +393,9 @@ var remote_default = class {
|
|
|
392
393
|
if (this.#device.state.nowPlayingClient?.isPlaying) await this.pause();
|
|
393
394
|
else await this.play();
|
|
394
395
|
}
|
|
396
|
+
async stop() {
|
|
397
|
+
await this.pressAndRelease(12, 183);
|
|
398
|
+
}
|
|
395
399
|
async next() {
|
|
396
400
|
await this.pressAndRelease(12, 181);
|
|
397
401
|
}
|
|
@@ -407,6 +411,15 @@ var remote_default = class {
|
|
|
407
411
|
async mute() {
|
|
408
412
|
await this.pressAndRelease(12, 226);
|
|
409
413
|
}
|
|
414
|
+
async topMenu() {
|
|
415
|
+
await this.pressAndRelease(12, 96);
|
|
416
|
+
}
|
|
417
|
+
async channelUp() {
|
|
418
|
+
await this.pressAndRelease(12, 156);
|
|
419
|
+
}
|
|
420
|
+
async channelDown() {
|
|
421
|
+
await this.pressAndRelease(12, 157);
|
|
422
|
+
}
|
|
410
423
|
async commandPlay() {
|
|
411
424
|
await this.#sendCommand(Proto.Command.Play);
|
|
412
425
|
}
|
|
@@ -479,6 +492,18 @@ var remote_default = class {
|
|
|
479
492
|
async commandAddNowPlayingItemToLibrary() {
|
|
480
493
|
await this.#sendCommand(Proto.Command.AddNowPlayingItemToLibrary);
|
|
481
494
|
}
|
|
495
|
+
async textSet(text) {
|
|
496
|
+
await this.#dataStream.send(DataStreamMessage.textInput(text, Proto.ActionType_Enum.Set));
|
|
497
|
+
}
|
|
498
|
+
async textAppend(text) {
|
|
499
|
+
await this.#dataStream.send(DataStreamMessage.textInput(text, Proto.ActionType_Enum.Insert));
|
|
500
|
+
}
|
|
501
|
+
async textClear() {
|
|
502
|
+
await this.#dataStream.send(DataStreamMessage.textInput("", Proto.ActionType_Enum.ClearAction));
|
|
503
|
+
}
|
|
504
|
+
async getKeyboardSession() {
|
|
505
|
+
await this.#dataStream.send(DataStreamMessage.getKeyboardSession());
|
|
506
|
+
}
|
|
482
507
|
async tap(x, y, finger = 1) {
|
|
483
508
|
await this.#sendTouch(x, y, 1, finger);
|
|
484
509
|
await waitFor(50);
|
|
@@ -554,6 +579,15 @@ var state_default = class extends EventEmitter {
|
|
|
554
579
|
get clients() {
|
|
555
580
|
return this.#clients;
|
|
556
581
|
}
|
|
582
|
+
get isKeyboardActive() {
|
|
583
|
+
return this.#keyboardState === Proto.KeyboardState_Enum.DidBeginEditing || this.#keyboardState === Proto.KeyboardState_Enum.Editing || this.#keyboardState === Proto.KeyboardState_Enum.TextDidChange;
|
|
584
|
+
}
|
|
585
|
+
get keyboardAttributes() {
|
|
586
|
+
return this.#keyboardAttributes;
|
|
587
|
+
}
|
|
588
|
+
get keyboardState() {
|
|
589
|
+
return this.#keyboardState;
|
|
590
|
+
}
|
|
557
591
|
get nowPlayingClient() {
|
|
558
592
|
return this.#nowPlayingClientBundleIdentifier ? this.#clients[this.#nowPlayingClientBundleIdentifier] ?? null : null;
|
|
559
593
|
}
|
|
@@ -563,6 +597,15 @@ var state_default = class extends EventEmitter {
|
|
|
563
597
|
get outputDevices() {
|
|
564
598
|
return this.#outputDevices;
|
|
565
599
|
}
|
|
600
|
+
get clusterID() {
|
|
601
|
+
return this.#clusterID;
|
|
602
|
+
}
|
|
603
|
+
get clusterType() {
|
|
604
|
+
return this.#clusterType;
|
|
605
|
+
}
|
|
606
|
+
get isClusterAware() {
|
|
607
|
+
return this.#isClusterAware;
|
|
608
|
+
}
|
|
566
609
|
get volume() {
|
|
567
610
|
return this.#volume;
|
|
568
611
|
}
|
|
@@ -574,10 +617,15 @@ var state_default = class extends EventEmitter {
|
|
|
574
617
|
}
|
|
575
618
|
#device;
|
|
576
619
|
#clients;
|
|
620
|
+
#keyboardAttributes;
|
|
621
|
+
#keyboardState;
|
|
577
622
|
#nowPlayingClientBundleIdentifier;
|
|
578
623
|
#nowPlayingSnapshot;
|
|
579
624
|
#outputDeviceUID;
|
|
580
625
|
#outputDevices = [];
|
|
626
|
+
#clusterID;
|
|
627
|
+
#clusterType;
|
|
628
|
+
#isClusterAware;
|
|
581
629
|
#volume;
|
|
582
630
|
#volumeAvailable;
|
|
583
631
|
#volumeCapabilities;
|
|
@@ -585,6 +633,7 @@ var state_default = class extends EventEmitter {
|
|
|
585
633
|
super();
|
|
586
634
|
this.#device = device;
|
|
587
635
|
this.clear();
|
|
636
|
+
this.onKeyboard = this.onKeyboard.bind(this);
|
|
588
637
|
this.onDeviceInfo = this.onDeviceInfo.bind(this);
|
|
589
638
|
this.onDeviceInfoUpdate = this.onDeviceInfoUpdate.bind(this);
|
|
590
639
|
this.onOriginClientProperties = this.onOriginClientProperties.bind(this);
|
|
@@ -607,6 +656,7 @@ var state_default = class extends EventEmitter {
|
|
|
607
656
|
this.onVolumeDidChange = this.onVolumeDidChange.bind(this);
|
|
608
657
|
}
|
|
609
658
|
[STATE_SUBSCRIBE_SYMBOL]() {
|
|
659
|
+
this.#dataStream.on("keyboard", this.onKeyboard);
|
|
610
660
|
this.#dataStream.on("deviceInfo", this.onDeviceInfo);
|
|
611
661
|
this.#dataStream.on("deviceInfoUpdate", this.onDeviceInfoUpdate);
|
|
612
662
|
this.#dataStream.on("originClientProperties", this.onOriginClientProperties);
|
|
@@ -631,6 +681,7 @@ var state_default = class extends EventEmitter {
|
|
|
631
681
|
[STATE_UNSUBSCRIBE_SYMBOL]() {
|
|
632
682
|
const dataStream = this.#dataStream;
|
|
633
683
|
if (!dataStream) return;
|
|
684
|
+
dataStream.off("keyboard", this.onKeyboard);
|
|
634
685
|
dataStream.off("deviceInfo", this.onDeviceInfo);
|
|
635
686
|
dataStream.off("deviceInfoUpdate", this.onDeviceInfoUpdate);
|
|
636
687
|
dataStream.off("originClientProperties", this.onOriginClientProperties);
|
|
@@ -654,14 +705,24 @@ var state_default = class extends EventEmitter {
|
|
|
654
705
|
}
|
|
655
706
|
clear() {
|
|
656
707
|
this.#clients = {};
|
|
708
|
+
this.#keyboardAttributes = null;
|
|
709
|
+
this.#keyboardState = Proto.KeyboardState_Enum.Unknown;
|
|
657
710
|
this.#nowPlayingClientBundleIdentifier = null;
|
|
658
711
|
this.#nowPlayingSnapshot = null;
|
|
659
712
|
this.#outputDeviceUID = null;
|
|
660
713
|
this.#outputDevices = [];
|
|
714
|
+
this.#clusterID = null;
|
|
715
|
+
this.#clusterType = 0;
|
|
716
|
+
this.#isClusterAware = false;
|
|
661
717
|
this.#volume = 0;
|
|
662
718
|
this.#volumeAvailable = false;
|
|
663
719
|
this.#volumeCapabilities = Proto.VolumeCapabilities_Enum.None;
|
|
664
720
|
}
|
|
721
|
+
onKeyboard(message) {
|
|
722
|
+
this.#keyboardState = message.state;
|
|
723
|
+
this.#keyboardAttributes = message.attributes ?? null;
|
|
724
|
+
this.emit("keyboard", message);
|
|
725
|
+
}
|
|
665
726
|
onDeviceInfo(message) {
|
|
666
727
|
this.#updateOutputDeviceUID(message);
|
|
667
728
|
this.emit("deviceInfo", message);
|
|
@@ -751,7 +812,7 @@ var state_default = class extends EventEmitter {
|
|
|
751
812
|
this.emit("clients", this.#clients);
|
|
752
813
|
}
|
|
753
814
|
onUpdateOutputDevice(message) {
|
|
754
|
-
this.#outputDevices = message.outputDevices;
|
|
815
|
+
this.#outputDevices = message.clusterAwareOutputDevices?.length > 0 ? message.clusterAwareOutputDevices : message.outputDevices;
|
|
755
816
|
this.emit("updateOutputDevice", message);
|
|
756
817
|
}
|
|
757
818
|
onVolumeControlAvailability(message) {
|
|
@@ -770,6 +831,9 @@ var state_default = class extends EventEmitter {
|
|
|
770
831
|
}
|
|
771
832
|
#updateOutputDeviceUID(message) {
|
|
772
833
|
this.#outputDeviceUID = message.clusterID || message.deviceUID || message.uniqueIdentifier || null;
|
|
834
|
+
this.#clusterID = message.clusterID || null;
|
|
835
|
+
this.#clusterType = message.clusterType ?? 0;
|
|
836
|
+
this.#isClusterAware = message.isClusterAware ?? false;
|
|
773
837
|
}
|
|
774
838
|
#client(bundleIdentifier, displayName) {
|
|
775
839
|
if (bundleIdentifier in this.#clients) {
|
|
@@ -844,31 +908,31 @@ var volume_default = class {
|
|
|
844
908
|
case Proto.VolumeCapabilities_Enum.Relative:
|
|
845
909
|
await this.#device.remote.volumeDown();
|
|
846
910
|
break;
|
|
847
|
-
default: throw new
|
|
911
|
+
default: throw new CommandError("Volume control is not available.");
|
|
848
912
|
}
|
|
849
913
|
}
|
|
850
914
|
async up() {
|
|
851
915
|
switch (this.#state.volumeCapabilities) {
|
|
852
916
|
case Proto.VolumeCapabilities_Enum.Absolute:
|
|
853
917
|
case Proto.VolumeCapabilities_Enum.Both:
|
|
854
|
-
const newVolume = Math.max(0, this.#state.volume + VOLUME_STEP);
|
|
918
|
+
const newVolume = Math.min(1, Math.max(0, this.#state.volume + VOLUME_STEP));
|
|
855
919
|
await this.set(newVolume);
|
|
856
920
|
break;
|
|
857
921
|
case Proto.VolumeCapabilities_Enum.Relative:
|
|
858
922
|
await this.#device.remote.volumeUp();
|
|
859
923
|
break;
|
|
860
|
-
default: throw new
|
|
924
|
+
default: throw new CommandError("Volume control is not available.");
|
|
861
925
|
}
|
|
862
926
|
}
|
|
863
927
|
async get() {
|
|
864
|
-
if (!this.#state.outputDeviceUID) throw new
|
|
928
|
+
if (!this.#state.outputDeviceUID) throw new CommandError("No output device active.");
|
|
865
929
|
const response = await this.#protocol.dataStream.exchange(DataStreamMessage.getVolume(this.#state.outputDeviceUID));
|
|
866
930
|
if (response.type === Proto.ProtocolMessage_Type.GET_VOLUME_RESULT_MESSAGE) return DataStreamMessage.getExtension(response, Proto.getVolumeResultMessage).volume;
|
|
867
|
-
throw new
|
|
931
|
+
throw new CommandError("Failed to get volume.");
|
|
868
932
|
}
|
|
869
933
|
async set(volume) {
|
|
870
|
-
if (!this.#state.outputDeviceUID) throw new
|
|
871
|
-
if (![Proto.VolumeCapabilities_Enum.Absolute, Proto.VolumeCapabilities_Enum.Both].includes(this.#state.volumeCapabilities)) throw new
|
|
934
|
+
if (!this.#state.outputDeviceUID) throw new CommandError("No output device active.");
|
|
935
|
+
if (![Proto.VolumeCapabilities_Enum.Absolute, Proto.VolumeCapabilities_Enum.Both].includes(this.#state.volumeCapabilities)) throw new CommandError("Absolute volume control is not available.");
|
|
872
936
|
volume = Math.min(1, Math.max(0, volume));
|
|
873
937
|
this.#protocol.context.logger.info(`Setting volume to ${volume} for device ${this.#state.outputDeviceUID}`);
|
|
874
938
|
await this.#protocol.dataStream.exchange(DataStreamMessage.setVolume(this.#state.outputDeviceUID, volume));
|
|
@@ -905,6 +969,9 @@ var device_default = class extends EventEmitter {
|
|
|
905
969
|
set timingServer(timingServer) {
|
|
906
970
|
this.#timingServer = timingServer;
|
|
907
971
|
}
|
|
972
|
+
#boundOnClose = () => this.#onClose();
|
|
973
|
+
#boundOnError = (err) => this.#onError(err);
|
|
974
|
+
#boundOnTimeout = () => this.#onTimeout();
|
|
908
975
|
#remote;
|
|
909
976
|
#state;
|
|
910
977
|
#volume;
|
|
@@ -913,6 +980,9 @@ var device_default = class extends EventEmitter {
|
|
|
913
980
|
#discoveryResult;
|
|
914
981
|
#feedbackInterval;
|
|
915
982
|
#keys;
|
|
983
|
+
#playUrlProtocol;
|
|
984
|
+
#prevDataStream;
|
|
985
|
+
#prevEventStream;
|
|
916
986
|
#protocol;
|
|
917
987
|
#timingServer;
|
|
918
988
|
constructor(discoveryResult) {
|
|
@@ -923,12 +993,17 @@ var device_default = class extends EventEmitter {
|
|
|
923
993
|
this.#volume = new volume_default(this);
|
|
924
994
|
}
|
|
925
995
|
async connect() {
|
|
996
|
+
if (this.#protocol) {
|
|
997
|
+
this.#protocol.controlStream.off("close", this.#boundOnClose);
|
|
998
|
+
this.#protocol.controlStream.off("error", this.#boundOnError);
|
|
999
|
+
this.#protocol.controlStream.off("timeout", this.#boundOnTimeout);
|
|
1000
|
+
}
|
|
926
1001
|
this.#disconnect = false;
|
|
927
1002
|
this.#state.clear();
|
|
928
1003
|
this.#protocol = new Protocol(this.#discoveryResult);
|
|
929
|
-
this.#protocol.controlStream.on("close", this.#
|
|
930
|
-
this.#protocol.controlStream.on("error", this.#
|
|
931
|
-
this.#protocol.controlStream.on("timeout", this.#
|
|
1004
|
+
this.#protocol.controlStream.on("close", this.#boundOnClose);
|
|
1005
|
+
this.#protocol.controlStream.on("error", this.#boundOnError);
|
|
1006
|
+
this.#protocol.controlStream.on("timeout", this.#boundOnTimeout);
|
|
932
1007
|
await this.#protocol.connect();
|
|
933
1008
|
if (this.#credentials) this.#keys = await this.#protocol.verify.start(this.#credentials);
|
|
934
1009
|
else {
|
|
@@ -944,6 +1019,7 @@ var device_default = class extends EventEmitter {
|
|
|
944
1019
|
clearInterval(this.#feedbackInterval);
|
|
945
1020
|
this.#feedbackInterval = void 0;
|
|
946
1021
|
}
|
|
1022
|
+
this.#cleanupPlayUrl();
|
|
947
1023
|
this.#unsubscribe();
|
|
948
1024
|
this.#protocol.disconnect();
|
|
949
1025
|
}
|
|
@@ -963,6 +1039,40 @@ var device_default = class extends EventEmitter {
|
|
|
963
1039
|
async setOutputDevices(deviceUIDs) {
|
|
964
1040
|
await this.#protocol.dataStream.exchange(DataStreamMessage.modifyOutputContext([], [], deviceUIDs));
|
|
965
1041
|
}
|
|
1042
|
+
async playUrl(url, position = 0) {
|
|
1043
|
+
if (!this.#keys) throw new Error("Not connected. Call connect() first.");
|
|
1044
|
+
this.#playUrlProtocol?.disconnect();
|
|
1045
|
+
const playProtocol = new Protocol(this.#discoveryResult);
|
|
1046
|
+
if (this.#timingServer) playProtocol.useTimingServer(this.#timingServer);
|
|
1047
|
+
await playProtocol.connect();
|
|
1048
|
+
let keys;
|
|
1049
|
+
if (this.#credentials) keys = await playProtocol.verify.start(this.#credentials);
|
|
1050
|
+
else {
|
|
1051
|
+
await playProtocol.pairing.start();
|
|
1052
|
+
keys = await playProtocol.pairing.transient();
|
|
1053
|
+
}
|
|
1054
|
+
playProtocol.controlStream.enableEncryption(keys.accessoryToControllerKey, keys.controllerToAccessoryKey);
|
|
1055
|
+
this.#playUrlProtocol = playProtocol;
|
|
1056
|
+
await playProtocol.playUrl(url, keys.sharedSecret, keys.pairingId, position);
|
|
1057
|
+
}
|
|
1058
|
+
async waitForPlaybackEnd() {
|
|
1059
|
+
if (!this.#playUrlProtocol) return;
|
|
1060
|
+
try {
|
|
1061
|
+
await this.#playUrlProtocol.waitForPlaybackEnd();
|
|
1062
|
+
} finally {
|
|
1063
|
+
this.#cleanupPlayUrl();
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
stopPlayUrl() {
|
|
1067
|
+
this.#cleanupPlayUrl();
|
|
1068
|
+
}
|
|
1069
|
+
#cleanupPlayUrl() {
|
|
1070
|
+
if (this.#playUrlProtocol) {
|
|
1071
|
+
this.#playUrlProtocol.stopPlayUrl();
|
|
1072
|
+
this.#playUrlProtocol.disconnect();
|
|
1073
|
+
this.#playUrlProtocol = void 0;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
966
1076
|
async streamAudio(source) {
|
|
967
1077
|
await this.#protocol.setupAudioStream(source);
|
|
968
1078
|
}
|
|
@@ -1002,17 +1112,23 @@ var device_default = class extends EventEmitter {
|
|
|
1002
1112
|
this.#unsubscribe();
|
|
1003
1113
|
if (this.#timingServer) this.#protocol.useTimingServer(this.#timingServer);
|
|
1004
1114
|
try {
|
|
1115
|
+
this.#prevDataStream?.off("error", this.#boundOnError);
|
|
1116
|
+
this.#prevDataStream?.off("timeout", this.#boundOnTimeout);
|
|
1117
|
+
this.#prevEventStream?.off("error", this.#boundOnError);
|
|
1118
|
+
this.#prevEventStream?.off("timeout", this.#boundOnTimeout);
|
|
1005
1119
|
await this.#protocol.setupEventStream(keys.sharedSecret, keys.pairingId);
|
|
1006
1120
|
await this.#protocol.setupDataStream(keys.sharedSecret, () => this.#subscribe());
|
|
1007
|
-
this.#protocol.dataStream.on("error", this.#
|
|
1008
|
-
this.#protocol.dataStream.on("timeout", this.#
|
|
1009
|
-
this.#protocol.eventStream.on("error", this.#
|
|
1010
|
-
this.#protocol.eventStream.on("timeout", this.#
|
|
1121
|
+
this.#protocol.dataStream.on("error", this.#boundOnError);
|
|
1122
|
+
this.#protocol.dataStream.on("timeout", this.#boundOnTimeout);
|
|
1123
|
+
this.#protocol.eventStream.on("error", this.#boundOnError);
|
|
1124
|
+
this.#protocol.eventStream.on("timeout", this.#boundOnTimeout);
|
|
1125
|
+
this.#prevDataStream = this.#protocol.dataStream;
|
|
1126
|
+
this.#prevEventStream = this.#protocol.eventStream;
|
|
1011
1127
|
if (this.#feedbackInterval) clearInterval(this.#feedbackInterval);
|
|
1012
1128
|
this.#feedbackInterval = setInterval(async () => await this.#feedback(), FEEDBACK_INTERVAL);
|
|
1013
1129
|
await this.#protocol.dataStream.exchange(DataStreamMessage.deviceInfo(keys.pairingId));
|
|
1014
1130
|
await this.#protocol.dataStream.exchange(DataStreamMessage.setConnectionState());
|
|
1015
|
-
await this.#protocol.dataStream.exchange(DataStreamMessage.clientUpdatesConfig());
|
|
1131
|
+
await this.#protocol.dataStream.exchange(DataStreamMessage.clientUpdatesConfig(true, true, true, true, true, true));
|
|
1016
1132
|
this.#protocol.context.logger.info("Protocol ready.");
|
|
1017
1133
|
} catch (err) {
|
|
1018
1134
|
if (this.#feedbackInterval) {
|
|
@@ -1061,6 +1177,23 @@ var device_default$1 = class extends EventEmitter {
|
|
|
1061
1177
|
#heartbeatInterval;
|
|
1062
1178
|
#keys;
|
|
1063
1179
|
#protocol;
|
|
1180
|
+
get textInputState() {
|
|
1181
|
+
return this.#textInputState;
|
|
1182
|
+
}
|
|
1183
|
+
#boundOnClose = async () => this.#onClose();
|
|
1184
|
+
#boundOnError = async (err) => this.#onError(err);
|
|
1185
|
+
#boundOnTimeout = async () => this.#onTimeout();
|
|
1186
|
+
#onMediaControl = (data) => {
|
|
1187
|
+
this.emit("mediaControl", data);
|
|
1188
|
+
};
|
|
1189
|
+
#textInputState = {
|
|
1190
|
+
isActive: false,
|
|
1191
|
+
documentText: "",
|
|
1192
|
+
isSecure: false,
|
|
1193
|
+
keyboardType: 0,
|
|
1194
|
+
autocorrection: false,
|
|
1195
|
+
autocapitalization: false
|
|
1196
|
+
};
|
|
1064
1197
|
constructor(discoveryResult) {
|
|
1065
1198
|
super();
|
|
1066
1199
|
this.#discoveryResult = discoveryResult;
|
|
@@ -1068,12 +1201,17 @@ var device_default$1 = class extends EventEmitter {
|
|
|
1068
1201
|
this.onTVSystemStatus = this.onTVSystemStatus.bind(this);
|
|
1069
1202
|
}
|
|
1070
1203
|
async connect() {
|
|
1071
|
-
if (!this.#credentials) throw new
|
|
1204
|
+
if (!this.#credentials) throw new CredentialsError("Credentials are required to connect to a Companion Link device.");
|
|
1205
|
+
if (this.#protocol) {
|
|
1206
|
+
this.#protocol.stream.off("close", this.#boundOnClose);
|
|
1207
|
+
this.#protocol.stream.off("error", this.#boundOnError);
|
|
1208
|
+
this.#protocol.stream.off("timeout", this.#boundOnTimeout);
|
|
1209
|
+
}
|
|
1072
1210
|
this.#disconnect = false;
|
|
1073
1211
|
this.#protocol = new Protocol$1(this.#discoveryResult);
|
|
1074
|
-
this.#protocol.stream.on("close",
|
|
1075
|
-
this.#protocol.stream.on("error",
|
|
1076
|
-
this.#protocol.stream.on("timeout",
|
|
1212
|
+
this.#protocol.stream.on("close", this.#boundOnClose);
|
|
1213
|
+
this.#protocol.stream.on("error", this.#boundOnError);
|
|
1214
|
+
this.#protocol.stream.on("timeout", this.#boundOnTimeout);
|
|
1077
1215
|
await this.#protocol.connect();
|
|
1078
1216
|
this.#keys = await this.#protocol.verify.start(this.#credentials);
|
|
1079
1217
|
await this.#setup();
|
|
@@ -1120,6 +1258,15 @@ var device_default$1 = class extends EventEmitter {
|
|
|
1120
1258
|
async switchUserAccount(accountId) {
|
|
1121
1259
|
await this.#protocol.switchUserAccount(accountId);
|
|
1122
1260
|
}
|
|
1261
|
+
async textSet(text) {
|
|
1262
|
+
await this.#protocol.textInputCommand(text, true);
|
|
1263
|
+
}
|
|
1264
|
+
async textAppend(text) {
|
|
1265
|
+
await this.#protocol.textInputCommand(text, false);
|
|
1266
|
+
}
|
|
1267
|
+
async textClear() {
|
|
1268
|
+
await this.#protocol.textInputCommand("", true);
|
|
1269
|
+
}
|
|
1123
1270
|
async #heartbeat() {
|
|
1124
1271
|
try {
|
|
1125
1272
|
this.#protocol.noOp();
|
|
@@ -1150,10 +1297,11 @@ var device_default$1 = class extends EventEmitter {
|
|
|
1150
1297
|
await this.#protocol.tvrcSessionStart();
|
|
1151
1298
|
await this.#protocol.touchStart();
|
|
1152
1299
|
await this.#protocol.tiStart();
|
|
1153
|
-
|
|
1154
|
-
this.
|
|
1155
|
-
|
|
1156
|
-
|
|
1300
|
+
this.#heartbeatInterval = setInterval(() => {
|
|
1301
|
+
this.#heartbeat().catch((err) => {
|
|
1302
|
+
this.#protocol.context.logger.error("Heartbeat failed", err);
|
|
1303
|
+
});
|
|
1304
|
+
}, 15e3);
|
|
1157
1305
|
await this.#subscribe();
|
|
1158
1306
|
} catch (err) {
|
|
1159
1307
|
clearInterval(this.#heartbeatInterval);
|
|
@@ -1162,17 +1310,73 @@ var device_default$1 = class extends EventEmitter {
|
|
|
1162
1310
|
}
|
|
1163
1311
|
}
|
|
1164
1312
|
async #subscribe() {
|
|
1165
|
-
|
|
1166
|
-
|
|
1313
|
+
this.#protocol.stream.on("_iMC", this.#onMediaControl);
|
|
1314
|
+
this.#protocol.stream.on("SystemStatus", this.onSystemStatus);
|
|
1315
|
+
this.#protocol.stream.on("TVSystemStatus", this.onTVSystemStatus);
|
|
1316
|
+
this.#protocol.stream.on("_tiStarted", this.#onTextInputStarted);
|
|
1317
|
+
this.#protocol.stream.on("_tiStopped", this.#onTextInputStopped);
|
|
1318
|
+
this.#protocol.registerInterests([
|
|
1319
|
+
"_iMC",
|
|
1320
|
+
"SystemStatus",
|
|
1321
|
+
"TVSystemStatus"
|
|
1322
|
+
]);
|
|
1167
1323
|
const state = await this.getAttentionState();
|
|
1168
1324
|
this.emit("power", state);
|
|
1169
1325
|
}
|
|
1170
1326
|
async #unsubscribe() {
|
|
1327
|
+
this.#protocol.stream.off("_iMC", this.#onMediaControl);
|
|
1328
|
+
this.#protocol.stream.off("SystemStatus", this.onSystemStatus);
|
|
1329
|
+
this.#protocol.stream.off("TVSystemStatus", this.onTVSystemStatus);
|
|
1330
|
+
this.#protocol.stream.off("_tiStarted", this.#onTextInputStarted);
|
|
1331
|
+
this.#protocol.stream.off("_tiStopped", this.#onTextInputStopped);
|
|
1171
1332
|
try {
|
|
1172
|
-
|
|
1173
|
-
|
|
1333
|
+
this.#protocol.deregisterInterests([
|
|
1334
|
+
"_iMC",
|
|
1335
|
+
"SystemStatus",
|
|
1336
|
+
"TVSystemStatus"
|
|
1337
|
+
]);
|
|
1174
1338
|
} catch (_) {}
|
|
1175
1339
|
}
|
|
1340
|
+
#onTextInputStarted = async (data) => {
|
|
1341
|
+
try {
|
|
1342
|
+
const payload = data;
|
|
1343
|
+
let documentText = "";
|
|
1344
|
+
let isSecure = false;
|
|
1345
|
+
let keyboardType = 0;
|
|
1346
|
+
let autocorrection = false;
|
|
1347
|
+
let autocapitalization = false;
|
|
1348
|
+
if (payload?._tiD) {
|
|
1349
|
+
const plistData = Plist.parse(Buffer.from(payload._tiD).buffer);
|
|
1350
|
+
documentText = plistData._tiDT ?? "";
|
|
1351
|
+
isSecure = plistData._tiSR ?? false;
|
|
1352
|
+
keyboardType = plistData._tiKT ?? 0;
|
|
1353
|
+
autocorrection = plistData._tiAC ?? false;
|
|
1354
|
+
autocapitalization = plistData._tiAP ?? false;
|
|
1355
|
+
}
|
|
1356
|
+
this.#textInputState = {
|
|
1357
|
+
isActive: true,
|
|
1358
|
+
documentText,
|
|
1359
|
+
isSecure,
|
|
1360
|
+
keyboardType,
|
|
1361
|
+
autocorrection,
|
|
1362
|
+
autocapitalization
|
|
1363
|
+
};
|
|
1364
|
+
this.emit("textInput", this.#textInputState);
|
|
1365
|
+
} catch (err) {
|
|
1366
|
+
this.#protocol.context.logger.error("Text input started parse error", err);
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
#onTextInputStopped = async (_data) => {
|
|
1370
|
+
this.#textInputState = {
|
|
1371
|
+
isActive: false,
|
|
1372
|
+
documentText: "",
|
|
1373
|
+
isSecure: false,
|
|
1374
|
+
keyboardType: 0,
|
|
1375
|
+
autocorrection: false,
|
|
1376
|
+
autocapitalization: false
|
|
1377
|
+
};
|
|
1378
|
+
this.emit("textInput", this.#textInputState);
|
|
1379
|
+
};
|
|
1176
1380
|
async onSystemStatus(data) {
|
|
1177
1381
|
this.#protocol.context.logger.info("System Status", data);
|
|
1178
1382
|
this.emit("power", convertAttentionState(data.state));
|
|
@@ -1255,12 +1459,18 @@ var apple_tv_default = class extends EventEmitter {
|
|
|
1255
1459
|
this.#companionLink.on("connected", () => this.#onConnected());
|
|
1256
1460
|
this.#companionLink.on("disconnected", (unexpected) => this.#onDisconnected(unexpected));
|
|
1257
1461
|
this.#companionLink.on("power", (state) => this.emit("power", state));
|
|
1462
|
+
this.#companionLink.on("textInput", (state) => this.emit("textInput", state));
|
|
1258
1463
|
}
|
|
1259
1464
|
async connect(airplayCredentials, companionLinkCredentials) {
|
|
1260
1465
|
this.#airplay.setCredentials(airplayCredentials);
|
|
1261
1466
|
await this.#companionLink.setCredentials(companionLinkCredentials ?? airplayCredentials);
|
|
1262
1467
|
await this.#airplay.connect();
|
|
1263
|
-
|
|
1468
|
+
try {
|
|
1469
|
+
await this.#companionLink.connect();
|
|
1470
|
+
} catch (err) {
|
|
1471
|
+
this.#airplay.disconnectSafely();
|
|
1472
|
+
throw err;
|
|
1473
|
+
}
|
|
1264
1474
|
this.#disconnect = false;
|
|
1265
1475
|
}
|
|
1266
1476
|
async disconnect() {
|
|
@@ -1315,6 +1525,15 @@ var apple_tv_default = class extends EventEmitter {
|
|
|
1315
1525
|
async switchUserAccount(accountId) {
|
|
1316
1526
|
await this.#companionLink.switchUserAccount(accountId);
|
|
1317
1527
|
}
|
|
1528
|
+
async textSet(text) {
|
|
1529
|
+
await this.#companionLink.textSet(text);
|
|
1530
|
+
}
|
|
1531
|
+
async textAppend(text) {
|
|
1532
|
+
await this.#companionLink.textAppend(text);
|
|
1533
|
+
}
|
|
1534
|
+
async textClear() {
|
|
1535
|
+
await this.#companionLink.textClear();
|
|
1536
|
+
}
|
|
1318
1537
|
async getCommandInfo(command) {
|
|
1319
1538
|
const client = this.#airplay.state.nowPlayingClient;
|
|
1320
1539
|
if (!client) return null;
|
|
@@ -1427,6 +1646,15 @@ var homepod_base_default = class extends EventEmitter {
|
|
|
1427
1646
|
async previous() {
|
|
1428
1647
|
await this.#airplay.sendCommand(AirPlay.Proto.Command.PreviousInContext);
|
|
1429
1648
|
}
|
|
1649
|
+
async playUrl(url, position = 0) {
|
|
1650
|
+
await this.#airplay.playUrl(url, position);
|
|
1651
|
+
}
|
|
1652
|
+
async waitForPlaybackEnd() {
|
|
1653
|
+
await this.#airplay.waitForPlaybackEnd();
|
|
1654
|
+
}
|
|
1655
|
+
stopPlayUrl() {
|
|
1656
|
+
this.#airplay.stopPlayUrl();
|
|
1657
|
+
}
|
|
1430
1658
|
async streamAudio(source) {
|
|
1431
1659
|
await this.#airplay.streamAudio(source);
|
|
1432
1660
|
}
|
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.
|
|
4
|
+
"version": "0.9.19",
|
|
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.
|
|
53
|
-
"@basmilius/apple-common": "0.9.
|
|
54
|
-
"@basmilius/apple-companion-link": "0.9.
|
|
55
|
-
"@basmilius/apple-encoding": "0.9.
|
|
52
|
+
"@basmilius/apple-airplay": "0.9.19",
|
|
53
|
+
"@basmilius/apple-common": "0.9.19",
|
|
54
|
+
"@basmilius/apple-companion-link": "0.9.19",
|
|
55
|
+
"@basmilius/apple-encoding": "0.9.19"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/bun": "^1.3.11",
|