1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.SiriAudioSession = exports.SiriAudioSessionEvents = exports.RemoteController = exports.RemoteControllerEvents = exports.TargetUpdates = exports.AudioCodecTypes = exports.ButtonState = exports.TargetCategory = exports.ButtonType = void 0;
|
4 | const tslib_1 = require("tslib");
|
5 | const assert_1 = tslib_1.__importDefault(require("assert"));
|
6 | const debug_1 = tslib_1.__importDefault(require("debug"));
|
7 | const events_1 = require("events");
|
8 | const Characteristic_1 = require("../Characteristic");
|
9 | const datastream_1 = require("../datastream");
|
10 | const Service_1 = require("../Service");
|
11 | const tlv = tslib_1.__importStar(require("../util/tlv"));
|
12 | const debug = (0, debug_1.default)("HAP-NodeJS:Remote:Controller");
|
13 | var TargetControlCommands;
|
14 | (function (TargetControlCommands) {
|
15 | TargetControlCommands[TargetControlCommands["MAXIMUM_TARGETS"] = 1] = "MAXIMUM_TARGETS";
|
16 | TargetControlCommands[TargetControlCommands["TICKS_PER_SECOND"] = 2] = "TICKS_PER_SECOND";
|
17 | TargetControlCommands[TargetControlCommands["SUPPORTED_BUTTON_CONFIGURATION"] = 3] = "SUPPORTED_BUTTON_CONFIGURATION";
|
18 | TargetControlCommands[TargetControlCommands["TYPE"] = 4] = "TYPE";
|
19 | })(TargetControlCommands || (TargetControlCommands = {}));
|
20 | var SupportedButtonConfigurationTypes;
|
21 | (function (SupportedButtonConfigurationTypes) {
|
22 | SupportedButtonConfigurationTypes[SupportedButtonConfigurationTypes["BUTTON_ID"] = 1] = "BUTTON_ID";
|
23 | SupportedButtonConfigurationTypes[SupportedButtonConfigurationTypes["BUTTON_TYPE"] = 2] = "BUTTON_TYPE";
|
24 | })(SupportedButtonConfigurationTypes || (SupportedButtonConfigurationTypes = {}));
|
25 |
|
26 |
|
27 |
|
28 | var ButtonType;
|
29 | (function (ButtonType) {
|
30 |
|
31 | ButtonType[ButtonType["UNDEFINED"] = 0] = "UNDEFINED";
|
32 | ButtonType[ButtonType["MENU"] = 1] = "MENU";
|
33 | ButtonType[ButtonType["PLAY_PAUSE"] = 2] = "PLAY_PAUSE";
|
34 | ButtonType[ButtonType["TV_HOME"] = 3] = "TV_HOME";
|
35 | ButtonType[ButtonType["SELECT"] = 4] = "SELECT";
|
36 | ButtonType[ButtonType["ARROW_UP"] = 5] = "ARROW_UP";
|
37 | ButtonType[ButtonType["ARROW_RIGHT"] = 6] = "ARROW_RIGHT";
|
38 | ButtonType[ButtonType["ARROW_DOWN"] = 7] = "ARROW_DOWN";
|
39 | ButtonType[ButtonType["ARROW_LEFT"] = 8] = "ARROW_LEFT";
|
40 | ButtonType[ButtonType["VOLUME_UP"] = 9] = "VOLUME_UP";
|
41 | ButtonType[ButtonType["VOLUME_DOWN"] = 10] = "VOLUME_DOWN";
|
42 | ButtonType[ButtonType["SIRI"] = 11] = "SIRI";
|
43 | ButtonType[ButtonType["POWER"] = 12] = "POWER";
|
44 | ButtonType[ButtonType["GENERIC"] = 13] = "GENERIC";
|
45 | })(ButtonType || (exports.ButtonType = ButtonType = {}));
|
46 | var TargetControlList;
|
47 | (function (TargetControlList) {
|
48 | TargetControlList[TargetControlList["OPERATION"] = 1] = "OPERATION";
|
49 | TargetControlList[TargetControlList["TARGET_CONFIGURATION"] = 2] = "TARGET_CONFIGURATION";
|
50 | })(TargetControlList || (TargetControlList = {}));
|
51 | var Operation;
|
52 | (function (Operation) {
|
53 |
|
54 | Operation[Operation["UNDEFINED"] = 0] = "UNDEFINED";
|
55 | Operation[Operation["LIST"] = 1] = "LIST";
|
56 | Operation[Operation["ADD"] = 2] = "ADD";
|
57 | Operation[Operation["REMOVE"] = 3] = "REMOVE";
|
58 | Operation[Operation["RESET"] = 4] = "RESET";
|
59 | Operation[Operation["UPDATE"] = 5] = "UPDATE";
|
60 | })(Operation || (Operation = {}));
|
61 | var TargetConfigurationTypes;
|
62 | (function (TargetConfigurationTypes) {
|
63 | TargetConfigurationTypes[TargetConfigurationTypes["TARGET_IDENTIFIER"] = 1] = "TARGET_IDENTIFIER";
|
64 | TargetConfigurationTypes[TargetConfigurationTypes["TARGET_NAME"] = 2] = "TARGET_NAME";
|
65 | TargetConfigurationTypes[TargetConfigurationTypes["TARGET_CATEGORY"] = 3] = "TARGET_CATEGORY";
|
66 | TargetConfigurationTypes[TargetConfigurationTypes["BUTTON_CONFIGURATION"] = 4] = "BUTTON_CONFIGURATION";
|
67 | })(TargetConfigurationTypes || (TargetConfigurationTypes = {}));
|
68 |
|
69 |
|
70 |
|
71 | var TargetCategory;
|
72 | (function (TargetCategory) {
|
73 |
|
74 | TargetCategory[TargetCategory["UNDEFINED"] = 0] = "UNDEFINED";
|
75 | TargetCategory[TargetCategory["APPLE_TV"] = 24] = "APPLE_TV";
|
76 | })(TargetCategory || (exports.TargetCategory = TargetCategory = {}));
|
77 | var ButtonConfigurationTypes;
|
78 | (function (ButtonConfigurationTypes) {
|
79 | ButtonConfigurationTypes[ButtonConfigurationTypes["BUTTON_ID"] = 1] = "BUTTON_ID";
|
80 | ButtonConfigurationTypes[ButtonConfigurationTypes["BUTTON_TYPE"] = 2] = "BUTTON_TYPE";
|
81 | ButtonConfigurationTypes[ButtonConfigurationTypes["BUTTON_NAME"] = 3] = "BUTTON_NAME";
|
82 | })(ButtonConfigurationTypes || (ButtonConfigurationTypes = {}));
|
83 | var ButtonEvent;
|
84 | (function (ButtonEvent) {
|
85 | ButtonEvent[ButtonEvent["BUTTON_ID"] = 1] = "BUTTON_ID";
|
86 | ButtonEvent[ButtonEvent["BUTTON_STATE"] = 2] = "BUTTON_STATE";
|
87 | ButtonEvent[ButtonEvent["TIMESTAMP"] = 3] = "TIMESTAMP";
|
88 | ButtonEvent[ButtonEvent["ACTIVE_IDENTIFIER"] = 4] = "ACTIVE_IDENTIFIER";
|
89 | })(ButtonEvent || (ButtonEvent = {}));
|
90 |
|
91 |
|
92 |
|
93 | var ButtonState;
|
94 | (function (ButtonState) {
|
95 | ButtonState[ButtonState["UP"] = 0] = "UP";
|
96 | ButtonState[ButtonState["DOWN"] = 1] = "DOWN";
|
97 | })(ButtonState || (exports.ButtonState = ButtonState = {}));
|
98 | var SelectedAudioInputStreamConfigurationTypes;
|
99 | (function (SelectedAudioInputStreamConfigurationTypes) {
|
100 | SelectedAudioInputStreamConfigurationTypes[SelectedAudioInputStreamConfigurationTypes["SELECTED_AUDIO_INPUT_STREAM_CONFIGURATION"] = 1] = "SELECTED_AUDIO_INPUT_STREAM_CONFIGURATION";
|
101 | })(SelectedAudioInputStreamConfigurationTypes || (SelectedAudioInputStreamConfigurationTypes = {}));
|
102 |
|
103 | var SupportedAudioStreamConfigurationTypes;
|
104 | (function (SupportedAudioStreamConfigurationTypes) {
|
105 |
|
106 | SupportedAudioStreamConfigurationTypes[SupportedAudioStreamConfigurationTypes["AUDIO_CODEC_CONFIGURATION"] = 1] = "AUDIO_CODEC_CONFIGURATION";
|
107 | SupportedAudioStreamConfigurationTypes[SupportedAudioStreamConfigurationTypes["COMFORT_NOISE_SUPPORT"] = 2] = "COMFORT_NOISE_SUPPORT";
|
108 | })(SupportedAudioStreamConfigurationTypes || (SupportedAudioStreamConfigurationTypes = {}));
|
109 | var AudioCodecConfigurationTypes;
|
110 | (function (AudioCodecConfigurationTypes) {
|
111 | AudioCodecConfigurationTypes[AudioCodecConfigurationTypes["CODEC_TYPE"] = 1] = "CODEC_TYPE";
|
112 | AudioCodecConfigurationTypes[AudioCodecConfigurationTypes["CODEC_PARAMETERS"] = 2] = "CODEC_PARAMETERS";
|
113 | })(AudioCodecConfigurationTypes || (AudioCodecConfigurationTypes = {}));
|
114 |
|
115 |
|
116 |
|
117 | var AudioCodecTypes;
|
118 | (function (AudioCodecTypes) {
|
119 |
|
120 | AudioCodecTypes[AudioCodecTypes["PCMU"] = 0] = "PCMU";
|
121 | AudioCodecTypes[AudioCodecTypes["PCMA"] = 1] = "PCMA";
|
122 | AudioCodecTypes[AudioCodecTypes["AAC_ELD"] = 2] = "AAC_ELD";
|
123 | AudioCodecTypes[AudioCodecTypes["OPUS"] = 3] = "OPUS";
|
124 | AudioCodecTypes[AudioCodecTypes["MSBC"] = 4] = "MSBC";
|
125 | AudioCodecTypes[AudioCodecTypes["AMR"] = 5] = "AMR";
|
126 | AudioCodecTypes[AudioCodecTypes["AMR_WB"] = 6] = "AMR_WB";
|
127 | })(AudioCodecTypes || (exports.AudioCodecTypes = AudioCodecTypes = {}));
|
128 | var AudioCodecParametersTypes;
|
129 | (function (AudioCodecParametersTypes) {
|
130 | AudioCodecParametersTypes[AudioCodecParametersTypes["CHANNEL"] = 1] = "CHANNEL";
|
131 | AudioCodecParametersTypes[AudioCodecParametersTypes["BIT_RATE"] = 2] = "BIT_RATE";
|
132 | AudioCodecParametersTypes[AudioCodecParametersTypes["SAMPLE_RATE"] = 3] = "SAMPLE_RATE";
|
133 | AudioCodecParametersTypes[AudioCodecParametersTypes["PACKET_TIME"] = 4] = "PACKET_TIME";
|
134 | })(AudioCodecParametersTypes || (AudioCodecParametersTypes = {}));
|
135 | var SiriAudioSessionState;
|
136 | (function (SiriAudioSessionState) {
|
137 | SiriAudioSessionState[SiriAudioSessionState["STARTING"] = 0] = "STARTING";
|
138 | SiriAudioSessionState[SiriAudioSessionState["SENDING"] = 1] = "SENDING";
|
139 | SiriAudioSessionState[SiriAudioSessionState["CLOSING"] = 2] = "CLOSING";
|
140 | SiriAudioSessionState[SiriAudioSessionState["CLOSED"] = 3] = "CLOSED";
|
141 | })(SiriAudioSessionState || (SiriAudioSessionState = {}));
|
142 |
|
143 |
|
144 |
|
145 | var TargetUpdates;
|
146 | (function (TargetUpdates) {
|
147 | TargetUpdates[TargetUpdates["NAME"] = 0] = "NAME";
|
148 | TargetUpdates[TargetUpdates["CATEGORY"] = 1] = "CATEGORY";
|
149 | TargetUpdates[TargetUpdates["UPDATED_BUTTONS"] = 2] = "UPDATED_BUTTONS";
|
150 | TargetUpdates[TargetUpdates["REMOVED_BUTTONS"] = 3] = "REMOVED_BUTTONS";
|
151 | })(TargetUpdates || (exports.TargetUpdates = TargetUpdates = {}));
|
152 |
|
153 |
|
154 |
|
155 | var RemoteControllerEvents;
|
156 | (function (RemoteControllerEvents) {
|
157 | |
158 |
|
159 |
|
160 |
|
161 | RemoteControllerEvents["ACTIVE_CHANGE"] = "active-change";
|
162 | |
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | RemoteControllerEvents["ACTIVE_IDENTIFIER_CHANGE"] = "active-identifier-change";
|
169 | |
170 |
|
171 |
|
172 |
|
173 | RemoteControllerEvents["TARGET_ADDED"] = "target-add";
|
174 | |
175 |
|
176 |
|
177 |
|
178 | RemoteControllerEvents["TARGET_UPDATED"] = "target-update";
|
179 | |
180 |
|
181 |
|
182 | RemoteControllerEvents["TARGET_REMOVED"] = "target-remove";
|
183 | |
184 |
|
185 |
|
186 |
|
187 |
|
188 | RemoteControllerEvents["TARGETS_RESET"] = "targets-reset";
|
189 | })(RemoteControllerEvents || (exports.RemoteControllerEvents = RemoteControllerEvents = {}));
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 | class RemoteController extends events_1.EventEmitter {
|
197 | stateChangeDelegate;
|
198 | audioSupported;
|
199 | audioProducerConstructor;
|
200 |
|
201 | audioProducerOptions;
|
202 | targetControlManagementService;
|
203 | targetControlService;
|
204 | siriService;
|
205 | audioStreamManagementService;
|
206 | dataStreamManagement;
|
207 | buttons = {};
|
208 | supportedConfiguration;
|
209 | targetConfigurations = new Map();
|
210 | targetConfigurationsString = "";
|
211 | lastButtonEvent = "";
|
212 | activeIdentifier = 0;
|
213 | activeConnection;
|
214 | activeConnectionDisconnectListener;
|
215 | supportedAudioConfiguration;
|
216 | selectedAudioConfiguration;
|
217 | selectedAudioConfigurationString;
|
218 | dataStreamConnections = new Map();
|
219 | activeAudioSession;
|
220 | nextAudioSession;
|
221 | |
222 |
|
223 |
|
224 | eventHandler;
|
225 | |
226 |
|
227 |
|
228 | requestHandler;
|
229 | |
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 | constructor(audioProducerConstructor, producerOptions) {
|
242 | super();
|
243 | this.audioSupported = audioProducerConstructor !== undefined;
|
244 | this.audioProducerConstructor = audioProducerConstructor;
|
245 | this.audioProducerOptions = producerOptions;
|
246 | const configuration = this.constructSupportedConfiguration();
|
247 | this.supportedConfiguration = this.buildTargetControlSupportedConfigurationTLV(configuration);
|
248 | const audioConfiguration = this.constructSupportedAudioConfiguration();
|
249 | this.supportedAudioConfiguration = RemoteController.buildSupportedAudioConfigurationTLV(audioConfiguration);
|
250 | this.selectedAudioConfiguration = {
|
251 | codecType: 3 ,
|
252 | parameters: {
|
253 | channels: 1,
|
254 | bitrate: 0 ,
|
255 | samplerate: 1 ,
|
256 | rtpTime: 20,
|
257 | },
|
258 | };
|
259 | this.selectedAudioConfigurationString = RemoteController.buildSelectedAudioConfigurationTLV({
|
260 | audioCodecConfiguration: this.selectedAudioConfiguration,
|
261 | });
|
262 | }
|
263 | |
264 |
|
265 |
|
266 | controllerId() {
|
267 | return "remote" ;
|
268 | }
|
269 | |
270 |
|
271 |
|
272 |
|
273 |
|
274 | setActiveIdentifier(activeIdentifier) {
|
275 | if (activeIdentifier === this.activeIdentifier) {
|
276 | return;
|
277 | }
|
278 | if (activeIdentifier !== 0 && !this.targetConfigurations.has(activeIdentifier)) {
|
279 | throw Error("Tried setting unconfigured targetIdentifier to active");
|
280 | }
|
281 | debug("%d is now the active target", activeIdentifier);
|
282 | this.activeIdentifier = activeIdentifier;
|
283 | this.targetControlService.getCharacteristic(Characteristic_1.Characteristic.ActiveIdentifier).updateValue(activeIdentifier);
|
284 | if (this.activeAudioSession) {
|
285 | this.handleSiriAudioStop();
|
286 | }
|
287 | setTimeout(() => this.emit("active-identifier-change" , activeIdentifier), 0);
|
288 | this.setInactive();
|
289 | }
|
290 | |
291 |
|
292 |
|
293 | isActive() {
|
294 | return !!this.activeConnection;
|
295 | }
|
296 | |
297 |
|
298 |
|
299 |
|
300 |
|
301 | isConfigured(targetIdentifier) {
|
302 | return this.targetConfigurations.has(targetIdentifier);
|
303 | }
|
304 | |
305 |
|
306 |
|
307 |
|
308 |
|
309 |
|
310 | getTargetIdentifierByName(name) {
|
311 | for (const [activeIdentifier, configuration] of Object.entries(this.targetConfigurations)) {
|
312 | if (configuration.targetName === name) {
|
313 | return parseInt(activeIdentifier, 10);
|
314 | }
|
315 | }
|
316 | return undefined;
|
317 | }
|
318 | |
319 |
|
320 |
|
321 |
|
322 |
|
323 | pushButton(button) {
|
324 | this.sendButtonEvent(button, 1 );
|
325 | }
|
326 | |
327 |
|
328 |
|
329 |
|
330 |
|
331 | releaseButton(button) {
|
332 | this.sendButtonEvent(button, 0 );
|
333 | }
|
334 | |
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 | pushAndReleaseButton(button, time = 200) {
|
341 | this.pushButton(button);
|
342 | setTimeout(() => this.releaseButton(button), time);
|
343 | }
|
344 |
|
345 |
|
346 | constructSupportedConfiguration() {
|
347 | const configuration = {
|
348 | maximumTargets: 10,
|
349 | ticksPerSecond: 1000,
|
350 | supportedButtonConfiguration: [],
|
351 | hardwareImplemented: this.audioSupported,
|
352 | };
|
353 | const supportedButtons = [
|
354 | 1 , 2 , 3 , 4 ,
|
355 | 5 , 6 , 7 , 8 ,
|
356 | 9 , 10 , 12 , 13 ,
|
357 | ];
|
358 | if (this.audioSupported) {
|
359 | supportedButtons.push(11 );
|
360 | }
|
361 | supportedButtons.forEach(button => {
|
362 | const buttonConfiguration = {
|
363 | buttonID: 100 + button,
|
364 | buttonType: button,
|
365 | };
|
366 | configuration.supportedButtonConfiguration.push(buttonConfiguration);
|
367 | this.buttons[button] = buttonConfiguration.buttonID;
|
368 | });
|
369 | return configuration;
|
370 | }
|
371 | constructSupportedAudioConfiguration() {
|
372 |
|
373 | return {
|
374 | audioCodecConfiguration: {
|
375 | codecType: 3 ,
|
376 | parameters: {
|
377 | channels: 1,
|
378 | bitrate: 0 ,
|
379 | samplerate: 1 ,
|
380 | },
|
381 | },
|
382 | };
|
383 | }
|
384 |
|
385 |
|
386 | handleTargetControlWrite(value, callback) {
|
387 | const data = Buffer.from(value, "base64");
|
388 | const objects = tlv.decode(data);
|
389 | const operation = objects[1 ][0];
|
390 | let targetConfiguration = undefined;
|
391 | if (objects[2 ]) {
|
392 | targetConfiguration = this.parseTargetConfigurationTLV(objects[2 ]);
|
393 | }
|
394 | debug("Received TargetControl write operation %s", Operation[operation]);
|
395 | let handler;
|
396 | switch (operation) {
|
397 | case Operation.ADD:
|
398 | handler = this.handleAddTarget.bind(this);
|
399 | break;
|
400 | case Operation.UPDATE:
|
401 | handler = this.handleUpdateTarget.bind(this);
|
402 | break;
|
403 | case Operation.REMOVE:
|
404 | handler = this.handleRemoveTarget.bind(this);
|
405 | break;
|
406 | case Operation.RESET:
|
407 | handler = this.handleResetTargets.bind(this);
|
408 | break;
|
409 | case Operation.LIST:
|
410 | handler = this.handleListTargets.bind(this);
|
411 | break;
|
412 | default:
|
413 | callback(-70410 , undefined);
|
414 | return;
|
415 | }
|
416 | const status = handler(targetConfiguration);
|
417 | if (status === 0 ) {
|
418 | callback(undefined, this.targetConfigurationsString);
|
419 | if (operation === Operation.ADD && this.activeIdentifier === 0) {
|
420 | this.setActiveIdentifier(targetConfiguration.targetIdentifier);
|
421 | }
|
422 | }
|
423 | else {
|
424 | callback(new Error(status + ""));
|
425 | }
|
426 | }
|
427 | handleAddTarget(targetConfiguration) {
|
428 | if (!targetConfiguration) {
|
429 | return -70410 ;
|
430 | }
|
431 | this.targetConfigurations.set(targetConfiguration.targetIdentifier, targetConfiguration);
|
432 | debug("Configured new target '" + targetConfiguration.targetName + "' with targetIdentifier '" + targetConfiguration.targetIdentifier + "'");
|
433 | setTimeout(() => this.emit("target-add" , targetConfiguration), 0);
|
434 | this.updatedTargetConfiguration();
|
435 | return 0 ;
|
436 | }
|
437 | handleUpdateTarget(targetConfiguration) {
|
438 | if (!targetConfiguration) {
|
439 | return -70410 ;
|
440 | }
|
441 | const updates = [];
|
442 | const configuredTarget = this.targetConfigurations.get(targetConfiguration.targetIdentifier);
|
443 | if (!configuredTarget) {
|
444 | return -70410 ;
|
445 | }
|
446 | if (targetConfiguration.targetName) {
|
447 | debug("Target name was updated '%s' => '%s' (%d)", configuredTarget.targetName, targetConfiguration.targetName, configuredTarget.targetIdentifier);
|
448 | configuredTarget.targetName = targetConfiguration.targetName;
|
449 | updates.push(0 );
|
450 | }
|
451 | if (targetConfiguration.targetCategory) {
|
452 | debug("Target category was updated '%d' => '%d' for target '%s' (%d)", configuredTarget.targetCategory, targetConfiguration.targetCategory, configuredTarget.targetName, configuredTarget.targetIdentifier);
|
453 | configuredTarget.targetCategory = targetConfiguration.targetCategory;
|
454 | updates.push(1 );
|
455 | }
|
456 | if (targetConfiguration.buttonConfiguration) {
|
457 | debug("%d button configurations were updated for target '%s' (%d)", Object.keys(targetConfiguration.buttonConfiguration).length, configuredTarget.targetName, configuredTarget.targetIdentifier);
|
458 | for (const configuration of Object.values(targetConfiguration.buttonConfiguration)) {
|
459 | const savedConfiguration = configuredTarget.buttonConfiguration[configuration.buttonID];
|
460 | savedConfiguration.buttonType = configuration.buttonType;
|
461 | savedConfiguration.buttonName = configuration.buttonName;
|
462 | }
|
463 | updates.push(2 );
|
464 | }
|
465 | setTimeout(() => this.emit("target-update" , targetConfiguration, updates), 0);
|
466 | this.updatedTargetConfiguration();
|
467 | return 0 ;
|
468 | }
|
469 | handleRemoveTarget(targetConfiguration) {
|
470 | if (!targetConfiguration) {
|
471 | return -70410 ;
|
472 | }
|
473 | const configuredTarget = this.targetConfigurations.get(targetConfiguration.targetIdentifier);
|
474 | if (!configuredTarget) {
|
475 | return -70410 ;
|
476 | }
|
477 | if (targetConfiguration.buttonConfiguration) {
|
478 | for (const key in targetConfiguration.buttonConfiguration) {
|
479 | if (Object.prototype.hasOwnProperty.call(targetConfiguration.buttonConfiguration, key)) {
|
480 | delete configuredTarget.buttonConfiguration[key];
|
481 | }
|
482 | }
|
483 | debug("Removed %d button configurations of target '%s' (%d)", Object.keys(targetConfiguration.buttonConfiguration).length, configuredTarget.targetName, configuredTarget.targetIdentifier);
|
484 | setTimeout(() => this.emit("target-update" , configuredTarget, [3 ]), 0);
|
485 | }
|
486 | else {
|
487 | this.targetConfigurations.delete(targetConfiguration.targetIdentifier);
|
488 | debug("Target '%s' (%d) was removed", configuredTarget.targetName, configuredTarget.targetIdentifier);
|
489 | setTimeout(() => this.emit("target-remove" , targetConfiguration.targetIdentifier), 0);
|
490 | const keys = Object.keys(this.targetConfigurations);
|
491 | this.setActiveIdentifier(keys.length === 0 ? 0 : parseInt(keys[0], 10));
|
492 | }
|
493 | this.updatedTargetConfiguration();
|
494 | return 0 ;
|
495 | }
|
496 | handleResetTargets(targetConfiguration) {
|
497 | if (targetConfiguration) {
|
498 | return -70410 ;
|
499 | }
|
500 | debug("Resetting all target configurations");
|
501 | this.targetConfigurations = new Map();
|
502 | this.updatedTargetConfiguration();
|
503 | setTimeout(() => this.emit("targets-reset" ), 0);
|
504 | this.setActiveIdentifier(0);
|
505 | return 0 ;
|
506 | }
|
507 | handleListTargets(targetConfiguration) {
|
508 | if (targetConfiguration) {
|
509 | return -70410 ;
|
510 | }
|
511 |
|
512 | debug("Returning " + Object.keys(this.targetConfigurations).length + " target configurations");
|
513 | return 0 ;
|
514 | }
|
515 | handleActiveWrite(value, callback, connection) {
|
516 | if (this.activeIdentifier === 0) {
|
517 | debug("Tried to change active state. There is no active target set though");
|
518 | callback(-70410 );
|
519 | return;
|
520 | }
|
521 | if (this.activeConnection) {
|
522 | this.activeConnection.removeListener("closed" , this.activeConnectionDisconnectListener);
|
523 | this.activeConnection = undefined;
|
524 | this.activeConnectionDisconnectListener = undefined;
|
525 | }
|
526 | this.activeConnection = value ? connection : undefined;
|
527 | if (this.activeConnection) {
|
528 | this.activeConnectionDisconnectListener = this.handleActiveSessionDisconnected.bind(this, this.activeConnection);
|
529 | this.activeConnection.on("closed" , this.activeConnectionDisconnectListener);
|
530 | }
|
531 | const activeTarget = this.targetConfigurations.get(this.activeIdentifier);
|
532 | if (!activeTarget) {
|
533 | callback(-70410 );
|
534 | return;
|
535 | }
|
536 | debug("Remote with activeTarget '%s' (%d) was set to %s", activeTarget.targetName, this.activeIdentifier, value ? "ACTIVE" : "INACTIVE");
|
537 | callback();
|
538 | this.emit("active-change" , value);
|
539 | }
|
540 | setInactive() {
|
541 | if (this.activeConnection === undefined) {
|
542 | return;
|
543 | }
|
544 | this.activeConnection.removeListener("closed" , this.activeConnectionDisconnectListener);
|
545 | this.activeConnection = undefined;
|
546 | this.activeConnectionDisconnectListener = undefined;
|
547 | this.targetControlService.getCharacteristic(Characteristic_1.Characteristic.Active).updateValue(false);
|
548 | debug("Remote was set to INACTIVE");
|
549 | setTimeout(() => this.emit("active-change" , false), 0);
|
550 | }
|
551 | handleActiveSessionDisconnected(connection) {
|
552 | if (connection !== this.activeConnection) {
|
553 | return;
|
554 | }
|
555 | debug("Active hap session disconnected!");
|
556 | this.setInactive();
|
557 | }
|
558 | sendButtonEvent(button, buttonState) {
|
559 | const buttonID = this.buttons[button];
|
560 | if (buttonID === undefined || buttonID === 0) {
|
561 | throw new Error("Tried sending button event for unsupported button (" + button + ")");
|
562 | }
|
563 | if (this.activeIdentifier === 0) {
|
564 | throw new Error("Tried sending button event although no target was selected");
|
565 | }
|
566 | if (!this.isActive()) {
|
567 | throw new Error("Tried sending button event although target was not marked as active");
|
568 | }
|
569 | if (button === 11 && this.audioSupported) {
|
570 | if (buttonState === 1 ) {
|
571 | this.handleSiriAudioStart();
|
572 | }
|
573 | else if (buttonState === 0 ) {
|
574 | this.handleSiriAudioStop();
|
575 | }
|
576 | return;
|
577 | }
|
578 | const buttonIdTlv = tlv.encode(1 , buttonID);
|
579 | const buttonStateTlv = tlv.encode(2 , buttonState);
|
580 | const timestampTlv = tlv.encode(3 , tlv.writeVariableUIntLE(new Date().getTime()));
|
581 | const activeIdentifierTlv = tlv.encode(4 , tlv.writeUInt32(this.activeIdentifier));
|
582 | this.lastButtonEvent = Buffer.concat([
|
583 | buttonIdTlv, buttonStateTlv, timestampTlv, activeIdentifierTlv,
|
584 | ]).toString("base64");
|
585 | this.targetControlService.getCharacteristic(Characteristic_1.Characteristic.ButtonEvent).sendEventNotification(this.lastButtonEvent);
|
586 | }
|
587 | parseTargetConfigurationTLV(data) {
|
588 | const configTLV = tlv.decode(data);
|
589 | const identifier = tlv.readUInt32(configTLV[1 ]);
|
590 | let name = undefined;
|
591 | if (configTLV[2 ]) {
|
592 | name = configTLV[2 ].toString();
|
593 | }
|
594 | let category = undefined;
|
595 | if (configTLV[3 ]) {
|
596 | category = tlv.readUInt16(configTLV[3 ]);
|
597 | }
|
598 | const buttonConfiguration = {};
|
599 | if (configTLV[4 ]) {
|
600 | const buttonConfigurationTLV = tlv.decodeList(configTLV[4 ], 1 );
|
601 | buttonConfigurationTLV.forEach(entry => {
|
602 | const buttonId = entry[1 ][0];
|
603 | const buttonType = tlv.readUInt16(entry[2 ]);
|
604 | let buttonName;
|
605 | if (entry[3 ]) {
|
606 | buttonName = entry[3 ].toString();
|
607 | }
|
608 | else {
|
609 |
|
610 | buttonName = ButtonType[buttonType];
|
611 | }
|
612 | buttonConfiguration[buttonId] = {
|
613 | buttonID: buttonId,
|
614 | buttonType: buttonType,
|
615 | buttonName: buttonName,
|
616 | };
|
617 | });
|
618 | }
|
619 | return {
|
620 | targetIdentifier: identifier,
|
621 | targetName: name,
|
622 | targetCategory: category,
|
623 | buttonConfiguration: buttonConfiguration,
|
624 | };
|
625 | }
|
626 | updatedTargetConfiguration() {
|
627 | const bufferList = [];
|
628 | for (const configuration of Object.values(this.targetConfigurations)) {
|
629 | const targetIdentifier = tlv.encode(1 , tlv.writeUInt32(configuration.targetIdentifier));
|
630 | const targetName = tlv.encode(2 , configuration.targetName);
|
631 | const targetCategory = tlv.encode(3 , tlv.writeUInt16(configuration.targetCategory));
|
632 | const buttonConfigurationBuffers = [];
|
633 | for (const value of configuration.buttonConfiguration.values()) {
|
634 | let tlvBuffer = tlv.encode(1 , value.buttonID, 2 , tlv.writeUInt16(value.buttonType));
|
635 | if (value.buttonName) {
|
636 | tlvBuffer = Buffer.concat([
|
637 | tlvBuffer,
|
638 | tlv.encode(3 , value.buttonName),
|
639 | ]);
|
640 | }
|
641 | buttonConfigurationBuffers.push(tlvBuffer);
|
642 | }
|
643 | const buttonConfiguration = tlv.encode(4 , Buffer.concat(buttonConfigurationBuffers));
|
644 | const targetConfiguration = Buffer.concat([targetIdentifier, targetName, targetCategory, buttonConfiguration]);
|
645 | bufferList.push(tlv.encode(2 , targetConfiguration));
|
646 | }
|
647 | this.targetConfigurationsString = Buffer.concat(bufferList).toString("base64");
|
648 | this.stateChangeDelegate?.();
|
649 | }
|
650 | buildTargetControlSupportedConfigurationTLV(configuration) {
|
651 | const maximumTargets = tlv.encode(1 , configuration.maximumTargets);
|
652 | const ticksPerSecond = tlv.encode(2 , tlv.writeVariableUIntLE(configuration.ticksPerSecond));
|
653 | const supportedButtonConfigurationBuffers = [];
|
654 | configuration.supportedButtonConfiguration.forEach(value => {
|
655 | const tlvBuffer = tlv.encode(1 , value.buttonID, 2 , tlv.writeUInt16(value.buttonType));
|
656 | supportedButtonConfigurationBuffers.push(tlvBuffer);
|
657 | });
|
658 | const supportedButtonConfiguration = tlv.encode(3 , Buffer.concat(supportedButtonConfigurationBuffers));
|
659 | const type = tlv.encode(4 , configuration.hardwareImplemented ? 1 : 0);
|
660 | return Buffer.concat([maximumTargets, ticksPerSecond, supportedButtonConfiguration, type]).toString("base64");
|
661 | }
|
662 |
|
663 |
|
664 | handleTargetControlWhoAmI(connection, message) {
|
665 | const targetIdentifier = message.identifier;
|
666 | this.dataStreamConnections.set(targetIdentifier, connection);
|
667 | debug("Discovered HDS connection for targetIdentifier %s", targetIdentifier);
|
668 | connection.addProtocolHandler("dataSend" , this);
|
669 | }
|
670 | handleSiriAudioStart() {
|
671 | if (!this.audioSupported) {
|
672 | throw new Error("Cannot start siri stream on remote where siri is not supported");
|
673 | }
|
674 | if (!this.isActive()) {
|
675 | debug("Tried opening Siri audio stream, however no controller is connected!");
|
676 | return;
|
677 | }
|
678 | if (this.activeAudioSession && (!this.activeAudioSession.isClosing() || this.nextAudioSession)) {
|
679 |
|
680 |
|
681 | debug("Tried opening Siri audio stream, however there is already one in progress");
|
682 | return;
|
683 | }
|
684 | const connection = this.dataStreamConnections.get(this.activeIdentifier);
|
685 | if (connection === undefined) {
|
686 | debug("Tried opening Siri audio stream however target is not connected via HDS");
|
687 | return;
|
688 | }
|
689 |
|
690 | const audioSession = new SiriAudioSession(connection, this.selectedAudioConfiguration, this.audioProducerConstructor, this.audioProducerOptions);
|
691 | if (!this.activeAudioSession) {
|
692 | this.activeAudioSession = audioSession;
|
693 | }
|
694 | else {
|
695 |
|
696 |
|
697 | this.nextAudioSession = audioSession;
|
698 | }
|
699 | audioSession.on("close" , this.handleSiriAudioSessionClosed.bind(this, audioSession));
|
700 | audioSession.start();
|
701 | }
|
702 | handleSiriAudioStop() {
|
703 | if (this.activeAudioSession) {
|
704 | if (!this.activeAudioSession.isClosing()) {
|
705 | this.activeAudioSession.stop();
|
706 | return;
|
707 | }
|
708 | else if (this.nextAudioSession && !this.nextAudioSession.isClosing()) {
|
709 | this.nextAudioSession.stop();
|
710 | return;
|
711 | }
|
712 | }
|
713 | debug("handleSiriAudioStop called although no audio session was started");
|
714 | }
|
715 |
|
716 | handleDataSendAckEvent(message) {
|
717 | const streamId = message.streamId;
|
718 | const endOfStream = message.endOfStream;
|
719 | if (this.activeAudioSession && this.activeAudioSession.streamId === streamId) {
|
720 | this.activeAudioSession.handleDataSendAckEvent(endOfStream);
|
721 | }
|
722 | else if (this.nextAudioSession && this.nextAudioSession.streamId === streamId) {
|
723 | this.nextAudioSession.handleDataSendAckEvent(endOfStream);
|
724 | }
|
725 | else {
|
726 | debug("Received dataSend acknowledgment event for unknown streamId '%s'", streamId);
|
727 | }
|
728 | }
|
729 |
|
730 | handleDataSendCloseEvent(message) {
|
731 | const streamId = message.streamId;
|
732 | const reason = message.reason;
|
733 | if (this.activeAudioSession && this.activeAudioSession.streamId === streamId) {
|
734 | this.activeAudioSession.handleDataSendCloseEvent(reason);
|
735 | }
|
736 | else if (this.nextAudioSession && this.nextAudioSession.streamId === streamId) {
|
737 | this.nextAudioSession.handleDataSendCloseEvent(reason);
|
738 | }
|
739 | else {
|
740 | debug("Received dataSend close event for unknown streamId '%s'", streamId);
|
741 | }
|
742 | }
|
743 | handleSiriAudioSessionClosed(session) {
|
744 | if (session === this.activeAudioSession) {
|
745 | this.activeAudioSession = this.nextAudioSession;
|
746 | this.nextAudioSession = undefined;
|
747 | }
|
748 | else if (session === this.nextAudioSession) {
|
749 | this.nextAudioSession = undefined;
|
750 | }
|
751 | }
|
752 | handleDataStreamConnectionClosed(connection) {
|
753 | for (const [targetIdentifier, connection0] of this.dataStreamConnections) {
|
754 | if (connection === connection0) {
|
755 | debug("HDS connection disconnected for targetIdentifier %s", targetIdentifier);
|
756 | this.dataStreamConnections.delete(targetIdentifier);
|
757 | break;
|
758 | }
|
759 | }
|
760 | }
|
761 |
|
762 |
|
763 | handleSelectedAudioConfigurationWrite(value, callback) {
|
764 | const data = Buffer.from(value, "base64");
|
765 | const objects = tlv.decode(data);
|
766 | const selectedAudioStreamConfiguration = tlv.decode(objects[1 ]);
|
767 | const codec = selectedAudioStreamConfiguration[1 ][0];
|
768 | const parameters = tlv.decode(selectedAudioStreamConfiguration[2 ]);
|
769 | const channels = parameters[1 ][0];
|
770 | const bitrate = parameters[2 ][0];
|
771 | const samplerate = parameters[3 ][0];
|
772 | this.selectedAudioConfiguration = {
|
773 | codecType: codec,
|
774 | parameters: {
|
775 | channels: channels,
|
776 | bitrate: bitrate,
|
777 | samplerate: samplerate,
|
778 | rtpTime: 20,
|
779 | },
|
780 | };
|
781 | this.selectedAudioConfigurationString = RemoteController.buildSelectedAudioConfigurationTLV({
|
782 | audioCodecConfiguration: this.selectedAudioConfiguration,
|
783 | });
|
784 | callback();
|
785 | }
|
786 | static buildSupportedAudioConfigurationTLV(configuration) {
|
787 | const codecConfigurationTLV = RemoteController.buildCodecConfigurationTLV(configuration.audioCodecConfiguration);
|
788 | const supportedAudioStreamConfiguration = tlv.encode(1 , codecConfigurationTLV);
|
789 | return supportedAudioStreamConfiguration.toString("base64");
|
790 | }
|
791 | static buildSelectedAudioConfigurationTLV(configuration) {
|
792 | const codecConfigurationTLV = RemoteController.buildCodecConfigurationTLV(configuration.audioCodecConfiguration);
|
793 | const supportedAudioStreamConfiguration = tlv.encode(1 , codecConfigurationTLV);
|
794 | return supportedAudioStreamConfiguration.toString("base64");
|
795 | }
|
796 | static buildCodecConfigurationTLV(codecConfiguration) {
|
797 | const parameters = codecConfiguration.parameters;
|
798 | let parametersTLV = tlv.encode(1 , parameters.channels, 2 , parameters.bitrate, 3 , parameters.samplerate);
|
799 | if (parameters.rtpTime) {
|
800 | parametersTLV = Buffer.concat([
|
801 | parametersTLV,
|
802 | tlv.encode(4 , parameters.rtpTime),
|
803 | ]);
|
804 | }
|
805 | return tlv.encode(1 , codecConfiguration.codecType, 2 , parametersTLV);
|
806 | }
|
807 |
|
808 | |
809 |
|
810 |
|
811 | constructServices() {
|
812 | this.targetControlManagementService = new Service_1.Service.TargetControlManagement("", "");
|
813 | this.targetControlManagementService.setCharacteristic(Characteristic_1.Characteristic.TargetControlSupportedConfiguration, this.supportedConfiguration);
|
814 | this.targetControlManagementService.setCharacteristic(Characteristic_1.Characteristic.TargetControlList, this.targetConfigurationsString);
|
815 | this.targetControlManagementService.setPrimaryService();
|
816 |
|
817 |
|
818 | this.targetControlService = new Service_1.Service.TargetControl("", "");
|
819 | this.targetControlService.setCharacteristic(Characteristic_1.Characteristic.ActiveIdentifier, 0);
|
820 | this.targetControlService.setCharacteristic(Characteristic_1.Characteristic.Active, false);
|
821 | this.targetControlService.setCharacteristic(Characteristic_1.Characteristic.ButtonEvent, this.lastButtonEvent);
|
822 | if (this.audioSupported) {
|
823 | this.siriService = new Service_1.Service.Siri("", "");
|
824 | this.siriService.setCharacteristic(Characteristic_1.Characteristic.SiriInputType, Characteristic_1.Characteristic.SiriInputType.PUSH_BUTTON_TRIGGERED_APPLE_TV);
|
825 | this.audioStreamManagementService = new Service_1.Service.AudioStreamManagement("", "");
|
826 | this.audioStreamManagementService.setCharacteristic(Characteristic_1.Characteristic.SupportedAudioStreamConfiguration, this.supportedAudioConfiguration);
|
827 | this.audioStreamManagementService.setCharacteristic(Characteristic_1.Characteristic.SelectedAudioStreamConfiguration, this.selectedAudioConfigurationString);
|
828 | this.dataStreamManagement = new datastream_1.DataStreamManagement();
|
829 | this.siriService.addLinkedService(this.dataStreamManagement.getService());
|
830 | this.siriService.addLinkedService(this.audioStreamManagementService);
|
831 | }
|
832 | return {
|
833 | targetControlManagement: this.targetControlManagementService,
|
834 | targetControl: this.targetControlService,
|
835 | siri: this.siriService,
|
836 | audioStreamManagement: this.audioStreamManagementService,
|
837 | dataStreamTransportManagement: this.dataStreamManagement?.getService(),
|
838 | };
|
839 | }
|
840 | |
841 |
|
842 |
|
843 | initWithServices(serviceMap) {
|
844 | this.targetControlManagementService = serviceMap.targetControlManagement;
|
845 | this.targetControlService = serviceMap.targetControl;
|
846 | this.siriService = serviceMap.siri;
|
847 | this.audioStreamManagementService = serviceMap.audioStreamManagement;
|
848 | this.dataStreamManagement = new datastream_1.DataStreamManagement(serviceMap.dataStreamTransportManagement);
|
849 | }
|
850 | |
851 |
|
852 |
|
853 | configureServices() {
|
854 | if (!this.targetControlManagementService || !this.targetControlService) {
|
855 | throw new Error("Unexpected state: Services not configured!");
|
856 | }
|
857 | this.targetControlManagementService.getCharacteristic(Characteristic_1.Characteristic.TargetControlList)
|
858 | .on("get" , callback => {
|
859 | callback(null, this.targetConfigurationsString);
|
860 | })
|
861 | .on("set" , this.handleTargetControlWrite.bind(this));
|
862 | this.targetControlService.getCharacteristic(Characteristic_1.Characteristic.ActiveIdentifier)
|
863 | .on("get" , callback => {
|
864 | callback(undefined, this.activeIdentifier);
|
865 | });
|
866 | this.targetControlService.getCharacteristic(Characteristic_1.Characteristic.Active)
|
867 | .on("get" , callback => {
|
868 | callback(undefined, this.isActive());
|
869 | })
|
870 | .on("set" , (value, callback, context, connection) => {
|
871 | if (!connection) {
|
872 | debug("Set event handler for Remote.Active cannot be called from plugin. Connection undefined!");
|
873 | callback(-70410 );
|
874 | return;
|
875 | }
|
876 | this.handleActiveWrite(value, callback, connection);
|
877 | });
|
878 | this.targetControlService.getCharacteristic(Characteristic_1.Characteristic.ButtonEvent)
|
879 | .on("get" , (callback) => {
|
880 | callback(undefined, this.lastButtonEvent);
|
881 | });
|
882 | if (this.audioSupported) {
|
883 | this.audioStreamManagementService.getCharacteristic(Characteristic_1.Characteristic.SelectedAudioStreamConfiguration)
|
884 | .on("get" , callback => {
|
885 | callback(null, this.selectedAudioConfigurationString);
|
886 | })
|
887 | .on("set" , this.handleSelectedAudioConfigurationWrite.bind(this))
|
888 | .updateValue(this.selectedAudioConfigurationString);
|
889 | this.dataStreamManagement
|
890 | .onEventMessage("targetControl" , "whoami" , this.handleTargetControlWhoAmI.bind(this))
|
891 | .onServerEvent("connection-closed" , this.handleDataStreamConnectionClosed.bind(this));
|
892 | this.eventHandler = {
|
893 | ["ack" ]: this.handleDataSendAckEvent.bind(this),
|
894 | ["close" ]: this.handleDataSendCloseEvent.bind(this),
|
895 | };
|
896 | }
|
897 | }
|
898 | |
899 |
|
900 |
|
901 | handleControllerRemoved() {
|
902 | this.targetControlManagementService = undefined;
|
903 | this.targetControlService = undefined;
|
904 | this.siriService = undefined;
|
905 | this.audioStreamManagementService = undefined;
|
906 | this.eventHandler = undefined;
|
907 | this.requestHandler = undefined;
|
908 | this.dataStreamManagement?.destroy();
|
909 | this.dataStreamManagement = undefined;
|
910 |
|
911 |
|
912 |
|
913 |
|
914 | }
|
915 | |
916 |
|
917 |
|
918 | handleFactoryReset() {
|
919 | debug("Running factory reset. Resetting targets...");
|
920 | this.handleResetTargets(undefined);
|
921 | this.lastButtonEvent = "";
|
922 | }
|
923 | |
924 |
|
925 |
|
926 | serialize() {
|
927 | if (!this.activeIdentifier && Object.keys(this.targetConfigurations).length === 0) {
|
928 | return undefined;
|
929 | }
|
930 | return {
|
931 | activeIdentifier: this.activeIdentifier,
|
932 | targetConfigurations: [...this.targetConfigurations].reduce((obj, [key, value]) => {
|
933 | obj[key] = value;
|
934 | return obj;
|
935 | }, {}),
|
936 | };
|
937 | }
|
938 | |
939 |
|
940 |
|
941 | deserialize(serialized) {
|
942 | this.activeIdentifier = serialized.activeIdentifier;
|
943 | this.targetConfigurations = Object.entries(serialized.targetConfigurations).reduce((map, [key, value]) => {
|
944 | const identifier = parseInt(key, 10);
|
945 | map.set(identifier, value);
|
946 | return map;
|
947 | }, new Map());
|
948 | this.updatedTargetConfiguration();
|
949 | }
|
950 | |
951 |
|
952 |
|
953 | setupStateChangeDelegate(delegate) {
|
954 | this.stateChangeDelegate = delegate;
|
955 | }
|
956 | }
|
957 | exports.RemoteController = RemoteController;
|
958 |
|
959 |
|
960 |
|
961 | var SiriAudioSessionEvents;
|
962 | (function (SiriAudioSessionEvents) {
|
963 | SiriAudioSessionEvents["CLOSE"] = "close";
|
964 | })(SiriAudioSessionEvents || (exports.SiriAudioSessionEvents = SiriAudioSessionEvents = {}));
|
965 |
|
966 |
|
967 |
|
968 |
|
969 |
|
970 | class SiriAudioSession extends events_1.EventEmitter {
|
971 | connection;
|
972 | selectedAudioConfiguration;
|
973 | producer;
|
974 | producerRunning = false;
|
975 | producerTimer;
|
976 | |
977 |
|
978 |
|
979 | state = 0 ;
|
980 | streamId;
|
981 | endOfStream = false;
|
982 | audioFrameQueue = [];
|
983 | maxQueueSize = 1024;
|
984 | sequenceNumber = 0;
|
985 | closeListener;
|
986 | constructor(connection, selectedAudioConfiguration, producerConstructor,
|
987 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/no-explicit-any
|
988 | producerOptions) {
|
989 | super();
|
990 | this.connection = connection;
|
991 | this.selectedAudioConfiguration = selectedAudioConfiguration;
|
992 | this.producer = new producerConstructor(this.handleSiriAudioFrame.bind(this), this.handleProducerError.bind(this), producerOptions);
|
993 | this.connection.on("closed" , this.closeListener = this.handleDataStreamConnectionClosed.bind(this));
|
994 | }
|
995 | |
996 |
|
997 |
|
998 | start() {
|
999 | debug("Sending request to start siri audio stream");
|
1000 |
|
1001 | this.connection.sendRequest("dataSend" , "open" , {
|
1002 | target: "controller",
|
1003 | type: "audio.siri",
|
1004 | }, (error, status, message) => {
|
1005 | if (this.state === 3 ) {
|
1006 | debug("Ignoring dataSend open response as the session is already closed");
|
1007 | return;
|
1008 | }
|
1009 | assert_1.default.strictEqual(this.state, 0 );
|
1010 | this.state = 1 ;
|
1011 | if (error || status) {
|
1012 | if (error) {
|
1013 | debug("Error occurred trying to start siri audio stream: %s", error.message);
|
1014 | }
|
1015 | else if (status) {
|
1016 | debug("Controller responded with non-zero status code: %s", datastream_1.HDSStatus[status]);
|
1017 | }
|
1018 | this.closed();
|
1019 | }
|
1020 | else {
|
1021 | this.streamId = message.streamId;
|
1022 | if (!this.producerRunning) {
|
1023 | this.sendDataSendCloseEvent(3 );
|
1024 | }
|
1025 | else {
|
1026 | debug("Successfully setup siri audio stream with streamId %d", this.streamId);
|
1027 | }
|
1028 | }
|
1029 | });
|
1030 | this.startAudioProducer();
|
1031 | }
|
1032 | |
1033 |
|
1034 |
|
1035 | isClosing() {
|
1036 | return this.state >= 2 ;
|
1037 | }
|
1038 | |
1039 |
|
1040 |
|
1041 | stop() {
|
1042 | (0, assert_1.default)(this.state <= 1 , "state was higher than SENDING");
|
1043 | debug("Stopping siri audio stream with streamId %d", this.streamId);
|
1044 | this.endOfStream = true;
|
1045 | this.stopAudioProducer();
|
1046 | if (this.state === 1 ) {
|
1047 | this.handleSiriAudioFrame(undefined);
|
1048 | this.state = 2 ;
|
1049 | }
|
1050 | else {
|
1051 | this.closed();
|
1052 | }
|
1053 | }
|
1054 | startAudioProducer() {
|
1055 | this.producer.startAudioProduction(this.selectedAudioConfiguration);
|
1056 | this.producerRunning = true;
|
1057 | this.producerTimer = setTimeout(() => {
|
1058 | debug("Didn't receive any frames from audio producer for stream with streamId %s. Canceling the stream now.", this.streamId);
|
1059 | this.producerTimer = undefined;
|
1060 | this.handleProducerError(3 );
|
1061 | }, 3000);
|
1062 | this.producerTimer.unref();
|
1063 | }
|
1064 | stopAudioProducer() {
|
1065 | this.producer.stopAudioProduction();
|
1066 | this.producerRunning = false;
|
1067 | if (this.producerTimer) {
|
1068 | clearTimeout(this.producerTimer);
|
1069 | this.producerTimer = undefined;
|
1070 | }
|
1071 | }
|
1072 | handleSiriAudioFrame(frame) {
|
1073 | if (this.state >= 2 ) {
|
1074 | return;
|
1075 | }
|
1076 | if (this.producerTimer) {
|
1077 | clearTimeout(this.producerTimer);
|
1078 | this.producerTimer = undefined;
|
1079 | }
|
1080 | if (frame && this.audioFrameQueue.length < this.maxQueueSize) {
|
1081 | this.audioFrameQueue.push(frame);
|
1082 | }
|
1083 | if (this.state !== 1 ) {
|
1084 | return;
|
1085 | }
|
1086 | let queued;
|
1087 | while ((queued = this.popSome()) !== null) {
|
1088 | const packets = [];
|
1089 | queued.forEach(frame => {
|
1090 | const packetData = {
|
1091 | data: frame.data,
|
1092 | metadata: {
|
1093 | rms: new datastream_1.Float32(frame.rms),
|
1094 | sequenceNumber: new datastream_1.Int64(this.sequenceNumber++),
|
1095 | },
|
1096 | };
|
1097 | packets.push(packetData);
|
1098 | });
|
1099 | const message = {
|
1100 | packets: packets,
|
1101 | streamId: new datastream_1.Int64(this.streamId),
|
1102 | endOfStream: this.endOfStream,
|
1103 | };
|
1104 | try {
|
1105 | this.connection.sendEvent("dataSend" , "data" , message);
|
1106 | }
|
1107 | catch (error) {
|
1108 | debug("Error occurred when trying to send audio frame of hds connection: %s", error.message);
|
1109 | this.stopAudioProducer();
|
1110 | this.closed();
|
1111 | }
|
1112 | if (this.endOfStream) {
|
1113 | break;
|
1114 | }
|
1115 | }
|
1116 | }
|
1117 | handleProducerError(error) {
|
1118 | if (this.state >= 2 ) {
|
1119 | return;
|
1120 | }
|
1121 | this.stopAudioProducer();
|
1122 | if (this.state === 1 ) {
|
1123 | this.sendDataSendCloseEvent(error);
|
1124 | }
|
1125 | }
|
1126 | handleDataSendAckEvent(endOfStream) {
|
1127 | assert_1.default.strictEqual(endOfStream, true);
|
1128 | debug("Received acknowledgment for siri audio stream with streamId %s, closing it now", this.streamId);
|
1129 | this.sendDataSendCloseEvent(0 );
|
1130 | }
|
1131 | handleDataSendCloseEvent(reason) {
|
1132 |
|
1133 | debug("Received close event from controller with reason %s for stream with streamId %s", datastream_1.HDSProtocolSpecificErrorReason[reason], this.streamId);
|
1134 | if (this.state <= 1 ) {
|
1135 | this.stopAudioProducer();
|
1136 | }
|
1137 | this.closed();
|
1138 | }
|
1139 | sendDataSendCloseEvent(reason) {
|
1140 | (0, assert_1.default)(this.state >= 1 , "state was less than SENDING");
|
1141 | (0, assert_1.default)(this.state <= 2 , "state was higher than CLOSING");
|
1142 | this.connection.sendEvent("dataSend" , "close" , {
|
1143 | streamId: new datastream_1.Int64(this.streamId),
|
1144 | reason: new datastream_1.Int64(reason),
|
1145 | });
|
1146 | this.closed();
|
1147 | }
|
1148 | handleDataStreamConnectionClosed() {
|
1149 | debug("Closing audio session with streamId %d", this.streamId);
|
1150 | if (this.state <= 1 ) {
|
1151 | this.stopAudioProducer();
|
1152 | }
|
1153 | this.closed();
|
1154 | }
|
1155 | closed() {
|
1156 | const lastState = this.state;
|
1157 | this.state = 3 ;
|
1158 | if (lastState !== 3 ) {
|
1159 | this.emit("close" );
|
1160 | this.connection.removeListener("closed" , this.closeListener);
|
1161 | }
|
1162 | this.removeAllListeners();
|
1163 | }
|
1164 | popSome() {
|
1165 | if (this.audioFrameQueue.length < 5 && !this.endOfStream) {
|
1166 | return null;
|
1167 | }
|
1168 | const size = Math.min(this.audioFrameQueue.length, 5);
|
1169 | const result = [];
|
1170 | for (let i = 0; i < size; i++) {
|
1171 | const element = this.audioFrameQueue.shift();
|
1172 | result.push(element);
|
1173 | }
|
1174 | return result;
|
1175 | }
|
1176 | }
|
1177 | exports.SiriAudioSession = SiriAudioSession;
|
1178 |
|
\ | No newline at end of file |