@ksp-gonogo/kerbcast 1.10.1 → 1.10.3
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/__generated__/types.d.ts +45 -0
- package/dist/client.d.ts +23 -1
- package/dist/client.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/noise.js +11 -0
- package/dist/recording.d.ts +113 -59
- package/dist/recording.js +297 -129
- package/package.json +1 -1
- package/src/__generated__/types.ts +46 -0
- package/src/client.test.ts +103 -0
- package/src/client.ts +29 -0
- package/src/index.ts +2 -0
- package/src/noise.test.ts +40 -0
- package/src/noise.ts +13 -0
- package/src/recording.test.ts +391 -59
- package/src/recording.ts +359 -150
|
@@ -99,6 +99,13 @@ export declare enum QualityPreset {
|
|
|
99
99
|
Half = "half",
|
|
100
100
|
Quarter = "quarter"
|
|
101
101
|
}
|
|
102
|
+
/** The per-dimension MAX display size held for a camera across consumers. */
|
|
103
|
+
export interface DisplaySizeReport {
|
|
104
|
+
width: number;
|
|
105
|
+
height: number;
|
|
106
|
+
/** Milliseconds since the most recent report that contributed to the max. */
|
|
107
|
+
ageMs: number;
|
|
108
|
+
}
|
|
102
109
|
/**
|
|
103
110
|
* Per-camera snapshot pushed by the sidecar on every state change
|
|
104
111
|
* (operator API call, adaptive shed, vessel change). Same shape served
|
|
@@ -238,6 +245,44 @@ export interface CameraState {
|
|
|
238
245
|
* Absent/None = the request is fully honored.
|
|
239
246
|
*/
|
|
240
247
|
qualityLimitedBy?: string;
|
|
248
|
+
/**
|
|
249
|
+
* Rate in fps Kerbcast is currently pacing this camera's capture to: the
|
|
250
|
+
* plugin's primary rate (MaxCaptureFps) while displayed, the background
|
|
251
|
+
* rate while subscribed but undisplayed, 0 when not capturing.
|
|
252
|
+
*/
|
|
253
|
+
targetCaptureFps?: number;
|
|
254
|
+
/**
|
|
255
|
+
* Whether any consumer reports a non-zero display size for this camera,
|
|
256
|
+
* or a consumer forces it (e.g. a full-resolution recording).
|
|
257
|
+
*/
|
|
258
|
+
displayed?: boolean;
|
|
259
|
+
/**
|
|
260
|
+
* Frames the plugin produced into this camera's ring per second, over
|
|
261
|
+
* roughly the last two seconds, as observed by the sidecar's encode loop.
|
|
262
|
+
*/
|
|
263
|
+
captureFps?: number;
|
|
264
|
+
/**
|
|
265
|
+
* Encoded frames handed to this camera's tracks per second, over the same
|
|
266
|
+
* window. Below `capture_fps` when the encoder skips frames (encode pacing,
|
|
267
|
+
* degrade); a viewer receiving less than this is losing frames in transit.
|
|
268
|
+
*/
|
|
269
|
+
sentFps?: number;
|
|
270
|
+
/**
|
|
271
|
+
* Keyframes the encoder emitted for this camera (periodic or requested)
|
|
272
|
+
* since its ring was attached.
|
|
273
|
+
*/
|
|
274
|
+
keyframesSent?: number;
|
|
275
|
+
/**
|
|
276
|
+
* PLI/FIR keyframe requests received from viewers for this camera, across
|
|
277
|
+
* peers, since its ring was attached. Counted before the per-slot debounce,
|
|
278
|
+
* so it can exceed the keyframes forced in response.
|
|
279
|
+
*/
|
|
280
|
+
keyframeRequestsReceived?: number;
|
|
281
|
+
/**
|
|
282
|
+
* Largest display size currently held for this camera across consumers.
|
|
283
|
+
* Absent when no consumer reports one (`GET /cameras` sends null instead).
|
|
284
|
+
*/
|
|
285
|
+
displaySize?: DisplaySizeReport;
|
|
241
286
|
}
|
|
242
287
|
export interface CameraSnapshotPayload {
|
|
243
288
|
cameras: CameraState[];
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AdaptiveShedPayload, CameraState, ClientMessage, ErrorPayload, Layer, QualityPreset, SettingsStatePayload, TrackMode } from "./__generated__/types";
|
|
1
|
+
import type { AdaptiveShedPayload, CameraState, ClientMessage, DisplaySizeReport, ErrorPayload, Layer, QualityPreset, SettingsStatePayload, TrackMode } from "./__generated__/types";
|
|
2
2
|
import { CameraLifecycle } from "./__generated__/types";
|
|
3
3
|
import { RecordingController, type RecordingControllerOptions } from "./recording";
|
|
4
4
|
/** Controls the digital-static noise overlay baked into `cam.mediaStream`. */
|
|
@@ -214,6 +214,21 @@ export interface DiscoveredCamera {
|
|
|
214
214
|
targetBitrateBps: number;
|
|
215
215
|
/** Effective degrade level (max across active subscribers). 0.0 to 1.0. */
|
|
216
216
|
degradeLevel: number;
|
|
217
|
+
/** Rate Kerbcast paces this camera's capture to: the primary rate while
|
|
218
|
+
* displayed, the background rate while undisplayed, 0 when not capturing. */
|
|
219
|
+
targetCaptureFps?: number;
|
|
220
|
+
/** Any consumer reports a non-zero display size, or the feed is forced. */
|
|
221
|
+
displayed?: boolean;
|
|
222
|
+
/** Frames the plugin produced per second over roughly the last 2 s. */
|
|
223
|
+
captureFps?: number;
|
|
224
|
+
/** Encoded frames handed to this camera's tracks per second, same window. */
|
|
225
|
+
sentFps?: number;
|
|
226
|
+
/** Keyframes the encoder emitted since the camera's stream started. */
|
|
227
|
+
keyframesSent?: number;
|
|
228
|
+
/** PLI/FIR keyframe requests received across peers since the stream started. */
|
|
229
|
+
keyframeRequestsReceived?: number;
|
|
230
|
+
/** Largest display size held across consumers; null when none is held. */
|
|
231
|
+
displaySize?: DisplaySizeReport | null;
|
|
217
232
|
}
|
|
218
233
|
/** Event payloads emitted by a {@link KerbcastClient}. */
|
|
219
234
|
export interface KerbcastClientEvents {
|
|
@@ -240,6 +255,13 @@ export interface KerbcastClientEvents {
|
|
|
240
255
|
* instead of per-camera SIGNAL LOST.
|
|
241
256
|
*/
|
|
242
257
|
"scene-change": boolean | undefined;
|
|
258
|
+
/**
|
|
259
|
+
* Fired each time the control channel of a freshly (re)connected peer
|
|
260
|
+
* opens, i.e. the earliest moment it can carry messages. Consumers that
|
|
261
|
+
* hold per-peer state on the sidecar (display sizes, subscriptions) re-send
|
|
262
|
+
* it here, because the sidecar forgets a peer's state when it leaves.
|
|
263
|
+
*/
|
|
264
|
+
"control-open": undefined;
|
|
243
265
|
}
|
|
244
266
|
/**
|
|
245
267
|
* Pluggable WebRTC transport. Production code uses the default
|
package/dist/client.js
CHANGED
|
@@ -626,6 +626,7 @@ export class KerbcastClient extends TypedEmitter {
|
|
|
626
626
|
this.control = peer.createDataChannel("kerbcast-control");
|
|
627
627
|
this.control.onOpen(() => {
|
|
628
628
|
void this._send({ type: "hello" });
|
|
629
|
+
this.emit("control-open", undefined);
|
|
629
630
|
});
|
|
630
631
|
this.control.onMessage((raw) => {
|
|
631
632
|
this.handleServerMessage(raw);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from "./__generated__/types";
|
|
2
2
|
export { KerbcastClient, BrowserKerbcastTransport, type BrowserKerbcastTransportOptions, type InboundVideoStats, type KerbcastCameraHandle, type KerbcastClientConfig, type KerbcastConnectionState, type KerbcastClientEvents, type KerbcastCameraEvents, type KerbcastTransport, type KerbcastPeer, type KerbcastDataChannel, type DiscoveredCamera, type NoiseConfig, } from "./client";
|
|
3
3
|
export { PanZoomController, type PanZoomCommandSink, type PanZoomBounds, type PanZoomControllerOptions, } from "./panZoom";
|
|
4
|
-
export { RecordingController, createMediabunnyTrimmerLoader, negotiateMimeType, commonUtWindow, utToRecordingTimeMs, DEFAULT_COVERAGE_TIMEOUT_MS, ARM_TIMEOUT_MS, type ActiveRecordingInfo, type RecordingClient, type RecordingControllerOptions, type RecordingHandle, type RecordingsSnapshot, type GroupedRecordingHandle, type GroupedRecordingStart, type GroupTrimmer, type TrimmerLoader, type UtSample, type StartRecordingOptions, type StopGroupedRecordingOptions, } from "./recording";
|
|
4
|
+
export { RecordingController, createMediabunnyTrimmerLoader, negotiateMimeType, commonUtWindow, utToRecordingTimeMs, DEFAULT_COVERAGE_TIMEOUT_MS, ARM_TIMEOUT_MS, videoElementDecodedSizeProbe, type ActiveRecordingInfo, type DecodedSizeProbe, type RecordingClient, type RecordingControllerOptions, type RecordingHandle, type RecordingsSnapshot, type GroupedRecordingHandle, type GroupedRecordingStart, type GroupTrimmer, type TrimmerLoader, type UtSample, type StartRecordingOptions, type StopGroupedRecordingOptions, } from "./recording";
|
package/dist/index.js
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
export * from "./__generated__/types";
|
|
6
6
|
export { KerbcastClient, BrowserKerbcastTransport, } from "./client";
|
|
7
7
|
export { PanZoomController, } from "./panZoom";
|
|
8
|
-
export { RecordingController, createMediabunnyTrimmerLoader, negotiateMimeType, commonUtWindow, utToRecordingTimeMs, DEFAULT_COVERAGE_TIMEOUT_MS, ARM_TIMEOUT_MS, } from "./recording";
|
|
8
|
+
export { RecordingController, createMediabunnyTrimmerLoader, negotiateMimeType, commonUtWindow, utToRecordingTimeMs, DEFAULT_COVERAGE_TIMEOUT_MS, ARM_TIMEOUT_MS, videoElementDecodedSizeProbe, } from "./recording";
|
package/dist/noise.js
CHANGED
|
@@ -145,6 +145,17 @@ export function tryCreateNoisePipeline(rawStream, initialIntensity, options) {
|
|
|
145
145
|
if (destroyed)
|
|
146
146
|
return;
|
|
147
147
|
const now = performance.now();
|
|
148
|
+
/*
|
|
149
|
+
* Follow mid-stream size changes. loadedmetadata fires once per source,
|
|
150
|
+
* so without this the canvas (and the processed stream every consumer
|
|
151
|
+
* displays and records) stays at the first decoded size forever, even
|
|
152
|
+
* after the sidecar raises the render size.
|
|
153
|
+
*/
|
|
154
|
+
if (video.srcObject &&
|
|
155
|
+
video.videoWidth > 0 &&
|
|
156
|
+
(video.videoWidth !== canvas.width || video.videoHeight !== canvas.height)) {
|
|
157
|
+
onMeta();
|
|
158
|
+
}
|
|
148
159
|
const cw = canvas.width;
|
|
149
160
|
const ch = canvas.height;
|
|
150
161
|
const nw = noiseCanvas.width;
|
package/dist/recording.d.ts
CHANGED
|
@@ -45,20 +45,22 @@ export interface StartRecordingOptions {
|
|
|
45
45
|
/**
|
|
46
46
|
* Force this feed to the operator's full render size for the duration of
|
|
47
47
|
* the recording (overrides the display-size demand only; still yields to
|
|
48
|
-
* the adaptive framerate shed). Off by default.
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
48
|
+
* the adaptive framerate shed). Off by default. The recording arms and
|
|
49
|
+
* waits (bounded) until the browser is decoding full-size frames before
|
|
50
|
+
* the clip starts: MediaRecorder fixes the clip's frame size when it
|
|
51
|
+
* starts, so a later bump would never reach the file. A grouped recording
|
|
52
|
+
* arms every member and starts all their recorders together once every
|
|
53
|
+
* feed is ready (or the shared bound passes). Released automatically on
|
|
54
|
+
* stop, discard, or auto-stop.
|
|
53
55
|
*/
|
|
54
56
|
forceFullResolution?: boolean;
|
|
55
57
|
}
|
|
56
58
|
/**
|
|
57
|
-
* Bound on the
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* Bound on the arm-and-wait: how long a forced recording waits for its feed
|
|
60
|
+
* (or, for a group, every member's feed) to reach full resolution before
|
|
61
|
+
* starting the recorder anyway. Never hangs a start indefinitely (feed out
|
|
62
|
+
* of flight, wedged, or the sidecar/plugin round trip taking longer than
|
|
63
|
+
* expected).
|
|
62
64
|
*/
|
|
63
65
|
export declare const ARM_TIMEOUT_MS = 3000;
|
|
64
66
|
/** Options for {@link RecordingController.stopGroupedRecording}. */
|
|
@@ -122,11 +124,11 @@ export interface ActiveRecordingInfo {
|
|
|
122
124
|
startedAt: number;
|
|
123
125
|
groupId?: string;
|
|
124
126
|
/**
|
|
125
|
-
* True while a
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* this is true.
|
|
127
|
+
* True while a forced recording is armed (force sent, waiting for the
|
|
128
|
+
* feed to reach full resolution) but the recorder has not started yet.
|
|
129
|
+
* Forced grouped members stay arming until every member is ready, then
|
|
130
|
+
* flip together. Always false for a non-forced recording. A UI shows a
|
|
131
|
+
* distinct "arming" state while this is true.
|
|
130
132
|
*/
|
|
131
133
|
arming: boolean;
|
|
132
134
|
}
|
|
@@ -208,6 +210,25 @@ export interface GroupTrimmer {
|
|
|
208
210
|
}
|
|
209
211
|
/** Loads a {@link GroupTrimmer}, or resolves null when trimming is unavailable. */
|
|
210
212
|
export type TrimmerLoader = () => Promise<GroupTrimmer | null>;
|
|
213
|
+
/**
|
|
214
|
+
* Watches the decoded frame size of a feed's live stream, calling `onSize`
|
|
215
|
+
* whenever it is known or changes (repeats are fine). Returns a stop function
|
|
216
|
+
* that releases everything the probe holds. The arm-and-wait gates a forced
|
|
217
|
+
* recording on this, not on the reported camera state: the state flips to
|
|
218
|
+
* the full size before the decoder delivers full-size frames, and
|
|
219
|
+
* MediaRecorder locks the clip to whatever size it sees first.
|
|
220
|
+
*/
|
|
221
|
+
export type DecodedSizeProbe = (flightId: number, stream: MediaStream, onSize: (width: number, height: number) => void) => () => void;
|
|
222
|
+
/**
|
|
223
|
+
* Default {@link DecodedSizeProbe}: plays the stream into an off-DOM muted
|
|
224
|
+
* `<video>` and reads the decoded size from each frame
|
|
225
|
+
* (requestVideoFrameCallback where available) and from `resize` /
|
|
226
|
+
* `loadedmetadata`. `track.getSettings()` is not used because Chromium often
|
|
227
|
+
* reports stale or missing dimensions for remote WebRTC tracks. Outside a
|
|
228
|
+
* browser it never reports, so a forced recording falls back to the arm
|
|
229
|
+
* timeout.
|
|
230
|
+
*/
|
|
231
|
+
export declare const videoElementDecodedSizeProbe: DecodedSizeProbe;
|
|
211
232
|
/** Options for {@link RecordingController}. */
|
|
212
233
|
export interface RecordingControllerOptions {
|
|
213
234
|
/**
|
|
@@ -216,6 +237,12 @@ export interface RecordingControllerOptions {
|
|
|
216
237
|
* `import("mediabunny")` keyframe remux.
|
|
217
238
|
*/
|
|
218
239
|
loadTrimmer?: TrimmerLoader;
|
|
240
|
+
/**
|
|
241
|
+
* Override how a forced recording observes its feed's decoded frame size
|
|
242
|
+
* (tests inject a fake; a harness without real decoding can follow the
|
|
243
|
+
* camera state instead). Defaults to {@link videoElementDecodedSizeProbe}.
|
|
244
|
+
*/
|
|
245
|
+
probeDecodedSize?: DecodedSizeProbe;
|
|
219
246
|
}
|
|
220
247
|
/**
|
|
221
248
|
* Builds a {@link TrimmerLoader} around a caller-supplied module import.
|
|
@@ -249,6 +276,7 @@ export declare class RecordingController {
|
|
|
249
276
|
/** Per-group trimmer load kicked off at group-start, awaited at stop. */
|
|
250
277
|
private readonly groupTrimmers;
|
|
251
278
|
private readonly loadTrimmer;
|
|
279
|
+
private readonly probeDecodedSize;
|
|
252
280
|
/** recordingIds in a grouped set, so they stay out of the standalone `recordings` list. */
|
|
253
281
|
private readonly groupMemberIds;
|
|
254
282
|
/** recordingId -> groupId, so discarding one member can pull it out of its group's array. */
|
|
@@ -268,6 +296,8 @@ export declare class RecordingController {
|
|
|
268
296
|
* one; see {@link acquireForce}.
|
|
269
297
|
*/
|
|
270
298
|
private readonly forceSendState;
|
|
299
|
+
/** groupId -> arm state, present only while a forced group is still arming. */
|
|
300
|
+
private readonly groupArms;
|
|
271
301
|
private readonly listeners;
|
|
272
302
|
private snapshot;
|
|
273
303
|
/** While true, {@link rebuild} is a no-op; used to batch startGroupedRecording's
|
|
@@ -307,31 +337,20 @@ export declare class RecordingController {
|
|
|
307
337
|
*/
|
|
308
338
|
private releaseForce;
|
|
309
339
|
/**
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
* arrives, and a `null` baseline is never ready: a placeholder `{0,0}`
|
|
323
|
-
* would let any positive operator size look like an increase, so no
|
|
324
|
-
* verdict is possible until a genuine baseline exists. This is a
|
|
325
|
-
* strictly weaker guarantee than the known-target path: a feed already
|
|
326
|
-
* at its true ceiling when forced will not be recognised as ready until
|
|
327
|
-
* something else changes its operator size, or the wait simply times
|
|
328
|
-
* out, but it still refuses to treat the pre-force snapshot itself as
|
|
329
|
-
* ready.
|
|
330
|
-
*
|
|
331
|
-
* Either way, a state whose rendered size has not yet caught up to its own
|
|
332
|
-
* operator size (adaptive shed still catching up) is never ready.
|
|
340
|
+
* Fallback readiness for a feed whose ceiling is unknown to the client (no
|
|
341
|
+
* `maxRenderSize`), judged from the reported camera state alone. Ready once
|
|
342
|
+
* the operator size has increased past `baseline` (the first REAL state
|
|
343
|
+
* observed at or after watch-start; see {@link watchResolutionReady}) and
|
|
344
|
+
* the rendered size has caught up to it. `baseline` is `null` until that
|
|
345
|
+
* first real state arrives, and a `null` baseline is never ready: a
|
|
346
|
+
* placeholder `{0,0}` would let any positive operator size look like an
|
|
347
|
+
* increase. A feed already at its true ceiling when forced is not
|
|
348
|
+
* recognised until something else changes its operator size, so the wait
|
|
349
|
+
* usually times out instead, but the pre-force snapshot itself is never
|
|
350
|
+
* treated as ready. A state whose rendered size has not yet caught up to
|
|
351
|
+
* its own operator size (adaptive shed still catching up) is never ready.
|
|
333
352
|
*/
|
|
334
|
-
private
|
|
353
|
+
private stateIncreasedPastBaseline;
|
|
335
354
|
/**
|
|
336
355
|
* Seeds a fallback baseline from an observed state, or returns `null` if
|
|
337
356
|
* the state isn't a REAL observation (operator size still zero, i.e. no
|
|
@@ -341,14 +360,24 @@ export declare class RecordingController {
|
|
|
341
360
|
*/
|
|
342
361
|
private seedResolutionBaseline;
|
|
343
362
|
/**
|
|
344
|
-
* Waits for a forced feed to actually
|
|
363
|
+
* Waits for a forced feed to actually deliver full-resolution frames.
|
|
364
|
+
*
|
|
365
|
+
* With the ceiling known (`maxRenderSize`), the gate is the decoded frame
|
|
366
|
+
* size from {@link probeDecodedSize} reaching it (within
|
|
367
|
+
* {@link DECODED_SIZE_TOLERANCE_PX}). The camera state is deliberately not
|
|
368
|
+
* trusted here: it reports the new size before the decoder delivers
|
|
369
|
+
* full-size frames, and a recorder started in that gap locks the clip to
|
|
370
|
+
* the old size. A feed already at its ceiling resolves on the probe's
|
|
371
|
+
* first report. With the ceiling unknown, falls back to the state-only
|
|
372
|
+
* check ({@link stateIncreasedPastBaseline}).
|
|
373
|
+
*
|
|
345
374
|
* `timeoutMs` omitted means no bound: the wait only ends when the feed
|
|
346
375
|
* becomes ready or the caller cancels it (the grouped path, bounded by the
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
376
|
+
* group's one shared timer instead; see {@link armGroup}). Resolves `true`
|
|
377
|
+
* once ready, `false` on timeout. `cancel` abandons the wait (stop/discard)
|
|
378
|
+
* without settling the promise; every exit path releases the "change"
|
|
379
|
+
* subscription, the probe, and the timer. `lastDecoded` reports the most
|
|
380
|
+
* recent decoded size seen, for logging.
|
|
352
381
|
*/
|
|
353
382
|
private watchResolutionReady;
|
|
354
383
|
/**
|
|
@@ -356,16 +385,38 @@ export declare class RecordingController {
|
|
|
356
385
|
* synchronously. Throws if the feed has no live track or is already
|
|
357
386
|
* recording.
|
|
358
387
|
*
|
|
359
|
-
* `
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
* (`resolutionReadyAt`/`resolutionReadyUt`) so {@link stopGroupedRecording}
|
|
366
|
-
* can fold it into the common trim window.
|
|
388
|
+
* With `opts.forceFullResolution`, the recording arms and waits, bounded by
|
|
389
|
+
* {@link ARM_TIMEOUT_MS}, for the feed to reach full resolution before the
|
|
390
|
+
* recorder actually starts, so the clip opens on full-res frames.
|
|
391
|
+
* `inGroup` is internal (set by {@link startGroupedRecording}): a forced
|
|
392
|
+
* grouped member arms the same way but leaves the wait to the group, which
|
|
393
|
+
* starts every member's recorder together (see {@link armGroup}).
|
|
367
394
|
*/
|
|
368
395
|
startRecording(flightId: number, opts?: StartRecordingOptions, inGroup?: boolean): string;
|
|
396
|
+
/**
|
|
397
|
+
* Arms a forced group: watches every member's feed and starts all their
|
|
398
|
+
* recorders together once each has reached full resolution, or once the
|
|
399
|
+
* one shared {@link ARM_TIMEOUT_MS} bound passes, whichever comes first.
|
|
400
|
+
* Members stopped or discarded mid-arm drop out of the wait, so a slow
|
|
401
|
+
* feed that gets cancelled no longer holds the rest back.
|
|
402
|
+
*/
|
|
403
|
+
private armGroup;
|
|
404
|
+
/** Members of a group still armed and waiting to start (not stopped or discarded). */
|
|
405
|
+
private armingMembers;
|
|
406
|
+
/** Starts an arming group once every member still waiting is ready. */
|
|
407
|
+
private checkGroupArm;
|
|
408
|
+
/**
|
|
409
|
+
* Ends a group's arm and starts every still-armed member's recorder in the
|
|
410
|
+
* same tick, on one shared clock base (`startedAt`, `utStart`), so the
|
|
411
|
+
* clips open together. Cancels any outstanding per-member watches (the
|
|
412
|
+
* timeout path).
|
|
413
|
+
*/
|
|
414
|
+
private startArmedGroup;
|
|
415
|
+
/**
|
|
416
|
+
* Abandons a group's arm without starting anything: the caller is tearing
|
|
417
|
+
* the whole group down (stop or discard), so no member should start mid-way.
|
|
418
|
+
*/
|
|
419
|
+
private cancelGroupArm;
|
|
369
420
|
/**
|
|
370
421
|
* Stop a recording and resolve its finished handle. Idempotent per id: a
|
|
371
422
|
* second call (or a race with maxDurationMs auto-stop) returns the same
|
|
@@ -386,12 +437,15 @@ export declare class RecordingController {
|
|
|
386
437
|
discardRecording(recordingId: string): void;
|
|
387
438
|
/**
|
|
388
439
|
* Start recording several feeds as one synchronised moment. All recorders
|
|
389
|
-
* open in a single tick to minimise skew.
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
440
|
+
* open in a single tick to minimise skew. With `opts.forceFullResolution`,
|
|
441
|
+
* every member arms first and that single tick comes later, once all feeds
|
|
442
|
+
* have reached full resolution or {@link ARM_TIMEOUT_MS} passes (see
|
|
443
|
+
* {@link armGroup}); the members report `arming` until then. Returns the
|
|
444
|
+
* groupId plus the real per-feed recordingIds (so a caller can cancel one
|
|
445
|
+
* member individually via `stopRecording`/`discardRecording`, the same ids
|
|
446
|
+
* that end up on each clip's `RecordingHandle`). If any feed can't start
|
|
447
|
+
* (no track / already recording), the ones already started are discarded
|
|
448
|
+
* and the error rethrows.
|
|
395
449
|
*/
|
|
396
450
|
startGroupedRecording(flightIds: number[], opts?: StartRecordingOptions): GroupedRecordingStart;
|
|
397
451
|
/**
|