@camstack/ui-library 1.1.16 → 1.1.17
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/composites/cap-settings/CoverageTrack.d.ts +12 -1
- package/dist/contexts/recorded-playback.d.ts +23 -0
- package/dist/index.cjs +514 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +502 -9
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/recorded-control-protocol.d.ts +54 -0
- package/dist/lib/scrub-controller.d.ts +115 -0
- package/package.json +1 -1
package/dist/lib/index.d.ts
CHANGED
|
@@ -10,3 +10,7 @@ export { PHASE_CONFIG, getPhaseVisual } from './phase-config';
|
|
|
10
10
|
export type { PhaseVisual } from './phase-config';
|
|
11
11
|
export { createSharedContext } from './shared-context';
|
|
12
12
|
export { ensureMfHostInit } from './mf-runtime-init';
|
|
13
|
+
export { serializeRecordedCommand, parseRecordedServerMessage, RECORDED_PLAYBACK_MODES, } from './recorded-control-protocol';
|
|
14
|
+
export type { RecordedControlCommand, RecordedPlaybackMode, RecordedServerMessage, } from './recorded-control-protocol';
|
|
15
|
+
export { scrubReducer, initialScrubState, shouldEmit, shouldCommit, cursorFractionFor, makeScrubBridge, useScrubController, COMMIT_DEDUPE_TOLERANCE_MS, COMMIT_DEDUPE_WINDOW_MS, } from './scrub-controller';
|
|
16
|
+
export type { ScrubSource, ScrubState, ScrubAction, ScrubController, PlaybackControlSink, LastCommit, } from './scrub-controller';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side wire codec for the `camstack-control` RTCDataChannel (R9).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the broker's `control-protocol.ts`
|
|
5
|
+
* (packages/addon-pipeline/src/stream-broker/recorded/control-protocol.ts):
|
|
6
|
+
* client→server commands drive the per-viewer `TimelineSession` /
|
|
7
|
+
* `RecordedFeeder` (frame-push recorded playback on the SAME WebRTC session
|
|
8
|
+
* as live video); server→client messages report playback state + a ~1 Hz
|
|
9
|
+
* position. Shapes are re-declared here (not imported) because ui-library
|
|
10
|
+
* cannot depend on an addon package — the embed SPA does the same.
|
|
11
|
+
*
|
|
12
|
+
* Parsing is defensive: a malformed frame returns `null`, never throws, so a
|
|
13
|
+
* bad datachannel message can't take down the player.
|
|
14
|
+
*/
|
|
15
|
+
export type RecordedControlCommand = {
|
|
16
|
+
readonly t: 'playRecorded';
|
|
17
|
+
readonly deviceId: number;
|
|
18
|
+
readonly epoch: number;
|
|
19
|
+
readonly profile: string;
|
|
20
|
+
} | {
|
|
21
|
+
readonly t: 'seek';
|
|
22
|
+
readonly epoch: number;
|
|
23
|
+
} | {
|
|
24
|
+
readonly t: 'scrubCommit';
|
|
25
|
+
readonly epoch: number;
|
|
26
|
+
} | {
|
|
27
|
+
readonly t: 'setRate';
|
|
28
|
+
readonly rate: 0 | 1;
|
|
29
|
+
} | {
|
|
30
|
+
readonly t: 'goLive';
|
|
31
|
+
} | {
|
|
32
|
+
readonly t: 'setProfile';
|
|
33
|
+
readonly profile: string;
|
|
34
|
+
};
|
|
35
|
+
/** Serialize a command for `RTCDataChannel.send` (matches the server schema). */
|
|
36
|
+
export declare function serializeRecordedCommand(cmd: RecordedControlCommand): string;
|
|
37
|
+
/**
|
|
38
|
+
* Playback mode reported by the server session. `live` = the shared live feed
|
|
39
|
+
* owns the track; every other value describes the recorded feeder.
|
|
40
|
+
*/
|
|
41
|
+
export declare const RECORDED_PLAYBACK_MODES: readonly ["live", "loading", "playing", "paused", "gap", "ended", "error"];
|
|
42
|
+
export type RecordedPlaybackMode = (typeof RECORDED_PLAYBACK_MODES)[number];
|
|
43
|
+
export type RecordedServerMessage = {
|
|
44
|
+
readonly kind: 'state';
|
|
45
|
+
readonly state: RecordedPlaybackMode;
|
|
46
|
+
} | {
|
|
47
|
+
readonly kind: 'position';
|
|
48
|
+
readonly epochMs: number;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Parse a raw server→client control frame. Returns `null` on any malformed
|
|
52
|
+
* payload (bad JSON, unknown discriminator, wrong field types).
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseRecordedServerMessage(raw: string): RecordedServerMessage | null;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export type ScrubSource = 'timeline' | 'heatmap';
|
|
2
|
+
export interface ScrubState {
|
|
3
|
+
readonly scrubbing: boolean;
|
|
4
|
+
readonly scrubEpoch: number | null;
|
|
5
|
+
readonly playheadEpoch: number | null;
|
|
6
|
+
readonly lastSource: ScrubSource | null;
|
|
7
|
+
}
|
|
8
|
+
export declare const initialScrubState: ScrubState;
|
|
9
|
+
export type ScrubAction = {
|
|
10
|
+
readonly type: 'begin';
|
|
11
|
+
readonly source: ScrubSource;
|
|
12
|
+
} | {
|
|
13
|
+
readonly type: 'update';
|
|
14
|
+
readonly epoch: number;
|
|
15
|
+
readonly source: ScrubSource;
|
|
16
|
+
} | {
|
|
17
|
+
readonly type: 'end';
|
|
18
|
+
readonly epoch: number;
|
|
19
|
+
readonly source: ScrubSource;
|
|
20
|
+
} | {
|
|
21
|
+
readonly type: 'seek';
|
|
22
|
+
readonly epoch: number;
|
|
23
|
+
readonly source: ScrubSource;
|
|
24
|
+
} | {
|
|
25
|
+
readonly type: 'playhead';
|
|
26
|
+
readonly epoch: number;
|
|
27
|
+
} | {
|
|
28
|
+
readonly type: 'reset';
|
|
29
|
+
};
|
|
30
|
+
export declare function scrubReducer(state: ScrubState, action: ScrubAction): ScrubState;
|
|
31
|
+
/**
|
|
32
|
+
* Throttle decision for continuous `update` emits (default ~70 ms).
|
|
33
|
+
* Returns true when enough time has elapsed since the last emit.
|
|
34
|
+
*/
|
|
35
|
+
export declare function shouldEmit(lastEmitMs: number, now: number, throttleMs?: number): boolean;
|
|
36
|
+
/** The last committed target (epoch + wall-clock of the commit). */
|
|
37
|
+
export interface LastCommit {
|
|
38
|
+
readonly epoch: number;
|
|
39
|
+
readonly at: number;
|
|
40
|
+
}
|
|
41
|
+
/** Epochs within this many ms of the last commit count as the SAME target. */
|
|
42
|
+
export declare const COMMIT_DEDUPE_TOLERANCE_MS = 750;
|
|
43
|
+
/** Only dedupe against a commit made within this recent window. */
|
|
44
|
+
export declare const COMMIT_DEDUPE_WINDOW_MS = 2000;
|
|
45
|
+
/**
|
|
46
|
+
* Decide whether a `seek`/`scrubCommit` should actually hit the playback sink.
|
|
47
|
+
* Each commit reloads the server feeder (loading→playing) and visibly stutters
|
|
48
|
+
* playback; firing the same (or a near-identical) target twice in quick
|
|
49
|
+
* succession produces a redundant reload. Drop a commit only when it targets
|
|
50
|
+
* essentially the SAME epoch as the last commit AND that commit was recent — a
|
|
51
|
+
* genuinely different target, or a re-seek to the same spot after the window,
|
|
52
|
+
* always commits.
|
|
53
|
+
*/
|
|
54
|
+
export declare function shouldCommit(last: LastCommit | null, epoch: number, now: number, toleranceMs?: number, windowMs?: number): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* The active cursor position (scrub target while scrubbing, else playhead)
|
|
57
|
+
* expressed as a 0..1 fraction across the day window.
|
|
58
|
+
* Returns null when no position is known or the day window is degenerate.
|
|
59
|
+
*/
|
|
60
|
+
export declare function cursorFractionFor(state: ScrubState, day: {
|
|
61
|
+
readonly from: number;
|
|
62
|
+
readonly to: number;
|
|
63
|
+
}): number | null;
|
|
64
|
+
/**
|
|
65
|
+
* Generic, always-active playback control surface the scrub controller
|
|
66
|
+
* drives. `scrubStart`/`scrubTo` are CLIENT-SIDE hooks (the wire protocol has
|
|
67
|
+
* no per-move scrub message — a future WebCodecs preview consumes them);
|
|
68
|
+
* `scrubCommit`/`seek` reach the server feeder.
|
|
69
|
+
*/
|
|
70
|
+
export interface PlaybackControlSink {
|
|
71
|
+
scrubStart(): void;
|
|
72
|
+
scrubTo(epochMs: number): void;
|
|
73
|
+
scrubCommit(epochMs: number): void;
|
|
74
|
+
seek(epochMs: number): void;
|
|
75
|
+
}
|
|
76
|
+
/** A mutable holder, structurally compatible with React's `MutableRefObject`. */
|
|
77
|
+
export interface MutableRef<T> {
|
|
78
|
+
current: T;
|
|
79
|
+
}
|
|
80
|
+
/** The gesture callbacks the hook exposes (everything except `state`). */
|
|
81
|
+
type ScrubBridge = Omit<ScrubController, 'state'>;
|
|
82
|
+
/**
|
|
83
|
+
* Builds the always-active bridge callbacks from a sink, a dispatch, and the
|
|
84
|
+
* throttle ref. Pure (no React) so it can be unit-tested directly: the hook
|
|
85
|
+
* just wraps the result in `useMemo`.
|
|
86
|
+
*
|
|
87
|
+
* Every gesture drives the sink immediately — there is no VOD-active gate.
|
|
88
|
+
*/
|
|
89
|
+
export declare function makeScrubBridge(sink: PlaybackControlSink, dispatch: (action: ScrubAction) => void, lastEmitRef: MutableRef<number>, lastCommitRef?: MutableRef<LastCommit | null>,
|
|
90
|
+
/** Reports whether the PLAYER is currently on the live edge (not in recorded
|
|
91
|
+
* mode). Used to bypass commit-dedupe on the live→recorded transition. */
|
|
92
|
+
isPlayerLive?: () => boolean): ScrubBridge;
|
|
93
|
+
export interface ScrubController {
|
|
94
|
+
readonly state: ScrubState;
|
|
95
|
+
readonly beginScrub: (source: ScrubSource) => void;
|
|
96
|
+
readonly updateScrub: (epoch: number, source: ScrubSource) => void;
|
|
97
|
+
readonly endScrub: (epoch: number, source: ScrubSource) => void;
|
|
98
|
+
readonly seek: (epoch: number, source: ScrubSource) => void;
|
|
99
|
+
readonly setPlayhead: (epoch: number) => void;
|
|
100
|
+
readonly reset: () => void;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Owns the shared scrub position and bridges it to a playback control sink.
|
|
104
|
+
*
|
|
105
|
+
* The bridge is always active: any gesture (drag or tap) drives the sink
|
|
106
|
+
* immediately. There is no VOD-active gate — the timeline always drives
|
|
107
|
+
* playback while frame-push mode is on.
|
|
108
|
+
*
|
|
109
|
+
* @param sink Always-active playback control surface (e.g. the datachannel)
|
|
110
|
+
* @param isPlayerLive Synchronous getter for "is the player on the live
|
|
111
|
+
* edge?" — lets a live→recorded commit bypass dedupe so the seek always
|
|
112
|
+
* reaches the server.
|
|
113
|
+
*/
|
|
114
|
+
export declare function useScrubController(sink: PlaybackControlSink, isPlayerLive?: () => boolean): ScrubController;
|
|
115
|
+
export {};
|