@homebridge-plugins/homebridge-eufy-security 0.0.1
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/CHANGELOG.md +5 -0
- package/FUNDING.yml +1 -0
- package/LICENSE +176 -0
- package/README.md +67 -0
- package/config.schema.json +6 -0
- package/dist/accessories/AutoSyncStationAccessory.js +156 -0
- package/dist/accessories/AutoSyncStationAccessory.js.map +1 -0
- package/dist/accessories/BaseAccessory.js +247 -0
- package/dist/accessories/BaseAccessory.js.map +1 -0
- package/dist/accessories/CameraAccessory.js +431 -0
- package/dist/accessories/CameraAccessory.js.map +1 -0
- package/dist/accessories/Device.js +67 -0
- package/dist/accessories/Device.js.map +1 -0
- package/dist/accessories/EntrySensorAccessory.js +48 -0
- package/dist/accessories/EntrySensorAccessory.js.map +1 -0
- package/dist/accessories/LockAccessory.js +142 -0
- package/dist/accessories/LockAccessory.js.map +1 -0
- package/dist/accessories/MotionSensorAccessory.js +48 -0
- package/dist/accessories/MotionSensorAccessory.js.map +1 -0
- package/dist/accessories/SmartDropAccessory.js +145 -0
- package/dist/accessories/SmartDropAccessory.js.map +1 -0
- package/dist/accessories/StationAccessory.js +371 -0
- package/dist/accessories/StationAccessory.js.map +1 -0
- package/dist/config.js +25 -0
- package/dist/config.js.map +1 -0
- package/dist/controller/LocalLivestreamManager.js +116 -0
- package/dist/controller/LocalLivestreamManager.js.map +1 -0
- package/dist/controller/recordingDelegate.js +208 -0
- package/dist/controller/recordingDelegate.js.map +1 -0
- package/dist/controller/snapshotDelegate.js +345 -0
- package/dist/controller/snapshotDelegate.js.map +1 -0
- package/dist/controller/streamingDelegate.js +345 -0
- package/dist/controller/streamingDelegate.js.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces.js +2 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/media/Snapshot-Unavailable.png +0 -0
- package/dist/media/Snapshot-Unavailable.xcf +0 -0
- package/dist/media/Snapshot-black.png +0 -0
- package/dist/media/camera-disabled.png +0 -0
- package/dist/media/camera-offline.png +0 -0
- package/dist/media/media/Snapshot-Unavailable.png +0 -0
- package/dist/media/media/Snapshot-Unavailable.xcf +0 -0
- package/dist/media/media/Snapshot-black.png +0 -0
- package/dist/media/media/camera-disabled.png +0 -0
- package/dist/media/media/camera-offline.png +0 -0
- package/dist/platform.js +716 -0
- package/dist/platform.js.map +1 -0
- package/dist/settings.js +38 -0
- package/dist/settings.js.map +1 -0
- package/dist/utils/Talkback.js +92 -0
- package/dist/utils/Talkback.js.map +1 -0
- package/dist/utils/accessoriesStore.js +206 -0
- package/dist/utils/accessoriesStore.js.map +1 -0
- package/dist/utils/configTypes.js +35 -0
- package/dist/utils/configTypes.js.map +1 -0
- package/dist/utils/ffmpeg.js +843 -0
- package/dist/utils/ffmpeg.js.map +1 -0
- package/dist/utils/interfaces.js +8 -0
- package/dist/utils/interfaces.js.map +1 -0
- package/dist/utils/utils.js +44 -0
- package/dist/utils/utils.js.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/eslint.config.mjs +18 -0
- package/homebridge-eufy-security.png +0 -0
- package/homebridge-ui/public/app.js +225 -0
- package/homebridge-ui/public/assets/devices/4g_lte_starlight_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/BATTERY_DOORBELL_C30.png +0 -0
- package/homebridge-ui/public/assets/devices/BATTERY_DOORBELL_C31.png +0 -0
- package/homebridge-ui/public/assets/devices/batterydoorbell1080p_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/batterydoorbell2kdual_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/batterydoorbell_e340_large.png +0 -0
- package/homebridge-ui/public/assets/devices/eufy-security-client.png +0 -0
- package/homebridge-ui/public/assets/devices/eufycam2_large.png +0 -0
- package/homebridge-ui/public/assets/devices/eufycam2c_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam2cpro_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam2pro_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam3_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam3c_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam3pro_large.png +0 -0
- package/homebridge-ui/public/assets/devices/eufycam_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycame330_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/floodlight2_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/floodlight2pro_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/floodlight_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/floodlightcame340_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/garage_camera_t8452_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/homebase2_large.png +0 -0
- package/homebridge-ui/public/assets/devices/homebase3_large.png +0 -0
- package/homebridge-ui/public/assets/devices/homebase_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/homebasemini_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamC210_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamC220_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamE30_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamc120_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcammini_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamp24_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcams350_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/keypad_large.png +0 -0
- package/homebridge-ui/public/assets/devices/minibase_chime_T8023_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/motionsensor_large.png +0 -0
- package/homebridge-ui/public/assets/devices/sensor_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartdrop_t8790_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8500_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8500_wifibridge_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8503_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8504_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8510P_t8520P_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8502_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8506_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8520_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_t8510_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_t8510_wifibridge_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_video_t8530_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlockwifibridge_t8021_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/smartsafe_s10_t7400_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartsafe_s12_t7401_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smarttrack_card_t87B2_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smarttrack_link_t87B0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocamc210_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocamc35_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocame20_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocame30_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocame40_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocaml20_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocams220_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocams340_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocams40_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/soloindoorcamc24_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solooutdoorcamc22_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solooutdoorcamc24_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/unknown.png +0 -0
- package/homebridge-ui/public/assets/devices/walllight_s100_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/walllight_s120_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/wireddoorbell1080p_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/wireddoorbell2k_large.png +0 -0
- package/homebridge-ui/public/assets/devices/wireddoorbelldual_large.jpg +0 -0
- package/homebridge-ui/public/assets/icons/attach.svg +1 -0
- package/homebridge-ui/public/assets/icons/battery_0.svg +1 -0
- package/homebridge-ui/public/assets/icons/battery_1.svg +1 -0
- package/homebridge-ui/public/assets/icons/battery_2.svg +1 -0
- package/homebridge-ui/public/assets/icons/battery_3.svg +1 -0
- package/homebridge-ui/public/assets/icons/battery_4.svg +1 -0
- package/homebridge-ui/public/assets/icons/battery_5.svg +1 -0
- package/homebridge-ui/public/assets/icons/battery_6.svg +1 -0
- package/homebridge-ui/public/assets/icons/bolt.svg +1 -0
- package/homebridge-ui/public/assets/icons/bug-report.svg +1 -0
- package/homebridge-ui/public/assets/icons/copy.svg +1 -0
- package/homebridge-ui/public/assets/icons/delete.svg +1 -0
- package/homebridge-ui/public/assets/icons/download.svg +1 -0
- package/homebridge-ui/public/assets/icons/info.svg +1 -0
- package/homebridge-ui/public/assets/icons/inventory.svg +1 -0
- package/homebridge-ui/public/assets/icons/refresh.svg +1 -0
- package/homebridge-ui/public/assets/icons/satellite_alt.svg +1 -0
- package/homebridge-ui/public/assets/icons/settings.svg +1 -0
- package/homebridge-ui/public/assets/icons/settings_backup_restore.svg +1 -0
- package/homebridge-ui/public/assets/icons/solar_power.svg +1 -0
- package/homebridge-ui/public/assets/icons/warning.svg +1 -0
- package/homebridge-ui/public/components/device-card.js +162 -0
- package/homebridge-ui/public/components/guard-modes.js +88 -0
- package/homebridge-ui/public/components/number-input.js +121 -0
- package/homebridge-ui/public/components/select.js +73 -0
- package/homebridge-ui/public/components/toggle.js +68 -0
- package/homebridge-ui/public/index.html +27 -0
- package/homebridge-ui/public/services/api.js +214 -0
- package/homebridge-ui/public/services/config.js +144 -0
- package/homebridge-ui/public/style.css +775 -0
- package/homebridge-ui/public/utils/countries.js +73 -0
- package/homebridge-ui/public/utils/device-images.js +89 -0
- package/homebridge-ui/public/utils/helpers.js +87 -0
- package/homebridge-ui/public/views/dashboard.js +226 -0
- package/homebridge-ui/public/views/device-detail.js +610 -0
- package/homebridge-ui/public/views/diagnostics.js +296 -0
- package/homebridge-ui/public/views/login.js +636 -0
- package/homebridge-ui/public/views/settings.js +192 -0
- package/homebridge-ui/public/views/unsupported-detail.js +296 -0
- package/homebridge-ui/server.js +1327 -0
- package/media/Snapshot-Unavailable.png +0 -0
- package/media/Snapshot-Unavailable.xcf +0 -0
- package/media/Snapshot-black.png +0 -0
- package/media/camera-disabled.png +0 -0
- package/media/camera-offline.png +0 -0
- package/package.json +64 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { PropertyName } from 'eufy-security-client';
|
|
2
|
+
import { FFmpeg, FFmpegParameters } from '../utils/ffmpeg.js';
|
|
3
|
+
import { CHAR, SERV, isRtspReady, log } from '../utils/utils.js';
|
|
4
|
+
const MAX_RECORDING_MINUTES = 1; // should never be used
|
|
5
|
+
const HKSVQuitReason = [
|
|
6
|
+
'Normal',
|
|
7
|
+
'Not allowed',
|
|
8
|
+
'Busy',
|
|
9
|
+
'Cancelled',
|
|
10
|
+
'Unsupported',
|
|
11
|
+
'Unexpected Failure',
|
|
12
|
+
'Timeout',
|
|
13
|
+
'Bad data',
|
|
14
|
+
'Protocol error',
|
|
15
|
+
'Invalid Configuration',
|
|
16
|
+
];
|
|
17
|
+
export class RecordingDelegate {
|
|
18
|
+
platform;
|
|
19
|
+
accessory;
|
|
20
|
+
camera;
|
|
21
|
+
cameraConfig;
|
|
22
|
+
localLivestreamManager;
|
|
23
|
+
snapshotDlg;
|
|
24
|
+
configuration;
|
|
25
|
+
forceStopTimeout;
|
|
26
|
+
closeReason;
|
|
27
|
+
handlingStreamingRequest = false;
|
|
28
|
+
controller;
|
|
29
|
+
session;
|
|
30
|
+
/** Delay before extracting a snapshot from a running HKSV recording (ms). */
|
|
31
|
+
static RECORDING_SNAPSHOT_DELAY_MS = 2_000;
|
|
32
|
+
constructor(platform, accessory, camera, cameraConfig, localLivestreamManager, snapshotDlg) {
|
|
33
|
+
this.platform = platform;
|
|
34
|
+
this.accessory = accessory;
|
|
35
|
+
this.camera = camera;
|
|
36
|
+
this.cameraConfig = cameraConfig;
|
|
37
|
+
this.localLivestreamManager = localLivestreamManager;
|
|
38
|
+
this.snapshotDlg = snapshotDlg;
|
|
39
|
+
}
|
|
40
|
+
setController(controller) {
|
|
41
|
+
this.controller = controller;
|
|
42
|
+
}
|
|
43
|
+
isRecording() {
|
|
44
|
+
return this.handlingStreamingRequest;
|
|
45
|
+
}
|
|
46
|
+
resetMotionSensor() {
|
|
47
|
+
const motionDetected = this.accessory
|
|
48
|
+
.getService(SERV.MotionSensor)?.getCharacteristic(CHAR.MotionDetected).value;
|
|
49
|
+
if (motionDetected) {
|
|
50
|
+
this.accessory
|
|
51
|
+
.getService(SERV.MotionSensor)?.getCharacteristic(CHAR.MotionDetected)
|
|
52
|
+
.updateValue(false);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
clearForceStopTimeout() {
|
|
56
|
+
if (this.forceStopTimeout) {
|
|
57
|
+
clearTimeout(this.forceStopTimeout);
|
|
58
|
+
this.forceStopTimeout = undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
isMotionDetected() {
|
|
62
|
+
return !!this.accessory
|
|
63
|
+
.getService(SERV.MotionSensor)?.getCharacteristic(CHAR.MotionDetected).value;
|
|
64
|
+
}
|
|
65
|
+
async configureInputSource(videoParams, audioParams) {
|
|
66
|
+
if (isRtspReady(this.camera, this.cameraConfig)) {
|
|
67
|
+
const url = this.camera.getPropertyValue(PropertyName.DeviceRTSPStreamUrl);
|
|
68
|
+
log.debug(this.camera.getName(), 'RTSP URL: ' + url);
|
|
69
|
+
videoParams.setInputSource(url);
|
|
70
|
+
audioParams.setInputSource(url);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const streamData = await this.localLivestreamManager.getLocalLiveStream();
|
|
74
|
+
await videoParams.setInputStream(streamData.videostream);
|
|
75
|
+
await audioParams.setInputStream(streamData.audiostream);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async *handleRecordingStreamRequest() {
|
|
79
|
+
this.handlingStreamingRequest = true;
|
|
80
|
+
this.closeReason = undefined;
|
|
81
|
+
log.info(this.camera.getName(), 'requesting recording for HomeKit Secure Video.');
|
|
82
|
+
try {
|
|
83
|
+
if (!this.configuration) {
|
|
84
|
+
log.error(this.camera.getName(), 'No recording configuration available. Aborting.');
|
|
85
|
+
yield { data: Buffer.alloc(0), isLast: true };
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const audioEnabled = this.controller?.recordingManagement?.recordingManagementService.getCharacteristic(CHAR.RecordingAudioActive).value;
|
|
89
|
+
log.debug(this.camera.getName(), `HKSV audio recording: ${audioEnabled ? 'enabled' : 'disabled'}.`);
|
|
90
|
+
const videoParams = await FFmpegParameters.forVideoRecording();
|
|
91
|
+
const audioParams = await FFmpegParameters.forAudioRecording();
|
|
92
|
+
const videoConfig = this.cameraConfig.videoConfig ?? {};
|
|
93
|
+
videoParams.setupForRecording(videoConfig, this.configuration);
|
|
94
|
+
audioParams.setupForRecording(videoConfig, this.configuration);
|
|
95
|
+
await this.configureInputSource(videoParams, audioParams);
|
|
96
|
+
// Opportunistically capture a snapshot from the HKSV recording stream
|
|
97
|
+
setTimeout(() => {
|
|
98
|
+
this.snapshotDlg.captureSnapshotFromActiveLivestream().catch((error) => {
|
|
99
|
+
log.debug(this.camera.getName(), 'Snapshot capture from HKSV recording failed: ' + error);
|
|
100
|
+
});
|
|
101
|
+
}, RecordingDelegate.RECORDING_SNAPSHOT_DELAY_MS);
|
|
102
|
+
const ffmpeg = new FFmpeg(`[${this.camera.getName()}] [HSV Recording Process]`, audioEnabled ? [videoParams, audioParams] : videoParams);
|
|
103
|
+
this.session = await ffmpeg.startFragmentedMP4Session();
|
|
104
|
+
const maxDuration = Math.min(this.cameraConfig.hsvRecordingDuration ?? MAX_RECORDING_MINUTES * 60, this.platform.config.CameraMaxLivestreamDuration);
|
|
105
|
+
if (maxDuration > 0) {
|
|
106
|
+
this.forceStopTimeout = setTimeout(() => {
|
|
107
|
+
log.warn(this.camera.getName(), `Recording force-stopped after ${maxDuration}s.`);
|
|
108
|
+
this.resetMotionSensor();
|
|
109
|
+
}, maxDuration * 1000);
|
|
110
|
+
}
|
|
111
|
+
yield* this.generateFragments(this.session.generator);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (!this.handlingStreamingRequest && this.closeReason && this.closeReason === 3 /* HDSProtocolSpecificErrorReason.CANCELLED */) {
|
|
115
|
+
log.debug(this.camera.getName(), 'Recording encountered an error but that is expected, as the recording was canceled beforehand. Error: ' + error);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
log.error(this.camera.getName(), 'Error while recording: ' + error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
this.logCloseReason();
|
|
123
|
+
this.clearForceStopTimeout();
|
|
124
|
+
this.resetMotionSensor();
|
|
125
|
+
this.localLivestreamManager.stopLocalLiveStream();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
logCloseReason() {
|
|
129
|
+
if (!this.closeReason) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (this.closeReason === 3 /* HDSProtocolSpecificErrorReason.CANCELLED */) {
|
|
133
|
+
log.debug(this.camera.getName(), 'The recording process was canceled by the HomeKit Controller.');
|
|
134
|
+
}
|
|
135
|
+
else if (this.closeReason !== 0 /* HDSProtocolSpecificErrorReason.NORMAL */) {
|
|
136
|
+
log.warn(this.camera.getName(), `The recording process was aborted by HSV with reason "${HKSVQuitReason[this.closeReason]}"`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Assembles fragmented MP4 boxes into HKSV-compatible recording packets.
|
|
141
|
+
* Yields an initialization segment (ftyp+moov), then paired moof+mdat fragments.
|
|
142
|
+
*/
|
|
143
|
+
async *generateFragments(generator) {
|
|
144
|
+
const cameraName = this.camera.getName();
|
|
145
|
+
let initPending = [];
|
|
146
|
+
let moofBuffer = null;
|
|
147
|
+
let isInit = true;
|
|
148
|
+
let fragmentCount = 0;
|
|
149
|
+
for await (const { header, type, data } of generator) {
|
|
150
|
+
if (!this.handlingStreamingRequest) {
|
|
151
|
+
log.debug(cameraName, 'Recording was ended prematurely.');
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
if (isInit) {
|
|
155
|
+
initPending.push(header, data);
|
|
156
|
+
if (type === 'moov') {
|
|
157
|
+
const fragment = Buffer.concat(initPending);
|
|
158
|
+
initPending = [];
|
|
159
|
+
isInit = false;
|
|
160
|
+
log.debug(cameraName, `HKSV: Sending initialization segment, size: ${fragment.length}`);
|
|
161
|
+
yield { data: fragment, isLast: false };
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (type === 'moof') {
|
|
166
|
+
moofBuffer = Buffer.concat([header, data]);
|
|
167
|
+
}
|
|
168
|
+
else if (type === 'mdat' && moofBuffer) {
|
|
169
|
+
const fragment = Buffer.concat([moofBuffer, header, data]);
|
|
170
|
+
moofBuffer = null;
|
|
171
|
+
fragmentCount++;
|
|
172
|
+
log.debug(cameraName, `HKSV: Fragment #${fragmentCount}, size: ${fragment.length}`);
|
|
173
|
+
yield { data: fragment, isLast: false };
|
|
174
|
+
if (!this.isMotionDetected()) {
|
|
175
|
+
log.debug(cameraName, 'Ending recording session due to motion stopped.');
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
updateRecordingActive(active) {
|
|
182
|
+
log.debug(`Recording: ${active}`, this.accessory.displayName);
|
|
183
|
+
}
|
|
184
|
+
updateRecordingConfiguration(configuration) {
|
|
185
|
+
this.configuration = configuration;
|
|
186
|
+
}
|
|
187
|
+
closeRecordingStream(streamId, reason) {
|
|
188
|
+
log.info(this.camera.getName(), 'Closing recording process');
|
|
189
|
+
if (this.session) {
|
|
190
|
+
log.debug(this.camera.getName(), 'Stopping recording session.');
|
|
191
|
+
this.session.socket?.destroy();
|
|
192
|
+
this.session.process?.kill('SIGKILL');
|
|
193
|
+
this.session = undefined;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
log.warn('Recording session could not be closed gracefully.');
|
|
197
|
+
}
|
|
198
|
+
this.clearForceStopTimeout();
|
|
199
|
+
this.resetMotionSensor();
|
|
200
|
+
this.closeReason = reason;
|
|
201
|
+
this.handlingStreamingRequest = false;
|
|
202
|
+
}
|
|
203
|
+
acknowledgeStream(streamId) {
|
|
204
|
+
log.debug('end of recording acknowledged!');
|
|
205
|
+
this.closeRecordingStream(streamId, undefined);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=recordingDelegate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordingDelegate.js","sourceRoot":"","sources":["../../src/controller/recordingDelegate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { PropertyName } from 'eufy-security-client';
|
|
3
|
+
import { SNAPSHOT_CACHE_BALANCED_SECONDS, SNAPSHOT_CACHE_FRESH_SECONDS, SNAPSHOT_CLOUD_SKIP_MS, SNAPSHOT_FETCH_TIMEOUT_MS, } from '../settings.js';
|
|
4
|
+
import { SnapshotHandlingMethod } from '../utils/configTypes.js';
|
|
5
|
+
import { FFmpeg, FFmpegParameters } from '../utils/ffmpeg.js';
|
|
6
|
+
import { isRtspReady } from '../utils/utils.js';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const PLACEHOLDER_PATHS = {
|
|
12
|
+
offline: path.resolve(__dirname, '../../media/camera-offline.png'),
|
|
13
|
+
disabled: path.resolve(__dirname, '../../media/camera-disabled.png'),
|
|
14
|
+
unavailable: path.resolve(__dirname, '../../media/Snapshot-Unavailable.png'),
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* possible performance settings:
|
|
18
|
+
* 1. snapshots as current as possible (weak homebridge performance)
|
|
19
|
+
* - always get a new image from cloud or cam
|
|
20
|
+
* 2. balanced
|
|
21
|
+
* - start snapshot refresh but return snapshot as fast as possible
|
|
22
|
+
* if request takes too long old snapshot will be returned
|
|
23
|
+
* 3. get an old snapshot immediately
|
|
24
|
+
* - wait on cloud snapshot with new events
|
|
25
|
+
*
|
|
26
|
+
* extra options:
|
|
27
|
+
* - force refresh snapshots with interval
|
|
28
|
+
* - force immediate snapshot-reject when ringing
|
|
29
|
+
*
|
|
30
|
+
* Drawbacks: elapsed time in homekit might be wrong
|
|
31
|
+
*/
|
|
32
|
+
export class snapshotDelegate {
|
|
33
|
+
livestreamManager;
|
|
34
|
+
eufyPath;
|
|
35
|
+
device;
|
|
36
|
+
cameraConfig;
|
|
37
|
+
currentSnapshot;
|
|
38
|
+
placeholders = new Map();
|
|
39
|
+
pendingFetch;
|
|
40
|
+
isDeviceOffline = false;
|
|
41
|
+
log;
|
|
42
|
+
constructor(camera, livestreamManager) {
|
|
43
|
+
this.livestreamManager = livestreamManager;
|
|
44
|
+
this.eufyPath = camera.platform.eufyPath;
|
|
45
|
+
this.device = camera.device;
|
|
46
|
+
this.cameraConfig = camera.cameraConfig;
|
|
47
|
+
this.log = camera.log;
|
|
48
|
+
this.setupEventListeners();
|
|
49
|
+
this.logSnapshotHandlingMethod();
|
|
50
|
+
this.loadPlaceholderImages();
|
|
51
|
+
this.initializeDeviceState();
|
|
52
|
+
this.loadInitialSnapshot();
|
|
53
|
+
}
|
|
54
|
+
setupEventListeners() {
|
|
55
|
+
this.device.on('property changed', this.onPropertyValueChanged.bind(this));
|
|
56
|
+
}
|
|
57
|
+
logSnapshotHandlingMethod() {
|
|
58
|
+
const method = this.cameraConfig.snapshotHandlingMethod;
|
|
59
|
+
switch (method) {
|
|
60
|
+
case SnapshotHandlingMethod.AlwaysFresh:
|
|
61
|
+
this.log.info('is set to generate new snapshots on events every time. This might reduce homebridge performance and increase power consumption.');
|
|
62
|
+
break;
|
|
63
|
+
case SnapshotHandlingMethod.Balanced:
|
|
64
|
+
this.log.info('is set to balanced snapshot handling.');
|
|
65
|
+
break;
|
|
66
|
+
case SnapshotHandlingMethod.Auto:
|
|
67
|
+
case SnapshotHandlingMethod.CloudOnly:
|
|
68
|
+
this.log.info('is set to handle snapshots with cloud images. Snapshots might be older than they appear.');
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
this.log.warn(`Unknown snapshot handling method (${method}), falling back to cloud-only.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
loadPlaceholderImages() {
|
|
75
|
+
for (const [key, path] of Object.entries(PLACEHOLDER_PATHS)) {
|
|
76
|
+
try {
|
|
77
|
+
this.placeholders.set(key, fs.readFileSync(path));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
this.log.error(`Could not cache ${key} placeholder for further use: ${error}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
getPlaceholder(key) {
|
|
85
|
+
const buf = this.placeholders.get(key);
|
|
86
|
+
if (!buf) {
|
|
87
|
+
throw new Error(`Placeholder image '${key}' is not available.`);
|
|
88
|
+
}
|
|
89
|
+
return buf;
|
|
90
|
+
}
|
|
91
|
+
initializeDeviceState() {
|
|
92
|
+
try {
|
|
93
|
+
const state = this.device.getPropertyValue(PropertyName.DeviceState);
|
|
94
|
+
this.isDeviceOffline = (state === 0 || state === 3);
|
|
95
|
+
if (this.isDeviceOffline) {
|
|
96
|
+
this.log.info('Device is currently offline (state: ' + state + ').');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
this.log.debug('Could not read initial device state: ' + error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
loadInitialSnapshot() {
|
|
104
|
+
try {
|
|
105
|
+
const picture = this.device.getPropertyValue(PropertyName.DevicePicture);
|
|
106
|
+
if (picture && picture.type) {
|
|
107
|
+
this.storeSnapshotForCache(picture.data, 0);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
this.log.debug('Could not fetch snapshot from device property: ' + error);
|
|
113
|
+
}
|
|
114
|
+
// Fallback: try to load a previously cached snapshot from disk
|
|
115
|
+
this.loadSnapshotFromDisk();
|
|
116
|
+
}
|
|
117
|
+
loadSnapshotFromDisk() {
|
|
118
|
+
const serial = this.device.getSerial();
|
|
119
|
+
const extensions = ['jpg', 'png', 'bmp'];
|
|
120
|
+
for (const ext of extensions) {
|
|
121
|
+
const filePath = `${this.eufyPath}/${serial}.${ext}`;
|
|
122
|
+
try {
|
|
123
|
+
if (fs.existsSync(filePath)) {
|
|
124
|
+
const data = fs.readFileSync(filePath);
|
|
125
|
+
if (data.length > 0) {
|
|
126
|
+
const mtime = fs.statSync(filePath).mtimeMs;
|
|
127
|
+
this.storeSnapshotForCache(data, mtime);
|
|
128
|
+
this.log.info(`Loaded cached snapshot from disk: ${filePath}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
this.log.debug(`Failed to load snapshot from ${filePath}: ${error}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
this.log.warn('No cached snapshot found on disk for device ' + serial);
|
|
138
|
+
}
|
|
139
|
+
storeSnapshotForCache(data, time) {
|
|
140
|
+
this.currentSnapshot = { timestamp: time ??= Date.now(), image: data };
|
|
141
|
+
}
|
|
142
|
+
async getSnapshotBufferResized(request) {
|
|
143
|
+
return await this.resizeSnapshot(await this.getSnapshotBuffer(), request);
|
|
144
|
+
}
|
|
145
|
+
async getSnapshotBuffer() {
|
|
146
|
+
if (!this.device.isEnabled()) {
|
|
147
|
+
this.log.debug('Device is disabled, returning disabled snapshot.');
|
|
148
|
+
return this.getPlaceholder('disabled');
|
|
149
|
+
}
|
|
150
|
+
if (this.isDeviceOffline) {
|
|
151
|
+
this.log.debug('Device is offline, returning offline snapshot.');
|
|
152
|
+
return this.getPlaceholder('offline');
|
|
153
|
+
}
|
|
154
|
+
if (this.isCacheFresh(SNAPSHOT_CACHE_FRESH_SECONDS)) {
|
|
155
|
+
return this.currentSnapshot.image;
|
|
156
|
+
}
|
|
157
|
+
return this.resolveByHandlingMethod();
|
|
158
|
+
}
|
|
159
|
+
async resolveByHandlingMethod() {
|
|
160
|
+
try {
|
|
161
|
+
switch (this.cameraConfig.snapshotHandlingMethod) {
|
|
162
|
+
case SnapshotHandlingMethod.AlwaysFresh:
|
|
163
|
+
return await this.fetchSnapshotFromStream();
|
|
164
|
+
case SnapshotHandlingMethod.Balanced:
|
|
165
|
+
if (this.isCacheFresh(SNAPSHOT_CACHE_BALANCED_SECONDS)) {
|
|
166
|
+
return this.currentSnapshot.image;
|
|
167
|
+
}
|
|
168
|
+
return await this.fetchSnapshotFromStream();
|
|
169
|
+
case SnapshotHandlingMethod.Auto:
|
|
170
|
+
case SnapshotHandlingMethod.CloudOnly:
|
|
171
|
+
default:
|
|
172
|
+
return this.getCachedOrPlaceholder();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
this.log.warn('Snapshot retrieval failed, falling back to cache:', err);
|
|
177
|
+
return this.getCachedOrPlaceholder();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Fetches a fresh snapshot from a live stream.
|
|
182
|
+
*/
|
|
183
|
+
async fetchSnapshotFromStream() {
|
|
184
|
+
this.log.info('Begin live streaming to access the most recent snapshot (significant battery drain on the device)');
|
|
185
|
+
await this.fetchCurrentCameraSnapshot();
|
|
186
|
+
if (this.currentSnapshot) {
|
|
187
|
+
return this.currentSnapshot.image;
|
|
188
|
+
}
|
|
189
|
+
throw new Error('Snapshot fetch completed but no snapshot stored');
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Returns cached snapshot or the unavailable placeholder.
|
|
193
|
+
*/
|
|
194
|
+
getCachedOrPlaceholder() {
|
|
195
|
+
if (this.currentSnapshot) {
|
|
196
|
+
return this.currentSnapshot.image;
|
|
197
|
+
}
|
|
198
|
+
this.log.warn('No currentSnapshot available, using fallback unavailable snapshot image');
|
|
199
|
+
return this.getPlaceholder('unavailable');
|
|
200
|
+
}
|
|
201
|
+
isCacheFresh(maxAgeSeconds) {
|
|
202
|
+
return !!this.currentSnapshot &&
|
|
203
|
+
(Date.now() - this.currentSnapshot.timestamp) / 1000 <= maxAgeSeconds;
|
|
204
|
+
}
|
|
205
|
+
storeImage(file, image) {
|
|
206
|
+
const filePath = `${this.eufyPath}/${file}`;
|
|
207
|
+
try {
|
|
208
|
+
fs.writeFileSync(filePath, image);
|
|
209
|
+
this.log.debug(`Stored Image: ${filePath}`);
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
this.log.warn(`Failed to store image: ${filePath} - ${error}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async onPropertyValueChanged(device, name) {
|
|
216
|
+
switch (name) {
|
|
217
|
+
case 'picture': {
|
|
218
|
+
const picture = device.getPropertyValue(PropertyName.DevicePicture);
|
|
219
|
+
if (picture && picture.type) {
|
|
220
|
+
this.storeImage(`${device.getSerial()}.${picture.type.ext}`, picture.data);
|
|
221
|
+
if (this.currentSnapshot && (Date.now() - this.currentSnapshot.timestamp) < SNAPSHOT_CLOUD_SKIP_MS) {
|
|
222
|
+
this.log.debug('Skipping cloud snapshot update, a recent stream snapshot already exists.');
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
this.storeSnapshotForCache(picture.data);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case 'enabled': {
|
|
231
|
+
const enabled = device.getPropertyValue(PropertyName.DeviceEnabled);
|
|
232
|
+
this.log.info(`Device enabled state changed to: ${enabled}`);
|
|
233
|
+
if (enabled) {
|
|
234
|
+
this.currentSnapshot = undefined;
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case 'state': {
|
|
239
|
+
const state = device.getPropertyValue(PropertyName.DeviceState);
|
|
240
|
+
const wasOffline = this.isDeviceOffline;
|
|
241
|
+
this.isDeviceOffline = (state === 0 || state === 3);
|
|
242
|
+
if (this.isDeviceOffline && !wasOffline) {
|
|
243
|
+
this.log.warn(`Device went offline (state: ${state}).`);
|
|
244
|
+
}
|
|
245
|
+
else if (!this.isDeviceOffline && wasOffline) {
|
|
246
|
+
this.log.info(`Device came back online (state: ${state}).`);
|
|
247
|
+
this.currentSnapshot = undefined;
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Fetches a snapshot from the camera, stores it in cache, and deduplicates concurrent calls.
|
|
255
|
+
*/
|
|
256
|
+
async fetchCurrentCameraSnapshot() {
|
|
257
|
+
if (this.pendingFetch) {
|
|
258
|
+
return this.pendingFetch;
|
|
259
|
+
}
|
|
260
|
+
this.log.debug('Fetching new snapshot from camera.');
|
|
261
|
+
this.pendingFetch = this.withTimeout((async () => {
|
|
262
|
+
const source = await this.getCameraSource();
|
|
263
|
+
const isLocalStream = source.type === 'local';
|
|
264
|
+
try {
|
|
265
|
+
const buffer = await this.runFFmpegSnapshot('[Snapshot Process]', async (params) => {
|
|
266
|
+
if (source.type === 'rtsp') {
|
|
267
|
+
params.setInputSource(source.url);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
await params.setInputStream(source.stream);
|
|
271
|
+
}
|
|
272
|
+
if (this.cameraConfig.delayCameraSnapshot) {
|
|
273
|
+
params.setDelayedSnapshot();
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
this.storeSnapshotForCache(buffer);
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
if (isLocalStream) {
|
|
280
|
+
this.livestreamManager.stopLocalLiveStream();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
})(), SNAPSHOT_FETCH_TIMEOUT_MS).finally(() => {
|
|
284
|
+
this.pendingFetch = undefined;
|
|
285
|
+
});
|
|
286
|
+
return this.pendingFetch;
|
|
287
|
+
}
|
|
288
|
+
async getCameraSource() {
|
|
289
|
+
if (isRtspReady(this.device, this.cameraConfig)) {
|
|
290
|
+
const url = this.device.getPropertyValue(PropertyName.DeviceRTSPStreamUrl);
|
|
291
|
+
this.log.debug('RTSP URL: ' + url);
|
|
292
|
+
return { type: 'rtsp', url };
|
|
293
|
+
}
|
|
294
|
+
const streamData = await this.livestreamManager.getLocalLiveStream();
|
|
295
|
+
return { type: 'local', stream: streamData.videostream };
|
|
296
|
+
}
|
|
297
|
+
withTimeout(promise, ms) {
|
|
298
|
+
return new Promise((resolve, reject) => {
|
|
299
|
+
const timer = setTimeout(() => reject(new Error(`Snapshot fetch timed out after ${ms}ms`)), ms);
|
|
300
|
+
promise.then(resolve, reject).finally(() => clearTimeout(timer));
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Captures a single frame from an already-running livestream and stores it
|
|
305
|
+
* as the latest snapshot. Does NOT stop the livestream — safe to call while
|
|
306
|
+
* a HomeKit live view is active.
|
|
307
|
+
*/
|
|
308
|
+
async captureSnapshotFromActiveLivestream() {
|
|
309
|
+
if (this.pendingFetch) {
|
|
310
|
+
this.log.debug('Snapshot fetch already in progress, skipping livestream capture.');
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
const source = await this.getCameraSource();
|
|
315
|
+
const buffer = await this.runFFmpegSnapshot('[Livestream Snapshot]', async (params) => {
|
|
316
|
+
if (source.type === 'rtsp') {
|
|
317
|
+
params.setInputSource(source.url);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
await params.setInputStream(source.stream);
|
|
321
|
+
}
|
|
322
|
+
if (this.cameraConfig.delayCameraSnapshot) {
|
|
323
|
+
params.setDelayedSnapshot();
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
this.storeSnapshotForCache(buffer);
|
|
327
|
+
this.storeImage(`${this.device.getSerial()}.jpg`, buffer);
|
|
328
|
+
this.log.info('Snapshot captured from active livestream.');
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
this.log.debug('Failed to capture snapshot from active livestream: ' + error);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async resizeSnapshot(snapshot, request) {
|
|
335
|
+
return this.runFFmpegSnapshot('[Snapshot Resize]', (params) => {
|
|
336
|
+
params.setup(this.cameraConfig, request);
|
|
337
|
+
}, snapshot);
|
|
338
|
+
}
|
|
339
|
+
async runFFmpegSnapshot(label, configure, input) {
|
|
340
|
+
const params = await FFmpegParameters.forSnapshot(this.cameraConfig.videoConfig?.debug);
|
|
341
|
+
await configure(params);
|
|
342
|
+
return new FFmpeg(label, params).getResult(input);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
//# sourceMappingURL=snapshotDelegate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshotDelegate.js","sourceRoot":"","sources":["../../src/controller/snapshotDelegate.ts"],"names":[],"mappings":"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"}
|