@moq/watch 0.3.1 → 0.4.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.
Files changed (73) hide show
  1. package/README.md +1 -2
  2. package/audio/decoder.d.ts +25 -15
  3. package/audio/decoder.d.ts.map +1 -1
  4. package/audio/emitter.d.ts +13 -10
  5. package/audio/emitter.d.ts.map +1 -1
  6. package/audio/index.d.ts +0 -2
  7. package/audio/index.d.ts.map +1 -1
  8. package/audio/source.d.ts +21 -17
  9. package/audio/source.d.ts.map +1 -1
  10. package/audio/unlock.d.ts +20 -0
  11. package/audio/unlock.d.ts.map +1 -0
  12. package/audio/unlock.test.d.ts +2 -0
  13. package/audio/unlock.test.d.ts.map +1 -0
  14. package/broadcast.d.ts +27 -57
  15. package/broadcast.d.ts.map +1 -1
  16. package/element.d.ts +42 -12
  17. package/element.d.ts.map +1 -1
  18. package/element.js +146 -90
  19. package/element.js.map +1 -1
  20. package/index.d.ts +9 -6
  21. package/index.d.ts.map +1 -1
  22. package/index.js +24 -5
  23. package/index.js.map +1 -0
  24. package/package.json +5 -6
  25. package/source-CWtgjSC_.js +1359 -0
  26. package/source-CWtgjSC_.js.map +1 -0
  27. package/support/element.d.ts.map +1 -1
  28. package/support/element.js.map +1 -1
  29. package/support/index.d.ts +3 -3
  30. package/support/index.d.ts.map +1 -1
  31. package/support/index.js +18 -17
  32. package/support/index.js.map +1 -1
  33. package/sync-zdwVEj9u.js +129 -0
  34. package/sync-zdwVEj9u.js.map +1 -0
  35. package/sync.d.ts +19 -18
  36. package/sync.d.ts.map +1 -1
  37. package/ui/components/buffer-control.d.ts +1 -1
  38. package/ui/components/buffer-control.d.ts.map +1 -1
  39. package/ui/components/fullscreen-button.d.ts +1 -2
  40. package/ui/components/fullscreen-button.d.ts.map +1 -1
  41. package/ui/components/play-pause.d.ts +1 -1
  42. package/ui/components/play-pause.d.ts.map +1 -1
  43. package/ui/element.d.ts.map +1 -1
  44. package/ui/element.js +269 -265
  45. package/ui/element.js.map +1 -1
  46. package/ui/fullscreen.d.ts +1 -2
  47. package/ui/fullscreen.d.ts.map +1 -1
  48. package/video/decoder.d.ts +32 -16
  49. package/video/decoder.d.ts.map +1 -1
  50. package/video/index.d.ts +0 -2
  51. package/video/index.d.ts.map +1 -1
  52. package/video/renderer.d.ts +14 -17
  53. package/video/renderer.d.ts.map +1 -1
  54. package/video/source.d.ts +23 -20
  55. package/video/source.d.ts.map +1 -1
  56. package/audio/backend.d.ts +0 -26
  57. package/audio/backend.d.ts.map +0 -1
  58. package/audio/mse.d.ts +0 -22
  59. package/audio/mse.d.ts.map +0 -1
  60. package/backend.d.ts +0 -65
  61. package/backend.d.ts.map +0 -1
  62. package/broadcast-C2qofUBv.js +0 -1701
  63. package/broadcast-C2qofUBv.js.map +0 -1
  64. package/buffered.d.ts +0 -19
  65. package/buffered.d.ts.map +0 -1
  66. package/mse.d.ts +0 -19
  67. package/mse.d.ts.map +0 -1
  68. package/sync-D67IxmxJ.js +0 -128
  69. package/sync-D67IxmxJ.js.map +0 -1
  70. package/video/backend.d.ts +0 -25
  71. package/video/backend.d.ts.map +0 -1
  72. package/video/mse.d.ts +0 -23
  73. package/video/mse.d.ts.map +0 -1
package/support/index.js CHANGED
@@ -1,5 +1,6 @@
1
+ import { Connection as e } from "@moq/net";
1
2
  //#region src/support/index.ts
2
- var e = navigator.userAgent.toLowerCase().includes("firefox"), t = {
3
+ var t = navigator.userAgent.toLowerCase().includes("firefox"), n = {
3
4
  aac: "mp4a.40.2",
4
5
  opus: "opus",
5
6
  av1: "av01.0.08M.08",
@@ -8,49 +9,49 @@ var e = navigator.userAgent.toLowerCase().includes("firefox"), t = {
8
9
  vp9: "vp09.00.10.08",
9
10
  vp8: "vp8"
10
11
  };
11
- async function n(e) {
12
+ async function r(e) {
12
13
  return globalThis.AudioDecoder ? (await AudioDecoder.isConfigSupported({
13
- codec: t[e],
14
+ codec: n[e],
14
15
  numberOfChannels: 2,
15
16
  sampleRate: 48e3
16
17
  })).supported === !0 : !1;
17
18
  }
18
- async function r(n) {
19
+ async function i(e) {
19
20
  let r = await VideoDecoder.isConfigSupported({
20
- codec: t[n],
21
+ codec: n[e],
21
22
  hardwareAcceleration: "prefer-software"
22
23
  }), i = await VideoDecoder.isConfigSupported({
23
- codec: t[n],
24
+ codec: n[e],
24
25
  hardwareAcceleration: "prefer-hardware"
25
26
  });
26
27
  return {
27
- hardware: e || i.config?.hardwareAcceleration !== "prefer-hardware" ? void 0 : i.supported === !0,
28
+ hardware: t || i.config?.hardwareAcceleration !== "prefer-hardware" ? void 0 : i.supported === !0,
28
29
  software: r.supported === !0
29
30
  };
30
31
  }
31
- async function i() {
32
+ async function a() {
32
33
  return {
33
- webtransport: typeof WebTransport < "u" ? e ? "partial" : "full" : "partial",
34
+ webtransport: e.isWebTransportSupported() ? "full" : "partial",
34
35
  audio: {
35
36
  decoding: {
36
- aac: await n("aac"),
37
- opus: await n("opus") ? "full" : "partial"
37
+ aac: await r("aac"),
38
+ opus: await r("opus") ? "full" : "partial"
38
39
  },
39
40
  render: typeof AudioContext < "u" && typeof AudioBufferSourceNode < "u"
40
41
  },
41
42
  video: {
42
43
  decoding: typeof VideoDecoder < "u" ? {
43
- h264: await r("h264"),
44
- h265: await r("h265"),
45
- vp8: await r("vp8"),
46
- vp9: await r("vp9"),
47
- av1: await r("av1")
44
+ h264: await i("h264"),
45
+ h265: await i("h265"),
46
+ vp8: await i("vp8"),
47
+ vp9: await i("vp9"),
48
+ av1: await i("av1")
48
49
  } : void 0,
49
50
  render: typeof OffscreenCanvas < "u" && typeof CanvasRenderingContext2D < "u"
50
51
  }
51
52
  };
52
53
  }
53
54
  //#endregion
54
- export { i as isSupported };
55
+ export { a as isSupported };
55
56
 
56
57
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/support/index.ts"],"sourcesContent":["// https://bugzilla.mozilla.org/show_bug.cgi?id=1967793\nconst isFirefox = navigator.userAgent.toLowerCase().includes(\"firefox\");\n\nexport type Partial = \"full\" | \"partial\" | \"none\";\n\nexport type Codec = {\n\thardware?: boolean; // undefined when we can't detect hardware acceleration\n\tsoftware: boolean;\n};\n\nexport type Audio = {\n\taac: boolean;\n\topus: Partial;\n};\n\nexport type Video = {\n\th264: Codec;\n\th265: Codec;\n\tvp8: Codec;\n\tvp9: Codec;\n\tav1: Codec;\n};\n\nexport type Full = {\n\twebtransport: Partial;\n\taudio: {\n\t\tdecoding: Audio;\n\t\trender: boolean;\n\t};\n\tvideo: {\n\t\tdecoding: Video | undefined;\n\t\trender: boolean;\n\t};\n};\n\n// Pick a codec string for each codec.\n// This is not strictly correct, as browsers may not support every profile or level.\nconst CODECS = {\n\taac: \"mp4a.40.2\",\n\topus: \"opus\",\n\tav1: \"av01.0.08M.08\",\n\th264: \"avc1.640028\",\n\th265: \"hev1.1.6.L93.B0\",\n\tvp9: \"vp09.00.10.08\",\n\tvp8: \"vp8\",\n};\n\nasync function audioDecoderSupported(codec: keyof typeof CODECS): Promise<boolean> {\n\tif (!globalThis.AudioDecoder) return false;\n\n\tconst res = await AudioDecoder.isConfigSupported({\n\t\tcodec: CODECS[codec],\n\t\tnumberOfChannels: 2,\n\t\tsampleRate: 48000,\n\t});\n\n\treturn res.supported === true;\n}\n\nasync function videoDecoderSupported(codec: keyof typeof CODECS): Promise<Codec> {\n\tconst software = await VideoDecoder.isConfigSupported({\n\t\tcodec: CODECS[codec],\n\t\thardwareAcceleration: \"prefer-software\",\n\t});\n\n\tconst hardware = await VideoDecoder.isConfigSupported({\n\t\tcodec: CODECS[codec],\n\t\thardwareAcceleration: \"prefer-hardware\",\n\t});\n\n\t// We can't reliably detect hardware encoding on Firefox: https://github.com/w3c/webcodecs/issues/896\n\tconst unknown = isFirefox || hardware.config?.hardwareAcceleration !== \"prefer-hardware\";\n\n\treturn {\n\t\thardware: unknown ? undefined : hardware.supported === true,\n\t\tsoftware: software.supported === true,\n\t};\n}\n\nexport async function isSupported(): Promise<Full> {\n\treturn {\n\t\t// Firefox's WebTransport drops server-initiated bidi streams, so we force the\n\t\t// WebSocket fallback. Report \"partial\" to surface the degraded path in UI.\n\t\twebtransport: typeof WebTransport !== \"undefined\" ? (isFirefox ? \"partial\" : \"full\") : \"partial\",\n\t\taudio: {\n\t\t\tdecoding: {\n\t\t\t\taac: await audioDecoderSupported(\"aac\"),\n\t\t\t\topus: (await audioDecoderSupported(\"opus\")) ? \"full\" : \"partial\",\n\t\t\t},\n\t\t\trender: typeof AudioContext !== \"undefined\" && typeof AudioBufferSourceNode !== \"undefined\",\n\t\t},\n\t\tvideo: {\n\t\t\tdecoding:\n\t\t\t\ttypeof VideoDecoder !== \"undefined\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\th264: await videoDecoderSupported(\"h264\"),\n\t\t\t\t\t\t\th265: await videoDecoderSupported(\"h265\"),\n\t\t\t\t\t\t\tvp8: await videoDecoderSupported(\"vp8\"),\n\t\t\t\t\t\t\tvp9: await videoDecoderSupported(\"vp9\"),\n\t\t\t\t\t\t\tav1: await videoDecoderSupported(\"av1\"),\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\t\t\trender: typeof OffscreenCanvas !== \"undefined\" && typeof CanvasRenderingContext2D !== \"undefined\",\n\t\t},\n\t};\n}\n"],"mappings":";AACA,IAAM,IAAY,UAAU,UAAU,YAAY,CAAC,CAAC,SAAS,SAAS,GAoChE,IAAS;CACd,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,MAAM;CACN,KAAK;CACL,KAAK;AACN;AAEA,eAAe,EAAsB,GAA8C;CASlF,OARK,WAAW,gBAQT,MANW,aAAa,kBAAkB;EAChD,OAAO,EAAO;EACd,kBAAkB;EAClB,YAAY;CACb,CAAC,EAAA,CAEU,cAAc,KARY;AAStC;AAEA,eAAe,EAAsB,GAA4C;CAChF,IAAM,IAAW,MAAM,aAAa,kBAAkB;EACrD,OAAO,EAAO;EACd,sBAAsB;CACvB,CAAC,GAEK,IAAW,MAAM,aAAa,kBAAkB;EACrD,OAAO,EAAO;EACd,sBAAsB;CACvB,CAAC;CAKD,OAAO;EACN,UAHe,KAAa,EAAS,QAAQ,yBAAyB,oBAGlD,KAAA,IAAY,EAAS,cAAc;EACvD,UAAU,EAAS,cAAc;CAClC;AACD;AAEA,eAAsB,IAA6B;CAClD,OAAO;EAGN,cAAc,OAAO,eAAiB,MAAe,IAAY,YAAY,SAAU;EACvF,OAAO;GACN,UAAU;IACT,KAAK,MAAM,EAAsB,KAAK;IACtC,MAAO,MAAM,EAAsB,MAAM,IAAK,SAAS;GACxD;GACA,QAAQ,OAAO,eAAiB,OAAe,OAAO,wBAA0B;EACjF;EACA,OAAO;GACN,UACC,OAAO,eAAiB,MACrB;IACA,MAAM,MAAM,EAAsB,MAAM;IACxC,MAAM,MAAM,EAAsB,MAAM;IACxC,KAAK,MAAM,EAAsB,KAAK;IACtC,KAAK,MAAM,EAAsB,KAAK;IACtC,KAAK,MAAM,EAAsB,KAAK;GACvC,IACC,KAAA;GACJ,QAAQ,OAAO,kBAAoB,OAAe,OAAO,2BAA6B;EACvF;CACD;AACD"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/support/index.ts"],"sourcesContent":["/**\n * Feature detection for playback: which codecs decode, and whether WebTransport works.\n *\n * @module\n */\nimport { Connection } from \"@moq/net\";\n\n// https://bugzilla.mozilla.org/show_bug.cgi?id=1967793\nconst isFirefox = navigator.userAgent.toLowerCase().includes(\"firefox\");\n\nexport type Level = \"full\" | \"partial\" | \"none\";\n\nexport type Codec = {\n\thardware?: boolean; // undefined when we can't detect hardware acceleration\n\tsoftware: boolean;\n};\n\nexport type Audio = {\n\taac: boolean;\n\topus: Level;\n};\n\nexport type Video = {\n\th264: Codec;\n\th265: Codec;\n\tvp8: Codec;\n\tvp9: Codec;\n\tav1: Codec;\n};\n\nexport type Full = {\n\twebtransport: Level;\n\taudio: {\n\t\tdecoding: Audio;\n\t\trender: boolean;\n\t};\n\tvideo: {\n\t\tdecoding: Video | undefined;\n\t\trender: boolean;\n\t};\n};\n\n// Pick a codec string for each codec.\n// This is not strictly correct, as browsers may not support every profile or level.\nconst CODECS = {\n\taac: \"mp4a.40.2\",\n\topus: \"opus\",\n\tav1: \"av01.0.08M.08\",\n\th264: \"avc1.640028\",\n\th265: \"hev1.1.6.L93.B0\",\n\tvp9: \"vp09.00.10.08\",\n\tvp8: \"vp8\",\n};\n\nasync function audioDecoderSupported(codec: keyof typeof CODECS): Promise<boolean> {\n\tif (!globalThis.AudioDecoder) return false;\n\n\tconst res = await AudioDecoder.isConfigSupported({\n\t\tcodec: CODECS[codec],\n\t\tnumberOfChannels: 2,\n\t\tsampleRate: 48000,\n\t});\n\n\treturn res.supported === true;\n}\n\nasync function videoDecoderSupported(codec: keyof typeof CODECS): Promise<Codec> {\n\tconst software = await VideoDecoder.isConfigSupported({\n\t\tcodec: CODECS[codec],\n\t\thardwareAcceleration: \"prefer-software\",\n\t});\n\n\tconst hardware = await VideoDecoder.isConfigSupported({\n\t\tcodec: CODECS[codec],\n\t\thardwareAcceleration: \"prefer-hardware\",\n\t});\n\n\t// We can't reliably detect hardware encoding on Firefox: https://github.com/w3c/webcodecs/issues/896\n\tconst unknown = isFirefox || hardware.config?.hardwareAcceleration !== \"prefer-hardware\";\n\n\treturn {\n\t\thardware: unknown ? undefined : hardware.supported === true,\n\t\tsoftware: software.supported === true,\n\t};\n}\n\nexport async function isSupported(): Promise<Full> {\n\treturn {\n\t\t// Report \"partial\" when @moq/net forces the WebSocket fallback.\n\t\twebtransport: Connection.isWebTransportSupported() ? \"full\" : \"partial\",\n\t\taudio: {\n\t\t\tdecoding: {\n\t\t\t\taac: await audioDecoderSupported(\"aac\"),\n\t\t\t\topus: (await audioDecoderSupported(\"opus\")) ? \"full\" : \"partial\",\n\t\t\t},\n\t\t\trender: typeof AudioContext !== \"undefined\" && typeof AudioBufferSourceNode !== \"undefined\",\n\t\t},\n\t\tvideo: {\n\t\t\tdecoding:\n\t\t\t\ttypeof VideoDecoder !== \"undefined\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\th264: await videoDecoderSupported(\"h264\"),\n\t\t\t\t\t\t\th265: await videoDecoderSupported(\"h265\"),\n\t\t\t\t\t\t\tvp8: await videoDecoderSupported(\"vp8\"),\n\t\t\t\t\t\t\tvp9: await videoDecoderSupported(\"vp9\"),\n\t\t\t\t\t\t\tav1: await videoDecoderSupported(\"av1\"),\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\t\t\trender: typeof OffscreenCanvas !== \"undefined\" && typeof CanvasRenderingContext2D !== \"undefined\",\n\t\t},\n\t};\n}\n"],"mappings":";;AAQA,IAAM,IAAY,UAAU,UAAU,YAAY,CAAC,CAAC,SAAS,SAAS,GAoChE,IAAS;CACd,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,MAAM;CACN,KAAK;CACL,KAAK;AACN;AAEA,eAAe,EAAsB,GAA8C;CASlF,OARK,WAAW,gBAQT,MANW,aAAa,kBAAkB;EAChD,OAAO,EAAO;EACd,kBAAkB;EAClB,YAAY;CACb,CAAC,EAAA,CAEU,cAAc,KARY;AAStC;AAEA,eAAe,EAAsB,GAA4C;CAChF,IAAM,IAAW,MAAM,aAAa,kBAAkB;EACrD,OAAO,EAAO;EACd,sBAAsB;CACvB,CAAC,GAEK,IAAW,MAAM,aAAa,kBAAkB;EACrD,OAAO,EAAO;EACd,sBAAsB;CACvB,CAAC;CAKD,OAAO;EACN,UAHe,KAAa,EAAS,QAAQ,yBAAyB,oBAGlD,KAAA,IAAY,EAAS,cAAc;EACvD,UAAU,EAAS,cAAc;CAClC;AACD;AAEA,eAAsB,IAA6B;CAClD,OAAO;EAEN,cAAc,EAAW,wBAAwB,IAAI,SAAS;EAC9D,OAAO;GACN,UAAU;IACT,KAAK,MAAM,EAAsB,KAAK;IACtC,MAAO,MAAM,EAAsB,MAAM,IAAK,SAAS;GACxD;GACA,QAAQ,OAAO,eAAiB,OAAe,OAAO,wBAA0B;EACjF;EACA,OAAO;GACN,UACC,OAAO,eAAiB,MACrB;IACA,MAAM,MAAM,EAAsB,MAAM;IACxC,MAAM,MAAM,EAAsB,MAAM;IACxC,KAAK,MAAM,EAAsB,KAAK;IACtC,KAAK,MAAM,EAAsB,KAAK;IACtC,KAAK,MAAM,EAAsB,KAAK;GACvC,IACC,KAAA;GACJ,QAAQ,OAAO,kBAAoB,OAAe,OAAO,2BAA6B;EACvF;CACD;AACD"}
@@ -0,0 +1,129 @@
1
+ import { Time as e } from "@moq/net";
2
+ import { Effect as t, Signal as n, getter as r, readonlys as i } from "@moq/signals";
3
+ //#region src/sync.ts
4
+ function a(e) {
5
+ return e === "real-time" || typeof e == "number" ? {
6
+ min: e,
7
+ max: e
8
+ } : {
9
+ min: e.min ?? "real-time",
10
+ max: e.max ?? "real-time"
11
+ };
12
+ }
13
+ function o(e, t) {
14
+ return e === t ? e : {
15
+ min: e,
16
+ max: t
17
+ };
18
+ }
19
+ var s = e.Milli(20), c = e.Milli(100), l = class o {
20
+ in;
21
+ #e = {
22
+ reference: new n(void 0),
23
+ buffer: new n(e.Milli.zero),
24
+ jitter: new n(c),
25
+ timestamp: new n(void 0),
26
+ buffered: new n(!1),
27
+ maxBuffer: new n(e.Milli.zero)
28
+ };
29
+ out = i(this.#e);
30
+ #t;
31
+ #n = /* @__PURE__ */ new Map();
32
+ #r;
33
+ #i = new t();
34
+ constructor(e) {
35
+ this.in = {
36
+ latency: r(e?.latency ?? "real-time"),
37
+ connection: r(e?.connection),
38
+ audio: r(e?.audio),
39
+ video: r(e?.video)
40
+ }, this.#t = Promise.withResolvers(), this.#i.run(this.#s.bind(this)), this.#i.run(this.#c.bind(this)), this.#i.run(this.#a.bind(this));
41
+ }
42
+ #a(t) {
43
+ let { max: n } = a(t.get(this.in.latency)), r = t.get(this.#e.buffer);
44
+ n === "real-time" ? (this.#e.buffered.set(!1), this.#e.maxBuffer.set(r)) : (this.#e.buffered.set(n > r), this.#e.maxBuffer.set(e.Milli.max(n, r)));
45
+ }
46
+ #o() {
47
+ let { max: t } = a(this.in.latency.peek()), n = this.#e.buffer.peek();
48
+ return t === "real-time" ? n : e.Milli.max(t, n);
49
+ }
50
+ #s(t) {
51
+ let { min: n } = a(t.get(this.in.latency));
52
+ if (typeof n == "number") {
53
+ this.#r = void 0, this.#e.jitter.set(n);
54
+ return;
55
+ }
56
+ let r = t.get(this.in.connection), i = r && t.get(r.probe).rtt;
57
+ if (i !== void 0) {
58
+ this.#r = this.#r === void 0 ? i : Math.min(this.#r, i);
59
+ let t = e.Milli(Math.max(s, this.#r * 1.25));
60
+ this.#e.jitter.set(t);
61
+ return;
62
+ }
63
+ this.#r = void 0, this.#e.jitter.set(c);
64
+ }
65
+ #c(t) {
66
+ let n = t.get(this.#e.jitter), r = t.get(this.in.video) ?? e.Milli.zero, i = t.get(this.in.audio) ?? e.Milli.zero, a = e.Milli.add(e.Milli.max(r, i), n);
67
+ this.#e.buffer.set(a), this.#t.resolve(), this.#t = Promise.withResolvers();
68
+ }
69
+ received(t, n = "") {
70
+ this.#e.timestamp.update((e) => e === void 0 || t > e ? t : e);
71
+ let r = e.Milli.now(), i = e.Milli.sub(r, t), a = this.#e.reference.peek();
72
+ if (a === void 0) {
73
+ this.#l(i);
74
+ return;
75
+ }
76
+ let s = this.#e.buffer.peek(), c = e.Milli.add(e.Milli.sub(a, i), s);
77
+ if (c < 0) {
78
+ let e = this.#n.get(n);
79
+ e ? (e.count++, e.maxMs = Math.max(e.maxMs, -c)) : this.#n.set(n, {
80
+ count: 1,
81
+ maxMs: -c
82
+ });
83
+ } else {
84
+ let e = this.#n.get(n);
85
+ if (e) {
86
+ let t = n ? `sync[${n}]` : "sync", r = o.#u(e.maxMs);
87
+ console.debug(`${t}: ${e.count} late frame(s), max ${r} behind`), this.#n.delete(n);
88
+ }
89
+ }
90
+ if (i >= a) return;
91
+ let l = this.#o();
92
+ c <= l || this.#l(e.Milli.add(i, e.Milli.sub(l, s)));
93
+ }
94
+ #l(e) {
95
+ this.#e.reference.set(e), this.#t.resolve(), this.#t = Promise.withResolvers();
96
+ }
97
+ reset() {
98
+ this.#e.reference.set(void 0), this.#n.clear(), this.#t.resolve(), this.#t = Promise.withResolvers();
99
+ }
100
+ now() {
101
+ let t = this.#e.reference.peek();
102
+ if (t !== void 0) return e.Milli.sub(e.Milli.sub(e.Milli.now(), t), this.#e.buffer.peek());
103
+ }
104
+ async wait(t) {
105
+ if (this.#e.reference.peek() === void 0) throw Error("reference not set; call received() first");
106
+ for (;;) {
107
+ let n = e.Milli.now(), r = e.Milli.sub(n, t), i = this.#e.reference.peek();
108
+ if (i === void 0) return;
109
+ let a = e.Milli.add(e.Milli.sub(i, r), this.#e.buffer.peek());
110
+ if (a <= 0 || a < 5) return;
111
+ let o = new Promise((e) => setTimeout(e, a)).then(() => !0);
112
+ if (await Promise.race([this.#t.promise, o])) return;
113
+ }
114
+ }
115
+ static #u(e) {
116
+ if (e = Math.round(e), e < 1e3) return `${e}ms`;
117
+ let t = e / 1e3;
118
+ if (t < 60) return `${Math.round(t * 10) / 10}s`;
119
+ let n = t / 60;
120
+ return `${Math.round(n * 10) / 10}m`;
121
+ }
122
+ close() {
123
+ this.#i.close();
124
+ }
125
+ };
126
+ //#endregion
127
+ export { a as n, o as r, l as t };
128
+
129
+ //# sourceMappingURL=sync-zdwVEj9u.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-zdwVEj9u.js","names":["#out","#update","#signals","#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, type Getter, getter, type Inputs, type Readonlys, readonlys, 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 = Time.Milli(20);\nconst FALLBACK_JITTER = Time.Milli(100);\n\nexport type SyncInput = {\n\t// Latency target: a scalar minimizes (collapsed range), an object opens a range. See {@link Latency}.\n\tlatency: Getter<Latency>;\n\n\t// The connection used for \"real-time\" jitter: PROBE supplies RTT.\n\tconnection: Getter<Moq.Connection.Established | undefined>;\n\n\t// Any additional delay required for audio or video (wired from the per-rendition source).\n\taudio: Getter<Time.Milli | undefined>;\n\tvideo: Getter<Time.Milli | undefined>;\n};\n\ntype SyncOutput = {\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\treference: Signal<Time.Milli | undefined>;\n\n\t// The total buffer required: jitter + max(audio, video).\n\tbuffer: Signal<Time.Milli>;\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// The media timestamp of the most recently received frame.\n\ttimestamp: 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\tbuffered: Signal<boolean>;\n\n\t// Derived cap on buffered audio (ms), consumed by the audio ring to size itself. Always finite.\n\tmaxBuffer: Signal<Time.Milli>;\n};\n\nexport class Sync {\n\treadonly in: Readonlys<SyncInput>;\n\n\treadonly #out: SyncOutput = {\n\t\treference: new Signal<Time.Milli | undefined>(undefined),\n\t\tbuffer: new Signal<Time.Milli>(Time.Milli.zero),\n\t\tjitter: new Signal<Time.Milli>(FALLBACK_JITTER),\n\t\ttimestamp: new Signal<Time.Milli | undefined>(undefined),\n\t\tbuffered: new Signal<boolean>(false),\n\t\tmaxBuffer: new Signal<Time.Milli>(Time.Milli.zero),\n\t};\n\treadonly out = readonlys(this.#out);\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// 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// 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\t#signals = new Effect();\n\n\tconstructor(props?: Inputs<SyncInput>) {\n\t\tthis.in = {\n\t\t\tlatency: getter(props?.latency ?? (\"real-time\" as Latency)),\n\t\t\tconnection: getter(props?.connection),\n\t\t\taudio: getter(props?.audio),\n\t\t\tvideo: getter(props?.video),\n\t\t};\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.in.latency));\n\t\tconst floor = effect.get(this.#out.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.#out.buffered.set(false);\n\t\t\tthis.#out.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.#out.buffered.set(max > floor);\n\t\t\tthis.#out.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.in.latency.peek());\n\t\tconst floor = this.#out.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.in.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.#out.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 = effect.get(this.in.connection);\n\t\tconst rtt = conn && effect.get(conn.probe).rtt;\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 = Time.Milli(Math.max(MIN_JITTER, this.#minRtt * 1.25));\n\t\t\tthis.#out.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.#out.jitter.set(FALLBACK_JITTER);\n\t}\n\n\t#runBuffer(effect: Effect): void {\n\t\tconst jitter = effect.get(this.#out.jitter);\n\t\tconst video = effect.get(this.in.video) ?? Time.Milli.zero;\n\t\tconst audio = effect.get(this.in.audio) ?? Time.Milli.zero;\n\n\t\tconst buffer = Time.Milli.add(Time.Milli.max(video, audio), jitter);\n\t\tthis.#out.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.#out.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.#out.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.#out.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, Time.Milli.sub(cap, floor)));\n\t}\n\n\t#setReference(ref: Time.Milli): void {\n\t\tthis.#out.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.#out.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.#out.reference.peek();\n\t\tif (reference === undefined) return undefined;\n\t\treturn Time.Milli.sub(Time.Milli.sub(Time.Milli.now(), reference), this.#out.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.#out.reference.peek();\n\t\tif (reference === undefined) {\n\t\t\tthrow new Error(\"reference not set; call received() 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.#out.reference.peek();\n\t\t\tif (currentRef === undefined) return;\n\n\t\t\tconst sleep = Time.Milli.add(Time.Milli.sub(currentRef, ref), this.#out.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,6 +1,6 @@
1
1
  import type * as Moq from "@moq/net";
2
2
  import { Time } from "@moq/net";
3
- import { Effect, Signal } from "@moq/signals";
3
+ import { type Getter, type Inputs, type Readonlys, Signal } from "@moq/signals";
4
4
  /** A single latency bound: `"real-time"` adapts to the RTT; a `Time.Milli` fixes the jitter buffer. */
5
5
  export type Bound = "real-time" | Time.Milli;
6
6
  /**
@@ -22,29 +22,30 @@ export declare function latencyBounds(latency: Latency): {
22
22
  };
23
23
  /** Build a {@link Latency} from explicit bounds, collapsing to a scalar when they're equal. */
24
24
  export declare function latencyFromBounds(min: Bound, max: Bound): Latency;
25
- export interface SyncProps {
26
- latency?: Latency | Signal<Latency>;
27
- connection?: Signal<Moq.Connection.Established | undefined>;
28
- audio?: Time.Milli | Signal<Time.Milli | undefined>;
29
- video?: Time.Milli | Signal<Time.Milli | undefined>;
30
- }
25
+ export type SyncInput = {
26
+ latency: Getter<Latency>;
27
+ connection: Getter<Moq.Connection.Established | undefined>;
28
+ audio: Getter<Time.Milli | undefined>;
29
+ video: Getter<Time.Milli | undefined>;
30
+ };
31
+ type SyncOutput = {
32
+ reference: Signal<Time.Milli | undefined>;
33
+ buffer: Signal<Time.Milli>;
34
+ jitter: Signal<Time.Milli>;
35
+ timestamp: Signal<Time.Milli | undefined>;
36
+ buffered: Signal<boolean>;
37
+ maxBuffer: Signal<Time.Milli>;
38
+ };
31
39
  export declare class Sync {
32
40
  #private;
33
- readonly reference: Signal<Time.Milli | undefined>;
34
- latency: Signal<Latency>;
35
- jitter: Signal<Time.Milli>;
36
- audio: Signal<Time.Milli | undefined>;
37
- video: Signal<Time.Milli | undefined>;
38
- readonly buffered: Signal<boolean>;
39
- readonly maxBuffer: Signal<Time.Milli>;
40
- readonly buffer: Signal<Time.Milli>;
41
- readonly timestamp: Signal<Time.Milli | undefined>;
42
- signals: Effect;
43
- constructor(props?: SyncProps);
41
+ readonly in: Readonlys<SyncInput>;
42
+ readonly out: Readonlys<SyncOutput>;
43
+ constructor(props?: Inputs<SyncInput>);
44
44
  received(timestamp: Time.Milli, label?: string): void;
45
45
  reset(): void;
46
46
  now(): Time.Milli | undefined;
47
47
  wait(timestamp: Time.Milli): Promise<void>;
48
48
  close(): void;
49
49
  }
50
+ export {};
50
51
  //# sourceMappingURL=sync.d.ts.map
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,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"}
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,KAAK,MAAM,EAAU,KAAK,MAAM,EAAE,KAAK,SAAS,EAAa,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3G,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,MAAM,SAAS,GAAG;IAEvB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAGzB,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAG3D,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;CACtC,CAAC;AAEF,KAAK,UAAU,GAAG;IAGjB,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAG1C,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAK3B,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAG3B,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAK1C,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAG1B,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;CAC9B,CAAC;AAEF,qBAAa,IAAI;;IAChB,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAUlC,QAAQ,CAAC,GAAG,wBAAwB;IAepC,YAAY,KAAK,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,EAapC;IAqED,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"}
@@ -1,4 +1,4 @@
1
- import type { Moq } from "@moq/hang";
1
+ import * as Moq from "@moq/net";
2
2
  import type { Effect } from "@moq/signals";
3
3
  import type MoqWatch from "../../element";
4
4
  export declare function bufferControl(parent: Effect, watch: MoqWatch, max?: Moq.Time.Milli): HTMLElement;
@@ -1 +1 @@
1
- {"version":3,"file":"buffer-control.d.ts","sourceRoot":"","sources":["../../../src/ui/components/buffer-control.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAyE1C,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAE,GAAG,CAAC,IAAI,CAAC,KAAmB,GAAG,WAAW,CAwH7G"}
1
+ {"version":3,"file":"buffer-control.d.ts","sourceRoot":"","sources":["../../../src/ui/components/buffer-control.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAyE1C,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAE,GAAG,CAAC,IAAI,CAAC,KAAmB,GAAG,WAAW,CAwH7G"}
@@ -1,5 +1,4 @@
1
1
  import type { Effect } from "@moq/signals";
2
- import type MoqWatch from "../../element";
3
2
  /** Fullscreen toggle button wired to the shared cross-browser fullscreen controller. */
4
- export declare function fullscreenButton(parent: Effect, player: HTMLElement, watch: MoqWatch): HTMLElement;
3
+ export declare function fullscreenButton(parent: Effect, player: HTMLElement): HTMLElement;
5
4
  //# sourceMappingURL=fullscreen-button.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fullscreen-button.d.ts","sourceRoot":"","sources":["../../../src/ui/components/fullscreen-button.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAK1C,wFAAwF;AACxF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,GAAG,WAAW,CAmBlG"}
1
+ {"version":3,"file":"fullscreen-button.d.ts","sourceRoot":"","sources":["../../../src/ui/components/fullscreen-button.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAK3C,wFAAwF;AACxF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,WAAW,CAiBjF"}
@@ -1,5 +1,5 @@
1
1
  import type { Effect } from "@moq/signals";
2
2
  import type MoqWatch from "../../element";
3
- /** Play/pause control bound to the backend paused state. */
3
+ /** Play/pause control bound to the paused state. */
4
4
  export declare function playPauseButton(parent: Effect, watch: MoqWatch): HTMLElement;
5
5
  //# sourceMappingURL=play-pause.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"play-pause.d.ts","sourceRoot":"","sources":["../../../src/ui/components/play-pause.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAI1C,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,WAAW,CAe5E"}
1
+ {"version":3,"file":"play-pause.d.ts","sourceRoot":"","sources":["../../../src/ui/components/play-pause.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAI1C,oDAAoD;AACpD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,WAAW,CAe5E"}
@@ -1 +1 @@
1
- {"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../../src/ui/element.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;;IAMlD,cASC;IAED,iBAAiB,SAOhB;IAED,oBAAoB,SAInB;CAqKD;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":"AAoCA,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;;IAMlD,cASC;IAED,iBAAiB,SAOhB;IAED,oBAAoB,SAInB;CAyKD;AAID,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,cAAc,EAAE,UAAU,CAAC;KAC3B;CACD"}