homebridge-plugin-wrapper
Version:
Wrapper for Homebridge and NodeJS-HAP with reduced dependencies that allows to intercept plugin values and also send to them
923 lines • 64.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SiriAudioSession = exports.SiriAudioSessionEvents = exports.HomeKitRemoteController = exports.RemoteController = exports.RemoteControllerEvents = exports.TargetUpdates = exports.AudioSamplerate = exports.AudioBitrate = exports.AudioCodecTypes = exports.ButtonState = exports.TargetCategory = exports.ButtonType = void 0;
var tslib_1 = require("tslib");
var assert_1 = (0, tslib_1.__importDefault)(require("assert"));
var debug_1 = (0, tslib_1.__importDefault)(require("debug"));
var events_1 = require("events");
var Characteristic_1 = require("../Characteristic");
var datastream_1 = require("../datastream");
var Service_1 = require("../Service");
var tlv = (0, tslib_1.__importStar)(require("../util/tlv"));
var debug = (0, debug_1.default)("HAP-NodeJS:Remote:Controller");
var TargetControlCommands;
(function (TargetControlCommands) {
TargetControlCommands[TargetControlCommands["MAXIMUM_TARGETS"] = 1] = "MAXIMUM_TARGETS";
TargetControlCommands[TargetControlCommands["TICKS_PER_SECOND"] = 2] = "TICKS_PER_SECOND";
TargetControlCommands[TargetControlCommands["SUPPORTED_BUTTON_CONFIGURATION"] = 3] = "SUPPORTED_BUTTON_CONFIGURATION";
TargetControlCommands[TargetControlCommands["TYPE"] = 4] = "TYPE";
})(TargetControlCommands || (TargetControlCommands = {}));
var SupportedButtonConfigurationTypes;
(function (SupportedButtonConfigurationTypes) {
SupportedButtonConfigurationTypes[SupportedButtonConfigurationTypes["BUTTON_ID"] = 1] = "BUTTON_ID";
SupportedButtonConfigurationTypes[SupportedButtonConfigurationTypes["BUTTON_TYPE"] = 2] = "BUTTON_TYPE";
})(SupportedButtonConfigurationTypes || (SupportedButtonConfigurationTypes = {}));
var ButtonType;
(function (ButtonType) {
// noinspection JSUnusedGlobalSymbols
ButtonType[ButtonType["UNDEFINED"] = 0] = "UNDEFINED";
ButtonType[ButtonType["MENU"] = 1] = "MENU";
ButtonType[ButtonType["PLAY_PAUSE"] = 2] = "PLAY_PAUSE";
ButtonType[ButtonType["TV_HOME"] = 3] = "TV_HOME";
ButtonType[ButtonType["SELECT"] = 4] = "SELECT";
ButtonType[ButtonType["ARROW_UP"] = 5] = "ARROW_UP";
ButtonType[ButtonType["ARROW_RIGHT"] = 6] = "ARROW_RIGHT";
ButtonType[ButtonType["ARROW_DOWN"] = 7] = "ARROW_DOWN";
ButtonType[ButtonType["ARROW_LEFT"] = 8] = "ARROW_LEFT";
ButtonType[ButtonType["VOLUME_UP"] = 9] = "VOLUME_UP";
ButtonType[ButtonType["VOLUME_DOWN"] = 10] = "VOLUME_DOWN";
ButtonType[ButtonType["SIRI"] = 11] = "SIRI";
ButtonType[ButtonType["POWER"] = 12] = "POWER";
ButtonType[ButtonType["GENERIC"] = 13] = "GENERIC";
})(ButtonType = exports.ButtonType || (exports.ButtonType = {}));
var TargetControlList;
(function (TargetControlList) {
TargetControlList[TargetControlList["OPERATION"] = 1] = "OPERATION";
TargetControlList[TargetControlList["TARGET_CONFIGURATION"] = 2] = "TARGET_CONFIGURATION";
})(TargetControlList || (TargetControlList = {}));
var Operation;
(function (Operation) {
// noinspection JSUnusedGlobalSymbols
Operation[Operation["UNDEFINED"] = 0] = "UNDEFINED";
Operation[Operation["LIST"] = 1] = "LIST";
Operation[Operation["ADD"] = 2] = "ADD";
Operation[Operation["REMOVE"] = 3] = "REMOVE";
Operation[Operation["RESET"] = 4] = "RESET";
Operation[Operation["UPDATE"] = 5] = "UPDATE";
})(Operation || (Operation = {}));
var TargetConfigurationTypes;
(function (TargetConfigurationTypes) {
TargetConfigurationTypes[TargetConfigurationTypes["TARGET_IDENTIFIER"] = 1] = "TARGET_IDENTIFIER";
TargetConfigurationTypes[TargetConfigurationTypes["TARGET_NAME"] = 2] = "TARGET_NAME";
TargetConfigurationTypes[TargetConfigurationTypes["TARGET_CATEGORY"] = 3] = "TARGET_CATEGORY";
TargetConfigurationTypes[TargetConfigurationTypes["BUTTON_CONFIGURATION"] = 4] = "BUTTON_CONFIGURATION";
})(TargetConfigurationTypes || (TargetConfigurationTypes = {}));
var TargetCategory;
(function (TargetCategory) {
// noinspection JSUnusedGlobalSymbols
TargetCategory[TargetCategory["UNDEFINED"] = 0] = "UNDEFINED";
TargetCategory[TargetCategory["APPLE_TV"] = 24] = "APPLE_TV";
})(TargetCategory = exports.TargetCategory || (exports.TargetCategory = {}));
var ButtonConfigurationTypes;
(function (ButtonConfigurationTypes) {
ButtonConfigurationTypes[ButtonConfigurationTypes["BUTTON_ID"] = 1] = "BUTTON_ID";
ButtonConfigurationTypes[ButtonConfigurationTypes["BUTTON_TYPE"] = 2] = "BUTTON_TYPE";
ButtonConfigurationTypes[ButtonConfigurationTypes["BUTTON_NAME"] = 3] = "BUTTON_NAME";
})(ButtonConfigurationTypes || (ButtonConfigurationTypes = {}));
var ButtonEvent;
(function (ButtonEvent) {
ButtonEvent[ButtonEvent["BUTTON_ID"] = 1] = "BUTTON_ID";
ButtonEvent[ButtonEvent["BUTTON_STATE"] = 2] = "BUTTON_STATE";
ButtonEvent[ButtonEvent["TIMESTAMP"] = 3] = "TIMESTAMP";
ButtonEvent[ButtonEvent["ACTIVE_IDENTIFIER"] = 4] = "ACTIVE_IDENTIFIER";
})(ButtonEvent || (ButtonEvent = {}));
var ButtonState;
(function (ButtonState) {
ButtonState[ButtonState["UP"] = 0] = "UP";
ButtonState[ButtonState["DOWN"] = 1] = "DOWN";
})(ButtonState = exports.ButtonState || (exports.ButtonState = {}));
var SelectedAudioInputStreamConfigurationTypes;
(function (SelectedAudioInputStreamConfigurationTypes) {
SelectedAudioInputStreamConfigurationTypes[SelectedAudioInputStreamConfigurationTypes["SELECTED_AUDIO_INPUT_STREAM_CONFIGURATION"] = 1] = "SELECTED_AUDIO_INPUT_STREAM_CONFIGURATION";
})(SelectedAudioInputStreamConfigurationTypes || (SelectedAudioInputStreamConfigurationTypes = {}));
// ----------
var SupportedAudioStreamConfigurationTypes;
(function (SupportedAudioStreamConfigurationTypes) {
// noinspection JSUnusedGlobalSymbols
SupportedAudioStreamConfigurationTypes[SupportedAudioStreamConfigurationTypes["AUDIO_CODEC_CONFIGURATION"] = 1] = "AUDIO_CODEC_CONFIGURATION";
SupportedAudioStreamConfigurationTypes[SupportedAudioStreamConfigurationTypes["COMFORT_NOISE_SUPPORT"] = 2] = "COMFORT_NOISE_SUPPORT";
})(SupportedAudioStreamConfigurationTypes || (SupportedAudioStreamConfigurationTypes = {}));
var AudioCodecConfigurationTypes;
(function (AudioCodecConfigurationTypes) {
AudioCodecConfigurationTypes[AudioCodecConfigurationTypes["CODEC_TYPE"] = 1] = "CODEC_TYPE";
AudioCodecConfigurationTypes[AudioCodecConfigurationTypes["CODEC_PARAMETERS"] = 2] = "CODEC_PARAMETERS";
})(AudioCodecConfigurationTypes || (AudioCodecConfigurationTypes = {}));
var AudioCodecTypes;
(function (AudioCodecTypes) {
// noinspection JSUnusedGlobalSymbols
AudioCodecTypes[AudioCodecTypes["PCMU"] = 0] = "PCMU";
AudioCodecTypes[AudioCodecTypes["PCMA"] = 1] = "PCMA";
AudioCodecTypes[AudioCodecTypes["AAC_ELD"] = 2] = "AAC_ELD";
AudioCodecTypes[AudioCodecTypes["OPUS"] = 3] = "OPUS";
AudioCodecTypes[AudioCodecTypes["MSBC"] = 4] = "MSBC";
AudioCodecTypes[AudioCodecTypes["AMR"] = 5] = "AMR";
AudioCodecTypes[AudioCodecTypes["AMR_WB"] = 6] = "AMR_WB";
})(AudioCodecTypes = exports.AudioCodecTypes || (exports.AudioCodecTypes = {}));
var AudioCodecParametersTypes;
(function (AudioCodecParametersTypes) {
AudioCodecParametersTypes[AudioCodecParametersTypes["CHANNEL"] = 1] = "CHANNEL";
AudioCodecParametersTypes[AudioCodecParametersTypes["BIT_RATE"] = 2] = "BIT_RATE";
AudioCodecParametersTypes[AudioCodecParametersTypes["SAMPLE_RATE"] = 3] = "SAMPLE_RATE";
AudioCodecParametersTypes[AudioCodecParametersTypes["PACKET_TIME"] = 4] = "PACKET_TIME"; // only present in selected audio codec parameters tlv
})(AudioCodecParametersTypes || (AudioCodecParametersTypes = {}));
var AudioBitrate;
(function (AudioBitrate) {
AudioBitrate[AudioBitrate["VARIABLE"] = 0] = "VARIABLE";
AudioBitrate[AudioBitrate["CONSTANT"] = 1] = "CONSTANT";
})(AudioBitrate = exports.AudioBitrate || (exports.AudioBitrate = {}));
var AudioSamplerate;
(function (AudioSamplerate) {
AudioSamplerate[AudioSamplerate["KHZ_8"] = 0] = "KHZ_8";
AudioSamplerate[AudioSamplerate["KHZ_16"] = 1] = "KHZ_16";
AudioSamplerate[AudioSamplerate["KHZ_24"] = 2] = "KHZ_24";
// 3, 4, 5 are theoretically defined, but no idea to what kHz value they correspond to
// probably KHZ_32, KHZ_44_1, KHZ_48 (as supported by Secure Video recordings)
})(AudioSamplerate = exports.AudioSamplerate || (exports.AudioSamplerate = {}));
var SiriAudioSessionState;
(function (SiriAudioSessionState) {
SiriAudioSessionState[SiriAudioSessionState["STARTING"] = 0] = "STARTING";
SiriAudioSessionState[SiriAudioSessionState["SENDING"] = 1] = "SENDING";
SiriAudioSessionState[SiriAudioSessionState["CLOSING"] = 2] = "CLOSING";
SiriAudioSessionState[SiriAudioSessionState["CLOSED"] = 3] = "CLOSED";
})(SiriAudioSessionState || (SiriAudioSessionState = {}));
var TargetUpdates;
(function (TargetUpdates) {
TargetUpdates[TargetUpdates["NAME"] = 0] = "NAME";
TargetUpdates[TargetUpdates["CATEGORY"] = 1] = "CATEGORY";
TargetUpdates[TargetUpdates["UPDATED_BUTTONS"] = 2] = "UPDATED_BUTTONS";
TargetUpdates[TargetUpdates["REMOVED_BUTTONS"] = 3] = "REMOVED_BUTTONS";
})(TargetUpdates = exports.TargetUpdates || (exports.TargetUpdates = {}));
var RemoteControllerEvents;
(function (RemoteControllerEvents) {
/**
* This event is emitted when the active state of the remote has changed.
* active = true indicates that there is currently an apple tv listening of button presses and audio streams.
*/
RemoteControllerEvents["ACTIVE_CHANGE"] = "active-change";
/**
* This event is emitted when the currently selected target has changed.
* Possible reasons for a changed active identifier: manual change via api call, first target configuration
* gets added, active target gets removed, accessory gets unpaired, reset request was sent.
* An activeIdentifier of 0 indicates that no target is selected.
*/
RemoteControllerEvents["ACTIVE_IDENTIFIER_CHANGE"] = "active-identifier-change";
/**
* This event is emitted when a new target configuration is received. As we currently do not persistently store
* configured targets, this will be called at every startup for every Apple TV configured in the home.
*/
RemoteControllerEvents["TARGET_ADDED"] = "target-add";
/**
* This event is emitted when a existing target was updated.
* The 'updates' array indicates what exactly was changed for the target.
*/
RemoteControllerEvents["TARGET_UPDATED"] = "target-update";
/**
* This event is emitted when a existing configuration for a target was removed.
*/
RemoteControllerEvents["TARGET_REMOVED"] = "target-remove";
/**
* This event is emitted when a reset of the target configuration is requested.
* With this event every configuration made should be reset. This event is also called
* when the accessory gets unpaired.
*/
RemoteControllerEvents["TARGETS_RESET"] = "targets-reset";
})(RemoteControllerEvents = exports.RemoteControllerEvents || (exports.RemoteControllerEvents = {}));
/**
* Handles everything needed to implement a fully working HomeKit remote controller.
*/
var RemoteController = /** @class */ (function (_super) {
(0, tslib_1.__extends)(RemoteController, _super);
/**
* Creates a new RemoteController.
* If siri voice input is supported the constructor to an SiriAudioStreamProducer needs to be supplied.
* Otherwise a remote without voice support will be created.
*
* For every audio session a new SiriAudioStreamProducer will be constructed.
*
* @param audioProducerConstructor {SiriAudioStreamProducerConstructor} - constructor for a SiriAudioStreamProducer
* @param producerOptions - if supplied this argument will be supplied as third argument of the SiriAudioStreamProducer
* constructor. This should be used to supply configurations to the stream producer.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types
function RemoteController(audioProducerConstructor, producerOptions) {
var _this = _super.call(this) || this;
_this.buttons = {}; // internal mapping of buttonId to buttonType for supported buttons
_this.targetConfigurations = new Map();
_this.targetConfigurationsString = "";
_this.lastButtonEvent = "";
_this.activeIdentifier = 0; // id of 0 means no device selected
_this.dataStreamConnections = new Map(); // maps targetIdentifiers to active data stream connections
_this.audioSupported = audioProducerConstructor !== undefined;
_this.audioProducerConstructor = audioProducerConstructor;
_this.audioProducerOptions = producerOptions;
var configuration = _this.constructSupportedConfiguration();
_this.supportedConfiguration = _this.buildTargetControlSupportedConfigurationTLV(configuration);
var audioConfiguration = _this.constructSupportedAudioConfiguration();
_this.supportedAudioConfiguration = RemoteController.buildSupportedAudioConfigurationTLV(audioConfiguration);
_this.selectedAudioConfiguration = {
codecType: 3 /* OPUS */,
parameters: {
channels: 1,
bitrate: 0 /* VARIABLE */,
samplerate: 1 /* KHZ_16 */,
rtpTime: 20,
},
};
_this.selectedAudioConfigurationString = RemoteController.buildSelectedAudioConfigurationTLV({
audioCodecConfiguration: _this.selectedAudioConfiguration,
});
return _this;
}
/**
* @private
*/
RemoteController.prototype.controllerId = function () {
return "remote" /* REMOTE */;
};
/**
* Set a new target as active target. A value of 0 indicates that no target is selected currently.
*
* @param activeIdentifier {number} - target identifier
*/
RemoteController.prototype.setActiveIdentifier = function (activeIdentifier) {
var _this = this;
if (activeIdentifier === this.activeIdentifier) {
return;
}
if (activeIdentifier !== 0 && !this.targetConfigurations.has(activeIdentifier)) {
throw Error("Tried setting unconfigured targetIdentifier to active");
}
debug("%d is now the active target", activeIdentifier);
this.activeIdentifier = activeIdentifier;
this.targetControlService.getCharacteristic(Characteristic_1.Characteristic.ActiveIdentifier).updateValue(activeIdentifier);
if (this.activeAudioSession) {
this.handleSiriAudioStop();
}
setTimeout(function () { return _this.emit("active-identifier-change" /* ACTIVE_IDENTIFIER_CHANGE */, activeIdentifier); }, 0);
this.setInactive();
};
/**
* @returns if the current target is active, meaning the active device is listening for button events or audio sessions
*/
RemoteController.prototype.isActive = function () {
return !!this.activeConnection;
};
/**
* Checks if the supplied targetIdentifier is configured.
*
* @param targetIdentifier {number}
*/
RemoteController.prototype.isConfigured = function (targetIdentifier) {
return this.targetConfigurations.has(targetIdentifier);
};
/**
* Returns the targetIdentifier for a give device name
*
* @param name {string} - the name of the device
* @returns the targetIdentifier of the device or undefined if not existent
*/
RemoteController.prototype.getTargetIdentifierByName = function (name) {
var e_1, _a;
try {
for (var _b = (0, tslib_1.__values)(Object.entries(this.targetConfigurations)), _c = _b.next(); !_c.done; _c = _b.next()) {
var _d = (0, tslib_1.__read)(_c.value, 2), activeIdentifier = _d[0], configuration = _d[1];
if (configuration.targetName === name) {
return parseInt(activeIdentifier, 10);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
return undefined;
};
/**
* Sends a button event to press the supplied button.
*
* @param button {ButtonType} - button to be pressed
*/
RemoteController.prototype.pushButton = function (button) {
this.sendButtonEvent(button, 1 /* DOWN */);
};
/**
* Sends a button event that the supplied button was released.
*
* @param button {ButtonType} - button which was released
*/
RemoteController.prototype.releaseButton = function (button) {
this.sendButtonEvent(button, 0 /* UP */);
};
/**
* Presses a supplied button for a given time.
*
* @param button {ButtonType} - button to be pressed and released
* @param time {number} - time in milliseconds (defaults to 200ms)
*/
RemoteController.prototype.pushAndReleaseButton = function (button, time) {
var _this = this;
if (time === void 0) { time = 200; }
this.pushButton(button);
setTimeout(function () { return _this.releaseButton(button); }, time);
};
/**
* This method adds and configures the remote services for a give accessory.
*
* @param accessory {Accessory} - the give accessory this remote should be added to
* @deprecated - use {@link Accessory.configureController} instead
*/
RemoteController.prototype.addServicesToAccessory = function (accessory) {
accessory.configureController(this);
};
// ---------------------------------- CONFIGURATION ----------------------------------
// override methods if you would like to change anything (but should not be necessary most likely)
RemoteController.prototype.constructSupportedConfiguration = function () {
var _this = this;
var configuration = {
maximumTargets: 10,
ticksPerSecond: 1000,
supportedButtonConfiguration: [],
hardwareImplemented: this.audioSupported, // siri is only allowed for hardware implemented remotes
};
var supportedButtons = [
1 /* MENU */, 2 /* PLAY_PAUSE */, 3 /* TV_HOME */, 4 /* SELECT */,
5 /* ARROW_UP */, 6 /* ARROW_RIGHT */, 7 /* ARROW_DOWN */, 8 /* ARROW_LEFT */,
9 /* VOLUME_UP */, 10 /* VOLUME_DOWN */, 12 /* POWER */, 13 /* GENERIC */,
];
if (this.audioSupported) { // add siri button if this remote supports it
supportedButtons.push(11 /* SIRI */);
}
supportedButtons.forEach(function (button) {
var buttonConfiguration = {
buttonID: 100 + button,
buttonType: button,
};
configuration.supportedButtonConfiguration.push(buttonConfiguration);
_this.buttons[button] = buttonConfiguration.buttonID; // also saving mapping of type to id locally
});
return configuration;
};
RemoteController.prototype.constructSupportedAudioConfiguration = function () {
// the following parameters are expected from HomeKit for a remote
return {
audioCodecConfiguration: {
codecType: 3 /* OPUS */,
parameters: {
channels: 1,
bitrate: 0 /* VARIABLE */,
samplerate: 1 /* KHZ_16 */,
},
},
};
};
// --------------------------------- TARGET CONTROL ----------------------------------
// eslint-disable-next-line @typescript-eslint/no-explicit-any
RemoteController.prototype.handleTargetControlWrite = function (value, callback) {
var data = Buffer.from(value, "base64");
var objects = tlv.decode(data);
var operation = objects[1 /* OPERATION */][0];
var targetConfiguration = undefined;
if (objects[2 /* TARGET_CONFIGURATION */]) { // if target configuration was sent, parse it
targetConfiguration = this.parseTargetConfigurationTLV(objects[2 /* TARGET_CONFIGURATION */]);
}
debug("Received TargetControl write operation %s", Operation[operation]);
var handler;
switch (operation) {
case Operation.ADD:
handler = this.handleAddTarget.bind(this);
break;
case Operation.UPDATE:
handler = this.handleUpdateTarget.bind(this);
break;
case Operation.REMOVE:
handler = this.handleRemoveTarget.bind(this);
break;
case Operation.RESET:
handler = this.handleResetTargets.bind(this);
break;
case Operation.LIST:
handler = this.handleListTargets.bind(this);
break;
default:
callback(-70410 /* INVALID_VALUE_IN_REQUEST */, undefined);
return;
}
var status = handler(targetConfiguration);
if (status === 0 /* SUCCESS */) {
callback(undefined, this.targetConfigurationsString); // passing value for write response
if (operation === Operation.ADD && this.activeIdentifier === 0) {
this.setActiveIdentifier(targetConfiguration.targetIdentifier);
}
}
else {
callback(new Error(status + ""));
}
};
RemoteController.prototype.handleAddTarget = function (targetConfiguration) {
var _this = this;
if (!targetConfiguration) {
return -70410 /* INVALID_VALUE_IN_REQUEST */;
}
this.targetConfigurations.set(targetConfiguration.targetIdentifier, targetConfiguration);
debug("Configured new target '" + targetConfiguration.targetName + "' with targetIdentifier '" + targetConfiguration.targetIdentifier + "'");
setTimeout(function () { return _this.emit("target-add" /* TARGET_ADDED */, targetConfiguration); }, 0);
this.updatedTargetConfiguration(); // set response
return 0 /* SUCCESS */;
};
RemoteController.prototype.handleUpdateTarget = function (targetConfiguration) {
var e_2, _a;
var _this = this;
if (!targetConfiguration) {
return -70410 /* INVALID_VALUE_IN_REQUEST */;
}
var updates = [];
var configuredTarget = this.targetConfigurations.get(targetConfiguration.targetIdentifier);
if (!configuredTarget) {
return -70410 /* INVALID_VALUE_IN_REQUEST */;
}
if (targetConfiguration.targetName) {
debug("Target name was updated '%s' => '%s' (%d)", configuredTarget.targetName, targetConfiguration.targetName, configuredTarget.targetIdentifier);
configuredTarget.targetName = targetConfiguration.targetName;
updates.push(0 /* NAME */);
}
if (targetConfiguration.targetCategory) {
debug("Target category was updated '%d' => '%d' for target '%s' (%d)", configuredTarget.targetCategory, targetConfiguration.targetCategory, configuredTarget.targetName, configuredTarget.targetIdentifier);
configuredTarget.targetCategory = targetConfiguration.targetCategory;
updates.push(1 /* CATEGORY */);
}
if (targetConfiguration.buttonConfiguration) {
debug("%d button configurations were updated for target '%s' (%d)", Object.keys(targetConfiguration.buttonConfiguration).length, configuredTarget.targetName, configuredTarget.targetIdentifier);
try {
for (var _b = (0, tslib_1.__values)(Object.values(targetConfiguration.buttonConfiguration)), _c = _b.next(); !_c.done; _c = _b.next()) {
var configuration = _c.value;
var savedConfiguration = configuredTarget.buttonConfiguration[configuration.buttonID];
savedConfiguration.buttonType = configuration.buttonType;
savedConfiguration.buttonName = configuration.buttonName;
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_2) throw e_2.error; }
}
updates.push(2 /* UPDATED_BUTTONS */);
}
setTimeout(function () { return _this.emit("target-update" /* TARGET_UPDATED */, targetConfiguration, updates); }, 0);
this.updatedTargetConfiguration(); // set response
return 0 /* SUCCESS */;
};
RemoteController.prototype.handleRemoveTarget = function (targetConfiguration) {
var _this = this;
if (!targetConfiguration) {
return -70410 /* INVALID_VALUE_IN_REQUEST */;
}
var configuredTarget = this.targetConfigurations.get(targetConfiguration.targetIdentifier);
if (!configuredTarget) {
return -70410 /* INVALID_VALUE_IN_REQUEST */;
}
if (targetConfiguration.buttonConfiguration) {
for (var key in targetConfiguration.buttonConfiguration) {
if (Object.prototype.hasOwnProperty.call(targetConfiguration.buttonConfiguration, key)) {
delete configuredTarget.buttonConfiguration[key];
}
}
debug("Removed %d button configurations of target '%s' (%d)", Object.keys(targetConfiguration.buttonConfiguration).length, configuredTarget.targetName, configuredTarget.targetIdentifier);
setTimeout(function () { return _this.emit("target-update" /* TARGET_UPDATED */, configuredTarget, [3 /* REMOVED_BUTTONS */]); }, 0);
}
else {
this.targetConfigurations.delete(targetConfiguration.targetIdentifier);
debug("Target '%s' (%d) was removed", configuredTarget.targetName, configuredTarget.targetIdentifier);
setTimeout(function () { return _this.emit("target-remove" /* TARGET_REMOVED */, targetConfiguration.targetIdentifier); }, 0);
var keys = Object.keys(this.targetConfigurations);
this.setActiveIdentifier(keys.length === 0 ? 0 : parseInt(keys[0], 10)); // switch to next available remote
}
this.updatedTargetConfiguration(); // set response
return 0 /* SUCCESS */;
};
RemoteController.prototype.handleResetTargets = function (targetConfiguration) {
var _this = this;
if (targetConfiguration) {
return -70410 /* INVALID_VALUE_IN_REQUEST */;
}
debug("Resetting all target configurations");
this.targetConfigurations = new Map();
this.updatedTargetConfiguration(); // set response
setTimeout(function () { return _this.emit("targets-reset" /* TARGETS_RESET */); }, 0);
this.setActiveIdentifier(0); // resetting active identifier (also sets active to false)
return 0 /* SUCCESS */;
};
RemoteController.prototype.handleListTargets = function (targetConfiguration) {
if (targetConfiguration) {
return -70410 /* INVALID_VALUE_IN_REQUEST */;
}
// this.targetConfigurationsString is updated after each change, so we basically don't need to do anything here
debug("Returning " + Object.keys(this.targetConfigurations).length + " target configurations");
return 0 /* SUCCESS */;
};
RemoteController.prototype.handleActiveWrite = function (value, callback, connection) {
if (this.activeIdentifier === 0) {
debug("Tried to change active state. There is no active target set though");
callback(-70410 /* INVALID_VALUE_IN_REQUEST */);
return;
}
if (this.activeConnection) {
this.activeConnection.removeListener("closed" /* CLOSED */, this.activeConnectionDisconnectListener);
this.activeConnection = undefined;
this.activeConnectionDisconnectListener = undefined;
}
this.activeConnection = value ? connection : undefined;
if (this.activeConnection) { // register listener when hap connection disconnects
this.activeConnectionDisconnectListener = this.handleActiveSessionDisconnected.bind(this, this.activeConnection);
this.activeConnection.on("closed" /* CLOSED */, this.activeConnectionDisconnectListener);
}
var activeTarget = this.targetConfigurations.get(this.activeIdentifier);
if (!activeTarget) {
callback(-70410 /* INVALID_VALUE_IN_REQUEST */);
return;
}
debug("Remote with activeTarget '%s' (%d) was set to %s", activeTarget.targetName, this.activeIdentifier, value ? "ACTIVE" : "INACTIVE");
callback();
this.emit("active-change" /* ACTIVE_CHANGE */, value);
};
RemoteController.prototype.setInactive = function () {
var _this = this;
if (this.activeConnection === undefined) {
return;
}
this.activeConnection.removeListener("closed" /* CLOSED */, this.activeConnectionDisconnectListener);
this.activeConnection = undefined;
this.activeConnectionDisconnectListener = undefined;
this.targetControlService.getCharacteristic(Characteristic_1.Characteristic.Active).updateValue(false);
debug("Remote was set to INACTIVE");
setTimeout(function () { return _this.emit("active-change" /* ACTIVE_CHANGE */, false); }, 0);
};
RemoteController.prototype.handleActiveSessionDisconnected = function (connection) {
if (connection !== this.activeConnection) {
return;
}
debug("Active hap session disconnected!");
this.setInactive();
};
RemoteController.prototype.sendButtonEvent = function (button, buttonState) {
var buttonID = this.buttons[button];
if (buttonID === undefined || buttonID === 0) {
throw new Error("Tried sending button event for unsupported button (" + button + ")");
}
if (this.activeIdentifier === 0) { // cannot press button if no device is selected
throw new Error("Tried sending button event although no target was selected");
}
if (!this.isActive()) { // cannot press button if device is not active (aka no apple tv is listening)
throw new Error("Tried sending button event although target was not marked as active");
}
if (button === 11 /* SIRI */ && this.audioSupported) {
if (buttonState === 1 /* DOWN */) { // start streaming session
this.handleSiriAudioStart();
}
else if (buttonState === 0 /* UP */) { // stop streaming session
this.handleSiriAudioStop();
}
return;
}
var buttonIdTlv = tlv.encode(1 /* BUTTON_ID */, buttonID);
var buttonStateTlv = tlv.encode(2 /* BUTTON_STATE */, buttonState);
var timestampTlv = tlv.encode(3 /* TIMESTAMP */, tlv.writeUInt64(new Date().getTime()));
var activeIdentifierTlv = tlv.encode(4 /* ACTIVE_IDENTIFIER */, tlv.writeUInt32(this.activeIdentifier));
this.lastButtonEvent = Buffer.concat([
buttonIdTlv, buttonStateTlv, timestampTlv, activeIdentifierTlv,
]).toString("base64");
this.targetControlService.getCharacteristic(Characteristic_1.Characteristic.ButtonEvent).sendEventNotification(this.lastButtonEvent);
};
RemoteController.prototype.parseTargetConfigurationTLV = function (data) {
var configTLV = tlv.decode(data);
var identifier = tlv.readUInt32(configTLV[1 /* TARGET_IDENTIFIER */]);
var name = undefined;
if (configTLV[2 /* TARGET_NAME */]) {
name = configTLV[2 /* TARGET_NAME */].toString();
}
var category = undefined;
if (configTLV[3 /* TARGET_CATEGORY */]) {
category = tlv.readUInt16(configTLV[3 /* TARGET_CATEGORY */]);
}
var buttonConfiguration = {};
if (configTLV[4 /* BUTTON_CONFIGURATION */]) {
var buttonConfigurationTLV = tlv.decodeList(configTLV[4 /* BUTTON_CONFIGURATION */], 1 /* BUTTON_ID */);
buttonConfigurationTLV.forEach(function (entry) {
var buttonId = entry[1 /* BUTTON_ID */][0];
var buttonType = tlv.readUInt16(entry[2 /* BUTTON_TYPE */]);
var buttonName;
if (entry[3 /* BUTTON_NAME */]) {
buttonName = entry[3 /* BUTTON_NAME */].toString();
}
else {
// @ts-expect-error: forceConsistentCasingInFileNames compiler option
buttonName = ButtonType[buttonType];
}
buttonConfiguration[buttonId] = {
buttonID: buttonId,
buttonType: buttonType,
buttonName: buttonName,
};
});
}
return {
targetIdentifier: identifier,
targetName: name,
targetCategory: category,
buttonConfiguration: buttonConfiguration,
};
};
RemoteController.prototype.updatedTargetConfiguration = function () {
var e_3, _a, e_4, _b;
var _c;
var bufferList = [];
try {
for (var _d = (0, tslib_1.__values)(Object.values(this.targetConfigurations)), _e = _d.next(); !_e.done; _e = _d.next()) {
var configuration = _e.value;
var targetIdentifier = tlv.encode(1 /* TARGET_IDENTIFIER */, tlv.writeUInt32(configuration.targetIdentifier));
var targetName = tlv.encode(2 /* TARGET_NAME */, configuration.targetName);
var targetCategory = tlv.encode(3 /* TARGET_CATEGORY */, tlv.writeUInt16(configuration.targetCategory));
var buttonConfigurationBuffers = [];
try {
for (var _f = (e_4 = void 0, (0, tslib_1.__values)(configuration.buttonConfiguration.values())), _g = _f.next(); !_g.done; _g = _f.next()) {
var value = _g.value;
var tlvBuffer = tlv.encode(1 /* BUTTON_ID */, value.buttonID, 2 /* BUTTON_TYPE */, tlv.writeUInt16(value.buttonType));
if (value.buttonName) {
tlvBuffer = Buffer.concat([
tlvBuffer,
tlv.encode(3 /* BUTTON_NAME */, value.buttonName),
]);
}
buttonConfigurationBuffers.push(tlvBuffer);
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (_g && !_g.done && (_b = _f.return)) _b.call(_f);
}
finally { if (e_4) throw e_4.error; }
}
var buttonConfiguration = tlv.encode(4 /* BUTTON_CONFIGURATION */, Buffer.concat(buttonConfigurationBuffers));
var targetConfiguration = Buffer.concat([targetIdentifier, targetName, targetCategory, buttonConfiguration]);
bufferList.push(tlv.encode(2 /* TARGET_CONFIGURATION */, targetConfiguration));
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
}
finally { if (e_3) throw e_3.error; }
}
this.targetConfigurationsString = Buffer.concat(bufferList).toString("base64");
(_c = this.stateChangeDelegate) === null || _c === void 0 ? void 0 : _c.call(this);
};
RemoteController.prototype.buildTargetControlSupportedConfigurationTLV = function (configuration) {
var maximumTargets = tlv.encode(1 /* MAXIMUM_TARGETS */, configuration.maximumTargets);
var ticksPerSecond = tlv.encode(2 /* TICKS_PER_SECOND */, tlv.writeUInt64(configuration.ticksPerSecond));
var supportedButtonConfigurationBuffers = [];
configuration.supportedButtonConfiguration.forEach(function (value) {
var tlvBuffer = tlv.encode(1 /* BUTTON_ID */, value.buttonID, 2 /* BUTTON_TYPE */, tlv.writeUInt16(value.buttonType));
supportedButtonConfigurationBuffers.push(tlvBuffer);
});
var supportedButtonConfiguration = tlv.encode(3 /* SUPPORTED_BUTTON_CONFIGURATION */, Buffer.concat(supportedButtonConfigurationBuffers));
var type = tlv.encode(4 /* TYPE */, configuration.hardwareImplemented ? 1 : 0);
return Buffer.concat([maximumTargets, ticksPerSecond, supportedButtonConfiguration, type]).toString("base64");
};
// --------------------------------- SIRI/DATA STREAM --------------------------------
// eslint-disable-next-line @typescript-eslint/no-explicit-any
RemoteController.prototype.handleTargetControlWhoAmI = function (connection, message) {
var targetIdentifier = message.identifier;
this.dataStreamConnections.set(targetIdentifier, connection);
debug("Discovered HDS connection for targetIdentifier %s", targetIdentifier);
connection.addProtocolHandler("dataSend" /* DATA_SEND */, this);
};
RemoteController.prototype.handleSiriAudioStart = function () {
if (!this.audioSupported) {
throw new Error("Cannot start siri stream on remote where siri is not supported");
}
if (!this.isActive()) {
debug("Tried opening Siri audio stream, however no controller is connected!");
return;
}
if (this.activeAudioSession && (!this.activeAudioSession.isClosing() || this.nextAudioSession)) {
// there is already a session running, which is not in closing state and/or there is even already a
// nextAudioSession running. ignoring start request
debug("Tried opening Siri audio stream, however there is already one in progress");
return;
}
var connection = this.dataStreamConnections.get(this.activeIdentifier); // get connection for current target
if (connection === undefined) { // target seems not connected, ignore it
debug("Tried opening Siri audio stream however target is not connected via HDS");
return;
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
var audioSession = new SiriAudioSession(connection, this.selectedAudioConfiguration, this.audioProducerConstructor, this.audioProducerOptions);
if (!this.activeAudioSession) {
this.activeAudioSession = audioSession;
}
else {
// we checked above that this only happens if the activeAudioSession is in closing state,
// so no collision with the input device can happen
this.nextAudioSession = audioSession;
}
audioSession.on("close" /* CLOSE */, this.handleSiriAudioSessionClosed.bind(this, audioSession));
audioSession.start();
};
RemoteController.prototype.handleSiriAudioStop = function () {
if (this.activeAudioSession) {
if (!this.activeAudioSession.isClosing()) {
this.activeAudioSession.stop();
return;
}
else if (this.nextAudioSession && !this.nextAudioSession.isClosing()) {
this.nextAudioSession.stop();
return;
}
}
debug("handleSiriAudioStop called although no audio session was started");
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
RemoteController.prototype.handleDataSendAckEvent = function (message) {
var streamId = message.streamId;
var endOfStream = message.endOfStream;
if (this.activeAudioSession && this.activeAudioSession.streamId === streamId) {
this.activeAudioSession.handleDataSendAckEvent(endOfStream);
}
else if (this.nextAudioSession && this.nextAudioSession.streamId === streamId) {
this.nextAudioSession.handleDataSendAckEvent(endOfStream);
}
else {
debug("Received dataSend acknowledgment event for unknown streamId '%s'", streamId);
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
RemoteController.prototype.handleDataSendCloseEvent = function (message) {
var streamId = message.streamId;
var reason = message.reason;
if (this.activeAudioSession && this.activeAudioSession.streamId === streamId) {
this.activeAudioSession.handleDataSendCloseEvent(reason);
}
else if (this.nextAudioSession && this.nextAudioSession.streamId === streamId) {
this.nextAudioSession.handleDataSendCloseEvent(reason);
}
else {
debug("Received dataSend close event for unknown streamId '%s'", streamId);
}
};
RemoteController.prototype.handleSiriAudioSessionClosed = function (session) {
if (session === this.activeAudioSession) {
this.activeAudioSession = this.nextAudioSession;
this.nextAudioSession = undefined;
}
else if (session === this.nextAudioSession) {
this.nextAudioSession = undefined;
}
};
RemoteController.prototype.handleDataStreamConnectionClosed = function (connection) {
var e_5, _a;
try {
for (var _b = (0, tslib_1.__values)(this.dataStreamConnections), _c = _b.next(); !_c.done; _c = _b.next()) {
var _d = (0, tslib_1.__read)(_c.value, 2), targetIdentifier = _d[0], connection0 = _d[1];
if (connection === connection0) {
debug("HDS connection disconnected for targetIdentifier %s", targetIdentifier);
this.dataStreamConnections.delete(targetIdentifier);
break;
}
}
}
catch (e_5_1) { e_5 = { error: e_5_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_5) throw e_5.error; }
}
};
// ------------------------------- AUDIO CONFIGURATION -------------------------------
// eslint-disable-next-line @typescript-eslint/no-explicit-any
RemoteController.prototype.handleSelectedAudioConfigurationWrite = function (value, callback) {
var data = Buffer.from(value, "base64");
var objects = tlv.decode(data);
var selectedAudioStreamConfiguration = tlv.decode(objects[1 /* SELECTED_AUDIO_INPUT_STREAM_CONFIGURATION */]);
var codec = selectedAudioStreamConfiguration[1 /* CODEC_TYPE */][0];
var parameters = tlv.decode(selectedAudioStreamConfiguration[2 /* CODEC_PARAMETERS */]);
var channels = parameters[1 /* CHANNEL */][0];
var bitrate = parameters[2 /* BIT_RATE */][0];
var samplerate = parameters[3 /* SAMPLE_RATE */][0];
this.selectedAudioConfiguration = {
codecType: codec,
parameters: {
channels: channels,
bitrate: bitrate,
samplerate: samplerate,
rtpTime: 20,
},
};
this.selectedAudioConfigurationString = RemoteController.buildSelectedAudioConfigurationTLV({
audioCodecConfiguration: this.selectedAudioConfiguration,
});
callback();
};
RemoteController.buildSupportedAudioConfigurationTLV = function (configuration) {
var codecConfigurationTLV = RemoteController.buildCodecConfigurationTLV(configuration.audioCodecConfiguration);
var supportedAudioStreamConfiguration = tlv.encode(1 /* AUDIO_CODEC_CONFIGURATION */, codecConfigurationTLV);
return supportedAudioStreamConfiguration.toString("base64");
};
RemoteController.buildSelectedAudioConfigurationTLV = function (configuration) {
var codecConfigurationTLV = RemoteController.buildCodecConfigurationTLV(configuration.audioCodecConfiguration);
var supportedAudioStreamConfiguration = tlv.encode(1 /* SELECTED_AUDIO_INPUT_STREAM_CONFIGURATION */, codecConfigurationTLV);
return supportedAudioStreamConfiguration.toString("base64");
};
RemoteController.buildCodecConfigurationTLV = function (codecConfiguration) {
var parameters = codecConfiguration.parameters;
var parametersTLV = tlv.encode(1 /* CHANNEL */, parameters.channels, 2 /* BIT_RATE */, parameters.bitrate, 3 /* SAMPLE_RATE */, parameters.samplerate);
if (parameters.rtpTime) {
parametersTLV = Buffer.concat([
parametersTLV,
tlv.encode(4 /* PACKET_TIME */, parameters.rtpTime),
]);
}
return tlv.encode(1 /* CODEC_TYPE */, codecConfiguration.codecType, 2 /* CODEC_PARAMETERS */, parametersTLV);
};
// -----------------------------------------------------------------------------------
/**
* @private
*/
RemoteController.prototype.constructServices = function () {
var _a;
this.targetControlManagementService = new Service_1.Service.TargetControlManagement("", "");
this.targetControlManagementService.setCharacteristic(Characteristic_1.Characteristic.TargetControlSupportedConfiguration, this.supportedConfiguration);
this.targetControlManagementService.setCharacteristic(Characteristic_1.Characteristic.TargetControlList, this.targetConfigurationsString);
this.targetControlManagementService.setPrimaryService();
// you can also expose multiple TargetControl services to control multiple apple tvs simultaneously.
// should we extend this class to support multiple TargetControl services or should users just create a second accessory?
this.targetControlService = new Service_1.Service.TargetControl("", "");
this.targetControlService.setCharacteristic(Characteristic_1.Characteristic.ActiveIdentifier, 0);
this.targetControlService.setCharacteristic(Characteristic_1.Characteristic.Active, false);
this.targetControlService.setCharacteristic(Characteristic_1.Characteristic.ButtonEvent, this.lastButtonEvent);
if (this.audioSupported) {
this.siriService = new Service_1.Service.Siri("", "");
this.siriService.setCharacteristic(Characteristic_1.Characteristic.SiriInputType, Characteristic_1.Characteristic.SiriInputType.PUSH_BUTTON_TRIGGERED_APPLE_TV);
this.audioStreamManagementService = new Service_1.Service.AudioStreamManagement("", "");
this.audioStreamManagementService.setCharacteristic(Characteristic_1.Characteristic.SupportedAudioStreamConfiguration, this.supportedAudioConfiguration);
this.audioStreamManagementService.setCharacteristic(Characteristic_1.Characteristic.SelectedAudioStreamConfiguration, this.selectedAudioConfigurationString);
this.dataStreamManagement = new datastream_1.DataStreamManagement();
this.siriService.addLinkedService(this.dataStreamManagement.getService());
this.siriService.addLinkedService(this.audioStreamManagementService);
}
return {
targetControlManagement: this.targetControlManagementService,
targetControl: this.targetControlService,
siri: this.siriService,
audioStreamManagement: this.audioStreamManagementService,
dataStreamTransportManagement: (_a = this.dataStreamManagement) === null || _a === void 0 ? void 0 : _a.getService(),
};
};
/**
* @private
*/
RemoteController.prototype.initWithServices = function (serviceMap) {
this.targetControlManagementService = serviceMap.targetControlManagement;
this.targetControlService = serviceMap.targetControl;
this.siriService = serviceMap.siri;
this.audioStreamManagementService = serviceMap.audioStreamManagement;
this.dataStreamManagement = new datastream_1.DataStreamManagement(serviceMap.dataStreamTransportManagement);
};
/**
* @private
*/
RemoteController.prototype.configureServices = function () {
var _a;
var _this = this;
if (!this.targetControlManagementService || !this.targetControlService) {
throw new Error("Unexpected state: Services not configured!"); // playing it save
}
this.targetControlManagementService.getCharacteristic(Characteristic_1.Characteristic.TargetControlList)
.on("get" /* GET */, function (callback) {
callback(null, _this.targetConfigurationsString);
})
.on("set" /* SET */, this.handleTargetControlWrite.bind(this));
this.targetControlService.getCharacteristic(Characteristic_1.Characteristic.ActiveIdentifier)
.on("get" /* GET */, function (callback) {
callback(undefined, _this.activeIdentifier);
});
this.targetControlService.getCharacteristic(Characteristic_1.Characteristic.Active)
.on("get" /* GET */, function (callback) {
callback(undefined, _this.isActive());
})
.on("set" /* SET */, function (value, callback, context, connection) {
if (!connection) {
debug("Set event handler for Remote.Active cannot be called from plugin. Connection undefined!");
callback(-70410 /* INVALID_VALUE_IN_REQUEST */);
return;
}
_this.handleActiveWrite(value, callback, connection);
});
this.targetControlService.getCharacteristic(Characteristic_1.Characteristic.ButtonEvent)
.on("get" /* GET */, function (callb