@norskvideo/moq-watch 0.2.1 → 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 +1 -0
- package/element.d.ts.map +1 -1
- package/element.js +3 -3
- package/element.js.map +1 -1
- package/index.js +1 -1
- package/package.json +6 -6
- package/{player-BFRzc5zw.js → player-C7Ih0ExN.js} +63 -35
- package/player-C7Ih0ExN.js.map +1 -0
- package/video/renderer.d.ts +10 -1
- package/video/renderer.d.ts.map +1 -1
- package/video/tab-visible.d.ts +28 -0
- package/video/tab-visible.d.ts.map +1 -0
- package/player-BFRzc5zw.js.map +0 -1
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 {
|
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,EAAY,MAAM,cAAc,CAAC;AACxD,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;
|
|
1
|
+
{"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"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"}
|
package/element.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { h as e, t } from "./player-
|
|
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
4
|
import { Computed as i, Effect as a, Signal as o } from "@norskvideo/moq-signals";
|
|
@@ -33,7 +33,7 @@ var l = [
|
|
|
33
33
|
];
|
|
34
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
38
|
function d(e) {
|
|
39
39
|
let t = e?.trim();
|
|
@@ -298,6 +298,6 @@ var h = new FinalizationRegistry((e) => e.close()), g = class extends HTMLElemen
|
|
|
298
298
|
};
|
|
299
299
|
customElements.define("moq-watch", g);
|
|
300
300
|
//#endregion
|
|
301
|
-
export { g as default, p as parseCatalogFormat };
|
|
301
|
+
export { g as default, p as parseCatalogFormat, u as parseVisible };
|
|
302
302
|
|
|
303
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, 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%\".\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. 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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"}
|
|
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-
|
|
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.
|
|
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.
|
|
42
|
-
"@norskvideo/moq-json": "^0.2.
|
|
43
|
-
"@norskvideo/moq-net": "^0.2.
|
|
44
|
-
"@norskvideo/moq-msf": "^0.2.
|
|
45
|
-
"@norskvideo/moq-signals": "^0.2.
|
|
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
|
}
|
|
@@ -1815,8 +1815,26 @@ async function tt(e) {
|
|
|
1815
1815
|
}
|
|
1816
1816
|
Object.assign(tt, { [Y]: We });
|
|
1817
1817
|
//#endregion
|
|
1818
|
-
//#region src/video/
|
|
1819
|
-
var nt =
|
|
1818
|
+
//#region src/video/tab-visible.ts
|
|
1819
|
+
var nt = class {
|
|
1820
|
+
visible;
|
|
1821
|
+
#e;
|
|
1822
|
+
#t;
|
|
1823
|
+
#n = new i();
|
|
1824
|
+
constructor(e = {}) {
|
|
1825
|
+
let t = e.doc ?? globalThis.document;
|
|
1826
|
+
this.#e = new a(t?.hidden ?? !1), this.visible = new a(!this.#e.peek()), this.#t = o(e.grace ?? 5e3), t && this.#n.event(t, "visibilitychange", () => this.#e.set(t.hidden)), this.#n.run((e) => {
|
|
1827
|
+
if (!e.get(this.#e)) {
|
|
1828
|
+
this.visible.set(!0);
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
e.timer(() => this.visible.set(!1), e.get(this.#t));
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
close() {
|
|
1835
|
+
this.#n.close();
|
|
1836
|
+
}
|
|
1837
|
+
}, rt = .01, it = class {
|
|
1820
1838
|
decoder;
|
|
1821
1839
|
in;
|
|
1822
1840
|
#e = {
|
|
@@ -1827,22 +1845,27 @@ var nt = .01, rt = class {
|
|
|
1827
1845
|
out = s(this.#e);
|
|
1828
1846
|
#t = new a(void 0);
|
|
1829
1847
|
#n = new i();
|
|
1848
|
+
#r;
|
|
1830
1849
|
constructor(e) {
|
|
1831
1850
|
this.decoder = e.decoder, this.in = {
|
|
1832
1851
|
canvas: o(e?.canvas),
|
|
1833
|
-
visible: o(e?.visible ?? "20%")
|
|
1834
|
-
|
|
1852
|
+
visible: o(e?.visible ?? "20%"),
|
|
1853
|
+
hiddenGrace: o(e?.hiddenGrace ?? 5e3)
|
|
1854
|
+
}, this.#r = new nt({
|
|
1855
|
+
doc: e.document,
|
|
1856
|
+
grace: this.in.hiddenGrace
|
|
1857
|
+
}), this.#n.run((e) => {
|
|
1835
1858
|
let t = e.get(this.in.canvas);
|
|
1836
1859
|
this.#t.set(t?.getContext("2d") ?? void 0);
|
|
1837
|
-
}), this.#n.run(this.#
|
|
1860
|
+
}), this.#n.run(this.#a.bind(this)), this.#n.run(this.#o.bind(this)), this.#n.run(this.#i.bind(this));
|
|
1838
1861
|
}
|
|
1839
|
-
#
|
|
1862
|
+
#i(e) {
|
|
1840
1863
|
let t = e.getAll([this.in.canvas, this.decoder.out.display]);
|
|
1841
1864
|
if (!t) return;
|
|
1842
1865
|
let [n, r] = t;
|
|
1843
1866
|
(n.width !== r.width || n.height !== r.height) && (n.width = r.width, n.height = r.height);
|
|
1844
1867
|
}
|
|
1845
|
-
#
|
|
1868
|
+
#a(e) {
|
|
1846
1869
|
let t = e.get(this.in.visible);
|
|
1847
1870
|
if (t === "never") {
|
|
1848
1871
|
this.#e.visible.set(!1);
|
|
@@ -1852,31 +1875,36 @@ var nt = .01, rt = class {
|
|
|
1852
1875
|
this.#e.visible.set(!0), e.cleanup(() => this.#e.visible.set(!1));
|
|
1853
1876
|
return;
|
|
1854
1877
|
}
|
|
1855
|
-
let n = e.get(this.
|
|
1856
|
-
if (
|
|
1878
|
+
let n = e.get(this.#r.visible);
|
|
1879
|
+
if (t === "tab") {
|
|
1880
|
+
this.#e.visible.set(n), e.cleanup(() => this.#e.visible.set(!1));
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
let r = e.get(this.in.canvas);
|
|
1884
|
+
if (!r) {
|
|
1857
1885
|
this.#e.visible.set(!1);
|
|
1858
1886
|
return;
|
|
1859
1887
|
}
|
|
1860
|
-
let
|
|
1861
|
-
this.#e.visible.set(
|
|
1862
|
-
},
|
|
1863
|
-
for (let t of e)
|
|
1864
|
-
},
|
|
1888
|
+
let i = !1, a = () => {
|
|
1889
|
+
this.#e.visible.set(i && n);
|
|
1890
|
+
}, o = (e) => {
|
|
1891
|
+
for (let t of e) i = t.isIntersecting, a();
|
|
1892
|
+
}, s;
|
|
1865
1893
|
try {
|
|
1866
|
-
|
|
1867
|
-
threshold:
|
|
1894
|
+
s = new IntersectionObserver(o, {
|
|
1895
|
+
threshold: rt,
|
|
1868
1896
|
rootMargin: t
|
|
1869
1897
|
});
|
|
1870
1898
|
} catch {
|
|
1871
|
-
console.warn(`moq-watch: invalid visible margin "${t}", using "0px"`),
|
|
1899
|
+
console.warn(`moq-watch: invalid visible margin "${t}", using "0px"`), s = new IntersectionObserver(o, { threshold: rt });
|
|
1872
1900
|
}
|
|
1873
|
-
|
|
1901
|
+
a(), s.observe(r), e.cleanup(() => s.disconnect()), e.cleanup(() => this.#e.visible.set(!1));
|
|
1874
1902
|
}
|
|
1875
|
-
#
|
|
1903
|
+
#o(e) {
|
|
1876
1904
|
let t = e.get(this.#t);
|
|
1877
1905
|
if (!t) return;
|
|
1878
1906
|
let n = e.get(this.decoder.out.frame), i = e.get(this.decoder.source.out.catalog), a = requestAnimationFrame(() => {
|
|
1879
|
-
this.#
|
|
1907
|
+
this.#s(t, n, i), n ? (this.#e.frame.update((e) => (e?.close(), n.clone())), this.#e.timestamp.set(r.Milli.fromMicro(n.timestamp))) : (this.#e.frame.update((e) => {
|
|
1880
1908
|
e?.close();
|
|
1881
1909
|
}), this.#e.timestamp.set(void 0)), a = void 0;
|
|
1882
1910
|
});
|
|
@@ -1884,7 +1912,7 @@ var nt = .01, rt = class {
|
|
|
1884
1912
|
a !== void 0 && cancelAnimationFrame(a);
|
|
1885
1913
|
});
|
|
1886
1914
|
}
|
|
1887
|
-
#
|
|
1915
|
+
#s(e, t, n) {
|
|
1888
1916
|
if (!t) {
|
|
1889
1917
|
e.fillStyle = "#000", e.fillRect(0, 0, e.canvas.width, e.canvas.height);
|
|
1890
1918
|
return;
|
|
@@ -1899,12 +1927,12 @@ var nt = .01, rt = class {
|
|
|
1899
1927
|
close() {
|
|
1900
1928
|
this.#e.frame.update((e) => {
|
|
1901
1929
|
e?.close();
|
|
1902
|
-
}), this.#e.timestamp.set(void 0), this.#n.close();
|
|
1930
|
+
}), this.#e.timestamp.set(void 0), this.#n.close(), this.#r.close();
|
|
1903
1931
|
}
|
|
1904
1932
|
};
|
|
1905
1933
|
//#endregion
|
|
1906
1934
|
//#region src/video/source.ts
|
|
1907
|
-
function
|
|
1935
|
+
function at(e) {
|
|
1908
1936
|
return (t) => {
|
|
1909
1937
|
let n = [], r = [];
|
|
1910
1938
|
for (let [i, a] of t) if (a.codedWidth && a.codedHeight) {
|
|
@@ -1920,7 +1948,7 @@ function it(e) {
|
|
|
1920
1948
|
return n.sort((e, t) => t.size - e.size), n.length > 0 ? n.map((e) => e.name) : r.length > 0 ? (r.sort((e, t) => e.size - t.size), [r[0].name]) : t.map(([e]) => e);
|
|
1921
1949
|
};
|
|
1922
1950
|
}
|
|
1923
|
-
function
|
|
1951
|
+
function ot(e, t) {
|
|
1924
1952
|
return (n) => {
|
|
1925
1953
|
let r = [], i = [];
|
|
1926
1954
|
for (let [a, o] of n) {
|
|
@@ -1950,7 +1978,7 @@ function $(e) {
|
|
|
1950
1978
|
return n.sort((e, t) => t.bitrate - e.bitrate), n.length > 0 ? n.map((e) => e.name) : r.length > 0 ? (r.sort((e, t) => e.bitrate - t.bitrate), [r[0].name]) : t.map(([e]) => e);
|
|
1951
1979
|
};
|
|
1952
1980
|
}
|
|
1953
|
-
function
|
|
1981
|
+
function st(e) {
|
|
1954
1982
|
let t = e[0];
|
|
1955
1983
|
for (let n of e) {
|
|
1956
1984
|
let [, e] = n, [, r] = t, i = (e.codedWidth ?? 0) * (e.codedHeight ?? 0), a = (r.codedWidth ?? 0) * (r.codedHeight ?? 0);
|
|
@@ -1962,15 +1990,15 @@ function ot(e) {
|
|
|
1962
1990
|
}
|
|
1963
1991
|
return t[0];
|
|
1964
1992
|
}
|
|
1965
|
-
function
|
|
1993
|
+
function ct(e) {
|
|
1966
1994
|
let t = Object.entries(e).filter(([, e]) => !e.stalled);
|
|
1967
1995
|
if (t.length > 0) return Object.fromEntries(t);
|
|
1968
1996
|
let n = Object.entries(e);
|
|
1969
1997
|
if (n.length === 0) return {};
|
|
1970
|
-
let r = $(0)(n), i = r.length === 1 ? r[0] :
|
|
1998
|
+
let r = $(0)(n), i = r.length === 1 ? r[0] : ot(0, 0)(n)[0];
|
|
1971
1999
|
return { [i]: e[i] };
|
|
1972
2000
|
}
|
|
1973
|
-
var
|
|
2001
|
+
var lt = class {
|
|
1974
2002
|
in;
|
|
1975
2003
|
#e = {
|
|
1976
2004
|
catalog: new a(void 0),
|
|
@@ -2039,7 +2067,7 @@ var ct = class {
|
|
|
2039
2067
|
e.set(this.#e.track, n.name), e.set(this.#e.config, r);
|
|
2040
2068
|
return;
|
|
2041
2069
|
}
|
|
2042
|
-
let r =
|
|
2070
|
+
let r = ct(t);
|
|
2043
2071
|
if (Object.keys(r).length === 0) return;
|
|
2044
2072
|
let i = n;
|
|
2045
2073
|
if (!n?.bitrate) {
|
|
@@ -2062,7 +2090,7 @@ var ct = class {
|
|
|
2062
2090
|
if (n.length === 0) return;
|
|
2063
2091
|
if (n.length === 1) return n[0][0];
|
|
2064
2092
|
let r = [];
|
|
2065
|
-
if (t?.pixels != null && r.push(
|
|
2093
|
+
if (t?.pixels != null && r.push(at(t.pixels)), (t?.width != null || t?.height != null) && r.push(ot(t.width, t.height)), t?.bitrate != null && r.push($(t.bitrate)), r.length === 0) return st(n);
|
|
2066
2094
|
let i = r.map((e) => e(n)), a = i.map((e) => new Set(e));
|
|
2067
2095
|
for (let e of i[0]) if (a.every((t) => t.has(e))) return e;
|
|
2068
2096
|
console.warn("conflicting rendition filters, no rendition satisfies all criteria");
|
|
@@ -2070,7 +2098,7 @@ var ct = class {
|
|
|
2070
2098
|
close() {
|
|
2071
2099
|
this.#t.close();
|
|
2072
2100
|
}
|
|
2073
|
-
},
|
|
2101
|
+
}, ut = class {
|
|
2074
2102
|
in;
|
|
2075
2103
|
broadcast;
|
|
2076
2104
|
sync;
|
|
@@ -2105,7 +2133,7 @@ var ct = class {
|
|
|
2105
2133
|
audioTarget: o(e.audioTarget),
|
|
2106
2134
|
captions: o(e.captions)
|
|
2107
2135
|
}), this.broadcast = new R(this.in), this.#r.cleanup(() => this.broadcast.close());
|
|
2108
|
-
let t = new
|
|
2136
|
+
let t = new lt({
|
|
2109
2137
|
broadcast: this.broadcast,
|
|
2110
2138
|
target: this.in.target,
|
|
2111
2139
|
supported: Q.supported,
|
|
@@ -2139,7 +2167,7 @@ var ct = class {
|
|
|
2139
2167
|
volume: this.in.volume,
|
|
2140
2168
|
muted: this.in.muted,
|
|
2141
2169
|
paused: this.in.paused
|
|
2142
|
-
}), this.renderer = new
|
|
2170
|
+
}), this.renderer = new it({
|
|
2143
2171
|
decoder: this.video,
|
|
2144
2172
|
canvas: this.in.canvas,
|
|
2145
2173
|
visible: this.in.visible
|
|
@@ -2167,6 +2195,6 @@ var ct = class {
|
|
|
2167
2195
|
}
|
|
2168
2196
|
};
|
|
2169
2197
|
//#endregion
|
|
2170
|
-
export { Ae as _, J as a, G as c, W as d, V as f, N as g, Le as h, Q as i, H as l, R as m,
|
|
2198
|
+
export { Ae as _, J as a, G as c, W as d, V as f, N as g, Le as h, Q as i, H as l, R as m, lt as n, q as o, B as p, it as r, K as s, ut as t, U as u, j as v };
|
|
2171
2199
|
|
|
2172
|
-
//# sourceMappingURL=player-
|
|
2200
|
+
//# sourceMappingURL=player-C7Ih0ExN.js.map
|