UNPKG

homebridge-plugin-wrapper

Version:

Wrapper for Homebridge and NodeJS-HAP with reduced dependencies that allows to intercept plugin values and also send to them

884 lines 54.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StreamController = exports.RTPStreamManagement = exports.StreamRequestTypes = exports.AudioStreamingSamplerate = exports.AudioStreamingCodecType = exports.SRTPCryptoSuites = exports.H264Level = exports.H264Profile = exports.VideoCodecType = void 0; var tslib_1 = require("tslib"); var assert_1 = (0, tslib_1.__importDefault)(require("assert")); var crypto_1 = (0, tslib_1.__importDefault)(require("crypto")); var debug_1 = (0, tslib_1.__importDefault)(require("debug")); var net_1 = (0, tslib_1.__importDefault)(require("net")); // noinspection JSDeprecatedSymbols var index_1 = require("../../index"); var Characteristic_1 = require("../Characteristic"); var controller_1 = require("../controller"); var Service_1 = require("../Service"); var tlv = (0, tslib_1.__importStar)(require("../util/tlv")); var RTPProxy_1 = (0, tslib_1.__importDefault)(require("./RTPProxy")); var debug = (0, debug_1.default)("HAP-NodeJS:Camera:RTPStreamManagement"); // ---------------------------------- TLV DEFINITIONS START ---------------------------------- var StreamingStatusTypes; (function (StreamingStatusTypes) { StreamingStatusTypes[StreamingStatusTypes["STATUS"] = 1] = "STATUS"; })(StreamingStatusTypes || (StreamingStatusTypes = {})); var StreamingStatus; (function (StreamingStatus) { StreamingStatus[StreamingStatus["AVAILABLE"] = 0] = "AVAILABLE"; StreamingStatus[StreamingStatus["IN_USE"] = 1] = "IN_USE"; StreamingStatus[StreamingStatus["UNAVAILABLE"] = 2] = "UNAVAILABLE"; })(StreamingStatus || (StreamingStatus = {})); // ---------- var SupportedVideoStreamConfigurationTypes; (function (SupportedVideoStreamConfigurationTypes) { SupportedVideoStreamConfigurationTypes[SupportedVideoStreamConfigurationTypes["VIDEO_CODEC_CONFIGURATION"] = 1] = "VIDEO_CODEC_CONFIGURATION"; })(SupportedVideoStreamConfigurationTypes || (SupportedVideoStreamConfigurationTypes = {})); var VideoCodecConfigurationTypes; (function (VideoCodecConfigurationTypes) { VideoCodecConfigurationTypes[VideoCodecConfigurationTypes["CODEC_TYPE"] = 1] = "CODEC_TYPE"; VideoCodecConfigurationTypes[VideoCodecConfigurationTypes["CODEC_PARAMETERS"] = 2] = "CODEC_PARAMETERS"; VideoCodecConfigurationTypes[VideoCodecConfigurationTypes["ATTRIBUTES"] = 3] = "ATTRIBUTES"; })(VideoCodecConfigurationTypes || (VideoCodecConfigurationTypes = {})); var VideoCodecParametersTypes; (function (VideoCodecParametersTypes) { VideoCodecParametersTypes[VideoCodecParametersTypes["PROFILE_ID"] = 1] = "PROFILE_ID"; VideoCodecParametersTypes[VideoCodecParametersTypes["LEVEL"] = 2] = "LEVEL"; VideoCodecParametersTypes[VideoCodecParametersTypes["PACKETIZATION_MODE"] = 3] = "PACKETIZATION_MODE"; VideoCodecParametersTypes[VideoCodecParametersTypes["CVO_ENABLED"] = 4] = "CVO_ENABLED"; VideoCodecParametersTypes[VideoCodecParametersTypes["CVO_ID"] = 5] = "CVO_ID"; })(VideoCodecParametersTypes || (VideoCodecParametersTypes = {})); var VideoAttributesTypes; (function (VideoAttributesTypes) { VideoAttributesTypes[VideoAttributesTypes["IMAGE_WIDTH"] = 1] = "IMAGE_WIDTH"; VideoAttributesTypes[VideoAttributesTypes["IMAGE_HEIGHT"] = 2] = "IMAGE_HEIGHT"; VideoAttributesTypes[VideoAttributesTypes["FRAME_RATE"] = 3] = "FRAME_RATE"; })(VideoAttributesTypes || (VideoAttributesTypes = {})); var VideoCodecType; (function (VideoCodecType) { VideoCodecType[VideoCodecType["H264"] = 0] = "H264"; // while the namespace is already reserved for H265 it isn't currently supported. // H265 = 0x01, })(VideoCodecType = exports.VideoCodecType || (exports.VideoCodecType = {})); var H264Profile; (function (H264Profile) { H264Profile[H264Profile["BASELINE"] = 0] = "BASELINE"; H264Profile[H264Profile["MAIN"] = 1] = "MAIN"; H264Profile[H264Profile["HIGH"] = 2] = "HIGH"; })(H264Profile = exports.H264Profile || (exports.H264Profile = {})); var H264Level; (function (H264Level) { H264Level[H264Level["LEVEL3_1"] = 0] = "LEVEL3_1"; H264Level[H264Level["LEVEL3_2"] = 1] = "LEVEL3_2"; H264Level[H264Level["LEVEL4_0"] = 2] = "LEVEL4_0"; })(H264Level = exports.H264Level || (exports.H264Level = {})); var VideoCodecPacketizationMode; (function (VideoCodecPacketizationMode) { VideoCodecPacketizationMode[VideoCodecPacketizationMode["NON_INTERLEAVED"] = 0] = "NON_INTERLEAVED"; })(VideoCodecPacketizationMode || (VideoCodecPacketizationMode = {})); var VideoCodecCVO; (function (VideoCodecCVO) { VideoCodecCVO[VideoCodecCVO["UNSUPPORTED"] = 0] = "UNSUPPORTED"; VideoCodecCVO[VideoCodecCVO["SUPPORTED"] = 1] = "SUPPORTED"; })(VideoCodecCVO || (VideoCodecCVO = {})); // ---------- var SupportedAudioStreamConfigurationTypes; (function (SupportedAudioStreamConfigurationTypes) { 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) { 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 || (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 || (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 || (AudioSamplerate = {})); // ---------- var SupportedRTPConfigurationTypes; (function (SupportedRTPConfigurationTypes) { SupportedRTPConfigurationTypes[SupportedRTPConfigurationTypes["SRTP_CRYPTO_SUITE"] = 2] = "SRTP_CRYPTO_SUITE"; })(SupportedRTPConfigurationTypes || (SupportedRTPConfigurationTypes = {})); var SRTPCryptoSuites; (function (SRTPCryptoSuites) { SRTPCryptoSuites[SRTPCryptoSuites["AES_CM_128_HMAC_SHA1_80"] = 0] = "AES_CM_128_HMAC_SHA1_80"; SRTPCryptoSuites[SRTPCryptoSuites["AES_CM_256_HMAC_SHA1_80"] = 1] = "AES_CM_256_HMAC_SHA1_80"; SRTPCryptoSuites[SRTPCryptoSuites["NONE"] = 2] = "NONE"; })(SRTPCryptoSuites = exports.SRTPCryptoSuites || (exports.SRTPCryptoSuites = {})); // ---------- var SetupEndpointsTypes; (function (SetupEndpointsTypes) { SetupEndpointsTypes[SetupEndpointsTypes["SESSION_ID"] = 1] = "SESSION_ID"; SetupEndpointsTypes[SetupEndpointsTypes["CONTROLLER_ADDRESS"] = 3] = "CONTROLLER_ADDRESS"; SetupEndpointsTypes[SetupEndpointsTypes["VIDEO_SRTP_PARAMETERS"] = 4] = "VIDEO_SRTP_PARAMETERS"; SetupEndpointsTypes[SetupEndpointsTypes["AUDIO_SRTP_PARAMETERS"] = 5] = "AUDIO_SRTP_PARAMETERS"; })(SetupEndpointsTypes || (SetupEndpointsTypes = {})); var AddressTypes; (function (AddressTypes) { AddressTypes[AddressTypes["ADDRESS_VERSION"] = 1] = "ADDRESS_VERSION"; AddressTypes[AddressTypes["ADDRESS"] = 2] = "ADDRESS"; AddressTypes[AddressTypes["VIDEO_RTP_PORT"] = 3] = "VIDEO_RTP_PORT"; AddressTypes[AddressTypes["AUDIO_RTP_PORT"] = 4] = "AUDIO_RTP_PORT"; })(AddressTypes || (AddressTypes = {})); var IPAddressVersion; (function (IPAddressVersion) { IPAddressVersion[IPAddressVersion["IPV4"] = 0] = "IPV4"; IPAddressVersion[IPAddressVersion["IPV6"] = 1] = "IPV6"; })(IPAddressVersion || (IPAddressVersion = {})); var SRTPParametersTypes; (function (SRTPParametersTypes) { SRTPParametersTypes[SRTPParametersTypes["SRTP_CRYPTO_SUITE"] = 1] = "SRTP_CRYPTO_SUITE"; SRTPParametersTypes[SRTPParametersTypes["MASTER_KEY"] = 2] = "MASTER_KEY"; SRTPParametersTypes[SRTPParametersTypes["MASTER_SALT"] = 3] = "MASTER_SALT"; // 14 bytes })(SRTPParametersTypes || (SRTPParametersTypes = {})); var SetupEndpointsResponseTypes; (function (SetupEndpointsResponseTypes) { SetupEndpointsResponseTypes[SetupEndpointsResponseTypes["SESSION_ID"] = 1] = "SESSION_ID"; SetupEndpointsResponseTypes[SetupEndpointsResponseTypes["STATUS"] = 2] = "STATUS"; SetupEndpointsResponseTypes[SetupEndpointsResponseTypes["ACCESSORY_ADDRESS"] = 3] = "ACCESSORY_ADDRESS"; SetupEndpointsResponseTypes[SetupEndpointsResponseTypes["VIDEO_SRTP_PARAMETERS"] = 4] = "VIDEO_SRTP_PARAMETERS"; SetupEndpointsResponseTypes[SetupEndpointsResponseTypes["AUDIO_SRTP_PARAMETERS"] = 5] = "AUDIO_SRTP_PARAMETERS"; SetupEndpointsResponseTypes[SetupEndpointsResponseTypes["VIDEO_SSRC"] = 6] = "VIDEO_SSRC"; SetupEndpointsResponseTypes[SetupEndpointsResponseTypes["AUDIO_SSRC"] = 7] = "AUDIO_SSRC"; })(SetupEndpointsResponseTypes || (SetupEndpointsResponseTypes = {})); var SetupEndpointsStatus; (function (SetupEndpointsStatus) { SetupEndpointsStatus[SetupEndpointsStatus["SUCCESS"] = 0] = "SUCCESS"; SetupEndpointsStatus[SetupEndpointsStatus["BUSY"] = 1] = "BUSY"; SetupEndpointsStatus[SetupEndpointsStatus["ERROR"] = 2] = "ERROR"; })(SetupEndpointsStatus || (SetupEndpointsStatus = {})); // ---------- var SelectedRTPStreamConfigurationTypes; (function (SelectedRTPStreamConfigurationTypes) { SelectedRTPStreamConfigurationTypes[SelectedRTPStreamConfigurationTypes["SESSION_CONTROL"] = 1] = "SESSION_CONTROL"; SelectedRTPStreamConfigurationTypes[SelectedRTPStreamConfigurationTypes["SELECTED_VIDEO_PARAMETERS"] = 2] = "SELECTED_VIDEO_PARAMETERS"; SelectedRTPStreamConfigurationTypes[SelectedRTPStreamConfigurationTypes["SELECTED_AUDIO_PARAMETERS"] = 3] = "SELECTED_AUDIO_PARAMETERS"; })(SelectedRTPStreamConfigurationTypes || (SelectedRTPStreamConfigurationTypes = {})); var SessionControlTypes; (function (SessionControlTypes) { SessionControlTypes[SessionControlTypes["SESSION_IDENTIFIER"] = 1] = "SESSION_IDENTIFIER"; SessionControlTypes[SessionControlTypes["COMMAND"] = 2] = "COMMAND"; })(SessionControlTypes || (SessionControlTypes = {})); var SessionControlCommand; (function (SessionControlCommand) { SessionControlCommand[SessionControlCommand["END_SESSION"] = 0] = "END_SESSION"; SessionControlCommand[SessionControlCommand["START_SESSION"] = 1] = "START_SESSION"; SessionControlCommand[SessionControlCommand["SUSPEND_SESSION"] = 2] = "SUSPEND_SESSION"; SessionControlCommand[SessionControlCommand["RESUME_SESSION"] = 3] = "RESUME_SESSION"; SessionControlCommand[SessionControlCommand["RECONFIGURE_SESSION"] = 4] = "RECONFIGURE_SESSION"; })(SessionControlCommand || (SessionControlCommand = {})); var SelectedVideoParametersTypes; (function (SelectedVideoParametersTypes) { SelectedVideoParametersTypes[SelectedVideoParametersTypes["CODEC_TYPE"] = 1] = "CODEC_TYPE"; SelectedVideoParametersTypes[SelectedVideoParametersTypes["CODEC_PARAMETERS"] = 2] = "CODEC_PARAMETERS"; SelectedVideoParametersTypes[SelectedVideoParametersTypes["ATTRIBUTES"] = 3] = "ATTRIBUTES"; SelectedVideoParametersTypes[SelectedVideoParametersTypes["RTP_PARAMETERS"] = 4] = "RTP_PARAMETERS"; })(SelectedVideoParametersTypes || (SelectedVideoParametersTypes = {})); var VideoRTPParametersTypes; (function (VideoRTPParametersTypes) { VideoRTPParametersTypes[VideoRTPParametersTypes["PAYLOAD_TYPE"] = 1] = "PAYLOAD_TYPE"; VideoRTPParametersTypes[VideoRTPParametersTypes["SYNCHRONIZATION_SOURCE"] = 2] = "SYNCHRONIZATION_SOURCE"; VideoRTPParametersTypes[VideoRTPParametersTypes["MAX_BIT_RATE"] = 3] = "MAX_BIT_RATE"; VideoRTPParametersTypes[VideoRTPParametersTypes["MIN_RTCP_INTERVAL"] = 4] = "MIN_RTCP_INTERVAL"; VideoRTPParametersTypes[VideoRTPParametersTypes["MAX_MTU"] = 5] = "MAX_MTU"; })(VideoRTPParametersTypes || (VideoRTPParametersTypes = {})); var SelectedAudioParametersTypes; (function (SelectedAudioParametersTypes) { SelectedAudioParametersTypes[SelectedAudioParametersTypes["CODEC_TYPE"] = 1] = "CODEC_TYPE"; SelectedAudioParametersTypes[SelectedAudioParametersTypes["CODEC_PARAMETERS"] = 2] = "CODEC_PARAMETERS"; SelectedAudioParametersTypes[SelectedAudioParametersTypes["RTP_PARAMETERS"] = 3] = "RTP_PARAMETERS"; SelectedAudioParametersTypes[SelectedAudioParametersTypes["COMFORT_NOISE"] = 4] = "COMFORT_NOISE"; })(SelectedAudioParametersTypes || (SelectedAudioParametersTypes = {})); var AudioRTPParametersTypes; (function (AudioRTPParametersTypes) { AudioRTPParametersTypes[AudioRTPParametersTypes["PAYLOAD_TYPE"] = 1] = "PAYLOAD_TYPE"; AudioRTPParametersTypes[AudioRTPParametersTypes["SYNCHRONIZATION_SOURCE"] = 2] = "SYNCHRONIZATION_SOURCE"; AudioRTPParametersTypes[AudioRTPParametersTypes["MAX_BIT_RATE"] = 3] = "MAX_BIT_RATE"; AudioRTPParametersTypes[AudioRTPParametersTypes["MIN_RTCP_INTERVAL"] = 4] = "MIN_RTCP_INTERVAL"; AudioRTPParametersTypes[AudioRTPParametersTypes["COMFORT_NOISE_PAYLOAD_TYPE"] = 6] = "COMFORT_NOISE_PAYLOAD_TYPE"; })(AudioRTPParametersTypes || (AudioRTPParametersTypes = {})); // eslint-disable-next-line @typescript-eslint/no-explicit-any function isLegacySRTPOptions(options) { return "srtp" in options; } var AudioStreamingCodecType; (function (AudioStreamingCodecType) { AudioStreamingCodecType["PCMU"] = "PCMU"; AudioStreamingCodecType["PCMA"] = "PCMA"; AudioStreamingCodecType["AAC_ELD"] = "AAC-eld"; AudioStreamingCodecType["OPUS"] = "OPUS"; AudioStreamingCodecType["MSBC"] = "mSBC"; AudioStreamingCodecType["AMR"] = "AMR"; AudioStreamingCodecType["AMR_WB"] = "AMR-WB"; })(AudioStreamingCodecType = exports.AudioStreamingCodecType || (exports.AudioStreamingCodecType = {})); var AudioStreamingSamplerate; (function (AudioStreamingSamplerate) { AudioStreamingSamplerate[AudioStreamingSamplerate["KHZ_8"] = 8] = "KHZ_8"; AudioStreamingSamplerate[AudioStreamingSamplerate["KHZ_16"] = 16] = "KHZ_16"; AudioStreamingSamplerate[AudioStreamingSamplerate["KHZ_24"] = 24] = "KHZ_24"; })(AudioStreamingSamplerate = exports.AudioStreamingSamplerate || (exports.AudioStreamingSamplerate = {})); var StreamRequestTypes; (function (StreamRequestTypes) { StreamRequestTypes["RECONFIGURE"] = "reconfigure"; StreamRequestTypes["START"] = "start"; StreamRequestTypes["STOP"] = "stop"; })(StreamRequestTypes = exports.StreamRequestTypes || (exports.StreamRequestTypes = {})); var RTPStreamManagement = /** @class */ (function () { function RTPStreamManagement(id, options, delegate, service, disabledThroughOperatingMode) { this.videoOnly = false; this.sessionIdentifier = undefined; this.streamStatus = 0 /* AVAILABLE */; // use _updateStreamStatus to update this property this.selectedConfiguration = ""; // base64 representation of the currently selected configuration this.setupEndpointsResponse = ""; // response of the SetupEndpoints Characteristic this.id = id; this.delegate = delegate; this.requireProxy = options.proxy || false; this.disableAudioProxy = options.disable_audio_proxy || false; if (isLegacySRTPOptions(options)) { this.supportedCryptoSuites = [options.srtp ? 0 /* AES_CM_128_HMAC_SHA1_80 */ : 2 /* NONE */]; } else { this.supportedCryptoSuites = options.supportedCryptoSuites; } if (this.supportedCryptoSuites.length === 0) { this.supportedCryptoSuites.push(2 /* NONE */); } if (!options.video) { throw new Error("Video parameters cannot be undefined in options"); } this.supportedRTPConfiguration = RTPStreamManagement._supportedRTPConfiguration(this.supportedCryptoSuites); this.supportedVideoStreamConfiguration = RTPStreamManagement._supportedVideoStreamConfiguration(options.video); this.supportedAudioStreamConfiguration = this._supportedAudioStreamConfiguration(options.audio); this.service = service || this.constructService(id); this.setupServiceHandlers(); this.resetSetupEndpointsResponse(); this.resetSelectedStreamConfiguration(); this.disabledThroughOperatingMode = disabledThroughOperatingMode; } RTPStreamManagement.prototype.forceStop = function () { this.handleSessionClosed(); }; RTPStreamManagement.prototype.getService = function () { return this.service; }; // noinspection JSUnusedGlobalSymbols,JSUnusedLocalSymbols /** * @deprecated */ // eslint-disable-next-line @typescript-eslint/no-unused-vars RTPStreamManagement.prototype.handleCloseConnection = function (connectionID) { // This method is only here for legacy compatibility. It used to be called by legacy style CameraSource // implementations to signal that the associated HAP connection was closed. // This is now handled automatically. Thus, we don't need to do anything anymore. }; RTPStreamManagement.prototype.handleFactoryReset = function () { this.resetSelectedStreamConfiguration(); this.resetSetupEndpointsResponse(); this.service.updateCharacteristic(Characteristic_1.Characteristic.Active, true); // on a factory reset the assumption is that all connections were already terminated and thus "handleStopStream" was already called }; RTPStreamManagement.prototype.destroy = function () { if (this.activeConnection) { this._handleStopStream(); } }; RTPStreamManagement.prototype.constructService = function (id) { var managementService = new Service_1.Service.CameraRTPStreamManagement("", id.toString()); // this service is required only when recording is enabled. We don't really have access to this info here, // so we just add the characteristic. Doesn't really hurt. managementService.setCharacteristic(Characteristic_1.Characteristic.Active, true); return managementService; }; RTPStreamManagement.prototype.setupServiceHandlers = function () { var _this = this; if (!this.service.testCharacteristic(Characteristic_1.Characteristic.Active)) { // the active characteristic might not be present on some older configurations. this.service.setCharacteristic(Characteristic_1.Characteristic.Active, true); } this.service.getCharacteristic(Characteristic_1.Characteristic.Active) .on("change" /* CHANGE */, function () { var _a; return (_a = _this.stateChangeDelegate) === null || _a === void 0 ? void 0 : _a.call(_this); }) .setProps({ adminOnlyAccess: [1 /* WRITE */] }); // ensure that configurations are up-to-date and reflected in the characteristic values this.service.setCharacteristic(Characteristic_1.Characteristic.SupportedRTPConfiguration, this.supportedRTPConfiguration); this.service.setCharacteristic(Characteristic_1.Characteristic.SupportedVideoStreamConfiguration, this.supportedVideoStreamConfiguration); this.service.setCharacteristic(Characteristic_1.Characteristic.SupportedAudioStreamConfiguration, this.supportedAudioStreamConfiguration); this._updateStreamStatus(0 /* AVAILABLE */); // reset streaming status to available this.service.setCharacteristic(Characteristic_1.Characteristic.SetupEndpoints, this.setupEndpointsResponse); // reset SetupEndpoints to default this.service.getCharacteristic(Characteristic_1.Characteristic.SelectedRTPStreamConfiguration) .on("get" /* GET */, function (callback) { if (_this.streamingIsDisabled()) { callback(null, tlv.encode(1 /* SESSION_CONTROL */, tlv.encode(2 /* COMMAND */, SessionControlCommand.SUSPEND_SESSION)).toString("base64")); return; } callback(null, _this.selectedConfiguration); }) .on("set" /* SET */, this._handleSelectedStreamConfigurationWrite.bind(this)); this.service.getCharacteristic(Characteristic_1.Characteristic.SetupEndpoints) .on("get" /* GET */, function (callback) { if (_this.streamingIsDisabled()) { callback(null, tlv.encode(2 /* STATUS */, 2 /* ERROR */).toString("base64")); return; } callback(null, _this.setupEndpointsResponse); }) .on("set" /* SET */, function (value, callback, context, connection) { if (!connection) { debug("Set event handler for SetupEndpoints cannot be called from plugin. Connection undefined!"); callback(-70410 /* INVALID_VALUE_IN_REQUEST */); return; } _this.handleSetupEndpoints(value, callback, connection); }); }; RTPStreamManagement.prototype.handleSessionClosed = function () { this.resetSelectedStreamConfiguration(); this.resetSetupEndpointsResponse(); if (this.activeConnectionClosedListener && this.activeConnection) { this.activeConnection.removeListener("closed" /* CLOSED */, this.activeConnectionClosedListener); this.activeConnectionClosedListener = undefined; } this._updateStreamStatus(0 /* AVAILABLE */); this.sessionIdentifier = undefined; this.activeConnection = undefined; // noinspection JSDeprecatedSymbols this.connectionID = undefined; this.ipVersion = undefined; if (this.videoProxy) { this.videoProxy.destroy(); this.videoProxy = undefined; } if (this.audioProxy) { this.audioProxy.destroy(); this.audioProxy = undefined; } }; RTPStreamManagement.prototype.streamingIsDisabled = function (callback) { var _a; if (!this.service.getCharacteristic(Characteristic_1.Characteristic.Active).value) { console.log("STREAMING DISABLED ACTIVE"); callback && callback(new index_1.HapStatusError(-70412 /* NOT_ALLOWED_IN_CURRENT_STATE */)); return true; } if ((_a = this.disabledThroughOperatingMode) === null || _a === void 0 ? void 0 : _a.call(this)) { console.log("STREAMING DISABLED OPERATION MODE!"); callback && callback(new index_1.HapStatusError(-70412 /* NOT_ALLOWED_IN_CURRENT_STATE */)); return true; } return false; }; RTPStreamManagement.prototype._handleSelectedStreamConfigurationWrite = function (value, callback) { var _this = this; if (this.streamingIsDisabled(callback)) { return; } var data = Buffer.from(value, "base64"); var objects = tlv.decode(data); var sessionControl = tlv.decode(objects[1 /* SESSION_CONTROL */]); var sessionIdentifier = index_1.uuid.unparse(sessionControl[1 /* SESSION_IDENTIFIER */]); var requestType = sessionControl[2 /* COMMAND */][0]; if (sessionIdentifier !== this.sessionIdentifier) { debug("Received unknown session Identifier with request to ".concat(SessionControlCommand[requestType])); callback(-70410 /* INVALID_VALUE_IN_REQUEST */); return; } this.selectedConfiguration = value; // intercept the callback chain to check if an error occurred. var streamCallback = function (error, writeResponse) { callback(error, writeResponse); // does not support writeResponse, but how knows what comes in the future. if (error) { _this.handleSessionClosed(); } }; switch (requestType) { case SessionControlCommand.START_SESSION: { var selectedVideoParameters = tlv.decode(objects[2 /* SELECTED_VIDEO_PARAMETERS */]); var selectedAudioParameters = tlv.decode(objects[3 /* SELECTED_AUDIO_PARAMETERS */]); this._handleStartStream(selectedVideoParameters, selectedAudioParameters, streamCallback); break; } case SessionControlCommand.RECONFIGURE_SESSION: { var reconfiguredVideoParameters = tlv.decode(objects[2 /* SELECTED_VIDEO_PARAMETERS */]); this.handleReconfigureStream(reconfiguredVideoParameters, streamCallback); break; } case SessionControlCommand.END_SESSION: this._handleStopStream(streamCallback); break; case SessionControlCommand.RESUME_SESSION: case SessionControlCommand.SUSPEND_SESSION: default: debug("Unhandled request type ".concat(SessionControlCommand[requestType])); callback(-70410 /* INVALID_VALUE_IN_REQUEST */); return; } }; RTPStreamManagement.prototype._handleStartStream = function (videoConfiguration, audioConfiguration, callback) { // selected video configuration // noinspection JSUnusedLocalSymbols var videoCodec = videoConfiguration[1 /* CODEC_TYPE */]; // always 0x00 for h264 var videoParametersTLV = videoConfiguration[2 /* CODEC_PARAMETERS */]; var videoAttributesTLV = videoConfiguration[3 /* ATTRIBUTES */]; var videoRTPParametersTLV = videoConfiguration[4 /* RTP_PARAMETERS */]; // video parameters var videoParameters = tlv.decode(videoParametersTLV); var h264Profile = videoParameters[1 /* PROFILE_ID */][0]; var h264Level = videoParameters[2 /* LEVEL */][0]; var packetizationMode = videoParameters[3 /* PACKETIZATION_MODE */][0]; var cvoEnabled = videoParameters[4 /* CVO_ENABLED */]; var cvoId = undefined; if (cvoEnabled && cvoEnabled[0] === 1 /* SUPPORTED */) { cvoId = videoParameters[5 /* CVO_ID */].readUInt8(0); } // video attributes var videoAttributes = tlv.decode(videoAttributesTLV); var width = videoAttributes[1 /* IMAGE_WIDTH */].readUInt16LE(0); var height = videoAttributes[2 /* IMAGE_HEIGHT */].readUInt16LE(0); var frameRate = videoAttributes[3 /* FRAME_RATE */].readUInt8(0); // video rtp parameters var videoRTPParameters = tlv.decode(videoRTPParametersTLV); var videoPayloadType = videoRTPParameters[1 /* PAYLOAD_TYPE */].readUInt8(0); // 99 var videoSSRC = videoRTPParameters[2 /* SYNCHRONIZATION_SOURCE */].readUInt32LE(0); var videoMaximumBitrate = videoRTPParameters[3 /* MAX_BIT_RATE */].readUInt16LE(0); var videoRTCPInterval = videoRTPParameters[4 /* MIN_RTCP_INTERVAL */].readFloatLE(0); var maxMTU = this.ipVersion === "ipv6" ? 1228 : 1378; // default values ipv4: 1378 bytes; ipv6: 1228 bytes if (videoRTPParameters[5 /* MAX_MTU */]) { maxMTU = videoRTPParameters[5 /* MAX_MTU */].readUInt16LE(0); } // selected audio configuration var audioCodec = audioConfiguration[1 /* CODEC_TYPE */][0]; var audioParametersTLV = audioConfiguration[2 /* CODEC_PARAMETERS */]; var audioRTPParametersTLV = audioConfiguration[3 /* RTP_PARAMETERS */]; var comfortNoise = !!audioConfiguration[4 /* COMFORT_NOISE */].readUInt8(0); // audio parameters var audioParameters = tlv.decode(audioParametersTLV); var channels = audioParameters[1 /* CHANNEL */][0]; var audioBitrate = audioParameters[2 /* BIT_RATE */][0]; var samplerate = audioParameters[3 /* SAMPLE_RATE */][0]; var rtpPacketTime = audioParameters[4 /* PACKET_TIME */].readUInt8(0); // audio rtp parameters var audioRTPParameters = tlv.decode(audioRTPParametersTLV); var audioPayloadType = audioRTPParameters[1 /* PAYLOAD_TYPE */].readUInt8(0); // 110 var audioSSRC = audioRTPParameters[2 /* SYNCHRONIZATION_SOURCE */].readUInt32LE(0); var audioMaximumBitrate = audioRTPParameters[3 /* MAX_BIT_RATE */].readUInt16LE(0); var audioRTCPInterval = audioRTPParameters[4 /* MIN_RTCP_INTERVAL */].readFloatLE(0); var comfortNoisePayloadType = audioRTPParameters[6 /* COMFORT_NOISE_PAYLOAD_TYPE */].readUInt8(0); // 13 if (this.requireProxy) { this.videoProxy.setOutgoingPayloadType(videoPayloadType); if (!this.disableAudioProxy) { this.audioProxy.setOutgoingPayloadType(audioPayloadType); } } var videoInfo = { codec: videoCodec.readUInt8(0), profile: h264Profile, level: h264Level, packetizationMode: packetizationMode, cvoId: cvoId, width: width, height: height, fps: frameRate, pt: videoPayloadType, ssrc: videoSSRC, max_bit_rate: videoMaximumBitrate, rtcp_interval: videoRTCPInterval, mtu: maxMTU, }; var audioCodecName; var samplerateNum; switch (audioCodec) { case 0 /* PCMU */: audioCodecName = "PCMU" /* PCMU */; break; case 1 /* PCMA */: audioCodecName = "PCMA" /* PCMA */; break; case 2 /* AAC_ELD */: audioCodecName = "AAC-eld" /* AAC_ELD */; break; case 3 /* OPUS */: audioCodecName = "OPUS" /* OPUS */; break; case 4 /* MSBC */: audioCodecName = "mSBC" /* MSBC */; break; case 5 /* AMR */: audioCodecName = "AMR" /* AMR */; break; case 6 /* AMR_WB */: audioCodecName = "AMR-WB" /* AMR_WB */; break; default: throw new Error("Encountered unknown selected audio codec ".concat(audioCodec)); } switch (samplerate) { case 0 /* KHZ_8 */: samplerateNum = 8; break; case 1 /* KHZ_16 */: samplerateNum = 16; break; case 2 /* KHZ_24 */: samplerateNum = 24; break; default: throw new Error("Encountered unknown selected audio samplerate ".concat(samplerate)); } var audioInfo = { codec: audioCodecName, channel: channels, bit_rate: audioBitrate, sample_rate: samplerateNum, packet_time: rtpPacketTime, pt: audioPayloadType, ssrc: audioSSRC, max_bit_rate: audioMaximumBitrate, rtcp_interval: audioRTCPInterval, comfort_pt: comfortNoisePayloadType, comfortNoiseEnabled: comfortNoise, }; var request = { sessionID: this.sessionIdentifier, type: "start" /* START */, video: videoInfo, audio: audioInfo, }; this.delegate.handleStreamRequest(request, function (error) { return callback(error); }); }; RTPStreamManagement.prototype.handleReconfigureStream = function (videoConfiguration, callback) { // selected video configuration var videoAttributesTLV = videoConfiguration[3 /* ATTRIBUTES */]; var videoRTPParametersTLV = videoConfiguration[4 /* RTP_PARAMETERS */]; // video attributes var videoAttributes = tlv.decode(videoAttributesTLV); var width = videoAttributes[1 /* IMAGE_WIDTH */].readUInt16LE(0); var height = videoAttributes[2 /* IMAGE_HEIGHT */].readUInt16LE(0); var frameRate = videoAttributes[3 /* FRAME_RATE */].readUInt8(0); // video rtp parameters var videoRTPParameters = tlv.decode(videoRTPParametersTLV); var videoMaximumBitrate = videoRTPParameters[3 /* MAX_BIT_RATE */].readUInt16LE(0); // seems to be always zero, use default of 0.5 var videoRTCPInterval = videoRTPParameters[4 /* MIN_RTCP_INTERVAL */].readFloatLE(0) || 0.5; var reconfiguredVideoInfo = { width: width, height: height, fps: frameRate, max_bit_rate: videoMaximumBitrate, rtcp_interval: videoRTCPInterval, }; var request = { sessionID: this.sessionIdentifier, type: "reconfigure" /* RECONFIGURE */, video: reconfiguredVideoInfo, }; this.delegate.handleStreamRequest(request, function (error) { return callback(error); }); }; RTPStreamManagement.prototype._handleStopStream = function (callback) { var request = { sessionID: this.sessionIdentifier, type: "stop" /* STOP */, }; this.handleSessionClosed(); this.delegate.handleStreamRequest(request, function (error) { return callback ? callback(error) : undefined; }); }; RTPStreamManagement.prototype.handleSetupEndpoints = function (value, callback, connection) { var _this = this; if (this.streamingIsDisabled(callback)) { return; } var data = Buffer.from(value, "base64"); var objects = tlv.decode(data); var sessionIdentifier = index_1.uuid.unparse(objects[1 /* SESSION_ID */]); if (this.streamStatus !== 0 /* AVAILABLE */) { this.setupEndpointsResponse = tlv.encode(1 /* SESSION_ID */, index_1.uuid.write(sessionIdentifier), 2 /* STATUS */, 1 /* BUSY */).toString("base64"); callback(); return; } this.activeConnection = connection; this.activeConnection.on("closed" /* CLOSED */, (this.activeConnectionClosedListener = this._handleStopStream.bind(this))); // noinspection JSDeprecatedSymbols this.connectionID = connection.sessionID; this.sessionIdentifier = sessionIdentifier; this._updateStreamStatus(1 /* IN_USE */); // Address var targetAddressPayload = objects[3 /* CONTROLLER_ADDRESS */]; var processedAddressInfo = tlv.decode(targetAddressPayload); var addressVersion = processedAddressInfo[1 /* ADDRESS_VERSION */][0]; var controllerAddress = processedAddressInfo[2 /* ADDRESS */].toString("utf8"); var targetVideoPort = processedAddressInfo[3 /* VIDEO_RTP_PORT */].readUInt16LE(0); var targetAudioPort = processedAddressInfo[4 /* AUDIO_RTP_PORT */].readUInt16LE(0); // Video SRTP Params var videoSRTPPayload = objects[4 /* VIDEO_SRTP_PARAMETERS */]; var processedVideoInfo = tlv.decode(videoSRTPPayload); var videoCryptoSuite = processedVideoInfo[1 /* SRTP_CRYPTO_SUITE */][0]; var videoMasterKey = processedVideoInfo[2 /* MASTER_KEY */]; var videoMasterSalt = processedVideoInfo[3 /* MASTER_SALT */]; // Audio SRTP Params var audioSRTPPayload = objects[5 /* AUDIO_SRTP_PARAMETERS */]; var processedAudioInfo = tlv.decode(audioSRTPPayload); var audioCryptoSuite = processedAudioInfo[1 /* SRTP_CRYPTO_SUITE */][0]; var audioMasterKey = processedAudioInfo[2 /* MASTER_KEY */]; var audioMasterSalt = processedAudioInfo[3 /* MASTER_SALT */]; debug("Session: ", sessionIdentifier, "\nControllerAddress: ", controllerAddress, "\nVideoPort: ", targetVideoPort, "\nAudioPort: ", targetAudioPort, "\nVideo Crypto: ", videoCryptoSuite, "\nVideo Master Key: ", videoMasterKey, "\nVideo Master Salt: ", videoMasterSalt, "\nAudio Crypto: ", audioCryptoSuite, "\nAudio Master Key: ", audioMasterKey, "\nAudio Master Salt: ", audioMasterSalt); var prepareRequest = { sessionID: sessionIdentifier, targetAddress: controllerAddress, addressVersion: addressVersion === 1 /* IPV6 */ ? "ipv6" : "ipv4", video: { port: targetVideoPort, srtpCryptoSuite: videoCryptoSuite, srtp_key: videoMasterKey, srtp_salt: videoMasterSalt, }, audio: { port: targetAudioPort, srtpCryptoSuite: audioCryptoSuite, srtp_key: audioMasterKey, srtp_salt: audioMasterSalt, }, }; var promises = []; if (this.requireProxy) { prepareRequest.targetAddress = connection.getLocalAddress(addressVersion === 1 /* IPV6 */ ? "ipv6" : "ipv4"); // ip versions must be the same this.videoProxy = new RTPProxy_1.default({ outgoingAddress: controllerAddress, outgoingPort: targetVideoPort, outgoingSSRC: crypto_1.default.randomBytes(4).readUInt32LE(0), disabled: false, }); promises.push(this.videoProxy.setup().then(function () { prepareRequest.video.proxy_rtp = _this.videoProxy.incomingRTPPort(); prepareRequest.video.proxy_rtcp = _this.videoProxy.incomingRTCPPort(); })); if (!this.disableAudioProxy) { this.audioProxy = new RTPProxy_1.default({ outgoingAddress: controllerAddress, outgoingPort: targetAudioPort, outgoingSSRC: crypto_1.default.randomBytes(4).readUInt32LE(0), disabled: this.videoOnly, }); promises.push(this.audioProxy.setup().then(function () { prepareRequest.audio.proxy_rtp = _this.audioProxy.incomingRTPPort(); prepareRequest.audio.proxy_rtcp = _this.audioProxy.incomingRTCPPort(); })); } } Promise.all(promises).then(function () { _this.delegate.prepareStream(prepareRequest, (0, index_1.once)(function (error, response) { if (error || !response) { debug("PrepareStream request encountered an error: ".concat(error ? error.message : undefined)); _this.setupEndpointsResponse = tlv.encode(1 /* SESSION_ID */, index_1.uuid.write(sessionIdentifier), 2 /* STATUS */, 2 /* ERROR */).toString("base64"); _this.handleSessionClosed(); callback(error); } else { _this.generateSetupEndpointResponse(connection, sessionIdentifier, prepareRequest, response, callback); } })); }); }; RTPStreamManagement.prototype.generateSetupEndpointResponse = function (connection, identifier, request, response, callback) { var address; var addressVersion = request.addressVersion; var videoPort; var audioPort; var videoCryptoSuite; var videoSRTPKey; var videoSRTPSalt; var audioCryptoSuite; var audioSRTPKey; var audioSRTPSalt; var videoSSRC; var audioSSRC; if (!this.videoOnly && !response.audio) { throw new Error("Audio was enabled but not supplied in PrepareStreamResponse!"); } // Provide default values if audio was not supplied var audio = response.audio || { port: request.audio.port, ssrc: controller_1.CameraController.generateSynchronisationSource(), srtp_key: request.audio.srtp_key, srtp_salt: request.audio.srtp_salt, }; if (!this.requireProxy) { var videoInfo = response.video; var audioInfo = audio; if (response.addressOverride) { addressVersion = net_1.default.isIPv4(response.addressOverride) ? "ipv4" : "ipv6"; address = response.addressOverride; } else { address = connection.getLocalAddress(addressVersion); } if (request.addressVersion !== addressVersion) { throw new Error("Incoming and outgoing ip address versions must match! Expected ".concat(request.addressVersion, " but got ").concat(addressVersion)); } videoPort = videoInfo.port; audioPort = audioInfo.port; if (request.video.srtpCryptoSuite !== 2 /* NONE */ && (videoInfo.srtp_key === undefined || videoInfo.srtp_salt === undefined)) { throw new Error("SRTP was selected for the prepared video stream, but no 'srtp_key' or 'srtp_salt' was specified!"); } if (request.audio.srtpCryptoSuite !== 2 /* NONE */ && (audioInfo.srtp_key === undefined || audioInfo.srtp_salt === undefined)) { throw new Error("SRTP was selected for the prepared audio stream, but no 'srtp_key' or 'srtp_salt' was specified!"); } videoCryptoSuite = request.video.srtpCryptoSuite; videoSRTPKey = videoInfo.srtp_key || Buffer.alloc(0); // key and salt are zero-length for cryptoSuite = NONE videoSRTPSalt = videoInfo.srtp_salt || Buffer.alloc(0); audioCryptoSuite = request.audio.srtpCryptoSuite; audioSRTPKey = audioInfo.srtp_key || Buffer.alloc(0); // key and salt are zero-length for cryptoSuite = NONE audioSRTPSalt = audioInfo.srtp_salt || Buffer.alloc(0); videoSSRC = videoInfo.ssrc; audioSSRC = audioInfo.ssrc; } else { var videoInfo = response.video; address = connection.getLocalAddress(request.addressVersion); videoCryptoSuite = 2 /* NONE */; videoSRTPKey = Buffer.alloc(0); videoSRTPSalt = Buffer.alloc(0); audioCryptoSuite = 2 /* NONE */; audioSRTPKey = Buffer.alloc(0); audioSRTPSalt = Buffer.alloc(0); this.videoProxy.setIncomingPayloadType(videoInfo.proxy_pt); this.videoProxy.setServerAddress(videoInfo.proxy_server_address); this.videoProxy.setServerRTPPort(videoInfo.proxy_server_rtp); this.videoProxy.setServerRTCPPort(videoInfo.proxy_server_rtcp); videoPort = this.videoProxy.outgoingLocalPort(); videoSSRC = this.videoProxy.outgoingSSRC; if (!this.disableAudioProxy) { var audioInfo = response.audio; this.audioProxy.setIncomingPayloadType(audioInfo.proxy_pt); this.audioProxy.setServerAddress(audioInfo.proxy_server_address); this.audioProxy.setServerRTPPort(audioInfo.proxy_server_rtp); this.audioProxy.setServerRTCPPort(audioInfo.proxy_server_rtcp); audioPort = this.audioProxy.outgoingLocalPort(); audioSSRC = this.audioProxy.outgoingSSRC; } else { var audioInfo = response.audio; audioPort = audioInfo.port; audioSSRC = audioInfo.ssrc; } } this.ipVersion = addressVersion; // we need to save this in order to calculate some default mtu values later var accessoryAddress = tlv.encode(1 /* ADDRESS_VERSION */, addressVersion === "ipv4" ? 0 /* IPV4 */ : 1 /* IPV6 */, 2 /* ADDRESS */, address, 3 /* VIDEO_RTP_PORT */, tlv.writeUInt16(videoPort), 4 /* AUDIO_RTP_PORT */, tlv.writeUInt16(audioPort)); var videoSRTPParameters = tlv.encode(1 /* SRTP_CRYPTO_SUITE */, videoCryptoSuite, 2 /* MASTER_KEY */, videoSRTPKey, 3 /* MASTER_SALT */, videoSRTPSalt); var audioSRTPParameters = tlv.encode(1 /* SRTP_CRYPTO_SUITE */, audioCryptoSuite, 2 /* MASTER_KEY */, audioSRTPKey, 3 /* MASTER_SALT */, audioSRTPSalt); this.setupEndpointsResponse = tlv.encode(1 /* SESSION_ID */, index_1.uuid.write(identifier), 2 /* STATUS */, 0 /* SUCCESS */, 3 /* ACCESSORY_ADDRESS */, accessoryAddress, 4 /* VIDEO_SRTP_PARAMETERS */, videoSRTPParameters, 5 /* AUDIO_SRTP_PARAMETERS */, audioSRTPParameters, 6 /* VIDEO_SSRC */, tlv.writeUInt32(videoSSRC), 7 /* AUDIO_SSRC */, tlv.writeUInt32(audioSSRC)).toString("base64"); callback(); }; RTPStreamManagement.prototype._updateStreamStatus = function (status) { this.streamStatus = status; this.service.updateCharacteristic(Characteristic_1.Characteristic.StreamingStatus, tlv.encode(1 /* STATUS */, this.streamStatus).toString("base64")); }; RTPStreamManagement._supportedRTPConfiguration = function (supportedCryptoSuites) { if (supportedCryptoSuites.length === 1 && supportedCryptoSuites[0] === 2 /* NONE */) { debug("Client claims it doesn't support SRTP. The stream may stops working with future iOS releases."); } return tlv.encode(2 /* SRTP_CRYPTO_SUITE */, supportedCryptoSuites).toString("base64"); }; RTPStreamManagement._supportedVideoStreamConfiguration = function (videoOptions) { if (!videoOptions.codec) { throw new Error("Video codec cannot be undefined"); } if (!videoOptions.resolutions) { throw new Error("Video resolutions cannot be undefined"); } var codecParameters = tlv.encode(1 /* PROFILE_ID */, videoOptions.codec.profiles, 2 /* LEVEL */, videoOptions.codec.levels, 3 /* PACKETIZATION_MODE */, 0 /* NON_INTERLEAVED */); if (videoOptions.cvoId != null) { codecParameters = Buffer.concat([ codecParameters, tlv.encode(4 /* CVO_ENABLED */, 1 /* SUPPORTED */, 5 /* CVO_ID */, videoOptions.cvoId), ]); } var videoStreamConfiguration = tlv.encode(1 /* CODEC_TYPE */, 0 /* H264 */, 2 /* CODEC_PARAMETERS */, codecParameters, 3 /* ATTRIBUTES */, videoOptions.resolutions.map(function (resolution) { if (resolution.length !== 3) { throw new Error("Unexpected video resolution"); } var width = Buffer.alloc(2); var height = Buffer.alloc(2); var frameRate = Buffer.alloc(1); width.writeUInt16LE(resolution[0], 0); height.writeUInt16LE(resolution[1], 0); frameRate.writeUInt8(resolution[2], 0); return tlv.encode(1 /* IMAGE_WIDTH */, width, 2 /* IMAGE_HEIGHT */, height, 3 /* FRAME_RATE */, frameRate); })); return tlv.encode(1 /* VIDEO_CODEC_CONFIGURATION */, videoStreamConfiguration).toString("base64"); }; RTPStreamManagement.prototype.checkForLegacyAudioCodecRepresentation = function (codecs) { var codecMap = {}; codecs.slice().forEach(function (codec) { var previous = codecMap[codec.type]; if (previous) { if (typeof previous.samplerate === "number") { previous.samplerate = [previous.samplerate]; } previous.samplerate = previous.samplerate.concat(codec.samplerate); var index = codecs.indexOf(codec); if (index >= 0) { codecs.splice(index, 1); } } else { codecMap[codec.type] = codec; } }); }; RTPStreamManagement.prototype._supportedAudioStreamConfiguration = function (audioOptions) { // Only AAC-ELD and OPUS are accepted by iOS currently, and we need to give it something it will accept // for it to start the video stream. var comfortNoise = audioOptions && !!audioOptions.comfort_noise; var supportedCodecs = (audioOptions && audioOptions.codecs) || []; this.checkForLegacyAudioCodecRepresentation(supportedCodecs); if (supportedCodecs.length === 0) { // Fake a Codec if we haven't got anything debug("Client doesn't support any audio codec that HomeKit supports."); this.videoOnly = true; supportedCodecs.push({ type: "OPUS" /* OPUS */, samplerate: [16 /* KHZ_16 */, 24 /* KHZ_24 */], // 16 and 24 must be supported }); } var codecConfigurations = supportedCodecs.map(function (codec) { var type; switch (codec.type) { case "OPUS" /* OPUS */: type = 3 /* OPUS */; break; case "AAC-eld" /* AAC_ELD */: type = 2 /* AAC_ELD */; break; case "PCMA" /* PCMA */: type = 1 /* PCMA */; break; case "PCMU" /* PCMU */: type = 0 /* PCMU */; break; case "mSBC" /* MSBC */: type = 4 /* MSBC */; break; case "