@moq/watch 0.2.17 → 0.2.18

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.
Files changed (56) hide show
  1. package/README.md +1 -1
  2. package/audio/backend.d.ts +1 -1
  3. package/audio/backend.d.ts.map +1 -1
  4. package/audio/buffer.d.ts +10 -1
  5. package/audio/buffer.d.ts.map +1 -1
  6. package/audio/decoder.d.ts +2 -1
  7. package/audio/decoder.d.ts.map +1 -1
  8. package/audio/emitter.d.ts.map +1 -1
  9. package/audio/mse.d.ts +1 -1
  10. package/audio/mse.d.ts.map +1 -1
  11. package/audio/render.d.ts +6 -1
  12. package/audio/render.d.ts.map +1 -1
  13. package/audio/ring-buffer.d.ts +2 -0
  14. package/audio/ring-buffer.d.ts.map +1 -1
  15. package/audio/shared-ring-buffer.d.ts +8 -1
  16. package/audio/shared-ring-buffer.d.ts.map +1 -1
  17. package/audio/source.d.ts.map +1 -1
  18. package/backend.d.ts +17 -18
  19. package/backend.d.ts.map +1 -1
  20. package/{broadcast-80k1hgfM.js → broadcast-oQKyLEQ1.js} +435 -436
  21. package/broadcast-oQKyLEQ1.js.map +1 -0
  22. package/broadcast.d.ts +3 -3
  23. package/broadcast.d.ts.map +1 -1
  24. package/buffered.d.ts +19 -0
  25. package/buffered.d.ts.map +1 -0
  26. package/element.d.ts +22 -4
  27. package/element.d.ts.map +1 -1
  28. package/element.js +59 -35
  29. package/element.js.map +1 -1
  30. package/index.d.ts +1 -0
  31. package/index.d.ts.map +1 -1
  32. package/index.js +7 -6
  33. package/mse.d.ts.map +1 -1
  34. package/package.json +6 -6
  35. package/support/element.d.ts.map +1 -1
  36. package/support/element.js.map +1 -1
  37. package/support/index.js.map +1 -1
  38. package/sync-D67IxmxJ.js +128 -0
  39. package/sync-D67IxmxJ.js.map +1 -0
  40. package/sync.d.ts +27 -5
  41. package/sync.d.ts.map +1 -1
  42. package/sync.test.d.ts +2 -0
  43. package/sync.test.d.ts.map +1 -0
  44. package/ui/components/latency.d.ts.map +1 -1
  45. package/ui/element.d.ts.map +1 -1
  46. package/ui/element.js +215 -178
  47. package/ui/element.js.map +1 -1
  48. package/video/backend.d.ts +1 -1
  49. package/video/backend.d.ts.map +1 -1
  50. package/video/decoder.d.ts +1 -1
  51. package/video/decoder.d.ts.map +1 -1
  52. package/video/mse.d.ts +3 -3
  53. package/video/mse.d.ts.map +1 -1
  54. package/video/renderer.d.ts.map +1 -1
  55. package/video/source.d.ts.map +1 -1
  56. package/broadcast-80k1hgfM.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-D67IxmxJ.js","names":["#reference","#buffered","#maxBuffer","#buffer","#connection","#update","#runJitter","#runBuffer","#runRange","#minRtt","#setReference","#late","#formatDuration","#latencyCap"],"sources":["../src/sync.ts"],"sourcesContent":["import type * as Moq from \"@moq/net\";\nimport { Time } from \"@moq/net\";\nimport { Effect, Signal } from \"@moq/signals\";\n\n/** A single latency bound: `\"real-time\"` adapts to the RTT; a `Time.Milli` fixes the jitter buffer. */\nexport type Bound = \"real-time\" | Time.Milli;\n\n/**\n * Latency target. A scalar (or `\"real-time\"`) collapses the range and minimizes latency, the live\n * default. An object opens a range `[min, max]`: playback buffers freely between the floor and the\n * ceiling and only skips ahead once latency would exceed the ceiling, so faster-than-real-time\n * frames (e.g. a TTS response with future timestamps) build up instead of being skipped. Both\n * bounds default to `\"real-time\"` when omitted. The ceiling is always finite (no uncapped buffering),\n * so worst case the audio ring drops its oldest samples rather than exhausting memory.\n */\nexport type Latency = Bound | { min?: Bound; max?: Bound };\n\n/** Resolve a {@link Latency} into explicit floor/ceiling bounds (a scalar collapses to `min == max`). */\nexport function latencyBounds(latency: Latency): { min: Bound; max: Bound } {\n\tif (latency === \"real-time\" || typeof latency === \"number\") {\n\t\treturn { min: latency, max: latency };\n\t}\n\treturn { min: latency.min ?? \"real-time\", max: latency.max ?? \"real-time\" };\n}\n\n/** Build a {@link Latency} from explicit bounds, collapsing to a scalar when they're equal. */\nexport function latencyFromBounds(min: Bound, max: Bound): Latency {\n\treturn min === max ? min : { min, max };\n}\n\nconst MIN_JITTER = 20 as Time.Milli;\nconst FALLBACK_JITTER = 100 as Time.Milli;\n\nexport interface SyncProps {\n\t// Latency target: a scalar minimizes (collapsed range), an object opens a range. See {@link Latency}.\n\tlatency?: Latency | Signal<Latency>;\n\n\tconnection?: Signal<Moq.Connection.Established | undefined>;\n\taudio?: Time.Milli | Signal<Time.Milli | undefined>;\n\tvideo?: Time.Milli | Signal<Time.Milli | undefined>;\n}\n\nexport class Sync {\n\t// The earliest time we've received a frame, relative to its timestamp.\n\t// This will keep being updated as we catch up to the live playhead then will be relatively static.\n\t#reference = new Signal<Time.Milli | undefined>(undefined);\n\treadonly reference: Signal<Time.Milli | undefined> = this.#reference;\n\n\t// The latency target: a scalar minimizes (collapsed range), an object opens a range. See {@link Latency}.\n\tlatency: Signal<Latency>;\n\n\t// The jitter buffer in milliseconds (always numeric).\n\t// In \"real-time\" mode this is updated automatically from RTT.\n\t// When the floor is a number, jitter equals that number.\n\tjitter: Signal<Time.Milli>;\n\n\t// Any additional delay required for audio or video.\n\taudio: Signal<Time.Milli | undefined>;\n\tvideo: Signal<Time.Milli | undefined>;\n\n\t// Derived: true when the ceiling sits above the floor. Buffered playback lets the reference\n\t// stay anchored so future-dated frames build up a buffer, re-anchoring (skipping ahead) only\n\t// when latency would exceed the ceiling. See `reset()`.\n\t#buffered = new Signal<boolean>(false);\n\treadonly buffered: Signal<boolean> = this.#buffered;\n\n\t// Derived cap on buffered audio (ms), consumed by the audio ring to size itself. Always finite.\n\t#maxBuffer = new Signal<Time.Milli>(Time.Milli.zero);\n\treadonly maxBuffer: Signal<Time.Milli> = this.#maxBuffer;\n\n\t// The total buffer required: jitter + max(audio, video).\n\t#buffer = new Signal<Time.Milli>(Time.Milli.zero);\n\treadonly buffer: Signal<Time.Milli> = this.#buffer;\n\n\t// A ghetto way to learn when the reference/buffer changes.\n\t// There's probably a way to use Effect, but lets keep it simple for now.\n\t#update: PromiseWithResolvers<void>;\n\n\t// The media timestamp of the most recently received frame.\n\treadonly timestamp = new Signal<Time.Milli | undefined>(undefined);\n\n\t// Per-label late-frame tracking: accumulate count and max lateness, flush on recovery.\n\t#late = new Map<string, { count: number; maxMs: number }>();\n\n\t// The connection used for \"real-time\" jitter: PROBE supplies RTT.\n\t#connection?: Signal<Moq.Connection.Established | undefined>;\n\n\t// Minimum RTT seen, used as the baseline for jitter calculation.\n\t// Avoids inflating jitter due to bufferbloat.\n\t#minRtt: number | undefined;\n\n\tsignals = new Effect();\n\n\tconstructor(props?: SyncProps) {\n\t\tthis.latency = Signal.from(props?.latency ?? (\"real-time\" as Latency));\n\t\tthis.jitter = new Signal<Time.Milli>(FALLBACK_JITTER);\n\t\tthis.#connection = props?.connection;\n\t\tthis.audio = Signal.from(props?.audio);\n\t\tthis.video = Signal.from(props?.video);\n\n\t\tthis.#update = Promise.withResolvers();\n\n\t\tthis.signals.run(this.#runJitter.bind(this));\n\t\tthis.signals.run(this.#runBuffer.bind(this));\n\t\tthis.signals.run(this.#runRange.bind(this));\n\t}\n\n\t// Derive `buffered` / `maxBuffer` from the floor (`buffer`) and the ceiling (the `max` bound).\n\t#runRange(effect: Effect): void {\n\t\tconst { max } = latencyBounds(effect.get(this.latency));\n\t\tconst floor = effect.get(this.buffer);\n\n\t\tif (max === \"real-time\") {\n\t\t\t// Ceiling tracks the floor: minimize latency, the live default.\n\t\t\tthis.#buffered.set(false);\n\t\t\tthis.#maxBuffer.set(floor);\n\t\t} else {\n\t\t\t// Buffered only when the ceiling is above the floor; otherwise it collapses to minimize.\n\t\t\tthis.#buffered.set(max > floor);\n\t\t\tthis.#maxBuffer.set(Time.Milli.max(max, floor));\n\t\t}\n\t}\n\n\t// The maximum total latency (lookahead + floor) we tolerate before re-anchoring, in ms.\n\t// Used by `received()` to decide when to skip ahead.\n\t#latencyCap(): Time.Milli {\n\t\tconst { max } = latencyBounds(this.latency.peek());\n\t\tconst floor = this.#buffer.peek();\n\t\tif (max === \"real-time\") return floor;\n\t\treturn Time.Milli.max(max, floor);\n\t}\n\n\t#runJitter(effect: Effect): void {\n\t\tconst { min } = latencyBounds(effect.get(this.latency));\n\n\t\tif (typeof min === \"number\") {\n\t\t\t// Fixed mode: the floor value is the jitter.\n\t\t\tthis.#minRtt = undefined;\n\t\t\tthis.jitter.set(min);\n\t\t\treturn;\n\t\t}\n\n\t\t// \"real-time\" mode: compute jitter from RTT on the established connection.\n\t\tconst conn = this.#connection ? effect.get(this.#connection) : undefined;\n\t\tconst rttSignal = conn?.rtt;\n\t\tconst rtt = rttSignal ? effect.get(rttSignal) : undefined;\n\t\tif (rtt !== undefined) {\n\t\t\t// Track minimum RTT as baseline, ignoring bufferbloat.\n\t\t\tthis.#minRtt = this.#minRtt !== undefined ? Math.min(this.#minRtt, rtt) : rtt;\n\n\t\t\t// Buffer enough for a retransmit (1 RTT for ACK + retransmit).\n\t\t\tconst jitter = Math.max(MIN_JITTER, this.#minRtt * 1.25) as Time.Milli;\n\t\t\tthis.jitter.set(jitter);\n\t\t\treturn;\n\t\t}\n\n\t\t// No RTT available: fall back to static default.\n\t\tthis.#minRtt = undefined;\n\t\tthis.jitter.set(FALLBACK_JITTER);\n\t}\n\n\t#runBuffer(effect: Effect): void {\n\t\tconst jitter = effect.get(this.jitter);\n\t\tconst video = effect.get(this.video) ?? Time.Milli.zero;\n\t\tconst audio = effect.get(this.audio) ?? Time.Milli.zero;\n\n\t\tconst buffer = Time.Milli.add(Time.Milli.max(video, audio), jitter);\n\t\tthis.#buffer.set(buffer);\n\n\t\tthis.#update.resolve();\n\t\tthis.#update = Promise.withResolvers();\n\t}\n\n\t// Fold a newly received frame into the reference. The reference anchors playback to the\n\t// wall clock; we lower it (skip ahead) only when keeping it would push latency past the cap.\n\treceived(timestamp: Time.Milli, label = \"\"): void {\n\t\tthis.timestamp.update((current) => (current === undefined || timestamp > current ? timestamp : current));\n\t\tconst now = Time.Milli.now();\n\t\tconst ref = Time.Milli.sub(now, timestamp);\n\t\tconst currentRef = this.#reference.peek();\n\n\t\t// First frame anchors the reference.\n\t\tif (currentRef === undefined) {\n\t\t\tthis.#setReference(ref);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if `wait()` would not sleep at all.\n\t\t// NOTE: We check here instead of in `wait()` so we can identify when frames are received late.\n\t\t// Otherwise, chained `wait()` calls would cause a false-positive during CPU starvation.\n\t\tconst floor = this.#buffer.peek();\n\t\tconst sleep = Time.Milli.add(Time.Milli.sub(currentRef, ref), floor);\n\t\tif (sleep < 0) {\n\t\t\tconst entry = this.#late.get(label);\n\t\t\tif (entry) {\n\t\t\t\tentry.count++;\n\t\t\t\tentry.maxMs = Math.max(entry.maxMs, -sleep);\n\t\t\t} else {\n\t\t\t\tthis.#late.set(label, { count: 1, maxMs: -sleep });\n\t\t\t}\n\t\t} else {\n\t\t\tconst entry = this.#late.get(label);\n\t\t\tif (entry) {\n\t\t\t\tconst prefix = label ? `sync[${label}]` : \"sync\";\n\t\t\t\tconst behind = Sync.#formatDuration(entry.maxMs);\n\t\t\t\tconsole.debug(`${prefix}: ${entry.count} late frame(s), max ${behind} behind`);\n\t\t\t\tthis.#late.delete(label);\n\t\t\t}\n\t\t}\n\n\t\t// Frame isn't earlier than the anchor: it can't lower latency, so keep the reference.\n\t\tif (ref >= currentRef) return;\n\n\t\t// Frame is earlier (more lookahead). `sleep` is the latency keeping the anchor would impose.\n\t\tconst cap = this.#latencyCap();\n\t\tif (sleep <= cap) return; // within budget: let the buffer grow instead of skipping ahead\n\n\t\t// Over the cap: re-anchor down so the resulting latency is exactly the cap.\n\t\tthis.#setReference(Time.Milli.add(ref, (cap - floor) as Time.Milli));\n\t}\n\n\t#setReference(ref: Time.Milli): void {\n\t\tthis.#reference.set(ref);\n\t\tthis.#update.resolve();\n\t\tthis.#update = Promise.withResolvers();\n\t}\n\n\t// Re-anchor playback to the next frame received. Call this at an utterance boundary\n\t// in buffered mode (typically alongside flushing the audio buffer) so the new content\n\t// plays from its own first frame instead of inheriting the previous reference.\n\treset(): void {\n\t\tthis.#reference.set(undefined);\n\t\tthis.#late.clear();\n\t\tthis.#update.resolve();\n\t\tthis.#update = Promise.withResolvers();\n\t}\n\n\t// The PTS that should be rendering right now, derived from the reference + buffer.\n\t// Returns undefined if no frames have been received yet.\n\tnow(): Time.Milli | undefined {\n\t\tconst reference = this.#reference.peek();\n\t\tif (reference === undefined) return undefined;\n\t\treturn Time.Milli.sub(Time.Milli.sub(Time.Milli.now(), reference), this.#buffer.peek());\n\t}\n\n\t// Sleep until it's time to render this frame.\n\tasync wait(timestamp: Time.Milli): Promise<void> {\n\t\tconst reference = this.#reference.peek();\n\t\tif (reference === undefined) {\n\t\t\tthrow new Error(\"reference not set; call update() first\");\n\t\t}\n\n\t\tfor (;;) {\n\t\t\t// Sleep until it's time to decode the next frame.\n\t\t\t// NOTE: This function runs in parallel for each frame.\n\t\t\tconst now = Time.Milli.now();\n\t\t\tconst ref = Time.Milli.sub(now, timestamp);\n\n\t\t\tconst currentRef = this.#reference.peek();\n\t\t\tif (currentRef === undefined) return;\n\n\t\t\tconst sleep = Time.Milli.add(Time.Milli.sub(currentRef, ref), this.#buffer.peek());\n\t\t\tif (sleep <= 0) return;\n\n\t\t\t// Skip setTimeout for small sleeps; the timer resolution (~4ms) would overshoot.\n\t\t\tif (sleep < 5) return;\n\n\t\t\tconst wait = new Promise((resolve) => setTimeout(resolve, sleep)).then(() => true);\n\n\t\t\tconst ok = await Promise.race([this.#update.promise, wait]);\n\t\t\tif (ok) return;\n\t\t}\n\t}\n\n\tstatic #formatDuration(ms: number): string {\n\t\tms = Math.round(ms);\n\t\tif (ms < 1000) return `${ms}ms`;\n\t\tconst s = ms / 1000;\n\t\tif (s < 60) return `${Math.round(s * 10) / 10}s`;\n\t\tconst m = s / 60;\n\t\treturn `${Math.round(m * 10) / 10}m`;\n\t}\n\n\tclose() {\n\t\tthis.signals.close();\n\t}\n}\n"],"mappings":"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"}
package/sync.d.ts CHANGED
@@ -1,8 +1,27 @@
1
1
  import type * as Moq from "@moq/net";
2
2
  import { Time } from "@moq/net";
3
- import { Signal } from "@moq/signals";
4
- /** Latency: `"real-time"` auto-computes jitter from RTT; a `Time.Milli` sets a fixed jitter. */
5
- export type Latency = "real-time" | Time.Milli;
3
+ import { Effect, Signal } from "@moq/signals";
4
+ /** A single latency bound: `"real-time"` adapts to the RTT; a `Time.Milli` fixes the jitter buffer. */
5
+ export type Bound = "real-time" | Time.Milli;
6
+ /**
7
+ * Latency target. A scalar (or `"real-time"`) collapses the range and minimizes latency, the live
8
+ * default. An object opens a range `[min, max]`: playback buffers freely between the floor and the
9
+ * ceiling and only skips ahead once latency would exceed the ceiling, so faster-than-real-time
10
+ * frames (e.g. a TTS response with future timestamps) build up instead of being skipped. Both
11
+ * bounds default to `"real-time"` when omitted. The ceiling is always finite (no uncapped buffering),
12
+ * so worst case the audio ring drops its oldest samples rather than exhausting memory.
13
+ */
14
+ export type Latency = Bound | {
15
+ min?: Bound;
16
+ max?: Bound;
17
+ };
18
+ /** Resolve a {@link Latency} into explicit floor/ceiling bounds (a scalar collapses to `min == max`). */
19
+ export declare function latencyBounds(latency: Latency): {
20
+ min: Bound;
21
+ max: Bound;
22
+ };
23
+ /** Build a {@link Latency} from explicit bounds, collapsing to a scalar when they're equal. */
24
+ export declare function latencyFromBounds(min: Bound, max: Bound): Latency;
6
25
  export interface SyncProps {
7
26
  latency?: Latency | Signal<Latency>;
8
27
  connection?: Signal<Moq.Connection.Established | undefined>;
@@ -16,11 +35,14 @@ export declare class Sync {
16
35
  jitter: Signal<Time.Milli>;
17
36
  audio: Signal<Time.Milli | undefined>;
18
37
  video: Signal<Time.Milli | undefined>;
38
+ readonly buffered: Signal<boolean>;
39
+ readonly maxBuffer: Signal<Time.Milli>;
19
40
  readonly buffer: Signal<Time.Milli>;
20
- readonly timestamp: Moq.Signals.Signal<Moq.Time.Milli | undefined>;
21
- signals: Moq.Signals.Effect;
41
+ readonly timestamp: Signal<Time.Milli | undefined>;
42
+ signals: Effect;
22
43
  constructor(props?: SyncProps);
23
44
  received(timestamp: Time.Milli, label?: string): void;
45
+ reset(): void;
24
46
  now(): Time.Milli | undefined;
25
47
  wait(timestamp: Time.Milli): Promise<void>;
26
48
  close(): void;
package/sync.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAU,MAAM,EAAE,MAAM,cAAc,CAAC;AAE9C,gGAAgG;AAChG,MAAM,MAAM,OAAO,GAAG,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC;AAK/C,MAAM,WAAW,SAAS;IACzB,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAC5D,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACpD,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;CACpD;AAED,qBAAa,IAAI;;IAIhB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAmB;IAGrE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAKzB,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAG3B,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAItC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAgB;IAOnD,QAAQ,CAAC,SAAS,iDAAiD;IAYnE,OAAO,qBAAgB;gBAEX,KAAK,CAAC,EAAE,SAAS;IAuD7B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,SAAK,GAAG,IAAI;IA0CjD,GAAG,IAAI,IAAI,CAAC,KAAK,GAAG,SAAS;IAOvB,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAqChD,KAAK;CAGL"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE9C,uGAAuG;AACvG,MAAM,MAAM,KAAK,GAAG,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC;IAAC,GAAG,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAE3D,yGAAyG;AACzG,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG;IAAE,GAAG,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,KAAK,CAAA;CAAE,CAK1E;AAED,+FAA+F;AAC/F,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,GAAG,OAAO,CAEjE;AAKD,MAAM,WAAW,SAAS;IAEzB,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAEpC,UAAU,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAC5D,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACpD,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;CACpD;AAED,qBAAa,IAAI;;IAIhB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAmB;IAGrE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAKzB,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAG3B,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAMtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAkB;IAIpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAmB;IAIzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAgB;IAOnD,QAAQ,CAAC,SAAS,iCAAiD;IAYnE,OAAO,SAAgB;IAEvB,YAAY,KAAK,CAAC,EAAE,SAAS,EAY5B;IAsED,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,SAAK,GAAG,IAAI,CA4ChD;IAWD,KAAK,IAAI,IAAI,CAKZ;IAID,GAAG,IAAI,IAAI,CAAC,KAAK,GAAG,SAAS,CAI5B;IAGK,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B/C;IAWD,KAAK,SAEJ;CACD"}
package/sync.test.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sync.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.test.d.ts","sourceRoot":"","sources":["../src/sync.test.ts"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"latency.d.ts","sourceRoot":"","sources":["../../../src/ui/components/latency.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAe1C,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,WAAW,CAmDvE"}
1
+ {"version":3,"file":"latency.d.ts","sourceRoot":"","sources":["../../../src/ui/components/latency.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAgB1C,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,WAAW,CAmDvE"}
@@ -1 +1 @@
1
- {"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../../src/ui/element.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;;;IAiBlD,iBAAiB;IASjB,oBAAoB;CAgFpB;AAID,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,cAAc,EAAE,UAAU,CAAC;KAC3B;CACD"}
1
+ {"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../../src/ui/element.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;;IAMlD,cASC;IAED,iBAAiB,SAOhB;IAED,oBAAoB,SAInB;CAgKD;AAID,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,cAAc,EAAE,UAAU,CAAC;KAC3B;CACD"}