@norskvideo/moq-watch 0.2.0 → 0.2.2

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/element.d.ts CHANGED
@@ -17,6 +17,7 @@ import type * as Text from "./text";
17
17
  import type * as Video from "./video";
18
18
  declare const OBSERVED: readonly ["url", "name", "paused", "volume", "muted", "visible", "announced", "delay", "buffer", "reload", "latency", "latency-min", "jitter", "catalog-format", "captions"];
19
19
  type Observed = (typeof OBSERVED)[number];
20
+ export declare function parseVisible(value: string | null): Video.Visible;
20
21
  /** Parse the element's catalog-format attribute. */
21
22
  export declare function parseCatalogFormat(value: string | null): CatalogFormat | undefined;
22
23
  export default class MoqWatch extends HTMLElement {
@@ -24,9 +25,9 @@ export default class MoqWatch extends HTMLElement {
24
25
  static observedAttributes: readonly ["url", "name", "paused", "volume", "muted", "visible", "announced", "delay", "buffer", "reload", "latency", "latency-min", "jitter", "catalog-format", "captions"];
25
26
  /**
26
27
  * The relay connection, shared with every other element on the page pointing at the
27
- * same URL. Its `origin` is where the broadcasts live.
28
+ * same URL. Its `origin` is where the broadcasts live. Replaced by {@link connectionProps}.
28
29
  */
29
- connection: Moq.Connection;
30
+ get connection(): Moq.Connection;
30
31
  /** Headless playback pipeline behind this element. */
31
32
  readonly player: Player;
32
33
  broadcast: Broadcast;
@@ -68,6 +69,13 @@ export default class MoqWatch extends HTMLElement {
68
69
  attributeChangedCallback(name: Observed, oldValue: string | null, newValue: string | null): void;
69
70
  get url(): URL | undefined;
70
71
  set url(value: string | URL | undefined);
72
+ /**
73
+ * Rebuild the connection with these options, for a relay the shared pool cannot reach on
74
+ * its own: a self-signed certificate pinned with `webtransport.serverCertificateHashes`,
75
+ * no WebSocket fallback, no discovery. The URL and the enabled state carry over, and the
76
+ * player follows the new connection. Set it before the URL to spare a wasted attempt.
77
+ */
78
+ set connectionProps(props: Omit<Moq.Connection.Props, "url" | "enabled">);
71
79
  get name(): Moq.Path.Valid;
72
80
  set name(value: string | Moq.Path.Valid);
73
81
  get paused(): boolean;
package/element.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,KAAK,OAAO,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,KAAK,KAAK,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,KAAK,SAAS,EAAmB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAElF,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,KAAK,KAAK,IAAI,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,KAAK,KAAK,MAAM,SAAS,CAAC;AAEtC,QAAA,MAAM,QAAQ,YACb,KAAK,EACL,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EACX,OAAO,EACP,QAAQ,EAER,QAAQ,EACR,SAAS,EACT,aAAa,EACb,QAAQ,EACR,gBAAgB,EAChB,UAAU,CACD,CAAC;AACX,KAAK,QAAQ,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAoC1C,oDAAoD;AACpD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,aAAa,GAAG,SAAS,CAGlF;AAmBD,MAAM,CAAC,OAAO,OAAO,QAAS,SAAQ,WAAW;;IAChD,MAAM,CAAC,kBAAkB,+KAAY;IAGrC;;;OAGG;IACH,UAAU,EAAE,GAAG,CAAC,UAAU,CAAC;IAE3B,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAGxB,SAAS,EAAE,SAAS,CAAC;IAErB,iFAAiF;IACjF,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;IAErB,iFAAiF;IACjF,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;IAErB,oDAAoD;IACpD,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC;IAEzB,kDAAkD;IAClD,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC;IAEvB,yFAAyF;IACzF,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;IAElB,uEAAuE;IACvE,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC;IAE5B,6DAA6D;IAC7D,IAAI,EAAE,IAAI,CAAC;IAKX,QAAQ,CAAC,QAAQ;QAChB,MAAM;QACN,MAAM;QACN,KAAK;QAEL,OAAO;QAEP,KAAK;QAEL,MAAM;QAEN,MAAM;QAGN,WAAW;QAEX,QAAQ;MACP;IAsBF;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,SAAgB;IAEhC,cAoJC;IAID,iBAAiB,SAiBhB;IAED,oBAAoB,SAGnB;IAED,wBAAwB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,QAqCxF;IAED,IAAI,GAAG,IAAI,GAAG,GAAG,SAAS,CAEzB;IAED,IAAI,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,EAEtC;IAED,IAAI,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAEzB;IAED,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,EAEtC;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,OAAO,EAExB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,EAEvB;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,OAAO,EAEvB;IAED,IAAI,OAAO,IAAI,KAAK,CAAC,OAAO,CAE3B;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,EAE/B;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,SAAS,CAAC,KAAK,EAAE,OAAO,EAE3B;IAOD;;;;;OAKG;IACH,IAAI,KAAK,IAAI,KAAK,CAEjB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,KAAK,EAErB;IAED;;;;;;;OAOG;IACH,IAAI,MAAM,IAAI,IAAI,CAAC,KAAK,CAEvB;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAE3B;IAOD,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,EAE1B;IAOD,IAAI,UAAU,CAAC,MAAM,EAAE,OAAO,EAE7B;IASD,yCAAyC;IACzC,IAAI,MAAM,IAAI,IAAI,CAAC,KAAK,CAEvB;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,EAEzB;IAED;;;OAGG;IACH,KAAK,IAAI,IAAI,CAEZ;IAED,IAAI,aAAa,IAAI,aAAa,GAAG,SAAS,CAE7C;IAED,IAAI,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,EAEjD;IAED;;;OAGG;IACH,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,GAAG,SAAS,CAEtC;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,GAAG,SAAS,EAE1C;IAED;;;;OAIG;IACH,IAAI,QAAQ,IAAI,MAAM,GAAG,SAAS,CAEjC;IAED,IAAI,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAErC;CACD;AAID,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,WAAW,EAAE,QAAQ,CAAC;KACtB;CACD"}
1
+ {"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"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"}
package/element.js CHANGED
@@ -1,20 +1,20 @@
1
- import { h as e, t } from "./player-C19uf7Dc.js";
1
+ import { h as e, t } from "./player-C7Ih0ExN.js";
2
2
  import * as n from "@norskvideo/moq-net";
3
3
  import { Time as r } from "@norskvideo/moq-net";
4
- import { Effect as i, Signal as a } from "@norskvideo/moq-signals";
4
+ import { Computed as i, Effect as a, Signal as o } from "@norskvideo/moq-signals";
5
5
  //#region src/duration.ts
6
- function o(e) {
6
+ function s(e) {
7
7
  let t = /^(\d+(?:\.\d+)?)(ms|s)?$/.exec(e.trim());
8
8
  if (!t) return;
9
9
  let n = Number.parseFloat(t[1]);
10
10
  if (Number.isFinite(n)) return t[2] === void 0 ? n === 0 ? r.Milli.zero : void 0 : r.Milli(t[2] === "s" ? n * 1e3 : n);
11
11
  }
12
- function s(e) {
12
+ function c(e) {
13
13
  return `${e}ms`;
14
14
  }
15
15
  //#endregion
16
16
  //#region src/element.ts
17
- var c = [
17
+ var l = [
18
18
  "url",
19
19
  "name",
20
20
  "paused",
@@ -31,34 +31,38 @@ var c = [
31
31
  "catalog-format",
32
32
  "captions"
33
33
  ];
34
- function l(e) {
34
+ function u(e) {
35
35
  let t = e?.trim();
36
- return t ? t === "never" || t === "always" || /^-?\d+(\.\d+)?(px|%)$/.test(t) ? t : /^-?\d+(\.\d+)?$/.test(t) ? `${t}px` : (console.warn(`moq-watch: invalid visible="${e}", expected "never", "always", or a CSS length like "200px"`), "20%") : "20%";
36
+ return t ? t === "never" || t === "always" || t === "tab" || /^-?\d+(\.\d+)?(px|%)$/.test(t) ? t : /^-?\d+(\.\d+)?$/.test(t) ? `${t}px` : (console.warn(`moq-watch: invalid visible="${e}", expected "never", "always", "tab", or a CSS length like "200px"`), "20%") : "20%";
37
37
  }
38
- function u(e) {
38
+ function d(e) {
39
39
  let t = e?.trim();
40
40
  if (!t || t === "auto") return "auto";
41
41
  if (t === "instant") return "instant";
42
- let n = o(t);
42
+ let n = s(t);
43
43
  return n === void 0 ? (console.warn(`moq-watch: invalid delay="${e}", expected "auto", "instant", or a duration like "300ms"`), "auto") : n;
44
44
  }
45
- function d(e) {
45
+ function f(e) {
46
46
  let t = e?.trim();
47
47
  if (!t) return n.Time.Milli.zero;
48
- let r = o(t);
48
+ let r = s(t);
49
49
  return r === void 0 ? (console.warn(`moq-watch: invalid buffer="${e}", expected a duration like "30s"`), n.Time.Milli.zero) : r;
50
50
  }
51
- function f(t) {
51
+ function p(t) {
52
52
  if (t !== null) return e.find((e) => e === t);
53
53
  }
54
- function p(e, t) {
54
+ function m(e, t) {
55
55
  if (e === null) return t;
56
56
  let n = e.trim().toLowerCase();
57
57
  return n !== "false" && n !== "0";
58
58
  }
59
- var m = new FinalizationRegistry((e) => e.close()), h = class extends HTMLElement {
60
- static observedAttributes = c;
61
- connection;
59
+ var h = new FinalizationRegistry((e) => e.close()), g = class extends HTMLElement {
60
+ static observedAttributes = l;
61
+ get connection() {
62
+ return this.#e.peek();
63
+ }
64
+ #e;
65
+ #t = new o(void 0);
62
66
  player;
63
67
  broadcast;
64
68
  video;
@@ -69,56 +73,59 @@ var m = new FinalizationRegistry((e) => e.close()), h = class extends HTMLElemen
69
73
  textRenderer;
70
74
  sync;
71
75
  controls = {
72
- paused: new a(!1),
73
- volume: new a(.5),
74
- muted: new a(!1),
75
- visible: new a("20%"),
76
- delay: new a("auto"),
77
- buffer: new a(n.Time.Milli.zero),
78
- target: new a(void 0),
79
- audioTarget: new a(void 0),
80
- captions: new a(void 0)
76
+ paused: new o(!1),
77
+ volume: new o(.5),
78
+ muted: new o(!1),
79
+ visible: new o("20%"),
80
+ delay: new o("auto"),
81
+ buffer: new o(n.Time.Milli.zero),
82
+ target: new o(void 0),
83
+ audioTarget: new o(void 0),
84
+ captions: new o(void 0)
81
85
  };
82
- #e = new a(n.Path.empty());
83
- #t = new a(!0);
84
- #n = new a(void 0);
85
- #r = new a(void 0);
86
- #i = new a(void 0);
87
- #a = new a(void 0);
88
- #o;
89
- #s = new a(!1);
90
- #c = .5;
91
- signals = new i();
86
+ #n = new o(n.Path.empty());
87
+ #r = new o(!0);
88
+ #i = new o(void 0);
89
+ #a = new o(void 0);
90
+ #o = new o(void 0);
91
+ #s = new o(void 0);
92
+ #c;
93
+ #l = new o(!1);
94
+ #u = .5;
95
+ signals = new a();
92
96
  constructor() {
93
- super(), m.register(this, this.signals), this.connection = new n.Connection({ enabled: this.#s }), this.signals.cleanup(() => this.connection.close()), this.player = new t({
94
- origin: this.connection.origin,
95
- probe: this.connection.probe,
96
- enabled: this.#s,
97
- name: this.#e,
98
- announced: this.#t,
99
- catalogFormat: this.#n,
100
- catalog: this.#r,
101
- canvas: this.#i,
102
- container: this.#a,
97
+ super(), h.register(this, this.signals), this.#e = new o(new n.Connection({
98
+ url: this.#t,
99
+ enabled: this.#l
100
+ })), this.signals.cleanup(() => this.connection.close()), this.player = new t({
101
+ origin: new i((e) => e.get(e.get(this.#e).origin)),
102
+ probe: new i((e) => e.get(e.get(this.#e).probe)),
103
+ enabled: this.#l,
104
+ name: this.#n,
105
+ announced: this.#r,
106
+ catalogFormat: this.#i,
107
+ catalog: this.#a,
108
+ canvas: this.#o,
109
+ container: this.#s,
103
110
  ...this.controls
104
111
  }), this.signals.cleanup(() => this.player.close()), this.broadcast = this.player.broadcast, this.video = this.player.video, this.audio = this.player.audio, this.renderer = this.player.renderer, this.emitter = this.player.emitter, this.text = this.player.text, this.textRenderer = this.player.textRenderer, this.sync = this.player.sync, this.signals.run((e) => {
105
- e.get(this.controls.muted) ? (this.#c = this.controls.volume.peek() || .5, this.controls.volume.set(0)) : this.controls.volume.set(this.#c);
112
+ e.get(this.controls.muted) ? (this.#u = this.controls.volume.peek() || .5, this.controls.volume.set(0)) : this.controls.volume.set(this.#u);
106
113
  }), this.signals.run((e) => {
107
114
  let t = e.get(this.controls.volume);
108
115
  this.controls.muted.set(t === 0);
109
116
  });
110
117
  let e = () => {
111
118
  let e = this.querySelector("canvas") ?? void 0;
112
- !e && this.querySelector("video") && console.warn("moq-watch: rendering requires a <canvas> child; a <video> child does nothing."), this.#i.set(e);
119
+ !e && this.querySelector("video") && console.warn("moq-watch: rendering requires a <canvas> child; a <video> child does nothing."), this.#o.set(e);
113
120
  }, r = new MutationObserver(e);
114
121
  r.observe(this, {
115
122
  childList: !0,
116
123
  subtree: !0
117
124
  }), this.signals.cleanup(() => r.disconnect()), e(), this.signals.run((e) => {
118
- let t = e.get(this.connection.url);
125
+ let t = e.get(this.#t);
119
126
  t ? this.setAttribute("url", t.toString()) : this.removeAttribute("url");
120
127
  }), this.signals.run((e) => {
121
- let t = e.get(this.#e);
128
+ let t = e.get(this.#n);
122
129
  this.setAttribute("name", t.toString());
123
130
  }), this.signals.run((e) => {
124
131
  e.get(this.controls.muted) ? this.setAttribute("muted", "") : this.removeAttribute("muted");
@@ -132,11 +139,11 @@ var m = new FinalizationRegistry((e) => e.close()), h = class extends HTMLElemen
132
139
  this.setAttribute("visible", t);
133
140
  }), this.signals.run((e) => {
134
141
  let t = e.get(this.controls.delay);
135
- this.setAttribute("delay", typeof t == "number" ? s(t) : t);
142
+ this.setAttribute("delay", typeof t == "number" ? c(t) : t);
136
143
  }), this.signals.run((e) => {
137
- this.setAttribute("buffer", s(e.get(this.controls.buffer)));
144
+ this.setAttribute("buffer", c(e.get(this.controls.buffer)));
138
145
  });
139
- let i = (e, t) => {
146
+ let a = (e, t) => {
140
147
  if (e <= 0 || t <= 0) return;
141
148
  let n = window.devicePixelRatio || 1;
142
149
  this.controls.target.update((r) => ({
@@ -144,54 +151,62 @@ var m = new FinalizationRegistry((e) => e.close()), h = class extends HTMLElemen
144
151
  width: Math.round(e * n),
145
152
  height: Math.round(t * n)
146
153
  }));
147
- }, a = new ResizeObserver((e) => {
154
+ }, s = new ResizeObserver((e) => {
148
155
  let t = e[0];
149
- t && i(t.contentRect.width, t.contentRect.height);
156
+ t && a(t.contentRect.width, t.contentRect.height);
150
157
  });
151
- a.observe(this), this.signals.cleanup(() => a.disconnect());
152
- let o = this.getBoundingClientRect();
153
- i(o.width, o.height);
158
+ s.observe(this), this.signals.cleanup(() => s.disconnect());
159
+ let l = this.getBoundingClientRect();
160
+ a(l.width, l.height);
154
161
  }
155
162
  connectedCallback() {
156
- if (this.#s.set(!0), this.style.display = "block", this.style.position = "relative", !this.#o) {
163
+ if (this.#l.set(!0), this.style.display = "block", this.style.position = "relative", !this.#c) {
157
164
  let e = document.createElement("div");
158
- e.style.position = "absolute", e.style.inset = "0", e.style.pointerEvents = "none", e.style.zIndex = "1", this.appendChild(e), this.#o = e, this.#a.set(e);
165
+ e.style.position = "absolute", e.style.inset = "0", e.style.pointerEvents = "none", e.style.zIndex = "1", this.appendChild(e), this.#c = e, this.#s.set(e);
159
166
  }
160
167
  }
161
168
  disconnectedCallback() {
162
- this.#s.set(!1);
169
+ this.#l.set(!1);
163
170
  }
164
171
  attributeChangedCallback(e, t, r) {
165
172
  if (t !== r) {
166
- if (e === "url") this.connection.url.set(r ? new URL(r) : void 0);
167
- else if (e === "name") this.#e.set(n.Path.from(r ?? ""));
168
- else if (e === "paused") this.controls.paused.set(p(r, !1));
173
+ if (e === "url") this.#t.set(r ? new URL(r) : void 0);
174
+ else if (e === "name") this.#n.set(n.Path.from(r ?? ""));
175
+ else if (e === "paused") this.controls.paused.set(m(r, !1));
169
176
  else if (e === "volume") {
170
177
  let e = r ? Number.parseFloat(r) : .5;
171
178
  this.controls.volume.set(e);
172
- } else if (e === "muted") this.controls.muted.set(p(r, !1));
173
- else if (e === "visible") this.controls.visible.set(l(r));
174
- else if (e === "announced") this.#t.set(p(r, !0));
175
- else if (e === "delay") this.controls.delay.set(u(r));
176
- else if (e === "buffer") this.controls.buffer.set(d(r));
179
+ } else if (e === "muted") this.controls.muted.set(m(r, !1));
180
+ else if (e === "visible") this.controls.visible.set(u(r));
181
+ else if (e === "announced") this.#r.set(m(r, !0));
182
+ else if (e === "delay") this.controls.delay.set(d(r));
183
+ else if (e === "buffer") this.controls.buffer.set(f(r));
177
184
  else if (e === "reload") console.warn("moq-watch: `reload` was renamed to `announced`");
178
185
  else if (e === "latency" || e === "latency-min" || e === "jitter") console.warn(`moq-watch: \`${e}\` is gone; use \`delay\` and \`buffer\``);
179
- else if (e === "catalog-format") this.#n.set(f(r));
186
+ else if (e === "catalog-format") this.#i.set(p(r));
180
187
  else if (e === "captions") this.controls.captions.set(r || void 0);
181
188
  else throw Error(`Invalid attribute: ${e}`);
182
189
  }
183
190
  }
184
191
  get url() {
185
- return this.connection.url.peek();
192
+ return this.#t.peek();
186
193
  }
187
194
  set url(e) {
188
- this.connection.url.set(e ? new URL(e) : void 0);
195
+ this.#t.set(e ? new URL(e) : void 0);
196
+ }
197
+ set connectionProps(e) {
198
+ let t = this.#e.peek();
199
+ this.#e.set(new n.Connection({
200
+ ...e,
201
+ url: this.#t,
202
+ enabled: this.#l
203
+ })), t.close();
189
204
  }
190
205
  get name() {
191
- return this.#e.peek();
206
+ return this.#n.peek();
192
207
  }
193
208
  set name(e) {
194
- this.#e.set(n.Path.from(e));
209
+ this.#n.set(n.Path.from(e));
195
210
  }
196
211
  get paused() {
197
212
  return this.controls.paused.peek();
@@ -218,10 +233,10 @@ var m = new FinalizationRegistry((e) => e.close()), h = class extends HTMLElemen
218
233
  this.controls.visible.set(e);
219
234
  }
220
235
  get announced() {
221
- return this.#t.peek();
236
+ return this.#r.peek();
222
237
  }
223
238
  set announced(e) {
224
- this.#t.set(e);
239
+ this.#r.set(e);
225
240
  }
226
241
  set reload(e) {
227
242
  throw Error("moq-watch: `reload` was renamed to `announced`");
@@ -263,16 +278,16 @@ var m = new FinalizationRegistry((e) => e.close()), h = class extends HTMLElemen
263
278
  this.player.reset();
264
279
  }
265
280
  get catalogFormat() {
266
- return this.#n.peek();
281
+ return this.#i.peek();
267
282
  }
268
283
  set catalogFormat(e) {
269
- this.#n.set(e);
284
+ this.#i.set(e);
270
285
  }
271
286
  get catalog() {
272
287
  return this.broadcast.out.catalog.peek();
273
288
  }
274
289
  set catalog(e) {
275
- this.#r.set(e);
290
+ this.#a.set(e);
276
291
  }
277
292
  get captions() {
278
293
  return this.controls.captions.peek();
@@ -281,8 +296,8 @@ var m = new FinalizationRegistry((e) => e.close()), h = class extends HTMLElemen
281
296
  this.controls.captions.set(e || void 0);
282
297
  }
283
298
  };
284
- customElements.define("moq-watch", h);
299
+ customElements.define("moq-watch", g);
285
300
  //#endregion
286
- export { h as default, f as parseCatalogFormat };
301
+ export { g as default, p as parseCatalogFormat, u as parseVisible };
287
302
 
288
303
  //# sourceMappingURL=element.js.map
package/element.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"element.js","names":[],"sources":["../src/duration.ts","../src/element.ts"],"sourcesContent":["import { Time } from \"@moq/net\";\n\n/**\n * Parse a duration attribute: `\"300ms\"`, `\"30s\"`, or a bare `\"0\"`.\n *\n * A bare number other than zero is rejected rather than guessed at. Most players measure these in\n * seconds while we store milliseconds, so assuming a unit would turn a value copied from another\n * player into a 1000x error. Zero is exempt because `0ms` and `0s` are the same value.\n *\n * Returns undefined when the value doesn't parse, leaving the fallback to the caller.\n *\n * @internal\n */\nexport function parseDuration(value: string): Time.Milli | undefined {\n\tconst match = /^(\\d+(?:\\.\\d+)?)(ms|s)?$/.exec(value.trim());\n\tif (!match) return undefined;\n\n\tconst amount = Number.parseFloat(match[1]);\n\tif (!Number.isFinite(amount)) return undefined;\n\tif (match[2] === undefined) return amount === 0 ? Time.Milli.zero : undefined;\n\n\treturn Time.Milli(match[2] === \"s\" ? amount * 1000 : amount);\n}\n\n/**\n * Render a duration back out as an attribute value, in the canonical unit.\n *\n * Not rounded: the reflected attribute is parsed straight back into the signal, so rounding here\n * would rewrite the value behind the caller (`1.5ms` becoming `2ms`, and `0.4ms` becoming zero,\n * which also switches buffered playback off).\n *\n * @internal\n */\nexport function formatDuration(value: Time.Milli): string {\n\treturn `${value}ms`;\n}\n","/**\n * The `<moq-watch>` custom element: a broadcast player driven by HTML attributes.\n *\n * Side-effectful: importing this registers the element.\n *\n * @module\n */\nimport type * as Catalog from \"@moq/hang/catalog\";\nimport type { Time } from \"@moq/net\";\nimport * as Moq from \"@moq/net\";\nimport { Effect, Signal } from \"@moq/signals\";\nimport type * as Audio from \"./audio\";\nimport { type Broadcast, CATALOG_FORMATS, type CatalogFormat } from \"./broadcast\";\nimport { formatDuration, parseDuration } from \"./duration\";\nimport { Player } from \"./player\";\nimport type { Delay, Sync } from \"./sync\";\nimport type * as Text from \"./text\";\nimport type * as Video from \"./video\";\n\nconst OBSERVED = [\n\t\"url\",\n\t\"name\",\n\t\"paused\",\n\t\"volume\",\n\t\"muted\",\n\t\"visible\",\n\t\"announced\",\n\t\"delay\",\n\t\"buffer\",\n\t// Released spellings are observed only so assigning them can fail loudly instead of being ignored.\n\t\"reload\",\n\t\"latency\",\n\t\"latency-min\",\n\t\"jitter\",\n\t\"catalog-format\",\n\t\"captions\",\n] as const;\ntype Observed = (typeof OBSERVED)[number];\n\n// Parse the `visible` attribute into a Visible value, falling back to \"20%\".\nfunction parseVisible(value: string | null): Video.Visible {\n\tconst trimmed = value?.trim();\n\tif (!trimmed) return \"20%\";\n\tif (trimmed === \"never\" || trimmed === \"always\") return trimmed;\n\t// A CSS length usable as an IntersectionObserver rootMargin (px or %).\n\tif (/^-?\\d+(\\.\\d+)?(px|%)$/.test(trimmed)) return trimmed;\n\t// Allow a bare number as a px convenience (e.g. visible=\"200\").\n\tif (/^-?\\d+(\\.\\d+)?$/.test(trimmed)) return `${trimmed}px`;\n\tconsole.warn(`moq-watch: invalid visible=\"${value}\", expected \"never\", \"always\", or a CSS length like \"200px\"`);\n\treturn \"20%\";\n}\n\n// Parse the `delay` attribute: \"auto\" (adaptive), \"instant\" (no buffer, no pacing), or a duration.\nfunction parseDelay(value: string | null): Delay {\n\tconst trimmed = value?.trim();\n\tif (!trimmed || trimmed === \"auto\") return \"auto\";\n\tif (trimmed === \"instant\") return \"instant\";\n\tconst parsed = parseDuration(trimmed);\n\tif (parsed !== undefined) return parsed;\n\tconsole.warn(`moq-watch: invalid delay=\"${value}\", expected \"auto\", \"instant\", or a duration like \"300ms\"`);\n\treturn \"auto\";\n}\n\n// Parse the `buffer` attribute: a duration, or none when absent.\nfunction parseBuffer(value: string | null): Time.Milli {\n\tconst trimmed = value?.trim();\n\tif (!trimmed) return Moq.Time.Milli.zero;\n\tconst parsed = parseDuration(trimmed);\n\tif (parsed !== undefined) return parsed;\n\tconsole.warn(`moq-watch: invalid buffer=\"${value}\", expected a duration like \"30s\"`);\n\treturn Moq.Time.Milli.zero;\n}\n\n/** Parse the element's catalog-format attribute. */\nexport function parseCatalogFormat(value: string | null): CatalogFormat | undefined {\n\tif (value === null) return undefined;\n\treturn CATALOG_FORMATS.find((format) => format === value);\n}\n\n/**\n * Parse a boolean attribute: absent uses `defaultValue`, bare presence is true, and an explicit\n * `\"false\"`/`\"0\"` is false. Presence alone can't express false, and attributes that default to\n * true (`announced`) need to, so every boolean attribute accepts the explicit form.\n */\nfunction parseBoolean(value: string | null, defaultValue: boolean): boolean {\n\tif (value === null) return defaultValue;\n\tconst normalized = value.trim().toLowerCase();\n\treturn normalized !== \"false\" && normalized !== \"0\";\n}\n\n// Close everything when this element is garbage collected.\n// This is primarily to avoid a console.warn that we didn't close() before GC.\n// There's no destructor for web components so this is the best we can do.\nconst cleanup = new FinalizationRegistry<Effect>((signals) => signals.close());\n\n// An optional web component that wraps a <canvas>\nexport default class MoqWatch extends HTMLElement {\n\tstatic observedAttributes = OBSERVED;\n\n\t// The connection to the moq-relay server.\n\t/**\n\t * The relay connection, shared with every other element on the page pointing at the\n\t * same URL. Its `origin` is where the broadcasts live.\n\t */\n\tconnection: Moq.Connection;\n\n\t/** Headless playback pipeline behind this element. */\n\treadonly player: Player;\n\n\t// The broadcast being watched.\n\tbroadcast: Broadcast;\n\n\t/** Downloads and decodes the video track. `video.source` picks the rendition. */\n\tvideo: Video.Decoder;\n\n\t/** Downloads and decodes the audio track. `audio.source` picks the rendition. */\n\taudio: Audio.Decoder;\n\n\t/** Paints decoded frames to the nested <canvas>. */\n\trenderer: Video.Renderer;\n\n\t/** Plays decoded samples through the speakers. */\n\temitter: Audio.Emitter;\n\n\t/** Selects the caption track. `text.out.available` lists the renditions for a picker. */\n\ttext: Text.Source;\n\n\t/** Renders the selected text cues into an overlay above the canvas. */\n\ttextRenderer: Text.Renderer;\n\n\t/** Keeps audio and video playing at the configured delay. */\n\tsync: Sync;\n\n\t// The mutable user controls. As the top of the tree, this element owns the\n\t// writable Signals and wires read-only views into the pipeline. The UI and\n\t// the attribute/property accessors read and write these directly.\n\treadonly controls = {\n\t\tpaused: new Signal(false),\n\t\tvolume: new Signal(0.5),\n\t\tmuted: new Signal(false),\n\t\t// When video is downloaded relative to the canvas position. See {@link Video.Visible}.\n\t\tvisible: new Signal<Video.Visible>(\"20%\"),\n\t\t// How far playback trails the live edge.\n\t\tdelay: new Signal<Delay>(\"auto\"),\n\t\t// Future-dated media held beyond the live edge before playback skips ahead.\n\t\tbuffer: new Signal<Time.Milli>(Moq.Time.Milli.zero),\n\t\t// The desired video rendition (resolution/bitrate cap).\n\t\ttarget: new Signal<Video.Target | undefined>(undefined),\n\t\t// The desired audio rendition, by track name: a player choosing one sound\n\t\t// among several (a language, say). Unset, the first playable is taken.\n\t\taudioTarget: new Signal<Audio.Target | undefined>(undefined),\n\t\t// The selected caption track name, or undefined for off (the default; captions are opt-in).\n\t\tcaptions: new Signal<string | undefined>(undefined),\n\t};\n\n\t// Broadcast configuration owned here and wired into `broadcast` as inputs.\n\t#name = new Signal<Moq.Path.Valid>(Moq.Path.empty());\n\t#announced = new Signal(true);\n\t#catalogFormat = new Signal<CatalogFormat | undefined>(undefined);\n\t#catalog = new Signal<Catalog.Root | undefined>(undefined);\n\n\t// The canvas element to render into.\n\t#canvas = new Signal<HTMLCanvasElement | undefined>(undefined);\n\n\t// The overlay element captions are drawn into, created lazily on connect (custom elements may not\n\t// touch children in their constructor). Positioned to fill the element, above the canvas.\n\t#captionsOverlay = new Signal<HTMLElement | undefined>(undefined);\n\t#captionsOverlayEl?: HTMLDivElement;\n\n\t// Set when the element is connected to the DOM.\n\t#enabled = new Signal(false);\n\n\t// Stashed volume to restore on unmute.\n\t#unmuteVolume = 0.5;\n\n\t/**\n\t * Effects scoped to this element's lifetime, closed on disconnect.\n\t *\n\t * Public because the element is the top of the tree: it's where an application hangs its own\n\t * reactivity. The components underneath keep theirs private, so `close()` is the only handle.\n\t */\n\treadonly signals = new Effect();\n\n\tconstructor() {\n\t\tsuper();\n\n\t\tcleanup.register(this, this.signals);\n\n\t\tthis.connection = new Moq.Connection({\n\t\t\tenabled: this.#enabled,\n\t\t});\n\t\tthis.signals.cleanup(() => this.connection.close());\n\n\t\tthis.player = new Player({\n\t\t\torigin: this.connection.origin,\n\t\t\tprobe: this.connection.probe,\n\t\t\tenabled: this.#enabled,\n\t\t\tname: this.#name,\n\t\t\tannounced: this.#announced,\n\t\t\tcatalogFormat: this.#catalogFormat,\n\t\t\tcatalog: this.#catalog,\n\t\t\tcanvas: this.#canvas,\n\t\t\tcontainer: this.#captionsOverlay,\n\t\t\t...this.controls,\n\t\t});\n\t\tthis.signals.cleanup(() => this.player.close());\n\t\tthis.broadcast = this.player.broadcast;\n\t\tthis.video = this.player.video;\n\t\tthis.audio = this.player.audio;\n\t\tthis.renderer = this.player.renderer;\n\t\tthis.emitter = this.player.emitter;\n\t\tthis.text = this.player.text;\n\t\tthis.textRenderer = this.player.textRenderer;\n\t\tthis.sync = this.player.sync;\n\n\t\t// Mute/volume coupling. The element owns the writable volume/muted Signals, so\n\t\t// the policy lives here: muting stashes and zeroes the volume; a zero volume\n\t\t// reports as muted.\n\t\tthis.signals.run((effect) => {\n\t\t\tconst muted = effect.get(this.controls.muted);\n\t\t\tif (muted) {\n\t\t\t\tthis.#unmuteVolume = this.controls.volume.peek() || 0.5;\n\t\t\t\tthis.controls.volume.set(0);\n\t\t\t} else {\n\t\t\t\tthis.controls.volume.set(this.#unmuteVolume);\n\t\t\t}\n\t\t});\n\t\tthis.signals.run((effect) => {\n\t\t\tconst volume = effect.get(this.controls.volume);\n\t\t\tthis.controls.muted.set(volume === 0);\n\t\t});\n\n\t\t// Watch to see if the canvas element is added or removed.\n\t\tconst setCanvas = () => {\n\t\t\tconst canvas = this.querySelector(\"canvas\") ?? undefined;\n\n\t\t\t// A <video> child used to render via MSE. Nothing renders it now, and audio still plays,\n\t\t\t// so the failure looks like a bug in the page instead of a removed feature.\n\t\t\tif (!canvas && this.querySelector(\"video\")) {\n\t\t\t\tconsole.warn(\"moq-watch: rendering requires a <canvas> child; a <video> child does nothing.\");\n\t\t\t}\n\n\t\t\tthis.#canvas.set(canvas);\n\t\t};\n\n\t\tconst observer = new MutationObserver(setCanvas);\n\t\tobserver.observe(this, { childList: true, subtree: true });\n\t\tthis.signals.cleanup(() => observer.disconnect());\n\t\tsetCanvas();\n\n\t\t// Optionally update attributes to match the library state.\n\t\t// This is kind of dangerous because it can create loops.\n\t\t// NOTE: This only runs when the element is connected to the DOM, which is not obvious.\n\t\t// This is because there's no destructor for web components to clean up our effects.\n\t\tthis.signals.run((effect) => {\n\t\t\tconst url = effect.get(this.connection.url);\n\t\t\tif (url) {\n\t\t\t\tthis.setAttribute(\"url\", url.toString());\n\t\t\t} else {\n\t\t\t\tthis.removeAttribute(\"url\");\n\t\t\t}\n\t\t});\n\n\t\tthis.signals.run((effect) => {\n\t\t\tconst name = effect.get(this.#name);\n\t\t\tthis.setAttribute(\"name\", name.toString());\n\t\t});\n\n\t\tthis.signals.run((effect) => {\n\t\t\tconst muted = effect.get(this.controls.muted);\n\t\t\tif (muted) {\n\t\t\t\tthis.setAttribute(\"muted\", \"\");\n\t\t\t} else {\n\t\t\t\tthis.removeAttribute(\"muted\");\n\t\t\t}\n\t\t});\n\n\t\tthis.signals.run((effect) => {\n\t\t\tconst paused = effect.get(this.controls.paused);\n\t\t\tif (paused) {\n\t\t\t\tthis.setAttribute(\"paused\", \"\");\n\t\t\t} else {\n\t\t\t\tthis.removeAttribute(\"paused\");\n\t\t\t}\n\t\t});\n\n\t\tthis.signals.run((effect) => {\n\t\t\tconst volume = effect.get(this.controls.volume);\n\t\t\tthis.setAttribute(\"volume\", volume.toString());\n\t\t});\n\n\t\tthis.signals.run((effect) => {\n\t\t\tconst visible = effect.get(this.controls.visible);\n\t\t\tthis.setAttribute(\"visible\", visible);\n\t\t});\n\n\t\t// Each knob is 1:1 with its attribute, so the echo back through attributeChangedCallback\n\t\t// parses to the value already held and the effect settles.\n\t\tthis.signals.run((effect) => {\n\t\t\tconst delay = effect.get(this.controls.delay);\n\t\t\tthis.setAttribute(\"delay\", typeof delay === \"number\" ? formatDuration(delay) : delay);\n\t\t});\n\n\t\tthis.signals.run((effect) => {\n\t\t\tthis.setAttribute(\"buffer\", formatDuration(effect.get(this.controls.buffer)));\n\t\t});\n\n\t\t// Track the element's rendered size and feed it into the rendition picker,\n\t\t// scaled by devicePixelRatio so high-DPI screens still get sharp renditions.\n\t\tconst updateDimensions = (width: number, height: number) => {\n\t\t\tif (width <= 0 || height <= 0) return;\n\t\t\tconst dpr = window.devicePixelRatio || 1;\n\t\t\tthis.controls.target.update((prev) => ({\n\t\t\t\t...prev,\n\t\t\t\twidth: Math.round(width * dpr),\n\t\t\t\theight: Math.round(height * dpr),\n\t\t\t}));\n\t\t};\n\n\t\tconst resizeObserver = new ResizeObserver((entries) => {\n\t\t\tconst entry = entries[0];\n\t\t\tif (!entry) return;\n\t\t\tupdateDimensions(entry.contentRect.width, entry.contentRect.height);\n\t\t});\n\t\tresizeObserver.observe(this);\n\t\tthis.signals.cleanup(() => resizeObserver.disconnect());\n\n\t\t// Seed with the current size in case the observer doesn't fire immediately\n\t\t// (e.g. the element is still 0x0 when we attach).\n\t\tconst rect = this.getBoundingClientRect();\n\t\tupdateDimensions(rect.width, rect.height);\n\t}\n\n\t// Annoyingly, we have to use these callbacks to figure out when the element is connected to the DOM.\n\t// This wouldn't be so bad if there was a destructor for web components to clean up our effects.\n\tconnectedCallback() {\n\t\tthis.#enabled.set(true);\n\t\tthis.style.display = \"block\";\n\t\tthis.style.position = \"relative\";\n\n\t\t// Create the caption overlay once, on first connect (the constructor may not add children).\n\t\tif (!this.#captionsOverlayEl) {\n\t\t\tconst overlay = document.createElement(\"div\");\n\t\t\toverlay.style.position = \"absolute\";\n\t\t\toverlay.style.inset = \"0\";\n\t\t\toverlay.style.pointerEvents = \"none\";\n\t\t\t// Above the canvas, which paints at the default stacking level.\n\t\t\toverlay.style.zIndex = \"1\";\n\t\t\tthis.appendChild(overlay);\n\t\t\tthis.#captionsOverlayEl = overlay;\n\t\t\tthis.#captionsOverlay.set(overlay);\n\t\t}\n\t}\n\n\tdisconnectedCallback() {\n\t\t// Stop everything but don't actually cleanup just in case we get added back to the DOM.\n\t\tthis.#enabled.set(false);\n\t}\n\n\tattributeChangedCallback(name: Observed, oldValue: string | null, newValue: string | null) {\n\t\tif (oldValue === newValue) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (name === \"url\") {\n\t\t\tthis.connection.url.set(newValue ? new URL(newValue) : undefined);\n\t\t} else if (name === \"name\") {\n\t\t\tthis.#name.set(Moq.Path.from(newValue ?? \"\"));\n\t\t} else if (name === \"paused\") {\n\t\t\tthis.controls.paused.set(parseBoolean(newValue, false));\n\t\t} else if (name === \"volume\") {\n\t\t\tconst volume = newValue ? Number.parseFloat(newValue) : 0.5;\n\t\t\tthis.controls.volume.set(volume);\n\t\t} else if (name === \"muted\") {\n\t\t\tthis.controls.muted.set(parseBoolean(newValue, false));\n\t\t} else if (name === \"visible\") {\n\t\t\tthis.controls.visible.set(parseVisible(newValue));\n\t\t} else if (name === \"announced\") {\n\t\t\tthis.#announced.set(parseBoolean(newValue, true));\n\t\t} else if (name === \"delay\") {\n\t\t\tthis.controls.delay.set(parseDelay(newValue));\n\t\t} else if (name === \"buffer\") {\n\t\t\tthis.controls.buffer.set(parseBuffer(newValue));\n\t\t} else if (name === \"reload\") {\n\t\t\tconsole.warn(\"moq-watch: `reload` was renamed to `announced`\");\n\t\t} else if (name === \"latency\" || name === \"latency-min\" || name === \"jitter\") {\n\t\t\tconsole.warn(`moq-watch: \\`${name}\\` is gone; use \\`delay\\` and \\`buffer\\``);\n\t\t} else if (name === \"catalog-format\") {\n\t\t\tthis.#catalogFormat.set(parseCatalogFormat(newValue));\n\t\t} else if (name === \"captions\") {\n\t\t\t// The selected caption track name; absent or empty turns captions off.\n\t\t\tthis.controls.captions.set(newValue || undefined);\n\t\t} else {\n\t\t\tconst exhaustive: never = name;\n\t\t\tthrow new Error(`Invalid attribute: ${exhaustive}`);\n\t\t}\n\t}\n\n\tget url(): URL | undefined {\n\t\treturn this.connection.url.peek();\n\t}\n\n\tset url(value: string | URL | undefined) {\n\t\tthis.connection.url.set(value ? new URL(value) : undefined);\n\t}\n\n\tget name(): Moq.Path.Valid {\n\t\treturn this.#name.peek();\n\t}\n\n\tset name(value: string | Moq.Path.Valid) {\n\t\tthis.#name.set(Moq.Path.from(value));\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.controls.paused.peek();\n\t}\n\n\tset paused(value: boolean) {\n\t\tthis.controls.paused.set(value);\n\t}\n\n\tget volume(): number {\n\t\treturn this.controls.volume.peek();\n\t}\n\n\tset volume(value: number) {\n\t\tthis.controls.volume.set(value);\n\t}\n\n\tget muted(): boolean {\n\t\treturn this.controls.muted.peek();\n\t}\n\n\tset muted(value: boolean) {\n\t\tthis.controls.muted.set(value);\n\t}\n\n\tget visible(): Video.Visible {\n\t\treturn this.controls.visible.peek();\n\t}\n\n\tset visible(value: Video.Visible) {\n\t\tthis.controls.visible.set(value);\n\t}\n\n\tget announced(): boolean {\n\t\treturn this.#announced.peek();\n\t}\n\n\tset announced(value: boolean) {\n\t\tthis.#announced.set(value);\n\t}\n\n\t/** @internal */\n\tset reload(_value: unknown) {\n\t\tthrow new Error(\"moq-watch: `reload` was renamed to `announced`\");\n\t}\n\n\t/**\n\t * How far playback trails the live edge, in milliseconds. See {@link Delay}.\n\t *\n\t * `\"auto\"` (the default) sizes the jitter buffer from the connection RTT. `\"instant\"` drops the\n\t * clock instead: video paints the moment it decodes and audio is disabled.\n\t */\n\tget delay(): Delay {\n\t\treturn this.controls.delay.peek();\n\t}\n\n\tset delay(value: Delay) {\n\t\tthis.controls.delay.set(value);\n\t}\n\n\t/**\n\t * Future-dated media held beyond the live edge before playback skips ahead, in milliseconds.\n\t *\n\t * Zero (the default) minimizes latency. A larger value enables buffered playback: build up a\n\t * buffer from future-dated frames (e.g. TTS written faster than real-time) and only skip ahead\n\t * once they would sit further than `delay + buffer` past the playhead. Call `reset()` at each\n\t * utterance boundary.\n\t */\n\tget buffer(): Time.Milli {\n\t\treturn this.controls.buffer.peek();\n\t}\n\n\tset buffer(value: Time.Milli) {\n\t\tthis.controls.buffer.set(value);\n\t}\n\n\t/** @internal */\n\tget latency(): Delay {\n\t\treturn this.controls.delay.peek();\n\t}\n\n\tset latency(_value: unknown) {\n\t\tthrow new Error(\"moq-watch: `latency` is gone; use `delay` and `buffer`\");\n\t}\n\n\t/** @internal */\n\tget latencyMin(): Delay {\n\t\treturn this.controls.delay.peek();\n\t}\n\n\tset latencyMin(_value: unknown) {\n\t\tthrow new Error(\"moq-watch: `latencyMin` is gone; use `delay` and `buffer`\");\n\t}\n\n\t/** @internal */\n\tset latencyMax(_value: unknown) {\n\t\tthrow new Error(\n\t\t\t\"moq-watch: `latencyMax` is gone. Use `buffer`, the media held beyond the live edge; the old ceiling included the floor, so it is `latencyMax - delay`.\",\n\t\t);\n\t}\n\n\t/** The jitter buffer in milliseconds. */\n\tget jitter(): Time.Milli {\n\t\treturn this.sync.out.jitter.peek();\n\t}\n\n\tset jitter(_value: unknown) {\n\t\tthrow new Error(\"moq-watch: `jitter` is a readout; set `delay` instead\");\n\t}\n\n\t/**\n\t * Re-anchor playback at an utterance boundary in buffered mode: reset the sync reference\n\t * and flush the audio buffer so the next utterance plays from its own first frame.\n\t */\n\treset(): void {\n\t\tthis.player.reset();\n\t}\n\n\tget catalogFormat(): CatalogFormat | undefined {\n\t\treturn this.#catalogFormat.peek();\n\t}\n\n\tset catalogFormat(value: CatalogFormat | undefined) {\n\t\tthis.#catalogFormat.set(value);\n\t}\n\n\t/**\n\t * The active catalog. Assign directly when `catalogFormat` is `\"manual\"`;\n\t * for `\"hang\"` and `\"msf\"` this is overwritten by the fetch loop.\n\t */\n\tget catalog(): Catalog.Root | undefined {\n\t\treturn this.broadcast.out.catalog.peek();\n\t}\n\n\tset catalog(value: Catalog.Root | undefined) {\n\t\tthis.#catalog.set(value);\n\t}\n\n\t/**\n\t * The selected caption track name, or `undefined` for off (the default). Captions are opt-in:\n\t * assign a track name from `text.out.available` to turn them on. See the `text` source for the\n\t * list of renditions the broadcast publishes.\n\t */\n\tget captions(): string | undefined {\n\t\treturn this.controls.captions.peek();\n\t}\n\n\tset captions(value: string | undefined) {\n\t\tthis.controls.captions.set(value || undefined);\n\t}\n}\n\ncustomElements.define(\"moq-watch\", MoqWatch);\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t\"moq-watch\": MoqWatch;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"element.js","names":[],"sources":["../src/duration.ts","../src/element.ts"],"sourcesContent":["import { Time } from \"@moq/net\";\n\n/**\n * Parse a duration attribute: `\"300ms\"`, `\"30s\"`, or a bare `\"0\"`.\n *\n * A bare number other than zero is rejected rather than guessed at. Most players measure these in\n * seconds while we store milliseconds, so assuming a unit would turn a value copied from another\n * player into a 1000x error. Zero is exempt because `0ms` and `0s` are the same value.\n *\n * Returns undefined when the value doesn't parse, leaving the fallback to the caller.\n *\n * @internal\n */\nexport function parseDuration(value: string): Time.Milli | undefined {\n\tconst match = /^(\\d+(?:\\.\\d+)?)(ms|s)?$/.exec(value.trim());\n\tif (!match) return undefined;\n\n\tconst amount = Number.parseFloat(match[1]);\n\tif (!Number.isFinite(amount)) return undefined;\n\tif (match[2] === undefined) return amount === 0 ? Time.Milli.zero : undefined;\n\n\treturn Time.Milli(match[2] === \"s\" ? amount * 1000 : amount);\n}\n\n/**\n * Render a duration back out as an attribute value, in the canonical unit.\n *\n * Not rounded: the reflected attribute is parsed straight back into the signal, so rounding here\n * would rewrite the value behind the caller (`1.5ms` becoming `2ms`, and `0.4ms` becoming zero,\n * which also switches buffered playback off).\n *\n * @internal\n */\nexport function formatDuration(value: Time.Milli): string {\n\treturn `${value}ms`;\n}\n","/**\n * The `<moq-watch>` custom element: a broadcast player driven by HTML attributes.\n *\n * Side-effectful: importing this registers the element.\n *\n * @module\n */\nimport type * as Catalog from \"@moq/hang/catalog\";\nimport type { Time } from \"@moq/net\";\nimport * as Moq from \"@moq/net\";\nimport { Effect, Signal, Computed } from \"@moq/signals\";\nimport type * as Audio from \"./audio\";\nimport { type Broadcast, CATALOG_FORMATS, type CatalogFormat } from \"./broadcast\";\nimport { formatDuration, parseDuration } from \"./duration\";\nimport { Player } from \"./player\";\nimport type { Delay, Sync } from \"./sync\";\nimport type * as Text from \"./text\";\nimport type * as Video from \"./video\";\n\nconst OBSERVED = [\n\t\"url\",\n\t\"name\",\n\t\"paused\",\n\t\"volume\",\n\t\"muted\",\n\t\"visible\",\n\t\"announced\",\n\t\"delay\",\n\t\"buffer\",\n\t// Released spellings are observed only so assigning them can fail loudly instead of being ignored.\n\t\"reload\",\n\t\"latency\",\n\t\"latency-min\",\n\t\"jitter\",\n\t\"catalog-format\",\n\t\"captions\",\n] as const;\ntype Observed = (typeof OBSERVED)[number];\n\n// Parse the `visible` attribute into a Visible value, falling back to \"20%\". Exported for tests.\nexport function parseVisible(value: string | null): Video.Visible {\n\tconst trimmed = value?.trim();\n\tif (!trimmed) return \"20%\";\n\tif (trimmed === \"never\" || trimmed === \"always\" || trimmed === \"tab\") return trimmed;\n\t// A CSS length usable as an IntersectionObserver rootMargin (px or %).\n\tif (/^-?\\d+(\\.\\d+)?(px|%)$/.test(trimmed)) return trimmed;\n\t// Allow a bare number as a px convenience (e.g. visible=\"200\").\n\tif (/^-?\\d+(\\.\\d+)?$/.test(trimmed)) return `${trimmed}px`;\n\tconsole.warn(\n\t\t`moq-watch: invalid visible=\"${value}\", expected \"never\", \"always\", \"tab\", or a CSS length like \"200px\"`,\n\t);\n\treturn \"20%\";\n}\n\n// Parse the `delay` attribute: \"auto\" (adaptive), \"instant\" (no buffer, no pacing), or a duration.\nfunction parseDelay(value: string | null): Delay {\n\tconst trimmed = value?.trim();\n\tif (!trimmed || trimmed === \"auto\") return \"auto\";\n\tif (trimmed === \"instant\") return \"instant\";\n\tconst parsed = parseDuration(trimmed);\n\tif (parsed !== undefined) return parsed;\n\tconsole.warn(`moq-watch: invalid delay=\"${value}\", expected \"auto\", \"instant\", or a duration like \"300ms\"`);\n\treturn \"auto\";\n}\n\n// Parse the `buffer` attribute: a duration, or none when absent.\nfunction parseBuffer(value: string | null): Time.Milli {\n\tconst trimmed = value?.trim();\n\tif (!trimmed) return Moq.Time.Milli.zero;\n\tconst parsed = parseDuration(trimmed);\n\tif (parsed !== undefined) return parsed;\n\tconsole.warn(`moq-watch: invalid buffer=\"${value}\", expected a duration like \"30s\"`);\n\treturn Moq.Time.Milli.zero;\n}\n\n/** Parse the element's catalog-format attribute. */\nexport function parseCatalogFormat(value: string | null): CatalogFormat | undefined {\n\tif (value === null) return undefined;\n\treturn CATALOG_FORMATS.find((format) => format === value);\n}\n\n/**\n * Parse a boolean attribute: absent uses `defaultValue`, bare presence is true, and an explicit\n * `\"false\"`/`\"0\"` is false. Presence alone can't express false, and attributes that default to\n * true (`announced`) need to, so every boolean attribute accepts the explicit form.\n */\nfunction parseBoolean(value: string | null, defaultValue: boolean): boolean {\n\tif (value === null) return defaultValue;\n\tconst normalized = value.trim().toLowerCase();\n\treturn normalized !== \"false\" && normalized !== \"0\";\n}\n\n// Close everything when this element is garbage collected.\n// This is primarily to avoid a console.warn that we didn't close() before GC.\n// There's no destructor for web components so this is the best we can do.\nconst cleanup = new FinalizationRegistry<Effect>((signals) => signals.close());\n\n// An optional web component that wraps a <canvas>\nexport default class MoqWatch extends HTMLElement {\n\tstatic observedAttributes = OBSERVED;\n\n\t// The connection to the moq-relay server.\n\t/**\n\t * The relay connection, shared with every other element on the page pointing at the\n\t * same URL. Its `origin` is where the broadcasts live. Replaced by {@link connectionProps}.\n\t */\n\tget connection(): Moq.Connection {\n\t\treturn this.#connection.peek();\n\t}\n\t#connection: Signal<Moq.Connection>;\n\t// The URL, one signal for every connection this element builds, so a rebuild keeps it.\n\t#url = new Signal<URL | undefined>(undefined);\n\n\t/** Headless playback pipeline behind this element. */\n\treadonly player: Player;\n\n\t// The broadcast being watched.\n\tbroadcast: Broadcast;\n\n\t/** Downloads and decodes the video track. `video.source` picks the rendition. */\n\tvideo: Video.Decoder;\n\n\t/** Downloads and decodes the audio track. `audio.source` picks the rendition. */\n\taudio: Audio.Decoder;\n\n\t/** Paints decoded frames to the nested <canvas>. */\n\trenderer: Video.Renderer;\n\n\t/** Plays decoded samples through the speakers. */\n\temitter: Audio.Emitter;\n\n\t/** Selects the caption track. `text.out.available` lists the renditions for a picker. */\n\ttext: Text.Source;\n\n\t/** Renders the selected text cues into an overlay above the canvas. */\n\ttextRenderer: Text.Renderer;\n\n\t/** Keeps audio and video playing at the configured delay. */\n\tsync: Sync;\n\n\t// The mutable user controls. As the top of the tree, this element owns the\n\t// writable Signals and wires read-only views into the pipeline. The UI and\n\t// the attribute/property accessors read and write these directly.\n\treadonly controls = {\n\t\tpaused: new Signal(false),\n\t\tvolume: new Signal(0.5),\n\t\tmuted: new Signal(false),\n\t\t// When video is downloaded relative to the canvas position. See {@link Video.Visible}.\n\t\tvisible: new Signal<Video.Visible>(\"20%\"),\n\t\t// How far playback trails the live edge.\n\t\tdelay: new Signal<Delay>(\"auto\"),\n\t\t// Future-dated media held beyond the live edge before playback skips ahead.\n\t\tbuffer: new Signal<Time.Milli>(Moq.Time.Milli.zero),\n\t\t// The desired video rendition (resolution/bitrate cap).\n\t\ttarget: new Signal<Video.Target | undefined>(undefined),\n\t\t// The desired audio rendition, by track name: a player choosing one sound\n\t\t// among several (a language, say). Unset, the first playable is taken.\n\t\taudioTarget: new Signal<Audio.Target | undefined>(undefined),\n\t\t// The selected caption track name, or undefined for off (the default; captions are opt-in).\n\t\tcaptions: new Signal<string | undefined>(undefined),\n\t};\n\n\t// Broadcast configuration owned here and wired into `broadcast` as inputs.\n\t#name = new Signal<Moq.Path.Valid>(Moq.Path.empty());\n\t#announced = new Signal(true);\n\t#catalogFormat = new Signal<CatalogFormat | undefined>(undefined);\n\t#catalog = new Signal<Catalog.Root | undefined>(undefined);\n\n\t// The canvas element to render into.\n\t#canvas = new Signal<HTMLCanvasElement | undefined>(undefined);\n\n\t// The overlay element captions are drawn into, created lazily on connect (custom elements may not\n\t// touch children in their constructor). Positioned to fill the element, above the canvas.\n\t#captionsOverlay = new Signal<HTMLElement | undefined>(undefined);\n\t#captionsOverlayEl?: HTMLDivElement;\n\n\t// Set when the element is connected to the DOM.\n\t#enabled = new Signal(false);\n\n\t// Stashed volume to restore on unmute.\n\t#unmuteVolume = 0.5;\n\n\t/**\n\t * Effects scoped to this element's lifetime, closed on disconnect.\n\t *\n\t * Public because the element is the top of the tree: it's where an application hangs its own\n\t * reactivity. The components underneath keep theirs private, so `close()` is the only handle.\n\t */\n\treadonly signals = new Effect();\n\n\tconstructor() {\n\t\tsuper();\n\n\t\tcleanup.register(this, this.signals);\n\n\t\tthis.#connection = new Signal(\n\t\t\tnew Moq.Connection({\n\t\t\t\turl: this.#url,\n\t\t\t\tenabled: this.#enabled,\n\t\t\t}),\n\t\t);\n\t\tthis.signals.cleanup(() => this.connection.close());\n\n\t\tthis.player = new Player({\n\t\t\t// through the signal, so a connection rebuilt by `connectionProps` is the one played\n\t\t\torigin: new Computed((effect) => effect.get(effect.get(this.#connection).origin)),\n\t\t\tprobe: new Computed((effect) => effect.get(effect.get(this.#connection).probe)),\n\t\t\tenabled: this.#enabled,\n\t\t\tname: this.#name,\n\t\t\tannounced: this.#announced,\n\t\t\tcatalogFormat: this.#catalogFormat,\n\t\t\tcatalog: this.#catalog,\n\t\t\tcanvas: this.#canvas,\n\t\t\tcontainer: this.#captionsOverlay,\n\t\t\t...this.controls,\n\t\t});\n\t\tthis.signals.cleanup(() => this.player.close());\n\t\tthis.broadcast = this.player.broadcast;\n\t\tthis.video = this.player.video;\n\t\tthis.audio = this.player.audio;\n\t\tthis.renderer = this.player.renderer;\n\t\tthis.emitter = this.player.emitter;\n\t\tthis.text = this.player.text;\n\t\tthis.textRenderer = this.player.textRenderer;\n\t\tthis.sync = this.player.sync;\n\n\t\t// Mute/volume coupling. The element owns the writable volume/muted Signals, so\n\t\t// the policy lives here: muting stashes and zeroes the volume; a zero volume\n\t\t// reports as muted.\n\t\tthis.signals.run((effect) => {\n\t\t\tconst muted = effect.get(this.controls.muted);\n\t\t\tif (muted) {\n\t\t\t\tthis.#unmuteVolume = this.controls.volume.peek() || 0.5;\n\t\t\t\tthis.controls.volume.set(0);\n\t\t\t} else {\n\t\t\t\tthis.controls.volume.set(this.#unmuteVolume);\n\t\t\t}\n\t\t});\n\t\tthis.signals.run((effect) => {\n\t\t\tconst volume = effect.get(this.controls.volume);\n\t\t\tthis.controls.muted.set(volume === 0);\n\t\t});\n\n\t\t// Watch to see if the canvas element is added or removed.\n\t\tconst setCanvas = () => {\n\t\t\tconst canvas = this.querySelector(\"canvas\") ?? undefined;\n\n\t\t\t// A <video> child used to render via MSE. Nothing renders it now, and audio still plays,\n\t\t\t// so the failure looks like a bug in the page instead of a removed feature.\n\t\t\tif (!canvas && this.querySelector(\"video\")) {\n\t\t\t\tconsole.warn(\"moq-watch: rendering requires a <canvas> child; a <video> child does nothing.\");\n\t\t\t}\n\n\t\t\tthis.#canvas.set(canvas);\n\t\t};\n\n\t\tconst observer = new MutationObserver(setCanvas);\n\t\tobserver.observe(this, { childList: true, subtree: true });\n\t\tthis.signals.cleanup(() => observer.disconnect());\n\t\tsetCanvas();\n\n\t\t// Optionally update attributes to match the library state.\n\t\t// This is kind of dangerous because it can create loops.\n\t\t// NOTE: This only runs when the element is connected to the DOM, which is not obvious.\n\t\t// This is because there's no destructor for web components to clean up our effects.\n\t\tthis.signals.run((effect) => {\n\t\t\tconst url = effect.get(this.#url);\n\t\t\tif (url) {\n\t\t\t\tthis.setAttribute(\"url\", url.toString());\n\t\t\t} else {\n\t\t\t\tthis.removeAttribute(\"url\");\n\t\t\t}\n\t\t});\n\n\t\tthis.signals.run((effect) => {\n\t\t\tconst name = effect.get(this.#name);\n\t\t\tthis.setAttribute(\"name\", name.toString());\n\t\t});\n\n\t\tthis.signals.run((effect) => {\n\t\t\tconst muted = effect.get(this.controls.muted);\n\t\t\tif (muted) {\n\t\t\t\tthis.setAttribute(\"muted\", \"\");\n\t\t\t} else {\n\t\t\t\tthis.removeAttribute(\"muted\");\n\t\t\t}\n\t\t});\n\n\t\tthis.signals.run((effect) => {\n\t\t\tconst paused = effect.get(this.controls.paused);\n\t\t\tif (paused) {\n\t\t\t\tthis.setAttribute(\"paused\", \"\");\n\t\t\t} else {\n\t\t\t\tthis.removeAttribute(\"paused\");\n\t\t\t}\n\t\t});\n\n\t\tthis.signals.run((effect) => {\n\t\t\tconst volume = effect.get(this.controls.volume);\n\t\t\tthis.setAttribute(\"volume\", volume.toString());\n\t\t});\n\n\t\tthis.signals.run((effect) => {\n\t\t\tconst visible = effect.get(this.controls.visible);\n\t\t\tthis.setAttribute(\"visible\", visible);\n\t\t});\n\n\t\t// Each knob is 1:1 with its attribute, so the echo back through attributeChangedCallback\n\t\t// parses to the value already held and the effect settles.\n\t\tthis.signals.run((effect) => {\n\t\t\tconst delay = effect.get(this.controls.delay);\n\t\t\tthis.setAttribute(\"delay\", typeof delay === \"number\" ? formatDuration(delay) : delay);\n\t\t});\n\n\t\tthis.signals.run((effect) => {\n\t\t\tthis.setAttribute(\"buffer\", formatDuration(effect.get(this.controls.buffer)));\n\t\t});\n\n\t\t// Track the element's rendered size and feed it into the rendition picker,\n\t\t// scaled by devicePixelRatio so high-DPI screens still get sharp renditions.\n\t\tconst updateDimensions = (width: number, height: number) => {\n\t\t\tif (width <= 0 || height <= 0) return;\n\t\t\tconst dpr = window.devicePixelRatio || 1;\n\t\t\tthis.controls.target.update((prev) => ({\n\t\t\t\t...prev,\n\t\t\t\twidth: Math.round(width * dpr),\n\t\t\t\theight: Math.round(height * dpr),\n\t\t\t}));\n\t\t};\n\n\t\tconst resizeObserver = new ResizeObserver((entries) => {\n\t\t\tconst entry = entries[0];\n\t\t\tif (!entry) return;\n\t\t\tupdateDimensions(entry.contentRect.width, entry.contentRect.height);\n\t\t});\n\t\tresizeObserver.observe(this);\n\t\tthis.signals.cleanup(() => resizeObserver.disconnect());\n\n\t\t// Seed with the current size in case the observer doesn't fire immediately\n\t\t// (e.g. the element is still 0x0 when we attach).\n\t\tconst rect = this.getBoundingClientRect();\n\t\tupdateDimensions(rect.width, rect.height);\n\t}\n\n\t// Annoyingly, we have to use these callbacks to figure out when the element is connected to the DOM.\n\t// This wouldn't be so bad if there was a destructor for web components to clean up our effects.\n\tconnectedCallback() {\n\t\tthis.#enabled.set(true);\n\t\tthis.style.display = \"block\";\n\t\tthis.style.position = \"relative\";\n\n\t\t// Create the caption overlay once, on first connect (the constructor may not add children).\n\t\tif (!this.#captionsOverlayEl) {\n\t\t\tconst overlay = document.createElement(\"div\");\n\t\t\toverlay.style.position = \"absolute\";\n\t\t\toverlay.style.inset = \"0\";\n\t\t\toverlay.style.pointerEvents = \"none\";\n\t\t\t// Above the canvas, which paints at the default stacking level.\n\t\t\toverlay.style.zIndex = \"1\";\n\t\t\tthis.appendChild(overlay);\n\t\t\tthis.#captionsOverlayEl = overlay;\n\t\t\tthis.#captionsOverlay.set(overlay);\n\t\t}\n\t}\n\n\tdisconnectedCallback() {\n\t\t// Stop everything but don't actually cleanup just in case we get added back to the DOM.\n\t\tthis.#enabled.set(false);\n\t}\n\n\tattributeChangedCallback(name: Observed, oldValue: string | null, newValue: string | null) {\n\t\tif (oldValue === newValue) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (name === \"url\") {\n\t\t\tthis.#url.set(newValue ? new URL(newValue) : undefined);\n\t\t} else if (name === \"name\") {\n\t\t\tthis.#name.set(Moq.Path.from(newValue ?? \"\"));\n\t\t} else if (name === \"paused\") {\n\t\t\tthis.controls.paused.set(parseBoolean(newValue, false));\n\t\t} else if (name === \"volume\") {\n\t\t\tconst volume = newValue ? Number.parseFloat(newValue) : 0.5;\n\t\t\tthis.controls.volume.set(volume);\n\t\t} else if (name === \"muted\") {\n\t\t\tthis.controls.muted.set(parseBoolean(newValue, false));\n\t\t} else if (name === \"visible\") {\n\t\t\tthis.controls.visible.set(parseVisible(newValue));\n\t\t} else if (name === \"announced\") {\n\t\t\tthis.#announced.set(parseBoolean(newValue, true));\n\t\t} else if (name === \"delay\") {\n\t\t\tthis.controls.delay.set(parseDelay(newValue));\n\t\t} else if (name === \"buffer\") {\n\t\t\tthis.controls.buffer.set(parseBuffer(newValue));\n\t\t} else if (name === \"reload\") {\n\t\t\tconsole.warn(\"moq-watch: `reload` was renamed to `announced`\");\n\t\t} else if (name === \"latency\" || name === \"latency-min\" || name === \"jitter\") {\n\t\t\tconsole.warn(`moq-watch: \\`${name}\\` is gone; use \\`delay\\` and \\`buffer\\``);\n\t\t} else if (name === \"catalog-format\") {\n\t\t\tthis.#catalogFormat.set(parseCatalogFormat(newValue));\n\t\t} else if (name === \"captions\") {\n\t\t\t// The selected caption track name; absent or empty turns captions off.\n\t\t\tthis.controls.captions.set(newValue || undefined);\n\t\t} else {\n\t\t\tconst exhaustive: never = name;\n\t\t\tthrow new Error(`Invalid attribute: ${exhaustive}`);\n\t\t}\n\t}\n\n\tget url(): URL | undefined {\n\t\treturn this.#url.peek();\n\t}\n\n\tset url(value: string | URL | undefined) {\n\t\tthis.#url.set(value ? new URL(value) : undefined);\n\t}\n\n\t/**\n\t * Rebuild the connection with these options, for a relay the shared pool cannot reach on\n\t * its own: a self-signed certificate pinned with `webtransport.serverCertificateHashes`,\n\t * no WebSocket fallback, no discovery. The URL and the enabled state carry over, and the\n\t * player follows the new connection. Set it before the URL to spare a wasted attempt.\n\t */\n\tset connectionProps(props: Omit<Moq.Connection.Props, \"url\" | \"enabled\">) {\n\t\tconst prev = this.#connection.peek();\n\t\tthis.#connection.set(new Moq.Connection({ ...props, url: this.#url, enabled: this.#enabled }));\n\t\tprev.close();\n\t}\n\n\tget name(): Moq.Path.Valid {\n\t\treturn this.#name.peek();\n\t}\n\n\tset name(value: string | Moq.Path.Valid) {\n\t\tthis.#name.set(Moq.Path.from(value));\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.controls.paused.peek();\n\t}\n\n\tset paused(value: boolean) {\n\t\tthis.controls.paused.set(value);\n\t}\n\n\tget volume(): number {\n\t\treturn this.controls.volume.peek();\n\t}\n\n\tset volume(value: number) {\n\t\tthis.controls.volume.set(value);\n\t}\n\n\tget muted(): boolean {\n\t\treturn this.controls.muted.peek();\n\t}\n\n\tset muted(value: boolean) {\n\t\tthis.controls.muted.set(value);\n\t}\n\n\tget visible(): Video.Visible {\n\t\treturn this.controls.visible.peek();\n\t}\n\n\tset visible(value: Video.Visible) {\n\t\tthis.controls.visible.set(value);\n\t}\n\n\tget announced(): boolean {\n\t\treturn this.#announced.peek();\n\t}\n\n\tset announced(value: boolean) {\n\t\tthis.#announced.set(value);\n\t}\n\n\t/** @internal */\n\tset reload(_value: unknown) {\n\t\tthrow new Error(\"moq-watch: `reload` was renamed to `announced`\");\n\t}\n\n\t/**\n\t * How far playback trails the live edge, in milliseconds. See {@link Delay}.\n\t *\n\t * `\"auto\"` (the default) sizes the jitter buffer from the connection RTT. `\"instant\"` drops the\n\t * clock instead: video paints the moment it decodes and audio is disabled.\n\t */\n\tget delay(): Delay {\n\t\treturn this.controls.delay.peek();\n\t}\n\n\tset delay(value: Delay) {\n\t\tthis.controls.delay.set(value);\n\t}\n\n\t/**\n\t * Future-dated media held beyond the live edge before playback skips ahead, in milliseconds.\n\t *\n\t * Zero (the default) minimizes latency. A larger value enables buffered playback: build up a\n\t * buffer from future-dated frames (e.g. TTS written faster than real-time) and only skip ahead\n\t * once they would sit further than `delay + buffer` past the playhead. Call `reset()` at each\n\t * utterance boundary.\n\t */\n\tget buffer(): Time.Milli {\n\t\treturn this.controls.buffer.peek();\n\t}\n\n\tset buffer(value: Time.Milli) {\n\t\tthis.controls.buffer.set(value);\n\t}\n\n\t/** @internal */\n\tget latency(): Delay {\n\t\treturn this.controls.delay.peek();\n\t}\n\n\tset latency(_value: unknown) {\n\t\tthrow new Error(\"moq-watch: `latency` is gone; use `delay` and `buffer`\");\n\t}\n\n\t/** @internal */\n\tget latencyMin(): Delay {\n\t\treturn this.controls.delay.peek();\n\t}\n\n\tset latencyMin(_value: unknown) {\n\t\tthrow new Error(\"moq-watch: `latencyMin` is gone; use `delay` and `buffer`\");\n\t}\n\n\t/** @internal */\n\tset latencyMax(_value: unknown) {\n\t\tthrow new Error(\n\t\t\t\"moq-watch: `latencyMax` is gone. Use `buffer`, the media held beyond the live edge; the old ceiling included the floor, so it is `latencyMax - delay`.\",\n\t\t);\n\t}\n\n\t/** The jitter buffer in milliseconds. */\n\tget jitter(): Time.Milli {\n\t\treturn this.sync.out.jitter.peek();\n\t}\n\n\tset jitter(_value: unknown) {\n\t\tthrow new Error(\"moq-watch: `jitter` is a readout; set `delay` instead\");\n\t}\n\n\t/**\n\t * Re-anchor playback at an utterance boundary in buffered mode: reset the sync reference\n\t * and flush the audio buffer so the next utterance plays from its own first frame.\n\t */\n\treset(): void {\n\t\tthis.player.reset();\n\t}\n\n\tget catalogFormat(): CatalogFormat | undefined {\n\t\treturn this.#catalogFormat.peek();\n\t}\n\n\tset catalogFormat(value: CatalogFormat | undefined) {\n\t\tthis.#catalogFormat.set(value);\n\t}\n\n\t/**\n\t * The active catalog. Assign directly when `catalogFormat` is `\"manual\"`;\n\t * for `\"hang\"` and `\"msf\"` this is overwritten by the fetch loop.\n\t */\n\tget catalog(): Catalog.Root | undefined {\n\t\treturn this.broadcast.out.catalog.peek();\n\t}\n\n\tset catalog(value: Catalog.Root | undefined) {\n\t\tthis.#catalog.set(value);\n\t}\n\n\t/**\n\t * The selected caption track name, or `undefined` for off (the default). Captions are opt-in:\n\t * assign a track name from `text.out.available` to turn them on. See the `text` source for the\n\t * list of renditions the broadcast publishes.\n\t */\n\tget captions(): string | undefined {\n\t\treturn this.controls.captions.peek();\n\t}\n\n\tset captions(value: string | undefined) {\n\t\tthis.controls.captions.set(value || undefined);\n\t}\n}\n\ncustomElements.define(\"moq-watch\", MoqWatch);\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t\"moq-watch\": MoqWatch;\n\t}\n}\n"],"mappings":"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"}
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as e, a as t, c as n, d as r, f as i, g as a, h as o, i as s, l as c, m as l, n as u, o as d, p as f, r as p, s as m, t as h, u as g, v as _ } from "./player-C19uf7Dc.js";
1
+ import { _ as e, a as t, c as n, d as r, f as i, g as a, h as o, i as s, l as c, m as l, n as u, o as d, p as f, r as p, s as m, t as h, u as g, v as _ } from "./player-C7Ih0ExN.js";
2
2
  import * as v from "@norskvideo/moq-hang";
3
3
  import * as y from "@norskvideo/moq-net";
4
4
  import * as b from "@norskvideo/moq-signals";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@norskvideo/moq-watch",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "description": "Watch Media over QUIC broadcasts",
6
6
  "license": "(MIT OR Apache-2.0)",
7
7
  "repository": {
@@ -38,10 +38,10 @@
38
38
  ],
39
39
  "dependencies": {
40
40
  "media-captions": "0.0.18",
41
- "@norskvideo/moq-hang": "^0.2.0",
42
- "@norskvideo/moq-json": "^0.2.0",
43
- "@norskvideo/moq-net": "^0.2.0",
44
- "@norskvideo/moq-msf": "^0.2.0",
45
- "@norskvideo/moq-signals": "^0.2.0"
41
+ "@norskvideo/moq-hang": "^0.2.2",
42
+ "@norskvideo/moq-json": "^0.2.2",
43
+ "@norskvideo/moq-net": "^0.2.2",
44
+ "@norskvideo/moq-msf": "^0.2.2",
45
+ "@norskvideo/moq-signals": "^0.2.2"
46
46
  }
47
47
  }