@apocaliss92/scrypted-reolink-native 0.1.30 → 0.1.31
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/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/baichuan-base.ts +0 -4
- package/src/camera-battery.ts +4 -59
- package/src/camera.ts +0 -15
- package/src/common.ts +187 -57
- package/src/debug-options.ts +0 -8
- package/src/main.ts +15 -30
- package/src/multiFocal.ts +53 -20
- package/src/nvr.ts +26 -8
- package/src/stream-utils.ts +144 -5
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/baichuan-base.ts
CHANGED
|
@@ -58,10 +58,6 @@ export class BaichuanLogger implements Console {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
isDebugEnabled(): boolean {
|
|
62
|
-
return this.isDebugEnabledCallback();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
61
|
// Console interface implementation - delegate to baseLogger
|
|
66
62
|
assert(condition?: boolean, ...data: any[]): void {
|
|
67
63
|
this.baseLogger.assert(condition, ...data);
|
package/src/camera-battery.ts
CHANGED
|
@@ -13,8 +13,6 @@ import { ReolinkNativeNvrDevice } from "./nvr";
|
|
|
13
13
|
import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
|
|
14
14
|
|
|
15
15
|
export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
16
|
-
private lastPicture: { mo: MediaObject; atMs: number } | undefined;
|
|
17
|
-
private takePictureInFlight: Promise<MediaObject> | undefined;
|
|
18
16
|
doorbellBinaryTimeout?: NodeJS.Timeout;
|
|
19
17
|
motionDetected: boolean = false;
|
|
20
18
|
motionTimeout: NodeJS.Timeout | undefined;
|
|
@@ -22,17 +20,11 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
|
22
20
|
private sleepCheckTimer: NodeJS.Timeout | undefined;
|
|
23
21
|
private batteryUpdateTimer: NodeJS.Timeout | undefined;
|
|
24
22
|
private lastBatteryLevel: number | undefined;
|
|
25
|
-
private forceNewSnapshot: boolean = false;
|
|
26
23
|
private batteryUpdateInProgress: boolean = false;
|
|
27
24
|
|
|
28
|
-
private isBatteryInfoLoggingEnabled(): boolean {
|
|
29
|
-
const debugLogs = this.storageSettings.values.debugLogs || [];
|
|
30
|
-
return debugLogs.includes(DebugLogOption.BatteryInfo);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
25
|
constructor(
|
|
34
|
-
nativeId: string,
|
|
35
|
-
public plugin: ReolinkNativePlugin,
|
|
26
|
+
nativeId: string,
|
|
27
|
+
public plugin: ReolinkNativePlugin,
|
|
36
28
|
nvrDevice?: ReolinkNativeNvrDevice,
|
|
37
29
|
multiFocalDevice?: ReolinkNativeMultiFocalDevice
|
|
38
30
|
) {
|
|
@@ -43,53 +35,6 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
|
43
35
|
});
|
|
44
36
|
}
|
|
45
37
|
|
|
46
|
-
async takePicture(options?: RequestPictureOptions): Promise<MediaObject> {
|
|
47
|
-
const logger = this.getBaichuanLogger();
|
|
48
|
-
// Allow new snapshot if:
|
|
49
|
-
// 1. forceNewSnapshot is true, OR
|
|
50
|
-
// 2. Camera is awake AND last snapshot was taken at least 10 seconds ago
|
|
51
|
-
// const minSnapshotIntervalMs = 10_000; // 10 seconds
|
|
52
|
-
// const now = Date.now();
|
|
53
|
-
const shouldTakeNewSnapshot = this.forceNewSnapshot;
|
|
54
|
-
// const now = Date.now();
|
|
55
|
-
// const shouldTakeNewSnapshot = this.forceNewSnapshot ||
|
|
56
|
-
// (!this.sleeping && this.lastPicture && (now - this.lastPicture.atMs >= minSnapshotIntervalMs));
|
|
57
|
-
|
|
58
|
-
if (!shouldTakeNewSnapshot && this.lastPicture) {
|
|
59
|
-
logger.debug(`Returning cached snapshot, taken at ${new Date(this.lastPicture.atMs).toLocaleString()}`);
|
|
60
|
-
return this.lastPicture.mo;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (this.takePictureInFlight) {
|
|
64
|
-
return await this.takePictureInFlight;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
logger.log(`Taking new snapshot from camera (forceNewSnapshot: ${this.forceNewSnapshot})`);
|
|
68
|
-
this.forceNewSnapshot = false;
|
|
69
|
-
|
|
70
|
-
this.takePictureInFlight = (async () => {
|
|
71
|
-
const channel = this.storageSettings.values.rtspChannel;
|
|
72
|
-
const snapshotBuffer = await this.withBaichuanClient(async (api) => {
|
|
73
|
-
return await api.getSnapshot(channel);
|
|
74
|
-
});
|
|
75
|
-
const mo = await sdk.mediaManager.createMediaObject(snapshotBuffer, 'image/jpeg');
|
|
76
|
-
this.lastPicture = { mo, atMs: Date.now() };
|
|
77
|
-
logger.log(`Snapshot taken at ${new Date(this.lastPicture.atMs).toLocaleString()}`);
|
|
78
|
-
return mo;
|
|
79
|
-
})();
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
return await this.takePictureInFlight;
|
|
83
|
-
}
|
|
84
|
-
finally {
|
|
85
|
-
this.takePictureInFlight = undefined;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async getPictureOptions(): Promise<ResponsePictureOptions[]> {
|
|
90
|
-
return [];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
38
|
async init(): Promise<void> {
|
|
94
39
|
this.startPeriodicTasks();
|
|
95
40
|
await this.alignAuxDevicesState();
|
|
@@ -154,7 +99,7 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
|
154
99
|
|
|
155
100
|
async updateSleepingState(sleepStatus: SleepStatus): Promise<void> {
|
|
156
101
|
try {
|
|
157
|
-
if (this.
|
|
102
|
+
if (this.isDebugEnabled()) {
|
|
158
103
|
this.getBaichuanLogger().debug('getSleepStatus result:', JSON.stringify(sleepStatus));
|
|
159
104
|
}
|
|
160
105
|
|
|
@@ -212,7 +157,7 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
|
212
157
|
const channel = this.storageSettings.values.rtspChannel;
|
|
213
158
|
|
|
214
159
|
const batteryInfo = await api.getBatteryInfo(channel);
|
|
215
|
-
if (this.
|
|
160
|
+
if (this.isDebugEnabled()) {
|
|
216
161
|
this.getBaichuanLogger().debug('getBatteryInfo result:', JSON.stringify(batteryInfo));
|
|
217
162
|
}
|
|
218
163
|
|
package/src/camera.ts
CHANGED
|
@@ -175,21 +175,6 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
|
|
|
175
175
|
return fn(client);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
async takePicture(options?: RequestPictureOptions) {
|
|
179
|
-
try {
|
|
180
|
-
return this.withBaichuanRetry(async () => {
|
|
181
|
-
const client = await this.ensureClient();
|
|
182
|
-
const snapshotBuffer = await client.getSnapshot(this.storageSettings.values.rtspChannel);
|
|
183
|
-
const mo = await this.createMediaObject(snapshotBuffer, 'image/jpeg');
|
|
184
|
-
|
|
185
|
-
return mo;
|
|
186
|
-
});
|
|
187
|
-
} catch (e) {
|
|
188
|
-
this.getBaichuanLogger().error('Error taking snapshot', e);
|
|
189
|
-
throw e;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
178
|
async getPictureOptions(): Promise<ResponsePictureOptions[]> {
|
|
194
179
|
return [];
|
|
195
180
|
}
|
package/src/common.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import type { DeviceCapabilities, PtzCommand, PtzPreset, ReolinkBaichuanApi, ReolinkSimpleEvent, ReolinkSupportedStream, StreamSamplingSelection } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
|
-
import sdk, { BinarySensor, Brightness, Camera, Device, DeviceProvider, Intercom, MediaObject, MediaStreamUrl, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, OnOff, PanTiltZoom, PanTiltZoomCommand, RequestMediaStreamOptions, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoTextOverlay, VideoTextOverlays } from "@scrypted/sdk";
|
|
2
|
+
import sdk, { BinarySensor, Brightness, Camera, Device, DeviceProvider, Intercom, MediaObject, MediaStreamUrl, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, OnOff, PanTiltZoom, PanTiltZoomCommand, Reboot, RequestMediaStreamOptions, RequestPictureOptions, ResponsePictureOptions, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoTextOverlay, VideoTextOverlays } from "@scrypted/sdk";
|
|
3
3
|
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import type { UrlMediaStreamOptions } from "../../scrypted/plugins/rtsp/src/rtsp";
|
|
6
6
|
import { BaseBaichuanClass, type BaichuanConnectionCallbacks, type BaichuanConnectionConfig } from "./baichuan-base";
|
|
7
7
|
import { normalizeUid, type BaichuanTransport } from "./connect";
|
|
8
|
-
import { convertDebugLogsToApiOptions,
|
|
8
|
+
import { convertDebugLogsToApiOptions, getApiRelevantDebugLogs, getDebugLogChoices } from "./debug-options";
|
|
9
9
|
import { ReolinkBaichuanIntercom } from "./intercom";
|
|
10
10
|
import ReolinkNativePlugin from "./main";
|
|
11
11
|
import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
|
|
12
12
|
import { ReolinkNativeNvrDevice } from "./nvr";
|
|
13
13
|
import { ReolinkPtzPresets } from "./presets";
|
|
14
14
|
import {
|
|
15
|
+
createRfc4571CompositeMediaObjectFromStreamManager,
|
|
15
16
|
createRfc4571MediaObjectFromStreamManager,
|
|
16
17
|
expectedVideoTypeFromUrlMediaStreamOptions,
|
|
17
18
|
parseStreamProfileFromId,
|
|
@@ -134,7 +135,8 @@ class ReolinkCameraPirSensor extends ScryptedDeviceBase implements OnOff, Settin
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
async getSettings(): Promise<Setting[]> {
|
|
137
|
-
|
|
138
|
+
const settings = await this.storageSettings.getSettings();
|
|
139
|
+
return settings;
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
async putSetting(key: string, value: SettingValue): Promise<void> {
|
|
@@ -187,7 +189,7 @@ class ReolinkCameraPirSensor extends ScryptedDeviceBase implements OnOff, Settin
|
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
191
|
|
|
190
|
-
export abstract class CommonCameraMixin extends BaseBaichuanClass implements VideoCamera, Camera, Settings, DeviceProvider, ObjectDetector, PanTiltZoom, VideoTextOverlays, BinarySensor, Intercom {
|
|
192
|
+
export abstract class CommonCameraMixin extends BaseBaichuanClass implements VideoCamera, Camera, Settings, DeviceProvider, ObjectDetector, PanTiltZoom, VideoTextOverlays, BinarySensor, Intercom, Reboot {
|
|
191
193
|
storageSettings = new StorageSettings(this, {
|
|
192
194
|
// Basic connection settings
|
|
193
195
|
ipAddress: {
|
|
@@ -197,12 +199,6 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
197
199
|
await this.credentialsChanged();
|
|
198
200
|
}
|
|
199
201
|
},
|
|
200
|
-
debugEvents: {
|
|
201
|
-
title: 'Debug Events',
|
|
202
|
-
type: 'boolean',
|
|
203
|
-
immediate: true,
|
|
204
|
-
hide: true,
|
|
205
|
-
},
|
|
206
202
|
username: {
|
|
207
203
|
type: 'string',
|
|
208
204
|
title: 'Username',
|
|
@@ -230,6 +226,53 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
230
226
|
json: true,
|
|
231
227
|
hide: true,
|
|
232
228
|
},
|
|
229
|
+
// Multifocal composite stream PIP settings
|
|
230
|
+
pipPosition: {
|
|
231
|
+
title: 'PIP Position',
|
|
232
|
+
description: 'Position of the tele lens overlay on the wider lens view',
|
|
233
|
+
type: 'string',
|
|
234
|
+
defaultValue: 'bottom-right',
|
|
235
|
+
choices: [
|
|
236
|
+
'top-left',
|
|
237
|
+
'top-right',
|
|
238
|
+
'bottom-left',
|
|
239
|
+
'bottom-right',
|
|
240
|
+
'center',
|
|
241
|
+
'top-center',
|
|
242
|
+
'bottom-center',
|
|
243
|
+
'left-center',
|
|
244
|
+
'right-center',
|
|
245
|
+
],
|
|
246
|
+
hide: true, // Only show for multifocal devices via getAdditionalSettings
|
|
247
|
+
},
|
|
248
|
+
pipSize: {
|
|
249
|
+
title: 'PIP Size',
|
|
250
|
+
description: 'Relative size of the PIP overlay (0.1 = 10%, 0.3 = 30%, etc.)',
|
|
251
|
+
type: 'number',
|
|
252
|
+
defaultValue: 0.25,
|
|
253
|
+
hide: true, // Only show for multifocal devices via getAdditionalSettings
|
|
254
|
+
},
|
|
255
|
+
pipMargin: {
|
|
256
|
+
title: 'PIP Margin',
|
|
257
|
+
description: 'Margin from edge in pixels',
|
|
258
|
+
type: 'number',
|
|
259
|
+
defaultValue: 10,
|
|
260
|
+
hide: true, // Only show for multifocal devices via getAdditionalSettings
|
|
261
|
+
},
|
|
262
|
+
widerChannel: {
|
|
263
|
+
title: 'Wider Channel',
|
|
264
|
+
description: 'Channel number for wider lens (typically 0)',
|
|
265
|
+
type: 'number',
|
|
266
|
+
defaultValue: 0,
|
|
267
|
+
hide: true, // Only show for multifocal devices via getAdditionalSettings
|
|
268
|
+
},
|
|
269
|
+
teleChannel: {
|
|
270
|
+
title: 'Tele Channel',
|
|
271
|
+
description: 'Channel number for tele lens (typically 1)',
|
|
272
|
+
type: 'number',
|
|
273
|
+
defaultValue: 1,
|
|
274
|
+
hide: true, // Only show for multifocal devices via getAdditionalSettings
|
|
275
|
+
},
|
|
233
276
|
// Battery camera specific
|
|
234
277
|
uid: {
|
|
235
278
|
title: 'UID',
|
|
@@ -240,6 +283,11 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
240
283
|
await this.credentialsChanged();
|
|
241
284
|
}
|
|
242
285
|
},
|
|
286
|
+
debugLogs: {
|
|
287
|
+
title: 'Debug logs',
|
|
288
|
+
type: 'boolean',
|
|
289
|
+
immediate: true,
|
|
290
|
+
},
|
|
243
291
|
mixinsSetup: {
|
|
244
292
|
type: 'boolean',
|
|
245
293
|
hide: true,
|
|
@@ -258,10 +306,10 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
258
306
|
await this.subscribeToEvents();
|
|
259
307
|
},
|
|
260
308
|
},
|
|
261
|
-
|
|
309
|
+
socketApiDebugLogs: {
|
|
262
310
|
subgroup: 'Advanced',
|
|
263
|
-
title: 'Debug Logs',
|
|
264
|
-
description: 'Enable specific debug logs.
|
|
311
|
+
title: 'Socket API Debug Logs',
|
|
312
|
+
description: 'Enable specific debug logs.',
|
|
265
313
|
multiple: true,
|
|
266
314
|
combobox: true,
|
|
267
315
|
immediate: true,
|
|
@@ -508,6 +556,11 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
508
556
|
floodlight?: ReolinkCameraFloodlight;
|
|
509
557
|
pirSensor?: ReolinkCameraPirSensor;
|
|
510
558
|
|
|
559
|
+
|
|
560
|
+
private lastPicture: { mo: MediaObject; atMs: number } | undefined;
|
|
561
|
+
private takePictureInFlight: Promise<MediaObject> | undefined;
|
|
562
|
+
forceNewSnapshot: boolean = false;
|
|
563
|
+
|
|
511
564
|
// Video stream properties
|
|
512
565
|
protected cachedVideoStreamOptions?: UrlMediaStreamOptions[];
|
|
513
566
|
protected fetchingStreams = false;
|
|
@@ -551,6 +604,11 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
551
604
|
}, 2000);
|
|
552
605
|
}
|
|
553
606
|
|
|
607
|
+
async reboot(): Promise<void> {
|
|
608
|
+
const api = await this.ensureBaichuanClient();
|
|
609
|
+
await api.reboot();
|
|
610
|
+
}
|
|
611
|
+
|
|
554
612
|
// BaseBaichuanClass abstract methods implementation
|
|
555
613
|
protected getConnectionConfig(): BaichuanConnectionConfig {
|
|
556
614
|
const { ipAddress, username, password, uid } = this.storageSettings.values;
|
|
@@ -599,9 +657,8 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
599
657
|
};
|
|
600
658
|
}
|
|
601
659
|
|
|
602
|
-
|
|
603
660
|
protected isDebugEnabled(): boolean {
|
|
604
|
-
return this.
|
|
661
|
+
return this.storageSettings.values.debugLogs;
|
|
605
662
|
}
|
|
606
663
|
|
|
607
664
|
protected getDeviceName(): string {
|
|
@@ -1029,11 +1086,6 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1029
1086
|
}
|
|
1030
1087
|
}
|
|
1031
1088
|
|
|
1032
|
-
isEventLogsEnabled(): boolean {
|
|
1033
|
-
const debugLogs = this.storageSettings.values.debugLogs || [];
|
|
1034
|
-
return debugLogs.includes(DebugLogDisplayNames[DebugLogOption.EventLogs]);
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
1089
|
// BinarySensor interface implementation (for doorbell)
|
|
1038
1090
|
handleDoorbellEvent(): void {
|
|
1039
1091
|
if (!this.doorbellBinaryTimeout) {
|
|
@@ -1114,9 +1166,59 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1114
1166
|
await this.storageSettings.putSetting(key, value);
|
|
1115
1167
|
}
|
|
1116
1168
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1169
|
+
async takePicture(options?: RequestPictureOptions) {
|
|
1170
|
+
if (this.protocol === 'tcp') {
|
|
1171
|
+
try {
|
|
1172
|
+
return this.withBaichuanRetry(async () => {
|
|
1173
|
+
const client = await this.ensureClient();
|
|
1174
|
+
const snapshotBuffer = await client.getSnapshot(this.storageSettings.values.rtspChannel);
|
|
1175
|
+
const mo = await this.createMediaObject(snapshotBuffer, 'image/jpeg');
|
|
1176
|
+
|
|
1177
|
+
return mo;
|
|
1178
|
+
});
|
|
1179
|
+
} catch (e) {
|
|
1180
|
+
this.getBaichuanLogger().error('Error taking snapshot', e);
|
|
1181
|
+
throw e;
|
|
1182
|
+
}
|
|
1183
|
+
} else {
|
|
1184
|
+
const logger = this.getBaichuanLogger();
|
|
1185
|
+
const shouldTakeNewSnapshot = this.forceNewSnapshot;
|
|
1186
|
+
|
|
1187
|
+
if (!shouldTakeNewSnapshot && this.lastPicture) {
|
|
1188
|
+
logger.log(`Returning cached snapshot, taken at ${new Date(this.lastPicture.atMs).toLocaleString()}`);
|
|
1189
|
+
return this.lastPicture.mo;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
if (this.takePictureInFlight) {
|
|
1193
|
+
return await this.takePictureInFlight;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
logger.log(`Taking new snapshot from camera (forceNewSnapshot: ${this.forceNewSnapshot})`);
|
|
1197
|
+
this.forceNewSnapshot = false;
|
|
1198
|
+
|
|
1199
|
+
this.takePictureInFlight = (async () => {
|
|
1200
|
+
const channel = this.storageSettings.values.rtspChannel;
|
|
1201
|
+
const snapshotBuffer = await this.withBaichuanClient(async (api) => {
|
|
1202
|
+
return await api.getSnapshot(channel);
|
|
1203
|
+
});
|
|
1204
|
+
const mo = await sdk.mediaManager.createMediaObject(snapshotBuffer, 'image/jpeg');
|
|
1205
|
+
this.lastPicture = { mo, atMs: Date.now() };
|
|
1206
|
+
logger.log(`Snapshot taken at ${new Date(this.lastPicture.atMs).toLocaleString()}`);
|
|
1207
|
+
return mo;
|
|
1208
|
+
})();
|
|
1209
|
+
|
|
1210
|
+
try {
|
|
1211
|
+
return await this.takePictureInFlight;
|
|
1212
|
+
}
|
|
1213
|
+
finally {
|
|
1214
|
+
this.takePictureInFlight = undefined;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
async getPictureOptions(): Promise<ResponsePictureOptions[]> {
|
|
1220
|
+
return [];
|
|
1221
|
+
}
|
|
1120
1222
|
|
|
1121
1223
|
// Intercom interface methods
|
|
1122
1224
|
async startIntercom(media: MediaObject): Promise<void> {
|
|
@@ -1342,12 +1444,15 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1342
1444
|
|
|
1343
1445
|
const client = await this.ensureClient();
|
|
1344
1446
|
|
|
1345
|
-
|
|
1447
|
+
// For multifocal devices, use undefined channel to get composite streams
|
|
1448
|
+
const isMultiFocal = this.options.type === 'multi-focal' || this.options.type === 'multi-focal-battery';
|
|
1449
|
+
const channel = isMultiFocal ? undefined : this.storageSettings.values.rtspChannel;
|
|
1346
1450
|
|
|
1347
1451
|
try {
|
|
1348
|
-
const { nativeStreams, rtmpStreams, rtspStreams } = await client.buildVideoStreamOptions(
|
|
1452
|
+
const { nativeStreams, rtmpStreams, rtspStreams } = await client.buildVideoStreamOptions(channel);
|
|
1349
1453
|
|
|
1350
1454
|
let supportedStreams: ReolinkSupportedStream[] = [];
|
|
1455
|
+
// Homehub RTMP is not efficient, crashes, offers native streams to not overload the hub
|
|
1351
1456
|
if (this.nvrDevice && this.nvrDevice.info.model === 'HOMEHUB') {
|
|
1352
1457
|
supportedStreams = [...nativeStreams, ...rtspStreams, ...rtmpStreams];
|
|
1353
1458
|
} else {
|
|
@@ -1408,6 +1513,28 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1408
1513
|
throw new Error('StreamManager not initialized');
|
|
1409
1514
|
}
|
|
1410
1515
|
|
|
1516
|
+
// Check if this is a composite stream request (for multifocal devices)
|
|
1517
|
+
const isComposite = selected.id?.startsWith('composite_');
|
|
1518
|
+
if (isComposite && this.options && (this.options.type === 'multi-focal' || this.options.type === 'multi-focal-battery')) {
|
|
1519
|
+
const profile = parseStreamProfileFromId(selected.id.replace('composite_', '')) || 'main';
|
|
1520
|
+
const streamKey = `composite_${profile}`;
|
|
1521
|
+
const expectedVideoType = expectedVideoTypeFromUrlMediaStreamOptions(selected);
|
|
1522
|
+
|
|
1523
|
+
const createStreamFn = async () => {
|
|
1524
|
+
return await createRfc4571CompositeMediaObjectFromStreamManager({
|
|
1525
|
+
streamManager: this.streamManager!,
|
|
1526
|
+
profile,
|
|
1527
|
+
streamKey,
|
|
1528
|
+
expectedVideoType,
|
|
1529
|
+
selected,
|
|
1530
|
+
sourceId: this.id,
|
|
1531
|
+
});
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
return await this.withBaichuanRetry(createStreamFn);
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// Regular stream for single channel
|
|
1411
1538
|
const profile = parseStreamProfileFromId(selected.id) || 'main';
|
|
1412
1539
|
const channel = this.storageSettings.values.rtspChannel;
|
|
1413
1540
|
const streamKey = `${channel}_${profile}`;
|
|
@@ -1539,17 +1666,22 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1539
1666
|
}
|
|
1540
1667
|
|
|
1541
1668
|
const { username, password } = this.storageSettings.values;
|
|
1542
|
-
const
|
|
1543
|
-
const
|
|
1544
|
-
const isBatteryMultiFocal = this.options.type === 'multi-focal-battery';
|
|
1545
|
-
const isBattery = isBatteryCamera || isBatteryMultiFocal;
|
|
1669
|
+
const isBattery = ['multi-focal-battery', 'battery'].includes(this.options.type);
|
|
1670
|
+
const isMultiFocal = ['multi-focal', 'multi-focal'].includes(this.options.type);
|
|
1546
1671
|
|
|
1547
1672
|
this.storageSettings.settings.uid.hide = !isBattery;
|
|
1548
1673
|
this.storageSettings.settings.batteryUpdateIntervalMinutes.hide = !isBattery;
|
|
1549
1674
|
this.storageSettings.settings.lowThresholdBatteryRecording.hide = !isBattery;
|
|
1550
1675
|
this.storageSettings.settings.highThresholdBatteryRecording.hide = !isBattery;
|
|
1551
1676
|
|
|
1552
|
-
|
|
1677
|
+
// Show PIP settings only for multifocal devices
|
|
1678
|
+
this.storageSettings.settings.pipPosition.hide = !isMultiFocal;
|
|
1679
|
+
this.storageSettings.settings.pipSize.hide = !isMultiFocal;
|
|
1680
|
+
this.storageSettings.settings.pipMargin.hide = !isMultiFocal;
|
|
1681
|
+
this.storageSettings.settings.widerChannel.hide = !isMultiFocal;
|
|
1682
|
+
this.storageSettings.settings.teleChannel.hide = !isMultiFocal;
|
|
1683
|
+
|
|
1684
|
+
if (isBattery && !this.storageSettings.values.mixinsSetup) {
|
|
1553
1685
|
try {
|
|
1554
1686
|
const device = sdk.systemManager.getDeviceById<Settings>(this.id);
|
|
1555
1687
|
if (device) {
|
|
@@ -1571,39 +1703,37 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1571
1703
|
logger.warn('Failed to subscribe to Baichuan events', e);
|
|
1572
1704
|
}
|
|
1573
1705
|
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
});
|
|
1706
|
+
this.streamManager = new StreamManager({
|
|
1707
|
+
createStreamClient: () => this.createStreamClient(),
|
|
1708
|
+
getLogger: () => logger,
|
|
1709
|
+
credentials: {
|
|
1710
|
+
username,
|
|
1711
|
+
password
|
|
1712
|
+
},
|
|
1713
|
+
sharedConnection: isBattery,
|
|
1714
|
+
});
|
|
1584
1715
|
|
|
1585
|
-
|
|
1716
|
+
const { hasIntercom, hasPtz } = this.getAbilities();
|
|
1586
1717
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1718
|
+
if (hasIntercom) {
|
|
1719
|
+
this.intercom = new ReolinkBaichuanIntercom(this);
|
|
1720
|
+
}
|
|
1590
1721
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1722
|
+
if (hasPtz) {
|
|
1723
|
+
const choices = (this.presets || []).map((preset: any) => preset.id + '=' + preset.name);
|
|
1593
1724
|
|
|
1594
|
-
|
|
1595
|
-
|
|
1725
|
+
this.storageSettings.settings.presets.choices = choices;
|
|
1726
|
+
this.storageSettings.settings.ptzSelectedPreset.choices = choices;
|
|
1596
1727
|
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1728
|
+
this.storageSettings.settings.presets.hide = false;
|
|
1729
|
+
this.storageSettings.settings.ptzMoveDurationMs.hide = false;
|
|
1730
|
+
this.storageSettings.settings.ptzZoomStep.hide = false;
|
|
1731
|
+
this.storageSettings.settings.ptzCreatePreset.hide = false;
|
|
1732
|
+
this.storageSettings.settings.ptzSelectedPreset.hide = false;
|
|
1733
|
+
this.storageSettings.settings.ptzUpdateSelectedPreset.hide = false;
|
|
1734
|
+
this.storageSettings.settings.ptzDeleteSelectedPreset.hide = false;
|
|
1604
1735
|
|
|
1605
|
-
|
|
1606
|
-
}
|
|
1736
|
+
this.updatePtzCaps();
|
|
1607
1737
|
}
|
|
1608
1738
|
|
|
1609
1739
|
if (this.nvrDevice || this.multiFocalDevice) {
|
package/src/debug-options.ts
CHANGED
|
@@ -18,10 +18,6 @@ export enum DebugLogOption {
|
|
|
18
18
|
DebugH264 = 'debugH264',
|
|
19
19
|
/** SPS/PPS parameter sets debug logs */
|
|
20
20
|
DebugParamSets = 'debugParamSets',
|
|
21
|
-
/** Event logs (plugin-specific, not passed to API) */
|
|
22
|
-
EventLogs = 'eventLogs',
|
|
23
|
-
/** Battery info logs (plugin-specific, not passed to API) */
|
|
24
|
-
BatteryInfo = 'batteryInfo',
|
|
25
21
|
}
|
|
26
22
|
|
|
27
23
|
/**
|
|
@@ -36,8 +32,6 @@ export function mapDebugLogToApiOption(option: DebugLogOption): keyof DebugOptio
|
|
|
36
32
|
[DebugLogOption.TraceEvents]: 'traceEvents',
|
|
37
33
|
[DebugLogOption.DebugH264]: 'debugH264',
|
|
38
34
|
[DebugLogOption.DebugParamSets]: 'debugParamSets',
|
|
39
|
-
[DebugLogOption.EventLogs]: null, // Plugin-specific, not passed to API
|
|
40
|
-
[DebugLogOption.BatteryInfo]: null, // Plugin-specific, not passed to API
|
|
41
35
|
};
|
|
42
36
|
return mapping[option];
|
|
43
37
|
}
|
|
@@ -88,8 +82,6 @@ export const DebugLogDisplayNames: Record<DebugLogOption, string> = {
|
|
|
88
82
|
[DebugLogOption.TraceEvents]: 'Trace events XML',
|
|
89
83
|
[DebugLogOption.DebugH264]: 'H264',
|
|
90
84
|
[DebugLogOption.DebugParamSets]: 'Video param sets',
|
|
91
|
-
[DebugLogOption.EventLogs]: 'Object detection events',
|
|
92
|
-
[DebugLogOption.BatteryInfo]: 'Battery info update',
|
|
93
85
|
};
|
|
94
86
|
|
|
95
87
|
/**
|
package/src/main.ts
CHANGED
|
@@ -57,7 +57,10 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
57
57
|
},
|
|
58
58
|
);
|
|
59
59
|
|
|
60
|
-
this.console.log(`[AutoDetect] Detected device type ${detection.type}
|
|
60
|
+
this.console.log(`[AutoDetect] Detected device type: ${detection.type} (transport: ${detection.transport})`);
|
|
61
|
+
|
|
62
|
+
// Use the API that was successfully used for detection
|
|
63
|
+
const detectedApi = detection.api;
|
|
61
64
|
|
|
62
65
|
// Handle multi-focal device case
|
|
63
66
|
if (detection.type === 'multifocal') {
|
|
@@ -69,18 +72,12 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
69
72
|
|
|
70
73
|
settings.newCamera ||= name;
|
|
71
74
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (isBattery) {
|
|
79
|
-
interfaces.push(
|
|
80
|
-
ScryptedInterface.Battery,
|
|
81
|
-
ScryptedInterface.Sleep
|
|
82
|
-
);
|
|
83
|
-
}
|
|
75
|
+
const { capabilities, objects, presets } = await detectedApi.getDeviceCapabilities();
|
|
76
|
+
|
|
77
|
+
const { interfaces } = getDeviceInterfaces({
|
|
78
|
+
capabilities,
|
|
79
|
+
logger: this.console,
|
|
80
|
+
});
|
|
84
81
|
|
|
85
82
|
await sdk.deviceManager.onDeviceDiscovered({
|
|
86
83
|
nativeId,
|
|
@@ -94,10 +91,13 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
94
91
|
if (!(device instanceof ReolinkNativeMultiFocalDevice)) {
|
|
95
92
|
throw new Error('Expected multi-focal device but got different type');
|
|
96
93
|
}
|
|
94
|
+
device.classes = objects;
|
|
95
|
+
device.presets = presets;
|
|
97
96
|
device.storageSettings.values.ipAddress = ipAddress;
|
|
98
97
|
device.storageSettings.values.username = username;
|
|
99
98
|
device.storageSettings.values.password = password;
|
|
100
99
|
device.storageSettings.values.uid = detection.uid || '';
|
|
100
|
+
device.storageSettings.values.capabilities = capabilities;
|
|
101
101
|
|
|
102
102
|
return nativeId;
|
|
103
103
|
}
|
|
@@ -149,22 +149,10 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
149
149
|
|
|
150
150
|
settings.newCamera ||= name;
|
|
151
151
|
|
|
152
|
-
//
|
|
153
|
-
const api = await createBaichuanApi({
|
|
154
|
-
inputs: {
|
|
155
|
-
host: ipAddress,
|
|
156
|
-
username,
|
|
157
|
-
password,
|
|
158
|
-
uid: detection.uid,
|
|
159
|
-
logger: this.console,
|
|
160
|
-
},
|
|
161
|
-
transport: detection.transport,
|
|
162
|
-
});
|
|
163
|
-
|
|
152
|
+
// Use the API that was successfully used for detection
|
|
164
153
|
try {
|
|
165
|
-
await api.login();
|
|
166
154
|
const rtspChannel = 0;
|
|
167
|
-
const { capabilities, objects, presets } = await
|
|
155
|
+
const { capabilities, objects, presets } = await detectedApi.getDeviceCapabilities(rtspChannel);
|
|
168
156
|
|
|
169
157
|
const { interfaces, type } = getDeviceInterfaces({
|
|
170
158
|
capabilities,
|
|
@@ -197,9 +185,6 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
197
185
|
this.console.error('Error adding Reolink device', e);
|
|
198
186
|
throw e;
|
|
199
187
|
}
|
|
200
|
-
finally {
|
|
201
|
-
await api.close();
|
|
202
|
-
}
|
|
203
188
|
}
|
|
204
189
|
|
|
205
190
|
async releaseDevice(id: string, nativeId: ScryptedNativeId): Promise<void> {
|