@apocaliss92/scrypted-reolink-native 0.3.12 → 0.3.14
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/camera.ts +18 -8
- package/src/intercom.ts +44 -9
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/camera.ts
CHANGED
|
@@ -341,19 +341,26 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
341
341
|
defaultValue: [],
|
|
342
342
|
},
|
|
343
343
|
intercomBlocksPerPayload: {
|
|
344
|
-
subgroup: '
|
|
345
|
-
title: '
|
|
344
|
+
subgroup: 'Intercom',
|
|
345
|
+
title: 'Blocks Per Payload',
|
|
346
346
|
description: 'Lower reduces latency (more packets). Typical: 1-4. Requires restarting talk session to take effect.',
|
|
347
347
|
type: 'number',
|
|
348
348
|
defaultValue: 1,
|
|
349
349
|
},
|
|
350
350
|
intercomMaxBacklogMs: {
|
|
351
|
-
subgroup: '
|
|
352
|
-
title: '
|
|
351
|
+
subgroup: 'Intercom',
|
|
352
|
+
title: 'Max Backlog (ms)',
|
|
353
353
|
description: 'Maximum PCM backlog before dropping old audio to cap latency. Higher improves stability on slow systems but increases latency. Typical: 80-250. Requires restarting talk session to take effect.',
|
|
354
354
|
type: 'number',
|
|
355
355
|
defaultValue: 120,
|
|
356
356
|
},
|
|
357
|
+
intercomGain: {
|
|
358
|
+
subgroup: 'Intercom',
|
|
359
|
+
title: 'Gain',
|
|
360
|
+
description: 'Output gain multiplier applied before encoding. 1.0 = normal, 2.0 ≈ +6dB, 0.5 ≈ -6dB. Requires restarting talk session to take effect.',
|
|
361
|
+
type: 'number',
|
|
362
|
+
defaultValue: 1.0,
|
|
363
|
+
},
|
|
357
364
|
// PTZ Presets
|
|
358
365
|
presets: {
|
|
359
366
|
subgroup: 'PTZ',
|
|
@@ -2437,13 +2444,16 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2437
2444
|
|
|
2438
2445
|
// Order streams based on preferredStreams setting
|
|
2439
2446
|
const preferredOrder = this.storageSettings.values.preferredStreams || 'Default';
|
|
2440
|
-
let supportedStreams:
|
|
2447
|
+
let supportedStreams: ReolinkSupportedStream[] = [];
|
|
2441
2448
|
|
|
2442
2449
|
if (preferredOrder === 'Default') {
|
|
2443
2450
|
// Default: Native -> RTSP -> RTMP
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2451
|
+
// BUT for multifocal cameras without NVR, prefer RTSP -> RTMP -> Native
|
|
2452
|
+
if (this.multiFocalDevice && !this.nvrDevice) {
|
|
2453
|
+
supportedStreams = [...rtspStreams, ...rtmpStreams, ...nativeStreams];
|
|
2454
|
+
} else {
|
|
2455
|
+
supportedStreams = [...nativeStreams, ...rtspStreams, ...rtmpStreams];
|
|
2456
|
+
}
|
|
2447
2457
|
} else if (preferredOrder === 'Native') {
|
|
2448
2458
|
supportedStreams = [...nativeStreams, ...rtspStreams, ...rtmpStreams];
|
|
2449
2459
|
} else if (preferredOrder === 'RTSP') {
|
package/src/intercom.ts
CHANGED
|
@@ -33,6 +33,13 @@ export class ReolinkBaichuanIntercom {
|
|
|
33
33
|
return Math.max(1, Math.min(8, this.camera.storageSettings.values.intercomBlocksPerPayload ?? 1));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
private get outputGain(): number {
|
|
37
|
+
const configured = Number(this.camera.storageSettings.values.intercomGain);
|
|
38
|
+
// Keep safe bounds: too high can clip and distort.
|
|
39
|
+
if (Number.isFinite(configured)) return Math.max(0.1, Math.min(10, configured));
|
|
40
|
+
return 1.0;
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
async start(media: MediaObject): Promise<void> {
|
|
37
44
|
const logger = this.camera.getBaichuanLogger();
|
|
38
45
|
|
|
@@ -44,13 +51,14 @@ export class ReolinkBaichuanIntercom {
|
|
|
44
51
|
await this.stop();
|
|
45
52
|
const channel = this.camera.storageSettings.values.rtspChannel;
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
try {
|
|
55
|
+
// IMPORTANT: intercom must run on its own independent Baichuan session (separate socket)
|
|
56
|
+
// to avoid interference with any other sessions (streams/events/etc).
|
|
57
|
+
const intercomStreamKey = `intercom_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
58
|
+
const intercomApi = await this.camera.withBaichuanRetry(async () => {
|
|
59
|
+
return await this.camera.createStreamClient(intercomStreamKey);
|
|
60
|
+
});
|
|
61
|
+
this.intercomApi = intercomApi;
|
|
54
62
|
|
|
55
63
|
// Best-effort: log codec requirements exposed by the camera.
|
|
56
64
|
// This mirrors neolink's source of truth: TalkAbility (cmd_id=10).
|
|
@@ -97,6 +105,9 @@ export class ReolinkBaichuanIntercom {
|
|
|
97
105
|
|
|
98
106
|
return await api.createTalkSession(channel, {
|
|
99
107
|
blocksPerPayload: this.blocksPerPayload,
|
|
108
|
+
// IMPORTANT: for dedicated intercom sessions, teardown should be owned by the socket/session.
|
|
109
|
+
// This mirrors stream behavior (closeApiOnTeardown) but for talk: session.stop() will close.
|
|
110
|
+
closeSocketOnStop: true,
|
|
100
111
|
});
|
|
101
112
|
});
|
|
102
113
|
|
|
@@ -155,9 +166,12 @@ export class ReolinkBaichuanIntercom {
|
|
|
155
166
|
|
|
156
167
|
// IMPORTANT: incoming audio from Scrypted/WebRTC is typically Opus.
|
|
157
168
|
// We must decode to PCM before IMA ADPCM encoding, otherwise it will be noise.
|
|
169
|
+
const gain = this.outputGain;
|
|
158
170
|
const ffmpegArgs = this.buildFfmpegPcmArgs(ffmpegInput, {
|
|
159
171
|
sampleRate,
|
|
160
172
|
channels: 1,
|
|
173
|
+
gain,
|
|
174
|
+
logger,
|
|
161
175
|
});
|
|
162
176
|
|
|
163
177
|
logger.log("Intercom ffmpeg decode args", ffmpegArgs);
|
|
@@ -193,6 +207,12 @@ export class ReolinkBaichuanIntercom {
|
|
|
193
207
|
});
|
|
194
208
|
|
|
195
209
|
logger.log("Intercom started (ffmpeg decode -> PCM -> IMA ADPCM)");
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
// Ensure the dedicated session gets torn down even if start fails half-way.
|
|
213
|
+
await this.stop();
|
|
214
|
+
throw e;
|
|
215
|
+
}
|
|
196
216
|
}
|
|
197
217
|
|
|
198
218
|
stop(): Promise<void> {
|
|
@@ -250,8 +270,10 @@ export class ReolinkBaichuanIntercom {
|
|
|
250
270
|
}
|
|
251
271
|
}
|
|
252
272
|
|
|
253
|
-
//
|
|
254
|
-
if
|
|
273
|
+
// Socket teardown is handled by session.stop() (closeSocketOnStop).
|
|
274
|
+
// Fallback cleanup: if we never created a session but we did create a dedicated client,
|
|
275
|
+
// ensure it doesn't leak.
|
|
276
|
+
if (!session && intercomApi) {
|
|
255
277
|
try {
|
|
256
278
|
await Promise.race([intercomApi.close(), sleepMs(2000)]);
|
|
257
279
|
}
|
|
@@ -344,6 +366,8 @@ export class ReolinkBaichuanIntercom {
|
|
|
344
366
|
options: {
|
|
345
367
|
sampleRate: number;
|
|
346
368
|
channels: number;
|
|
369
|
+
gain?: number;
|
|
370
|
+
logger?: any;
|
|
347
371
|
},
|
|
348
372
|
): string[] {
|
|
349
373
|
const inputArgs = ffmpegInput.inputArguments ?? [];
|
|
@@ -375,6 +399,16 @@ export class ReolinkBaichuanIntercom {
|
|
|
375
399
|
throw new Error("FFmpegInput missing url/input");
|
|
376
400
|
}
|
|
377
401
|
|
|
402
|
+
const gain = options.gain ?? 1.0;
|
|
403
|
+
const hasExistingAudioFilter = sanitizedArgs.includes("-af") || sanitizedArgs.includes("-filter:a") || sanitizedArgs.includes("-filter_complex");
|
|
404
|
+
const gainArgs = (gain !== 1.0)
|
|
405
|
+
? (
|
|
406
|
+
hasExistingAudioFilter
|
|
407
|
+
? (options.logger?.warn?.("Intercom gain skipped: FFmpegInput already contains audio filters") ?? undefined, [])
|
|
408
|
+
: ["-filter:a", `volume=${gain}`]
|
|
409
|
+
)
|
|
410
|
+
: [];
|
|
411
|
+
|
|
378
412
|
return [
|
|
379
413
|
...sanitizedArgs,
|
|
380
414
|
"-i", url,
|
|
@@ -387,6 +421,7 @@ export class ReolinkBaichuanIntercom {
|
|
|
387
421
|
"-flush_packets", "1",
|
|
388
422
|
|
|
389
423
|
"-vn", "-sn", "-dn",
|
|
424
|
+
...gainArgs,
|
|
390
425
|
"-acodec", "pcm_s16le",
|
|
391
426
|
"-ar", options.sampleRate.toString(),
|
|
392
427
|
"-ac", options.channels.toString(),
|