@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/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.2.10",
3
+ "version": "0.2.11",
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",
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 = id.startsWith('composite_') || lens === 'composite';
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;
@@ -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
- * @param streamKey The unique stream key (e.g., "composite_default_main", "channel_0_main", etc.)
21
- * Contains all necessary information (profile, variantType, channel) for stream identification.
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
- function parseRfcStreamKey(streamKey: string): {
97
- isComposite: boolean;
96
+ type ReolinkRfc4571Metadata = {
97
+ profile: StreamProfile;
98
98
  channel?: number;
99
99
  variant?: NativeVideoStreamVariant;
100
- profile?: StreamProfile;
101
- } {
102
- const key = String(streamKey ?? '');
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 { host, port, sdp, audio, username, password } = await streamManager.getRfcServer(streamKey);
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
- * The streamKey is the single source of truth:
315
- * - Composite: composite_* (channel-less)
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, profile?: StreamProfile): Promise<RfcServerInfo> {
319
- const parsed = parseRfcStreamKey(streamKey);
320
- const resolvedProfile = profile ?? parsed.profile;
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
- return await this.ensureRfcServer(streamKey, resolvedProfile, {
326
- channel: parsed.isComposite ? undefined : parsed.channel,
327
- variant: parsed.variant,
328
- compositeOptions: parsed.isComposite ? this.opts.compositeOptions : undefined,
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