avbridge 2.9.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/dist/{chunk-EY6DZEDT.cjs → chunk-37UOSAVI.cjs} +55 -10
- package/dist/chunk-37UOSAVI.cjs.map +1 -0
- package/dist/{chunk-5KVLE6YI.js → chunk-EDDWAN2L.js} +3 -2
- package/dist/chunk-EDDWAN2L.js.map +1 -0
- package/dist/{chunk-SN4WZE24.js → chunk-IHNHHEA2.js} +51 -6
- package/dist/chunk-IHNHHEA2.js.map +1 -0
- package/dist/{chunk-S4WAZC2T.cjs → chunk-WRKO6Q42.cjs} +3 -2
- package/dist/chunk-WRKO6Q42.cjs.map +1 -0
- package/dist/element-browser.js +63 -4
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +18 -5
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +1 -1
- package/dist/element.d.ts +1 -1
- package/dist/element.js +17 -4
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +10 -10
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{player-DEcidWk6.d.cts → player-DDdNVFDv.d.cts} +23 -1
- package/dist/{player-DEcidWk6.d.ts → player-DDdNVFDv.d.ts} +23 -1
- package/dist/player.cjs +329 -109
- package/dist/player.cjs.map +1 -1
- package/dist/player.d.cts +42 -0
- package/dist/player.d.ts +42 -0
- package/dist/player.js +325 -105
- package/dist/player.js.map +1 -1
- package/dist/subtitles-5H24MEBJ.js +4 -0
- package/dist/{subtitles-4T74JRGT.js.map → subtitles-5H24MEBJ.js.map} +1 -1
- package/dist/subtitles-HMVGWTU2.cjs +29 -0
- package/dist/{subtitles-QUH4LPI4.cjs.map → subtitles-HMVGWTU2.cjs.map} +1 -1
- package/package.json +1 -1
- package/src/element/avbridge-player.ts +235 -78
- package/src/element/avbridge-subtitles.ts +273 -0
- package/src/element/avbridge-video.ts +21 -1
- package/src/element/player-styles.ts +85 -35
- package/src/index.ts +1 -0
- package/src/strategies/fallback/audio-output.ts +39 -4
- package/src/strategies/fallback/index.ts +12 -0
- package/src/strategies/hybrid/index.ts +9 -0
- package/src/subtitles/index.ts +2 -0
- package/src/types.ts +25 -0
- package/dist/chunk-5KVLE6YI.js.map +0 -1
- package/dist/chunk-EY6DZEDT.cjs.map +0 -1
- package/dist/chunk-S4WAZC2T.cjs.map +0 -1
- package/dist/chunk-SN4WZE24.js.map +0 -1
- package/dist/subtitles-4T74JRGT.js +0 -4
- package/dist/subtitles-QUH4LPI4.cjs +0 -29
|
@@ -133,6 +133,7 @@ async function attachSubtitleTracks(video, tracks, bag, onError, transport) {
|
|
|
133
133
|
}
|
|
134
134
|
for (const t of tracks) {
|
|
135
135
|
if (!t.sidecarUrl) continue;
|
|
136
|
+
console.log(`[avbridge:subs] attaching track id=${t.id} format=${t.format} url=${t.sidecarUrl.slice(0, 60)}`);
|
|
136
137
|
try {
|
|
137
138
|
let url = t.sidecarUrl;
|
|
138
139
|
if (t.format === "srt") {
|
|
@@ -163,5 +164,5 @@ async function attachSubtitleTracks(video, tracks, bag, onError, transport) {
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
export { SubtitleOverlay, SubtitleResourceBag, attachSubtitleTracks, discoverSidecars, srtToVtt };
|
|
166
|
-
//# sourceMappingURL=chunk-
|
|
167
|
-
//# sourceMappingURL=chunk-
|
|
167
|
+
//# sourceMappingURL=chunk-EDDWAN2L.js.map
|
|
168
|
+
//# sourceMappingURL=chunk-EDDWAN2L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/subtitles/srt.ts","../src/subtitles/vtt.ts","../src/subtitles/render.ts","../src/subtitles/index.ts"],"names":[],"mappings":"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","file":"chunk-EDDWAN2L.js","sourcesContent":["/**\n * SRT → WebVTT converter.\n *\n * SRT cues:\n *\n * 1\n * 00:00:20,000 --> 00:00:24,400\n * Subtitle text, possibly multiple lines.\n *\n * WebVTT cues:\n *\n * WEBVTT\n *\n * 00:00:20.000 --> 00:00:24.400\n * Subtitle text, possibly multiple lines.\n *\n * The differences in v1 are:\n * - leading `WEBVTT` magic line\n * - `,` → `.` for milliseconds\n * - cue index lines are stripped (WebVTT allows them but SRT-style ints can\n * confuse some parsers; we drop them)\n * - BOM is stripped\n */\nexport function srtToVtt(srt: string): string {\n // Strip BOM\n if (srt.charCodeAt(0) === 0xfeff) srt = srt.slice(1);\n // Normalize line endings\n const normalized = srt.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").trim();\n\n const blocks = normalized.split(/\\n{2,}/);\n const out: string[] = [\"WEBVTT\", \"\"];\n\n for (const block of blocks) {\n const lines = block.split(\"\\n\");\n // Drop the leading numeric index, if present.\n if (lines.length > 0 && /^\\d+$/.test(lines[0].trim())) {\n lines.shift();\n }\n if (lines.length === 0) continue;\n\n const timing = lines.shift()!;\n const vttTiming = convertTiming(timing);\n if (!vttTiming) continue; // skip malformed cue\n\n out.push(vttTiming);\n for (const l of lines) out.push(l);\n out.push(\"\");\n }\n\n return out.join(\"\\n\");\n}\n\nfunction convertTiming(line: string): string | null {\n // SRT: HH:MM:SS,mmm --> HH:MM:SS,mmm (optional cue settings after)\n const m = /^(\\d{1,2}):(\\d{2}):(\\d{2})[,.](\\d{1,3})\\s*-->\\s*(\\d{1,2}):(\\d{2}):(\\d{2})[,.](\\d{1,3})(.*)$/.exec(\n line.trim(),\n );\n if (!m) return null;\n const fmt = (h: string, mm: string, s: string, ms: string) =>\n `${h.padStart(2, \"0\")}:${mm}:${s}.${ms.padEnd(3, \"0\").slice(0, 3)}`;\n return `${fmt(m[1], m[2], m[3], m[4])} --> ${fmt(m[5], m[6], m[7], m[8])}${m[9] ?? \"\"}`;\n}\n","/** Light validation for incoming VTT — we do not parse cues, just confirm header. */\nexport function isVtt(text: string): boolean {\n const trimmed = text.replace(/^\\ufeff/, \"\").trimStart();\n return trimmed.startsWith(\"WEBVTT\");\n}\n","/**\n * Custom subtitle overlay for the fallback strategy. We don't have a `<video>`\n * with text tracks here, so we render cues into a positioned div ourselves.\n *\n * v1 only handles plain-text WebVTT cues with `HH:MM:SS.mmm` timing. Cue\n * settings, voice tags, and styling are ignored.\n */\n\ninterface Cue {\n start: number;\n end: number;\n text: string;\n}\n\nexport class SubtitleOverlay {\n private el: HTMLDivElement;\n private cues: Cue[] = [];\n\n constructor(parent: HTMLElement) {\n this.el = document.createElement(\"div\");\n this.el.style.cssText =\n \"position:absolute;left:0;right:0;bottom:8%;text-align:center;color:white;text-shadow:0 0 4px black;font-family:sans-serif;font-size:1.4em;pointer-events:none;\";\n parent.appendChild(this.el);\n }\n\n loadVtt(text: string): void {\n this.cues = parseVtt(text);\n }\n\n update(currentTime: number): void {\n const active = this.cues.find((c) => currentTime >= c.start && currentTime <= c.end);\n this.el.textContent = active?.text ?? \"\";\n }\n\n /** Set the currently-displayed text directly (bypasses loadVtt/update). */\n setText(text: string): void {\n // Only touch the DOM if it actually changed — rAF tick runs 60Hz.\n if (this.el.textContent !== text) {\n this.el.textContent = text;\n }\n }\n\n destroy(): void {\n this.el.remove();\n this.cues = [];\n }\n}\n\nfunction parseVtt(text: string): Cue[] {\n const cues: Cue[] = [];\n const blocks = text.replace(/\\r\\n/g, \"\\n\").split(/\\n{2,}/);\n for (const block of blocks) {\n const lines = block.split(\"\\n\").filter(Boolean);\n if (lines.length === 0 || lines[0] === \"WEBVTT\") continue;\n const timingIdx = lines.findIndex((l) => l.includes(\"-->\"));\n if (timingIdx < 0) continue;\n const m = /(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})\\s*-->\\s*(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})/.exec(\n lines[timingIdx],\n );\n if (!m) continue;\n const t = (h: string, mm: string, s: string, ms: string) =>\n Number(h) * 3600 + Number(mm) * 60 + Number(s) + Number(ms) / 1000;\n cues.push({\n start: t(m[1], m[2], m[3], m[4]),\n end: t(m[5], m[6], m[7], m[8]),\n text: lines.slice(timingIdx + 1).join(\"\\n\"),\n });\n }\n return cues;\n}\n","import type { SubtitleTrackInfo, TransportConfig } from \"../types.js\";\nimport { fetchWith } from \"../util/transport.js\";\nimport { srtToVtt } from \"./srt.js\";\nimport { isVtt } from \"./vtt.js\";\n\nexport { srtToVtt } from \"./srt.js\";\nexport { SubtitleOverlay } from \"./render.js\";\n\n/**\n * Discover sidecar `.srt` / `.vtt` files next to the source. Requires the\n * caller to pass a `FileSystemDirectoryHandle` (e.g. via the File System\n * Access API). Without that handle we can't enumerate sibling files.\n *\n * The returned `url` fields are blob URLs created via `URL.createObjectURL`.\n * They must be revoked by the caller (e.g. via `revokeSubtitleResources()`)\n * when the player tears down or the source changes — otherwise repeated\n * source swaps in a single-page app will leak.\n */\nexport interface DiscoveredSidecar {\n url: string;\n format: \"srt\" | \"vtt\";\n language?: string;\n}\n\nexport async function discoverSidecars(\n file: File,\n directory: FileSystemDirectoryHandle,\n): Promise<DiscoveredSidecar[]> {\n const baseName = file.name.replace(/\\.[^.]+$/, \"\");\n const found: DiscoveredSidecar[] = [];\n\n // Walk the directory and look for `${baseName}*.srt` / `*.vtt`.\n for await (const [name, handle] of (directory as unknown as AsyncIterable<[string, FileSystemHandle]>)) {\n if (handle.kind !== \"file\") continue;\n if (!name.startsWith(baseName)) continue;\n const lower = name.toLowerCase();\n let format: \"srt\" | \"vtt\" | null = null;\n if (lower.endsWith(\".srt\")) format = \"srt\";\n else if (lower.endsWith(\".vtt\")) format = \"vtt\";\n if (!format) continue;\n\n const sidecarFile = await (handle as FileSystemFileHandle).getFile();\n const url = URL.createObjectURL(sidecarFile);\n\n // Try to extract a language tag (eg. movie.en.srt → \"en\").\n const langMatch = name.slice(baseName.length).match(/[._-]([a-z]{2,3})(?:[._-]|\\.)/i);\n found.push({\n url,\n format,\n language: langMatch?.[1],\n });\n }\n\n return found;\n}\n\n/**\n * Owns every blob URL created during sidecar discovery and SRT→VTT\n * conversion for a single player session. Revoking the bag releases all of\n * them in one shot at teardown.\n */\nexport class SubtitleResourceBag {\n private urls = new Set<string>();\n\n /** Track an externally-created blob URL (e.g. from `discoverSidecars`). */\n track(url: string): void {\n this.urls.add(url);\n }\n\n /** Convenience: create a blob URL and track it in one call. */\n createObjectURL(blob: Blob): string {\n const url = URL.createObjectURL(blob);\n this.urls.add(url);\n return url;\n }\n\n /** Revoke every tracked URL. Idempotent — safe to call multiple times. */\n revokeAll(): void {\n for (const u of this.urls) URL.revokeObjectURL(u);\n this.urls.clear();\n }\n}\n\n/**\n * Attach `<track>` elements for each subtitle to the player's `<video>`. SRT\n * sources are converted to VTT first via blob URLs because `<track>` only\n * accepts WebVTT.\n *\n * Pass a {@link SubtitleResourceBag} so the player can revoke the generated\n * blob URLs at teardown. Without one, every SRT subtitle leaks a blob URL\n * per attach.\n *\n * Errors during fetch/parse are caught per-track and reported via the\n * `onError` callback (if provided) so a single bad subtitle doesn't break\n * bootstrap. Subtitles are *not* load-bearing for playback.\n */\nexport async function attachSubtitleTracks(\n video: HTMLVideoElement,\n tracks: SubtitleTrackInfo[],\n bag?: SubtitleResourceBag,\n onError?: (err: Error, track: SubtitleTrackInfo) => void,\n transport?: TransportConfig,\n): Promise<void> {\n const doFetch = fetchWith(transport);\n\n // Clear existing dynamically-attached tracks.\n for (const t of Array.from(video.querySelectorAll(\"track[data-avbridge]\"))) {\n t.remove();\n }\n\n for (const t of tracks) {\n if (!t.sidecarUrl) continue;\n // eslint-disable-next-line no-console\n console.log(`[avbridge:subs] attaching track id=${t.id} format=${t.format} url=${t.sidecarUrl.slice(0, 60)}`);\n try {\n let url = t.sidecarUrl;\n if (t.format === \"srt\") {\n const res = await doFetch(t.sidecarUrl, transport?.requestInit);\n const text = await res.text();\n const vtt = srtToVtt(text);\n const blob = new Blob([vtt], { type: \"text/vtt\" });\n url = bag ? bag.createObjectURL(blob) : URL.createObjectURL(blob);\n } else if (t.format === \"vtt\") {\n // Validate quickly so a malformed file fails loudly here.\n const res = await doFetch(t.sidecarUrl, transport?.requestInit);\n const text = await res.text();\n if (!isVtt(text)) {\n // eslint-disable-next-line no-console\n console.warn(\"[avbridge] subtitle missing WEBVTT header:\", t.sidecarUrl);\n }\n }\n const trackEl = document.createElement(\"track\");\n trackEl.kind = \"subtitles\";\n trackEl.src = url;\n trackEl.srclang = t.language ?? \"und\";\n trackEl.label = t.language ?? `Subtitle ${t.id}`;\n trackEl.dataset.avbridge = \"true\";\n video.appendChild(trackEl);\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n onError?.(e, t);\n }\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SubtitleResourceBag, discoverSidecars, attachSubtitleTracks, SubtitleOverlay } from './chunk-
|
|
1
|
+
import { SubtitleResourceBag, discoverSidecars, attachSubtitleTracks, SubtitleOverlay } from './chunk-EDDWAN2L.js';
|
|
2
2
|
import { probe, avbridgeVideoToMediabunny, buildMediabunnySourceFromInput, avbridgeAudioToMediabunny } from './chunk-GYIJU44C.js';
|
|
3
3
|
import { AvbridgeError, ERR_ALL_STRATEGIES_EXHAUSTED, ERR_PLAYER_NOT_READY, ERR_MSE_NOT_SUPPORTED, ERR_MSE_CODEC_NOT_SUPPORTED } from './chunk-CPJLFFCC.js';
|
|
4
4
|
import { packetPtsSec, sanitizePacketTimestamp, sanitizeFrameTimestamp, libavFrameToInterleavedFloat32 } from './chunk-2NSOOMXW.js';
|
|
@@ -1374,6 +1374,10 @@ var AudioOutput = class {
|
|
|
1374
1374
|
_volume = 1;
|
|
1375
1375
|
/** User-set muted flag. When true, gain is forced to 0. */
|
|
1376
1376
|
_muted = false;
|
|
1377
|
+
/** Playback rate. Scales the media clock and each AudioBufferSourceNode's
|
|
1378
|
+
* playbackRate so audio pitches up/down accordingly (same as native
|
|
1379
|
+
* <video>.playbackRate). Default 1. */
|
|
1380
|
+
_rate = 1;
|
|
1377
1381
|
constructor() {
|
|
1378
1382
|
this.ctx = new AudioContext();
|
|
1379
1383
|
this.gain = this.ctx.createGain();
|
|
@@ -1395,6 +1399,20 @@ var AudioOutput = class {
|
|
|
1395
1399
|
getMuted() {
|
|
1396
1400
|
return this._muted;
|
|
1397
1401
|
}
|
|
1402
|
+
/** Set playback rate. Scales the media clock and pitches audio output
|
|
1403
|
+
* (same as native <video>.playbackRate — speed without pitch correction).
|
|
1404
|
+
* Rebases the anchor so the clock transition is seamless. */
|
|
1405
|
+
setPlaybackRate(rate) {
|
|
1406
|
+
if (rate === this._rate) return;
|
|
1407
|
+
const t = this.now();
|
|
1408
|
+
this.mediaTimeOfAnchor = t;
|
|
1409
|
+
this.ctxTimeAtAnchor = this.ctx.currentTime;
|
|
1410
|
+
this.wallAnchorMs = performance.now();
|
|
1411
|
+
this._rate = rate;
|
|
1412
|
+
}
|
|
1413
|
+
getPlaybackRate() {
|
|
1414
|
+
return this._rate;
|
|
1415
|
+
}
|
|
1398
1416
|
applyGain() {
|
|
1399
1417
|
const target = this._muted ? 0 : this._volume;
|
|
1400
1418
|
try {
|
|
@@ -1415,12 +1433,12 @@ var AudioOutput = class {
|
|
|
1415
1433
|
now() {
|
|
1416
1434
|
if (this.noAudio) {
|
|
1417
1435
|
if (this.state === "playing") {
|
|
1418
|
-
return this.mediaTimeOfAnchor + (performance.now() - this.wallAnchorMs) / 1e3;
|
|
1436
|
+
return this.mediaTimeOfAnchor + (performance.now() - this.wallAnchorMs) / 1e3 * this._rate;
|
|
1419
1437
|
}
|
|
1420
1438
|
return this.mediaTimeOfAnchor;
|
|
1421
1439
|
}
|
|
1422
1440
|
if (this.state === "playing") {
|
|
1423
|
-
return this.mediaTimeOfAnchor + (this.ctx.currentTime - this.ctxTimeAtAnchor);
|
|
1441
|
+
return this.mediaTimeOfAnchor + (this.ctx.currentTime - this.ctxTimeAtAnchor) * this._rate;
|
|
1424
1442
|
}
|
|
1425
1443
|
return this.mediaTimeOfAnchor;
|
|
1426
1444
|
}
|
|
@@ -1472,7 +1490,8 @@ var AudioOutput = class {
|
|
|
1472
1490
|
const node = this.ctx.createBufferSource();
|
|
1473
1491
|
node.buffer = buffer;
|
|
1474
1492
|
node.connect(this.gain);
|
|
1475
|
-
|
|
1493
|
+
if (this._rate !== 1) node.playbackRate.value = this._rate;
|
|
1494
|
+
let ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor) / this._rate;
|
|
1476
1495
|
if (ctxStart < this.ctx.currentTime) {
|
|
1477
1496
|
this.ctxTimeAtAnchor = this.ctx.currentTime;
|
|
1478
1497
|
this.mediaTimeOfAnchor = this.mediaTimeOfNext;
|
|
@@ -1499,6 +1518,10 @@ var AudioOutput = class {
|
|
|
1499
1518
|
if (this.ctx.state === "suspended") {
|
|
1500
1519
|
await this.ctx.resume();
|
|
1501
1520
|
}
|
|
1521
|
+
try {
|
|
1522
|
+
this.gain.connect(this.ctx.destination);
|
|
1523
|
+
} catch {
|
|
1524
|
+
}
|
|
1502
1525
|
if (this.state === "paused") {
|
|
1503
1526
|
this.ctxTimeAtAnchor = this.ctx.currentTime;
|
|
1504
1527
|
this.state = "playing";
|
|
@@ -1525,6 +1548,10 @@ var AudioOutput = class {
|
|
|
1525
1548
|
this.mediaTimeOfAnchor = this.now();
|
|
1526
1549
|
this.state = "paused";
|
|
1527
1550
|
if (this.noAudio) return;
|
|
1551
|
+
try {
|
|
1552
|
+
this.gain.disconnect();
|
|
1553
|
+
} catch {
|
|
1554
|
+
}
|
|
1528
1555
|
if (this.ctx.state === "running") {
|
|
1529
1556
|
await this.ctx.suspend();
|
|
1530
1557
|
}
|
|
@@ -2120,6 +2147,14 @@ async function createHybridSession(ctx, target, transport) {
|
|
|
2120
2147
|
get: () => ctx.duration ?? NaN
|
|
2121
2148
|
});
|
|
2122
2149
|
}
|
|
2150
|
+
Object.defineProperty(target, "playbackRate", {
|
|
2151
|
+
configurable: true,
|
|
2152
|
+
get: () => audio.getPlaybackRate(),
|
|
2153
|
+
set: (v) => {
|
|
2154
|
+
audio.setPlaybackRate(v);
|
|
2155
|
+
target.dispatchEvent(new Event("ratechange"));
|
|
2156
|
+
}
|
|
2157
|
+
});
|
|
2123
2158
|
Object.defineProperty(target, "readyState", {
|
|
2124
2159
|
configurable: true,
|
|
2125
2160
|
get: () => {
|
|
@@ -2230,6 +2265,7 @@ async function createHybridSession(ctx, target, transport) {
|
|
|
2230
2265
|
delete target.muted;
|
|
2231
2266
|
delete target.readyState;
|
|
2232
2267
|
delete target.seekable;
|
|
2268
|
+
delete target.playbackRate;
|
|
2233
2269
|
} catch {
|
|
2234
2270
|
}
|
|
2235
2271
|
},
|
|
@@ -2765,6 +2801,14 @@ async function createFallbackSession(ctx, target, transport) {
|
|
|
2765
2801
|
get: () => ctx.duration ?? NaN
|
|
2766
2802
|
});
|
|
2767
2803
|
}
|
|
2804
|
+
Object.defineProperty(target, "playbackRate", {
|
|
2805
|
+
configurable: true,
|
|
2806
|
+
get: () => audio.getPlaybackRate(),
|
|
2807
|
+
set: (v) => {
|
|
2808
|
+
audio.setPlaybackRate(v);
|
|
2809
|
+
target.dispatchEvent(new Event("ratechange"));
|
|
2810
|
+
}
|
|
2811
|
+
});
|
|
2768
2812
|
Object.defineProperty(target, "readyState", {
|
|
2769
2813
|
configurable: true,
|
|
2770
2814
|
get: () => {
|
|
@@ -2896,6 +2940,7 @@ async function createFallbackSession(ctx, target, transport) {
|
|
|
2896
2940
|
delete target.muted;
|
|
2897
2941
|
delete target.readyState;
|
|
2898
2942
|
delete target.seekable;
|
|
2943
|
+
delete target.playbackRate;
|
|
2899
2944
|
} catch {
|
|
2900
2945
|
}
|
|
2901
2946
|
},
|
|
@@ -3498,5 +3543,5 @@ function defaultFallbackChain(strategy) {
|
|
|
3498
3543
|
}
|
|
3499
3544
|
|
|
3500
3545
|
export { FALLBACK_AUDIO_CODECS, FALLBACK_VIDEO_CODECS, NATIVE_AUDIO_CODECS, NATIVE_VIDEO_CODECS, UnifiedPlayer, classifyContext, createPlayer };
|
|
3501
|
-
//# sourceMappingURL=chunk-
|
|
3502
|
-
//# sourceMappingURL=chunk-
|
|
3546
|
+
//# sourceMappingURL=chunk-IHNHHEA2.js.map
|
|
3547
|
+
//# sourceMappingURL=chunk-IHNHHEA2.js.map
|