@apocaliss92/scrypted-reolink-native 0.1.9 → 0.1.11
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 +478 -0
- package/src/camera-battery.ts +40 -36
- package/src/camera.ts +6 -9
- package/src/common.ts +108 -231
- package/src/connect.ts +1 -2
- package/src/debug-options.ts +1 -1
- package/src/intercom.ts +3 -3
- package/src/nvr.ts +66 -217
- package/src/stream-utils.ts +19 -8
package/src/intercom.ts
CHANGED
|
@@ -31,7 +31,7 @@ export class ReolinkBaichuanIntercom {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async start(media: MediaObject): Promise<void> {
|
|
34
|
-
const logger = this.camera.
|
|
34
|
+
const logger = this.camera.getBaichuanLogger();
|
|
35
35
|
|
|
36
36
|
const ffmpegInput = await sdk.mediaManager.convertMediaObjectToJSON<FFmpegInput>(
|
|
37
37
|
media,
|
|
@@ -177,7 +177,7 @@ export class ReolinkBaichuanIntercom {
|
|
|
177
177
|
if (this.stopping) return this.stopping;
|
|
178
178
|
|
|
179
179
|
this.stopping = (async () => {
|
|
180
|
-
const logger = this.camera.
|
|
180
|
+
const logger = this.camera.getBaichuanLogger();
|
|
181
181
|
|
|
182
182
|
const ffmpeg = this.ffmpeg;
|
|
183
183
|
this.ffmpeg = undefined;
|
|
@@ -243,7 +243,7 @@ export class ReolinkBaichuanIntercom {
|
|
|
243
243
|
bytesNeeded: number,
|
|
244
244
|
blockSize: number,
|
|
245
245
|
): void {
|
|
246
|
-
const logger = this.camera.
|
|
246
|
+
const logger = this.camera.getBaichuanLogger();
|
|
247
247
|
|
|
248
248
|
this.sendChain = this.sendChain
|
|
249
249
|
.then(async () => {
|
package/src/nvr.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { DeviceInfoResponse, DeviceInputData, EventsResponse, ReolinkBaichuanApi, ReolinkCgiApi, ReolinkSimpleEvent } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
2
|
import sdk, { AdoptDevice, Device, DeviceDiscovery, DeviceProvider, DiscoveredDevice, Reboot, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, Settings, SettingValue } from "@scrypted/sdk";
|
|
3
3
|
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
|
4
|
+
import { BaseBaichuanClass, type BaichuanConnectionConfig, type BaichuanConnectionCallbacks } from "./baichuan-base";
|
|
4
5
|
import { ReolinkNativeCamera } from "./camera";
|
|
5
6
|
import { ReolinkNativeBatteryCamera } from "./camera-battery";
|
|
6
7
|
import { normalizeUid } from "./connect";
|
|
7
8
|
import ReolinkNativePlugin from "./main";
|
|
8
9
|
import { getDeviceInterfaces, updateDeviceInfo } from "./utils";
|
|
9
10
|
|
|
10
|
-
export class ReolinkNativeNvrDevice extends
|
|
11
|
+
export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Settings, DeviceDiscovery, DeviceProvider, Reboot {
|
|
11
12
|
storageSettings = new StorageSettings(this, {
|
|
12
13
|
debugEvents: {
|
|
13
14
|
title: 'Debug Events',
|
|
@@ -45,8 +46,7 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
45
46
|
});
|
|
46
47
|
plugin: ReolinkNativePlugin;
|
|
47
48
|
nvrApi: ReolinkCgiApi | undefined;
|
|
48
|
-
baichuanApi
|
|
49
|
-
baichuanApiPromise: Promise<ReolinkBaichuanApi> | undefined;
|
|
49
|
+
// baichuanApi, ensureClientPromise, connectionTime inherited from BaseBaichuanClass
|
|
50
50
|
discoveredDevices = new Map<string, {
|
|
51
51
|
device: Device;
|
|
52
52
|
description: string;
|
|
@@ -59,9 +59,6 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
59
59
|
cameraNativeMap = new Map<string, ReolinkNativeCamera | ReolinkNativeBatteryCamera>();
|
|
60
60
|
private channelToNativeIdMap = new Map<number, string>();
|
|
61
61
|
processing = false;
|
|
62
|
-
private eventSubscriptionActive = false;
|
|
63
|
-
private errorListener?: (err: unknown) => void;
|
|
64
|
-
private closeListener?: () => void;
|
|
65
62
|
|
|
66
63
|
constructor(nativeId: string, plugin: ReolinkNativePlugin) {
|
|
67
64
|
super(nativeId);
|
|
@@ -77,98 +74,49 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
77
74
|
await api.Reboot();
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
* Centralized cleanup method for Baichuan API
|
|
86
|
-
* Removes all listeners, closes connection, and resets state
|
|
87
|
-
*/
|
|
88
|
-
private async cleanupBaichuanApi(): Promise<void> {
|
|
89
|
-
if (!this.baichuanApi) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const api = this.baichuanApi;
|
|
94
|
-
|
|
95
|
-
// Unsubscribe from events first
|
|
96
|
-
await this.unsubscribeFromAllEvents();
|
|
97
|
-
|
|
98
|
-
// Remove all listeners
|
|
99
|
-
try {
|
|
100
|
-
api.client.off("close", this.closeListener);
|
|
101
|
-
} catch {
|
|
102
|
-
// ignore
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
api.client.off("error", this.errorListener);
|
|
107
|
-
} catch {
|
|
108
|
-
// ignore
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Close connection if still connected
|
|
112
|
-
try {
|
|
113
|
-
if (api.client.isSocketConnected()) {
|
|
114
|
-
await api.close();
|
|
115
|
-
}
|
|
116
|
-
} catch {
|
|
117
|
-
// ignore
|
|
77
|
+
// BaseBaichuanClass abstract methods implementation
|
|
78
|
+
protected getConnectionConfig(): BaichuanConnectionConfig {
|
|
79
|
+
const { ipAddress, username, password } = this.storageSettings.values;
|
|
80
|
+
if (!ipAddress || !username || !password) {
|
|
81
|
+
throw new Error('Missing NVR credentials');
|
|
118
82
|
}
|
|
119
83
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
84
|
+
return {
|
|
85
|
+
host: ipAddress,
|
|
86
|
+
username,
|
|
87
|
+
password,
|
|
88
|
+
transport: 'tcp',
|
|
89
|
+
logger: this.console,
|
|
90
|
+
};
|
|
123
91
|
}
|
|
124
92
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
msg.includes('Baichuan socket closed') ||
|
|
138
|
-
msg.includes('Baichuan UDP stream closed') ||
|
|
139
|
-
msg.includes('Not running')
|
|
140
|
-
)) {
|
|
141
|
-
logger.debug(`[NVR BaichuanClient] error (recoverable): ${msg}`);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
logger.error(`[NVR BaichuanClient] error: ${msg}`);
|
|
93
|
+
protected getConnectionCallbacks(): BaichuanConnectionCallbacks {
|
|
94
|
+
return {
|
|
95
|
+
onError: undefined, // Use default error handling
|
|
96
|
+
onClose: async () => {
|
|
97
|
+
// Reinit after cleanup
|
|
98
|
+
await this.reinit();
|
|
99
|
+
},
|
|
100
|
+
onSimpleEvent: (ev) => this.forwardNativeEvent(ev),
|
|
101
|
+
getEventSubscriptionEnabled: () => {
|
|
102
|
+
const eventSource = this.storageSettings.values.eventSource || 'Native';
|
|
103
|
+
return eventSource === 'Native';
|
|
104
|
+
},
|
|
145
105
|
};
|
|
106
|
+
}
|
|
146
107
|
|
|
147
|
-
// Close listener
|
|
148
|
-
this.closeListener = async () => {
|
|
149
|
-
try {
|
|
150
|
-
const wasConnected = api.client.isSocketConnected();
|
|
151
|
-
const wasLoggedIn = api.client.loggedIn;
|
|
152
|
-
logger.log(`[NVR BaichuanClient] Connection state before close: connected=${wasConnected}, loggedIn=${wasLoggedIn}`);
|
|
153
|
-
|
|
154
|
-
// Try to get last message info if available
|
|
155
|
-
const client = api.client as any;
|
|
156
|
-
if (client?.lastRx || client?.lastTx) {
|
|
157
|
-
logger.log(`[NVR BaichuanClient] Last message info: lastRx=${JSON.stringify(client.lastRx)}, lastTx=${JSON.stringify(client.lastTx)}`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
catch (e) {
|
|
161
|
-
logger.debug(`[NVR BaichuanClient] Could not get connection state: ${e}`);
|
|
162
|
-
}
|
|
163
108
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
109
|
+
protected isDebugEnabled(): boolean {
|
|
110
|
+
return this.storageSettings.values.debugEvents || false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
protected getDeviceName(): string {
|
|
114
|
+
return this.name || 'NVR';
|
|
115
|
+
}
|
|
168
116
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
117
|
+
protected async onBeforeCleanup(): Promise<void> {
|
|
118
|
+
// Unsubscribe from events if needed
|
|
119
|
+
await this.unsubscribeFromAllEvents();
|
|
172
120
|
}
|
|
173
121
|
|
|
174
122
|
async reinit() {
|
|
@@ -183,7 +131,7 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
183
131
|
this.nvrApi = undefined;
|
|
184
132
|
|
|
185
133
|
// Cleanup Baichuan API (this handles all listeners and connection)
|
|
186
|
-
await
|
|
134
|
+
await super.cleanupBaichuanApi();
|
|
187
135
|
}
|
|
188
136
|
|
|
189
137
|
async ensureClient(): Promise<ReolinkCgiApi> {
|
|
@@ -208,7 +156,7 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
208
156
|
}
|
|
209
157
|
|
|
210
158
|
private forwardNativeEvent(ev: ReolinkSimpleEvent): void {
|
|
211
|
-
const logger = this.
|
|
159
|
+
const logger = this.getBaichuanLogger();
|
|
212
160
|
|
|
213
161
|
const eventSource = this.storageSettings.values.eventSource || 'Native';
|
|
214
162
|
if (eventSource !== 'Native') {
|
|
@@ -216,16 +164,12 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
216
164
|
}
|
|
217
165
|
|
|
218
166
|
try {
|
|
219
|
-
|
|
220
|
-
logger.log(`NVR Baichuan event: ${JSON.stringify(ev)}`);
|
|
221
|
-
}
|
|
167
|
+
logger.debug(`Baichuan event: ${JSON.stringify(ev)}`);
|
|
222
168
|
|
|
223
169
|
// Find camera for this channel
|
|
224
170
|
const channel = ev?.channel;
|
|
225
171
|
if (channel === undefined) {
|
|
226
|
-
|
|
227
|
-
logger.debug('Event has no channel, ignoring');
|
|
228
|
-
}
|
|
172
|
+
logger.debug('Event has no channel, ignoring');
|
|
229
173
|
return;
|
|
230
174
|
}
|
|
231
175
|
|
|
@@ -233,9 +177,7 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
233
177
|
const targetCamera = nativeId ? this.cameraNativeMap.get(nativeId) : undefined;
|
|
234
178
|
|
|
235
179
|
if (!targetCamera) {
|
|
236
|
-
|
|
237
|
-
logger.debug(`No camera found for channel ${channel}, ignoring event`);
|
|
238
|
-
}
|
|
180
|
+
logger.debug(`No camera found for channel ${channel}, ignoring event`);
|
|
239
181
|
return;
|
|
240
182
|
}
|
|
241
183
|
|
|
@@ -269,9 +211,7 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
269
211
|
motion = true;
|
|
270
212
|
break;
|
|
271
213
|
default:
|
|
272
|
-
|
|
273
|
-
logger.debug(`Unknown event type: ${ev?.type}`);
|
|
274
|
-
}
|
|
214
|
+
logger.debug(`Unknown event type: ${ev?.type}`);
|
|
275
215
|
return;
|
|
276
216
|
}
|
|
277
217
|
|
|
@@ -285,121 +225,36 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
285
225
|
}
|
|
286
226
|
}
|
|
287
227
|
|
|
288
|
-
async onSimpleEventHandler(ev: ReolinkSimpleEvent) {
|
|
289
|
-
this.forwardNativeEvent(ev);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
228
|
async ensureBaichuanClient(): Promise<ReolinkBaichuanApi> {
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
return this.baichuanApi;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Prevent concurrent login storms
|
|
299
|
-
if (this.baichuanApiPromise) return await this.baichuanApiPromise;
|
|
300
|
-
|
|
301
|
-
this.baichuanApiPromise = (async () => {
|
|
302
|
-
const { ipAddress, username, password } = this.storageSettings.values;
|
|
303
|
-
if (!ipAddress || !username || !password) {
|
|
304
|
-
throw new Error('Missing NVR credentials');
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Clean up old client if exists
|
|
308
|
-
if (this.baichuanApi) {
|
|
309
|
-
await this.cleanupBaichuanApi();
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Create new Baichuan client
|
|
313
|
-
const { ReolinkBaichuanApi } = await import("@apocaliss92/reolink-baichuan-js");
|
|
314
|
-
this.baichuanApi = new ReolinkBaichuanApi({
|
|
315
|
-
host: ipAddress,
|
|
316
|
-
username,
|
|
317
|
-
password,
|
|
318
|
-
transport: 'tcp',
|
|
319
|
-
logger: this.getLogger(),
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
await this.baichuanApi.login();
|
|
323
|
-
|
|
324
|
-
// Verify socket is connected before returning
|
|
325
|
-
if (!this.baichuanApi.client.isSocketConnected()) {
|
|
326
|
-
throw new Error('Socket not connected after login');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Attach listeners (error and close)
|
|
330
|
-
this.attachBaichuanListeners(this.baichuanApi);
|
|
331
|
-
|
|
332
|
-
return this.baichuanApi;
|
|
333
|
-
})();
|
|
334
|
-
|
|
335
|
-
try {
|
|
336
|
-
return await this.baichuanApiPromise;
|
|
337
|
-
}
|
|
338
|
-
finally {
|
|
339
|
-
// Allow future reconnects and avoid pinning rejected promises
|
|
340
|
-
this.baichuanApiPromise = undefined;
|
|
341
|
-
}
|
|
229
|
+
// Use base class implementation
|
|
230
|
+
return await super.ensureBaichuanClient();
|
|
342
231
|
}
|
|
343
232
|
|
|
344
233
|
async subscribeToAllEvents(): Promise<void> {
|
|
345
|
-
const logger = this.
|
|
346
|
-
|
|
347
|
-
// If already subscribed and connection is valid, return
|
|
348
|
-
if (this.eventSubscriptionActive && this.baichuanApi) {
|
|
349
|
-
if (this.baichuanApi.client.isSocketConnected() && this.baichuanApi.client.loggedIn) {
|
|
350
|
-
logger.log('Event subscription already active');
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
// Connection is invalid, reset subscription state
|
|
354
|
-
this.eventSubscriptionActive = false;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Unsubscribe first if handler exists (idempotent)
|
|
358
|
-
await this.unsubscribeFromAllEvents();
|
|
359
|
-
|
|
360
|
-
// Get Baichuan client connection
|
|
361
|
-
const api = await this.ensureBaichuanClient();
|
|
234
|
+
const logger = this.getBaichuanLogger();
|
|
235
|
+
const eventSource = this.storageSettings.values.eventSource || 'Native';
|
|
362
236
|
|
|
363
|
-
//
|
|
364
|
-
if (
|
|
365
|
-
|
|
237
|
+
// Only subscribe if Native is selected
|
|
238
|
+
if (eventSource !== 'Native') {
|
|
239
|
+
await this.unsubscribeFromAllEvents();
|
|
366
240
|
return;
|
|
367
241
|
}
|
|
368
242
|
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
this.eventSubscriptionActive = true;
|
|
373
|
-
logger.log('Subscribed to all events for NVR cameras');
|
|
374
|
-
}
|
|
375
|
-
catch (e) {
|
|
376
|
-
logger.warn('Failed to subscribe to events', e);
|
|
377
|
-
this.eventSubscriptionActive = false;
|
|
378
|
-
}
|
|
243
|
+
// Use base class implementation
|
|
244
|
+
await super.subscribeToEvents();
|
|
245
|
+
logger.log('Subscribed to all events for NVR cameras');
|
|
379
246
|
}
|
|
380
247
|
|
|
381
248
|
async unsubscribeFromAllEvents(): Promise<void> {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
// Only unsubscribe if we have an active subscription
|
|
385
|
-
if (this.eventSubscriptionActive && this.baichuanApi) {
|
|
386
|
-
try {
|
|
387
|
-
this.baichuanApi.offSimpleEvent(this.onSimpleEventHandler);
|
|
388
|
-
logger.log('Unsubscribed from all events');
|
|
389
|
-
}
|
|
390
|
-
catch (e) {
|
|
391
|
-
logger.warn('Error unsubscribing from events', e);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
this.eventSubscriptionActive = false;
|
|
249
|
+
// Use base class implementation
|
|
250
|
+
await super.unsubscribeFromEvents();
|
|
396
251
|
}
|
|
397
252
|
|
|
398
253
|
/**
|
|
399
254
|
* Reinitialize event subscriptions based on selected event source
|
|
400
255
|
*/
|
|
401
256
|
private async reinitEventSubscriptions(): Promise<void> {
|
|
402
|
-
const logger = this.
|
|
257
|
+
const logger = this.getBaichuanLogger();
|
|
403
258
|
const { eventSource } = this.storageSettings.values;
|
|
404
259
|
|
|
405
260
|
// Unsubscribe from Native events if switching away
|
|
@@ -419,11 +274,9 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
419
274
|
* Forward events from CGI source to cameras
|
|
420
275
|
*/
|
|
421
276
|
private forwardCgiEvents(eventsRes: Record<number, EventsResponse>): void {
|
|
422
|
-
const logger = this.
|
|
277
|
+
const logger = this.getBaichuanLogger();
|
|
423
278
|
|
|
424
|
-
|
|
425
|
-
logger.debug(`CGI Events call result: ${JSON.stringify(eventsRes)}`);
|
|
426
|
-
}
|
|
279
|
+
logger.debug(`CGI Events call result: ${JSON.stringify(eventsRes)}`);
|
|
427
280
|
|
|
428
281
|
// Use channel map for efficient lookup
|
|
429
282
|
for (const [channel, nativeId] of this.channelToNativeIdMap.entries()) {
|
|
@@ -437,7 +290,7 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
437
290
|
|
|
438
291
|
async init() {
|
|
439
292
|
const api = await this.ensureClient();
|
|
440
|
-
const logger = this.
|
|
293
|
+
const logger = this.getBaichuanLogger();
|
|
441
294
|
await this.updateDeviceInfo();
|
|
442
295
|
|
|
443
296
|
// Initialize event subscriptions based on selected source
|
|
@@ -462,9 +315,7 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
462
315
|
const { hubData } = await api.getHubInfo();
|
|
463
316
|
const { devicesData, channelsResponse, response } = await api.getDevicesInfo();
|
|
464
317
|
logger.log('Hub info data fetched');
|
|
465
|
-
|
|
466
|
-
logger.log(`${JSON.stringify({ hubData, devicesData, channelsResponse, response })}`);
|
|
467
|
-
}
|
|
318
|
+
logger.debug(`${JSON.stringify({ hubData, devicesData, channelsResponse, response })}`);
|
|
468
319
|
|
|
469
320
|
await this.discoverDevices(true);
|
|
470
321
|
}
|
|
@@ -479,9 +330,7 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
479
330
|
// Always fetch battery info (not event-related)
|
|
480
331
|
const { batteryInfoData, response } = await api.getAllChannelsBatteryInfo();
|
|
481
332
|
|
|
482
|
-
|
|
483
|
-
logger.debug(`Battery info call result: ${JSON.stringify({ batteryInfoData, response })}`);
|
|
484
|
-
}
|
|
333
|
+
logger.debug(`Battery info call result: ${JSON.stringify({ batteryInfoData, response })}`);
|
|
485
334
|
|
|
486
335
|
this.cameraNativeMap.forEach((camera) => {
|
|
487
336
|
if (camera) {
|
|
@@ -498,7 +347,7 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
498
347
|
}
|
|
499
348
|
});
|
|
500
349
|
} catch (e) {
|
|
501
|
-
|
|
350
|
+
logger.error('Error on events flow', e);
|
|
502
351
|
} finally {
|
|
503
352
|
this.processing = false;
|
|
504
353
|
}
|
|
@@ -517,7 +366,7 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
517
366
|
deviceData,
|
|
518
367
|
});
|
|
519
368
|
} catch (e) {
|
|
520
|
-
this.
|
|
369
|
+
this.getBaichuanLogger().warn('Failed to fetch device info', e);
|
|
521
370
|
}
|
|
522
371
|
}
|
|
523
372
|
|
|
@@ -571,7 +420,7 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
571
420
|
|
|
572
421
|
async syncEntitiesFromRemote() {
|
|
573
422
|
const api = await this.ensureClient();
|
|
574
|
-
const logger = this.
|
|
423
|
+
const logger = this.getBaichuanLogger();
|
|
575
424
|
|
|
576
425
|
logger.log('Starting channels discovery using getDevicesInfo...');
|
|
577
426
|
|
|
@@ -683,7 +532,7 @@ export class ReolinkNativeNvrDevice extends ScryptedDeviceBase implements Settin
|
|
|
683
532
|
await sdk.deviceManager.onDeviceDiscovered(actualDevice);
|
|
684
533
|
|
|
685
534
|
const device = await this.getDevice(adopt.nativeId);
|
|
686
|
-
this.
|
|
535
|
+
this.getBaichuanLogger().debug('Adopted device', entry, device?.name);
|
|
687
536
|
const { username, password, ipAddress } = this.storageSettings.values;
|
|
688
537
|
|
|
689
538
|
device.storageSettings.values.rtspChannel = entry.rtspChannel;
|
package/src/stream-utils.ts
CHANGED
|
@@ -12,6 +12,7 @@ import sdk, {
|
|
|
12
12
|
} from "@scrypted/sdk";
|
|
13
13
|
|
|
14
14
|
import type { UrlMediaStreamOptions } from "../../scrypted/plugins/rtsp/src/rtsp";
|
|
15
|
+
import { ReolinkNativeNvrDevice } from "./nvr";
|
|
15
16
|
|
|
16
17
|
export interface StreamManagerOptions {
|
|
17
18
|
/**
|
|
@@ -101,12 +102,12 @@ export async function buildVideoStreamOptionsFromRtspRtmp(
|
|
|
101
102
|
client: ReolinkBaichuanApi,
|
|
102
103
|
ipAddress: string,
|
|
103
104
|
cachedNetPort: { rtsp?: { port?: number; enable?: number }; rtmp?: { port?: number; enable?: number } },
|
|
104
|
-
|
|
105
|
+
nvrDevice?: ReolinkNativeNvrDevice,
|
|
105
106
|
rtspChannel: number,
|
|
106
107
|
logger: Console
|
|
107
108
|
},
|
|
108
109
|
): Promise<UrlMediaStreamOptions[]> {
|
|
109
|
-
const { client, ipAddress, cachedNetPort, rtspChannel, logger } = props;
|
|
110
|
+
const { client, ipAddress, cachedNetPort, rtspChannel, logger, nvrDevice } = props;
|
|
110
111
|
const rtspStreams: UrlMediaStreamOptions[] = [];
|
|
111
112
|
const rtmpStreams: UrlMediaStreamOptions[] = [];
|
|
112
113
|
|
|
@@ -179,11 +180,21 @@ export async function buildVideoStreamOptionsFromRtspRtmp(
|
|
|
179
180
|
|
|
180
181
|
const nativeStreams = await fetchVideoStreamOptionsFromApi(client, rtspChannel, logger);
|
|
181
182
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
let streams: UrlMediaStreamOptions[] = [];
|
|
184
|
+
|
|
185
|
+
if (nvrDevice && nvrDevice.info.model === 'HOMEHUB') {
|
|
186
|
+
streams = [
|
|
187
|
+
...nativeStreams,
|
|
188
|
+
...rtspStreams,
|
|
189
|
+
...rtmpStreams,
|
|
190
|
+
];
|
|
191
|
+
} else {
|
|
192
|
+
streams = [
|
|
193
|
+
...rtspStreams,
|
|
194
|
+
...rtmpStreams,
|
|
195
|
+
...nativeStreams,
|
|
196
|
+
];
|
|
197
|
+
}
|
|
187
198
|
|
|
188
199
|
return streams;
|
|
189
200
|
}
|
|
@@ -274,7 +285,7 @@ export class StreamManager {
|
|
|
274
285
|
}
|
|
275
286
|
|
|
276
287
|
private getLogger() {
|
|
277
|
-
return this.opts.getLogger();
|
|
288
|
+
return this.opts.getLogger() as Console;
|
|
278
289
|
}
|
|
279
290
|
|
|
280
291
|
private async ensureNativeRfcServer(
|