@kymoscope/sdk 0.3.0 → 0.5.0
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/assets/{importer.worker-Drm9u2Rc.js → importer.worker-CLBGjz4d.js} +1 -1
- package/dist/assets/{offscreenLineWorker-mShhnyXX.js → offscreenLineWorker-BLxay-Po.js} +2 -2
- package/dist/importers-Y-PAIexV.js +5 -0
- package/dist/keys-DMRJGG6U.js +10 -0
- package/dist/kymoscope-embed.js +92 -90
- package/dist/kymoscope-sdk.js +87 -84
- package/dist/rules/kyr1.schema.json +135 -0
- package/dist/rules.js +2 -0
- package/dist/styles/charts.css +191 -0
- package/dist/threshold-C9it0Y7P.js +2 -0
- package/dist/types/charts/AnnotationLayer.d.ts +7 -2
- package/dist/types/charts/CameraPanel.d.ts +43 -0
- package/dist/types/charts/ChartBody.d.ts +12 -0
- package/dist/types/charts/beacon.d.ts +4 -1
- package/dist/types/charts/cameraLayout.d.ts +10 -0
- package/dist/types/charts/liveOverlay.d.ts +14 -0
- package/dist/types/charts/runtime.d.ts +12 -1
- package/dist/types/charts/useWorksheetData.d.ts +3 -5
- package/dist/types/components/GaugeChart.d.ts +2 -0
- package/dist/types/components/MultiBandCanvasChart.d.ts +4 -2
- package/dist/types/components/dashboardGridLayout.d.ts +1 -1
- package/dist/types/components/multiBandHover.d.ts +7 -0
- package/dist/types/engine/alerts.d.ts +56 -0
- package/dist/types/engine/columns.d.ts +16 -0
- package/dist/types/engine/createEngine.d.ts +111 -3
- package/dist/types/engine/derive.d.ts +1 -1
- package/dist/types/engine/errors.d.ts +1 -1
- package/dist/types/engine/liveController.d.ts +11 -1
- package/dist/types/engine/types.d.ts +69 -4
- package/dist/types/index.d.ts +29 -4
- package/dist/types/lib/annotations.d.ts +20 -0
- package/dist/types/lib/beacon.d.ts +2 -1
- package/dist/types/lib/cycles/cut.d.ts +48 -0
- package/dist/types/lib/cycles/evaluator.d.ts +127 -0
- package/dist/types/lib/data/columnarDataSource.d.ts +1 -0
- package/dist/types/lib/data/dataSource.d.ts +3 -0
- package/dist/types/lib/data/duckDbDataSource.d.ts +1 -0
- package/dist/types/lib/data/historyDataSource.d.ts +51 -0
- package/dist/types/lib/data/inMemoryDataSource.d.ts +1 -0
- package/dist/types/lib/data/liveDataSource.d.ts +20 -1
- package/dist/types/lib/data/pointColumns.d.ts +11 -0
- package/dist/types/lib/duckdb/duckdbInstance.d.ts +5 -0
- package/dist/types/lib/historian/blockCache.d.ts +32 -0
- package/dist/types/lib/historian/export.d.ts +31 -0
- package/dist/types/lib/historian/khd1.d.ts +41 -0
- package/dist/types/lib/historian/parquet.d.ts +21 -0
- package/dist/types/lib/historian/reader.d.ts +73 -0
- package/dist/types/lib/live/blockWire.d.ts +16 -0
- package/dist/types/lib/live/buffer.d.ts +34 -0
- package/dist/types/lib/live/cameraSender.d.ts +70 -0
- package/dist/types/lib/live/clockMap.d.ts +20 -0
- package/dist/types/lib/live/crypto.d.ts +10 -0
- package/dist/types/lib/live/publisher.d.ts +168 -0
- package/dist/types/lib/live/types.d.ts +21 -3
- package/dist/types/lib/live/videoTrack.d.ts +96 -0
- package/dist/types/lib/live/videoWire.d.ts +35 -0
- package/dist/types/lib/live/wire.d.ts +4 -0
- package/dist/types/lib/panelChartSettings.d.ts +40 -1
- package/dist/types/lib/recordings/export.d.ts +1 -0
- package/dist/types/lib/recordings/videoSource.d.ts +56 -0
- package/dist/types/lib/render/alertGlyph.d.ts +7 -0
- package/dist/types/lib/render/glSeries.d.ts +1 -1
- package/dist/types/lib/segments/align.d.ts +85 -0
- package/dist/types/lib/segments/liveAxis.d.ts +65 -0
- package/dist/types/lib/segments/rules.d.ts +91 -0
- package/dist/types/lib/workspaces/io.d.ts +12 -0
- package/dist/types/lib/yAxisTicks.d.ts +11 -0
- package/dist/types/rules/compile.d.ts +47 -0
- package/dist/types/rules/evaluator.d.ts +51 -0
- package/dist/types/rules/index.d.ts +4 -0
- package/dist/types/rules/threshold.d.ts +3 -0
- package/dist/types/rules/types.d.ts +89 -0
- package/dist/types/rules/validate.d.ts +6 -0
- package/dist/types/types/loadedDataset.d.ts +20 -0
- package/dist/types/types/plotSeries.d.ts +5 -0
- package/package.json +7 -2
- package/dist/importers-DHeUdZcP.js +0 -5
- package/dist/krc-BIfw0rX1.js +0 -9
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cycle rules v2 (spec docs/superpowers/specs/2026-10-28-cycles-design.md, "Rules"): where a source is cut into cycles,
|
|
3
|
+
* and phases inside them. Boundaries and flags only; KPIs are the app's job. The collector runs the same semantics
|
|
4
|
+
* (packages/collector/src/kymoscope_collector/cycles.py), pinned by tests/vectors/cycle-rules-v2.json. Saved v1 rules
|
|
5
|
+
* (segment_definitions rule_v 1) keep running through ../segments/rules.ts RuleRunner.
|
|
6
|
+
*
|
|
7
|
+
* Semantics:
|
|
8
|
+
* - A start fires when its trigger becomes true (a condition rising, or an event: change, cross, reset, clock,
|
|
9
|
+
* parent-start). With minHold_s, a condition start must stay true that long; the cycle starts when it first held.
|
|
10
|
+
* - An end fires while its trigger is true, from the sample after the start on. With ignoreDips_s, a condition end must
|
|
11
|
+
* stay true that long; the cycle ends when it first held. `next-start`: each start closes the previous cycle.
|
|
12
|
+
* - A NaN value never changes a condition. A phase only sees samples inside its parent and closes with it (incomplete
|
|
13
|
+
* unless its end names parent-end).
|
|
14
|
+
* - Limits never drop a cycle; a missed one adds a flag: late (expectedWithin_s from the parent's or its own start),
|
|
15
|
+
* too-long (max_s), too-short (min_s).
|
|
16
|
+
*/
|
|
17
|
+
import { type CompareOp, type KpiSpec } from '../segments/rules';
|
|
18
|
+
export type Condition = {
|
|
19
|
+
kind: 'compare';
|
|
20
|
+
channel: string;
|
|
21
|
+
op: CompareOp;
|
|
22
|
+
value: number;
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'change';
|
|
25
|
+
channel: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'cross';
|
|
28
|
+
channel: string;
|
|
29
|
+
dir: 'rise' | 'fall';
|
|
30
|
+
value: number;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'reset';
|
|
33
|
+
channel: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'elapsed';
|
|
36
|
+
from: 'parent-start' | 'start';
|
|
37
|
+
op: '>=' | '<=';
|
|
38
|
+
seconds: number;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'clock';
|
|
41
|
+
at: string[];
|
|
42
|
+
tz: string;
|
|
43
|
+
};
|
|
44
|
+
export type Trigger = {
|
|
45
|
+
kind: 'parent-start';
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'parent-end';
|
|
48
|
+
} | {
|
|
49
|
+
kind: 'next-start';
|
|
50
|
+
} | {
|
|
51
|
+
kind: 'manual';
|
|
52
|
+
} | {
|
|
53
|
+
kind: 'all' | 'any';
|
|
54
|
+
of: Trigger[];
|
|
55
|
+
} | Condition;
|
|
56
|
+
export interface CycleLimits {
|
|
57
|
+
expectedWithin_s?: number;
|
|
58
|
+
from?: 'parent-start' | 'start';
|
|
59
|
+
min_s?: number;
|
|
60
|
+
max_s?: number;
|
|
61
|
+
}
|
|
62
|
+
export interface CycleRule {
|
|
63
|
+
start: Trigger;
|
|
64
|
+
end: Trigger;
|
|
65
|
+
minHold_s?: number;
|
|
66
|
+
ignoreDips_s?: number;
|
|
67
|
+
limits?: CycleLimits;
|
|
68
|
+
}
|
|
69
|
+
export type CycleFlag = 'late' | 'too-long' | 'too-short';
|
|
70
|
+
export interface CycleInstance {
|
|
71
|
+
tstart: number;
|
|
72
|
+
tend: number | null;
|
|
73
|
+
flags: CycleFlag[];
|
|
74
|
+
incomplete?: true;
|
|
75
|
+
}
|
|
76
|
+
/** Every channel a rule reads. */
|
|
77
|
+
export declare function cycleRuleChannels(r: CycleRule): string[];
|
|
78
|
+
export declare class CycleRunner {
|
|
79
|
+
#private;
|
|
80
|
+
readonly rule: CycleRule;
|
|
81
|
+
constructor(rule: CycleRule);
|
|
82
|
+
/** A phase's parent opened: state starts over. Top-level rules never need it (the first sample is the start). */
|
|
83
|
+
parentStart(t: number): void;
|
|
84
|
+
/** The parent closed at t: an open phase closes with it. */
|
|
85
|
+
parentEnd(t: number, out: CycleInstance[]): void;
|
|
86
|
+
/** One sample, in time order; closed cycles are pushed to `out`. */
|
|
87
|
+
step(t: number, v: (channel: string) => number, out: CycleInstance[]): void;
|
|
88
|
+
/** The open cycle so far (flags up to the last sample), or null. */
|
|
89
|
+
open(): CycleInstance | null;
|
|
90
|
+
}
|
|
91
|
+
export interface RuleDef {
|
|
92
|
+
id: string;
|
|
93
|
+
rule: CycleRule;
|
|
94
|
+
parentId: string | null;
|
|
95
|
+
}
|
|
96
|
+
export interface CycleFound extends CycleInstance {
|
|
97
|
+
definitionId: string;
|
|
98
|
+
index: number;
|
|
99
|
+
parentIndex?: number;
|
|
100
|
+
/** Its definition's KPIs by column name ("TIC201 max"), when asked for (engine.datasets.findCycles). */
|
|
101
|
+
kpis?: Record<string, number | null>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Definitions evaluated together, phases inside their parents. Indexes count per definition from 1 (a phase's within
|
|
105
|
+
* its parent). ponytail: a parent whose start waits for minHold_s opens after the fact, so its phases only see samples
|
|
106
|
+
* from the moment it was confirmed; buffer the held samples if a phase ever needs them.
|
|
107
|
+
*/
|
|
108
|
+
export declare class CycleSet {
|
|
109
|
+
#private;
|
|
110
|
+
constructor(defs: RuleDef[]);
|
|
111
|
+
/** Samples in time order; returns the cycles and phases they closed. `col(name)`: a channel's column aligned with
|
|
112
|
+
* `t` (NaN where it has no value), or undefined. */
|
|
113
|
+
feed(t: ArrayLike<number>, col: (name: string) => ArrayLike<number> | undefined): CycleFound[];
|
|
114
|
+
/** The cycles and phases open now. */
|
|
115
|
+
open(): CycleFound[];
|
|
116
|
+
}
|
|
117
|
+
/** A saved rule: a cycle (parentId null) or a phase inside another definition. `kpis`: a column per KPI in the cycles
|
|
118
|
+
* and phases tables (duration is always there). */
|
|
119
|
+
export interface CycleDefinition extends RuleDef {
|
|
120
|
+
name: string;
|
|
121
|
+
kpis?: KpiSpec[];
|
|
122
|
+
/** The value beside each cycle (in a cut's list): a channel's statistic over the cycle; absent, its duration. */
|
|
123
|
+
show?: KpiSpec;
|
|
124
|
+
}
|
|
125
|
+
/** The definitions in `x` that are well formed (a workbook from anywhere: trust nothing); a phase whose parent is
|
|
126
|
+
* missing is dropped too. */
|
|
127
|
+
export declare function parseCycleDefinitions(x: unknown): CycleDefinition[];
|
|
@@ -19,6 +19,7 @@ export declare class ColumnarDataSource implements DataSource {
|
|
|
19
19
|
/** {@link getSeriesMulti} without the Promise: decimating the columns is pure computation. */
|
|
20
20
|
getSeriesMultiSync(columns: string[], viewport: Viewport, maxPoints: number): MultiSeriesResult;
|
|
21
21
|
getColumnsSync(columns: string[], viewport: Viewport, limit: number): PointColumns | null;
|
|
22
|
+
readColumns(columns: string[], offset: number, limit: number): Promise<Record<string, Float64Array>>;
|
|
22
23
|
xBounds(column: string): Promise<{
|
|
23
24
|
x0: number;
|
|
24
25
|
x1: number;
|
|
@@ -61,6 +61,9 @@ export interface DataSource {
|
|
|
61
61
|
* when there are more than `limit` of them or X isn't ascending.
|
|
62
62
|
*/
|
|
63
63
|
getColumnsSync?(columns: string[], viewport: Viewport, limit: number): PointColumns | null;
|
|
64
|
+
/** The rows [offset, offset + limit) of `columns` in file order, as numbers (NaN for empty or text cells).
|
|
65
|
+
* Unknown columns are left out. For whole-column passes (alert rules on loaded data); read in chunks. */
|
|
66
|
+
readColumns(columns: string[], offset: number, limit: number): Promise<Record<string, Float64Array>>;
|
|
64
67
|
xBoundsSync?(column: string): {
|
|
65
68
|
x0: number;
|
|
66
69
|
x1: number;
|
|
@@ -33,6 +33,7 @@ export declare class DuckDbDataSource implements DataSource {
|
|
|
33
33
|
* a narrow window are denser, so the same `maxRows` budget buys finer detail.
|
|
34
34
|
*/
|
|
35
35
|
sampleRows(maxRows: number, range?: XRange): Promise<DatasetRow[]>;
|
|
36
|
+
readColumns(columns: string[], offset: number, limit: number): Promise<Record<string, Float64Array>>;
|
|
36
37
|
/** Full-range x bounds for a column, for an initial viewport. */
|
|
37
38
|
xBounds(xKey: string): Promise<{
|
|
38
39
|
x0: number;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Historian ranges as a DataSource (plan P5): a list of pieces, each one block's times with Float32 values per channel
|
|
3
|
+
* (raw: lo and hi are the same arrays; a rollup: the bucket's min and max at its middle). The time column is
|
|
4
|
+
* `ElapsedTime` in Unix seconds, the same X as a collector's live stream. Decimation reads each piece on its own times,
|
|
5
|
+
* so tag groups never share a padded table. Everything is pure computation (sync), like the in-memory sources.
|
|
6
|
+
*/
|
|
7
|
+
import type { ColumnSchema, DataSource, DecimatedSeries, MultiSeriesResult, SeriesQuery, Viewport } from './dataSource';
|
|
8
|
+
import type { DatasetRow } from '../parseDataset';
|
|
9
|
+
import type { HistoryLevel } from '../historian/khd1';
|
|
10
|
+
export interface HistoryPiece {
|
|
11
|
+
/** Unix seconds, ascending. */
|
|
12
|
+
t: Float64Array;
|
|
13
|
+
/** Per channel: the value (raw) or the bucket minimum (rollup). NaN: no value at that time. */
|
|
14
|
+
lo: Map<string, Float32Array>;
|
|
15
|
+
/** Per channel: the bucket maximum (rollup); the very same Map as `lo` for raw data. */
|
|
16
|
+
hi: Map<string, Float32Array>;
|
|
17
|
+
level: HistoryLevel;
|
|
18
|
+
}
|
|
19
|
+
/** First index with t[i] >= x, or with t[i] > x when `after`. */
|
|
20
|
+
export declare function bound(t: Float64Array, x: number, after?: boolean): number;
|
|
21
|
+
/** Rows [i, j) of a piece (views, nothing copied); null when empty. */
|
|
22
|
+
export declare function slicePiece(p: HistoryPiece, i: number, j: number): HistoryPiece | null;
|
|
23
|
+
/** The rows of `p` with t in [t0, t1]. */
|
|
24
|
+
export declare const clipPiece: (p: HistoryPiece, t0: number, t1: number) => HistoryPiece | null;
|
|
25
|
+
/** The overview outside [a, b] with the detail inside: the pan/zoom view (plan P6). */
|
|
26
|
+
export declare function splicePieces(overview: HistoryPiece[], detail: HistoryPiece[], a: number, b: number): HistoryPiece[];
|
|
27
|
+
export declare class HistoryDataSource implements DataSource {
|
|
28
|
+
#private;
|
|
29
|
+
readonly pieces: readonly HistoryPiece[];
|
|
30
|
+
constructor(pieces: HistoryPiece[], units?: Record<string, string>);
|
|
31
|
+
schemaSync(): ColumnSchema[];
|
|
32
|
+
getSchema(): Promise<ColumnSchema[]>;
|
|
33
|
+
/** Rows are the pieces' rows in time order (a rollup: its minimum); NaN where a piece lacks the channel. */
|
|
34
|
+
readColumns(columns: string[], offset: number, limit: number): Promise<Record<string, Float64Array>>;
|
|
35
|
+
getSeriesMultiSync(columns: string[], viewport: Viewport, maxPoints: number): MultiSeriesResult;
|
|
36
|
+
getSeriesMulti(columns: string[], viewport: Viewport, maxPoints: number): Promise<MultiSeriesResult>;
|
|
37
|
+
getSeries(q: SeriesQuery): Promise<DecimatedSeries>;
|
|
38
|
+
xBoundsSync(column: string): {
|
|
39
|
+
x0: number;
|
|
40
|
+
x1: number;
|
|
41
|
+
};
|
|
42
|
+
xBounds(column: string): Promise<{
|
|
43
|
+
x0: number;
|
|
44
|
+
x1: number;
|
|
45
|
+
}>;
|
|
46
|
+
/** At most `maxRows` rows in time order for the row-based charts and math channels: one stride over all pieces' rows
|
|
47
|
+
* end to end (pieces smaller than the stride may be skipped); a row holds its piece's channels only (a rollup: the
|
|
48
|
+
* bucket minimum). ponytail: no cross-group alignment in the sample. */
|
|
49
|
+
sampleRowsSync(maxRows: number): DatasetRow[];
|
|
50
|
+
dispose(): Promise<void>;
|
|
51
|
+
}
|
|
@@ -12,6 +12,7 @@ export declare class InMemoryDataSource implements DataSource {
|
|
|
12
12
|
getSeriesMultiSync(columns: string[], viewport: Viewport, maxPoints: number): MultiSeriesResult;
|
|
13
13
|
/** {@link DataSource.getColumnsSync}: a table's columns are the samples (other rows: null, decimate). */
|
|
14
14
|
getColumnsSync(columns: string[], viewport: Viewport, limit: number): PointColumns | null;
|
|
15
|
+
readColumns(columns: string[], offset: number, limit: number): Promise<Record<string, Float64Array>>;
|
|
15
16
|
/** Exact min/max like the DuckDB source ({0, 0} when the column has no numbers), not `columnBounds`' padded axis range. */
|
|
16
17
|
xBounds(column: string): Promise<{
|
|
17
18
|
x0: number;
|
|
@@ -16,6 +16,8 @@ export declare class LiveDataSource implements DataSource {
|
|
|
16
16
|
* a host's sample-count badge show the climbing count by polling itself, instead of the
|
|
17
17
|
* count riding on the ~1 Hz `connections` state whose update re-renders the App. */
|
|
18
18
|
size(): number;
|
|
19
|
+
/** The stream's column names now (the time column first). */
|
|
20
|
+
get columnNames(): string[];
|
|
19
21
|
/** Changes whenever the buffered data does (see LiveBuffer.version). */
|
|
20
22
|
version(): number;
|
|
21
23
|
/**
|
|
@@ -24,6 +26,8 @@ export declare class LiveDataSource implements DataSource {
|
|
|
24
26
|
* LiveBuffer.mirrorColumn). A new map per call: the columns as they are now.
|
|
25
27
|
*/
|
|
26
28
|
columns(): ReadonlyMap<string, Float64Array>;
|
|
29
|
+
/** Whether `column` has had a finite value (a column the snapshot rows may thin out of view). */
|
|
30
|
+
hasValues(column: string): boolean;
|
|
27
31
|
/** A fresh row snapshot of the newest samples (at most `maxRows`, evenly thinned). */
|
|
28
32
|
snapshot(maxRows: number): Dataset;
|
|
29
33
|
getSchema(): Promise<ColumnSchema[]>;
|
|
@@ -57,8 +61,22 @@ export declare class LiveDataSource implements DataSource {
|
|
|
57
61
|
xAtTimeSync(column: string, t: number, hold?: boolean): number;
|
|
58
62
|
/** The newest sample's value of `column` (NaN if none): the live position on X. */
|
|
59
63
|
latestSync(column: string): number;
|
|
60
|
-
/** Newest received value of each column it has (finite values only)
|
|
64
|
+
/** Newest received value of each column it has (finite values only): the newest sample's, else the column's
|
|
65
|
+
* last known value (a channel that has no value at the newest time, or is quiet). */
|
|
61
66
|
latestValuesSync(columns: string[]): Map<string, number>;
|
|
67
|
+
/**
|
|
68
|
+
* Y range of `column` for a scale: its samples in the ring and its last known value (a quiet channel has only
|
|
69
|
+
* that); a constant gets ±max(0.5, 5%) so it sits mid-band. Null when it has neither.
|
|
70
|
+
*/
|
|
71
|
+
valueBoundsSync(column: string): {
|
|
72
|
+
min: number;
|
|
73
|
+
max: number;
|
|
74
|
+
} | null;
|
|
75
|
+
/** The newest finite value of `column` and its time, past the ring (see LiveBuffer.lastKnown). */
|
|
76
|
+
lastKnownSync(column: string): {
|
|
77
|
+
t: number;
|
|
78
|
+
v: number;
|
|
79
|
+
} | undefined;
|
|
62
80
|
/** Follow cycles on `xKey` (beacon): see LiveBuffer.trackCycles. */
|
|
63
81
|
trackCycles(xKey: string): void;
|
|
64
82
|
/** X span of every sample seen on a tracked key, or null. */
|
|
@@ -73,5 +91,6 @@ export declare class LiveDataSource implements DataSource {
|
|
|
73
91
|
x0: number;
|
|
74
92
|
x1: number;
|
|
75
93
|
}, maxPoints: number): PointColumns;
|
|
94
|
+
readColumns(columns: string[], offset: number, limit: number): Promise<Record<string, Float64Array>>;
|
|
76
95
|
dispose(): Promise<void>;
|
|
77
96
|
}
|
|
@@ -13,6 +13,8 @@ export interface PointColumns {
|
|
|
13
13
|
/** 1 where an independent trace starts (never joined to the previous point). */
|
|
14
14
|
readonly brk?: Uint8Array;
|
|
15
15
|
readonly cols: ReadonlyMap<string, Float64Array>;
|
|
16
|
+
/** Held values (see heldColumns): a column's points always join, however far apart (no gap rule). */
|
|
17
|
+
readonly hold?: boolean;
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
20
|
* Columns known to hold no NaN (marked where they are made, which reads every
|
|
@@ -40,6 +42,15 @@ export declare function columnsToPoints(layers: PointColumns | PointColumns[]):
|
|
|
40
42
|
* comb. A change of pass (laps of a wrapping X) starts a new trace.
|
|
41
43
|
*/
|
|
42
44
|
export declare function multiDecimatedToColumns(rows: MultiDecimationRow[], columns: string[], x0: number, bucket: number): PointColumns;
|
|
45
|
+
/**
|
|
46
|
+
* A live stream's quiet channels, held: each column whose newest value (`last`: a sample or the sender's last known
|
|
47
|
+
* value) is older than the window end `x1`, as one flat segment of that value from max(`x0`, its time) to `end` (the
|
|
48
|
+
* stream's newest time on show). X ascending (the segments' starts, then one shared end); null when none is held.
|
|
49
|
+
*/
|
|
50
|
+
export declare function heldColumns(columns: string[], last: (column: string) => {
|
|
51
|
+
t: number;
|
|
52
|
+
v: number;
|
|
53
|
+
} | undefined, x0: number, x1: number, end: number): PointColumns | null;
|
|
43
54
|
/** X-sorted points up to `cut` (all when undefined): a live trace's display edge. */
|
|
44
55
|
export declare function columnsUpTo(pc: PointColumns, cut: number | undefined): PointColumns;
|
|
45
56
|
/** Points with X in [x0, x1] of an X-sorted set (views, no copy). */
|
|
@@ -5,3 +5,8 @@ import * as duckdb from '@duckdb/duckdb-wasm';
|
|
|
5
5
|
export declare function wasmModuleUrl(url: string): Promise<string>;
|
|
6
6
|
/** Lazily instantiate a single DuckDB-WASM database for this tab. */
|
|
7
7
|
export declare function getDuckDb(): Promise<duckdb.AsyncDuckDB>;
|
|
8
|
+
/** The DuckDB of getDuckDb() with the Parquet extension loaded (the browser build doesn't include it). It is loaded
|
|
9
|
+
* from this site (`/duckdb-ext/v<version>/wasm_eh/parquet.duckdb_extension.wasm`, DuckDB's own signed file copied
|
|
10
|
+
* into public/), never from extensions.duckdb.org. A failed load is retried on the next call.
|
|
11
|
+
* ponytail: same origin as the page; an SDK host that serves the app elsewhere copies that folder too. */
|
|
12
|
+
export declare function getParquetDuckDb(): Promise<duckdb.AsyncDuckDB>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History bytes cached in the origin-private file system (spec D2; plan P4): "<segmentId>/footer" and
|
|
3
|
+
* "<segmentId>/<block>". It keeps the bytes as stored (sealed): the device holds ciphertext only, and a segment id
|
|
4
|
+
* never changes content (a new version is a new segment), so nothing is invalidated. Past the budget (min(2 GiB,
|
|
5
|
+
* 20 % of the origin's quota)) the oldest-written files go, down to 90 %. Without OPFS (old browsers, some private
|
|
6
|
+
* windows) every call is a no-op. ponytail: eviction by write time (FIFO); track reads (LRU) if old ranges miss too often.
|
|
7
|
+
* ponytail: each tab keeps its own index and total, so several tabs writing at once can overshoot the budget until one
|
|
8
|
+
* of them rescans (next page load); share the index through a BroadcastChannel or a Web Lock if that ever matters.
|
|
9
|
+
*/
|
|
10
|
+
export interface BlockCache {
|
|
11
|
+
get(key: string): Promise<Uint8Array | null>;
|
|
12
|
+
put(key: string, bytes: Uint8Array): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/** The part of FileSystemDirectoryHandle the cache uses (a fake in tests). */
|
|
15
|
+
export interface CacheDir {
|
|
16
|
+
getFileHandle(name: string, o?: {
|
|
17
|
+
create?: boolean;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
getFile(): Promise<File>;
|
|
20
|
+
createWritable(): Promise<{
|
|
21
|
+
write(b: BufferSource): Promise<void>;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
abort?(): Promise<void>;
|
|
24
|
+
}>;
|
|
25
|
+
}>;
|
|
26
|
+
removeEntry(name: string): Promise<void>;
|
|
27
|
+
entries(): AsyncIterable<[string, {
|
|
28
|
+
kind: string;
|
|
29
|
+
getFile?: () => Promise<File>;
|
|
30
|
+
}]>;
|
|
31
|
+
}
|
|
32
|
+
export declare function blockCache(root?: () => Promise<CacheDir | null>, budget?: () => Promise<number>): BlockCache;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ExportPart } from '../recordings/export';
|
|
2
|
+
import { type HistoryPiece } from '../data/historyDataSource';
|
|
3
|
+
import { type HistoryReadOptions, type HistorySegmentRef } from './reader';
|
|
4
|
+
export type HistoryExportFormat = 'csv' | 'mf4' | 'parquet';
|
|
5
|
+
/** ponytail: wide tables over unaligned tag groups grow as times x channels (float32 cells); Parquet (long) has no such limit. */
|
|
6
|
+
export declare const MAX_WIDE_CELLS = 25000000;
|
|
7
|
+
/** ponytail: the Parquet file is built in DuckDB-WASM's memory (~4 GB address space); stream row groups out if larger
|
|
8
|
+
* exports are needed. */
|
|
9
|
+
export declare const MAX_PARQUET_VALUES = 30000000;
|
|
10
|
+
/** The shortest decimal that reads back as the same float32 (0.1, not 0.10000000149011612). */
|
|
11
|
+
export declare function f32text(v: number): string;
|
|
12
|
+
/** ISO 8601 UTC with microseconds. */
|
|
13
|
+
export declare function isoMicros(t: number): string;
|
|
14
|
+
/** One row per distinct time across `channels`, NaN where a channel has none. */
|
|
15
|
+
export declare function wideDay(pieces: HistoryPiece[], channels: string[]): {
|
|
16
|
+
t: Float64Array;
|
|
17
|
+
cols: Float32Array[];
|
|
18
|
+
};
|
|
19
|
+
export declare function exportHistoryTo(o: Omit<HistoryReadOptions, 'level'>, format: HistoryExportFormat, write: (part: ExportPart) => void | Promise<void>, opts?: {
|
|
20
|
+
onProgress?: (fraction: number) => void;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
skippedDays: string[];
|
|
23
|
+
missingDays: string[];
|
|
24
|
+
}>;
|
|
25
|
+
/** Days of raw values into one Parquet file: appended to DuckDB `chunk` values at a time, at most `max` values. */
|
|
26
|
+
export declare function parquetTo(raw: HistorySegmentRef[], day: (s: HistorySegmentRef) => Promise<{
|
|
27
|
+
pieces: HistoryPiece[];
|
|
28
|
+
}>, channels: string[], write: (part: ExportPart) => void | Promise<void>, step: (i: number) => void, limits?: {
|
|
29
|
+
max: number;
|
|
30
|
+
chunk: number;
|
|
31
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export declare const KHD1_TRAILER_BYTES = 9;
|
|
2
|
+
export type HistoryKind = 'raw' | 'rollup';
|
|
3
|
+
export type HistoryLevel = 0 | 60 | 3600;
|
|
4
|
+
export interface KhdChannel {
|
|
5
|
+
n: string;
|
|
6
|
+
u: string;
|
|
7
|
+
}
|
|
8
|
+
export interface KhdBlock {
|
|
9
|
+
ch: number[];
|
|
10
|
+
t0: number;
|
|
11
|
+
t1: number;
|
|
12
|
+
off: number;
|
|
13
|
+
len: number;
|
|
14
|
+
rows: number;
|
|
15
|
+
values: number;
|
|
16
|
+
}
|
|
17
|
+
export interface KhdFooter {
|
|
18
|
+
format: 1;
|
|
19
|
+
stream: string;
|
|
20
|
+
day: string;
|
|
21
|
+
tz: string;
|
|
22
|
+
kind: HistoryKind;
|
|
23
|
+
level: HistoryLevel;
|
|
24
|
+
t0: number;
|
|
25
|
+
t1: number;
|
|
26
|
+
channels: KhdChannel[];
|
|
27
|
+
blocks: KhdBlock[];
|
|
28
|
+
}
|
|
29
|
+
/** Which file of a history: what the AAD of its blocks and footer binds. */
|
|
30
|
+
export interface KhdFileId {
|
|
31
|
+
stream: string;
|
|
32
|
+
day: string;
|
|
33
|
+
kind: HistoryKind;
|
|
34
|
+
level: HistoryLevel;
|
|
35
|
+
}
|
|
36
|
+
export declare const blockAad: (f: KhdFileId, index: number | "footer") => string;
|
|
37
|
+
/** The sealed footer's length, from bytes that end where the file ends (at least the 9-byte trailer). */
|
|
38
|
+
export declare function footerLength(tail: Uint8Array): number;
|
|
39
|
+
/** One sealed block (or the footer) opened; WebCrypto throws on a wrong key, file or index. */
|
|
40
|
+
export declare function openSealed(key: CryptoKey, sealed: Uint8Array, f: KhdFileId, index: number | 'footer'): Promise<Uint8Array>;
|
|
41
|
+
export declare function openFooter(key: CryptoKey, sealed: Uint8Array, f: KhdFileId): Promise<KhdFooter>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface RawBlock {
|
|
2
|
+
t: Float64Array;
|
|
3
|
+
cols: Float32Array[];
|
|
4
|
+
}
|
|
5
|
+
export interface RollupBlock {
|
|
6
|
+
t: Float64Array;
|
|
7
|
+
ch: Int32Array;
|
|
8
|
+
min: Float32Array;
|
|
9
|
+
max: Float32Array;
|
|
10
|
+
}
|
|
11
|
+
/** Loads DuckDB-WASM ahead of the first read (the History dialog calls it while the user picks). */
|
|
12
|
+
/** Loads DuckDB and runs one tiny Parquet round trip, so the first real decode doesn't pay the first-query cost
|
|
13
|
+
* (0.3-0.7 s measured, historian-notes/phase4.md). Call it while the user picks channels. */
|
|
14
|
+
export declare function warmHistoryDecoder(): void;
|
|
15
|
+
/** A raw block's times (Unix seconds) and the columns of `channels` (footer indexes; NaN = no value). */
|
|
16
|
+
export declare function decodeRawBlock(bytes: Uint8Array, channels: number[]): Promise<RawBlock>;
|
|
17
|
+
/** Rollup blocks' rows of their `channels`, one result per block, sorted by (ch, t): one query for the whole batch. */
|
|
18
|
+
export declare function decodeRollupBlocks(blocks: Array<{
|
|
19
|
+
bytes: Uint8Array;
|
|
20
|
+
channels: number[];
|
|
21
|
+
}>): Promise<RollupBlock[]>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a time range of a history (spec D1; plan Formats "Reading a range"): one file per local day at the wanted
|
|
3
|
+
* level (else the nearest that day has), its footer (memo, cache, one suffix GET, or the whole file when small), then
|
|
4
|
+
* only the blocks that overlap the range and hold a wanted channel (neighbours in one GET), RANGE_PARALLEL requests at
|
|
5
|
+
* a time; opened with the history key and decoded into pieces. Days need no joining: each block carries its own times.
|
|
6
|
+
*/
|
|
7
|
+
import { type HistoryPiece } from '../data/historyDataSource';
|
|
8
|
+
import { type HistoryKind, type HistoryLevel, type KhdFooter } from './khd1';
|
|
9
|
+
import type { BlockCache } from './blockCache';
|
|
10
|
+
/** One verified file of a history (the latest version of its day, kind and level). */
|
|
11
|
+
export interface HistorySegmentRef {
|
|
12
|
+
id: string;
|
|
13
|
+
day: string;
|
|
14
|
+
kind: HistoryKind;
|
|
15
|
+
level: HistoryLevel;
|
|
16
|
+
bytes: number;
|
|
17
|
+
keyId: string;
|
|
18
|
+
}
|
|
19
|
+
export interface HistoryReadOptions {
|
|
20
|
+
historyStreamId: string;
|
|
21
|
+
/** Segments of the days in range (any levels; the latest version of each). */
|
|
22
|
+
segments: HistorySegmentRef[];
|
|
23
|
+
t0: number;
|
|
24
|
+
t1: number;
|
|
25
|
+
channels: string[];
|
|
26
|
+
level: HistoryLevel;
|
|
27
|
+
key: (keyId: string) => Promise<CryptoKey>;
|
|
28
|
+
/** Bytes [start, end) of a segment's file. */
|
|
29
|
+
fetchRange: (segmentId: string, start: number, end: number, signal?: AbortSignal) => Promise<Uint8Array>;
|
|
30
|
+
cache?: BlockCache;
|
|
31
|
+
/** Opened footers by segment id, kept by the caller between reads of one history. */
|
|
32
|
+
footers?: Map<string, KhdFooter>;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
/** The segments of the same days listed again. A read that fails (a 404 or 403, or no URL for a segment) asks for
|
|
35
|
+
* it once: a newer version may have replaced a segment meanwhile (today's summaries every 15 min, a late-data
|
|
36
|
+
* rebuild), and the older one is deleted. When the listing picks other files the read runs again on them; when it
|
|
37
|
+
* picks the same, the error stands. */
|
|
38
|
+
relist?: () => Promise<HistorySegmentRef[]>;
|
|
39
|
+
/** false: blocks are read from `cache` but not added to it (an export would push out what charts reuse). Default true. */
|
|
40
|
+
cacheWrite?: boolean;
|
|
41
|
+
/** 'gap': a segment whose `fetchRange` throws an error with `code: 'missing'` (its file is gone from storage) is read
|
|
42
|
+
* as no data for its day, listed in `missingDays`, and the rest of the range is read. Default 'error': the read fails. */
|
|
43
|
+
onMissing?: 'error' | 'gap';
|
|
44
|
+
}
|
|
45
|
+
export interface HistoryRange {
|
|
46
|
+
pieces: HistoryPiece[];
|
|
47
|
+
units: Record<string, string>;
|
|
48
|
+
/** The level each day was read at (a day without the wanted level falls back). */
|
|
49
|
+
levels: Record<string, HistoryLevel>;
|
|
50
|
+
/** Days whose file is missing from storage (onMissing 'gap'): read as no data. */
|
|
51
|
+
missingDays: string[];
|
|
52
|
+
}
|
|
53
|
+
export declare const RANGE_PARALLEL = 16;
|
|
54
|
+
export declare const HISTORY_PIXELS = 2000;
|
|
55
|
+
/** The coarsest level giving at least half a bucket per pixel over `spanS` (plan P2). */
|
|
56
|
+
export declare function chooseLevel(spanS: number, pixels?: number): HistoryLevel;
|
|
57
|
+
/** One file per day, in day order: the wanted level, else the nearest the day has (raw expired, today, a failed upload). */
|
|
58
|
+
export declare function pickSegments(segments: HistorySegmentRef[], level: HistoryLevel): HistorySegmentRef[];
|
|
59
|
+
/** f over items, n at a time; results in order. */
|
|
60
|
+
export declare function pool<T, R>(items: T[], n: number, f: (x: T) => Promise<R>): Promise<R[]>;
|
|
61
|
+
/** Byte ranges covering `blocks`: neighbours closer than 64 KiB join, up to 8 MiB per range. */
|
|
62
|
+
export declare function coalesce(blocks: Array<{
|
|
63
|
+
off: number;
|
|
64
|
+
len: number;
|
|
65
|
+
}>): Array<{
|
|
66
|
+
start: number;
|
|
67
|
+
end: number;
|
|
68
|
+
}>;
|
|
69
|
+
type Reader = Pick<HistoryReadOptions, 'historyStreamId' | 'key' | 'fetchRange' | 'cache' | 'footers' | 'signal'>;
|
|
70
|
+
/** A segment's opened footer: the memo, else the cache, else the file (whole when ≤ 512 KiB, kept in `whole`). */
|
|
71
|
+
export declare function readFooter(o: Reader, s: HistorySegmentRef, whole?: Map<string, Uint8Array<ArrayBufferLike>>): Promise<KhdFooter>;
|
|
72
|
+
export declare function readHistoryRange(o: HistoryReadOptions): Promise<HistoryRange>;
|
|
73
|
+
export {};
|
|
@@ -18,6 +18,22 @@ export interface BlockSchema {
|
|
|
18
18
|
export declare function decodeBlockSchema(p: Uint8Array): BlockSchema;
|
|
19
19
|
/** KIND_BLOCK_SCHEMA plaintext with a given id (tests and local publishers; senders use the CRC-32 of the table). */
|
|
20
20
|
export declare function encodeBlockSchema(id: number, channels: BlockChannel[]): Uint8Array;
|
|
21
|
+
/** One channel of a KIND_LAST_VALUES: its last finite value and that value's time (stream seconds). */
|
|
22
|
+
export interface LastValue {
|
|
23
|
+
name: string;
|
|
24
|
+
unit: string;
|
|
25
|
+
t: number;
|
|
26
|
+
v: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The channels with a value of a decrypted KIND_LAST_VALUES plaintext (u8 version 1, u8 flags, u16 reserved,
|
|
30
|
+
* u32 schema id, u32 count, count f64 times then count f64 values, NaN time = none yet; little-endian), named by
|
|
31
|
+
* the schema it refers to; null until that schema has arrived. Throws RangeError if malformed.
|
|
32
|
+
* Normative format: docs/superpowers/specs/2026-10-26-last-values.md; pinned by tests/vectors/last-values-v1.json.
|
|
33
|
+
*/
|
|
34
|
+
export declare function decodeLastValues(p: Uint8Array, schemas: ReadonlyMap<number, BlockSchema>): LastValue[] | null;
|
|
35
|
+
/** KIND_LAST_VALUES plaintext of `t` / `v` in `schema`'s channel order, NaN for none (tests and local publishers). */
|
|
36
|
+
export declare function encodeLastValues(schema: BlockSchema, t: ArrayLike<number>, v: ArrayLike<number>): Uint8Array;
|
|
21
37
|
/** A decrypted KIND_BLOCK plaintext's rows; null until its schema has arrived. Throws RangeError if malformed. */
|
|
22
38
|
export declare function decodeBlock(p: Uint8Array, schemas: ReadonlyMap<number, BlockSchema>): Promise<{
|
|
23
39
|
block: LiveColumnsBlock;
|
|
@@ -79,6 +79,10 @@ export declare class LiveBuffer {
|
|
|
79
79
|
private readonly mirrors;
|
|
80
80
|
/** The last decimation of a window in the history (its samples don't change while it stays put). */
|
|
81
81
|
private historyDec;
|
|
82
|
+
/** The buckets of the row snapshot (see toDataset), kept across calls like timeDec. */
|
|
83
|
+
private snapDec;
|
|
84
|
+
/** Each column's newest finite value and its time, kept past the ring (see lastKnown). */
|
|
85
|
+
private latest;
|
|
82
86
|
/**
|
|
83
87
|
* `capacity`: samples kept (the oldest are dropped past it). Memory grows with
|
|
84
88
|
* the samples actually received; `maxBytes` caps it — once the columns would
|
|
@@ -273,4 +277,34 @@ export declare class LiveBuffer {
|
|
|
273
277
|
* keeps a sample selected until it ages out of the buffer.
|
|
274
278
|
*/
|
|
275
279
|
toDataset(maxRows?: number): Dataset;
|
|
280
|
+
/**
|
|
281
|
+
* At most ~`maxRows` rows of the ring (time ascending): per bucket of time, at its centre (the oldest and
|
|
282
|
+
* newest buckets at the oldest and newest sample's time), a row of each column's min and, where any differs,
|
|
283
|
+
* one of its max. The buckets are kept across calls and fold only the samples pushed since (see timeBuckets),
|
|
284
|
+
* on the absolute grid floor(t / bucket), so a snapshot refreshed while the ring rolls doesn't re-quantise.
|
|
285
|
+
* Null when every sample has the same time.
|
|
286
|
+
*/
|
|
287
|
+
private snapshotRows;
|
|
288
|
+
/** Min/max of `name`'s samples in the ring, or null when it has none (columnBounds reads that as 0..0). */
|
|
289
|
+
valueBounds(name: string): {
|
|
290
|
+
x0: number;
|
|
291
|
+
x1: number;
|
|
292
|
+
} | null;
|
|
293
|
+
/** Whether `name` has had a finite value (a sample or a last known value) since the stream started (or was
|
|
294
|
+
* cleared), in the ring or not. */
|
|
295
|
+
hasValues(name: string): boolean;
|
|
296
|
+
/** How many columns have had a finite value (moves when one gets its first). */
|
|
297
|
+
get valuedCount(): number;
|
|
298
|
+
/** The newest finite value of `name` and its time (a sample, or a last known value when newer), past the ring. */
|
|
299
|
+
lastKnown(name: string): {
|
|
300
|
+
t: number;
|
|
301
|
+
v: number;
|
|
302
|
+
} | undefined;
|
|
303
|
+
/**
|
|
304
|
+
* A channel's last value as the sender knows it (KIND_LAST_VALUES): not a sample (the ring, decimation, cycles
|
|
305
|
+
* and readers of samples never see it), only the value a quiet channel holds. Creates the column (no samples)
|
|
306
|
+
* so the channel is listed; a newer sample always wins.
|
|
307
|
+
*/
|
|
308
|
+
setLastKnown(name: string, unit: string, t: number, v: number): void;
|
|
309
|
+
private noteValue;
|
|
276
310
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { LivePublisher } from './publisher';
|
|
2
|
+
export interface CameraPreset {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
fps: number;
|
|
6
|
+
kbps: number;
|
|
7
|
+
}
|
|
8
|
+
export declare const CAMERA_PRESETS: Record<'720p30' | '1080p30', CameraPreset>;
|
|
9
|
+
/** The low layer's default: what small panels and camera walls draw. */
|
|
10
|
+
export declare const LOW_PRESET: CameraPreset;
|
|
11
|
+
export interface CameraLayerStats {
|
|
12
|
+
fps: number;
|
|
13
|
+
kbps: number;
|
|
14
|
+
viewers: number;
|
|
15
|
+
encoding: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** `error`: the encoder's failure message (encoding has stopped), for the page to show; null while healthy.
|
|
18
|
+
* `low`: the low layer's, null without one (or when this browser can't encode it). */
|
|
19
|
+
export interface CameraStats extends CameraLayerStats {
|
|
20
|
+
dropped: number;
|
|
21
|
+
queue: number;
|
|
22
|
+
error: string | null;
|
|
23
|
+
low: CameraLayerStats | null;
|
|
24
|
+
}
|
|
25
|
+
export interface CameraSenderDeps {
|
|
26
|
+
VideoEncoder?: typeof VideoEncoder;
|
|
27
|
+
/** ms epoch */
|
|
28
|
+
now?: () => number;
|
|
29
|
+
/** The frame source (test seam); defaults to MediaStreamTrackProcessor or a hidden <video>. */
|
|
30
|
+
frames?: (track: MediaStreamTrack) => AsyncIterable<VideoFrame>;
|
|
31
|
+
/** The low layer's downscale (test seam); defaults to drawing onto an OffscreenCanvas. The caller closes the result. */
|
|
32
|
+
scale?: (frame: VideoFrame, width: number, height: number) => VideoFrame;
|
|
33
|
+
}
|
|
34
|
+
type Publisher = Pick<LivePublisher, 'send' | 'viewers' | 'onViewers' | 'limits'>;
|
|
35
|
+
export declare class CameraSender {
|
|
36
|
+
private readonly high;
|
|
37
|
+
private readonly lowOpt;
|
|
38
|
+
private low;
|
|
39
|
+
/** When the low layer last took a frame (ms, `now`), for its frame rate. */
|
|
40
|
+
private lowAt;
|
|
41
|
+
private readonly keyframeMs;
|
|
42
|
+
private readonly Encoder;
|
|
43
|
+
private readonly now;
|
|
44
|
+
private readonly frames;
|
|
45
|
+
private readonly scale;
|
|
46
|
+
private stopped;
|
|
47
|
+
private readonly statsFns;
|
|
48
|
+
private lastStats;
|
|
49
|
+
constructor(publisher: Publisher, opts: {
|
|
50
|
+
preset: CameraPreset;
|
|
51
|
+
keyframeS?: number;
|
|
52
|
+
low?: {
|
|
53
|
+
publisher: Publisher;
|
|
54
|
+
preset?: CameraPreset;
|
|
55
|
+
};
|
|
56
|
+
}, deps?: CameraSenderDeps);
|
|
57
|
+
/** Encode frames from `track` (a getUserMedia video track). Resolves once the encoder is configured;
|
|
58
|
+
* frames then flow until stop(). Throws KymoscopeError('camera_codec_unsupported'). A low layer this
|
|
59
|
+
* browser can't encode is left out (stats().low null); the camera still runs. */
|
|
60
|
+
start(track: MediaStreamTrack): Promise<void>;
|
|
61
|
+
private loop;
|
|
62
|
+
private onFrame;
|
|
63
|
+
stats(): CameraStats;
|
|
64
|
+
/** At most about once a second, while frames flow. */
|
|
65
|
+
onStats(fn: (s: CameraStats) => void): () => void;
|
|
66
|
+
private emitStats;
|
|
67
|
+
/** Stop encoding. The publishers and the track belong to the caller and keep running. */
|
|
68
|
+
stop(): void;
|
|
69
|
+
}
|
|
70
|
+
export {};
|