homebridge-eufy-security
Version:
Control Eufy Security from homebridge.
190 lines • 8.95 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RecordingDelegate = void 0;
const eufy_security_client_1 = require("eufy-security-client");
const ffmpeg_1 = require("../utils/ffmpeg");
const utils_1 = require("../utils/utils");
const MAX_RECORDING_MINUTES = 1; // should never be used
const HKSVQuitReason = [
'Normal',
'Not allowed',
'Busy',
'Cancelled',
'Unsupported',
'Unexpected Failure',
'Timeout',
'Bad data',
'Protocol error',
'Invalid Configuration',
];
class RecordingDelegate {
platform;
accessory;
camera;
cameraConfig;
localLivestreamManager;
configuration;
forceStopTimeout;
closeReason;
handlingStreamingRequest = false;
controller;
session;
constructor(platform, accessory, camera, cameraConfig, localLivestreamManager) {
this.platform = platform;
this.accessory = accessory;
this.camera = camera;
this.cameraConfig = cameraConfig;
this.localLivestreamManager = localLivestreamManager;
}
setController(controller) {
this.controller = controller;
}
isRecording() {
return this.handlingStreamingRequest;
}
async *handleRecordingStreamRequest() {
this.handlingStreamingRequest = true;
utils_1.log.info(this.camera.getName(), 'requesting recording for HomeKit Secure Video.');
let pending = [];
let filebuffer = Buffer.alloc(0);
try {
const audioEnabled = this.controller?.recordingManagement?.recordingManagementService.getCharacteristic(utils_1.CHAR.RecordingAudioActive).value;
if (audioEnabled) {
utils_1.log.debug('HKSV and plugin are set to record audio.');
}
else {
utils_1.log.debug('HKSV and plugin are set to omit audio recording.');
}
const videoParams = await ffmpeg_1.FFmpegParameters.forVideoRecording();
const audioParams = await ffmpeg_1.FFmpegParameters.forAudioRecording();
const videoConfig = this.cameraConfig.videoConfig ?? {};
if (this.cameraConfig.videoConfig && this.cameraConfig.videoConfig.videoProcessor) {
videoConfig.videoProcessor = this.cameraConfig.videoConfig.videoProcessor;
}
videoParams.setupForRecording(videoConfig, this.configuration);
audioParams.setupForRecording(videoConfig, this.configuration);
const rtsp = (0, utils_1.is_rtsp_ready)(this.camera, this.cameraConfig);
if (rtsp) {
const url = this.camera.getPropertyValue(eufy_security_client_1.PropertyName.DeviceRTSPStreamUrl);
utils_1.log.debug(this.camera.getName(), 'RTSP URL: ' + url);
videoParams.setInputSource(url);
audioParams.setInputSource(url);
}
else {
const streamData = await this.localLivestreamManager.getLocalLivestream().catch((error) => {
throw error;
});
await videoParams.setInputStream(streamData.videostream);
await audioParams.setInputStream(streamData.audiostream);
}
const ffmpeg = new ffmpeg_1.FFmpeg(`[${this.camera.getName()}] [HSV Recording Process]`, audioEnabled ? [videoParams, audioParams] : videoParams);
this.session = await ffmpeg.startFragmentedMP4Session();
let timer = this.cameraConfig.hsvRecordingDuration ?? MAX_RECORDING_MINUTES * 60;
if (this.platform.config.CameraMaxLivestreamDuration < timer) {
timer = this.platform.config.CameraMaxLivestreamDuration;
}
if (timer > 0) {
this.forceStopTimeout = setTimeout(() => {
utils_1.log.warn(this.camera.getName(), `The recording process has been running for ${timer} seconds and is now being forced closed!`);
this.accessory
.getService(utils_1.SERV.MotionSensor)?.getCharacteristic(utils_1.CHAR.MotionDetected)
.updateValue(false);
}, timer * 1000);
}
for await (const box of this.session.generator) {
if (!this.handlingStreamingRequest) {
utils_1.log.debug(this.camera.getName(), 'Recording was ended prematurely.');
break;
}
const { header, type, data } = box;
pending.push(header, data);
const motionDetected = this.accessory
.getService(utils_1.SERV.MotionSensor)?.getCharacteristic(utils_1.CHAR.MotionDetected).value;
if (type === 'moov' || type === 'mdat') {
const fragment = Buffer.concat(pending);
filebuffer = Buffer.concat([filebuffer, Buffer.concat(pending)]);
pending = [];
yield {
data: fragment,
isLast: !motionDetected,
};
if (!motionDetected) {
utils_1.log.debug(this.camera.getName(), 'Ending recording session due to motion stopped!');
break;
}
}
}
}
catch (error) {
if (!this.handlingStreamingRequest && this.closeReason && this.closeReason === 3 /* HDSProtocolSpecificErrorReason.CANCELLED */) {
utils_1.log.debug(this.camera.getName(), 'Recording encountered an error but that is expected, as the recording was canceled beforehand. Error: ' + error);
}
else {
utils_1.log.error(this.camera.getName(), 'Error while recording: ' + error);
}
}
finally {
if (this.closeReason &&
this.closeReason !== 0 /* HDSProtocolSpecificErrorReason.NORMAL */ && this.closeReason !== 3 /* HDSProtocolSpecificErrorReason.CANCELLED */) {
utils_1.log.warn(this.camera.getName(), `The recording process was aborted by HSV with reason "${HKSVQuitReason[this.closeReason]}"`);
}
if (this.closeReason && this.closeReason === 3 /* HDSProtocolSpecificErrorReason.CANCELLED */) {
utils_1.log.debug(this.camera.getName(), 'The recording process was canceled by the HomeKit Controller."');
}
if (filebuffer.length > 0) {
utils_1.log.debug(this.camera.getName(), 'Recording completed (HSV). Send ' + filebuffer.length + ' bytes.');
}
if (this.forceStopTimeout) {
clearTimeout(this.forceStopTimeout);
this.forceStopTimeout = undefined;
}
// check whether motion is still in progress
const motionDetected = this.accessory
.getService(utils_1.SERV.MotionSensor)?.getCharacteristic(utils_1.CHAR.MotionDetected).value;
if (motionDetected) {
this.accessory
.getService(utils_1.SERV.MotionSensor)?.getCharacteristic(utils_1.CHAR.MotionDetected)
.updateValue(false);
}
this.localLivestreamManager.stopLocalLiveStream();
}
}
updateRecordingActive(active) {
utils_1.log.debug(`Recording: ${active}`, this.accessory.displayName);
}
updateRecordingConfiguration(configuration) {
this.configuration = configuration;
}
closeRecordingStream(streamId, reason) {
utils_1.log.info(this.camera.getName(), 'Closing recording process');
if (this.session) {
utils_1.log.debug(this.camera.getName(), 'Stopping recording session.');
this.session.socket?.destroy();
this.session.process?.kill('SIGKILL');
this.session = undefined;
}
else {
utils_1.log.warn('Recording session could not be closed gracefully.');
}
if (this.forceStopTimeout) {
clearTimeout(this.forceStopTimeout);
this.forceStopTimeout = undefined;
}
// check whether motion is still in progress
const motionDetected = this.accessory
.getService(utils_1.SERV.MotionSensor)?.getCharacteristic(utils_1.CHAR.MotionDetected).value;
if (motionDetected) {
this.accessory
.getService(utils_1.SERV.MotionSensor)?.getCharacteristic(utils_1.CHAR.MotionDetected)
.updateValue(false);
}
this.closeReason = reason;
this.handlingStreamingRequest = false;
}
acknowledgeStream(streamId) {
utils_1.log.debug('end of recording acknowledged!');
this.closeRecordingStream(streamId, undefined);
}
}
exports.RecordingDelegate = RecordingDelegate;
//# sourceMappingURL=recordingDelegate.js.map