hap-nodejs 0.12.3-beta.2 → 0.12.3-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/accessories/AirConditioner_accessory.js +24 -24
- package/dist/accessories/AirConditioner_accessory.js.map +1 -1
- package/dist/accessories/AppleTVRemote_accessory.js +23 -23
- package/dist/accessories/AppleTVRemote_accessory.js.map +1 -1
- package/dist/accessories/Camera_accessory.js +292 -373
- package/dist/accessories/Camera_accessory.js.map +1 -1
- package/dist/accessories/Fan_accessory.js +15 -21
- package/dist/accessories/Fan_accessory.js.map +1 -1
- package/dist/accessories/GarageDoorOpener_accessory.js +12 -12
- package/dist/accessories/GarageDoorOpener_accessory.js.map +1 -1
- package/dist/accessories/Light-AdaptiveLighting_accessory.js +31 -21
- package/dist/accessories/Light-AdaptiveLighting_accessory.js.map +1 -1
- package/dist/accessories/Light_accessory.js +45 -48
- package/dist/accessories/Light_accessory.js.map +1 -1
- package/dist/accessories/Lock_accessory.js +11 -11
- package/dist/accessories/Lock_accessory.js.map +1 -1
- package/dist/accessories/MotionSensor_accessory.js +8 -8
- package/dist/accessories/MotionSensor_accessory.js.map +1 -1
- package/dist/accessories/Outlet_accessory.js +10 -10
- package/dist/accessories/Outlet_accessory.js.map +1 -1
- package/dist/accessories/SmartSpeaker_accessory.js +11 -11
- package/dist/accessories/SmartSpeaker_accessory.js.map +1 -1
- package/dist/accessories/Sprinkler_accessory.js +19 -19
- package/dist/accessories/Sprinkler_accessory.js.map +1 -1
- package/dist/accessories/TV_accessory.js +17 -17
- package/dist/accessories/TV_accessory.js.map +1 -1
- package/dist/accessories/TemperatureSensor_accessory.js +6 -6
- package/dist/accessories/TemperatureSensor_accessory.js.map +1 -1
- package/dist/accessories/Wi-FiRouter_accessory.js +3 -3
- package/dist/accessories/Wi-FiRouter_accessory.js.map +1 -1
- package/dist/accessories/Wi-FiSatellite_accessory.js +4 -4
- package/dist/accessories/Wi-FiSatellite_accessory.js.map +1 -1
- package/dist/accessories/gstreamer-audioProducer.js +36 -47
- package/dist/accessories/gstreamer-audioProducer.js.map +1 -1
- package/dist/accessories/types.js +2 -2
- package/dist/accessories/types.js.map +1 -1
- package/dist/index.d.ts +0 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -28
- package/dist/index.js.map +1 -1
- package/dist/lib/Accessory.d.ts +1 -58
- package/dist/lib/Accessory.d.ts.map +1 -1
- package/dist/lib/Accessory.js +747 -1149
- package/dist/lib/Accessory.js.map +1 -1
- package/dist/lib/Advertiser.d.ts +1 -2
- package/dist/lib/Advertiser.d.ts.map +1 -1
- package/dist/lib/Advertiser.js +392 -524
- package/dist/lib/Advertiser.js.map +1 -1
- package/dist/lib/Bridge.js +6 -10
- package/dist/lib/Bridge.js.map +1 -1
- package/dist/lib/Characteristic.d.ts +2 -133
- package/dist/lib/Characteristic.d.ts.map +1 -1
- package/dist/lib/Characteristic.js +1467 -669
- package/dist/lib/Characteristic.js.map +1 -1
- package/dist/lib/HAPServer.d.ts +0 -10
- package/dist/lib/HAPServer.d.ts.map +1 -1
- package/dist/lib/HAPServer.js +216 -280
- package/dist/lib/HAPServer.js.map +1 -1
- package/dist/lib/Service.d.ts +1 -51
- package/dist/lib/Service.d.ts.map +1 -1
- package/dist/lib/Service.js +474 -322
- package/dist/lib/Service.js.map +1 -1
- package/dist/lib/camera/RTPProxy.js +112 -104
- package/dist/lib/camera/RTPProxy.js.map +1 -1
- package/dist/lib/camera/RTPStreamManagement.d.ts +0 -65
- package/dist/lib/camera/RTPStreamManagement.d.ts.map +1 -1
- package/dist/lib/camera/RTPStreamManagement.js +255 -278
- package/dist/lib/camera/RTPStreamManagement.js.map +1 -1
- package/dist/lib/camera/RecordingManagement.js +318 -381
- package/dist/lib/camera/RecordingManagement.js.map +1 -1
- package/dist/lib/camera/index.d.ts +0 -1
- package/dist/lib/camera/index.d.ts.map +1 -1
- package/dist/lib/camera/index.js +1 -2
- package/dist/lib/camera/index.js.map +1 -1
- package/dist/lib/controller/AdaptiveLightingController.d.ts +19 -3
- package/dist/lib/controller/AdaptiveLightingController.d.ts.map +1 -1
- package/dist/lib/controller/AdaptiveLightingController.js +217 -218
- package/dist/lib/controller/AdaptiveLightingController.js.map +1 -1
- package/dist/lib/controller/CameraController.d.ts +0 -4
- package/dist/lib/controller/CameraController.d.ts.map +1 -1
- package/dist/lib/controller/CameraController.js +189 -256
- package/dist/lib/controller/CameraController.js.map +1 -1
- package/dist/lib/controller/DoorbellController.js +38 -39
- package/dist/lib/controller/DoorbellController.js.map +1 -1
- package/dist/lib/controller/RemoteController.d.ts +0 -14
- package/dist/lib/controller/RemoteController.d.ts.map +1 -1
- package/dist/lib/controller/RemoteController.js +340 -415
- package/dist/lib/controller/RemoteController.js.map +1 -1
- package/dist/lib/controller/index.js +1 -1
- package/dist/lib/datastream/DataStreamManagement.js +56 -57
- package/dist/lib/datastream/DataStreamManagement.js.map +1 -1
- package/dist/lib/datastream/DataStreamParser.js +259 -304
- package/dist/lib/datastream/DataStreamParser.js.map +1 -1
- package/dist/lib/datastream/DataStreamServer.d.ts +0 -5
- package/dist/lib/datastream/DataStreamServer.d.ts.map +1 -1
- package/dist/lib/datastream/DataStreamServer.js +252 -269
- package/dist/lib/datastream/DataStreamServer.js.map +1 -1
- package/dist/lib/datastream/index.js +1 -1
- package/dist/lib/definitions/CharacteristicDefinitions.d.ts +1 -106
- package/dist/lib/definitions/CharacteristicDefinitions.d.ts.map +1 -1
- package/dist/lib/definitions/CharacteristicDefinitions.js +2000 -2995
- package/dist/lib/definitions/CharacteristicDefinitions.js.map +1 -1
- package/dist/lib/definitions/ServiceDefinitions.d.ts +0 -32
- package/dist/lib/definitions/ServiceDefinitions.d.ts.map +1 -1
- package/dist/lib/definitions/ServiceDefinitions.js +820 -1147
- package/dist/lib/definitions/ServiceDefinitions.js.map +1 -1
- package/dist/lib/definitions/generate-definitions.js +383 -679
- package/dist/lib/definitions/generate-definitions.js.map +1 -1
- package/dist/lib/definitions/generator-configuration.js +29 -29
- package/dist/lib/definitions/generator-configuration.js.map +1 -1
- package/dist/lib/definitions/index.js +1 -1
- package/dist/lib/model/AccessoryInfo.js +101 -136
- package/dist/lib/model/AccessoryInfo.js.map +1 -1
- package/dist/lib/model/ControllerStorage.js +86 -89
- package/dist/lib/model/ControllerStorage.js.map +1 -1
- package/dist/lib/model/HAPStorage.js +15 -16
- package/dist/lib/model/HAPStorage.js.map +1 -1
- package/dist/lib/model/IdentifierCache.js +49 -49
- package/dist/lib/model/IdentifierCache.js.map +1 -1
- package/dist/lib/tv/AccessControlManagement.js +40 -44
- package/dist/lib/tv/AccessControlManagement.js.map +1 -1
- package/dist/lib/util/checkName.d.ts +2 -1
- package/dist/lib/util/checkName.d.ts.map +1 -1
- package/dist/lib/util/checkName.js +7 -11
- package/dist/lib/util/checkName.js.map +1 -1
- package/dist/lib/util/clone.js +5 -27
- package/dist/lib/util/clone.js.map +1 -1
- package/dist/lib/util/color-utils.js +8 -12
- package/dist/lib/util/color-utils.js.map +1 -1
- package/dist/lib/util/eventedhttp.d.ts.map +1 -1
- package/dist/lib/util/eventedhttp.js +301 -409
- package/dist/lib/util/eventedhttp.js.map +1 -1
- package/dist/lib/util/hapCrypto.js +31 -32
- package/dist/lib/util/hapCrypto.js.map +1 -1
- package/dist/lib/util/hapStatusError.js +9 -12
- package/dist/lib/util/hapStatusError.js.map +1 -1
- package/dist/lib/util/net-utils.js +32 -53
- package/dist/lib/util/net-utils.js.map +1 -1
- package/dist/lib/util/once.js +3 -8
- package/dist/lib/util/once.js.map +1 -1
- package/dist/lib/util/promise-utils.js +8 -13
- package/dist/lib/util/promise-utils.js.map +1 -1
- package/dist/lib/util/request-util.js +2 -3
- package/dist/lib/util/request-util.js.map +1 -1
- package/dist/lib/util/time.js +5 -5
- package/dist/lib/util/time.js.map +1 -1
- package/dist/lib/util/tlv.d.ts +0 -27
- package/dist/lib/util/tlv.d.ts.map +1 -1
- package/dist/lib/util/tlv.js +71 -113
- package/dist/lib/util/tlv.js.map +1 -1
- package/dist/lib/util/uuid.d.ts +0 -9
- package/dist/lib/util/uuid.d.ts.map +1 -1
- package/dist/lib/util/uuid.js +15 -33
- package/dist/lib/util/uuid.js.map +1 -1
- package/dist/types.d.ts +0 -35
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +10 -10
- package/dist/BridgedCore.d.ts +0 -2
- package/dist/BridgedCore.d.ts.map +0 -1
- package/dist/BridgedCore.js +0 -43
- package/dist/BridgedCore.js.map +0 -1
- package/dist/Core.d.ts +0 -2
- package/dist/Core.d.ts.map +0 -1
- package/dist/Core.js +0 -52
- package/dist/Core.js.map +0 -1
- package/dist/lib/AccessoryLoader.d.ts +0 -28
- package/dist/lib/AccessoryLoader.d.ts.map +0 -1
- package/dist/lib/AccessoryLoader.js +0 -166
- package/dist/lib/AccessoryLoader.js.map +0 -1
- package/dist/lib/camera/Camera.d.ts +0 -43
- package/dist/lib/camera/Camera.d.ts.map +0 -1
- package/dist/lib/camera/Camera.js +0 -36
- package/dist/lib/camera/Camera.js.map +0 -1
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RecordingManagement = exports.PacketDataType = exports.AudioRecordingSamplerate = exports.AudioRecordingCodecType = exports.MediaContainerType = exports.EventTriggerOption = void 0;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const crypto_1 = tslib_1.__importDefault(require("crypto"));
|
|
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 hapStatusError_1 = require("../util/hapStatusError");
|
|
12
|
+
const tlv = tslib_1.__importStar(require("../util/tlv"));
|
|
13
|
+
const debug = (0, debug_1.default)("HAP-NodeJS:Camera:RecordingManagement");
|
|
14
14
|
/**
|
|
15
15
|
* Describes the Event trigger.
|
|
16
16
|
*
|
|
@@ -136,63 +136,70 @@ var PacketDataType;
|
|
|
136
136
|
/**
|
|
137
137
|
* @group Camera
|
|
138
138
|
*/
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
139
|
+
class RecordingManagement {
|
|
140
|
+
options;
|
|
141
|
+
delegate;
|
|
142
|
+
stateChangeDelegate;
|
|
143
|
+
supportedCameraRecordingConfiguration;
|
|
144
|
+
supportedVideoRecordingConfiguration;
|
|
145
|
+
supportedAudioRecordingConfiguration;
|
|
146
|
+
/**
|
|
147
|
+
* 32 bit mask of enabled {@link EventTriggerOption}s.
|
|
148
|
+
*/
|
|
149
|
+
eventTriggerOptions;
|
|
150
|
+
recordingManagementService;
|
|
151
|
+
operatingModeService;
|
|
152
|
+
dataStreamManagement;
|
|
153
|
+
/**
|
|
154
|
+
* The currently active recording stream.
|
|
155
|
+
* Any camera only supports one stream at a time.
|
|
156
|
+
*/
|
|
157
|
+
recordingStream;
|
|
158
|
+
selectedConfiguration;
|
|
159
|
+
/**
|
|
160
|
+
* Array of sensor services (e.g. {@link Service.MotionSensor} or {@link Service.OccupancySensor}).
|
|
161
|
+
* Any service in this array owns a {@link Characteristic.StatusActive} characteristic.
|
|
162
|
+
* The value of the {@link Characteristic.HomeKitCameraActive} is mirrored towards the {@link Characteristic.StatusActive} characteristic.
|
|
163
|
+
* The array is initialized my the caller shortly after calling the constructor.
|
|
164
|
+
*/
|
|
165
|
+
sensorServices = [];
|
|
166
|
+
/**
|
|
167
|
+
* Defines if recording is enabled for this recording management.
|
|
168
|
+
*/
|
|
169
|
+
recordingActive = false;
|
|
170
|
+
constructor(options, delegate, eventTriggerOptions, services) {
|
|
153
171
|
this.options = options;
|
|
154
172
|
this.delegate = delegate;
|
|
155
|
-
|
|
173
|
+
const recordingServices = services || this.constructService();
|
|
156
174
|
this.recordingManagementService = recordingServices.recordingManagement;
|
|
157
175
|
this.operatingModeService = recordingServices.operatingMode;
|
|
158
176
|
this.dataStreamManagement = recordingServices.dataStreamManagement;
|
|
159
177
|
this.eventTriggerOptions = 0;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
var option = eventTriggerOptions_1_1.value;
|
|
163
|
-
this.eventTriggerOptions |= option; // OR
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
167
|
-
finally {
|
|
168
|
-
try {
|
|
169
|
-
if (eventTriggerOptions_1_1 && !eventTriggerOptions_1_1.done && (_a = eventTriggerOptions_1.return)) _a.call(eventTriggerOptions_1);
|
|
170
|
-
}
|
|
171
|
-
finally { if (e_1) throw e_1.error; }
|
|
178
|
+
for (const option of eventTriggerOptions) {
|
|
179
|
+
this.eventTriggerOptions |= option; // OR
|
|
172
180
|
}
|
|
173
181
|
this.supportedCameraRecordingConfiguration = this._supportedCameraRecordingConfiguration(options);
|
|
174
182
|
this.supportedVideoRecordingConfiguration = this._supportedVideoRecordingConfiguration(options.video);
|
|
175
183
|
this.supportedAudioRecordingConfiguration = this._supportedAudioStreamConfiguration(options.audio);
|
|
176
184
|
this.setupServiceHandlers();
|
|
177
185
|
}
|
|
178
|
-
|
|
179
|
-
|
|
186
|
+
constructService() {
|
|
187
|
+
const recordingManagement = new Service_1.Service.CameraRecordingManagement("", "");
|
|
180
188
|
recordingManagement.setCharacteristic(Characteristic_1.Characteristic.Active, false);
|
|
181
189
|
recordingManagement.setCharacteristic(Characteristic_1.Characteristic.RecordingAudioActive, false);
|
|
182
|
-
|
|
190
|
+
const operatingMode = new Service_1.Service.CameraOperatingMode("", "");
|
|
183
191
|
operatingMode.setCharacteristic(Characteristic_1.Characteristic.EventSnapshotsActive, true);
|
|
184
192
|
operatingMode.setCharacteristic(Characteristic_1.Characteristic.HomeKitCameraActive, true);
|
|
185
193
|
operatingMode.setCharacteristic(Characteristic_1.Characteristic.PeriodicSnapshotsActive, true);
|
|
186
|
-
|
|
194
|
+
const dataStreamManagement = new datastream_1.DataStreamManagement();
|
|
187
195
|
recordingManagement.addLinkedService(dataStreamManagement.getService());
|
|
188
196
|
return {
|
|
189
197
|
recordingManagement: recordingManagement,
|
|
190
198
|
operatingMode: operatingMode,
|
|
191
199
|
dataStreamManagement: dataStreamManagement,
|
|
192
200
|
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
var _this = this;
|
|
201
|
+
}
|
|
202
|
+
setupServiceHandlers() {
|
|
196
203
|
// update the current configuration values to the current state.
|
|
197
204
|
this.recordingManagementService.setCharacteristic(Characteristic_1.Characteristic.SupportedCameraRecordingConfiguration, this.supportedCameraRecordingConfiguration);
|
|
198
205
|
this.recordingManagementService.setCharacteristic(Characteristic_1.Characteristic.SupportedVideoRecordingConfiguration, this.supportedVideoRecordingConfiguration);
|
|
@@ -202,57 +209,44 @@ var RecordingManagement = /** @class */ (function () {
|
|
|
202
209
|
.onSet(this.handleSelectedCameraRecordingConfigurationWrite.bind(this))
|
|
203
210
|
.setProps({ adminOnlyAccess: [1 /* Access.WRITE */] });
|
|
204
211
|
this.recordingManagementService.getCharacteristic(Characteristic_1.Characteristic.Active)
|
|
205
|
-
.onSet(
|
|
206
|
-
if (!!value ===
|
|
212
|
+
.onSet(value => {
|
|
213
|
+
if (!!value === this.recordingActive) {
|
|
207
214
|
return; // skip delegate call if state didn't change!
|
|
208
215
|
}
|
|
209
|
-
|
|
210
|
-
|
|
216
|
+
this.recordingActive = !!value;
|
|
217
|
+
this.delegate.updateRecordingActive(this.recordingActive);
|
|
211
218
|
})
|
|
212
|
-
.on("change" /* CharacteristicEventTypes.CHANGE */,
|
|
219
|
+
.on("change" /* CharacteristicEventTypes.CHANGE */, () => this.stateChangeDelegate?.())
|
|
213
220
|
.setProps({ adminOnlyAccess: [1 /* Access.WRITE */] });
|
|
214
221
|
this.recordingManagementService.getCharacteristic(Characteristic_1.Characteristic.RecordingAudioActive)
|
|
215
|
-
.on("change" /* CharacteristicEventTypes.CHANGE */,
|
|
222
|
+
.on("change" /* CharacteristicEventTypes.CHANGE */, () => this.stateChangeDelegate?.());
|
|
216
223
|
this.operatingModeService.getCharacteristic(Characteristic_1.Characteristic.HomeKitCameraActive)
|
|
217
|
-
.on("change" /* CharacteristicEventTypes.CHANGE */,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
try {
|
|
221
|
-
for (var _c = tslib_1.__values(_this.sensorServices), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
222
|
-
var service = _d.value;
|
|
223
|
-
service.setCharacteristic(Characteristic_1.Characteristic.StatusActive, !!change.newValue);
|
|
224
|
-
}
|
|
224
|
+
.on("change" /* CharacteristicEventTypes.CHANGE */, change => {
|
|
225
|
+
for (const service of this.sensorServices) {
|
|
226
|
+
service.setCharacteristic(Characteristic_1.Characteristic.StatusActive, !!change.newValue);
|
|
225
227
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
try {
|
|
229
|
-
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
230
|
-
}
|
|
231
|
-
finally { if (e_2) throw e_2.error; }
|
|
228
|
+
if (!change.newValue && this.recordingStream) {
|
|
229
|
+
this.recordingStream.close(1 /* HDSProtocolSpecificErrorReason.NOT_ALLOWED */);
|
|
232
230
|
}
|
|
233
|
-
|
|
234
|
-
_this.recordingStream.close(1 /* HDSProtocolSpecificErrorReason.NOT_ALLOWED */);
|
|
235
|
-
}
|
|
236
|
-
(_b = _this.stateChangeDelegate) === null || _b === void 0 ? void 0 : _b.call(_this);
|
|
231
|
+
this.stateChangeDelegate?.();
|
|
237
232
|
})
|
|
238
233
|
.setProps({ adminOnlyAccess: [1 /* Access.WRITE */] });
|
|
239
234
|
this.operatingModeService.getCharacteristic(Characteristic_1.Characteristic.EventSnapshotsActive)
|
|
240
|
-
.on("change" /* CharacteristicEventTypes.CHANGE */,
|
|
235
|
+
.on("change" /* CharacteristicEventTypes.CHANGE */, () => this.stateChangeDelegate?.())
|
|
241
236
|
.setProps({ adminOnlyAccess: [1 /* Access.WRITE */] });
|
|
242
237
|
this.operatingModeService.getCharacteristic(Characteristic_1.Characteristic.PeriodicSnapshotsActive)
|
|
243
|
-
.on("change" /* CharacteristicEventTypes.CHANGE */,
|
|
238
|
+
.on("change" /* CharacteristicEventTypes.CHANGE */, () => this.stateChangeDelegate?.())
|
|
244
239
|
.setProps({ adminOnlyAccess: [1 /* Access.WRITE */] });
|
|
245
240
|
this.dataStreamManagement
|
|
246
241
|
.onRequestMessage("dataSend" /* Protocols.DATA_SEND */, "open" /* Topics.OPEN */, this.handleDataSendOpen.bind(this));
|
|
247
|
-
}
|
|
242
|
+
}
|
|
248
243
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
249
|
-
|
|
250
|
-
var _this = this;
|
|
244
|
+
handleDataSendOpen(connection, id, message) {
|
|
251
245
|
// for message fields see https://github.com/Supereg/secure-video-specification#41-start
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
246
|
+
const streamId = message.streamId;
|
|
247
|
+
const type = message.type;
|
|
248
|
+
const target = message.target;
|
|
249
|
+
const reason = message.reason;
|
|
256
250
|
if (target !== "controller" || type !== "ipcamera.recording") {
|
|
257
251
|
debug("[HDS %s] Received data send with unexpected target: %s or type: %d. Rejecting...", connection.remoteAddress, target, type);
|
|
258
252
|
connection.sendResponse("dataSend" /* Protocols.DATA_SEND */, "open" /* Topics.OPEN */, id, datastream_1.HDSStatus.PROTOCOL_SPECIFIC_ERROR, {
|
|
@@ -289,23 +283,22 @@ var RecordingManagement = /** @class */ (function () {
|
|
|
289
283
|
debug("[HDS %s] HDS DATA_SEND Open with reason '%s'.", connection.remoteAddress, reason);
|
|
290
284
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
291
285
|
this.recordingStream = new CameraRecordingStream(connection, this.delegate, id, streamId);
|
|
292
|
-
this.recordingStream.on("closed" /* CameraRecordingStreamEvents.CLOSED */,
|
|
286
|
+
this.recordingStream.on("closed" /* CameraRecordingStreamEvents.CLOSED */, () => {
|
|
293
287
|
debug("[HDS %s] Removing active recoding session from recording management!", connection.remoteAddress);
|
|
294
|
-
|
|
288
|
+
this.recordingStream = undefined;
|
|
295
289
|
});
|
|
296
290
|
this.recordingStream.startStreaming();
|
|
297
|
-
}
|
|
298
|
-
|
|
291
|
+
}
|
|
292
|
+
handleSelectedCameraRecordingConfigurationRead() {
|
|
299
293
|
if (!this.selectedConfiguration) {
|
|
300
294
|
throw new hapStatusError_1.HapStatusError(-70402 /* HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
|
|
301
295
|
}
|
|
302
296
|
return this.selectedConfiguration.base64;
|
|
303
|
-
}
|
|
297
|
+
}
|
|
304
298
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
var changed = ((_a = this.selectedConfiguration) === null || _a === void 0 ? void 0 : _a.base64) !== value;
|
|
299
|
+
handleSelectedCameraRecordingConfigurationWrite(value) {
|
|
300
|
+
const configuration = this.parseSelectedConfiguration(value);
|
|
301
|
+
const changed = this.selectedConfiguration?.base64 !== value;
|
|
309
302
|
this.selectedConfiguration = {
|
|
310
303
|
parsed: configuration,
|
|
311
304
|
base64: value,
|
|
@@ -313,38 +306,38 @@ var RecordingManagement = /** @class */ (function () {
|
|
|
313
306
|
if (changed) {
|
|
314
307
|
this.delegate.updateRecordingConfiguration(this.selectedConfiguration.parsed);
|
|
315
308
|
// notify controller storage about updated values!
|
|
316
|
-
|
|
309
|
+
this.stateChangeDelegate?.();
|
|
317
310
|
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
311
|
+
}
|
|
312
|
+
parseSelectedConfiguration(value) {
|
|
313
|
+
const decoded = tlv.decode(Buffer.from(value, "base64"));
|
|
314
|
+
const recording = tlv.decode(decoded[1 /* SelectedCameraRecordingConfigurationTypes.SELECTED_RECORDING_CONFIGURATION */]);
|
|
315
|
+
const video = tlv.decode(decoded[2 /* SelectedCameraRecordingConfigurationTypes.SELECTED_VIDEO_CONFIGURATION */]);
|
|
316
|
+
const audio = tlv.decode(decoded[3 /* SelectedCameraRecordingConfigurationTypes.SELECTED_AUDIO_CONFIGURATION */]);
|
|
317
|
+
const prebufferLength = recording[1 /* SupportedCameraRecordingConfigurationTypes.PREBUFFER_LENGTH */].readInt32LE(0);
|
|
318
|
+
let eventTriggerOptions = recording[2 /* SupportedCameraRecordingConfigurationTypes.EVENT_TRIGGER_OPTIONS */].readInt32LE(0);
|
|
319
|
+
const mediaContainerConfiguration = tlv.decode(recording[3 /* SupportedCameraRecordingConfigurationTypes.MEDIA_CONTAINER_CONFIGURATIONS */]);
|
|
320
|
+
const containerType = mediaContainerConfiguration[1 /* MediaContainerConfigurationTypes.MEDIA_CONTAINER_TYPE */][0];
|
|
321
|
+
const mediaContainerParameters = tlv.decode(mediaContainerConfiguration[2 /* MediaContainerConfigurationTypes.MEDIA_CONTAINER_PARAMETERS */]);
|
|
322
|
+
const fragmentLength = mediaContainerParameters[1 /* MediaContainerParameterTypes.FRAGMENT_LENGTH */].readInt32LE(0);
|
|
323
|
+
const videoCodec = video[1 /* VideoCodecConfigurationTypes.CODEC_TYPE */][0];
|
|
324
|
+
const videoParameters = tlv.decode(video[2 /* VideoCodecConfigurationTypes.CODEC_PARAMETERS */]);
|
|
325
|
+
const videoAttributes = tlv.decode(video[3 /* VideoCodecConfigurationTypes.ATTRIBUTES */]);
|
|
326
|
+
const profile = videoParameters[1 /* VideoCodecParametersTypes.PROFILE_ID */][0];
|
|
327
|
+
const level = videoParameters[2 /* VideoCodecParametersTypes.LEVEL */][0];
|
|
328
|
+
const videoBitrate = videoParameters[3 /* VideoCodecParametersTypes.BITRATE */].readInt32LE(0);
|
|
329
|
+
const iFrameInterval = videoParameters[4 /* VideoCodecParametersTypes.IFRAME_INTERVAL */].readInt32LE(0);
|
|
330
|
+
const width = videoAttributes[1 /* VideoAttributesTypes.IMAGE_WIDTH */].readInt16LE(0);
|
|
331
|
+
const height = videoAttributes[2 /* VideoAttributesTypes.IMAGE_HEIGHT */].readInt16LE(0);
|
|
332
|
+
const framerate = videoAttributes[3 /* VideoAttributesTypes.FRAME_RATE */][0];
|
|
333
|
+
const audioCodec = audio[1 /* AudioCodecConfigurationTypes.CODEC_TYPE */][0];
|
|
334
|
+
const audioParameters = tlv.decode(audio[2 /* AudioCodecConfigurationTypes.CODEC_PARAMETERS */]);
|
|
335
|
+
const audioChannels = audioParameters[1 /* AudioCodecParametersTypes.CHANNEL */][0];
|
|
336
|
+
const samplerate = audioParameters[3 /* AudioCodecParametersTypes.SAMPLE_RATE */][0];
|
|
337
|
+
const audioBitrateMode = audioParameters[2 /* AudioCodecParametersTypes.BIT_RATE */][0];
|
|
338
|
+
const audioBitrate = audioParameters[4 /* AudioCodecParametersTypes.MAX_AUDIO_BITRATE */].readUInt32LE(0);
|
|
339
|
+
const typedEventTriggers = [];
|
|
340
|
+
let bit_index = 0;
|
|
348
341
|
while (eventTriggerOptions > 0) {
|
|
349
342
|
if (eventTriggerOptions & 0x01) { // of the lowest bit is set add the next event trigger option
|
|
350
343
|
typedEventTriggers.push(1 << bit_index);
|
|
@@ -357,7 +350,7 @@ var RecordingManagement = /** @class */ (function () {
|
|
|
357
350
|
eventTriggerTypes: typedEventTriggers,
|
|
358
351
|
mediaContainerConfiguration: {
|
|
359
352
|
type: containerType,
|
|
360
|
-
fragmentLength
|
|
353
|
+
fragmentLength,
|
|
361
354
|
},
|
|
362
355
|
videoCodec: {
|
|
363
356
|
type: videoCodec,
|
|
@@ -370,104 +363,100 @@ var RecordingManagement = /** @class */ (function () {
|
|
|
370
363
|
resolution: [width, height, framerate],
|
|
371
364
|
},
|
|
372
365
|
audioCodec: {
|
|
373
|
-
audioChannels
|
|
366
|
+
audioChannels,
|
|
374
367
|
type: audioCodec,
|
|
375
|
-
samplerate
|
|
368
|
+
samplerate,
|
|
376
369
|
bitrateMode: audioBitrateMode,
|
|
377
370
|
bitrate: audioBitrate,
|
|
378
371
|
},
|
|
379
372
|
};
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
373
|
+
}
|
|
374
|
+
_supportedCameraRecordingConfiguration(options) {
|
|
375
|
+
const mediaContainers = Array.isArray(options.mediaContainerConfiguration)
|
|
383
376
|
? options.mediaContainerConfiguration
|
|
384
377
|
: [options.mediaContainerConfiguration];
|
|
385
|
-
|
|
386
|
-
|
|
378
|
+
const prebufferLength = Buffer.alloc(4);
|
|
379
|
+
const eventTriggerOptions = Buffer.alloc(8);
|
|
387
380
|
prebufferLength.writeInt32LE(options.prebufferLength, 0);
|
|
388
381
|
eventTriggerOptions.writeInt32LE(this.eventTriggerOptions, 0);
|
|
389
|
-
return tlv.encode(1 /* SupportedCameraRecordingConfigurationTypes.PREBUFFER_LENGTH */, prebufferLength, 2 /* SupportedCameraRecordingConfigurationTypes.EVENT_TRIGGER_OPTIONS */, eventTriggerOptions, 3 /* SupportedCameraRecordingConfigurationTypes.MEDIA_CONTAINER_CONFIGURATIONS */, mediaContainers.map(
|
|
390
|
-
|
|
382
|
+
return tlv.encode(1 /* SupportedCameraRecordingConfigurationTypes.PREBUFFER_LENGTH */, prebufferLength, 2 /* SupportedCameraRecordingConfigurationTypes.EVENT_TRIGGER_OPTIONS */, eventTriggerOptions, 3 /* SupportedCameraRecordingConfigurationTypes.MEDIA_CONTAINER_CONFIGURATIONS */, mediaContainers.map(config => {
|
|
383
|
+
const fragmentLength = Buffer.alloc(4);
|
|
391
384
|
fragmentLength.writeInt32LE(config.fragmentLength, 0);
|
|
392
385
|
return tlv.encode(1 /* MediaContainerConfigurationTypes.MEDIA_CONTAINER_TYPE */, config.type, 2 /* MediaContainerConfigurationTypes.MEDIA_CONTAINER_PARAMETERS */, tlv.encode(1 /* MediaContainerParameterTypes.FRAGMENT_LENGTH */, fragmentLength));
|
|
393
386
|
})).toString("base64");
|
|
394
|
-
}
|
|
395
|
-
|
|
387
|
+
}
|
|
388
|
+
_supportedVideoRecordingConfiguration(videoOptions) {
|
|
396
389
|
if (!videoOptions.parameters) {
|
|
397
390
|
throw new Error("Video parameters cannot be undefined");
|
|
398
391
|
}
|
|
399
392
|
if (!videoOptions.resolutions) {
|
|
400
393
|
throw new Error("Video resolutions cannot be undefined");
|
|
401
394
|
}
|
|
402
|
-
|
|
403
|
-
|
|
395
|
+
const codecParameters = tlv.encode(1 /* VideoCodecParametersTypes.PROFILE_ID */, videoOptions.parameters.profiles, 2 /* VideoCodecParametersTypes.LEVEL */, videoOptions.parameters.levels);
|
|
396
|
+
const videoStreamConfiguration = tlv.encode(1 /* VideoCodecConfigurationTypes.CODEC_TYPE */, videoOptions.type, 2 /* VideoCodecConfigurationTypes.CODEC_PARAMETERS */, codecParameters, 3 /* VideoCodecConfigurationTypes.ATTRIBUTES */, videoOptions.resolutions.map(resolution => {
|
|
404
397
|
if (resolution.length !== 3) {
|
|
405
398
|
throw new Error("Unexpected video resolution");
|
|
406
399
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
400
|
+
const width = Buffer.alloc(2);
|
|
401
|
+
const height = Buffer.alloc(2);
|
|
402
|
+
const frameRate = Buffer.alloc(1);
|
|
410
403
|
width.writeUInt16LE(resolution[0], 0);
|
|
411
404
|
height.writeUInt16LE(resolution[1], 0);
|
|
412
405
|
frameRate.writeUInt8(resolution[2], 0);
|
|
413
406
|
return tlv.encode(1 /* VideoAttributesTypes.IMAGE_WIDTH */, width, 2 /* VideoAttributesTypes.IMAGE_HEIGHT */, height, 3 /* VideoAttributesTypes.FRAME_RATE */, frameRate);
|
|
414
407
|
}));
|
|
415
408
|
return tlv.encode(1 /* SupportedVideoRecordingConfigurationTypes.VIDEO_CODEC_CONFIGURATION */, videoStreamConfiguration).toString("base64");
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
|
|
409
|
+
}
|
|
410
|
+
_supportedAudioStreamConfiguration(audioOptions) {
|
|
411
|
+
const audioCodecs = Array.isArray(audioOptions.codecs)
|
|
419
412
|
? audioOptions.codecs
|
|
420
413
|
: [audioOptions.codecs];
|
|
421
414
|
if (audioCodecs.length === 0) {
|
|
422
415
|
throw Error("CameraRecordingOptions.audio: At least one audio codec configuration must be specified!");
|
|
423
416
|
}
|
|
424
|
-
|
|
425
|
-
|
|
417
|
+
const codecConfigurations = audioCodecs.map(codec => {
|
|
418
|
+
const providedSamplerates = Array.isArray(codec.samplerate)
|
|
426
419
|
? codec.samplerate
|
|
427
420
|
: [codec.samplerate];
|
|
428
421
|
if (providedSamplerates.length === 0) {
|
|
429
422
|
throw new Error("CameraRecordingOptions.audio.codecs: Audio samplerate cannot be empty!");
|
|
430
423
|
}
|
|
431
|
-
|
|
424
|
+
const audioParameters = tlv.encode(1 /* AudioCodecParametersTypes.CHANNEL */, Math.max(1, codec.audioChannels || 1), 2 /* AudioCodecParametersTypes.BIT_RATE */, codec.bitrateMode || 0 /* AudioBitrate.VARIABLE */, 3 /* AudioCodecParametersTypes.SAMPLE_RATE */, providedSamplerates);
|
|
432
425
|
return tlv.encode(1 /* AudioCodecConfigurationTypes.CODEC_TYPE */, codec.type, 2 /* AudioCodecConfigurationTypes.CODEC_PARAMETERS */, audioParameters);
|
|
433
426
|
});
|
|
434
427
|
return tlv.encode(1 /* SupportedAudioRecordingConfigurationTypes.AUDIO_CODEC_CONFIGURATION */, codecConfigurations).toString("base64");
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
var configurationHash = crypto_1.default.createHash(algorithm);
|
|
428
|
+
}
|
|
429
|
+
computeConfigurationHash(algorithm = "sha256") {
|
|
430
|
+
const configurationHash = crypto_1.default.createHash(algorithm);
|
|
439
431
|
configurationHash.update(this.supportedCameraRecordingConfiguration);
|
|
440
432
|
configurationHash.update(this.supportedVideoRecordingConfiguration);
|
|
441
433
|
configurationHash.update(this.supportedAudioRecordingConfiguration);
|
|
442
434
|
return configurationHash.digest().toString("hex");
|
|
443
|
-
}
|
|
435
|
+
}
|
|
444
436
|
/**
|
|
445
437
|
* @private
|
|
446
438
|
*/
|
|
447
|
-
|
|
448
|
-
var _a;
|
|
439
|
+
serialize() {
|
|
449
440
|
return {
|
|
450
441
|
configurationHash: {
|
|
451
442
|
algorithm: "sha256",
|
|
452
443
|
hash: this.computeConfigurationHash("sha256"),
|
|
453
444
|
},
|
|
454
|
-
selectedConfiguration:
|
|
445
|
+
selectedConfiguration: this.selectedConfiguration?.base64,
|
|
455
446
|
recordingActive: this.recordingActive,
|
|
456
447
|
recordingAudioActive: !!this.recordingManagementService.getCharacteristic(Characteristic_1.Characteristic.RecordingAudioActive).value,
|
|
457
448
|
eventSnapshotsActive: !!this.operatingModeService.getCharacteristic(Characteristic_1.Characteristic.EventSnapshotsActive).value,
|
|
458
449
|
homeKitCameraActive: !!this.operatingModeService.getCharacteristic(Characteristic_1.Characteristic.HomeKitCameraActive).value,
|
|
459
450
|
periodicSnapshotsActive: !!this.operatingModeService.getCharacteristic(Characteristic_1.Characteristic.PeriodicSnapshotsActive).value,
|
|
460
451
|
};
|
|
461
|
-
}
|
|
452
|
+
}
|
|
462
453
|
/**
|
|
463
454
|
* @private
|
|
464
455
|
*/
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
var _b;
|
|
468
|
-
var changedState = false;
|
|
456
|
+
deserialize(serialized) {
|
|
457
|
+
let changedState = false;
|
|
469
458
|
// we only restore the `selectedConfiguration` if our supported configuration hasn't changed.
|
|
470
|
-
|
|
459
|
+
const currentConfigurationHash = this.computeConfigurationHash(serialized.configurationHash.algorithm);
|
|
471
460
|
if (serialized.selectedConfiguration) {
|
|
472
461
|
if (currentConfigurationHash === serialized.configurationHash.hash) {
|
|
473
462
|
this.selectedConfiguration = {
|
|
@@ -485,18 +474,8 @@ var RecordingManagement = /** @class */ (function () {
|
|
|
485
474
|
this.operatingModeService.updateCharacteristic(Characteristic_1.Characteristic.EventSnapshotsActive, serialized.eventSnapshotsActive);
|
|
486
475
|
this.operatingModeService.updateCharacteristic(Characteristic_1.Characteristic.PeriodicSnapshotsActive, serialized.periodicSnapshotsActive);
|
|
487
476
|
this.operatingModeService.updateCharacteristic(Characteristic_1.Characteristic.HomeKitCameraActive, serialized.homeKitCameraActive);
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
var service = _d.value;
|
|
491
|
-
service.setCharacteristic(Characteristic_1.Characteristic.StatusActive, serialized.homeKitCameraActive);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
495
|
-
finally {
|
|
496
|
-
try {
|
|
497
|
-
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
498
|
-
}
|
|
499
|
-
finally { if (e_3) throw e_3.error; }
|
|
477
|
+
for (const service of this.sensorServices) {
|
|
478
|
+
service.setCharacteristic(Characteristic_1.Characteristic.StatusActive, serialized.homeKitCameraActive);
|
|
500
479
|
}
|
|
501
480
|
try {
|
|
502
481
|
if (this.selectedConfiguration) {
|
|
@@ -510,38 +489,27 @@ var RecordingManagement = /** @class */ (function () {
|
|
|
510
489
|
console.error("Failed to properly initialize CameraRecordingDelegate from persistent storage: " + error.stack);
|
|
511
490
|
}
|
|
512
491
|
if (changedState) {
|
|
513
|
-
|
|
492
|
+
this.stateChangeDelegate?.();
|
|
514
493
|
}
|
|
515
|
-
}
|
|
494
|
+
}
|
|
516
495
|
/**
|
|
517
496
|
* @private
|
|
518
497
|
*/
|
|
519
|
-
|
|
498
|
+
setupStateChangeDelegate(delegate) {
|
|
520
499
|
this.stateChangeDelegate = delegate;
|
|
521
|
-
}
|
|
522
|
-
|
|
500
|
+
}
|
|
501
|
+
destroy() {
|
|
523
502
|
this.dataStreamManagement.destroy();
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
var e_4, _a;
|
|
503
|
+
}
|
|
504
|
+
handleFactoryReset() {
|
|
527
505
|
this.selectedConfiguration = undefined;
|
|
528
506
|
this.recordingManagementService.updateCharacteristic(Characteristic_1.Characteristic.Active, false);
|
|
529
507
|
this.recordingManagementService.updateCharacteristic(Characteristic_1.Characteristic.RecordingAudioActive, false);
|
|
530
508
|
this.operatingModeService.updateCharacteristic(Characteristic_1.Characteristic.EventSnapshotsActive, true);
|
|
531
509
|
this.operatingModeService.updateCharacteristic(Characteristic_1.Characteristic.PeriodicSnapshotsActive, true);
|
|
532
510
|
this.operatingModeService.updateCharacteristic(Characteristic_1.Characteristic.HomeKitCameraActive, true);
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
var service = _c.value;
|
|
536
|
-
service.setCharacteristic(Characteristic_1.Characteristic.StatusActive, true);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
540
|
-
finally {
|
|
541
|
-
try {
|
|
542
|
-
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
543
|
-
}
|
|
544
|
-
finally { if (e_4) throw e_4.error; }
|
|
511
|
+
for (const service of this.sensorServices) {
|
|
512
|
+
service.setCharacteristic(Characteristic_1.Characteristic.StatusActive, true);
|
|
545
513
|
}
|
|
546
514
|
try {
|
|
547
515
|
// notifying the delegate about the updated state
|
|
@@ -551,9 +519,8 @@ var RecordingManagement = /** @class */ (function () {
|
|
|
551
519
|
catch (error) {
|
|
552
520
|
console.error("CameraRecordingDelegate failed to update state after handleFactoryReset: " + error.stack);
|
|
553
521
|
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
}());
|
|
522
|
+
}
|
|
523
|
+
}
|
|
557
524
|
exports.RecordingManagement = RecordingManagement;
|
|
558
525
|
/**
|
|
559
526
|
* @group Camera
|
|
@@ -574,199 +541,172 @@ var CameraRecordingStreamEvents;
|
|
|
574
541
|
* @group Camera
|
|
575
542
|
*/
|
|
576
543
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
544
|
+
class CameraRecordingStream extends events_1.EventEmitter {
|
|
545
|
+
connection;
|
|
546
|
+
delegate;
|
|
547
|
+
hdsRequestId;
|
|
548
|
+
streamId;
|
|
549
|
+
closed = false;
|
|
550
|
+
eventHandler = {
|
|
551
|
+
["close" /* Topics.CLOSE */]: this.handleDataSendClose.bind(this),
|
|
552
|
+
["ack" /* Topics.ACK */]: this.handleDataSendAck.bind(this),
|
|
553
|
+
};
|
|
554
|
+
requestHandler = undefined;
|
|
555
|
+
closeListener;
|
|
556
|
+
generator;
|
|
557
|
+
/**
|
|
558
|
+
* This timeout is used to detect non-returning generators.
|
|
559
|
+
* When we signal the delegate that it is being closed its generator must return withing 10s.
|
|
560
|
+
*/
|
|
561
|
+
generatorTimeout;
|
|
562
|
+
/**
|
|
563
|
+
* This timer is used to check if the stream is properly closed when we expect it to do so.
|
|
564
|
+
* When we expect a close signal from the remote, we wait 12s for it. Otherwise, we abort and close it ourselves.
|
|
565
|
+
* This ensures memory is freed, and that we recover fast from erroneous states.
|
|
566
|
+
*/
|
|
567
|
+
closingTimeout;
|
|
568
|
+
constructor(connection, delegate, requestId, streamId) {
|
|
569
|
+
super();
|
|
570
|
+
this.connection = connection;
|
|
571
|
+
this.delegate = delegate;
|
|
572
|
+
this.hdsRequestId = requestId;
|
|
573
|
+
this.streamId = streamId;
|
|
574
|
+
this.connection.on("closed" /* DataStreamConnectionEvent.CLOSED */, this.closeListener = this.handleDataStreamConnectionClosed.bind(this));
|
|
575
|
+
this.connection.addProtocolHandler("dataSend" /* Protocols.DATA_SEND */, this);
|
|
595
576
|
}
|
|
596
|
-
|
|
577
|
+
startStreaming() {
|
|
597
578
|
// noinspection JSIgnoredPromiseFromCall
|
|
598
579
|
this._startStreaming();
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
return tslib_1.__generator(this, function (_g) {
|
|
605
|
-
switch (_g.label) {
|
|
606
|
-
case 0:
|
|
607
|
-
debug("[HDS %s] Sending DATA_SEND OPEN response for streamId %d", this.connection.remoteAddress, this.streamId);
|
|
608
|
-
this.connection.sendResponse("dataSend" /* Protocols.DATA_SEND */, "open" /* Topics.OPEN */, this.hdsRequestId, datastream_1.HDSStatus.SUCCESS, {
|
|
609
|
-
status: datastream_1.HDSStatus.SUCCESS,
|
|
610
|
-
});
|
|
611
|
-
maxChunk = 0x40000;
|
|
612
|
-
initialization = true;
|
|
613
|
-
dataSequenceNumber = 1;
|
|
614
|
-
lastFragmentWasMarkedLast = false;
|
|
615
|
-
_g.label = 1;
|
|
616
|
-
case 1:
|
|
617
|
-
_g.trys.push([1, 14, 15, 16]);
|
|
618
|
-
this.generator = this.delegate.handleRecordingStreamRequest(this.streamId);
|
|
619
|
-
_g.label = 2;
|
|
620
|
-
case 2:
|
|
621
|
-
_g.trys.push([2, 7, 8, 13]);
|
|
622
|
-
_a = true, _b = tslib_1.__asyncValues(this.generator);
|
|
623
|
-
_g.label = 3;
|
|
624
|
-
case 3: return [4 /*yield*/, _b.next()];
|
|
625
|
-
case 4:
|
|
626
|
-
if (!(_c = _g.sent(), _d = _c.done, !_d)) return [3 /*break*/, 6];
|
|
627
|
-
_f = _c.value;
|
|
628
|
-
_a = false;
|
|
629
|
-
packet = _f;
|
|
630
|
-
if (this.closed) {
|
|
631
|
-
console.error("[HDS ".concat(this.connection.remoteAddress, "] Delegate yielded fragment after stream ").concat(this.streamId, " was already closed!"));
|
|
632
|
-
return [3 /*break*/, 6];
|
|
633
|
-
}
|
|
634
|
-
if (lastFragmentWasMarkedLast) {
|
|
635
|
-
console.error("[HDS ".concat(this.connection.remoteAddress, "] Delegate yielded fragment for stream ").concat(this.streamId, " after already signaling end of stream!"));
|
|
636
|
-
return [3 /*break*/, 6];
|
|
637
|
-
}
|
|
638
|
-
fragment = packet.data;
|
|
639
|
-
offset = 0;
|
|
640
|
-
dataChunkSequenceNumber = 1;
|
|
641
|
-
while (offset < fragment.length) {
|
|
642
|
-
data = fragment.slice(offset, offset + maxChunk);
|
|
643
|
-
offset += data.length;
|
|
644
|
-
event = {
|
|
645
|
-
streamId: this.streamId,
|
|
646
|
-
packets: [{
|
|
647
|
-
data: data,
|
|
648
|
-
metadata: {
|
|
649
|
-
dataType: initialization ? "mediaInitialization" /* PacketDataType.MEDIA_INITIALIZATION */ : "mediaFragment" /* PacketDataType.MEDIA_FRAGMENT */,
|
|
650
|
-
dataSequenceNumber: dataSequenceNumber,
|
|
651
|
-
dataChunkSequenceNumber: dataChunkSequenceNumber,
|
|
652
|
-
isLastDataChunk: offset >= fragment.length,
|
|
653
|
-
dataTotalSize: dataChunkSequenceNumber === 1 ? fragment.length : undefined,
|
|
654
|
-
},
|
|
655
|
-
}],
|
|
656
|
-
endOfStream: offset >= fragment.length ? Boolean(packet.isLast).valueOf() : undefined,
|
|
657
|
-
};
|
|
658
|
-
debug("[HDS %s] Sending DATA_SEND DATA for stream %d with metadata: %o and length %d; EoS: %s", this.connection.remoteAddress, this.streamId, event.packets[0].metadata, data.length, event.endOfStream);
|
|
659
|
-
this.connection.sendEvent("dataSend" /* Protocols.DATA_SEND */, "data" /* Topics.DATA */, event);
|
|
660
|
-
dataChunkSequenceNumber++;
|
|
661
|
-
initialization = false;
|
|
662
|
-
}
|
|
663
|
-
lastFragmentWasMarkedLast = packet.isLast;
|
|
664
|
-
if (packet.isLast) {
|
|
665
|
-
return [3 /*break*/, 6];
|
|
666
|
-
}
|
|
667
|
-
dataSequenceNumber++;
|
|
668
|
-
_g.label = 5;
|
|
669
|
-
case 5:
|
|
670
|
-
_a = true;
|
|
671
|
-
return [3 /*break*/, 3];
|
|
672
|
-
case 6: return [3 /*break*/, 13];
|
|
673
|
-
case 7:
|
|
674
|
-
e_5_1 = _g.sent();
|
|
675
|
-
e_5 = { error: e_5_1 };
|
|
676
|
-
return [3 /*break*/, 13];
|
|
677
|
-
case 8:
|
|
678
|
-
_g.trys.push([8, , 11, 12]);
|
|
679
|
-
if (!(!_a && !_d && (_e = _b.return))) return [3 /*break*/, 10];
|
|
680
|
-
return [4 /*yield*/, _e.call(_b)];
|
|
681
|
-
case 9:
|
|
682
|
-
_g.sent();
|
|
683
|
-
_g.label = 10;
|
|
684
|
-
case 10: return [3 /*break*/, 12];
|
|
685
|
-
case 11:
|
|
686
|
-
if (e_5) throw e_5.error;
|
|
687
|
-
return [7 /*endfinally*/];
|
|
688
|
-
case 12: return [7 /*endfinally*/];
|
|
689
|
-
case 13:
|
|
690
|
-
if (!lastFragmentWasMarkedLast && !this.closed) {
|
|
691
|
-
// Delegate violates the contract. Exited normally on a non-closed stream without properly setting `isLast`.
|
|
692
|
-
console.warn("[HDS ".concat(this.connection.remoteAddress, "] Delegate finished streaming for ").concat(this.streamId, " without setting RecordingPacket.isLast. ") +
|
|
693
|
-
"Can't notify Controller about endOfStream!");
|
|
694
|
-
}
|
|
695
|
-
return [3 /*break*/, 16];
|
|
696
|
-
case 14:
|
|
697
|
-
error_1 = _g.sent();
|
|
698
|
-
if (this.closed) {
|
|
699
|
-
console.warn("[HDS ".concat(this.connection.remoteAddress, "] Encountered unexpected error on already closed recording stream ").concat(this.streamId, ": ").concat(error_1.stack));
|
|
700
|
-
}
|
|
701
|
-
else {
|
|
702
|
-
closeReason = 5 /* HDSProtocolSpecificErrorReason.UNEXPECTED_FAILURE */;
|
|
703
|
-
if (error_1 instanceof datastream_1.HDSProtocolError) {
|
|
704
|
-
closeReason = error_1.reason;
|
|
705
|
-
debug("[HDS %s] Delegate signaled to close the recording stream %d.", this.connection.remoteAddress, this.streamId);
|
|
706
|
-
}
|
|
707
|
-
else if (error_1 instanceof datastream_1.HDSConnectionError && error_1.type === 2 /* HDSConnectionErrorType.CLOSED_SOCKET */) {
|
|
708
|
-
// we are probably on a shutdown or just late. Connection is dead. End the stream!
|
|
709
|
-
debug("[HDS %s] Exited recording stream due to closed HDS socket: stream id %d.", this.connection.remoteAddress, this.streamId);
|
|
710
|
-
return [2 /*return*/]; // execute finally and then exit (we want to skip the `sendEvent` below)
|
|
711
|
-
}
|
|
712
|
-
else {
|
|
713
|
-
console.error("[HDS ".concat(this.connection.remoteAddress, "] Encountered unexpected error for recording stream ").concat(this.streamId, ": ").concat(error_1.stack));
|
|
714
|
-
}
|
|
715
|
-
// call close to go through standard close routine!
|
|
716
|
-
this.close(closeReason);
|
|
717
|
-
}
|
|
718
|
-
return [2 /*return*/];
|
|
719
|
-
case 15:
|
|
720
|
-
this.generator = undefined;
|
|
721
|
-
if (this.generatorTimeout) {
|
|
722
|
-
clearTimeout(this.generatorTimeout);
|
|
723
|
-
}
|
|
724
|
-
if (!this.closed) {
|
|
725
|
-
// e.g. when returning with `endOfStream` we rely on the HomeHub to send an ACK event to close the recording.
|
|
726
|
-
// With this timer we ensure that the HomeHub has the chance to close the stream gracefully but at the same time
|
|
727
|
-
// ensure that if something fails the recording stream is freed nonetheless.
|
|
728
|
-
this.kickOffCloseTimeout();
|
|
729
|
-
}
|
|
730
|
-
return [7 /*endfinally*/];
|
|
731
|
-
case 16:
|
|
732
|
-
debug("[HDS %s] Finished DATA_SEND transmission for stream %d!", this.connection.remoteAddress, this.streamId);
|
|
733
|
-
return [2 /*return*/];
|
|
734
|
-
}
|
|
735
|
-
});
|
|
580
|
+
}
|
|
581
|
+
async _startStreaming() {
|
|
582
|
+
debug("[HDS %s] Sending DATA_SEND OPEN response for streamId %d", this.connection.remoteAddress, this.streamId);
|
|
583
|
+
this.connection.sendResponse("dataSend" /* Protocols.DATA_SEND */, "open" /* Topics.OPEN */, this.hdsRequestId, datastream_1.HDSStatus.SUCCESS, {
|
|
584
|
+
status: datastream_1.HDSStatus.SUCCESS,
|
|
736
585
|
});
|
|
737
|
-
|
|
586
|
+
// 256 KiB (1KiB to 900 KiB)
|
|
587
|
+
const maxChunk = 0x40000;
|
|
588
|
+
// The first buffer which we receive from the generator is always the `mediaInitialization` packet (mp4 `moov` box).
|
|
589
|
+
let initialization = true;
|
|
590
|
+
let dataSequenceNumber = 1;
|
|
591
|
+
// tracks if the last received RecordingPacket was yielded with `isLast=true`.
|
|
592
|
+
let lastFragmentWasMarkedLast = false;
|
|
593
|
+
try {
|
|
594
|
+
this.generator = this.delegate.handleRecordingStreamRequest(this.streamId);
|
|
595
|
+
for await (const packet of this.generator) {
|
|
596
|
+
if (this.closed) {
|
|
597
|
+
console.error(`[HDS ${this.connection.remoteAddress}] Delegate yielded fragment after stream ${this.streamId} was already closed!`);
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
if (lastFragmentWasMarkedLast) {
|
|
601
|
+
console.error(`[HDS ${this.connection.remoteAddress}] Delegate yielded fragment for stream ${this.streamId} after already signaling end of stream!`);
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
const fragment = packet.data;
|
|
605
|
+
let offset = 0;
|
|
606
|
+
let dataChunkSequenceNumber = 1;
|
|
607
|
+
while (offset < fragment.length) {
|
|
608
|
+
if (this.closed) {
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
const data = fragment.slice(offset, offset + maxChunk);
|
|
612
|
+
offset += data.length;
|
|
613
|
+
// see https://github.com/Supereg/secure-video-specification#42-binary-data
|
|
614
|
+
const event = {
|
|
615
|
+
streamId: this.streamId,
|
|
616
|
+
packets: [{
|
|
617
|
+
data: data,
|
|
618
|
+
metadata: {
|
|
619
|
+
dataType: initialization ? "mediaInitialization" /* PacketDataType.MEDIA_INITIALIZATION */ : "mediaFragment" /* PacketDataType.MEDIA_FRAGMENT */,
|
|
620
|
+
dataSequenceNumber: dataSequenceNumber,
|
|
621
|
+
dataChunkSequenceNumber: dataChunkSequenceNumber,
|
|
622
|
+
isLastDataChunk: offset >= fragment.length,
|
|
623
|
+
dataTotalSize: dataChunkSequenceNumber === 1 ? fragment.length : undefined,
|
|
624
|
+
},
|
|
625
|
+
}],
|
|
626
|
+
endOfStream: offset >= fragment.length ? Boolean(packet.isLast).valueOf() : undefined,
|
|
627
|
+
};
|
|
628
|
+
debug("[HDS %s] Sending DATA_SEND DATA for stream %d with metadata: %o and length %d; EoS: %s", this.connection.remoteAddress, this.streamId, event.packets[0].metadata, data.length, event.endOfStream);
|
|
629
|
+
this.connection.sendEvent("dataSend" /* Protocols.DATA_SEND */, "data" /* Topics.DATA */, event);
|
|
630
|
+
dataChunkSequenceNumber++;
|
|
631
|
+
initialization = false;
|
|
632
|
+
}
|
|
633
|
+
lastFragmentWasMarkedLast = packet.isLast;
|
|
634
|
+
if (packet.isLast) {
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
dataSequenceNumber++;
|
|
638
|
+
}
|
|
639
|
+
if (!lastFragmentWasMarkedLast && !this.closed) {
|
|
640
|
+
// Delegate violates the contract. Exited normally on a non-closed stream without properly setting `isLast`.
|
|
641
|
+
console.warn(`[HDS ${this.connection.remoteAddress}] Delegate finished streaming for ${this.streamId} without setting RecordingPacket.isLast. ` +
|
|
642
|
+
"Can't notify Controller about endOfStream!");
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
catch (error) {
|
|
646
|
+
if (this.closed) {
|
|
647
|
+
console.warn(`[HDS ${this.connection.remoteAddress}] Encountered unexpected error on already closed recording stream ${this.streamId}: ${error.stack}`);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
let closeReason = 5 /* HDSProtocolSpecificErrorReason.UNEXPECTED_FAILURE */;
|
|
651
|
+
if (error instanceof datastream_1.HDSProtocolError) {
|
|
652
|
+
closeReason = error.reason;
|
|
653
|
+
debug("[HDS %s] Delegate signaled to close the recording stream %d.", this.connection.remoteAddress, this.streamId);
|
|
654
|
+
}
|
|
655
|
+
else if (error instanceof datastream_1.HDSConnectionError && error.type === 2 /* HDSConnectionErrorType.CLOSED_SOCKET */) {
|
|
656
|
+
// we are probably on a shutdown or just late. Connection is dead. End the stream!
|
|
657
|
+
debug("[HDS %s] Exited recording stream due to closed HDS socket: stream id %d.", this.connection.remoteAddress, this.streamId);
|
|
658
|
+
return; // execute finally and then exit (we want to skip the `sendEvent` below)
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
console.error(`[HDS ${this.connection.remoteAddress}] Encountered unexpected error for recording stream ${this.streamId}: ${error.stack}`);
|
|
662
|
+
}
|
|
663
|
+
// call close to go through standard close routine!
|
|
664
|
+
this.close(closeReason);
|
|
665
|
+
}
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
finally {
|
|
669
|
+
this.generator = undefined;
|
|
670
|
+
if (this.generatorTimeout) {
|
|
671
|
+
clearTimeout(this.generatorTimeout);
|
|
672
|
+
}
|
|
673
|
+
if (!this.closed) {
|
|
674
|
+
// e.g. when returning with `endOfStream` we rely on the HomeHub to send an ACK event to close the recording.
|
|
675
|
+
// With this timer we ensure that the HomeHub has the chance to close the stream gracefully but at the same time
|
|
676
|
+
// ensure that if something fails the recording stream is freed nonetheless.
|
|
677
|
+
this.kickOffCloseTimeout();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
debug("[HDS %s] Finished DATA_SEND transmission for stream %d!", this.connection.remoteAddress, this.streamId);
|
|
681
|
+
}
|
|
738
682
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
var endOfStream = message.endOfStream;
|
|
683
|
+
handleDataSendAck(message) {
|
|
684
|
+
const streamId = message.streamId;
|
|
685
|
+
const endOfStream = message.endOfStream;
|
|
743
686
|
// The HomeKit Controller will send a DATA_SEND ACK if we set the `endOfStream` flag in the last packet
|
|
744
687
|
// of our DATA_SEND DATA packet.
|
|
745
688
|
// To my testing the session is then considered complete and the HomeKit controller will close the HDS Connection after 5 seconds.
|
|
746
689
|
debug("[HDS %s] Received DATA_SEND ACK packet for streamId %s. Acknowledged %s.", this.connection.remoteAddress, streamId, endOfStream);
|
|
747
|
-
this.handleClosed(
|
|
748
|
-
}
|
|
690
|
+
this.handleClosed(() => this.delegate.acknowledgeStream?.(this.streamId));
|
|
691
|
+
}
|
|
749
692
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
750
|
-
|
|
751
|
-
var _this = this;
|
|
693
|
+
handleDataSendClose(message) {
|
|
752
694
|
// see https://github.com/Supereg/secure-video-specification#43-close
|
|
753
|
-
|
|
754
|
-
|
|
695
|
+
const streamId = message.streamId;
|
|
696
|
+
const reason = message.reason;
|
|
755
697
|
if (streamId !== this.streamId) {
|
|
756
698
|
return;
|
|
757
699
|
}
|
|
758
700
|
debug("[HDS %s] Received DATA_SEND CLOSE for streamId %d with reason %s",
|
|
759
701
|
// @ts-expect-error: forceConsistentCasingInFileNames compiler option
|
|
760
702
|
this.connection.remoteAddress, streamId, datastream_1.HDSProtocolSpecificErrorReason[reason]);
|
|
761
|
-
this.handleClosed(
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
var _this = this;
|
|
703
|
+
this.handleClosed(() => this.delegate.closeRecordingStream(streamId, reason));
|
|
704
|
+
}
|
|
705
|
+
handleDataStreamConnectionClosed() {
|
|
765
706
|
debug("[HDS %s] The HDS connection of the stream %d closed.", this.connection.remoteAddress, this.streamId);
|
|
766
|
-
this.handleClosed(
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
var _this = this;
|
|
707
|
+
this.handleClosed(() => this.delegate.closeRecordingStream(this.streamId, undefined));
|
|
708
|
+
}
|
|
709
|
+
handleClosed(closure) {
|
|
770
710
|
this.closed = true;
|
|
771
711
|
if (this.closingTimeout) {
|
|
772
712
|
clearTimeout(this.closingTimeout);
|
|
@@ -777,25 +717,24 @@ var CameraRecordingStream = /** @class */ (function (_super) {
|
|
|
777
717
|
if (this.generator) {
|
|
778
718
|
// when this variable is defined, the generator hasn't returned yet.
|
|
779
719
|
// we start a timeout to uncover potential programming mistakes where we await forever and can't free resources.
|
|
780
|
-
this.generatorTimeout = setTimeout(
|
|
720
|
+
this.generatorTimeout = setTimeout(() => {
|
|
781
721
|
console.error("[HDS %s] Recording download stream %d is still awaiting generator although stream was closed 10s ago! " +
|
|
782
|
-
"This is a programming mistake by the camera implementation which prevents freeing up resources.",
|
|
722
|
+
"This is a programming mistake by the camera implementation which prevents freeing up resources.", this.connection.remoteAddress, this.streamId);
|
|
783
723
|
}, 10000);
|
|
784
724
|
}
|
|
785
725
|
try {
|
|
786
726
|
closure();
|
|
787
727
|
}
|
|
788
728
|
catch (error) {
|
|
789
|
-
console.error(
|
|
729
|
+
console.error(`[HDS ${this.connection.remoteAddress}] CameraRecordingDelegated failed to handle closing the stream ${this.streamId}: ${error.stack}`);
|
|
790
730
|
}
|
|
791
731
|
this.emit("closed" /* CameraRecordingStreamEvents.CLOSED */);
|
|
792
|
-
}
|
|
732
|
+
}
|
|
793
733
|
/**
|
|
794
734
|
* This method can be used to close a recording session from the outside.
|
|
795
735
|
* @param reason - The reason to close the stream with.
|
|
796
736
|
*/
|
|
797
|
-
|
|
798
|
-
var _this = this;
|
|
737
|
+
close(reason) {
|
|
799
738
|
if (this.closed) {
|
|
800
739
|
return;
|
|
801
740
|
}
|
|
@@ -809,21 +748,19 @@ var CameraRecordingStream = /** @class */ (function (_super) {
|
|
|
809
748
|
reason: reason,
|
|
810
749
|
});
|
|
811
750
|
}
|
|
812
|
-
this.handleClosed(
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
var _this = this;
|
|
751
|
+
this.handleClosed(() => this.delegate.closeRecordingStream(this.streamId, reason));
|
|
752
|
+
}
|
|
753
|
+
kickOffCloseTimeout() {
|
|
816
754
|
if (this.closingTimeout) {
|
|
817
755
|
clearTimeout(this.closingTimeout);
|
|
818
756
|
}
|
|
819
|
-
this.closingTimeout = setTimeout(
|
|
820
|
-
if (
|
|
757
|
+
this.closingTimeout = setTimeout(() => {
|
|
758
|
+
if (this.closed) {
|
|
821
759
|
return;
|
|
822
760
|
}
|
|
823
|
-
debug("[HDS %s] Recording stream %d took longer than expected to fully close. Force closing now!",
|
|
824
|
-
|
|
761
|
+
debug("[HDS %s] Recording stream %d took longer than expected to fully close. Force closing now!", this.connection.remoteAddress, this.streamId);
|
|
762
|
+
this.close(3 /* HDSProtocolSpecificErrorReason.CANCELLED */);
|
|
825
763
|
}, 12000);
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
}(events_1.EventEmitter));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
829
766
|
//# sourceMappingURL=RecordingManagement.js.map
|