@apocaliss92/scrypted-reolink-native 0.1.31 → 0.1.33
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 +3 -2
- package/src/baichuan-base.ts +35 -7
- package/src/camera-battery.ts +0 -9
- package/src/camera.ts +1 -58
- package/src/common.ts +669 -47
- package/src/debug-options.ts +4 -0
- package/src/main.ts +160 -6
- package/src/multiFocal.ts +3 -103
- package/src/nvr.ts +1 -3
- package/src/stream-utils.ts +49 -96
- package/src/utils.ts +472 -2
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apocaliss92/scrypted-reolink-native",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"description": "Use any reolink camera with Scrypted, even older/unsupported models without HTTP protocol support",
|
|
5
5
|
"author": "@apocaliss92",
|
|
6
6
|
"license": "Apache",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"ScryptedDeviceCreator",
|
|
34
34
|
"DeviceProvider",
|
|
35
35
|
"DeviceCreator",
|
|
36
|
-
"Settings"
|
|
36
|
+
"Settings",
|
|
37
|
+
"HttpRequestHandler"
|
|
37
38
|
]
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
package/src/baichuan-base.ts
CHANGED
|
@@ -208,14 +208,27 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
|
|
|
208
208
|
* Ensure Baichuan client is connected and ready
|
|
209
209
|
*/
|
|
210
210
|
async ensureBaichuanClient(): Promise<ReolinkBaichuanApi> {
|
|
211
|
+
// Prevent concurrent login storms - check promise first
|
|
212
|
+
if (this.ensureClientPromise) return await this.ensureClientPromise;
|
|
213
|
+
|
|
211
214
|
// Reuse existing client if socket is still connected and logged in
|
|
212
|
-
|
|
213
|
-
|
|
215
|
+
// Check this AFTER checking the promise to avoid race conditions
|
|
216
|
+
if (this.baichuanApi) {
|
|
217
|
+
const isConnected = this.baichuanApi.client.isSocketConnected();
|
|
218
|
+
const isLoggedIn = this.baichuanApi.client.loggedIn;
|
|
219
|
+
|
|
220
|
+
// Only reuse if both conditions are true
|
|
221
|
+
if (isConnected && isLoggedIn) {
|
|
222
|
+
return this.baichuanApi;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// If socket is not connected or not logged in, cleanup the stale client
|
|
226
|
+
// This prevents leaking connections when the socket appears connected but isn't
|
|
227
|
+
const logger = this.getBaichuanLogger();
|
|
228
|
+
logger.log(`Stale client detected: connected=${isConnected}, loggedIn=${isLoggedIn}, cleaning up`);
|
|
229
|
+
await this.cleanupBaichuanApi();
|
|
214
230
|
}
|
|
215
231
|
|
|
216
|
-
// Prevent concurrent login storms
|
|
217
|
-
if (this.ensureClientPromise) return await this.ensureClientPromise;
|
|
218
|
-
|
|
219
232
|
// Apply backoff to avoid aggressive reconnection after disconnection
|
|
220
233
|
if (this.lastDisconnectTime > 0) {
|
|
221
234
|
const timeSinceDisconnect = Date.now() - this.lastDisconnectTime;
|
|
@@ -309,6 +322,13 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
|
|
|
309
322
|
|
|
310
323
|
// Close listener
|
|
311
324
|
this.closeListener = async () => {
|
|
325
|
+
// Prevent multiple concurrent cleanup operations
|
|
326
|
+
if (!this.baichuanApi || this.baichuanApi !== api) {
|
|
327
|
+
// This close event is for a different/old client, ignore it
|
|
328
|
+
logger.debug('Close event for stale client, ignoring');
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
312
332
|
try {
|
|
313
333
|
const wasConnected = api.client.isSocketConnected();
|
|
314
334
|
const wasLoggedIn = api.client.loggedIn;
|
|
@@ -330,8 +350,16 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
|
|
|
330
350
|
|
|
331
351
|
logger.log(`Socket closed, resetting client state for reconnection (last disconnect ${timeSinceLastDisconnect}ms ago)`);
|
|
332
352
|
|
|
333
|
-
//
|
|
334
|
-
|
|
353
|
+
// Mark as disconnected immediately to prevent reuse
|
|
354
|
+
// This prevents race conditions where ensureBaichuanClient might check
|
|
355
|
+
// isSocketConnected() before cleanup completes
|
|
356
|
+
const currentApi = this.baichuanApi;
|
|
357
|
+
if (currentApi === api) {
|
|
358
|
+
// Only cleanup if this is still the current API instance
|
|
359
|
+
// This prevents cleanup of a new connection that was created
|
|
360
|
+
// while the old one was closing
|
|
361
|
+
await this.cleanupBaichuanApi();
|
|
362
|
+
}
|
|
335
363
|
|
|
336
364
|
// Call custom close handler if provided
|
|
337
365
|
if (callbacks.onClose) {
|
package/src/camera-battery.ts
CHANGED
|
@@ -318,13 +318,4 @@ export class ReolinkNativeBatteryCamera extends CommonCameraMixin {
|
|
|
318
318
|
this.getBaichuanLogger().warn(`Baichuan client reset requested: ${message}`);
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
|
-
|
|
322
|
-
protected async withBaichuanClient<T>(fn: (api: ReolinkBaichuanApi) => Promise<T>): Promise<T> {
|
|
323
|
-
const client = await this.ensureClient();
|
|
324
|
-
return fn(client);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
async createStreamClient(): Promise<ReolinkBaichuanApi> {
|
|
328
|
-
return await this.ensureClient();
|
|
329
|
-
}
|
|
330
321
|
}
|
package/src/camera.ts
CHANGED
|
@@ -74,28 +74,6 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
async createStreamClient(): Promise<ReolinkBaichuanApi> {
|
|
78
|
-
const { ipAddress, username, password } = this.storageSettings.values;
|
|
79
|
-
const logger = this.getBaichuanLogger();
|
|
80
|
-
|
|
81
|
-
const debugOptions = this.getBaichuanDebugOptions();
|
|
82
|
-
const api = await createBaichuanApi(
|
|
83
|
-
{
|
|
84
|
-
inputs: {
|
|
85
|
-
host: ipAddress,
|
|
86
|
-
username: username,
|
|
87
|
-
password: password,
|
|
88
|
-
logger,
|
|
89
|
-
debugOptions
|
|
90
|
-
},
|
|
91
|
-
transport: 'tcp',
|
|
92
|
-
},
|
|
93
|
-
);
|
|
94
|
-
await api.login();
|
|
95
|
-
|
|
96
|
-
return api;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
77
|
private passiveRefreshTimer: ReturnType<typeof setTimeout> | undefined;
|
|
100
78
|
|
|
101
79
|
async release() {
|
|
@@ -125,24 +103,15 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
|
|
|
125
103
|
await this.alignAuxDevicesState();
|
|
126
104
|
}
|
|
127
105
|
|
|
128
|
-
async getDetectionInput(detectionId: string, eventId?: any): Promise<MediaObject> {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
106
|
async processEvents(events: { motion?: boolean; objects?: string[] }) {
|
|
133
107
|
const logger = this.getBaichuanLogger();
|
|
134
108
|
|
|
135
109
|
if (!this.isEventDispatchEnabled()) return;
|
|
136
110
|
|
|
137
|
-
if (this.
|
|
111
|
+
if (this.isDebugEnabled()) {
|
|
138
112
|
logger.debug(`Events received: ${JSON.stringify(events)}`);
|
|
139
113
|
}
|
|
140
114
|
|
|
141
|
-
// const debugEvents = this.storageSettings.values.debugEvents;
|
|
142
|
-
// if (debugEvents) {
|
|
143
|
-
// logger.debug(`Events received: ${JSON.stringify(events)}`);
|
|
144
|
-
// }
|
|
145
|
-
|
|
146
115
|
if (this.shouldDispatchMotion() && events.motion !== this.motionDetected) {
|
|
147
116
|
if (events.motion) {
|
|
148
117
|
this.motionDetected = true;
|
|
@@ -169,30 +138,4 @@ export class ReolinkNativeCamera extends CommonCameraMixin {
|
|
|
169
138
|
sdk.deviceManager.onDeviceEvent(this.nativeId, ScryptedInterface.ObjectDetector, od);
|
|
170
139
|
}
|
|
171
140
|
}
|
|
172
|
-
|
|
173
|
-
protected async withBaichuanClient<T>(fn: (api: ReolinkBaichuanApi) => Promise<T>): Promise<T> {
|
|
174
|
-
const client = await this.ensureClient();
|
|
175
|
-
return fn(client);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async getPictureOptions(): Promise<ResponsePictureOptions[]> {
|
|
179
|
-
return [];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async getOtherSettings(): Promise<Setting[]> {
|
|
183
|
-
return await this.getSettings();
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
showRtspUrlOverride() {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
async startIntercom(media: MediaObject): Promise<void> {
|
|
192
|
-
await this.intercom.start(media);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
stopIntercom(): Promise<void> {
|
|
196
|
-
return this.intercom.stop();
|
|
197
|
-
}
|
|
198
141
|
}
|