@apocaliss92/scrypted-reolink-native 0.1.36 → 0.1.38
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/.vscode/settings.json +1 -1
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/baichuan-base.ts +3 -2
- package/src/camera-battery.ts +16 -1
- package/src/common.ts +58 -91
- package/src/connect.ts +2 -0
- package/src/main.ts +1 -0
- package/src/nvr.ts +123 -126
- package/src/utils.ts +47 -0
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/baichuan-base.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReolinkBaichuanApi, ReolinkSimpleEvent } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
1
|
+
import type { BaichuanClientOptions, ReolinkBaichuanApi, ReolinkSimpleEvent } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
2
|
import { ScryptedDeviceBase } from "@scrypted/sdk";
|
|
3
3
|
import { createBaichuanApi, type BaichuanTransport } from "./connect";
|
|
4
4
|
|
|
@@ -8,8 +8,8 @@ export interface BaichuanConnectionConfig {
|
|
|
8
8
|
password: string;
|
|
9
9
|
uid?: string;
|
|
10
10
|
transport: BaichuanTransport;
|
|
11
|
-
logger: Console;
|
|
12
11
|
debugOptions?: any;
|
|
12
|
+
udpDiscoveryMethod?: BaichuanClientOptions["udpDiscoveryMethod"];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface BaichuanConnectionCallbacks {
|
|
@@ -259,6 +259,7 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
|
|
|
259
259
|
uid: config.uid,
|
|
260
260
|
logger,
|
|
261
261
|
debugOptions: config.debugOptions,
|
|
262
|
+
udpDiscoveryMethod: config.udpDiscoveryMethod,
|
|
262
263
|
},
|
|
263
264
|
transport: config.transport,
|
|
264
265
|
});
|
package/src/camera-battery.ts
CHANGED
|
@@ -128,7 +128,22 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
|
128
128
|
}
|
|
129
129
|
} catch (e) {
|
|
130
130
|
// Silently ignore errors in sleep check to avoid spam
|
|
131
|
-
this.getBaichuanLogger().debug('Error in
|
|
131
|
+
this.getBaichuanLogger().debug('Error in updateSleepingState:', e);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async updateOnlineState(isOnline: boolean): Promise<void> {
|
|
136
|
+
try {
|
|
137
|
+
if (this.isDebugEnabled()) {
|
|
138
|
+
this.getBaichuanLogger().debug('updateOnlineState result:', isOnline);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (isOnline !== this.online) {
|
|
142
|
+
this.online = isOnline;
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
// Silently ignore errors in sleep check to avoid spam
|
|
146
|
+
this.getBaichuanLogger().debug('Error in updateOnlineState:', e);
|
|
132
147
|
}
|
|
133
148
|
}
|
|
134
149
|
|
package/src/common.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DeviceCapabilities, PtzCommand, PtzPreset, ReolinkBaichuanApi, ReolinkSimpleEvent, ReolinkSupportedStream, StreamProfile, StreamSamplingSelection } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
1
|
+
import type { BaichuanClientOptions, DeviceCapabilities, PtzCommand, PtzPreset, ReolinkBaichuanApi, ReolinkSimpleEvent, ReolinkSupportedStream, StreamProfile, StreamSamplingSelection } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
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, VideoClip, VideoClipOptions, VideoClips, VideoClipThumbnailOptions, VideoTextOverlay, VideoTextOverlays } from "@scrypted/sdk";
|
|
3
3
|
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
|
4
4
|
import path from 'path';
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
selectStreamOption,
|
|
25
25
|
StreamManager
|
|
26
26
|
} from "./stream-utils";
|
|
27
|
-
import { floodlightSuffix, getDeviceInterfaces, getVideoClipWebhookUrls, pirSuffix,
|
|
27
|
+
import { floodlightSuffix, getDeviceInterfaces, getVideoClipWebhookUrls, pirSuffix, recordingsToVideoClips, sanitizeFfmpegOutput, sirenSuffix, updateDeviceInfo, vodSearchResultsToVideoClips } from "./utils";
|
|
28
28
|
|
|
29
29
|
export type CameraType = 'battery' | 'regular' | 'multi-focal' | 'multi-focal-battery';
|
|
30
30
|
|
|
@@ -300,6 +300,17 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
300
300
|
await this.credentialsChanged();
|
|
301
301
|
}
|
|
302
302
|
},
|
|
303
|
+
discoveryMethod: {
|
|
304
|
+
title: 'Discovery Method',
|
|
305
|
+
description: 'UDP discovery method for battery cameras (BCUDP).',
|
|
306
|
+
type: 'string',
|
|
307
|
+
choices: ['local', 'remote', 'map', 'relay'],
|
|
308
|
+
defaultValue: 'local',
|
|
309
|
+
hide: true,
|
|
310
|
+
onPut: async () => {
|
|
311
|
+
await this.credentialsChanged();
|
|
312
|
+
}
|
|
313
|
+
},
|
|
303
314
|
debugLogs: {
|
|
304
315
|
title: 'Debug logs',
|
|
305
316
|
type: 'boolean',
|
|
@@ -664,6 +675,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
664
675
|
private streamManagerRestartTimeout: NodeJS.Timeout | undefined;
|
|
665
676
|
private videoClipsAutoLoadInterval: NodeJS.Timeout | undefined;
|
|
666
677
|
private videoClipsAutoLoadInProgress: boolean = false;
|
|
678
|
+
private videoClipsAutoLoadMode: boolean = false;
|
|
667
679
|
|
|
668
680
|
constructor(
|
|
669
681
|
nativeId: string,
|
|
@@ -698,7 +710,8 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
698
710
|
return [];
|
|
699
711
|
}
|
|
700
712
|
|
|
701
|
-
|
|
713
|
+
// Skip sleeping check during auto-load to allow auto-load to start for battery cameras
|
|
714
|
+
if (!this.videoClipsAutoLoadMode && this.isBattery && this.sleeping) {
|
|
702
715
|
const logger = this.getBaichuanLogger();
|
|
703
716
|
logger.debug('getVideoClips: disabled for battery devices');
|
|
704
717
|
return [];
|
|
@@ -728,13 +741,14 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
728
741
|
|
|
729
742
|
const start = new Date(startMs);
|
|
730
743
|
const end = new Date(endMs);
|
|
731
|
-
|
|
732
|
-
start.setUTCHours(0, 0, 0, 0);
|
|
744
|
+
start.setHours(0, 0, 0, 0);
|
|
733
745
|
|
|
734
746
|
try {
|
|
735
747
|
const { clipsSource } = this.storageSettings.values;
|
|
736
748
|
const useNvr = clipsSource === "NVR" && this.nvrDevice;
|
|
737
749
|
|
|
750
|
+
const api = await this.ensureClient();
|
|
751
|
+
|
|
738
752
|
if (useNvr) {
|
|
739
753
|
// Fetch from NVR using listEnrichedVodFiles (library handles parsing correctly)
|
|
740
754
|
const channel = this.storageSettings.values.rtspChannel ?? 0;
|
|
@@ -742,106 +756,49 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
742
756
|
// Use listEnrichedVodFiles which properly parses filenames and extracts detection info
|
|
743
757
|
logger.debug(`[NVR VOD] Searching for video clips: channel=${channel}, start=${start.toISOString()}, end=${end.toISOString()}`);
|
|
744
758
|
// Filter to only include recordings within the requested time window
|
|
745
|
-
const enrichedRecordings = await
|
|
759
|
+
const enrichedRecordings = await api.listNvrRecordings({
|
|
746
760
|
channel,
|
|
747
761
|
start,
|
|
748
762
|
end,
|
|
749
763
|
streamType: "main",
|
|
750
|
-
autoSearchByDay: false, // Disable autoSearchByDay to avoid searching past days
|
|
751
|
-
bypassCache: false,
|
|
752
764
|
});
|
|
753
765
|
|
|
754
766
|
logger.debug(`[NVR VOD] Found ${enrichedRecordings.length} enriched recordings from NVR`);
|
|
755
767
|
|
|
756
|
-
//
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
durationMs: rec.durationMs,
|
|
766
|
-
hasPerson: rec.hasPerson,
|
|
767
|
-
hasVehicle: rec.hasVehicle,
|
|
768
|
-
hasAnimal: rec.hasAnimal,
|
|
769
|
-
hasFace: rec.hasFace,
|
|
770
|
-
hasMotion: rec.hasMotion,
|
|
771
|
-
hasDoorbell: rec.hasDoorbell,
|
|
772
|
-
hasPackage: rec.hasPackage,
|
|
773
|
-
recordType: rec.recordType,
|
|
774
|
-
parsedFileName: rec.parsedFileName ? {
|
|
775
|
-
start: rec.parsedFileName.start?.toISOString(),
|
|
776
|
-
end: rec.parsedFileName.end?.toISOString(),
|
|
777
|
-
flags: rec.parsedFileName.flags,
|
|
778
|
-
} : null,
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// Convert enriched recordings to VideoClip array
|
|
784
|
-
const clips: VideoClip[] = [];
|
|
785
|
-
|
|
786
|
-
for (const rec of enrichedRecordings) {
|
|
787
|
-
// Log detection flags before conversion
|
|
788
|
-
const flags = {
|
|
789
|
-
hasPerson: 'hasPerson' in rec ? rec.hasPerson : false,
|
|
790
|
-
hasVehicle: 'hasVehicle' in rec ? rec.hasVehicle : false,
|
|
791
|
-
hasAnimal: 'hasAnimal' in rec ? rec.hasAnimal : false,
|
|
792
|
-
hasFace: 'hasFace' in rec ? rec.hasFace : false,
|
|
793
|
-
hasMotion: 'hasMotion' in rec ? rec.hasMotion : false,
|
|
794
|
-
hasDoorbell: 'hasDoorbell' in rec ? rec.hasDoorbell : false,
|
|
795
|
-
hasPackage: 'hasPackage' in rec ? rec.hasPackage : false,
|
|
796
|
-
recordType: rec.recordType || 'none',
|
|
797
|
-
};
|
|
798
|
-
logger.debug(`[NVR VOD] Processing recording: fileName=${rec.fileName}, flags=${JSON.stringify(flags)}`);
|
|
799
|
-
|
|
800
|
-
const clip = await recordingFileToVideoClip(rec, {
|
|
801
|
-
fallbackStart: start,
|
|
802
|
-
logger,
|
|
803
|
-
plugin: this,
|
|
804
|
-
deviceId: this.id,
|
|
805
|
-
useWebhook: true,
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
// Log detection classes in the final clip
|
|
809
|
-
logger.debug(`[NVR VOD] Generated clip: id=${clip.id}, detectionClasses=${clip.detectionClasses?.join(',') || 'none'}`);
|
|
810
|
-
clips.push(clip);
|
|
811
|
-
}
|
|
768
|
+
// Convert enriched recordings to VideoClip array using the shared parser
|
|
769
|
+
const clips = await recordingsToVideoClips(enrichedRecordings, {
|
|
770
|
+
fallbackStart: start,
|
|
771
|
+
logger,
|
|
772
|
+
plugin: this,
|
|
773
|
+
deviceId: this.id,
|
|
774
|
+
useWebhook: true,
|
|
775
|
+
count,
|
|
776
|
+
});
|
|
812
777
|
|
|
813
|
-
|
|
814
|
-
const finalClips = count ? clips.slice(0, count) : clips;
|
|
815
|
-
logger.debug(`[NVR VOD] Converted ${finalClips.length} video clips (limit: ${count || 'none'})`);
|
|
778
|
+
logger.debug(`[NVR VOD] Converted ${clips.length} video clips (limit: ${count || 'none'})`);
|
|
816
779
|
|
|
817
|
-
return
|
|
780
|
+
return clips;
|
|
818
781
|
} else {
|
|
819
|
-
|
|
820
|
-
const api = await this.ensureClient();
|
|
821
|
-
|
|
822
|
-
const recordings = await api.listEnrichedRecordingsByTime({
|
|
782
|
+
const recordings = await api.listDeviceRecordings({
|
|
823
783
|
start,
|
|
824
784
|
end,
|
|
825
785
|
count,
|
|
826
786
|
channel: this.storageSettings.values.rtspChannel,
|
|
827
787
|
streamType: 'mainStream',
|
|
828
788
|
httpFallback: false,
|
|
829
|
-
fetchRtmpUrls:
|
|
789
|
+
fetchRtmpUrls: false
|
|
830
790
|
});
|
|
831
791
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
});
|
|
843
|
-
clips.push(clip);
|
|
844
|
-
}
|
|
792
|
+
// Convert recordings to VideoClip array using the shared parser
|
|
793
|
+
const clips = await recordingsToVideoClips(recordings, {
|
|
794
|
+
fallbackStart: start,
|
|
795
|
+
api,
|
|
796
|
+
logger,
|
|
797
|
+
plugin: this,
|
|
798
|
+
deviceId: this.id,
|
|
799
|
+
useWebhook: true,
|
|
800
|
+
count,
|
|
801
|
+
});
|
|
845
802
|
|
|
846
803
|
logger.debug(`Videoclips found: ${clips.length}`);
|
|
847
804
|
|
|
@@ -1166,12 +1123,12 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1166
1123
|
|
|
1167
1124
|
if (useNvr) {
|
|
1168
1125
|
logger.debug(`[getVideoClipRtmpUrl] Using NVR API for fileId="${fileId}", forThumbnail=${forThumbnail}`);
|
|
1169
|
-
const
|
|
1126
|
+
const api = await this.ensureClient();
|
|
1170
1127
|
const channel = this.storageSettings.values.rtspChannel ?? 0;
|
|
1171
1128
|
|
|
1172
1129
|
try {
|
|
1173
1130
|
logger.debug(`[getVideoClipRtmpUrl] Trying getVodUrl with Download requestType...`);
|
|
1174
|
-
const url = await
|
|
1131
|
+
const url = await api.getVodUrl(fileId, channel, {
|
|
1175
1132
|
requestType: "Download",
|
|
1176
1133
|
streamType: "main",
|
|
1177
1134
|
});
|
|
@@ -1250,6 +1207,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1250
1207
|
const logger = this.getBaichuanLogger();
|
|
1251
1208
|
|
|
1252
1209
|
this.videoClipsAutoLoadInProgress = true;
|
|
1210
|
+
this.videoClipsAutoLoadMode = true;
|
|
1253
1211
|
|
|
1254
1212
|
try {
|
|
1255
1213
|
const daysToPreload = this.storageSettings.values.videoclipsDaysToPreload ?? 1;
|
|
@@ -1315,6 +1273,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1315
1273
|
logger.error('Error during auto-loading video clips:', e);
|
|
1316
1274
|
} finally {
|
|
1317
1275
|
this.videoClipsAutoLoadInProgress = false;
|
|
1276
|
+
this.videoClipsAutoLoadMode = false;
|
|
1318
1277
|
}
|
|
1319
1278
|
}
|
|
1320
1279
|
|
|
@@ -1325,7 +1284,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1325
1284
|
|
|
1326
1285
|
// BaseBaichuanClass abstract methods implementation
|
|
1327
1286
|
protected getConnectionConfig(): BaichuanConnectionConfig {
|
|
1328
|
-
const { ipAddress, username, password, uid } = this.storageSettings.values;
|
|
1287
|
+
const { ipAddress, username, password, uid, discoveryMethod } = this.storageSettings.values;
|
|
1329
1288
|
const debugOptions = this.getBaichuanDebugOptions();
|
|
1330
1289
|
const normalizedUid = this.isBattery ? normalizeUid(uid) : undefined;
|
|
1331
1290
|
|
|
@@ -1333,15 +1292,14 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1333
1292
|
throw new Error('UID is required for battery cameras (BCUDP)');
|
|
1334
1293
|
}
|
|
1335
1294
|
|
|
1336
|
-
const logger = this.getBaichuanLogger();
|
|
1337
1295
|
return {
|
|
1338
1296
|
host: ipAddress,
|
|
1339
1297
|
username,
|
|
1340
1298
|
password,
|
|
1341
1299
|
uid: normalizedUid,
|
|
1342
1300
|
transport: this.protocol,
|
|
1343
|
-
logger,
|
|
1344
1301
|
debugOptions,
|
|
1302
|
+
udpDiscoveryMethod: discoveryMethod as BaichuanClientOptions["udpDiscoveryMethod"],
|
|
1345
1303
|
};
|
|
1346
1304
|
}
|
|
1347
1305
|
|
|
@@ -1424,12 +1382,17 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
1424
1382
|
|
|
1425
1383
|
try {
|
|
1426
1384
|
const api = await this.ensureClient();
|
|
1385
|
+
const { ipAddress, username, password } = this.storageSettings.values;
|
|
1427
1386
|
|
|
1428
1387
|
const result = await api.runAllDiagnosticsConsecutively({
|
|
1388
|
+
host: ipAddress,
|
|
1389
|
+
username,
|
|
1390
|
+
password,
|
|
1429
1391
|
outDir: outputPath,
|
|
1430
1392
|
channel,
|
|
1431
1393
|
durationSeconds,
|
|
1432
1394
|
selection,
|
|
1395
|
+
api,
|
|
1433
1396
|
});
|
|
1434
1397
|
|
|
1435
1398
|
logger.log(`Diagnostics completed successfully. Output directory: ${result.runDir}`);
|
|
@@ -2497,6 +2460,7 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
2497
2460
|
logger.warn('Failed to connect/refresh during init', e);
|
|
2498
2461
|
}
|
|
2499
2462
|
}
|
|
2463
|
+
this.storageSettings.settings.socketApiDebugLogs.hide = !!this.nvrDevice;
|
|
2500
2464
|
this.storageSettings.settings.clipsSource.hide = !this.nvrDevice;
|
|
2501
2465
|
this.storageSettings.settings.clipsSource.defaultValue = this.nvrDevice ? "NVR" : "Device";
|
|
2502
2466
|
|
|
@@ -2513,6 +2477,9 @@ export abstract class CommonCameraMixin extends BaseBaichuanClass implements Vid
|
|
|
2513
2477
|
this.storageSettings.settings.widerChannel.hide = !this.isMultiFocal;
|
|
2514
2478
|
this.storageSettings.settings.teleChannel.hide = !this.isMultiFocal;
|
|
2515
2479
|
|
|
2480
|
+
this.storageSettings.settings.uid.hide = !this.isBattery;
|
|
2481
|
+
this.storageSettings.settings.discoveryMethod.hide = !this.isBattery;
|
|
2482
|
+
|
|
2516
2483
|
if (this.isBattery && !this.storageSettings.values.mixinsSetup) {
|
|
2517
2484
|
try {
|
|
2518
2485
|
const device = sdk.systemManager.getDeviceById<Settings>(this.id);
|
package/src/connect.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type BaichuanConnectInputs = {
|
|
|
9
9
|
uid?: string;
|
|
10
10
|
logger?: Console;
|
|
11
11
|
debugOptions?: BaichuanClientOptions['debugOptions'];
|
|
12
|
+
udpDiscoveryMethod?: "local" | "remote" | "map" | "relay";
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
export function normalizeUid(uid?: string): string | undefined {
|
|
@@ -81,6 +82,7 @@ export async function createBaichuanApi(props: {
|
|
|
81
82
|
transport: "udp",
|
|
82
83
|
uid,
|
|
83
84
|
idleDisconnect: true,
|
|
85
|
+
udpDiscoveryMethod: inputs.udpDiscoveryMethod,
|
|
84
86
|
});
|
|
85
87
|
attachErrorHandler(api);
|
|
86
88
|
return api;
|
package/src/main.ts
CHANGED
|
@@ -200,6 +200,7 @@ class ReolinkNativePlugin extends ScryptedDeviceBase implements DeviceProvider,
|
|
|
200
200
|
device.storageSettings.values.ipAddress = ipAddress;
|
|
201
201
|
device.storageSettings.values.capabilities = capabilities;
|
|
202
202
|
device.storageSettings.values.uid = uid;
|
|
203
|
+
device.storageSettings.values.discoveryMethod = detection.udpDiscoveryMethod;
|
|
203
204
|
|
|
204
205
|
return nativeId;
|
|
205
206
|
}
|