@apocaliss92/scrypted-reolink-native 0.3.17 → 0.4.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/src/multiFocal.ts CHANGED
@@ -1,307 +1,337 @@
1
- import type { BatteryInfo, DeviceCapabilities, DualLensChannelAnalysis, NativeVideoStreamVariant, ReolinkBaichuanApi, ReolinkSimpleEvent, SleepStatus } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
2
- import sdk, { Device, DeviceProvider, Reboot, ScryptedDeviceType, Settings } from "@scrypted/sdk";
1
+ import type {
2
+ BatteryInfo,
3
+ DeviceCapabilities,
4
+ DualLensChannelAnalysis,
5
+ NativeVideoStreamVariant,
6
+ ReolinkBaichuanApi,
7
+ ReolinkSimpleEvent,
8
+ SleepStatus,
9
+ } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
10
+ import sdk, {
11
+ Device,
12
+ DeviceProvider,
13
+ Reboot,
14
+ ScryptedDeviceType,
15
+ Settings,
16
+ } from "@scrypted/sdk";
3
17
  import type { BaichuanConnectionConfig } from "./baichuan-base";
4
18
  import { CameraType, ReolinkCamera } from "./camera";
5
19
  import ReolinkNativePlugin from "./main";
6
20
  import { ReolinkNativeNvrDevice } from "./nvr";
7
- import { batteryCameraSuffix, cameraSuffix, getDeviceInterfaces } from "./utils";
8
-
9
- export class ReolinkNativeMultiFocalDevice extends ReolinkCamera implements Settings, DeviceProvider, Reboot {
10
- plugin: ReolinkNativePlugin;
11
- lensDevicesMap = new Map<string, ReolinkCamera>();
12
- private channelToNativeIdMap = new Map<number, string>();
13
-
14
- constructor(nativeId: string, plugin: ReolinkNativePlugin, type: CameraType, nvrDevice?: ReolinkNativeNvrDevice) {
15
- super(nativeId, plugin, { type, nvrDevice });
16
-
17
- this.plugin = plugin;
18
- }
19
-
20
- protected async onBeforeCleanup(): Promise<void> {
21
- await this.unsubscribeFromAllEvents();
21
+ import {
22
+ batteryCameraSuffix,
23
+ cameraSuffix,
24
+ getDeviceInterfaces,
25
+ } from "./utils";
26
+
27
+ export class ReolinkNativeMultiFocalDevice
28
+ extends ReolinkCamera
29
+ implements Settings, DeviceProvider, Reboot
30
+ {
31
+ plugin: ReolinkNativePlugin;
32
+ lensDevicesMap = new Map<string, ReolinkCamera>();
33
+ private channelToNativeIdMap = new Map<number, string>();
34
+
35
+ constructor(
36
+ nativeId: string,
37
+ plugin: ReolinkNativePlugin,
38
+ type: CameraType,
39
+ nvrDevice?: ReolinkNativeNvrDevice,
40
+ ) {
41
+ super(nativeId, plugin, { type, nvrDevice });
42
+
43
+ this.plugin = plugin;
44
+ }
45
+
46
+ protected async onBeforeCleanup(): Promise<void> {
47
+ await this.unsubscribeFromAllEvents();
48
+ }
49
+
50
+ protected getDeviceName(): string {
51
+ return this.name || "Multi-Focal Device";
52
+ }
53
+
54
+ async getInterfaces(lensType?: NativeVideoStreamVariant) {
55
+ const logger = this.getBaichuanLogger();
56
+ const { multifocalInfo } = this.storageSettings.values;
57
+ const caps = await this.getAbilities();
58
+
59
+ let capabilities: DeviceCapabilities = { ...caps };
60
+
61
+ if (lensType) {
62
+ const channelInfo = (
63
+ multifocalInfo as DualLensChannelAnalysis
64
+ ).channels.find((c) => c.variantType === lensType);
65
+
66
+ const hasPtz =
67
+ channelInfo?.hasPan || channelInfo?.hasTilt || channelInfo?.hasZoom;
68
+
69
+ capabilities = {
70
+ ...capabilities,
71
+ hasPan: channelInfo.hasPan,
72
+ hasTilt: channelInfo.hasTilt,
73
+ hasZoom: channelInfo?.hasZoom,
74
+ hasPresets: channelInfo?.hasPresets || hasPtz,
75
+ hasIntercom: channelInfo?.hasIntercom,
76
+ hasPtz,
77
+ };
22
78
  }
23
79
 
24
- protected getDeviceName(): string {
25
- return this.name || 'Multi-Focal Device';
26
- }
27
-
28
- async getInterfaces(lensType?: NativeVideoStreamVariant) {
29
- const logger = this.getBaichuanLogger();
30
- const { multifocalInfo } = this.storageSettings.values;
31
- const caps = await this.getAbilities();
80
+ const { interfaces } = getDeviceInterfaces({
81
+ capabilities,
82
+ logger,
83
+ isLensDevice: !!lensType,
84
+ });
85
+
86
+ logger.debug(
87
+ `Interfaces found for lens ${lensType}: ${JSON.stringify({ interfaces, capabilities, multifocalInfo, isLensDevice: !!lensType })}`,
88
+ );
89
+
90
+ return { interfaces, capabilities };
91
+ }
92
+
93
+ async reportDevices(): Promise<void> {
94
+ await super.reportDevices();
95
+
96
+ const logger = this.getBaichuanLogger();
97
+
98
+ try {
99
+ const api = await this.ensureClient();
100
+ const { username, password, ipAddress, uid, rtspChannel } =
101
+ this.storageSettings.values;
102
+
103
+ const { capabilities, objects, presets } =
104
+ await api.getDeviceCapabilities(rtspChannel);
105
+ const multifocalInfo = await api.getDualLensChannelInfo(rtspChannel, {
106
+ onNvr: !!this.nvrDevice,
107
+ });
108
+ logger.log(`Discovering ${multifocalInfo.channels.length} lenses`);
109
+ logger.debug({ multifocalInfo, capabilities });
110
+
111
+ this.storageSettings.values.multifocalInfo = multifocalInfo;
112
+
113
+ for (const channelInfo of multifocalInfo?.channels ?? []) {
114
+ const { channel, lensType, variantType } = channelInfo;
115
+
116
+ const name = `${this.name} - ${lensType}`;
117
+ const nativeId = `${this.nativeId}-${lensType}${this.isBattery ? batteryCameraSuffix : cameraSuffix}`;
118
+
119
+ this.channelToNativeIdMap.set(channel, nativeId);
120
+ const { interfaces, capabilities: deviceCapabilities } =
121
+ await this.getInterfaces();
122
+
123
+ const device: Device = {
124
+ providerNativeId: this.nativeId,
125
+ name,
126
+ nativeId,
127
+ info: {
128
+ ...this.info,
129
+ metadata: {
130
+ channel,
131
+ lensType,
132
+ },
133
+ },
134
+ interfaces,
135
+ type: ScryptedDeviceType.Camera,
136
+ };
32
137
 
33
- let capabilities: DeviceCapabilities = { ...caps };
138
+ await sdk.deviceManager.onDeviceDiscovered(device);
34
139
 
35
- if (lensType) {
36
- const channelInfo = (multifocalInfo as DualLensChannelAnalysis).channels.find(c => c.variantType === lensType);
140
+ logger.log(`Discovering lens ${lensType}`);
141
+ logger.debug(`${JSON.stringify({ interfaces, deviceCapabilities })}`);
37
142
 
38
- const hasPtz = channelInfo?.hasPan || channelInfo?.hasTilt || channelInfo?.hasZoom;
143
+ const camera = await this.getDevice(nativeId);
39
144
 
40
- capabilities = {
41
- ...capabilities,
42
- hasPan: channelInfo.hasPan,
43
- hasTilt: channelInfo.hasTilt,
44
- hasZoom: channelInfo?.hasZoom,
45
- hasPresets: channelInfo?.hasPresets || hasPtz,
46
- hasIntercom: channelInfo?.hasIntercom,
47
- hasPtz,
48
- };
145
+ if (!camera) {
146
+ logger.error(`Failed to get device ${nativeId}`);
147
+ continue;
49
148
  }
50
149
 
51
- const { interfaces } = getDeviceInterfaces({
52
- capabilities,
53
- logger,
54
- isLensDevice: !!lensType
55
- });
56
-
57
- logger.debug(`Interfaces found for lens ${lensType}: ${JSON.stringify({ interfaces, capabilities, multifocalInfo, isLensDevice: !!lensType })}`);
58
-
59
- return { interfaces, capabilities };
150
+ camera.storageSettings.values.rtspChannel = channel;
151
+ camera.classes = objects;
152
+ camera.presets = presets;
153
+ camera.storageSettings.values.username = username;
154
+ camera.storageSettings.values.password = password;
155
+ camera.storageSettings.values.ipAddress = ipAddress;
156
+ camera.storageSettings.values.variantType = variantType;
157
+ camera.storageSettings.values.rtspChannel = channel;
158
+ camera.storageSettings.values.capabilities = deviceCapabilities;
159
+ camera.storageSettings.values.uid = uid;
160
+ }
161
+ } catch (e) {
162
+ logger.error("Failed to report devices", e?.message || String(e));
163
+ throw e;
60
164
  }
61
-
62
- async reportDevices(): Promise<void> {
63
- await super.reportDevices();
64
-
65
- const logger = this.getBaichuanLogger();
66
-
67
- try {
68
- const api = await this.ensureClient();
69
- const { username, password, ipAddress, uid, rtspChannel } = this.storageSettings.values;
70
-
71
- const { capabilities, objects, presets } = await api.getDeviceCapabilities(rtspChannel, {
72
- mergeDualLensOnSameChannel: true,
73
- });
74
- const multifocalInfo = await api.getDualLensChannelInfo(rtspChannel, {
75
- onNvr: !!this.nvrDevice
76
- });
77
- logger.log(`Discovering ${multifocalInfo.channels.length} lenses`);
78
- logger.debug({ multifocalInfo, capabilities });
79
-
80
- this.storageSettings.values.multifocalInfo = multifocalInfo;
81
-
82
- for (const channelInfo of multifocalInfo?.channels ?? []) {
83
- const { channel, lensType, variantType } = channelInfo;
84
-
85
- const name = `${this.name} - ${lensType}`;
86
- const nativeId = `${this.nativeId}-${lensType}${this.isBattery ? batteryCameraSuffix : cameraSuffix}`;
87
-
88
- this.channelToNativeIdMap.set(channel, nativeId);
89
- const { interfaces, capabilities: deviceCapabilities } = await this.getInterfaces();
90
-
91
- const device: Device = {
92
- providerNativeId: this.nativeId,
93
- name,
94
- nativeId,
95
- info: {
96
- ...this.info,
97
- metadata: {
98
- channel,
99
- lensType
100
- }
101
- },
102
- interfaces,
103
- type: ScryptedDeviceType.Camera,
104
- };
105
-
106
- await sdk.deviceManager.onDeviceDiscovered(device);
107
-
108
- logger.log(`Discovering lens ${lensType}`);
109
- logger.debug(`${JSON.stringify({ interfaces, deviceCapabilities })}`)
110
-
111
- const camera = await this.getDevice(nativeId);
112
-
113
- if (!camera) {
114
- logger.error(`Failed to get device ${nativeId}`);
115
- continue;
116
- }
117
-
118
- camera.storageSettings.values.rtspChannel = channel;
119
- camera.classes = objects;
120
- camera.presets = presets;
121
- camera.storageSettings.values.username = username;
122
- camera.storageSettings.values.password = password;
123
- camera.storageSettings.values.ipAddress = ipAddress;
124
- camera.storageSettings.values.variantType = variantType;
125
- camera.storageSettings.values.rtspChannel = channel;
126
- camera.storageSettings.values.capabilities = deviceCapabilities;
127
- camera.storageSettings.values.uid = uid;
128
- }
129
- } catch (e) {
130
- logger.error('Failed to report devices', e?.message || String(e));
131
- throw e;
132
- }
133
- }
134
-
135
- async getDevice(nativeId: string) {
136
- if (nativeId.endsWith(cameraSuffix) || nativeId.endsWith(batteryCameraSuffix)) {
137
- let device = this.lensDevicesMap.get(nativeId);
138
- if (!device) {
139
- if (nativeId.endsWith(batteryCameraSuffix)) {
140
- device = new ReolinkCamera(nativeId, this.plugin, { type: 'battery', multiFocalDevice: this });
141
- } else {
142
- device = new ReolinkCamera(nativeId, this.plugin, { type: 'regular', multiFocalDevice: this });
143
- }
144
- }
145
-
146
- if (device) {
147
- this.lensDevicesMap.set(nativeId, device);
148
- }
149
-
150
- return device;
165
+ }
166
+
167
+ async getDevice(nativeId: string) {
168
+ if (
169
+ nativeId.endsWith(cameraSuffix) ||
170
+ nativeId.endsWith(batteryCameraSuffix)
171
+ ) {
172
+ let device = this.lensDevicesMap.get(nativeId);
173
+ if (!device) {
174
+ if (nativeId.endsWith(batteryCameraSuffix)) {
175
+ device = new ReolinkCamera(nativeId, this.plugin, {
176
+ type: "battery",
177
+ multiFocalDevice: this,
178
+ });
151
179
  } else {
152
- return super.getDevice(nativeId);
180
+ device = new ReolinkCamera(nativeId, this.plugin, {
181
+ type: "regular",
182
+ multiFocalDevice: this,
183
+ });
153
184
  }
154
- }
185
+ }
155
186
 
156
- async releaseDevice(id: string, nativeId: string) {
157
- this.lensDevicesMap.delete(nativeId);
158
- super.releaseDevice(id, nativeId);
159
- }
187
+ if (device) {
188
+ this.lensDevicesMap.set(nativeId, device);
189
+ }
160
190
 
161
- async unsubscribeFromAllEvents(): Promise<void> {
162
- await super.unsubscribeFromEvents();
191
+ return device;
192
+ } else {
193
+ return super.getDevice(nativeId);
163
194
  }
164
-
165
- public async runDiagnostics(): Promise<void> {
166
- const logger = this.getBaichuanLogger();
167
- logger.log(`Starting Multifocal diagnostics...`);
168
-
169
- try {
170
- const { ipAddress, username, password } = this.storageSettings.values;
171
- if (!ipAddress || !username || !password) {
172
- throw new Error('Missing device credentials');
173
- }
174
-
175
- const outputPath = this.storageSettings.values.diagnosticsOutputPath || process.env.SCRYPTED_PLUGIN_VOLUME || "";
176
- if (!outputPath) {
177
- throw new Error('Diagnostics output path is required');
178
- }
179
-
180
- const api = await this.ensureClient();
181
-
182
- const channel = this.storageSettings.values.rtspChannel || 0;
183
- const durationSeconds = 8;
184
- const result = await api.runMultifocalDiagnosticsConsecutively({
185
- logger,
186
- outDir: outputPath,
187
- channel,
188
- durationSeconds,
189
- rtmpApps: ["bcs"],
190
- probeFull: true,
191
- onNvr: !!this.nvrDevice,
192
- });
193
-
194
- logger.log(`Multifocal diagnostics completed successfully. Output directory: ${result.runDir}`);
195
- logger.log(`Results file: ${result.resultsPath}`);
196
- logger.log(`Streams directory: ${result.streamsDir}`);
197
- } catch (e) {
198
- logger.error('Failed to run NVR diagnostics', e?.message || String(e));
199
- throw e;
200
- }
195
+ }
196
+
197
+ async releaseDevice(id: string, nativeId: string) {
198
+ this.lensDevicesMap.delete(nativeId);
199
+ super.releaseDevice(id, nativeId);
200
+ }
201
+
202
+ async unsubscribeFromAllEvents(): Promise<void> {
203
+ await super.unsubscribeFromEvents();
204
+ }
205
+
206
+ public async runDiagnostics(): Promise<void> {
207
+ const logger = this.getBaichuanLogger();
208
+ logger.log(`Starting Multifocal diagnostics...`);
209
+
210
+ try {
211
+ const { ipAddress, username, password } = this.storageSettings.values;
212
+ if (!ipAddress || !username || !password) {
213
+ throw new Error("Missing device credentials");
214
+ }
215
+
216
+ const outputPath =
217
+ this.storageSettings.values.diagnosticsOutputPath ||
218
+ process.env.SCRYPTED_PLUGIN_VOLUME ||
219
+ "";
220
+ if (!outputPath) {
221
+ throw new Error("Diagnostics output path is required");
222
+ }
223
+
224
+ const api = await this.ensureClient();
225
+
226
+ const channel = this.storageSettings.values.rtspChannel || 0;
227
+ const durationSeconds = 8;
228
+ const result = await api.runMultifocalDiagnosticsConsecutively({
229
+ logger,
230
+ outDir: outputPath,
231
+ channel,
232
+ durationSeconds,
233
+ rtmpApps: ["bcs"],
234
+ probeFull: true,
235
+ onNvr: !!this.nvrDevice,
236
+ });
237
+
238
+ logger.log(
239
+ `Multifocal diagnostics completed successfully. Output directory: ${result.runDir}`,
240
+ );
241
+ logger.log(`Results file: ${result.resultsPath}`);
242
+ logger.log(`Streams directory: ${result.streamsDir}`);
243
+ } catch (e) {
244
+ logger.error("Failed to run NVR diagnostics", e?.message || String(e));
245
+ throw e;
201
246
  }
247
+ }
202
248
 
203
- async ensureClient(): Promise<ReolinkBaichuanApi> {
204
- if (this.nvrDevice) {
205
- return await this.nvrDevice.ensureBaichuanClient();
206
- }
207
-
208
- // Use base class implementation
209
- return await this.ensureBaichuanClient();
249
+ async ensureClient(): Promise<ReolinkBaichuanApi> {
250
+ if (this.nvrDevice) {
251
+ return await this.nvrDevice.ensureBaichuanClient();
210
252
  }
211
253
 
212
- protected getStreamClientInputs(): BaichuanConnectionConfig {
213
- const { ipAddress, username, password, uid, discoveryMethod } = this.storageSettings.values;
214
- const debugOptions = this.getBaichuanDebugOptions();
215
-
216
- // Multifocal battery cams use BCUDP for streaming too; UID is required.
217
- const normalizedUid = this.isBattery ? uid?.trim() || undefined : undefined;
218
- if (this.isBattery && !normalizedUid) {
219
- throw new Error('UID is required for battery cameras (BCUDP)');
220
- }
221
-
222
- return {
223
- host: ipAddress,
224
- username,
225
- password,
226
- uid: normalizedUid,
227
- transport: this.transport,
228
- debugOptions,
229
- udpDiscoveryMethod: discoveryMethod,
230
- };
254
+ // Use base class implementation
255
+ return await this.ensureBaichuanClient();
256
+ }
257
+
258
+ /**
259
+ * Create a dedicated Baichuan API session for streaming (used by StreamManager).
260
+ * MultiFocal creates its own socket for stream clients, or delegates to NVR if on NVR.
261
+ */
262
+ async createStreamClient(streamKey: string): Promise<ReolinkBaichuanApi> {
263
+ // If on NVR, delegate to NVR to create the socket
264
+ if (this.nvrDevice) {
265
+ return await this.nvrDevice.createStreamClient(streamKey);
231
266
  }
232
267
 
233
- /**
234
- * Create a dedicated Baichuan API session for streaming (used by StreamManager).
235
- * MultiFocal creates its own socket for stream clients, or delegates to NVR if on NVR.
236
- */
237
- async createStreamClient(streamKey: string): Promise<ReolinkBaichuanApi> {
238
- // If on NVR, delegate to NVR to create the socket
239
- if (this.nvrDevice) {
240
- return await this.nvrDevice.createStreamClient(streamKey);
241
- }
242
-
243
- // For multifocal battery cams (BCUDP), reuse the main client to avoid D2C_DISC storms.
244
- if (this.isBattery) {
245
- // For battery (BCUDP) cameras, streaming must be keyed by streamKey.
246
- // Do NOT reuse ensureClient(): composite needs two concurrent streams, and single-lens streams
247
- // should reuse the same API that composite already created for that same streamKey.
248
- return await super.createStreamClient(streamKey);
249
- }
250
-
251
- // Otherwise, use base class createStreamClient which manages stream clients per streamKey
252
- return await super.createStreamClient(streamKey);
268
+ // For multifocal battery cams (BCUDP), reuse the main client to avoid D2C_DISC storms.
269
+ if (this.isBattery) {
270
+ // For battery (BCUDP) cameras, streaming must be keyed by streamKey.
271
+ // Do NOT reuse ensureClient(): composite needs two concurrent streams, and single-lens streams
272
+ // should reuse the same API that composite already created for that same streamKey.
273
+ return await super.createStreamClient(streamKey);
253
274
  }
254
275
 
255
- getLensDevices() {
256
- const devices = Array.from(this.lensDevicesMap.values());
257
- // const logger = this.getBaichuanLogger();
258
- // logger.debug(`Found ${devices.length} lens devices: ${devices.map(d => d.nativeId).join(', ')}`);
276
+ // Otherwise, use base class createStreamClient which manages stream clients per streamKey
277
+ return await super.createStreamClient(streamKey);
278
+ }
259
279
 
260
- return devices;
261
- }
280
+ getLensDevices() {
281
+ const devices = Array.from(this.lensDevicesMap.values());
282
+ // const logger = this.getBaichuanLogger();
283
+ // logger.debug(`Found ${devices.length} lens devices: ${devices.map(d => d.nativeId).join(', ')}`);
262
284
 
263
- async updateBatteryInfo() {
264
- const batteryInfo = await super.updateBatteryInfo();
265
- const lensDevices = this.getLensDevices();
285
+ return devices;
286
+ }
266
287
 
267
- for (const camera of lensDevices) {
268
- await camera.updateBatteryInfo(batteryInfo);
269
- }
288
+ async updateBatteryInfo() {
289
+ const batteryInfo = await super.updateBatteryInfo();
290
+ const lensDevices = this.getLensDevices();
270
291
 
271
- return batteryInfo;
292
+ for (const camera of lensDevices) {
293
+ await camera.updateBatteryInfo(batteryInfo);
272
294
  }
273
295
 
274
- onSimpleEvent(ev: ReolinkSimpleEvent) {
275
- super.onSimpleEvent(ev);
276
- const logger = this.getBaichuanLogger();
277
- const lensDevices = this.getLensDevices();
296
+ return batteryInfo;
297
+ }
278
298
 
279
- for (const camera of lensDevices) {
280
- logger.debug(`Forward ${ev.type} event to lens device ${camera.nativeId}`);
281
- camera.onSimpleEvent(ev);
282
- }
283
- }
284
-
285
- async updateSleepingState(sleepStatus: SleepStatus) {
286
- const logger = this.getBaichuanLogger();
287
- await super.updateSleepingState(sleepStatus);
288
- const lensDevices = this.getLensDevices();
299
+ onSimpleEvent(ev: ReolinkSimpleEvent) {
300
+ super.onSimpleEvent(ev);
301
+ const logger = this.getBaichuanLogger();
302
+ const lensDevices = this.getLensDevices();
289
303
 
290
- for (const camera of lensDevices) {
291
- logger.debug(`Forward ${JSON.stringify(sleepStatus)} sleeping state to lens device ${camera.nativeId}`);
292
- await camera.updateSleepingState(sleepStatus);
293
- }
304
+ for (const camera of lensDevices) {
305
+ logger.debug(
306
+ `Forward ${ev.type} event to lens device ${camera.nativeId}`,
307
+ );
308
+ camera.onSimpleEvent(ev);
294
309
  }
295
-
296
- async updateOnlineState(isOnline: boolean) {
297
- const logger = this.getBaichuanLogger();
298
- await super.updateOnlineState(isOnline);
299
- const lensDevices = this.getLensDevices();
300
-
301
- for (const camera of lensDevices) {
302
- logger.debug(`Forward ${isOnline ? 'online' : 'offline'} state to lens device ${camera.nativeId}`);
303
- await camera.updateOnlineState(isOnline);
304
- }
310
+ }
311
+
312
+ async updateSleepingState(sleepStatus: SleepStatus) {
313
+ const logger = this.getBaichuanLogger();
314
+ await super.updateSleepingState(sleepStatus);
315
+ const lensDevices = this.getLensDevices();
316
+
317
+ for (const camera of lensDevices) {
318
+ logger.debug(
319
+ `Forward ${JSON.stringify(sleepStatus)} sleeping state to lens device ${camera.nativeId}`,
320
+ );
321
+ await camera.updateSleepingState(sleepStatus);
305
322
  }
323
+ }
324
+
325
+ async updateOnlineState(isOnline: boolean) {
326
+ const logger = this.getBaichuanLogger();
327
+ await super.updateOnlineState(isOnline);
328
+ const lensDevices = this.getLensDevices();
329
+
330
+ for (const camera of lensDevices) {
331
+ logger.debug(
332
+ `Forward ${isOnline ? "online" : "offline"} state to lens device ${camera.nativeId}`,
333
+ );
334
+ await camera.updateOnlineState(isOnline);
335
+ }
336
+ }
306
337
  }
307
-