@apocaliss92/scrypted-reolink-native 0.2.10 → 0.2.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/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/camera.ts +11 -4
- package/src/stream-utils.ts +24 -96
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/camera.ts
CHANGED
|
@@ -2438,11 +2438,11 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2438
2438
|
// logger.log({ supportedStreams, variantType, lensParam, rtspChannel, onNvr: this.isOnNvr, nativeStreams: nativeStreams.map(s => ({ id: s.id, nativeVariant: s.nativeVariant, lens: s.lens })), rtspStreams: rtspStreams.map(s => ({ id: s.id, lens: s.lens })), rtmpStreams: rtmpStreams.map(s => ({ id: s.id, lens: s.lens })) });
|
|
2439
2439
|
|
|
2440
2440
|
for (const supportedStream of supportedStreams) {
|
|
2441
|
-
const { id, metadata, url, name, container, lens } = supportedStream;
|
|
2441
|
+
const { id, metadata, url, name, container, lens, channel, profile, nativeVariant } = supportedStream;
|
|
2442
2442
|
|
|
2443
2443
|
// Composite streams are re-encoded to H.264 by the library (ffmpeg/libx264).
|
|
2444
2444
|
// Do not infer codec from underlying camera metadata.
|
|
2445
|
-
const isComposite =
|
|
2445
|
+
const isComposite = lens === 'composite' || channel === undefined;
|
|
2446
2446
|
const codec = (() => {
|
|
2447
2447
|
if (isComposite) return 'h264';
|
|
2448
2448
|
|
|
@@ -2472,7 +2472,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2472
2472
|
container,
|
|
2473
2473
|
video: { codec, width: metadata.width, height: metadata.height },
|
|
2474
2474
|
// audio: { codec: metadata.audioCodec }
|
|
2475
|
-
|
|
2475
|
+
|
|
2476
|
+
// Provide explicit RFC4571 metadata so stream-utils can avoid parsing the streamKey.
|
|
2477
|
+
reolinkRfc4571: {
|
|
2478
|
+
channel,
|
|
2479
|
+
profile,
|
|
2480
|
+
variant: nativeVariant,
|
|
2481
|
+
},
|
|
2482
|
+
} as any)
|
|
2476
2483
|
}
|
|
2477
2484
|
} catch (e) {
|
|
2478
2485
|
if (!this.isRecoverableBaichuanError?.(e)) {
|
|
@@ -2725,7 +2732,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2725
2732
|
this.intercom = new ReolinkBaichuanIntercom(this);
|
|
2726
2733
|
}
|
|
2727
2734
|
|
|
2728
|
-
if (hasPtz) {
|
|
2735
|
+
if (hasPtz && !this.multiFocalDevice) {
|
|
2729
2736
|
const choices = (this.presets || []).map((preset: any) => preset.id + '=' + preset.name);
|
|
2730
2737
|
|
|
2731
2738
|
this.storageSettings.settings.presets.choices = choices;
|
package/src/stream-utils.ts
CHANGED
|
@@ -17,8 +17,8 @@ export interface StreamManagerOptions {
|
|
|
17
17
|
/**
|
|
18
18
|
* Creates a dedicated Baichuan session for streaming.
|
|
19
19
|
* Required to support concurrent main+ext streams on firmwares where streamType overlaps.
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
* @param streamKey The unique stream key (e.g., "composite-rtsp-default-sub-sub", "channel_0_main", etc.)
|
|
21
|
+
* Forwarded to the library as `requestedId`.
|
|
22
22
|
*/
|
|
23
23
|
createStreamClient: (streamKey: string) => Promise<ReolinkBaichuanApi>;
|
|
24
24
|
logger: Console;
|
|
@@ -93,89 +93,13 @@ export function parseStreamProfileFromId(id: string | undefined): StreamProfile
|
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
type ReolinkRfc4571Metadata = {
|
|
97
|
+
profile: StreamProfile;
|
|
98
98
|
channel?: number;
|
|
99
99
|
variant?: NativeVideoStreamVariant;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!key) {
|
|
104
|
-
throw new Error('parseRfcStreamKey: missing streamKey');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Composite forms supported by the library/server:
|
|
108
|
-
// - composite-main-main (wider-tele)
|
|
109
|
-
// - composite_<profile>
|
|
110
|
-
// - composite_<variant>_<profile>
|
|
111
|
-
// - composite_<variant>_<wider>_<tele>
|
|
112
|
-
if (key.startsWith('composite-')) {
|
|
113
|
-
const parts = key.split('-').filter(Boolean);
|
|
114
|
-
const tele = parts.length >= 3 ? parts[2] : undefined;
|
|
115
|
-
const teleProfile = tele === 'main' || tele === 'sub' || tele === 'ext' ? (tele as StreamProfile) : undefined;
|
|
116
|
-
return { isComposite: true, profile: teleProfile };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (key.startsWith('composite_')) {
|
|
120
|
-
const parts = key.split('_').filter(Boolean);
|
|
121
|
-
// parts[0] === 'composite'
|
|
122
|
-
const maybeVariant = parts.length >= 2 ? parts[1] : undefined;
|
|
123
|
-
const variant =
|
|
124
|
-
maybeVariant === 'default' || maybeVariant === 'autotrack' || maybeVariant === 'telephoto'
|
|
125
|
-
? (maybeVariant as NativeVideoStreamVariant)
|
|
126
|
-
: undefined;
|
|
127
|
-
|
|
128
|
-
// Heuristic: pick last token that looks like a profile as the tele profile.
|
|
129
|
-
const last = parts[parts.length - 1];
|
|
130
|
-
const profile = last === 'main' || last === 'sub' || last === 'ext' ? (last as StreamProfile) : undefined;
|
|
131
|
-
return {
|
|
132
|
-
isComposite: true,
|
|
133
|
-
...(variant && variant !== 'default' ? { variant } : {}),
|
|
134
|
-
...(profile ? { profile } : {}),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Non-composite forms supported by the plugin:
|
|
139
|
-
// - channel_<ch>_<profile>
|
|
140
|
-
// - channel_<ch>_<variant>_<profile>
|
|
141
|
-
// - <ch>_<profile>
|
|
142
|
-
// - <ch>_<variant>_<profile>
|
|
143
|
-
const parts = key.split('_').filter(Boolean);
|
|
144
|
-
if (!parts.length) {
|
|
145
|
-
throw new Error(`parseRfcStreamKey: invalid streamKey='${key}'`);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
let idx = 0;
|
|
149
|
-
if (parts[0] === 'channel') {
|
|
150
|
-
idx = 1;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const channelStr = parts[idx];
|
|
154
|
-
const channel = channelStr !== undefined ? Number(channelStr) : NaN;
|
|
155
|
-
if (!Number.isFinite(channel)) {
|
|
156
|
-
throw new Error(`parseRfcStreamKey: could not parse channel from streamKey='${key}'`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const tail = parts.slice(idx + 1);
|
|
160
|
-
const last = tail[tail.length - 1];
|
|
161
|
-
const profile = last === 'main' || last === 'sub' || last === 'ext' ? (last as StreamProfile) : undefined;
|
|
162
|
-
|
|
163
|
-
// tail can be:
|
|
164
|
-
// - [profile]
|
|
165
|
-
// - [variant, profile]
|
|
166
|
-
const maybeVariant = tail.length >= 2 ? tail[0] : undefined;
|
|
167
|
-
const variant =
|
|
168
|
-
maybeVariant === 'default' || maybeVariant === 'autotrack' || maybeVariant === 'telephoto'
|
|
169
|
-
? (maybeVariant as NativeVideoStreamVariant)
|
|
170
|
-
: undefined;
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
isComposite: false,
|
|
174
|
-
channel,
|
|
175
|
-
...(variant && variant !== 'default' ? { variant } : {}),
|
|
176
|
-
...(profile ? { profile } : {}),
|
|
177
|
-
};
|
|
178
|
-
}
|
|
100
|
+
/** Explicitly mark composite (channel-less) streams. If omitted, `channel===undefined` implies composite. */
|
|
101
|
+
isComposite?: boolean;
|
|
102
|
+
};
|
|
179
103
|
|
|
180
104
|
/**
|
|
181
105
|
* Extract and normalize variant type from stream ID or URL (e.g., "autotrack" from "native_autotrack_main" or "?variant=autotrack")
|
|
@@ -253,7 +177,12 @@ export async function createRfc4571MediaObjectFromStreamManager(params: {
|
|
|
253
177
|
}): Promise<MediaObject> {
|
|
254
178
|
const { streamManager, streamKey, selected, sourceId } = params;
|
|
255
179
|
|
|
256
|
-
const
|
|
180
|
+
const meta = (selected as any)?.reolinkRfc4571 as ReolinkRfc4571Metadata | undefined;
|
|
181
|
+
if (!meta?.profile) {
|
|
182
|
+
throw new Error(`Missing RFC4571 metadata/profile for streamKey='${streamKey}'`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const { host, port, sdp, audio, username, password } = await streamManager.getRfcServer(streamKey, meta);
|
|
257
186
|
|
|
258
187
|
const { url: _ignoredUrl, ...mso }: any = selected;
|
|
259
188
|
mso.container = 'rtp';
|
|
@@ -311,21 +240,20 @@ export class StreamManager {
|
|
|
311
240
|
/**
|
|
312
241
|
* Unified RFC4571 server accessor.
|
|
313
242
|
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* - Single channel: channel_<ch>_* or <ch>_*
|
|
243
|
+
* `stream-utils` does not parse `streamKey`. It forwards the `streamKey` as `requestedId`
|
|
244
|
+
* and relies on explicit metadata from the selected stream option (profile/channel/variant).
|
|
317
245
|
*/
|
|
318
|
-
async getRfcServer(streamKey: string,
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (!resolvedProfile) {
|
|
322
|
-
throw new Error(`getRfcServer: could not infer profile from streamKey='${streamKey}'`);
|
|
246
|
+
async getRfcServer(streamKey: string, meta: ReolinkRfc4571Metadata): Promise<RfcServerInfo> {
|
|
247
|
+
if (!meta?.profile) {
|
|
248
|
+
throw new Error(`getRfcServer: missing profile for streamKey='${streamKey}'`);
|
|
323
249
|
}
|
|
324
250
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
251
|
+
const isComposite = meta.isComposite ?? (meta.channel === undefined);
|
|
252
|
+
|
|
253
|
+
return await this.ensureRfcServer(streamKey, meta.profile, {
|
|
254
|
+
channel: isComposite ? undefined : meta.channel,
|
|
255
|
+
variant: meta.variant,
|
|
256
|
+
compositeOptions: isComposite ? this.opts.compositeOptions : undefined,
|
|
329
257
|
});
|
|
330
258
|
}
|
|
331
259
|
|