@camstack/ui-library 1.2.181 → 1.2.182
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/camera-stream-player.d.ts +15 -2
- package/dist/composites/index.d.ts +4 -0
- package/dist/composites/stream-debug/stream-debug-overlay.d.ts +34 -0
- package/dist/composites/stream-debug/webrtc-debug-stats.d.ts +129 -0
- package/dist/generated/system-hooks.d.ts +2 -0
- package/dist/hooks/use-device-webrtc.d.ts +15 -1
- package/dist/index.cjs +649 -36
- package/dist/index.js +646 -37
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { CamProfile } from '@camstack/types';
|
|
2
|
+
import { CamProfile, WebrtcSessionDebug } from '@camstack/types';
|
|
3
3
|
import { ReconnectAction } from './reconnect-schedule';
|
|
4
4
|
import { AdaptiveHintsReport } from './adaptive-downlink';
|
|
5
5
|
type WebkitPresentationMode = 'inline' | 'picture-in-picture' | 'fullscreen';
|
|
@@ -204,6 +204,19 @@ export interface CameraStreamPlayerProps {
|
|
|
204
204
|
* loader. Best-effort: omit it to disable adaptive re-offer entirely.
|
|
205
205
|
*/
|
|
206
206
|
getSessionState?: (sessionId: string) => Promise<SessionSignalingState>;
|
|
207
|
+
/**
|
|
208
|
+
* Poll the SERVER's account of this session for the stream-debug overlay —
|
|
209
|
+
* the rung `auto` actually resolved to, whether that rung is the camera's
|
|
210
|
+
* own encode or one this hub built, which node ingests it, whether the
|
|
211
|
+
* egress leg copies or re-encodes, and the cause + numbers of the ladder's
|
|
212
|
+
* last move (`webrtcSession.getSessionDebug`).
|
|
213
|
+
*
|
|
214
|
+
* The client can measure its own transport; it cannot see any of that, and
|
|
215
|
+
* "quality is stuck on low" is unanswerable without it. Called at ~1 Hz
|
|
216
|
+
* ONLY while `showStats` is true, and never when it is false. Omit it and
|
|
217
|
+
* the overlay shows its client half alone, saying so.
|
|
218
|
+
*/
|
|
219
|
+
getSessionDebug?: (sessionId: string) => Promise<WebrtcSessionDebug>;
|
|
207
220
|
/**
|
|
208
221
|
* Re-offer signaling for an adaptive tier switch (client-offer mode).
|
|
209
222
|
* Posts a fresh browser-built offer for an EXISTING `sessionId` against
|
|
@@ -274,5 +287,5 @@ export interface CameraStreamPlayerProps {
|
|
|
274
287
|
*/
|
|
275
288
|
onVideoElement?: (el: HTMLVideoElement | null) => void;
|
|
276
289
|
}
|
|
277
|
-
export declare function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay, muted: initialMuted, showControls, showStats, onPlaybackStats, onClientNetworkSample, onConnectTiming, className, onStateChange, onError, onReconnectAttempt, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, reoffer, posterUrl, hintsOverride, onAdaptiveHints, reconnectSignal, onControlChannel, onVideoElement, }: CameraStreamPlayerProps): import("react").JSX.Element;
|
|
290
|
+
export declare function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay, muted: initialMuted, showControls, showStats, onPlaybackStats, onClientNetworkSample, onConnectTiming, className, onStateChange, onError, onReconnectAttempt, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, getSessionDebug, reoffer, posterUrl, hintsOverride, onAdaptiveHints, reconnectSignal, onControlChannel, onVideoElement, }: CameraStreamPlayerProps): import("react").JSX.Element;
|
|
278
291
|
export {};
|
|
@@ -17,6 +17,10 @@ export * from './breadcrumb';
|
|
|
17
17
|
export type { AdaptiveHintsReport, AdaptiveOfferPhase, } from './adaptive-downlink';
|
|
18
18
|
export type { CameraStreamPlayerProps, ClientOfferResult, ClientStreamHints, PlayerConnectionState, PlayerWebrtcTarget, SessionSignalingState, SignalingResult, } from './camera-stream-player';
|
|
19
19
|
export { CameraStreamPlayer } from './camera-stream-player';
|
|
20
|
+
export { StreamDebugOverlay } from './stream-debug/stream-debug-overlay';
|
|
21
|
+
export type { StreamDebugOverlayProps } from './stream-debug/stream-debug-overlay';
|
|
22
|
+
export { deriveWebrtcDebugStats, freezeVerdict, } from './stream-debug/webrtc-debug-stats';
|
|
23
|
+
export type { DeriveWebrtcDebugStatsInput, FreezeVerdict, FreezeVerdictKind, ResolutionMark, WebrtcDebugStats, WebrtcDebugWindow, } from './stream-debug/webrtc-debug-stats';
|
|
20
24
|
export { AutotrackSection } from './cap-settings/AutotrackSection';
|
|
21
25
|
export { ConsumablesPanel } from './cap-settings/ConsumablesPanel';
|
|
22
26
|
export type { CapSettingsComponentProps } from './cap-settings/index';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { WebrtcSessionDebug } from '@camstack/types';
|
|
3
|
+
import { WebrtcDebugStats } from './webrtc-debug-stats.js';
|
|
4
|
+
/**
|
|
5
|
+
* The stream-debug overlay: the CLIENT's transport measurements next to what
|
|
6
|
+
* the SERVER decided, over the live picture.
|
|
7
|
+
*
|
|
8
|
+
* Three things about this surface are requirements, not styling:
|
|
9
|
+
*
|
|
10
|
+
* 1. It must be legible over a FROZEN picture. The panel is opaque, not a
|
|
11
|
+
* wash — a translucent overlay on a stuck bright frame is unreadable
|
|
12
|
+
* exactly when it is needed.
|
|
13
|
+
* 2. Every number says WHEN it was measured. Two independent clocks feed
|
|
14
|
+
* this (a 1 s `getStats()` poll and a 1 s server poll) and either can
|
|
15
|
+
* stop. A stale poll that looks live is how a debug tool lies.
|
|
16
|
+
* 3. An UNKNOWN value renders as `—`, and a measured zero renders as `0`
|
|
17
|
+
* (D393). In a tool whose whole job is telling a stalled decoder from a
|
|
18
|
+
* stalled transport, "nobody measured" and "measured nothing" are
|
|
19
|
+
* opposite conclusions.
|
|
20
|
+
*/
|
|
21
|
+
export interface StreamDebugOverlayProps {
|
|
22
|
+
/** The client half. Null before the first successful poll. */
|
|
23
|
+
readonly stats: WebrtcDebugStats | null;
|
|
24
|
+
/** The server half. Null when not wired, or before the first answer. */
|
|
25
|
+
readonly server: WebrtcSessionDebug | null;
|
|
26
|
+
/** Why the last server poll produced nothing. Shown verbatim. */
|
|
27
|
+
readonly serverError: string | null;
|
|
28
|
+
/** Whether a server poll is wired at all (vs. simply not configured). */
|
|
29
|
+
readonly serverWired: boolean;
|
|
30
|
+
/** Ticking clock, so the ages re-render between polls. */
|
|
31
|
+
readonly now: number;
|
|
32
|
+
readonly sessionId: string | null;
|
|
33
|
+
}
|
|
34
|
+
export declare function StreamDebugOverlay(props: StreamDebugOverlayProps): ReactNode;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure derivation of the stream-debug overlay's CLIENT half from one
|
|
3
|
+
* `RTCPeerConnection.getStats()` report.
|
|
4
|
+
*
|
|
5
|
+
* Why it is pure: this is the surface an operator reads while a camera is
|
|
6
|
+
* frozen, and the numbers it shows decide whether the stall is in the
|
|
7
|
+
* transport or in the decoder. A derivation that can only be exercised by
|
|
8
|
+
* mounting a player and waiting for a real freeze is a derivation nobody
|
|
9
|
+
* checks. Everything here is a function of (report, previous sample, clock).
|
|
10
|
+
*
|
|
11
|
+
* D393 — a measurement that FAILED is `null`, never `0`. A stat the browser
|
|
12
|
+
* did not report is absent, and "absent" must never render as "zero" in a
|
|
13
|
+
* debug tool: `freezeCount: 0` means the decoder measured no freeze,
|
|
14
|
+
* `freezeCount: null` means nobody measured. Those are opposite conclusions.
|
|
15
|
+
*/
|
|
16
|
+
/** One resolution the stream actually presented, and when it started. */
|
|
17
|
+
export interface ResolutionMark {
|
|
18
|
+
readonly width: number;
|
|
19
|
+
readonly height: number;
|
|
20
|
+
readonly at: number;
|
|
21
|
+
}
|
|
22
|
+
/** Deltas over the interval between the previous sample and this one. */
|
|
23
|
+
export interface WebrtcDebugWindow {
|
|
24
|
+
/** Length of the window, from the RTP report clock. Null on the first
|
|
25
|
+
* sample and whenever the report timestamp did not advance. */
|
|
26
|
+
readonly spanMs: number | null;
|
|
27
|
+
readonly kbps: number | null;
|
|
28
|
+
readonly packetsReceived: number | null;
|
|
29
|
+
readonly packetsLost: number | null;
|
|
30
|
+
readonly lossPercent: number | null;
|
|
31
|
+
/** 0 here is the load-bearing value: the transport is delivering and the
|
|
32
|
+
* decoder emitted nothing. */
|
|
33
|
+
readonly framesDecoded: number | null;
|
|
34
|
+
readonly framesDropped: number | null;
|
|
35
|
+
readonly freezeCount: number | null;
|
|
36
|
+
readonly freezeMs: number | null;
|
|
37
|
+
readonly nackCount: number | null;
|
|
38
|
+
readonly pliCount: number | null;
|
|
39
|
+
/** Mean decode time per frame decoded IN THIS WINDOW. */
|
|
40
|
+
readonly decodeMs: number | null;
|
|
41
|
+
}
|
|
42
|
+
export interface WebrtcDebugStats {
|
|
43
|
+
/** Wall clock at which this sample was taken — the overlay prints its age
|
|
44
|
+
* so a stale poll can never be mistaken for a live one. */
|
|
45
|
+
readonly at: number;
|
|
46
|
+
/** Whether an inbound-rtp video report existed at all. `false` means every
|
|
47
|
+
* inbound field below is null because nothing was measured. */
|
|
48
|
+
readonly hasInboundVideo: boolean;
|
|
49
|
+
readonly width: number | null;
|
|
50
|
+
readonly height: number | null;
|
|
51
|
+
readonly fps: number | null;
|
|
52
|
+
readonly codec: string | null;
|
|
53
|
+
readonly codecProfileLevelId: string | null;
|
|
54
|
+
readonly jitterMs: number | null;
|
|
55
|
+
readonly packetsReceived: number | null;
|
|
56
|
+
readonly packetsLost: number | null;
|
|
57
|
+
readonly lossPercent: number | null;
|
|
58
|
+
readonly nackCount: number | null;
|
|
59
|
+
readonly pliCount: number | null;
|
|
60
|
+
readonly framesDecoded: number | null;
|
|
61
|
+
readonly framesDropped: number | null;
|
|
62
|
+
readonly freezeCount: number | null;
|
|
63
|
+
readonly totalFreezeMs: number | null;
|
|
64
|
+
readonly avgDecodeMs: number | null;
|
|
65
|
+
readonly rttMs: number | null;
|
|
66
|
+
readonly availableIncomingKbps: number | null;
|
|
67
|
+
readonly localCandidateType: string | null;
|
|
68
|
+
readonly remoteCandidateType: string | null;
|
|
69
|
+
/** `local/remote` candidate types of the selected pair, e.g. `srflx/host`. */
|
|
70
|
+
readonly candidatePairType: string | null;
|
|
71
|
+
readonly candidateProtocol: string | null;
|
|
72
|
+
readonly iceConnectionState: string | null;
|
|
73
|
+
readonly dtlsState: string | null;
|
|
74
|
+
readonly window: WebrtcDebugWindow;
|
|
75
|
+
/** Every resolution the stream presented, oldest first, newest last. */
|
|
76
|
+
readonly resolutionLog: readonly ResolutionMark[];
|
|
77
|
+
/** Raw counters carried forward so the NEXT sample can window them. */
|
|
78
|
+
readonly raw: RawCounters;
|
|
79
|
+
}
|
|
80
|
+
/** The cumulative counters a window is computed from. */
|
|
81
|
+
export interface RawCounters {
|
|
82
|
+
/** The RTP report clock (`RTCStats.timestamp`), not the wall clock. */
|
|
83
|
+
readonly timestamp: number | null;
|
|
84
|
+
readonly bytesReceived: number | null;
|
|
85
|
+
readonly packetsReceived: number | null;
|
|
86
|
+
readonly packetsLost: number | null;
|
|
87
|
+
readonly framesDecoded: number | null;
|
|
88
|
+
readonly framesDropped: number | null;
|
|
89
|
+
readonly freezeCount: number | null;
|
|
90
|
+
readonly totalFreezesDurationS: number | null;
|
|
91
|
+
readonly totalDecodeTimeS: number | null;
|
|
92
|
+
readonly nackCount: number | null;
|
|
93
|
+
readonly pliCount: number | null;
|
|
94
|
+
}
|
|
95
|
+
export interface DeriveWebrtcDebugStatsInput {
|
|
96
|
+
readonly report: RTCStatsReport;
|
|
97
|
+
readonly prev: WebrtcDebugStats | null;
|
|
98
|
+
/** Wall clock — `Date.now()` in production, fixed in tests. */
|
|
99
|
+
readonly now: number;
|
|
100
|
+
readonly iceConnectionState: string | null;
|
|
101
|
+
readonly dtlsState: string | null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Derive one debug sample. Immutable: `prev` is read, never mutated, and the
|
|
105
|
+
* returned object is the input for the next call.
|
|
106
|
+
*/
|
|
107
|
+
export declare function deriveWebrtcDebugStats(input: DeriveWebrtcDebugStatsInput): WebrtcDebugStats;
|
|
108
|
+
/**
|
|
109
|
+
* The one question this overlay exists to answer while a camera is frozen:
|
|
110
|
+
* did the BYTES stop, or did the DECODER stop?
|
|
111
|
+
*
|
|
112
|
+
* The open investigation is a `mid` stream that freezes until the user hits
|
|
113
|
+
* pause/play. Those two causes need opposite fixes and look identical on the
|
|
114
|
+
* screen; they differ only here, in one window's worth of counters:
|
|
115
|
+
* - bytes arriving, no frame out ⇒ the decoder is wedged (pause/play
|
|
116
|
+
* rebuilds it, which is exactly the reported workaround);
|
|
117
|
+
* - nothing arriving ⇒ the transport stopped, and the server
|
|
118
|
+
* half of the overlay says whether the broker was still sending.
|
|
119
|
+
*
|
|
120
|
+
* Every other case is `unknown`, deliberately. A verdict guessed from a
|
|
121
|
+
* missing byte rate is worse than no verdict — it is the thing an operator
|
|
122
|
+
* will trust and stop measuring.
|
|
123
|
+
*/
|
|
124
|
+
export type FreezeVerdictKind = 'unknown' | 'decoding' | 'decoder-stall' | 'transport-stall' | 'frozen-unknown';
|
|
125
|
+
export interface FreezeVerdict {
|
|
126
|
+
readonly kind: FreezeVerdictKind;
|
|
127
|
+
readonly label: string;
|
|
128
|
+
}
|
|
129
|
+
export declare function freezeVerdict(stats: WebrtcDebugStats | null): FreezeVerdict;
|
|
@@ -2097,6 +2097,8 @@ export declare const useWebrtcSessionCloseSession: typeof trpc.webrtcSession.clo
|
|
|
2097
2097
|
export declare const useWebrtcSessionHasAdaptiveBitrate: typeof trpc.webrtcSession.hasAdaptiveBitrate.useQuery;
|
|
2098
2098
|
/** Generated alias around `trpc.webrtcSession.getSessionState.useQuery`. */
|
|
2099
2099
|
export declare const useWebrtcSessionGetSessionState: typeof trpc.webrtcSession.getSessionState.useQuery;
|
|
2100
|
+
/** Generated alias around `trpc.webrtcSession.getSessionDebug.useQuery`. */
|
|
2101
|
+
export declare const useWebrtcSessionGetSessionDebug: typeof trpc.webrtcSession.getSessionDebug.useQuery;
|
|
2100
2102
|
/** Generated alias around `trpc.zoneAnalytics.getCurrentSnapshot.useQuery`. */
|
|
2101
2103
|
export declare const useZoneAnalyticsGetCurrentSnapshot: typeof trpc.zoneAnalytics.getCurrentSnapshot.useQuery;
|
|
2102
2104
|
/** Generated alias around `trpc.zoneAnalytics.getCurrentSnapshotBatch.useQuery`. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CamProfile } from '@camstack/types';
|
|
1
|
+
import { CamProfile, WebrtcSessionDebug } from '@camstack/types';
|
|
2
2
|
import { StreamChoice } from '../composites/stream-panel';
|
|
3
3
|
import { ClientStreamHints, SignalingResult, ClientOfferResult } from '../composites/camera-stream-player';
|
|
4
4
|
/**
|
|
@@ -114,6 +114,12 @@ export interface UseDeviceWebrtcTrpc {
|
|
|
114
114
|
epoch: number;
|
|
115
115
|
} | null;
|
|
116
116
|
}>;
|
|
117
|
+
/** The SERVER's account of a live session, for the stream-debug overlay.
|
|
118
|
+
* Read-only; the overlay polls it at ~1 Hz only while it is visible. */
|
|
119
|
+
getSessionDebug: QueryFn<{
|
|
120
|
+
deviceId: number;
|
|
121
|
+
sessionId: string;
|
|
122
|
+
}, WebrtcSessionDebug>;
|
|
117
123
|
};
|
|
118
124
|
pipelineOrchestrator?: {
|
|
119
125
|
getCameraMetrics: QueryFn<{
|
|
@@ -187,6 +193,14 @@ export interface DeviceWebrtcResult {
|
|
|
187
193
|
* `{ pendingRenegotiation: null }` for non-adaptive / steady sessions.
|
|
188
194
|
*/
|
|
189
195
|
readonly getSessionState: (sessionId: string) => Promise<SessionRenegotiationState>;
|
|
196
|
+
/**
|
|
197
|
+
* The SERVER half of the stream-debug overlay for a session. Unlike
|
|
198
|
+
* `getSessionState`, a failure here THROWS rather than resolving a benign
|
|
199
|
+
* default: this is a diagnostic read, and a swallowed error would render as
|
|
200
|
+
* a confident set of unknowns with no reason attached. The overlay catches
|
|
201
|
+
* it and shows the message.
|
|
202
|
+
*/
|
|
203
|
+
readonly getSessionDebug: (sessionId: string) => Promise<WebrtcSessionDebug>;
|
|
190
204
|
}
|
|
191
205
|
/** Live signaling state for a session — the adaptive re-offer signal. */
|
|
192
206
|
export interface SessionRenegotiationState {
|