@basmilius/apple-airplay 0.1.3 → 0.2.0

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.js CHANGED
@@ -9,30 +9,12 @@ var __export = (target, all) => {
9
9
  });
10
10
  };
11
11
 
12
- // src/dataStreamMessages.ts
13
- var exports_dataStreamMessages = {};
14
- __export(exports_dataStreamMessages, {
15
- wakeDevice: () => wakeDevice,
16
- setVolumeMuted: () => setVolumeMuted,
17
- setVolume: () => setVolume,
18
- setReadyState: () => setReadyState,
19
- setConnectionState: () => setConnectionState,
20
- sendHIDEvent: () => sendHIDEvent,
21
- sendCommand: () => sendCommand,
22
- sendButtonEvent: () => sendButtonEvent,
23
- protocol: () => protocol,
24
- playbackQueueRequest: () => playbackQueueRequest,
25
- notification: () => notification,
26
- getVolumeMuted: () => getVolumeMuted,
27
- getVolume: () => getVolume,
28
- getState: () => getState,
29
- getExtension: () => getExtension,
30
- deviceInfo: () => deviceInfo,
31
- configureConnection: () => configureConnection,
32
- clientUpdatesConfig: () => clientUpdatesConfig
33
- });
34
- import { uint16ToBE, uuid } from "@basmilius/apple-common";
35
- import { create, getExtension as _getExtension, setExtension } from "@bufbuild/protobuf";
12
+ // src/baseStream.ts
13
+ import { Chacha20, ENCRYPTION, EncryptionAwareConnection } from "@basmilius/apple-common";
14
+
15
+ // src/utils.ts
16
+ import { Plist } from "@basmilius/apple-encoding";
17
+ import { fromBinary } from "@bufbuild/protobuf";
36
18
 
37
19
  // src/proto/index.ts
38
20
  var exports_proto = {};
@@ -1498,381 +1480,304 @@ import { extDesc as extDesc63, fileDesc as fileDesc90, messageDesc as messageDes
1498
1480
  var file_WakeDeviceMessage = /* @__PURE__ */ fileDesc90("ChdXYWtlRGV2aWNlTWVzc2FnZS5wcm90byITChFXYWtlRGV2aWNlTWVzc2FnZTpSChF3YWtlRGV2aWNlTWVzc2FnZRIQLlByb3RvY29sTWVzc2FnZRgtIAEoCzISLldha2VEZXZpY2VNZXNzYWdlUhF3YWtlRGV2aWNlTWVzc2FnZQ", [file_ProtocolMessage]);
1499
1481
  var WakeDeviceMessageSchema = /* @__PURE__ */ messageDesc90(file_WakeDeviceMessage, 0);
1500
1482
  var wakeDeviceMessage = /* @__PURE__ */ extDesc63(file_WakeDeviceMessage, 0);
1501
- // src/dataStreamMessages.ts
1502
- function protocol(type, errorCode = 0 /* NoError */) {
1503
- return create(ProtocolMessageSchema, {
1504
- type,
1505
- errorCode,
1506
- identifier: uuid().toUpperCase(),
1507
- uniqueIdentifier: uuid().toUpperCase()
1508
- });
1509
- }
1510
- function clientUpdatesConfig(artworkUpdates = true, nowPlayingUpdates = true, volumeUpdates = true, keyboardUpdates = false, outputDeviceUpdates = true, systemEndpointUpdates = true) {
1511
- const protocolMessage = protocol(16 /* CLIENT_UPDATES_CONFIG_MESSAGE */);
1512
- const message = create(ClientUpdatesConfigMessageSchema, {
1513
- artworkUpdates,
1514
- nowPlayingUpdates,
1515
- volumeUpdates,
1516
- keyboardUpdates,
1517
- outputDeviceUpdates,
1518
- systemEndpointUpdates
1519
- });
1520
- setExtension(protocolMessage, clientUpdatesConfigMessage, message);
1521
- return protocolMessage;
1522
- }
1523
- function configureConnection(groupId) {
1524
- const protocolMessage = protocol(120 /* CONFIGURE_CONNECTION_MESSAGE */);
1525
- const message = create(ConfigureConnectionMessageSchema, {
1526
- groupID: groupId
1527
- });
1528
- setExtension(protocolMessage, configureConnectionMessage, message);
1529
- return protocolMessage;
1530
- }
1531
- function deviceInfo(pairingId) {
1532
- const protocolMessage = protocol(15 /* DEVICE_INFO_MESSAGE */);
1533
- const message = create(DeviceInfoMessageSchema, {
1534
- uniqueIdentifier: pairingId.toString(),
1535
- name: "iPhone van Bas",
1536
- localizedModelName: "iPhone",
1537
- systemBuildVersion: "18C66",
1538
- applicationBundleIdentifier: "com.apple.TVRemote",
1539
- applicationBundleVersion: "344.28",
1540
- protocolVersion: 1,
1541
- lastSupportedMessageType: 129,
1542
- supportsSystemPairing: true,
1543
- allowsPairing: true,
1544
- systemMediaApplication: "com.apple.TVMusic",
1545
- supportsACL: true,
1546
- supportsSharedQueue: true,
1547
- supportsExtendedMotion: true,
1548
- sharedQueueVersion: 2,
1549
- deviceClass: 1 /* iPhone */,
1550
- logicalDeviceCount: 1
1551
- });
1552
- setExtension(protocolMessage, deviceInfoMessage, message);
1553
- return protocolMessage;
1554
- }
1555
- function getState() {
1556
- const protocolMessage = protocol(3 /* GET_STATE_MESSAGE */);
1557
- const message = create(GetStateMessageSchema, {});
1558
- setExtension(protocolMessage, getStateMessage, message);
1559
- return protocolMessage;
1560
- }
1561
- function getVolume(outputDeviceUID) {
1562
- const protocolMessage = protocol(49 /* GET_VOLUME_MESSAGE */);
1563
- const message = create(GetVolumeMessageSchema, {
1564
- outputDeviceUID
1565
- });
1566
- setExtension(protocolMessage, getVolumeMessage, message);
1567
- return protocolMessage;
1568
- }
1569
- function getVolumeMuted(outputDeviceUID) {
1570
- const protocolMessage = protocol(126 /* GET_VOLUME_MUTED_MESSAGE */);
1571
- const message = create(GetVolumeMutedMessageSchema, {
1572
- outputDeviceUID
1573
- });
1574
- setExtension(protocolMessage, getVolumeMutedMessage, message);
1575
- return protocolMessage;
1576
- }
1577
- function notification(notification2) {
1578
- const protocolMessage = protocol(11 /* NOTIFICATION_MESSAGE */);
1579
- const message = create(NotificationMessageSchema, {
1580
- notification: notification2
1581
- });
1582
- setExtension(protocolMessage, notificationMessage, message);
1583
- return protocolMessage;
1584
- }
1585
- function playbackQueueRequest(location, length, includeMetadata = true, includeLanguageOptions = false) {
1586
- const protocolMessage = protocol(32 /* PLAYBACK_QUEUE_REQUEST_MESSAGE */);
1587
- const message = create(PlaybackQueueRequestMessageSchema, {
1588
- location,
1589
- length,
1590
- includeMetadata,
1591
- includeLanguageOptions,
1592
- artworkHeight: 600,
1593
- artworkWidth: 600,
1594
- includeInfo: true,
1595
- includeLyrics: false,
1596
- includeSections: false,
1597
- includeAlignments: false,
1598
- includeAvailableArtworkFormats: true,
1599
- includeParticipants: false,
1600
- isLegacyNowPlayingInfoRequest: false,
1601
- returnContentItemAssetsInUserCompletion: true,
1602
- requestedAnimatedArtworkAssetURLFormats: [
1603
- "MRContentItemAnimatedArtworkFormatSquare",
1604
- "MRContentItemAnimatedArtworkFormatTall"
1605
- ]
1606
- });
1607
- setExtension(protocolMessage, playbackQueueRequestMessage, message);
1608
- return protocolMessage;
1609
- }
1610
- function sendButtonEvent(usagePage, usage, buttonDown) {
1611
- const protocolMessage = protocol(39 /* SEND_BUTTON_EVENT_MESSAGE */);
1612
- const message = create(SendButtonEventMessageSchema, {
1613
- usagePage,
1614
- usage,
1615
- buttonDown
1616
- });
1617
- setExtension(protocolMessage, sendButtonEventMessage, message);
1618
- return protocolMessage;
1619
- }
1620
- function sendCommand(command, options) {
1621
- const protocolMessage = protocol(1 /* SEND_COMMAND_MESSAGE */);
1622
- const message = create(SendCommandMessageSchema, {
1623
- command,
1624
- options
1625
- });
1626
- setExtension(protocolMessage, sendCommandMessage, message);
1627
- return protocolMessage;
1628
- }
1629
- function sendHIDEvent(usePage, usage, down) {
1630
- const protocolMessage = protocol(8 /* SEND_HID_EVENT_MESSAGE */);
1631
- const message = create(SendHIDEventMessageSchema, {
1632
- hidEventData: Buffer.concat([
1633
- Buffer.from("438922cf08020000", "hex"),
1634
- Buffer.from("00000000000000000100000000000000020" + "00000200000000300000001000000000000", "hex"),
1635
- Buffer.concat([
1636
- uint16ToBE(usePage),
1637
- uint16ToBE(usage),
1638
- uint16ToBE(down ? 1 : 0)
1639
- ]),
1640
- Buffer.from("0000000000000001000000", "hex")
1641
- ])
1642
- });
1643
- setExtension(protocolMessage, sendHIDEventMessage, message);
1644
- return protocolMessage;
1645
- }
1646
- function setConnectionState(state = 2 /* Connected */) {
1647
- const protocolMessage = protocol(38 /* SET_CONNECTION_STATE_MESSAGE */);
1648
- const message = create(SetConnectionStateMessageSchema, {
1649
- state
1650
- });
1651
- setExtension(protocolMessage, setConnectionStateMessage, message);
1652
- return protocolMessage;
1653
- }
1654
- function setReadyState() {
1655
- const protocolMessage = protocol(36 /* SET_READY_STATE_MESSAGE */);
1656
- const message = create(SetReadyStateMessageSchema, {});
1657
- setExtension(protocolMessage, readyStateMessage, message);
1658
- return protocolMessage;
1659
- }
1660
- function setVolume(outputDeviceUID, volume) {
1661
- const protocolMessage = protocol(51 /* SET_VOLUME_MESSAGE */);
1662
- const message = create(SetVolumeMessageSchema, {
1663
- outputDeviceUID,
1664
- volume
1665
- });
1666
- setExtension(protocolMessage, setVolumeMessage, message);
1667
- return protocolMessage;
1668
- }
1669
- function setVolumeMuted(outputDeviceUID, isMuted) {
1670
- const protocolMessage = protocol(128 /* SET_VOLUME_MUTED_MESSAGE */);
1671
- const message = create(SetVolumeMutedMessageSchema, {
1672
- outputDeviceUID,
1673
- isMuted
1674
- });
1675
- setExtension(protocolMessage, setVolumeMutedMessage, message);
1676
- return protocolMessage;
1677
- }
1678
- function wakeDevice() {
1679
- const protocolMessage = protocol(41 /* WAKE_DEVICE_MESSAGE */);
1680
- const message = create(WakeDeviceMessageSchema, {});
1681
- setExtension(protocolMessage, wakeDeviceMessage, message);
1682
- return protocolMessage;
1683
- }
1684
- function getExtension(message, extension) {
1685
- return _getExtension(message, extension);
1686
- }
1687
- // src/protocol.ts
1688
- import { getMacAddress, reporter as reporter4, uuid as uuid2 } from "@basmilius/apple-common";
1689
- import { Plist as Plist3 } from "@basmilius/apple-encoding";
1690
-
1691
1483
  // src/utils.ts
1692
- import { randomBytes } from "node:crypto";
1693
- function makeHttpHeader(method, path, headers, cseq) {
1694
- const lines = [];
1695
- lines.push(`${method} ${path} RTSP/1.0`);
1696
- lines.push(`CSeq: ${cseq}`);
1697
- lines.push("User-Agent: AirPlay/320.20");
1698
- lines.push("X-Apple-ProtocolVersion: 1");
1699
- lines.push("X-ProtocolVersion: 1");
1700
- for (const [name, value] of Object.entries(headers)) {
1701
- lines.push(`${name}: ${value}`);
1702
- }
1703
- lines.push("");
1704
- lines.push("");
1705
- return lines.join(`\r
1706
- `);
1707
- }
1708
- function makeHttpRequest(buffer) {
1709
- const headerLength = buffer.indexOf(`\r
1710
- \r
1711
- `);
1712
- const { headers, method, path } = parseRequestHeaders(buffer.subarray(0, headerLength));
1713
- let contentLength = headers["Content-Length"] ? Number(headers["Content-Length"]) : 0;
1714
- if (isNaN(contentLength)) {
1715
- contentLength = 0;
1716
- }
1717
- const requestLength = headerLength + 4 + contentLength;
1718
- if (buffer.byteLength < requestLength) {
1719
- return null;
1720
- }
1721
- const body = buffer.subarray(headerLength + 4, requestLength);
1722
- return {
1723
- headers,
1724
- method,
1725
- path,
1726
- body,
1727
- requestLength
1728
- };
1729
- }
1730
- function makeHttpResponse(buffer) {
1731
- const headerLength = buffer.indexOf(`\r
1732
- \r
1733
- `);
1734
- const { headers, status, statusText } = parseResponseHeaders(buffer.subarray(0, headerLength));
1735
- let contentLength = headers["Content-Length"] ? Number(headers["Content-Length"]) : 0;
1736
- if (isNaN(contentLength)) {
1737
- contentLength = 0;
1738
- }
1739
- const responseLength = headerLength + 4 + contentLength;
1740
- if (buffer.byteLength < responseLength) {
1741
- return null;
1742
- }
1743
- const body = buffer.subarray(headerLength + 4, responseLength);
1744
- const response = new Response(body, {
1745
- status,
1746
- statusText,
1747
- headers
1748
- });
1749
- return {
1750
- response,
1751
- responseLength
1752
- };
1484
+ function generateActiveRemoteId() {
1485
+ return Math.floor(Math.random() * 2 ** 32).toString(10);
1753
1486
  }
1754
- function randomInt32() {
1755
- return randomBytes(4).readUInt32BE(0);
1487
+ function generateDacpId() {
1488
+ return Math.floor(Math.random() * 2 ** 64).toString(16).toUpperCase();
1756
1489
  }
1757
- function randomInt64() {
1758
- return randomBytes(8).readBigUint64LE(0);
1490
+ function generateSessionId() {
1491
+ return Math.floor(Math.random() * 2 ** 32).toString(10);
1759
1492
  }
1760
- function parseHeaders(lines) {
1761
- const headers = {};
1762
- for (let i = 0;i < lines.length; i++) {
1763
- const colon = lines[i].indexOf(":");
1764
- if (colon <= 0) {
1765
- continue;
1766
- }
1767
- const name = lines[i].substring(0, colon).trim();
1768
- headers[name] = lines[i].substring(colon + 1).trim();
1769
- }
1770
- return headers;
1493
+ function nonce(counter) {
1494
+ const nonceArray = new Uint8Array(12);
1495
+ const view = new DataView(nonceArray.buffer);
1496
+ view.setBigUint64(4, BigInt(counter), true);
1497
+ return Buffer.from(nonceArray);
1771
1498
  }
1772
- function parseRequestHeaders(buffer) {
1773
- const lines = buffer.toString("utf8").split(`\r
1774
- `);
1775
- const rawRequest = lines[0].match(/^(\S+)\s+(\S+)\s+RTSP\/1\.0$/);
1776
- const method = rawRequest[1];
1777
- const path = rawRequest[2];
1778
- const headers = parseHeaders(lines.slice(1));
1779
- return {
1780
- headers,
1781
- method,
1782
- path
1783
- };
1499
+ function buildHeader(totalSize, seqno) {
1500
+ const buf = Buffer.alloc(32);
1501
+ buf.writeUInt32BE(totalSize, 0);
1502
+ buf.write("sync", 4, "ascii");
1503
+ buf.fill(0, 8, 16);
1504
+ buf.write("comm", 16, "ascii");
1505
+ buf.writeBigUInt64BE(seqno, 20);
1506
+ buf.writeUInt32BE(0, 28);
1507
+ return buf;
1784
1508
  }
1785
- function parseResponseHeaders(buffer) {
1786
- const lines = buffer.toString("utf8").split(`\r
1787
- `);
1788
- const rawStatus = lines[0].match(/(HTTP|RTSP)\/[\d.]+\s+(\d+)\s+(.+)/);
1789
- const status = Number(rawStatus[2]);
1790
- const statusText = rawStatus[3];
1791
- const headers = parseHeaders(lines.slice(1));
1792
- return {
1793
- headers,
1794
- status,
1795
- statusText
1796
- };
1509
+ function buildReply(seqno) {
1510
+ const header = Buffer.alloc(32);
1511
+ header.writeUInt32BE(0, 0);
1512
+ header.write("rply", 4, "ascii");
1513
+ header.fill(0, 8, 16);
1514
+ header.writeBigUInt64BE(seqno, 20);
1515
+ header.writeUInt32BE(0, 28);
1516
+ const plist = Buffer.from(Plist.serialize(Buffer.alloc(0)));
1517
+ const total = header.length + plist.length;
1518
+ header.writeUInt32BE(total, 0);
1519
+ return Buffer.concat([header, plist]);
1797
1520
  }
1798
-
1799
- // src/dataStream.ts
1800
- import { Chacha20 as Chacha202, ENCRYPTION as ENCRYPTION2, hkdf, reporter } from "@basmilius/apple-common";
1801
- import { Plist } from "@basmilius/apple-encoding";
1802
- import { fromBinary, getExtension as getExtension2, toBinary } from "@bufbuild/protobuf";
1803
-
1804
- // src/stream.ts
1805
- import { Chacha20, ENCRYPTION, EncryptionAwareConnection } from "@basmilius/apple-common";
1806
-
1807
- class AirPlayStream extends EncryptionAwareConnection {
1808
- get #encryption() {
1809
- return this[ENCRYPTION];
1810
- }
1811
- #buffer = Buffer.alloc(0);
1812
- async decrypt(data) {
1813
- if (this.#buffer) {
1814
- data = Buffer.concat([this.#buffer, data]);
1815
- this.#buffer = undefined;
1816
- }
1817
- let result = Buffer.alloc(0);
1818
- let offset = 0;
1819
- while (offset + 2 <= data.length) {
1820
- const length = data.readUInt16LE(offset);
1821
- const totalChunkLength = 2 + length + 16;
1822
- if (offset + totalChunkLength > data.length) {
1823
- this.#buffer = data.subarray(offset);
1824
- break;
1825
- }
1826
- const aad = data.subarray(offset, offset + 2);
1827
- const ciphertext = data.subarray(offset + 2, offset + 2 + length);
1828
- const authTag = data.subarray(offset + 2 + length, offset + 2 + length + 16);
1829
- const nonce = this.nonce(this.#encryption.readCount++);
1830
- const plaintext = Chacha20.decrypt(this.#encryption.readKey, nonce, aad, ciphertext, authTag);
1831
- result = Buffer.concat([result, plaintext]);
1832
- offset += totalChunkLength;
1833
- }
1834
- return result;
1521
+ function encodeVarint(value) {
1522
+ if (value < 0) {
1523
+ throw new RangeError("Varint only supports non-negative integers");
1835
1524
  }
1836
- async encrypt(data) {
1837
- const total = data.length;
1838
- let result = Buffer.alloc(0);
1839
- for (let offset = 0;offset < total; ) {
1840
- const length = Math.min(total - offset, 1024);
1841
- const leLength = Buffer.alloc(2);
1842
- leLength.writeUInt16LE(length, 0);
1843
- const nonce = this.nonce(this.#encryption.writeCount++);
1844
- const encrypted = Chacha20.encrypt(this.#encryption.writeKey, nonce, leLength, data.subarray(offset, offset + length));
1845
- offset += length;
1846
- result = Buffer.concat([result, leLength, encrypted.ciphertext, encrypted.authTag]);
1847
- }
1848
- return result;
1525
+ const bytes = [];
1526
+ while (value > 127) {
1527
+ bytes.push(value & 127 | 128);
1528
+ value >>>= 7;
1849
1529
  }
1850
- nonce(counter) {
1851
- const nonceArray = new Uint8Array(12);
1852
- const view = new DataView(nonceArray.buffer);
1853
- view.setBigUint64(4, BigInt(counter), true);
1854
- return Buffer.from(nonceArray);
1530
+ bytes.push(value);
1531
+ return Uint8Array.from(bytes);
1532
+ }
1533
+ function parseHeaderSeqno(header) {
1534
+ if (header.length < 28) {
1535
+ throw new Error("Header too short");
1536
+ }
1537
+ return header.readBigUInt64BE(20);
1538
+ }
1539
+ function parseMessages(content) {
1540
+ const messages = [];
1541
+ let offset = 0;
1542
+ while (offset < content.length) {
1543
+ const firstByte = content[offset];
1544
+ if (firstByte === 8) {
1545
+ const message2 = content.subarray(offset);
1546
+ const decoded2 = fromBinary(ProtocolMessageSchema, message2, { readUnknownFields: true });
1547
+ messages.push(decoded2);
1548
+ break;
1549
+ }
1550
+ const [length, variantLen] = readVariant(content, offset);
1551
+ offset += variantLen;
1552
+ if (offset + length > content.length) {
1553
+ break;
1554
+ }
1555
+ const message = content.subarray(offset, offset + length);
1556
+ offset += length;
1557
+ const decoded = fromBinary(ProtocolMessageSchema, message, { readUnknownFields: true });
1558
+ messages.push(decoded);
1559
+ }
1560
+ return messages;
1561
+ }
1562
+ function readVariant(buf, offset = 0) {
1563
+ let result = 0;
1564
+ let shift = 0;
1565
+ let bytesRead = 0;
1566
+ while (true) {
1567
+ const byte = buf[offset + bytesRead++];
1568
+ result |= (byte & 127) << shift;
1569
+ if ((byte & 128) === 0) {
1570
+ break;
1571
+ }
1572
+ shift += 7;
1573
+ }
1574
+ return [result, bytesRead];
1575
+ }
1576
+
1577
+ // src/baseStream.ts
1578
+ class BaseStream extends EncryptionAwareConnection {
1579
+ get #encryptionState() {
1580
+ return this[ENCRYPTION];
1581
+ }
1582
+ decrypt(data) {
1583
+ const result = [];
1584
+ let offset = 0;
1585
+ let readCount = this.#encryptionState.readCount ?? 0;
1586
+ while (offset < data.length) {
1587
+ if (offset + 2 > data.length) {
1588
+ this.context.logger.warn("Expected frame length to be within buffer bounds.");
1589
+ return false;
1590
+ }
1591
+ const frameLength = data.readUInt16LE(offset);
1592
+ offset += 2;
1593
+ const end = offset + frameLength + 16;
1594
+ if (end > data.length) {
1595
+ this.context.logger.warn(`Truncated frame end=${end} length=${data.length}`);
1596
+ return false;
1597
+ }
1598
+ const ciphertext = data.subarray(offset, offset + frameLength);
1599
+ const authTag = data.subarray(offset + frameLength, end);
1600
+ offset = end;
1601
+ const plaintext = Chacha20.decrypt(this.#encryptionState.readKey, nonce(readCount++), Buffer.from(Uint16Array.of(frameLength).buffer.slice(0, 2)), ciphertext, authTag);
1602
+ result.push(plaintext);
1603
+ }
1604
+ this.#encryptionState.readCount = readCount;
1605
+ return Buffer.concat(result);
1606
+ }
1607
+ encrypt(data) {
1608
+ const FRAME_LENGTH = 1024;
1609
+ const result = [];
1610
+ for (let offset = 0;offset < data.length; ) {
1611
+ const frame = data.subarray(offset, offset + FRAME_LENGTH);
1612
+ offset += frame.length;
1613
+ const leLength = Buffer.alloc(2);
1614
+ leLength.writeUInt16LE(frame.length, 0);
1615
+ const encrypted = Chacha20.encrypt(this.#encryptionState.writeKey, nonce(this.#encryptionState.writeCount++), leLength, frame);
1616
+ result.push(leLength, encrypted.ciphertext, encrypted.authTag);
1617
+ }
1618
+ return Buffer.concat(result);
1619
+ }
1620
+ }
1621
+ // src/controlStream.ts
1622
+ import { HTTP_TIMEOUT } from "@basmilius/apple-common";
1623
+ import { RTSP } from "@basmilius/apple-encoding";
1624
+ class ControlStream extends BaseStream {
1625
+ get activeRemoteId() {
1626
+ return this.#activeRemoteId;
1627
+ }
1628
+ get dacpId() {
1629
+ return this.#dacpId;
1630
+ }
1631
+ get sessionId() {
1632
+ return this.#sessionId;
1633
+ }
1634
+ #activeRemoteId;
1635
+ #dacpId;
1636
+ #sessionId;
1637
+ #buffer = Buffer.alloc(0);
1638
+ #cseq = 0;
1639
+ #requesting = false;
1640
+ #requestTimer;
1641
+ #reject;
1642
+ #resolve;
1643
+ constructor(context, address, port) {
1644
+ super(context, address, port);
1645
+ this.#activeRemoteId = generateActiveRemoteId();
1646
+ this.#dacpId = generateDacpId();
1647
+ this.#sessionId = generateSessionId();
1648
+ this.on("close", this.#onClose.bind(this));
1649
+ this.on("data", this.#onData.bind(this));
1650
+ this.on("error", this.#onError.bind(this));
1651
+ this.on("timeout", this.#onTimeout.bind(this));
1652
+ }
1653
+ async get(path, headers = {}, timeout = HTTP_TIMEOUT) {
1654
+ return await this.#request("GET", path, null, headers, timeout);
1655
+ }
1656
+ async post(path, body = null, headers = {}, timeout = HTTP_TIMEOUT) {
1657
+ return await this.#request("POST", path, body, headers, timeout);
1658
+ }
1659
+ async record(path, headers = {}, timeout = HTTP_TIMEOUT) {
1660
+ return await this.#request("RECORD", path, null, headers, timeout);
1661
+ }
1662
+ async setup(path, body = null, headers = {}, timeout = HTTP_TIMEOUT) {
1663
+ return await this.#request("SETUP", path, body, headers, timeout);
1664
+ }
1665
+ #handle(data, err) {
1666
+ if (this.#requestTimer) {
1667
+ clearTimeout(this.#requestTimer);
1668
+ this.#requestTimer = undefined;
1669
+ }
1670
+ if (err) {
1671
+ this.#reject?.(err);
1672
+ } else {
1673
+ this.#resolve?.(data);
1674
+ }
1675
+ this.#reject = undefined;
1676
+ this.#resolve = undefined;
1677
+ this.#requesting = false;
1678
+ }
1679
+ async#request(method, path, body, headers, timeout = HTTP_TIMEOUT) {
1680
+ if (this.#requesting) {
1681
+ return Promise.reject(new Error("Another request is currently being made."));
1682
+ }
1683
+ this.#requesting = true;
1684
+ headers["Active-Remote"] = this.activeRemoteId;
1685
+ headers["Client-Instance"] = this.dacpId;
1686
+ headers["DACP-ID"] = this.dacpId;
1687
+ const cseq = this.#cseq++;
1688
+ let data;
1689
+ if (body) {
1690
+ headers["Content-Length"] = Buffer.byteLength(body);
1691
+ data = Buffer.concat([
1692
+ Buffer.from(RTSP.makeHeader(method, path, headers, cseq)),
1693
+ Buffer.from(body)
1694
+ ]);
1695
+ } else {
1696
+ headers["Content-Length"] = 0;
1697
+ data = Buffer.from(RTSP.makeHeader(method, path, headers, cseq));
1698
+ }
1699
+ this.context.logger.net("[control]", method, path, `cseq = ${cseq}`);
1700
+ if (this.isEncrypted) {
1701
+ data = this.encrypt(data);
1702
+ }
1703
+ return new Promise(async (resolve, reject) => {
1704
+ this.#reject = reject;
1705
+ this.#resolve = resolve;
1706
+ this.#requestTimer = setTimeout(() => this.#handle(undefined, new Error("Request timed out")), timeout);
1707
+ await this.write(data).catch((err) => this.#handle(undefined, err));
1708
+ });
1709
+ }
1710
+ #onClose() {
1711
+ this.#buffer = Buffer.alloc(0);
1712
+ this.#handle(undefined, new Error("Connection closed."));
1713
+ this.context.logger.net("[control]", "#onClose()");
1714
+ }
1715
+ #onData(data) {
1716
+ try {
1717
+ this.#buffer = Buffer.concat([this.#buffer, data]);
1718
+ if (this.isEncrypted) {
1719
+ const decrypted = this.decrypt(this.#buffer);
1720
+ if (!decrypted) {
1721
+ return;
1722
+ }
1723
+ this.#buffer = decrypted;
1724
+ }
1725
+ while (this.#buffer.byteLength > 0) {
1726
+ const result = RTSP.makeResponse(this.#buffer);
1727
+ if (result === null) {
1728
+ return;
1729
+ }
1730
+ this.#buffer = this.#buffer.subarray(result.responseLength);
1731
+ this.#handle(result.response, undefined);
1732
+ }
1733
+ } catch (err) {
1734
+ this.context.logger.error("[control]", "#onData()", err);
1735
+ this.emit("error", err);
1736
+ }
1737
+ }
1738
+ #onError(err) {
1739
+ this.#handle(undefined, err);
1740
+ this.context.logger.error("[control]", "#onError()", err);
1741
+ }
1742
+ #onTimeout() {
1743
+ this.#handle(undefined, new Error("Request timed out."));
1744
+ this.context.logger.net("[control]", "#onTimeout()");
1855
1745
  }
1856
1746
  }
1857
-
1858
1747
  // src/dataStream.ts
1748
+ import { hkdf, randomInt32 } from "@basmilius/apple-common";
1749
+ import { Plist as Plist2 } from "@basmilius/apple-encoding";
1750
+ import { getExtension, toBinary } from "@bufbuild/protobuf";
1859
1751
  var DATA_HEADER_LENGTH = 32;
1860
1752
 
1861
- class AirPlayDataStream extends AirPlayStream {
1862
- get #encryption() {
1863
- return this[ENCRYPTION2];
1864
- }
1865
- #bindings;
1753
+ class DataStream extends BaseStream {
1866
1754
  #buffer = Buffer.alloc(0);
1867
1755
  #seqno;
1868
1756
  #handler;
1869
- constructor(address, port) {
1870
- super(address, port);
1757
+ #handlers = {};
1758
+ constructor(context, address, port) {
1759
+ super(context, address, port);
1871
1760
  this.#seqno = 0x100000000n + BigInt(randomInt32());
1872
- this.#bindings = {
1873
- onData: this.#onData.bind(this)
1874
- };
1875
- this.on("data", this.#bindings.onData);
1761
+ this.on("data", this.#onData.bind(this));
1762
+ this.#handlers[15 /* DEVICE_INFO_MESSAGE */] = [deviceInfoMessage, this.#onDeviceInfoMessage.bind(this)];
1763
+ this.#handlers[37 /* DEVICE_INFO_UPDATE_MESSAGE */] = [deviceInfoMessage, this.#onDeviceInfoUpdateMessage.bind(this)];
1764
+ this.#handlers[105 /* ORIGIN_CLIENT_PROPERTIES_MESSAGE */] = [originClientPropertiesMessage, this.#onOriginClientPropertiesMessage.bind(this)];
1765
+ this.#handlers[104 /* PLAYER_CLIENT_PROPERTIES_MESSAGE */] = [playerClientPropertiesMessage, this.#onPlayerClientPropertiesMessage.bind(this)];
1766
+ this.#handlers[2 /* SEND_COMMAND_RESULT_MESSAGE */] = [sendCommandResultMessage, this.#onSendCommandResultMessage.bind(this)];
1767
+ this.#handlers[5 /* SET_ARTWORK_MESSAGE */] = [setArtworkMessage, this.#onSetArtworkMessage.bind(this)];
1768
+ this.#handlers[72 /* SET_DEFAULT_SUPPORTED_COMMANDS_MESSAGE */] = [setDefaultSupportedCommandsMessage, this.#onSetDefaultSupportedCommandsMessage.bind(this)];
1769
+ this.#handlers[46 /* SET_NOW_PLAYING_CLIENT_MESSAGE */] = [setNowPlayingClientMessage, this.#onSetNowPlayingClientMessage.bind(this)];
1770
+ this.#handlers[47 /* SET_NOW_PLAYING_PLAYER_MESSAGE */] = [setNowPlayingPlayerMessage, this.#onSetNowPlayingPlayerMessage.bind(this)];
1771
+ this.#handlers[4 /* SET_STATE_MESSAGE */] = [setStateMessage, this.#onSetStateMessage.bind(this)];
1772
+ this.#handlers[53 /* REMOVE_CLIENT_MESSAGE */] = [removeClientMessage, this.#onRemoveClientMessage.bind(this)];
1773
+ this.#handlers[55 /* UPDATE_CLIENT_MESSAGE */] = [updateClientMessage, this.#onUpdateClientMessage.bind(this)];
1774
+ this.#handlers[56 /* UPDATE_CONTENT_ITEM_MESSAGE */] = [updateContentItemMessage, this.#onUpdateContentItemMessage.bind(this)];
1775
+ this.#handlers[57 /* UPDATE_CONTENT_ITEM_ARTWORK_MESSAGE */] = [updateContentItemArtworkMessage, this.#onUpdateContentItemArtworkMessage.bind(this)];
1776
+ this.#handlers[58 /* UPDATE_PLAYER_MESSAGE */] = [updatePlayerMessage, this.#onUpdatePlayerMessage.bind(this)];
1777
+ this.#handlers[65 /* UPDATE_OUTPUT_DEVICE_MESSAGE */] = [updateOutputDeviceMessage, this.#onUpdateOutputDeviceMessage.bind(this)];
1778
+ this.#handlers[17 /* VOLUME_CONTROL_AVAILABILITY_MESSAGE */] = [volumeControlAvailabilityMessage, this.#onVolumeControlAvailabilityMessage.bind(this)];
1779
+ this.#handlers[64 /* VOLUME_CONTROL_CAPABILITIES_DID_CHANGE_MESSAGE */] = [volumeControlCapabilitiesDidChangeMessage, this.#onVolumeControlCapabilitiesDidChangeMessage.bind(this)];
1780
+ this.#handlers[52 /* VOLUME_DID_CHANGE_MESSAGE */] = [volumeDidChangeMessage, this.#onVolumeDidChangeMessage.bind(this)];
1876
1781
  }
1877
1782
  async exchange(message) {
1878
1783
  return new Promise(async (resolve, reject) => {
@@ -1882,25 +1787,32 @@ class AirPlayDataStream extends AirPlayStream {
1882
1787
  }
1883
1788
  async reply(seqno) {
1884
1789
  const rply = buildReply(seqno);
1885
- reporter.raw(`[datastream] Sending reply for seqno ${seqno}.`);
1886
- await this.write(await this.#encrypt(rply));
1790
+ this.context.logger.raw("[data]", `Sending reply packet seqno=${seqno}`);
1791
+ await this.write(this.encrypt(rply));
1887
1792
  }
1888
1793
  async send(message) {
1889
- const bytes = toBinary(ProtocolMessageSchema, message, { writeUnknownFields: true });
1890
- const lenPrefix = Buffer.from(encodeVarint(bytes.length));
1891
- const pbPayload = Buffer.concat([lenPrefix, Buffer.from(bytes)]);
1892
- const plistPayload = Buffer.from(Plist.serialize({
1794
+ let extension;
1795
+ if (Array.isArray(message)) {
1796
+ extension = message[1];
1797
+ message = message[0];
1798
+ }
1799
+ const bytes = toBinary(ProtocolMessageSchema, message, { writeUnknownFields: false });
1800
+ const lengthPrefix = Buffer.from(encodeVarint(bytes.byteLength));
1801
+ const payload = Buffer.concat([lengthPrefix, bytes]);
1802
+ const plistPayload = Buffer.from(Plist2.serialize({
1893
1803
  params: {
1894
- data: pbPayload.buffer.slice(pbPayload.byteOffset, pbPayload.byteOffset + pbPayload.byteLength)
1804
+ data: payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength)
1895
1805
  }
1896
1806
  }));
1897
1807
  const header = buildHeader(DATA_HEADER_LENGTH + plistPayload.byteLength, this.#seqno++);
1898
- const frame = Buffer.concat([header, plistPayload]);
1899
- const encrypted = await this.#encrypt(frame);
1900
- reporter.raw("[datastream] Sending message.", message);
1901
- await this.write(encrypted);
1808
+ let frame = Buffer.concat([header, plistPayload]);
1809
+ if (this.isEncrypted) {
1810
+ frame = this.encrypt(frame);
1811
+ }
1812
+ this.context.logger.raw("[data]", "Sending message.", message.type, extension ? getExtension(message, extension) : message);
1813
+ await this.write(frame);
1902
1814
  }
1903
- async setup(sharedSecret, seed) {
1815
+ setup(sharedSecret, seed) {
1904
1816
  const readKey = hkdf({
1905
1817
  hash: "sha512",
1906
1818
  key: sharedSecret,
@@ -1915,103 +1827,32 @@ class AirPlayDataStream extends AirPlayStream {
1915
1827
  salt: Buffer.from(`DataStream-Salt${seed}`),
1916
1828
  info: Buffer.from("DataStream-Output-Encryption-Key")
1917
1829
  });
1918
- await this.enableEncryption(readKey, writeKey);
1919
- }
1920
- async#onDeviceInfoMessage(message) {
1921
- reporter.info("Connected to device", message.name);
1922
- this.emit("deviceInfo", message);
1923
- }
1924
- async#onDeviceInfoUpdateMessage(message) {
1925
- reporter.info("Device info update", message);
1926
- this.emit("deviceInfoUpdate", message);
1927
- }
1928
- async#onOriginClientPropertiesMessage(message) {
1929
- reporter.raw("Origin client properties", message);
1930
- this.emit("originClientProperties", message);
1830
+ this.enableEncryption(readKey, writeKey);
1931
1831
  }
1932
- async#onPlayerClientPropertiesMessage(message) {
1933
- reporter.raw("Player client properties", message);
1934
- this.emit("playerClientProperties", message);
1935
- }
1936
- async#onRemoveClientMessage(message) {
1937
- reporter.info("Remove client", message);
1938
- this.emit("removeClient", message);
1939
- }
1940
- async#onSendCommandResultMessage(message) {
1941
- reporter.info("Send command result", message);
1942
- this.emit("sendCommandResult", message);
1943
- }
1944
- async#onSetArtworkMessage(message) {
1945
- reporter.info("Set artwork", message);
1946
- this.emit("setArtwork", message);
1947
- }
1948
- async#onSetDefaultSupportedCommandsMessage(message) {
1949
- reporter.info("Set default supported commands", message);
1950
- this.emit("setDefaultSupportedCommands", message);
1951
- }
1952
- async#onSetNowPlayingClientMessage(message) {
1953
- reporter.info("Set now playing client", message);
1954
- this.emit("setNowPlayingClient", message);
1955
- }
1956
- async#onSetNowPlayingPlayerMessage(message) {
1957
- reporter.info("Set now playing player", message);
1958
- this.emit("setNowPlayingPlayer", message);
1959
- }
1960
- async#onSetStateMessage(message) {
1961
- reporter.info("Set state", message);
1962
- this.emit("setState", message);
1963
- }
1964
- async#onUpdateClientMessage(message) {
1965
- reporter.info("Update client", message);
1966
- this.emit("updateClient", message);
1967
- }
1968
- async#onUpdateContentItemMessage(message) {
1969
- reporter.info("Update content item", message);
1970
- this.emit("updateContentItem", message);
1971
- }
1972
- async#onUpdateContentItemArtworkMessage(message) {
1973
- reporter.info("Update content artwork", message);
1974
- this.emit("updateContentItemArtwork", message);
1975
- }
1976
- async#onUpdatePlayerMessage(message) {
1977
- reporter.info("Update player", message);
1978
- this.emit("updatePlayer", message);
1979
- }
1980
- async#onUpdateOutputDeviceMessage(message) {
1981
- reporter.info("Update output device", message);
1982
- this.emit("updateOutputDevice", message);
1983
- }
1984
- async#onVolumeControlAvailabilityMessage(message) {
1985
- reporter.info("Volume control availability", message);
1986
- this.emit("volumeControlAvailability", message);
1987
- }
1988
- async#onVolumeControlCapabilitiesDidChangeMessage(message) {
1989
- reporter.info("Volume control capabilities did change", message);
1990
- this.emit("volumeControlCapabilitiesDidChange", message);
1991
- }
1992
- async#onVolumeDidChangeMessage(message) {
1993
- reporter.info("VolumeDidChange message", message);
1994
- this.emit("volumeDidChange", message);
1995
- }
1996
- async#onData(buffer) {
1832
+ async#onData(data) {
1997
1833
  try {
1998
- this.#buffer = Buffer.concat([this.#buffer, buffer]);
1999
- this.#buffer = await this.#decrypt(this.#buffer);
1834
+ this.#buffer = Buffer.concat([this.#buffer, data]);
1835
+ if (this.isEncrypted) {
1836
+ const decrypted = this.decrypt(this.#buffer);
1837
+ if (!decrypted) {
1838
+ return;
1839
+ }
1840
+ this.#buffer = decrypted;
1841
+ }
2000
1842
  while (this.#buffer.byteLength > DATA_HEADER_LENGTH) {
2001
1843
  const header = this.#buffer.subarray(0, DATA_HEADER_LENGTH);
2002
1844
  const totalLength = header.readUint32BE();
2003
1845
  if (this.#buffer.byteLength < totalLength) {
2004
- reporter.warn(`Not enough data yet, waiting on the next frame.. needed=${totalLength} available=${this.#buffer.byteLength} receivedLength=${buffer.byteLength}`);
1846
+ this.context.logger.warn("[data]", `Data packet is too short needed=${totalLength} available=${this.#buffer.byteLength} receivedLength=${data.byteLength}`);
2005
1847
  return;
2006
1848
  }
2007
1849
  const frame = this.#buffer.subarray(DATA_HEADER_LENGTH, totalLength);
2008
- const plist = Plist.parse(frame.buffer.slice(frame.byteOffset, frame.byteOffset + frame.byteLength));
1850
+ const plist = Plist2.parse(frame.buffer.slice(frame.byteOffset, frame.byteOffset + frame.byteLength));
2009
1851
  const command = header.toString("ascii", 4, 8);
2010
- reporter.raw("Raw data received", header.toString());
2011
1852
  this.#buffer = this.#buffer.subarray(totalLength);
2012
1853
  if (!plist || !plist.params || !plist.params.data) {
2013
1854
  if (command === "rply") {
2014
- reporter.raw("Got reply...");
1855
+ this.context.logger.raw("[data]", "Received reply packet.");
2015
1856
  } else if (command === "sync") {
2016
1857
  await this.reply(parseHeaderSeqno(header));
2017
1858
  }
@@ -2023,221 +1864,117 @@ class AirPlayDataStream extends AirPlayStream {
2023
1864
  continue;
2024
1865
  }
2025
1866
  const content = Buffer.from(plist.params.data);
2026
- for (const message of await parseMessages(content)) {
2027
- reporter.raw("Parsed message", message);
2028
- await this.#handleMessage(message);
1867
+ for (const message of parseMessages(content)) {
1868
+ this.context.logger.raw("[data]", `Received message.`, message);
1869
+ this.#handleMessage(message);
2029
1870
  }
2030
1871
  if (command === "sync") {
2031
1872
  await this.reply(parseHeaderSeqno(header));
2032
1873
  }
2033
1874
  }
2034
1875
  } catch (err) {
2035
- reporter.error("Error in data stream #onData()", err);
1876
+ this.context.logger.error("[data]", "#onData()", err);
2036
1877
  this.emit("error", err);
2037
1878
  }
2038
1879
  }
2039
- async#decrypt(data) {
2040
- const result = [];
2041
- let offset = 0;
2042
- let readCount = this.#encryption.readCount ?? 0;
2043
- while (offset < data.length) {
2044
- if (offset + 2 > data.length) {
2045
- reporter.warn("Truncated frame length");
2046
- return this.#buffer;
2047
- }
2048
- const frameLength = data.readUInt16LE(offset);
2049
- offset += 2;
2050
- const nonce = this.nonce(readCount++);
2051
- const end = offset + frameLength + 16;
2052
- if (end > data.length) {
2053
- reporter.warn(`Truncated frame end=${end} length=${data.length}`);
2054
- return this.#buffer;
2055
- }
2056
- const ciphertext = data.subarray(offset, offset + frameLength);
2057
- const authTag = data.subarray(offset + frameLength, end);
2058
- offset = end;
2059
- const plaintext = Chacha202.decrypt(this.#encryption.readKey, nonce, Buffer.from(Uint16Array.of(frameLength).buffer.slice(0, 2)), ciphertext, authTag);
2060
- result.push(plaintext);
2061
- }
2062
- this.#encryption.readCount = readCount;
2063
- return Buffer.concat(result);
2064
- }
2065
- async#encrypt(data) {
2066
- const FRAME_LENGTH = 1024;
2067
- const result = [];
2068
- for (let offset = 0;offset < data.length; ) {
2069
- const frame = data.subarray(offset, offset + FRAME_LENGTH);
2070
- offset += frame.length;
2071
- const leLength = Buffer.alloc(2);
2072
- leLength.writeUInt16LE(frame.length, 0);
2073
- const nonce = this.nonce(this.#encryption.writeCount++);
2074
- const encrypted = Chacha202.encrypt(this.#encryption.writeKey, nonce, leLength, frame);
2075
- result.push(leLength, encrypted.ciphertext, encrypted.authTag);
2076
- }
2077
- return Buffer.concat(result);
2078
- }
2079
- async#handleMessage(message) {
1880
+ #handleMessage(message) {
2080
1881
  if (this.#handler) {
2081
1882
  const [resolve] = this.#handler;
2082
1883
  this.#handler = undefined;
2083
1884
  resolve(message);
2084
1885
  }
2085
- switch (message.type) {
2086
- case 15 /* DEVICE_INFO_MESSAGE */:
2087
- await this.#onDeviceInfoMessage(getExtension2(message, deviceInfoMessage));
2088
- break;
2089
- case 37 /* DEVICE_INFO_UPDATE_MESSAGE */:
2090
- await this.#onDeviceInfoUpdateMessage(getExtension2(message, deviceInfoMessage));
2091
- break;
2092
- case 105 /* ORIGIN_CLIENT_PROPERTIES_MESSAGE */:
2093
- await this.#onOriginClientPropertiesMessage(getExtension2(message, originClientPropertiesMessage));
2094
- break;
2095
- case 104 /* PLAYER_CLIENT_PROPERTIES_MESSAGE */:
2096
- await this.#onPlayerClientPropertiesMessage(getExtension2(message, playerClientPropertiesMessage));
2097
- break;
2098
- case 2 /* SEND_COMMAND_RESULT_MESSAGE */:
2099
- await this.#onSendCommandResultMessage(getExtension2(message, sendCommandResultMessage));
2100
- break;
2101
- case 5 /* SET_ARTWORK_MESSAGE */:
2102
- await this.#onSetArtworkMessage(getExtension2(message, setArtworkMessage));
2103
- break;
2104
- case 72 /* SET_DEFAULT_SUPPORTED_COMMANDS_MESSAGE */:
2105
- await this.#onSetDefaultSupportedCommandsMessage(getExtension2(message, setDefaultSupportedCommandsMessage));
2106
- break;
2107
- case 46 /* SET_NOW_PLAYING_CLIENT_MESSAGE */:
2108
- await this.#onSetNowPlayingClientMessage(getExtension2(message, setNowPlayingClientMessage));
2109
- break;
2110
- case 47 /* SET_NOW_PLAYING_PLAYER_MESSAGE */:
2111
- await this.#onSetNowPlayingPlayerMessage(getExtension2(message, setNowPlayingPlayerMessage));
2112
- break;
2113
- case 4 /* SET_STATE_MESSAGE */:
2114
- await this.#onSetStateMessage(getExtension2(message, setStateMessage));
2115
- break;
2116
- case 53 /* REMOVE_CLIENT_MESSAGE */:
2117
- await this.#onRemoveClientMessage(getExtension2(message, removeClientMessage));
2118
- break;
2119
- case 55 /* UPDATE_CLIENT_MESSAGE */:
2120
- await this.#onUpdateClientMessage(getExtension2(message, updateClientMessage));
2121
- break;
2122
- case 56 /* UPDATE_CONTENT_ITEM_MESSAGE */:
2123
- await this.#onUpdateContentItemMessage(getExtension2(message, updateContentItemMessage));
2124
- break;
2125
- case 57 /* UPDATE_CONTENT_ITEM_ARTWORK_MESSAGE */:
2126
- await this.#onUpdateContentItemArtworkMessage(getExtension2(message, updateContentItemArtworkMessage));
2127
- break;
2128
- case 58 /* UPDATE_PLAYER_MESSAGE */:
2129
- await this.#onUpdatePlayerMessage(getExtension2(message, updatePlayerMessage));
2130
- break;
2131
- case 65 /* UPDATE_OUTPUT_DEVICE_MESSAGE */:
2132
- await this.#onUpdateOutputDeviceMessage(getExtension2(message, updateOutputDeviceMessage));
2133
- break;
2134
- case 17 /* VOLUME_CONTROL_AVAILABILITY_MESSAGE */:
2135
- await this.#onVolumeControlAvailabilityMessage(getExtension2(message, volumeControlAvailabilityMessage));
2136
- break;
2137
- case 64 /* VOLUME_CONTROL_CAPABILITIES_DID_CHANGE_MESSAGE */:
2138
- await this.#onVolumeControlCapabilitiesDidChangeMessage(getExtension2(message, volumeControlCapabilitiesDidChangeMessage));
2139
- break;
2140
- case 52 /* VOLUME_DID_CHANGE_MESSAGE */:
2141
- await this.#onVolumeDidChangeMessage(getExtension2(message, volumeDidChangeMessage));
2142
- break;
2143
- case 0 /* UNKNOWN_MESSAGE */:
2144
- break;
2145
- default:
2146
- reporter.warn("Received unknown message.", message);
2147
- break;
1886
+ if (message.type in this.#handlers) {
1887
+ const [extension, handler] = this.#handlers[message.type];
1888
+ handler(getExtension(message, extension));
1889
+ } else if (message.type !== 0 /* UNKNOWN_MESSAGE */) {
1890
+ this.context.logger.warn("[data]", `Unknown message type ${message.type}.`);
2148
1891
  }
2149
1892
  }
2150
- }
2151
- function buildHeader(totalSize, seqno) {
2152
- const buf = Buffer.alloc(32);
2153
- buf.writeUInt32BE(totalSize, 0);
2154
- buf.write("sync", 4, "ascii");
2155
- buf.fill(0, 8, 16);
2156
- buf.write("comm", 16, "ascii");
2157
- buf.writeBigUInt64BE(seqno, 20);
2158
- buf.writeUInt32BE(0, 28);
2159
- return buf;
2160
- }
2161
- function buildReply(seqno) {
2162
- const header = Buffer.alloc(32);
2163
- header.writeUInt32BE(0, 0);
2164
- header.write("rply", 4, "ascii");
2165
- header.fill(0, 8, 16);
2166
- header.writeBigUInt64BE(seqno, 20);
2167
- header.writeUInt32BE(0, 28);
2168
- const plist = Buffer.from(Plist.serialize(Buffer.alloc(0)));
2169
- const total = header.length + plist.length;
2170
- header.writeUInt32BE(total, 0);
2171
- return Buffer.concat([header, plist]);
2172
- }
2173
- function encodeVarint(value) {
2174
- if (value < 0) {
2175
- throw new RangeError("Varint only supports non-negative integers");
1893
+ #onDeviceInfoMessage(message) {
1894
+ this.context.logger.info("[data]", "Connected to device", message.name);
1895
+ this.emit("deviceInfo", message);
2176
1896
  }
2177
- const bytes = [];
2178
- while (value > 127) {
2179
- bytes.push(value & 127 | 128);
2180
- value >>>= 7;
1897
+ #onDeviceInfoUpdateMessage(message) {
1898
+ this.context.logger.info("[data]", "Device info update", message);
1899
+ this.emit("deviceInfoUpdate", message);
2181
1900
  }
2182
- bytes.push(value);
2183
- return Uint8Array.from(bytes);
2184
- }
2185
- function parseHeaderSeqno(header) {
2186
- if (header.length < 28) {
2187
- throw new Error("Header too short");
1901
+ #onOriginClientPropertiesMessage(message) {
1902
+ this.context.logger.raw("[data]", "Origin client properties", message);
1903
+ this.emit("originClientProperties", message);
2188
1904
  }
2189
- return header.readBigUInt64BE(20);
2190
- }
2191
- async function parseMessages(content) {
2192
- const messages = [];
2193
- let offset = 0;
2194
- while (offset < content.length) {
2195
- const firstByte = content[offset];
2196
- if (firstByte === 8) {
2197
- const message2 = content.subarray(offset);
2198
- const decoded2 = fromBinary(ProtocolMessageSchema, message2, { readUnknownFields: true });
2199
- messages.push(decoded2);
2200
- break;
2201
- }
2202
- const [length, variantLen] = readVariant(content, offset);
2203
- offset += variantLen;
2204
- if (offset + length > content.length) {
2205
- break;
2206
- }
2207
- const message = content.subarray(offset, offset + length);
2208
- offset += length;
2209
- const decoded = fromBinary(ProtocolMessageSchema, message, { readUnknownFields: true });
2210
- messages.push(decoded);
1905
+ #onPlayerClientPropertiesMessage(message) {
1906
+ this.context.logger.raw("[data]", "Player client properties", message);
1907
+ this.emit("playerClientProperties", message);
2211
1908
  }
2212
- return messages;
2213
- }
2214
- function readVariant(buf, offset = 0) {
2215
- let result = 0;
2216
- let shift = 0;
2217
- let bytesRead = 0;
2218
- while (true) {
2219
- const byte = buf[offset + bytesRead++];
2220
- result |= (byte & 127) << shift;
2221
- if ((byte & 128) === 0) {
2222
- break;
2223
- }
2224
- shift += 7;
1909
+ #onRemoveClientMessage(message) {
1910
+ this.context.logger.info("[data]", "Remove client", message);
1911
+ this.emit("removeClient", message);
1912
+ }
1913
+ #onSendCommandResultMessage(message) {
1914
+ this.context.logger.info("[data]", "Send command result", message);
1915
+ this.emit("sendCommandResult", message);
1916
+ }
1917
+ #onSetArtworkMessage(message) {
1918
+ this.context.logger.info("[data]", "Set artwork", message);
1919
+ this.emit("setArtwork", message);
1920
+ }
1921
+ #onSetDefaultSupportedCommandsMessage(message) {
1922
+ this.context.logger.info("[data]", "Set default supported commands", message);
1923
+ this.emit("setDefaultSupportedCommands", message);
1924
+ }
1925
+ #onSetNowPlayingClientMessage(message) {
1926
+ this.context.logger.info("[data]", "Set now playing client", message);
1927
+ this.emit("setNowPlayingClient", message);
1928
+ }
1929
+ #onSetNowPlayingPlayerMessage(message) {
1930
+ this.context.logger.info("[data]", "Set now playing player", message);
1931
+ this.emit("setNowPlayingPlayer", message);
1932
+ }
1933
+ #onSetStateMessage(message) {
1934
+ this.context.logger.info("[data]", "Set state", message);
1935
+ this.emit("setState", message);
1936
+ }
1937
+ #onUpdateClientMessage(message) {
1938
+ this.context.logger.info("[data]", "Update client", message);
1939
+ this.emit("updateClient", message);
1940
+ }
1941
+ #onUpdateContentItemMessage(message) {
1942
+ this.context.logger.info("[data]", "Update content item", message);
1943
+ this.emit("updateContentItem", message);
1944
+ }
1945
+ #onUpdateContentItemArtworkMessage(message) {
1946
+ this.context.logger.info("[data]", "Update content artwork", message);
1947
+ this.emit("updateContentItemArtwork", message);
1948
+ }
1949
+ #onUpdatePlayerMessage(message) {
1950
+ this.context.logger.info("[data]", "Update player", message);
1951
+ this.emit("updatePlayer", message);
1952
+ }
1953
+ #onUpdateOutputDeviceMessage(message) {
1954
+ this.context.logger.info("[data]", "Update output device", message);
1955
+ this.emit("updateOutputDevice", message);
1956
+ }
1957
+ #onVolumeControlAvailabilityMessage(message) {
1958
+ this.context.logger.info("[data]", "Volume control availability", message);
1959
+ this.emit("volumeControlAvailability", message);
1960
+ }
1961
+ #onVolumeControlCapabilitiesDidChangeMessage(message) {
1962
+ this.context.logger.info("[data]", "Volume control capabilities did change", message);
1963
+ this.emit("volumeControlCapabilitiesDidChange", message);
1964
+ }
1965
+ #onVolumeDidChangeMessage(message) {
1966
+ this.context.logger.info("[data]", "VolumeDidChange message", message);
1967
+ this.emit("volumeDidChange", message);
2225
1968
  }
2226
- return [result, bytesRead];
2227
1969
  }
2228
-
2229
1970
  // src/eventStream.ts
2230
- import { hkdf as hkdf2, reporter as reporter2 } from "@basmilius/apple-common";
2231
- import { Plist as Plist2 } from "@basmilius/apple-encoding";
2232
- class AirPlayEventStream extends AirPlayStream {
2233
- #bindings;
1971
+ import { hkdf as hkdf2 } from "@basmilius/apple-common";
1972
+ import { Plist as Plist3, RTSP as RTSP2 } from "@basmilius/apple-encoding";
1973
+ class EventStream extends BaseStream {
2234
1974
  #buffer = Buffer.alloc(0);
2235
- constructor(address, port) {
2236
- super(address, port);
2237
- this.#bindings = {
2238
- onData: this.#onData.bind(this)
2239
- };
2240
- this.on("data", this.#bindings.onData);
1975
+ constructor(context, address, port) {
1976
+ super(context, address, port);
1977
+ this.on("data", this.#onData.bind(this));
2241
1978
  }
2242
1979
  async respond(response) {
2243
1980
  const body = Buffer.from(await response.arrayBuffer());
@@ -2265,11 +2002,11 @@ class AirPlayEventStream extends AirPlayStream {
2265
2002
  data = Buffer.from(headers);
2266
2003
  }
2267
2004
  if (this.isEncrypted) {
2268
- data = await this.encrypt(data);
2005
+ data = this.encrypt(data);
2269
2006
  }
2270
2007
  await this.write(data);
2271
2008
  }
2272
- async setup(sharedSecret) {
2009
+ setup(sharedSecret) {
2273
2010
  const readKey = hkdf2({
2274
2011
  hash: "sha512",
2275
2012
  key: sharedSecret,
@@ -2284,14 +2021,14 @@ class AirPlayEventStream extends AirPlayStream {
2284
2021
  salt: Buffer.from("Events-Salt"),
2285
2022
  info: Buffer.from("Events-Write-Encryption-Key")
2286
2023
  });
2287
- await this.enableEncryption(writeKey, readKey);
2024
+ this.enableEncryption(writeKey, readKey);
2288
2025
  }
2289
2026
  async#handle(method, path, headers, body) {
2290
2027
  const key = `${method} ${path}`;
2291
2028
  switch (key) {
2292
2029
  case "POST /command":
2293
- const data = Plist2.parse(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength));
2294
- reporter2.info("Received event stream request.", data);
2030
+ const data = Plist3.parse(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength));
2031
+ this.context.logger.info("[event]", "Received event stream request.", data);
2295
2032
  const response = new Response(null, {
2296
2033
  status: 200,
2297
2034
  statusText: "OK",
@@ -2303,224 +2040,102 @@ class AirPlayEventStream extends AirPlayStream {
2303
2040
  await this.respond(response);
2304
2041
  break;
2305
2042
  default:
2306
- reporter2.warn("No handler for url", key);
2307
- break;
2308
- }
2309
- }
2310
- async#onData(buffer) {
2311
- try {
2312
- this.#buffer = Buffer.concat([this.#buffer, buffer]);
2313
- if (this.isEncrypted) {
2314
- this.#buffer = await this.decrypt(this.#buffer);
2315
- }
2316
- while (this.#buffer.byteLength > 0) {
2317
- const result = makeHttpRequest(this.#buffer);
2318
- if (result === null) {
2319
- return;
2320
- }
2321
- this.#buffer = this.#buffer.subarray(result.requestLength);
2322
- await this.#handle(result.method, result.path, result.headers, result.body);
2323
- }
2324
- } catch (err) {
2325
- reporter2.error("Error in event stream #onData()", err);
2326
- this.emit("error", err);
2327
- }
2328
- }
2329
- }
2330
-
2331
- // src/pairing.ts
2332
- import { AccessoryPair } from "@basmilius/apple-common";
2333
-
2334
- class AirPlayPairing {
2335
- get internal() {
2336
- return this.#internal;
2337
- }
2338
- get rtsp() {
2339
- return this.#protocol.rtsp;
2340
- }
2341
- #internal;
2342
- #protocol;
2343
- #hkp;
2344
- constructor(protocol2) {
2345
- this.#internal = new AccessoryPair(this.#request.bind(this));
2346
- this.#protocol = protocol2;
2347
- }
2348
- async start() {
2349
- await this.#internal.start();
2350
- }
2351
- async pin(askPin) {
2352
- this.#hkp = 3;
2353
- await this.#pinStart();
2354
- return this.#internal.pin(askPin);
2355
- }
2356
- async pinStart() {
2357
- this.#hkp = 3;
2358
- await this.#pinStart();
2359
- }
2360
- async transient() {
2361
- this.#hkp = 4;
2362
- await this.#pinStart();
2363
- return this.#internal.transient();
2364
- }
2365
- async#pinStart() {
2366
- const response = await this.rtsp.post("/pair-pin-start", null, {
2367
- "Content-Type": "application/octet-stream",
2368
- "X-Apple-HKP": this.#hkp.toString()
2369
- });
2370
- if (response.status !== 200) {
2371
- throw new Error(`Cannot start pairing session. ${response.status} ${response.statusText} ${await response.text()}`);
2372
- }
2373
- }
2374
- async#request(_, data) {
2375
- const response = await this.rtsp.post("/pair-setup", data, {
2376
- "Content-Type": "application/octet-stream",
2377
- "X-Apple-HKP": this.#hkp.toString()
2378
- });
2379
- return Buffer.from(await response.arrayBuffer());
2380
- }
2381
- }
2382
-
2383
- // src/rtsp.ts
2384
- import { HTTP_TIMEOUT, reporter as reporter3 } from "@basmilius/apple-common";
2385
- class AirPlayRTSP extends AirPlayStream {
2386
- get activeRemote() {
2387
- return this.#activeRemote;
2388
- }
2389
- get dacpId() {
2390
- return this.#dacpId;
2391
- }
2392
- get sessionId() {
2393
- return this.#sessionId;
2394
- }
2395
- #activeRemote;
2396
- #bindings;
2397
- #dacpId;
2398
- #sessionId;
2399
- #buffer = Buffer.alloc(0);
2400
- #cseq = 0;
2401
- #requesting = false;
2402
- #requestTimer;
2403
- #reject;
2404
- #resolve;
2405
- constructor(address, port) {
2406
- super(address, port);
2407
- this.#activeRemote = Math.floor(Math.random() * 2 ** 32).toString(10);
2408
- this.#dacpId = Math.floor(Math.random() * 2 ** 64).toString(16).toUpperCase();
2409
- this.#sessionId = Math.floor(Math.random() * 2 ** 32).toString(10);
2410
- this.#bindings = {
2411
- onClose: this.#onClose.bind(this),
2412
- onData: this.#onData.bind(this),
2413
- onError: this.#onError.bind(this),
2414
- onTimeout: this.#onTimeout.bind(this)
2415
- };
2416
- this.on("close", this.#bindings.onClose);
2417
- this.on("data", this.#bindings.onData);
2418
- this.on("error", this.#bindings.onError);
2419
- this.on("timeout", this.#bindings.onTimeout);
2420
- }
2421
- async get(path, headers = {}, timeout = HTTP_TIMEOUT) {
2422
- return await this.#request("GET", path, null, headers, timeout);
2423
- }
2424
- async post(path, body = null, headers = {}, timeout = HTTP_TIMEOUT) {
2425
- return await this.#request("POST", path, body, headers, timeout);
2426
- }
2427
- async record(path, headers = {}, timeout = HTTP_TIMEOUT) {
2428
- return await this.#request("RECORD", path, null, headers, timeout);
2429
- }
2430
- async setup(path, body = null, headers = {}, timeout = HTTP_TIMEOUT) {
2431
- return await this.#request("SETUP", path, body, headers, timeout);
2432
- }
2433
- async#handle(data, err) {
2434
- if (this.#requestTimer) {
2435
- clearTimeout(this.#requestTimer);
2436
- this.#requestTimer = undefined;
2437
- }
2438
- if (err) {
2439
- this.#reject?.(err);
2440
- } else {
2441
- this.#resolve?.(data);
2442
- }
2443
- this.#reject = undefined;
2444
- this.#resolve = undefined;
2445
- this.#requesting = false;
2446
- }
2447
- async#request(method, path, body, headers, timeout = HTTP_TIMEOUT) {
2448
- if (!this.isConnected) {
2449
- return Promise.reject(new Error("Accessory not connected."));
2450
- }
2451
- if (this.#requesting) {
2452
- return Promise.reject(new Error("Another request is currently being made."));
2453
- }
2454
- this.#requesting = true;
2455
- headers["Active-Remote"] = this.activeRemote;
2456
- headers["Client-Instance"] = this.dacpId;
2457
- headers["DACP-ID"] = this.dacpId;
2458
- const cseq = this.#cseq++;
2459
- let data;
2460
- if (body) {
2461
- headers["Content-Length"] = Buffer.byteLength(body).toString();
2462
- data = Buffer.concat([
2463
- Buffer.from(makeHttpHeader(method, path, headers, cseq)),
2464
- Buffer.from(body)
2465
- ]);
2466
- } else {
2467
- headers["Content-Length"] = "0";
2468
- data = Buffer.from(makeHttpHeader(method, path, headers, cseq));
2469
- }
2470
- reporter3.net("[rtsp]", method, path, `cseq = ${cseq}`);
2471
- if (this.isEncrypted) {
2472
- data = await this.encrypt(data);
2043
+ this.context.logger.warn("[event]", "No handler for url", key);
2044
+ break;
2473
2045
  }
2474
- return new Promise(async (resolve, reject) => {
2475
- this.#reject = reject;
2476
- this.#resolve = resolve;
2477
- this.#requestTimer = setTimeout(() => this.#handle(undefined, new Error("Request timed out")), timeout);
2478
- await this.write(data).catch((err) => this.#handle(undefined, err));
2479
- });
2480
- }
2481
- async#onClose() {
2482
- this.#buffer = Buffer.alloc(0);
2483
- await this.#handle(undefined, new Error("Connection closed."));
2484
2046
  }
2485
- async#onData(buffer) {
2047
+ async#onData(data) {
2486
2048
  try {
2487
- this.#buffer = Buffer.concat([this.#buffer, buffer]);
2049
+ this.#buffer = Buffer.concat([this.#buffer, data]);
2488
2050
  if (this.isEncrypted) {
2489
- this.#buffer = await this.decrypt(this.#buffer);
2051
+ const decrypted = this.decrypt(this.#buffer);
2052
+ if (!decrypted) {
2053
+ return;
2054
+ }
2055
+ this.#buffer = decrypted;
2490
2056
  }
2491
2057
  while (this.#buffer.byteLength > 0) {
2492
- const result = makeHttpResponse(this.#buffer);
2058
+ const result = RTSP2.makeRequest(this.#buffer);
2493
2059
  if (result === null) {
2494
2060
  return;
2495
2061
  }
2496
- this.#buffer = this.#buffer.subarray(result.responseLength);
2497
- await this.#handle(result.response, undefined);
2062
+ this.#buffer = this.#buffer.subarray(result.requestLength);
2063
+ await this.#handle(result.method, result.path, result.headers, result.body);
2498
2064
  }
2499
2065
  } catch (err) {
2500
- reporter3.error("Error in rtsp #onData()", err);
2066
+ this.context.logger.error("[event]", "#onData()", err);
2501
2067
  this.emit("error", err);
2502
2068
  }
2503
2069
  }
2504
- async#onError(err) {
2505
- await this.#handle(undefined, err);
2070
+ }
2071
+ // src/protocol.ts
2072
+ import { AIRPLAY_SERVICE, Context, getMacAddress, randomInt64, uuid } from "@basmilius/apple-common";
2073
+ import { Plist as Plist4 } from "@basmilius/apple-encoding";
2074
+
2075
+ // src/pairing.ts
2076
+ import { AccessoryPair, AccessoryVerify, hkdf as hkdf3 } from "@basmilius/apple-common";
2077
+
2078
+ class Pairing {
2079
+ get controlStream() {
2080
+ return this.#controlStream;
2081
+ }
2082
+ get internal() {
2083
+ return this.#internal;
2084
+ }
2085
+ #controlStream;
2086
+ #internal;
2087
+ #hkp;
2088
+ constructor(protocol) {
2089
+ this.#controlStream = protocol.controlStream;
2090
+ this.#internal = new AccessoryPair(protocol.context, this.#request.bind(this));
2091
+ }
2092
+ async start() {
2093
+ await this.#internal.start();
2094
+ }
2095
+ async pin(askPin) {
2096
+ this.#hkp = 3;
2097
+ await this.#pinStart();
2098
+ return this.#internal.pin(askPin);
2099
+ }
2100
+ async pinStart() {
2101
+ this.#hkp = 3;
2102
+ await this.#pinStart();
2103
+ }
2104
+ async transient() {
2105
+ this.#hkp = 4;
2106
+ await this.#pinStart();
2107
+ return this.#internal.transient();
2108
+ }
2109
+ async#pinStart() {
2110
+ const response = await this.#controlStream.post("/pair-pin-start", null, {
2111
+ "Content-Type": "application/octet-stream",
2112
+ "X-Apple-HKP": this.#hkp.toString()
2113
+ });
2114
+ if (response.status !== 200) {
2115
+ throw new Error(`Cannot start pairing session. ${response.status} ${response.statusText} ${await response.text()}`);
2116
+ }
2506
2117
  }
2507
- async#onTimeout() {
2508
- await this.#handle(undefined, new Error("Request timed out."));
2118
+ async#request(_, data) {
2119
+ const response = await this.#controlStream.post("/pair-setup", data, {
2120
+ "Content-Type": "application/octet-stream",
2121
+ "X-Apple-HKP": this.#hkp.toString()
2122
+ });
2123
+ return Buffer.from(await response.arrayBuffer());
2509
2124
  }
2510
2125
  }
2511
2126
 
2512
- // src/verify.ts
2513
- import { AccessoryVerify, hkdf as hkdf3 } from "@basmilius/apple-common";
2514
-
2515
- class AirPlayVerify {
2516
- get rtsp() {
2517
- return this.#protocol.rtsp;
2127
+ class Verify {
2128
+ get controlStream() {
2129
+ return this.#controlStream;
2518
2130
  }
2131
+ get internal() {
2132
+ return this.#internal;
2133
+ }
2134
+ #controlStream;
2519
2135
  #internal;
2520
- #protocol;
2521
- constructor(protocol2) {
2522
- this.#internal = new AccessoryVerify(this.#request.bind(this));
2523
- this.#protocol = protocol2;
2136
+ constructor(protocol) {
2137
+ this.#controlStream = protocol.controlStream;
2138
+ this.#internal = new AccessoryVerify(protocol.context, this.#request.bind(this));
2524
2139
  }
2525
2140
  async start(credentials) {
2526
2141
  const keys = await this.#internal.start(credentials);
@@ -2546,7 +2161,7 @@ class AirPlayVerify {
2546
2161
  };
2547
2162
  }
2548
2163
  async#request(_, data) {
2549
- const response = await this.rtsp.post("/pair-verify", data, {
2164
+ const response = await this.controlStream.post("/pair-verify", data, {
2550
2165
  "Content-Type": "application/octet-stream",
2551
2166
  "X-Apple-HKP": "3"
2552
2167
  });
@@ -2555,79 +2170,89 @@ class AirPlayVerify {
2555
2170
  }
2556
2171
 
2557
2172
  // src/protocol.ts
2558
- class AirPlay {
2559
- get device() {
2560
- return this.#device;
2173
+ class Protocol {
2174
+ get context() {
2175
+ return this.#context;
2176
+ }
2177
+ get controlStream() {
2178
+ return this.#controlStream;
2561
2179
  }
2562
2180
  get dataStream() {
2563
2181
  return this.#dataStream;
2564
2182
  }
2183
+ get discoveryResult() {
2184
+ return this.#discoveryResult;
2185
+ }
2565
2186
  get eventStream() {
2566
2187
  return this.#eventStream;
2567
2188
  }
2568
2189
  get pairing() {
2569
2190
  return this.#pairing;
2570
2191
  }
2571
- get rtsp() {
2572
- return this.#rtsp;
2573
- }
2574
2192
  get sessionUUID() {
2575
2193
  return this.#sessionUUID;
2576
2194
  }
2577
2195
  get verify() {
2578
2196
  return this.#verify;
2579
2197
  }
2580
- #device;
2198
+ #context;
2199
+ #controlStream;
2200
+ #discoveryResult;
2581
2201
  #pairing;
2582
- #rtsp;
2583
- #verify;
2584
2202
  #sessionUUID;
2203
+ #verify;
2585
2204
  #dataStream;
2586
2205
  #eventStream;
2587
2206
  #timingServer;
2588
- constructor(device) {
2589
- this.#device = device;
2590
- this.#rtsp = new AirPlayRTSP(device.address, device.service.port);
2591
- this.#pairing = new AirPlayPairing(this);
2592
- this.#verify = new AirPlayVerify(this);
2593
- this.#sessionUUID = uuid2();
2207
+ constructor(deviceId, discoveryResult) {
2208
+ this.#context = new Context(deviceId.replace(`.${AIRPLAY_SERVICE}`, "").replace(".local", ""));
2209
+ this.#discoveryResult = discoveryResult;
2210
+ this.#sessionUUID = uuid();
2211
+ this.#controlStream = new ControlStream(this.#context, discoveryResult.address, discoveryResult.service.port);
2212
+ this.#pairing = new Pairing(this);
2213
+ this.#verify = new Verify(this);
2594
2214
  }
2595
2215
  async connect() {
2596
- await this.#rtsp.connect();
2216
+ await this.#controlStream.connect();
2217
+ }
2218
+ async destroy() {
2219
+ await this.#controlStream.destroy();
2597
2220
  }
2598
2221
  async disconnect() {
2599
- await this.#rtsp.disconnect();
2222
+ await this.#dataStream.disconnect();
2600
2223
  }
2601
2224
  async feedback() {
2602
- await this.#rtsp.post("/feedback", undefined, undefined, 1900);
2225
+ await this.#controlStream.post("/feedback", undefined, undefined, 1900);
2603
2226
  }
2604
2227
  async setupDataStream(sharedSecret, onBeforeConnect) {
2605
2228
  const seed = randomInt64();
2606
- const request = Plist3.serialize({
2607
- streams: [
2608
- {
2609
- controlType: 2,
2610
- channelID: uuid2().toUpperCase(),
2611
- seed,
2612
- clientUUID: uuid2().toUpperCase(),
2613
- type: 130,
2614
- wantsDedicatedSocket: true,
2615
- clientTypeUUID: "1910A70F-DBC0-4242-AF95-115DB30604E1"
2616
- }
2617
- ]
2229
+ const request = Plist4.serialize({
2230
+ streams: [{
2231
+ controlType: 2,
2232
+ channelID: uuid().toUpperCase(),
2233
+ seed,
2234
+ clientUUID: uuid().toUpperCase(),
2235
+ type: 130,
2236
+ wantsDedicatedSocket: true,
2237
+ clientTypeUUID: "1910A70F-DBC0-4242-AF95-115DB30604E1"
2238
+ }]
2618
2239
  });
2619
- const response = await this.#rtsp.setup(`/${this.rtsp.sessionId}`, Buffer.from(request), {
2240
+ const response = await this.#controlStream.setup(`/${this.#controlStream.sessionId}`, Buffer.from(request), {
2620
2241
  "Content-Type": "application/x-apple-binary-plist"
2621
2242
  });
2622
- const plist = Plist3.parse(await response.arrayBuffer());
2243
+ if (response.status !== 200) {
2244
+ this.context.logger.error("[protocol]", "Failed to setup data stream.", response.status, response.statusText, await response.text());
2245
+ throw new Error("Failed to setup data stream.");
2246
+ }
2247
+ const plist = Plist4.parse(await response.arrayBuffer());
2623
2248
  const dataPort = plist.streams[0].dataPort & 65535;
2624
- reporter4.net(`Connecting to data stream on port ${dataPort}...`);
2625
- this.#dataStream = new AirPlayDataStream(this.#rtsp.address, dataPort);
2626
- await this.#dataStream.setup(sharedSecret, seed);
2627
- await onBeforeConnect?.();
2249
+ this.context.logger.net("[protocol]", `Connecting to data stream on port ${dataPort}...`);
2250
+ this.#dataStream = new DataStream(this.context, this.#controlStream.address, dataPort);
2251
+ this.#dataStream.setup(sharedSecret, seed);
2252
+ onBeforeConnect?.();
2628
2253
  await this.#dataStream.connect();
2629
2254
  }
2630
- async setupEventStream(pairingId, sharedSecret) {
2255
+ async setupEventStream(sharedSecret, pairingId) {
2631
2256
  const body = {
2632
2257
  deviceID: pairingId.toString(),
2633
2258
  macAddress: getMacAddress().toUpperCase(),
@@ -2645,28 +2270,291 @@ class AirPlay {
2645
2270
  body.timingPort = this.#timingServer.port;
2646
2271
  body.timingProtocol = "NTP";
2647
2272
  }
2648
- const request = Plist3.serialize(body);
2649
- const response = await this.#rtsp.setup(`/${this.rtsp.sessionId}`, Buffer.from(request), {
2273
+ const request = Plist4.serialize(body);
2274
+ const response = await this.#controlStream.setup(`/${this.#controlStream.sessionId}`, Buffer.from(request), {
2650
2275
  "Content-Type": "application/x-apple-binary-plist"
2651
2276
  });
2652
2277
  if (response.status !== 200) {
2653
- reporter4.error("Cannot setup event stream", response.status, response.statusText, await response.text());
2654
- throw new Error("Cannot setup event stream.");
2278
+ this.context.logger.error("[protocol]", "Failed to setup event stream.", response.status, response.statusText, await response.text());
2279
+ throw new Error("Failed to setup event stream.");
2655
2280
  }
2656
- const plist = Plist3.parse(await response.arrayBuffer());
2281
+ const plist = Plist4.parse(await response.arrayBuffer());
2657
2282
  const eventPort = plist.eventPort & 65535;
2658
- reporter4.net(`Connecting to event stream on port ${eventPort}...`);
2659
- this.#eventStream = new AirPlayEventStream(this.#rtsp.address, eventPort);
2660
- await this.#eventStream.setup(sharedSecret);
2283
+ this.context.logger.net("[protocol]", `Connecting to event stream on port ${eventPort}...`);
2284
+ this.#eventStream = new EventStream(this.#context, this.#controlStream.address, eventPort);
2285
+ this.#eventStream.setup(sharedSecret);
2661
2286
  await this.#eventStream.connect();
2662
- await this.#rtsp.record(`/${this.rtsp.sessionId}`);
2287
+ await this.#controlStream.record(`/${this.#controlStream.sessionId}`);
2663
2288
  }
2664
- async setupTimingServer(timing) {
2665
- this.#timingServer = timing;
2289
+ useTimingServer(timingServer) {
2290
+ this.#timingServer = timingServer;
2666
2291
  }
2667
2292
  }
2293
+ // src/dataStreamMessages.ts
2294
+ var exports_dataStreamMessages = {};
2295
+ __export(exports_dataStreamMessages, {
2296
+ wakeDevice: () => wakeDevice,
2297
+ setVolumeMuted: () => setVolumeMuted,
2298
+ setVolume: () => setVolume,
2299
+ setReadyState: () => setReadyState,
2300
+ setConnectionState: () => setConnectionState,
2301
+ sendHIDEvent: () => sendHIDEvent,
2302
+ sendCommand: () => sendCommand,
2303
+ sendButtonEvent: () => sendButtonEvent,
2304
+ protocol: () => protocol,
2305
+ playbackQueueRequest: () => playbackQueueRequest,
2306
+ notification: () => notification,
2307
+ getVolumeMuted: () => getVolumeMuted,
2308
+ getVolume: () => getVolume,
2309
+ getState: () => getState,
2310
+ getExtension: () => getExtension2,
2311
+ deviceInfo: () => deviceInfo,
2312
+ configureConnection: () => configureConnection,
2313
+ clientUpdatesConfig: () => clientUpdatesConfig
2314
+ });
2315
+ import { uint16ToBE, uuid as uuid2 } from "@basmilius/apple-common";
2316
+ import { create, getExtension as _getExtension, setExtension } from "@bufbuild/protobuf";
2317
+ function protocol(type, errorCode = 0 /* NoError */) {
2318
+ return create(ProtocolMessageSchema, {
2319
+ type,
2320
+ errorCode,
2321
+ identifier: uuid2().toUpperCase(),
2322
+ uniqueIdentifier: uuid2().toUpperCase()
2323
+ });
2324
+ }
2325
+ function clientUpdatesConfig(artworkUpdates = true, nowPlayingUpdates = true, volumeUpdates = true, keyboardUpdates = false, outputDeviceUpdates = true, systemEndpointUpdates = true) {
2326
+ const protocolMessage = protocol(16 /* CLIENT_UPDATES_CONFIG_MESSAGE */);
2327
+ const message = create(ClientUpdatesConfigMessageSchema, {
2328
+ artworkUpdates,
2329
+ nowPlayingUpdates,
2330
+ volumeUpdates,
2331
+ keyboardUpdates,
2332
+ outputDeviceUpdates,
2333
+ systemEndpointUpdates
2334
+ });
2335
+ setExtension(protocolMessage, clientUpdatesConfigMessage, message);
2336
+ return [
2337
+ protocolMessage,
2338
+ clientUpdatesConfigMessage
2339
+ ];
2340
+ }
2341
+ function configureConnection(groupId) {
2342
+ const protocolMessage = protocol(120 /* CONFIGURE_CONNECTION_MESSAGE */);
2343
+ const message = create(ConfigureConnectionMessageSchema, {
2344
+ groupID: groupId
2345
+ });
2346
+ setExtension(protocolMessage, configureConnectionMessage, message);
2347
+ return [
2348
+ protocolMessage,
2349
+ configureConnectionMessage
2350
+ ];
2351
+ }
2352
+ function deviceInfo(pairingId) {
2353
+ const protocolMessage = protocol(15 /* DEVICE_INFO_MESSAGE */);
2354
+ const message = create(DeviceInfoMessageSchema, {
2355
+ uniqueIdentifier: pairingId.toString(),
2356
+ name: "iPhone van Bas",
2357
+ localizedModelName: "iPhone",
2358
+ systemBuildVersion: "18C66",
2359
+ applicationBundleIdentifier: "com.apple.TVRemote",
2360
+ applicationBundleVersion: "344.28",
2361
+ protocolVersion: 1,
2362
+ lastSupportedMessageType: 129,
2363
+ supportsSystemPairing: true,
2364
+ allowsPairing: true,
2365
+ systemMediaApplication: "com.apple.TVMusic",
2366
+ supportsACL: true,
2367
+ supportsSharedQueue: true,
2368
+ supportsExtendedMotion: true,
2369
+ sharedQueueVersion: 2,
2370
+ deviceClass: 1 /* iPhone */,
2371
+ logicalDeviceCount: 1
2372
+ });
2373
+ setExtension(protocolMessage, deviceInfoMessage, message);
2374
+ return [
2375
+ protocolMessage,
2376
+ deviceInfoMessage
2377
+ ];
2378
+ }
2379
+ function getState() {
2380
+ const protocolMessage = protocol(3 /* GET_STATE_MESSAGE */);
2381
+ const message = create(GetStateMessageSchema, {});
2382
+ setExtension(protocolMessage, getStateMessage, message);
2383
+ return [
2384
+ protocolMessage,
2385
+ getStateMessage
2386
+ ];
2387
+ }
2388
+ function getVolume(outputDeviceUID) {
2389
+ const protocolMessage = protocol(49 /* GET_VOLUME_MESSAGE */);
2390
+ const message = create(GetVolumeMessageSchema, {
2391
+ outputDeviceUID
2392
+ });
2393
+ setExtension(protocolMessage, getVolumeMessage, message);
2394
+ return [
2395
+ protocolMessage,
2396
+ getVolumeMessage
2397
+ ];
2398
+ }
2399
+ function getVolumeMuted(outputDeviceUID) {
2400
+ const protocolMessage = protocol(126 /* GET_VOLUME_MUTED_MESSAGE */);
2401
+ const message = create(GetVolumeMutedMessageSchema, {
2402
+ outputDeviceUID
2403
+ });
2404
+ setExtension(protocolMessage, getVolumeMutedMessage, message);
2405
+ return [
2406
+ protocolMessage,
2407
+ getVolumeMutedMessage
2408
+ ];
2409
+ }
2410
+ function notification(notification2) {
2411
+ const protocolMessage = protocol(11 /* NOTIFICATION_MESSAGE */);
2412
+ const message = create(NotificationMessageSchema, {
2413
+ notification: notification2
2414
+ });
2415
+ setExtension(protocolMessage, notificationMessage, message);
2416
+ return [
2417
+ protocolMessage,
2418
+ notificationMessage
2419
+ ];
2420
+ }
2421
+ function playbackQueueRequest(location, length, includeMetadata = true, includeLanguageOptions = false) {
2422
+ const protocolMessage = protocol(32 /* PLAYBACK_QUEUE_REQUEST_MESSAGE */);
2423
+ const message = create(PlaybackQueueRequestMessageSchema, {
2424
+ location,
2425
+ length,
2426
+ includeMetadata,
2427
+ includeLanguageOptions,
2428
+ artworkHeight: 600,
2429
+ artworkWidth: 600,
2430
+ includeInfo: true,
2431
+ includeLyrics: false,
2432
+ includeSections: false,
2433
+ includeAlignments: false,
2434
+ includeAvailableArtworkFormats: true,
2435
+ includeParticipants: false,
2436
+ isLegacyNowPlayingInfoRequest: false,
2437
+ returnContentItemAssetsInUserCompletion: true,
2438
+ requestedAnimatedArtworkAssetURLFormats: [
2439
+ "MRContentItemAnimatedArtworkFormatSquare",
2440
+ "MRContentItemAnimatedArtworkFormatTall"
2441
+ ]
2442
+ });
2443
+ setExtension(protocolMessage, playbackQueueRequestMessage, message);
2444
+ return [
2445
+ protocolMessage,
2446
+ playbackQueueRequestMessage
2447
+ ];
2448
+ }
2449
+ function sendButtonEvent(usagePage, usage, buttonDown) {
2450
+ const protocolMessage = protocol(39 /* SEND_BUTTON_EVENT_MESSAGE */);
2451
+ const message = create(SendButtonEventMessageSchema, {
2452
+ usagePage,
2453
+ usage,
2454
+ buttonDown
2455
+ });
2456
+ setExtension(protocolMessage, sendButtonEventMessage, message);
2457
+ return [
2458
+ protocolMessage,
2459
+ sendButtonEventMessage
2460
+ ];
2461
+ }
2462
+ function sendCommand(command, options) {
2463
+ const protocolMessage = protocol(1 /* SEND_COMMAND_MESSAGE */);
2464
+ const message = create(SendCommandMessageSchema, {
2465
+ command,
2466
+ options
2467
+ });
2468
+ setExtension(protocolMessage, sendCommandMessage, message);
2469
+ return [
2470
+ protocolMessage,
2471
+ sendCommandMessage
2472
+ ];
2473
+ }
2474
+ function sendHIDEvent(usePage, usage, down) {
2475
+ const protocolMessage = protocol(8 /* SEND_HID_EVENT_MESSAGE */);
2476
+ const message = create(SendHIDEventMessageSchema, {
2477
+ hidEventData: Buffer.concat([
2478
+ Buffer.from("438922cf08020000", "hex"),
2479
+ Buffer.from("00000000000000000100000000000000020" + "00000200000000300000001000000000000", "hex"),
2480
+ Buffer.concat([
2481
+ uint16ToBE(usePage),
2482
+ uint16ToBE(usage),
2483
+ uint16ToBE(down ? 1 : 0)
2484
+ ]),
2485
+ Buffer.from("0000000000000001000000", "hex")
2486
+ ])
2487
+ });
2488
+ setExtension(protocolMessage, sendHIDEventMessage, message);
2489
+ return [
2490
+ protocolMessage,
2491
+ sendHIDEventMessage
2492
+ ];
2493
+ }
2494
+ function setConnectionState(state = 2 /* Connected */) {
2495
+ const protocolMessage = protocol(38 /* SET_CONNECTION_STATE_MESSAGE */);
2496
+ const message = create(SetConnectionStateMessageSchema, {
2497
+ state
2498
+ });
2499
+ setExtension(protocolMessage, setConnectionStateMessage, message);
2500
+ return [
2501
+ protocolMessage,
2502
+ setConnectionStateMessage
2503
+ ];
2504
+ }
2505
+ function setReadyState() {
2506
+ const protocolMessage = protocol(36 /* SET_READY_STATE_MESSAGE */);
2507
+ const message = create(SetReadyStateMessageSchema, {});
2508
+ setExtension(protocolMessage, readyStateMessage, message);
2509
+ return [
2510
+ protocolMessage,
2511
+ readyStateMessage
2512
+ ];
2513
+ }
2514
+ function setVolume(outputDeviceUID, volume) {
2515
+ const protocolMessage = protocol(51 /* SET_VOLUME_MESSAGE */);
2516
+ const message = create(SetVolumeMessageSchema, {
2517
+ outputDeviceUID,
2518
+ volume
2519
+ });
2520
+ setExtension(protocolMessage, setVolumeMessage, message);
2521
+ return [
2522
+ protocolMessage,
2523
+ setVolumeMessage
2524
+ ];
2525
+ }
2526
+ function setVolumeMuted(outputDeviceUID, isMuted) {
2527
+ const protocolMessage = protocol(128 /* SET_VOLUME_MUTED_MESSAGE */);
2528
+ const message = create(SetVolumeMutedMessageSchema, {
2529
+ outputDeviceUID,
2530
+ isMuted
2531
+ });
2532
+ setExtension(protocolMessage, setVolumeMutedMessage, message);
2533
+ return [
2534
+ protocolMessage,
2535
+ setVolumeMutedMessage
2536
+ ];
2537
+ }
2538
+ function wakeDevice() {
2539
+ const protocolMessage = protocol(41 /* WAKE_DEVICE_MESSAGE */);
2540
+ const message = create(WakeDeviceMessageSchema, {});
2541
+ setExtension(protocolMessage, wakeDeviceMessage, message);
2542
+ return [
2543
+ protocolMessage,
2544
+ wakeDeviceMessage
2545
+ ];
2546
+ }
2547
+ function getExtension2(message, extension) {
2548
+ return _getExtension(message, extension);
2549
+ }
2668
2550
  export {
2551
+ Verify,
2552
+ Protocol,
2669
2553
  exports_proto as Proto,
2554
+ Pairing,
2555
+ EventStream,
2670
2556
  exports_dataStreamMessages as DataStreamMessage,
2671
- AirPlay
2557
+ DataStream,
2558
+ ControlStream,
2559
+ BaseStream
2672
2560
  };