@bardway/trpg-media 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/NOTICE +10 -0
- package/NOTICE.md +1 -0
- package/README.md +23 -2
- package/dist/AudioByteCache.d.ts +8 -0
- package/dist/DisplayAudioController.d.ts +2 -0
- package/dist/MediaPreloader.d.ts +12 -0
- package/dist/RuntimeLogger.d.ts +4 -0
- package/dist/audio/HtmlAudioSessionBackend.d.ts +36 -0
- package/dist/audio/SessionAudioBackend.d.ts +19 -0
- package/dist/audio/SessionAudioEngine.d.ts +93 -0
- package/dist/audio/SessionAudioPlaybackPolicy.d.ts +8 -0
- package/dist/audio.d.ts +5 -0
- package/dist/audio.js +625 -0
- package/dist/audio.js.map +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +493 -0
- package/dist/index.js.map +7 -0
- package/dist/sessionDisplayPreload.d.ts +15 -0
- package/dist/sessionDisplayResourceRetry.d.ts +11 -0
- package/dist/tuning/SessionTuningMigration.d.ts +9 -0
- package/dist/tuning/SessionTuningResolver.d.ts +12 -0
- package/dist/tuning/SessionTuningSchema.d.ts +27 -0
- package/dist/tuning/SessionTuningStore.d.ts +31 -0
- package/dist/tuning/SessionTuningTypes.d.ts +50 -0
- package/dist/tuning.d.ts +5 -0
- package/dist/tuning.js +292 -0
- package/dist/tuning.js.map +7 -0
- package/dist/video-poster.d.ts +77 -0
- package/dist/video.js +259 -0
- package/dist/video.js.map +7 -0
- package/package.json +49 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/AudioByteCache.ts", "../src/audio/SessionAudioEngine.ts", "../src/audio/HtmlAudioSessionBackend.ts", "../src/audio/SessionAudioPlaybackPolicy.ts"],
|
|
4
|
+
"sourcesContent": ["/** Instance ownership allows a host to share a preload cache without sharing accounts or sessions. */\nexport class AudioByteCache {\n private readonly records = new Map<\n string,\n { controller: AbortController; promise: Promise<ArrayBuffer> }\n >();\n constructor(private readonly fetchAudio: typeof fetch = fetch) {}\n\n async load(url: string): Promise<ArrayBuffer> {\n let record = this.records.get(url);\n if (!record) {\n const controller = new AbortController();\n const promise = this.fetchAudio(url, { signal: controller.signal }).then(async (response) => {\n if (!response.ok) throw new Error(`Audio request failed: ${response.status}`);\n const bytes = await response.arrayBuffer();\n if (controller.signal.aborted) throw new DOMException('Audio load cancelled', 'AbortError');\n return bytes;\n });\n record = { controller, promise };\n this.records.set(url, record);\n const owner = record;\n void promise.catch(() => {\n if (this.records.get(url) === owner) this.records.delete(url);\n });\n }\n return (await record.promise).slice(0);\n }\n\n clear(): void {\n for (const record of this.records.values()) record.controller.abort();\n this.records.clear();\n }\n}\n", "import { AudioByteCache } from '../AudioByteCache.js';\n\nexport type SessionAudioBus = string;\nexport type SessionAudioEngineState = 'blocked' | 'initializing' | 'ready' | 'failed';\n\nexport interface SessionAudioCueDefinition {\n id: string;\n asset: string;\n bus: SessionAudioBus;\n gain: number;\n polyphony: number;\n cooldownMs: number;\n pitchMin?: number;\n pitchMax?: number;\n panMin?: number;\n panMax?: number;\n loop?: boolean;\n startOffsetMs?: number;\n fadeInMs?: number;\n fadeOutMs?: number;\n stealMode?: 'oldest' | 'ignoreNewest';\n}\n\nexport interface SessionAudioPlayOptions {\n signal?: AbortSignal;\n bus: SessionAudioBus;\n loop?: boolean;\n volume?: number;\n volumeCeiling?: number;\n playbackRate?: number;\n pan?: number;\n startOffsetMs?: number;\n fadeInMs?: number;\n fadeOutMs?: number;\n onEnded?: () => void;\n}\n\nexport interface SessionAudioCuePlayOptions {\n signal?: AbortSignal;\n deterministicSeed?: string;\n onEnded?: () => void;\n volume?: number;\n volumeCeiling?: number;\n playbackRate?: number;\n pan?: number;\n voiceGroupId?: string;\n cooldownId?: string;\n maxVoices?: number;\n cooldownMs?: number;\n}\n\nexport class SessionAudioEngine {\n private readonly bytes: AudioByteCache;\n private context: AudioContext | null = null;\n private master: GainNode | null = null;\n private readonly buses = new Map<SessionAudioBus, GainNode>();\n private readonly busGains = new Map<SessionAudioBus, number>();\n private readonly buffers = new Map<string, Promise<AudioBuffer>>();\n private readonly activeVoices = new Map<string, AudioBufferSourceNode[]>();\n private readonly playbackCleanups = new Map<AudioBufferSourceNode, () => void>();\n private readonly lastCueTimes = new Map<string, number>();\n private readonly busMute = new Set<SessionAudioBus>();\n private soloBus: SessionAudioBus | null = null;\n private masterGain = 1;\n private fieldSource: AudioBufferSourceNode | null = null;\n private fieldUrl: string | null = null;\n private fieldRevision = 0;\n private unlockGeneration = 0;\n private destroying = false;\n private _state: SessionAudioEngineState = 'blocked';\n\n constructor(\n private readonly createContext: () => AudioContext = () => new AudioContext(),\n fetchAudio: typeof fetch = fetch,\n private readonly busIds: readonly string[] = [\n 'ui',\n 'action',\n 'impact',\n 'status',\n 'dice',\n 'field',\n 'cinematic',\n ],\n private readonly limiterBuses: readonly string[] = ['dice'],\n private readonly report: (error: unknown) => void = () => undefined,\n ) {\n if (\n busIds.length === 0 ||\n new Set(busIds).size !== busIds.length ||\n busIds.some((bus) => !bus.trim())\n )\n throw new Error('Audio bus IDs must be nonempty and unique');\n this.bytes = new AudioByteCache(fetchAudio.bind(globalThis));\n }\n\n get state(): SessionAudioEngineState {\n return this._state;\n }\n\n async unlock(preloadUrls: string[] = []): Promise<boolean> {\n if (this.destroying) return false;\n if (this._state === 'ready') return true;\n const generation = ++this.unlockGeneration;\n this._state = 'initializing';\n try {\n this.ensureGraph();\n await this.context!.resume();\n if (generation !== this.unlockGeneration) return false;\n this._state = 'ready';\n // Browser permission is granted by resume(). Preloading every cue must\n // remain best-effort so one missing or slow optional sound cannot keep\n // the user-facing audio control in its blocked state.\n void this.preload(preloadUrls);\n return true;\n } catch (error) {\n if (generation !== this.unlockGeneration) return false;\n this._state =\n error instanceof DOMException &&\n (error.name === 'NotAllowedError' || error.name === 'AbortError')\n ? 'blocked'\n : 'failed';\n return false;\n }\n }\n\n async preload(urls: readonly string[]): Promise<void> {\n await Promise.allSettled(urls.map((url) => this.load(url)));\n }\n\n async play(url: string, options: SessionAudioPlayOptions): Promise<AudioBufferSourceNode | null> {\n if (this._state !== 'ready' || options.signal?.aborted) return null;\n const context = this.context;\n const generation = this.unlockGeneration;\n let cleanup: (() => void) | undefined;\n try {\n const buffer = await this.load(url);\n if (\n this._state !== 'ready' ||\n !this.context ||\n this.context !== context ||\n generation !== this.unlockGeneration ||\n options.signal?.aborted\n )\n return null;\n const source = this.context.createBufferSource();\n const voiceGain = this.context.createGain();\n const panner =\n 'createStereoPanner' in this.context &&\n typeof this.context.createStereoPanner === 'function'\n ? this.context.createStereoPanner()\n : null;\n const now = this.context.currentTime;\n const volumeCeiling = Math.max(1, Math.min(4, options.volumeCeiling ?? 1));\n const volume = Math.max(0, Math.min(volumeCeiling, options.volume ?? 1));\n const fadeInSeconds = Math.max(0, options.fadeInMs ?? 0) / 1000;\n voiceGain.gain.setValueAtTime(fadeInSeconds > 0 ? 0 : volume, now);\n if (fadeInSeconds > 0) voiceGain.gain.linearRampToValueAtTime(volume, now + fadeInSeconds);\n source.buffer = buffer;\n source.loop = options.loop ?? false;\n const playbackRate = source.playbackRate;\n if (playbackRate) playbackRate.value = Math.max(0.01, options.playbackRate ?? 1);\n source.connect(voiceGain);\n if (panner) {\n panner.pan.value = Math.max(-1, Math.min(1, options.pan ?? 0));\n voiceGain.connect(panner);\n panner.connect(this.buses.get(options.bus)!);\n } else voiceGain.connect(this.buses.get(options.bus)!);\n let finished = false;\n const finish = () => {\n if (finished) return;\n finished = true;\n options.signal?.removeEventListener('abort', abort);\n this.playbackCleanups.delete(source);\n source.disconnect();\n voiceGain.disconnect();\n panner?.disconnect();\n try {\n options.onEnded?.();\n } catch (error) {\n this.reportSafely(error);\n }\n };\n const abort = () => {\n try {\n source.stop();\n } finally {\n finish();\n }\n };\n cleanup = finish;\n source.onended = finish;\n this.playbackCleanups.set(source, finish);\n options.signal?.addEventListener('abort', abort, { once: true });\n if (options.signal?.aborted) {\n finish();\n return null;\n }\n source.start(0, Math.max(0, options.startOffsetMs ?? 0) / 1000);\n return source;\n } catch {\n cleanup?.();\n if (\n options.signal?.aborted ||\n this.context !== context ||\n generation !== this.unlockGeneration\n )\n return null;\n this._state = 'failed';\n return null;\n }\n }\n\n async playCue(\n cue: SessionAudioCueDefinition,\n options?: SessionAudioCuePlayOptions,\n ): Promise<AudioBufferSourceNode | null>;\n async playCue(\n cue: SessionAudioCueDefinition,\n deterministicSeed?: string,\n onEnded?: () => void,\n ): Promise<AudioBufferSourceNode | null>;\n async playCue(\n cue: SessionAudioCueDefinition,\n optionsOrSeed: SessionAudioCuePlayOptions | string = {},\n legacyOnEnded?: () => void,\n ): Promise<AudioBufferSourceNode | null> {\n const options =\n typeof optionsOrSeed === 'string'\n ? { deterministicSeed: optionsOrSeed, onEnded: legacyOnEnded }\n : optionsOrSeed;\n const deterministicSeed = options.deterministicSeed ?? cue.id;\n const onEnded = options.onEnded;\n if (this._state !== 'ready' || !this.context || options.signal?.aborted) return null;\n const nowMs = this.context.currentTime * 1000;\n const cooldownId = options.cooldownId ?? cue.id;\n const voiceGroupId = options.voiceGroupId ?? cue.id;\n const lastTime = this.lastCueTimes.get(cooldownId);\n if (\n lastTime !== undefined &&\n nowMs - lastTime < Math.max(0, options.cooldownMs ?? cue.cooldownMs)\n )\n return null;\n const voices = this.activeVoices.get(voiceGroupId) ?? [];\n if (voices.length >= Math.max(1, options.maxVoices ?? cue.polyphony)) {\n if (cue.stealMode === 'ignoreNewest') return null;\n voices.shift()?.stop();\n }\n const random = deterministicUnit(deterministicSeed);\n const rate =\n options.playbackRate ??\n interpolate(cue.pitchMin ?? 1, cue.pitchMax ?? cue.pitchMin ?? 1, random);\n let source: AudioBufferSourceNode | null = null;\n source = await this.play(cue.asset, {\n signal: options.signal,\n bus: cue.bus,\n loop: cue.loop,\n volume: options.volume ?? cue.gain,\n volumeCeiling: options.volumeCeiling,\n playbackRate: rate,\n pan: options.pan ?? interpolate(cue.panMin ?? 0, cue.panMax ?? cue.panMin ?? 0, random),\n startOffsetMs: cue.startOffsetMs,\n fadeInMs: cue.fadeInMs,\n fadeOutMs: cue.fadeOutMs,\n onEnded: () => {\n this.releaseVoice(voiceGroupId, source);\n onEnded?.();\n },\n });\n if (!source || options.signal?.aborted) return null;\n this.lastCueTimes.set(cooldownId, nowMs);\n this.activeVoices.set(voiceGroupId, [...voices, source]);\n return source;\n }\n\n stopCueGroup(groupId: string): void {\n for (const source of this.activeVoices.get(groupId) ?? []) source.stop();\n this.activeVoices.delete(groupId);\n }\n\n async playField(url: string, volume = 1): Promise<void> {\n await this.replaceField(url, volume);\n }\n\n async replaceField(url: string | null, volume = 1): Promise<void> {\n const revision = ++this.fieldRevision;\n this.stopFieldSource();\n if (!url) return;\n const source = await this.play(url, { bus: 'field', loop: true, volume });\n if (revision !== this.fieldRevision) {\n source?.stop();\n return;\n }\n this.fieldUrl = url;\n this.fieldSource = source;\n }\n\n suspendField(): void {\n this.buses.get('field')?.gain.setValueAtTime(0, this.context?.currentTime ?? 0);\n }\n\n resumeField(): void {\n if (this.fieldUrl)\n this.buses.get('field')?.gain.setValueAtTime(1, this.context?.currentTime ?? 0);\n }\n\n stopField(): void {\n this.fieldRevision += 1;\n this.stopFieldSource();\n }\n\n private stopFieldSource(): void {\n const source = this.fieldSource;\n this.fieldSource = null;\n this.fieldUrl = null;\n source?.stop();\n }\n\n setMasterGain(value: number): void {\n this.masterGain = Math.max(0, Math.min(1, value));\n if (this.master) this.master.gain.value = this.masterGain;\n }\n\n setBusGain(bus: SessionAudioBus, value: number, rampMs = 0): void {\n const node = this.buses.get(bus);\n if (!node) return;\n this.busGains.set(bus, Math.max(0, Math.min(1, value)));\n this.applyBusAudibility(rampMs);\n }\n\n setBusMuted(bus: SessionAudioBus, muted: boolean): void {\n if (muted) this.busMute.add(bus);\n else this.busMute.delete(bus);\n this.applyBusAudibility();\n }\n\n solo(bus: SessionAudioBus | null): void {\n this.soloBus = bus;\n this.applyBusAudibility();\n }\n\n async destroy(): Promise<void> {\n this.destroying = true;\n this.unlockGeneration += 1;\n this._state = 'blocked';\n try {\n this.stopField();\n } catch (error) {\n this.reportSafely(error);\n }\n const cleanups = [...this.playbackCleanups];\n this.playbackCleanups.clear();\n this.buffers.clear();\n this.bytes.clear();\n this.activeVoices.clear();\n this.lastCueTimes.clear();\n this.buses.clear();\n this.busGains.clear();\n const context = this.context;\n this.context = null;\n this.master = null;\n try {\n for (const [source, finish] of cleanups) {\n try {\n source.stop();\n } catch (error) {\n this.reportSafely(error);\n }\n try {\n finish();\n } catch (error) {\n this.reportSafely(error);\n }\n }\n await context?.close();\n } finally {\n this.destroying = false;\n }\n }\n\n private reportSafely(error: unknown): void {\n try {\n this.report(error);\n } catch {\n /* Diagnostics cannot interrupt audio teardown. */\n }\n }\n\n private ensureGraph(): void {\n if (this.context) return;\n this.context = this.createContext();\n this.master = this.context.createGain();\n this.master.gain.value = this.masterGain;\n this.master.connect(this.context.destination);\n for (const bus of this.busIds) {\n const gain = this.context.createGain();\n if (\n this.limiterBuses.includes(bus) &&\n typeof this.context.createDynamicsCompressor === 'function'\n ) {\n const limiter = this.context.createDynamicsCompressor();\n limiter.threshold.value = -1;\n limiter.knee.value = 0;\n limiter.ratio.value = 20;\n limiter.attack.value = 0.001;\n limiter.release.value = 0.08;\n gain.connect(limiter);\n limiter.connect(this.master);\n } else gain.connect(this.master);\n this.buses.set(bus, gain);\n this.busGains.set(bus, 1);\n }\n }\n\n private load(url: string): Promise<AudioBuffer> {\n const existing = this.buffers.get(url);\n if (existing) return existing;\n const context = this.context;\n const generation = this.unlockGeneration;\n if (!context) return Promise.reject(new Error('Audio context is not initialized'));\n const loading = this.bytes\n .load(url)\n .then((data) => {\n if (context !== this.context || generation !== this.unlockGeneration)\n throw new DOMException('Audio context replaced', 'AbortError');\n return context.decodeAudioData(data);\n })\n .catch((error: unknown) => {\n if (this.buffers.get(url) === loading) this.buffers.delete(url);\n throw error;\n });\n this.buffers.set(url, loading);\n return loading;\n }\n\n private releaseVoice(cueId: string, source: AudioBufferSourceNode | null): void {\n if (!source) return;\n const voices = this.activeVoices.get(cueId);\n if (!voices) return;\n const next = voices.filter((voice) => voice !== source);\n if (next.length === 0) this.activeVoices.delete(cueId);\n else this.activeVoices.set(cueId, next);\n }\n\n private applyBusAudibility(rampMs = 0): void {\n for (const [bus, node] of this.buses) {\n const audible = !this.busMute.has(bus) && (!this.soloBus || this.soloBus === bus);\n const value = audible ? (this.busGains.get(bus) ?? 1) : 0;\n const now = this.context?.currentTime ?? 0;\n if (rampMs > 0) {\n if (typeof node.gain.cancelAndHoldAtTime === 'function') node.gain.cancelAndHoldAtTime(now);\n else {\n node.gain.cancelScheduledValues(now);\n node.gain.setValueAtTime(node.gain.value, now);\n }\n node.gain.linearRampToValueAtTime(value, now + rampMs / 1000);\n } else {\n node.gain.cancelScheduledValues(now);\n node.gain.setValueAtTime(value, now);\n }\n }\n }\n}\n\nconst interpolate = (min: number, max: number, amount: number): number =>\n min + (max - min) * amount;\n\nconst deterministicUnit = (value: string): number => {\n let hash = 2166136261;\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index);\n hash = Math.imul(hash, 16777619);\n }\n return (hash >>> 0) / 0xffffffff;\n};\n", "import type { SessionAudioBackend } from './SessionAudioBackend.js';\nimport type {\n SessionAudioBus,\n SessionAudioCueDefinition,\n SessionAudioCuePlayOptions,\n SessionAudioEngineState,\n SessionAudioPlayOptions,\n} from './SessionAudioEngine.js';\n\nconst classifyFailure = (error: unknown): SessionAudioEngineState =>\n error instanceof DOMException && (error.name === 'NotAllowedError' || error.name === 'AbortError')\n ? 'blocked'\n : 'failed';\n\n/** Compatibility backend for jsdom and browsers without Web Audio. */\nexport class HtmlAudioSessionBackend implements SessionAudioBackend {\n private readonly elements = new Map<string, HTMLAudioElement>();\n private readonly activeCueGroups = new Map<string, Set<HTMLAudioElement>>();\n private readonly playbackOwners = new WeakMap<HTMLAudioElement, symbol>();\n private readonly playbackCleanups = new Map<HTMLAudioElement, () => void>();\n private fieldElement: HTMLAudioElement | null = null;\n private masterGain = 1;\n private readonly busGains = new Map<SessionAudioBus, number>();\n private unlockGeneration = 0;\n private destroying = false;\n private _state: SessionAudioEngineState = 'blocked';\n\n constructor(private readonly report: (error: unknown) => void = () => undefined) {}\n\n get state(): SessionAudioEngineState {\n return this._state;\n }\n\n async unlock(preloadUrls: string[] = []): Promise<boolean> {\n if (this.destroying) return false;\n if (this._state === 'ready') return true;\n const generation = ++this.unlockGeneration;\n this._state = 'initializing';\n try {\n const firstUrl = preloadUrls.at(0);\n if (firstUrl) {\n const firstAudio = this.getElement(firstUrl);\n firstAudio.pause();\n firstAudio.currentTime = 0;\n await firstAudio.play();\n if (generation !== this.unlockGeneration) return false;\n firstAudio.pause();\n firstAudio.currentTime = 0;\n }\n for (const url of preloadUrls.slice(1)) this.getElement(url).load();\n this._state = 'ready';\n return true;\n } catch (error) {\n if (generation !== this.unlockGeneration) return false;\n this._state = classifyFailure(error);\n return false;\n }\n }\n\n async preload(urls: readonly string[]): Promise<void> {\n urls.forEach((url) => this.getElement(url).load());\n }\n\n async play(url: string, options: SessionAudioPlayOptions): Promise<HTMLAudioElement | null> {\n if (this._state !== 'ready' || options.signal?.aborted) return null;\n const generation = this.unlockGeneration;\n const audio = this.getElement(url);\n this.playbackCleanups.get(audio)?.();\n const owner = Symbol(url);\n this.playbackOwners.set(audio, owner);\n audio.pause();\n audio.currentTime = 0;\n audio.loop = options.loop ?? false;\n audio.playbackRate = Math.max(0.01, options.playbackRate ?? 1);\n audio.volume =\n Math.max(0, Math.min(1, options.volume ?? 1)) *\n this.masterGain *\n (this.busGains.get(options.bus) ?? 1);\n let finished = false;\n const finish = () => {\n if (finished) return;\n finished = true;\n options.signal?.removeEventListener('abort', abort);\n if (this.playbackOwners.get(audio) === owner) this.playbackCleanups.delete(audio);\n try {\n options.onEnded?.();\n } catch (error) {\n this.reportSafely(error);\n }\n };\n const abort = () => {\n if (this.playbackOwners.get(audio) === owner) {\n audio.pause();\n audio.currentTime = 0;\n }\n finish();\n };\n audio.onended = finish;\n this.playbackCleanups.set(audio, finish);\n options.signal?.addEventListener('abort', abort, { once: true });\n try {\n await audio.play();\n if (\n this.playbackOwners.get(audio) !== owner ||\n generation !== this.unlockGeneration ||\n this._state !== 'ready'\n ) {\n finish();\n return null;\n }\n if (options.signal?.aborted) {\n abort();\n return null;\n }\n return audio;\n } catch (error) {\n if (\n this.playbackOwners.get(audio) !== owner ||\n generation !== this.unlockGeneration ||\n this._state !== 'ready' ||\n options.signal?.aborted\n ) {\n finish();\n return null;\n }\n if (error instanceof DOMException && error.name === 'AbortError') return audio;\n this._state = classifyFailure(error);\n finish();\n return null;\n }\n }\n\n async playCue(\n cue: SessionAudioCueDefinition,\n options: SessionAudioCuePlayOptions = {},\n ): Promise<HTMLAudioElement | null> {\n const groupId = options.voiceGroupId ?? cue.id;\n let audio: HTMLAudioElement | null = null;\n audio = await this.play(cue.asset, {\n signal: options.signal,\n bus: cue.bus,\n loop: cue.loop,\n volume: options.volume ?? cue.gain,\n playbackRate: options.playbackRate,\n pan: options.pan,\n startOffsetMs: cue.startOffsetMs,\n fadeInMs: cue.fadeInMs,\n fadeOutMs: cue.fadeOutMs,\n onEnded: () => {\n if (audio) this.releaseCueGroup(groupId, audio);\n options.onEnded?.();\n },\n });\n if (audio && !options.signal?.aborted) {\n const active = this.activeCueGroups.get(groupId) ?? new Set<HTMLAudioElement>();\n active.add(audio);\n this.activeCueGroups.set(groupId, active);\n }\n return audio;\n }\n\n stopCueGroup(groupId: string): void {\n for (const audio of this.activeCueGroups.get(groupId) ?? []) {\n audio.pause();\n audio.currentTime = 0;\n this.playbackCleanups.get(audio)?.();\n }\n this.activeCueGroups.delete(groupId);\n }\n\n async playField(url: string, volume = 1): Promise<void> {\n await this.replaceField(url, volume);\n }\n\n async replaceField(url: string | null, volume = 1): Promise<void> {\n this.stopField();\n if (!url) return;\n const audio = this.getElement(url);\n audio.loop = true;\n this.fieldElement = audio;\n await this.play(url, { bus: 'field', loop: true, volume });\n }\n\n suspendField(): void {\n this.fieldElement?.pause();\n }\n\n resumeField(): void {\n if (this.fieldElement && this._state === 'ready') void this.fieldElement.play();\n }\n\n stopField(): void {\n const audio = this.fieldElement;\n if (!audio) return;\n audio.pause();\n audio.currentTime = 0;\n this.fieldElement = null;\n }\n\n setMasterGain(value: number): void {\n this.masterGain = Math.max(0, Math.min(1, value));\n }\n\n setBusGain(bus: SessionAudioBus, value: number): void {\n this.busGains.set(bus, Math.max(0, Math.min(1, value)));\n }\n\n setBusMuted(bus: SessionAudioBus, muted: boolean): void {\n void bus;\n void muted;\n }\n\n solo(bus: SessionAudioBus | null): void {\n void bus;\n }\n\n async destroy(): Promise<void> {\n this.destroying = true;\n this.unlockGeneration += 1;\n this._state = 'blocked';\n const elements = [...this.elements.values()];\n const cleanups = [...this.playbackCleanups.values()];\n this.playbackCleanups.clear();\n this.elements.clear();\n this.activeCueGroups.clear();\n this.fieldElement = null;\n try {\n for (const audio of elements) {\n try {\n audio.pause();\n } catch (error) {\n this.reportSafely(error);\n }\n }\n for (const cleanup of cleanups) {\n try {\n cleanup();\n } catch (error) {\n this.reportSafely(error);\n }\n }\n } finally {\n this.destroying = false;\n }\n }\n\n private reportSafely(error: unknown): void {\n try {\n this.report(error);\n } catch {\n /* Diagnostics cannot interrupt audio teardown. */\n }\n }\n\n private getElement(url: string): HTMLAudioElement {\n const existing = this.elements.get(url);\n if (existing) return existing;\n const audio = new Audio(url);\n audio.preload = 'auto';\n this.elements.set(url, audio);\n return audio;\n }\n\n private releaseCueGroup(groupId: string, audio: HTMLAudioElement): void {\n const active = this.activeCueGroups.get(groupId);\n if (!active) return;\n active.delete(audio);\n if (active.size === 0) this.activeCueGroups.delete(groupId);\n }\n}\n", "export interface SessionAudioPlaybackPolicy {\n readonly mode: 'production' | 'preview';\n readonly allowSemanticSideEffects: boolean;\n readonly allowScrubAudio: boolean;\n readonly deterministicSeed?: string;\n}\n\nexport const productionAudioPlaybackPolicy: SessionAudioPlaybackPolicy = {\n mode: 'production',\n allowSemanticSideEffects: true,\n allowScrubAudio: false,\n};\n\nexport const previewAudioPlaybackPolicy: SessionAudioPlaybackPolicy = {\n mode: 'preview',\n allowSemanticSideEffects: true,\n allowScrubAudio: false,\n deterministicSeed: 'visual-lab',\n};\n"],
|
|
5
|
+
"mappings": ";AACO,IAAM,iBAAN,MAAqB;AAAA,EAK1B,YAA6B,aAA2B,OAAO;AAAlC;AAAA,EAAmC;AAAA,EAAnC;AAAA,EAJZ,UAAU,oBAAI,IAG7B;AAAA,EAGF,MAAM,KAAK,KAAmC;AAC5C,QAAI,SAAS,KAAK,QAAQ,IAAI,GAAG;AACjC,QAAI,CAAC,QAAQ;AACX,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,UAAU,KAAK,WAAW,KAAK,EAAE,QAAQ,WAAW,OAAO,CAAC,EAAE,KAAK,OAAO,aAAa;AAC3F,YAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,yBAAyB,SAAS,MAAM,EAAE;AAC5E,cAAM,QAAQ,MAAM,SAAS,YAAY;AACzC,YAAI,WAAW,OAAO,QAAS,OAAM,IAAI,aAAa,wBAAwB,YAAY;AAC1F,eAAO;AAAA,MACT,CAAC;AACD,eAAS,EAAE,YAAY,QAAQ;AAC/B,WAAK,QAAQ,IAAI,KAAK,MAAM;AAC5B,YAAM,QAAQ;AACd,WAAK,QAAQ,MAAM,MAAM;AACvB,YAAI,KAAK,QAAQ,IAAI,GAAG,MAAM,MAAO,MAAK,QAAQ,OAAO,GAAG;AAAA,MAC9D,CAAC;AAAA,IACH;AACA,YAAQ,MAAM,OAAO,SAAS,MAAM,CAAC;AAAA,EACvC;AAAA,EAEA,QAAc;AACZ,eAAW,UAAU,KAAK,QAAQ,OAAO,EAAG,QAAO,WAAW,MAAM;AACpE,SAAK,QAAQ,MAAM;AAAA,EACrB;AACF;;;ACmBO,IAAM,qBAAN,MAAyB;AAAA,EAoB9B,YACmB,gBAAoC,MAAM,IAAI,aAAa,GAC5E,aAA2B,OACV,SAA4B;AAAA,IAC3C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GACiB,eAAkC,CAAC,MAAM,GACzC,SAAmC,MAAM,QAC1D;AAbiB;AAEA;AASA;AACA;AAEjB,QACE,OAAO,WAAW,KAClB,IAAI,IAAI,MAAM,EAAE,SAAS,OAAO,UAChC,OAAO,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC;AAEhC,YAAM,IAAI,MAAM,2CAA2C;AAC7D,SAAK,QAAQ,IAAI,eAAe,WAAW,KAAK,UAAU,CAAC;AAAA,EAC7D;AAAA,EArBmB;AAAA,EAEA;AAAA,EASA;AAAA,EACA;AAAA,EAhCF;AAAA,EACT,UAA+B;AAAA,EAC/B,SAA0B;AAAA,EACjB,QAAQ,oBAAI,IAA+B;AAAA,EAC3C,WAAW,oBAAI,IAA6B;AAAA,EAC5C,UAAU,oBAAI,IAAkC;AAAA,EAChD,eAAe,oBAAI,IAAqC;AAAA,EACxD,mBAAmB,oBAAI,IAAuC;AAAA,EAC9D,eAAe,oBAAI,IAAoB;AAAA,EACvC,UAAU,oBAAI,IAAqB;AAAA,EAC5C,UAAkC;AAAA,EAClC,aAAa;AAAA,EACb,cAA4C;AAAA,EAC5C,WAA0B;AAAA,EAC1B,gBAAgB;AAAA,EAChB,mBAAmB;AAAA,EACnB,aAAa;AAAA,EACb,SAAkC;AAAA,EA0B1C,IAAI,QAAiC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,OAAO,cAAwB,CAAC,GAAqB;AACzD,QAAI,KAAK,WAAY,QAAO;AAC5B,QAAI,KAAK,WAAW,QAAS,QAAO;AACpC,UAAM,aAAa,EAAE,KAAK;AAC1B,SAAK,SAAS;AACd,QAAI;AACF,WAAK,YAAY;AACjB,YAAM,KAAK,QAAS,OAAO;AAC3B,UAAI,eAAe,KAAK,iBAAkB,QAAO;AACjD,WAAK,SAAS;AAId,WAAK,KAAK,QAAQ,WAAW;AAC7B,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,eAAe,KAAK,iBAAkB,QAAO;AACjD,WAAK,SACH,iBAAiB,iBAChB,MAAM,SAAS,qBAAqB,MAAM,SAAS,gBAChD,YACA;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,MAAwC;AACpD,UAAM,QAAQ,WAAW,KAAK,IAAI,CAAC,QAAQ,KAAK,KAAK,GAAG,CAAC,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAM,KAAK,KAAa,SAAyE;AAC/F,QAAI,KAAK,WAAW,WAAW,QAAQ,QAAQ,QAAS,QAAO;AAC/D,UAAM,UAAU,KAAK;AACrB,UAAM,aAAa,KAAK;AACxB,QAAI;AACJ,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,KAAK,GAAG;AAClC,UACE,KAAK,WAAW,WAChB,CAAC,KAAK,WACN,KAAK,YAAY,WACjB,eAAe,KAAK,oBACpB,QAAQ,QAAQ;AAEhB,eAAO;AACT,YAAM,SAAS,KAAK,QAAQ,mBAAmB;AAC/C,YAAM,YAAY,KAAK,QAAQ,WAAW;AAC1C,YAAM,SACJ,wBAAwB,KAAK,WAC7B,OAAO,KAAK,QAAQ,uBAAuB,aACvC,KAAK,QAAQ,mBAAmB,IAChC;AACN,YAAM,MAAM,KAAK,QAAQ;AACzB,YAAM,gBAAgB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,QAAQ,iBAAiB,CAAC,CAAC;AACzE,YAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,eAAe,QAAQ,UAAU,CAAC,CAAC;AACvE,YAAM,gBAAgB,KAAK,IAAI,GAAG,QAAQ,YAAY,CAAC,IAAI;AAC3D,gBAAU,KAAK,eAAe,gBAAgB,IAAI,IAAI,QAAQ,GAAG;AACjE,UAAI,gBAAgB,EAAG,WAAU,KAAK,wBAAwB,QAAQ,MAAM,aAAa;AACzF,aAAO,SAAS;AAChB,aAAO,OAAO,QAAQ,QAAQ;AAC9B,YAAM,eAAe,OAAO;AAC5B,UAAI,aAAc,cAAa,QAAQ,KAAK,IAAI,MAAM,QAAQ,gBAAgB,CAAC;AAC/E,aAAO,QAAQ,SAAS;AACxB,UAAI,QAAQ;AACV,eAAO,IAAI,QAAQ,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,QAAQ,OAAO,CAAC,CAAC;AAC7D,kBAAU,QAAQ,MAAM;AACxB,eAAO,QAAQ,KAAK,MAAM,IAAI,QAAQ,GAAG,CAAE;AAAA,MAC7C,MAAO,WAAU,QAAQ,KAAK,MAAM,IAAI,QAAQ,GAAG,CAAE;AACrD,UAAI,WAAW;AACf,YAAM,SAAS,MAAM;AACnB,YAAI,SAAU;AACd,mBAAW;AACX,gBAAQ,QAAQ,oBAAoB,SAAS,KAAK;AAClD,aAAK,iBAAiB,OAAO,MAAM;AACnC,eAAO,WAAW;AAClB,kBAAU,WAAW;AACrB,gBAAQ,WAAW;AACnB,YAAI;AACF,kBAAQ,UAAU;AAAA,QACpB,SAAS,OAAO;AACd,eAAK,aAAa,KAAK;AAAA,QACzB;AAAA,MACF;AACA,YAAM,QAAQ,MAAM;AAClB,YAAI;AACF,iBAAO,KAAK;AAAA,QACd,UAAE;AACA,iBAAO;AAAA,QACT;AAAA,MACF;AACA,gBAAU;AACV,aAAO,UAAU;AACjB,WAAK,iBAAiB,IAAI,QAAQ,MAAM;AACxC,cAAQ,QAAQ,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;AAC/D,UAAI,QAAQ,QAAQ,SAAS;AAC3B,eAAO;AACP,eAAO;AAAA,MACT;AACA,aAAO,MAAM,GAAG,KAAK,IAAI,GAAG,QAAQ,iBAAiB,CAAC,IAAI,GAAI;AAC9D,aAAO;AAAA,IACT,QAAQ;AACN,gBAAU;AACV,UACE,QAAQ,QAAQ,WAChB,KAAK,YAAY,WACjB,eAAe,KAAK;AAEpB,eAAO;AACT,WAAK,SAAS;AACd,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAWA,MAAM,QACJ,KACA,gBAAqD,CAAC,GACtD,eACuC;AACvC,UAAM,UACJ,OAAO,kBAAkB,WACrB,EAAE,mBAAmB,eAAe,SAAS,cAAc,IAC3D;AACN,UAAM,oBAAoB,QAAQ,qBAAqB,IAAI;AAC3D,UAAM,UAAU,QAAQ;AACxB,QAAI,KAAK,WAAW,WAAW,CAAC,KAAK,WAAW,QAAQ,QAAQ,QAAS,QAAO;AAChF,UAAM,QAAQ,KAAK,QAAQ,cAAc;AACzC,UAAM,aAAa,QAAQ,cAAc,IAAI;AAC7C,UAAM,eAAe,QAAQ,gBAAgB,IAAI;AACjD,UAAM,WAAW,KAAK,aAAa,IAAI,UAAU;AACjD,QACE,aAAa,UACb,QAAQ,WAAW,KAAK,IAAI,GAAG,QAAQ,cAAc,IAAI,UAAU;AAEnE,aAAO;AACT,UAAM,SAAS,KAAK,aAAa,IAAI,YAAY,KAAK,CAAC;AACvD,QAAI,OAAO,UAAU,KAAK,IAAI,GAAG,QAAQ,aAAa,IAAI,SAAS,GAAG;AACpE,UAAI,IAAI,cAAc,eAAgB,QAAO;AAC7C,aAAO,MAAM,GAAG,KAAK;AAAA,IACvB;AACA,UAAM,SAAS,kBAAkB,iBAAiB;AAClD,UAAM,OACJ,QAAQ,gBACR,YAAY,IAAI,YAAY,GAAG,IAAI,YAAY,IAAI,YAAY,GAAG,MAAM;AAC1E,QAAI,SAAuC;AAC3C,aAAS,MAAM,KAAK,KAAK,IAAI,OAAO;AAAA,MAClC,QAAQ,QAAQ;AAAA,MAChB,KAAK,IAAI;AAAA,MACT,MAAM,IAAI;AAAA,MACV,QAAQ,QAAQ,UAAU,IAAI;AAAA,MAC9B,eAAe,QAAQ;AAAA,MACvB,cAAc;AAAA,MACd,KAAK,QAAQ,OAAO,YAAY,IAAI,UAAU,GAAG,IAAI,UAAU,IAAI,UAAU,GAAG,MAAM;AAAA,MACtF,eAAe,IAAI;AAAA,MACnB,UAAU,IAAI;AAAA,MACd,WAAW,IAAI;AAAA,MACf,SAAS,MAAM;AACb,aAAK,aAAa,cAAc,MAAM;AACtC,kBAAU;AAAA,MACZ;AAAA,IACF,CAAC;AACD,QAAI,CAAC,UAAU,QAAQ,QAAQ,QAAS,QAAO;AAC/C,SAAK,aAAa,IAAI,YAAY,KAAK;AACvC,SAAK,aAAa,IAAI,cAAc,CAAC,GAAG,QAAQ,MAAM,CAAC;AACvD,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,SAAuB;AAClC,eAAW,UAAU,KAAK,aAAa,IAAI,OAAO,KAAK,CAAC,EAAG,QAAO,KAAK;AACvE,SAAK,aAAa,OAAO,OAAO;AAAA,EAClC;AAAA,EAEA,MAAM,UAAU,KAAa,SAAS,GAAkB;AACtD,UAAM,KAAK,aAAa,KAAK,MAAM;AAAA,EACrC;AAAA,EAEA,MAAM,aAAa,KAAoB,SAAS,GAAkB;AAChE,UAAM,WAAW,EAAE,KAAK;AACxB,SAAK,gBAAgB;AACrB,QAAI,CAAC,IAAK;AACV,UAAM,SAAS,MAAM,KAAK,KAAK,KAAK,EAAE,KAAK,SAAS,MAAM,MAAM,OAAO,CAAC;AACxE,QAAI,aAAa,KAAK,eAAe;AACnC,cAAQ,KAAK;AACb;AAAA,IACF;AACA,SAAK,WAAW;AAChB,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,eAAqB;AACnB,SAAK,MAAM,IAAI,OAAO,GAAG,KAAK,eAAe,GAAG,KAAK,SAAS,eAAe,CAAC;AAAA,EAChF;AAAA,EAEA,cAAoB;AAClB,QAAI,KAAK;AACP,WAAK,MAAM,IAAI,OAAO,GAAG,KAAK,eAAe,GAAG,KAAK,SAAS,eAAe,CAAC;AAAA,EAClF;AAAA,EAEA,YAAkB;AAChB,SAAK,iBAAiB;AACtB,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEQ,kBAAwB;AAC9B,UAAM,SAAS,KAAK;AACpB,SAAK,cAAc;AACnB,SAAK,WAAW;AAChB,YAAQ,KAAK;AAAA,EACf;AAAA,EAEA,cAAc,OAAqB;AACjC,SAAK,aAAa,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AAChD,QAAI,KAAK,OAAQ,MAAK,OAAO,KAAK,QAAQ,KAAK;AAAA,EACjD;AAAA,EAEA,WAAW,KAAsB,OAAe,SAAS,GAAS;AAChE,UAAM,OAAO,KAAK,MAAM,IAAI,GAAG;AAC/B,QAAI,CAAC,KAAM;AACX,SAAK,SAAS,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC,CAAC;AACtD,SAAK,mBAAmB,MAAM;AAAA,EAChC;AAAA,EAEA,YAAY,KAAsB,OAAsB;AACtD,QAAI,MAAO,MAAK,QAAQ,IAAI,GAAG;AAAA,QAC1B,MAAK,QAAQ,OAAO,GAAG;AAC5B,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEA,KAAK,KAAmC;AACtC,SAAK,UAAU;AACf,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,aAAa;AAClB,SAAK,oBAAoB;AACzB,SAAK,SAAS;AACd,QAAI;AACF,WAAK,UAAU;AAAA,IACjB,SAAS,OAAO;AACd,WAAK,aAAa,KAAK;AAAA,IACzB;AACA,UAAM,WAAW,CAAC,GAAG,KAAK,gBAAgB;AAC1C,SAAK,iBAAiB,MAAM;AAC5B,SAAK,QAAQ,MAAM;AACnB,SAAK,MAAM,MAAM;AACjB,SAAK,aAAa,MAAM;AACxB,SAAK,aAAa,MAAM;AACxB,SAAK,MAAM,MAAM;AACjB,SAAK,SAAS,MAAM;AACpB,UAAM,UAAU,KAAK;AACrB,SAAK,UAAU;AACf,SAAK,SAAS;AACd,QAAI;AACF,iBAAW,CAAC,QAAQ,MAAM,KAAK,UAAU;AACvC,YAAI;AACF,iBAAO,KAAK;AAAA,QACd,SAAS,OAAO;AACd,eAAK,aAAa,KAAK;AAAA,QACzB;AACA,YAAI;AACF,iBAAO;AAAA,QACT,SAAS,OAAO;AACd,eAAK,aAAa,KAAK;AAAA,QACzB;AAAA,MACF;AACA,YAAM,SAAS,MAAM;AAAA,IACvB,UAAE;AACA,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEQ,aAAa,OAAsB;AACzC,QAAI;AACF,WAAK,OAAO,KAAK;AAAA,IACnB,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,cAAoB;AAC1B,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU,KAAK,cAAc;AAClC,SAAK,SAAS,KAAK,QAAQ,WAAW;AACtC,SAAK,OAAO,KAAK,QAAQ,KAAK;AAC9B,SAAK,OAAO,QAAQ,KAAK,QAAQ,WAAW;AAC5C,eAAW,OAAO,KAAK,QAAQ;AAC7B,YAAM,OAAO,KAAK,QAAQ,WAAW;AACrC,UACE,KAAK,aAAa,SAAS,GAAG,KAC9B,OAAO,KAAK,QAAQ,6BAA6B,YACjD;AACA,cAAM,UAAU,KAAK,QAAQ,yBAAyB;AACtD,gBAAQ,UAAU,QAAQ;AAC1B,gBAAQ,KAAK,QAAQ;AACrB,gBAAQ,MAAM,QAAQ;AACtB,gBAAQ,OAAO,QAAQ;AACvB,gBAAQ,QAAQ,QAAQ;AACxB,aAAK,QAAQ,OAAO;AACpB,gBAAQ,QAAQ,KAAK,MAAM;AAAA,MAC7B,MAAO,MAAK,QAAQ,KAAK,MAAM;AAC/B,WAAK,MAAM,IAAI,KAAK,IAAI;AACxB,WAAK,SAAS,IAAI,KAAK,CAAC;AAAA,IAC1B;AAAA,EACF;AAAA,EAEQ,KAAK,KAAmC;AAC9C,UAAM,WAAW,KAAK,QAAQ,IAAI,GAAG;AACrC,QAAI,SAAU,QAAO;AACrB,UAAM,UAAU,KAAK;AACrB,UAAM,aAAa,KAAK;AACxB,QAAI,CAAC,QAAS,QAAO,QAAQ,OAAO,IAAI,MAAM,kCAAkC,CAAC;AACjF,UAAM,UAAU,KAAK,MAClB,KAAK,GAAG,EACR,KAAK,CAAC,SAAS;AACd,UAAI,YAAY,KAAK,WAAW,eAAe,KAAK;AAClD,cAAM,IAAI,aAAa,0BAA0B,YAAY;AAC/D,aAAO,QAAQ,gBAAgB,IAAI;AAAA,IACrC,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAI,KAAK,QAAQ,IAAI,GAAG,MAAM,QAAS,MAAK,QAAQ,OAAO,GAAG;AAC9D,YAAM;AAAA,IACR,CAAC;AACH,SAAK,QAAQ,IAAI,KAAK,OAAO;AAC7B,WAAO;AAAA,EACT;AAAA,EAEQ,aAAa,OAAe,QAA4C;AAC9E,QAAI,CAAC,OAAQ;AACb,UAAM,SAAS,KAAK,aAAa,IAAI,KAAK;AAC1C,QAAI,CAAC,OAAQ;AACb,UAAM,OAAO,OAAO,OAAO,CAAC,UAAU,UAAU,MAAM;AACtD,QAAI,KAAK,WAAW,EAAG,MAAK,aAAa,OAAO,KAAK;AAAA,QAChD,MAAK,aAAa,IAAI,OAAO,IAAI;AAAA,EACxC;AAAA,EAEQ,mBAAmB,SAAS,GAAS;AAC3C,eAAW,CAAC,KAAK,IAAI,KAAK,KAAK,OAAO;AACpC,YAAM,UAAU,CAAC,KAAK,QAAQ,IAAI,GAAG,MAAM,CAAC,KAAK,WAAW,KAAK,YAAY;AAC7E,YAAM,QAAQ,UAAW,KAAK,SAAS,IAAI,GAAG,KAAK,IAAK;AACxD,YAAM,MAAM,KAAK,SAAS,eAAe;AACzC,UAAI,SAAS,GAAG;AACd,YAAI,OAAO,KAAK,KAAK,wBAAwB,WAAY,MAAK,KAAK,oBAAoB,GAAG;AAAA,aACrF;AACH,eAAK,KAAK,sBAAsB,GAAG;AACnC,eAAK,KAAK,eAAe,KAAK,KAAK,OAAO,GAAG;AAAA,QAC/C;AACA,aAAK,KAAK,wBAAwB,OAAO,MAAM,SAAS,GAAI;AAAA,MAC9D,OAAO;AACL,aAAK,KAAK,sBAAsB,GAAG;AACnC,aAAK,KAAK,eAAe,OAAO,GAAG;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAM,cAAc,CAAC,KAAa,KAAa,WAC7C,OAAO,MAAM,OAAO;AAEtB,IAAM,oBAAoB,CAAC,UAA0B;AACnD,MAAI,OAAO;AACX,WAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;AACpD,YAAQ,MAAM,WAAW,KAAK;AAC9B,WAAO,KAAK,KAAK,MAAM,QAAQ;AAAA,EACjC;AACA,UAAQ,SAAS,KAAK;AACxB;;;AChdA,IAAM,kBAAkB,CAAC,UACvB,iBAAiB,iBAAiB,MAAM,SAAS,qBAAqB,MAAM,SAAS,gBACjF,YACA;AAGC,IAAM,0BAAN,MAA6D;AAAA,EAYlE,YAA6B,SAAmC,MAAM,QAAW;AAApD;AAAA,EAAqD;AAAA,EAArD;AAAA,EAXZ,WAAW,oBAAI,IAA8B;AAAA,EAC7C,kBAAkB,oBAAI,IAAmC;AAAA,EACzD,iBAAiB,oBAAI,QAAkC;AAAA,EACvD,mBAAmB,oBAAI,IAAkC;AAAA,EAClE,eAAwC;AAAA,EACxC,aAAa;AAAA,EACJ,WAAW,oBAAI,IAA6B;AAAA,EACrD,mBAAmB;AAAA,EACnB,aAAa;AAAA,EACb,SAAkC;AAAA,EAI1C,IAAI,QAAiC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,OAAO,cAAwB,CAAC,GAAqB;AACzD,QAAI,KAAK,WAAY,QAAO;AAC5B,QAAI,KAAK,WAAW,QAAS,QAAO;AACpC,UAAM,aAAa,EAAE,KAAK;AAC1B,SAAK,SAAS;AACd,QAAI;AACF,YAAM,WAAW,YAAY,GAAG,CAAC;AACjC,UAAI,UAAU;AACZ,cAAM,aAAa,KAAK,WAAW,QAAQ;AAC3C,mBAAW,MAAM;AACjB,mBAAW,cAAc;AACzB,cAAM,WAAW,KAAK;AACtB,YAAI,eAAe,KAAK,iBAAkB,QAAO;AACjD,mBAAW,MAAM;AACjB,mBAAW,cAAc;AAAA,MAC3B;AACA,iBAAW,OAAO,YAAY,MAAM,CAAC,EAAG,MAAK,WAAW,GAAG,EAAE,KAAK;AAClE,WAAK,SAAS;AACd,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,eAAe,KAAK,iBAAkB,QAAO;AACjD,WAAK,SAAS,gBAAgB,KAAK;AACnC,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,MAAwC;AACpD,SAAK,QAAQ,CAAC,QAAQ,KAAK,WAAW,GAAG,EAAE,KAAK,CAAC;AAAA,EACnD;AAAA,EAEA,MAAM,KAAK,KAAa,SAAoE;AAC1F,QAAI,KAAK,WAAW,WAAW,QAAQ,QAAQ,QAAS,QAAO;AAC/D,UAAM,aAAa,KAAK;AACxB,UAAM,QAAQ,KAAK,WAAW,GAAG;AACjC,SAAK,iBAAiB,IAAI,KAAK,IAAI;AACnC,UAAM,QAAQ,OAAO,GAAG;AACxB,SAAK,eAAe,IAAI,OAAO,KAAK;AACpC,UAAM,MAAM;AACZ,UAAM,cAAc;AACpB,UAAM,OAAO,QAAQ,QAAQ;AAC7B,UAAM,eAAe,KAAK,IAAI,MAAM,QAAQ,gBAAgB,CAAC;AAC7D,UAAM,SACJ,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,QAAQ,UAAU,CAAC,CAAC,IAC5C,KAAK,cACJ,KAAK,SAAS,IAAI,QAAQ,GAAG,KAAK;AACrC,QAAI,WAAW;AACf,UAAM,SAAS,MAAM;AACnB,UAAI,SAAU;AACd,iBAAW;AACX,cAAQ,QAAQ,oBAAoB,SAAS,KAAK;AAClD,UAAI,KAAK,eAAe,IAAI,KAAK,MAAM,MAAO,MAAK,iBAAiB,OAAO,KAAK;AAChF,UAAI;AACF,gBAAQ,UAAU;AAAA,MACpB,SAAS,OAAO;AACd,aAAK,aAAa,KAAK;AAAA,MACzB;AAAA,IACF;AACA,UAAM,QAAQ,MAAM;AAClB,UAAI,KAAK,eAAe,IAAI,KAAK,MAAM,OAAO;AAC5C,cAAM,MAAM;AACZ,cAAM,cAAc;AAAA,MACtB;AACA,aAAO;AAAA,IACT;AACA,UAAM,UAAU;AAChB,SAAK,iBAAiB,IAAI,OAAO,MAAM;AACvC,YAAQ,QAAQ,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;AAC/D,QAAI;AACF,YAAM,MAAM,KAAK;AACjB,UACE,KAAK,eAAe,IAAI,KAAK,MAAM,SACnC,eAAe,KAAK,oBACpB,KAAK,WAAW,SAChB;AACA,eAAO;AACP,eAAO;AAAA,MACT;AACA,UAAI,QAAQ,QAAQ,SAAS;AAC3B,cAAM;AACN,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UACE,KAAK,eAAe,IAAI,KAAK,MAAM,SACnC,eAAe,KAAK,oBACpB,KAAK,WAAW,WAChB,QAAQ,QAAQ,SAChB;AACA,eAAO;AACP,eAAO;AAAA,MACT;AACA,UAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAAc,QAAO;AACzE,WAAK,SAAS,gBAAgB,KAAK;AACnC,aAAO;AACP,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,KACA,UAAsC,CAAC,GACL;AAClC,UAAM,UAAU,QAAQ,gBAAgB,IAAI;AAC5C,QAAI,QAAiC;AACrC,YAAQ,MAAM,KAAK,KAAK,IAAI,OAAO;AAAA,MACjC,QAAQ,QAAQ;AAAA,MAChB,KAAK,IAAI;AAAA,MACT,MAAM,IAAI;AAAA,MACV,QAAQ,QAAQ,UAAU,IAAI;AAAA,MAC9B,cAAc,QAAQ;AAAA,MACtB,KAAK,QAAQ;AAAA,MACb,eAAe,IAAI;AAAA,MACnB,UAAU,IAAI;AAAA,MACd,WAAW,IAAI;AAAA,MACf,SAAS,MAAM;AACb,YAAI,MAAO,MAAK,gBAAgB,SAAS,KAAK;AAC9C,gBAAQ,UAAU;AAAA,MACpB;AAAA,IACF,CAAC;AACD,QAAI,SAAS,CAAC,QAAQ,QAAQ,SAAS;AACrC,YAAM,SAAS,KAAK,gBAAgB,IAAI,OAAO,KAAK,oBAAI,IAAsB;AAC9E,aAAO,IAAI,KAAK;AAChB,WAAK,gBAAgB,IAAI,SAAS,MAAM;AAAA,IAC1C;AACA,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,SAAuB;AAClC,eAAW,SAAS,KAAK,gBAAgB,IAAI,OAAO,KAAK,CAAC,GAAG;AAC3D,YAAM,MAAM;AACZ,YAAM,cAAc;AACpB,WAAK,iBAAiB,IAAI,KAAK,IAAI;AAAA,IACrC;AACA,SAAK,gBAAgB,OAAO,OAAO;AAAA,EACrC;AAAA,EAEA,MAAM,UAAU,KAAa,SAAS,GAAkB;AACtD,UAAM,KAAK,aAAa,KAAK,MAAM;AAAA,EACrC;AAAA,EAEA,MAAM,aAAa,KAAoB,SAAS,GAAkB;AAChE,SAAK,UAAU;AACf,QAAI,CAAC,IAAK;AACV,UAAM,QAAQ,KAAK,WAAW,GAAG;AACjC,UAAM,OAAO;AACb,SAAK,eAAe;AACpB,UAAM,KAAK,KAAK,KAAK,EAAE,KAAK,SAAS,MAAM,MAAM,OAAO,CAAC;AAAA,EAC3D;AAAA,EAEA,eAAqB;AACnB,SAAK,cAAc,MAAM;AAAA,EAC3B;AAAA,EAEA,cAAoB;AAClB,QAAI,KAAK,gBAAgB,KAAK,WAAW,QAAS,MAAK,KAAK,aAAa,KAAK;AAAA,EAChF;AAAA,EAEA,YAAkB;AAChB,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAC,MAAO;AACZ,UAAM,MAAM;AACZ,UAAM,cAAc;AACpB,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,cAAc,OAAqB;AACjC,SAAK,aAAa,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AAAA,EAClD;AAAA,EAEA,WAAW,KAAsB,OAAqB;AACpD,SAAK,SAAS,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC,CAAC;AAAA,EACxD;AAAA,EAEA,YAAY,KAAsB,OAAsB;AACtD,SAAK;AACL,SAAK;AAAA,EACP;AAAA,EAEA,KAAK,KAAmC;AACtC,SAAK;AAAA,EACP;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,aAAa;AAClB,SAAK,oBAAoB;AACzB,SAAK,SAAS;AACd,UAAM,WAAW,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC;AAC3C,UAAM,WAAW,CAAC,GAAG,KAAK,iBAAiB,OAAO,CAAC;AACnD,SAAK,iBAAiB,MAAM;AAC5B,SAAK,SAAS,MAAM;AACpB,SAAK,gBAAgB,MAAM;AAC3B,SAAK,eAAe;AACpB,QAAI;AACF,iBAAW,SAAS,UAAU;AAC5B,YAAI;AACF,gBAAM,MAAM;AAAA,QACd,SAAS,OAAO;AACd,eAAK,aAAa,KAAK;AAAA,QACzB;AAAA,MACF;AACA,iBAAW,WAAW,UAAU;AAC9B,YAAI;AACF,kBAAQ;AAAA,QACV,SAAS,OAAO;AACd,eAAK,aAAa,KAAK;AAAA,QACzB;AAAA,MACF;AAAA,IACF,UAAE;AACA,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEQ,aAAa,OAAsB;AACzC,QAAI;AACF,WAAK,OAAO,KAAK;AAAA,IACnB,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,WAAW,KAA+B;AAChD,UAAM,WAAW,KAAK,SAAS,IAAI,GAAG;AACtC,QAAI,SAAU,QAAO;AACrB,UAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,UAAM,UAAU;AAChB,SAAK,SAAS,IAAI,KAAK,KAAK;AAC5B,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB,SAAiB,OAA+B;AACtE,UAAM,SAAS,KAAK,gBAAgB,IAAI,OAAO;AAC/C,QAAI,CAAC,OAAQ;AACb,WAAO,OAAO,KAAK;AACnB,QAAI,OAAO,SAAS,EAAG,MAAK,gBAAgB,OAAO,OAAO;AAAA,EAC5D;AACF;;;ACtQO,IAAM,gCAA4D;AAAA,EACvE,MAAM;AAAA,EACN,0BAA0B;AAAA,EAC1B,iBAAiB;AACnB;AAEO,IAAM,6BAAyD;AAAA,EACpE,MAAM;AAAA,EACN,0BAA0B;AAAA,EAC1B,iBAAiB;AAAA,EACjB,mBAAmB;AACrB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './RuntimeLogger.js';
|
|
2
|
+
export * from './sessionDisplayResourceRetry.js';
|
|
3
|
+
export * from './sessionDisplayPreload.js';
|
|
4
|
+
export * from './DisplayAudioController.js';
|
|
5
|
+
export * from './tuning.js';
|
|
6
|
+
export * from './MediaPreloader.js';
|
|
7
|
+
export * from './AudioByteCache.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
// src/sessionDisplayResourceRetry.ts
|
|
2
|
+
var SESSION_DISPLAY_RESOURCE_RETRY_COUNT = 3;
|
|
3
|
+
var SESSION_DISPLAY_RESOURCE_RETRY_DELAY_MS = 500;
|
|
4
|
+
var waitForSessionMediaReady = (ready, signal) => {
|
|
5
|
+
if (!ready || signal.aborted) return Promise.resolve(false);
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const finish = (result) => {
|
|
8
|
+
signal.removeEventListener("abort", abort);
|
|
9
|
+
resolve(result);
|
|
10
|
+
};
|
|
11
|
+
const abort = () => finish(false);
|
|
12
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
13
|
+
void ready.then(finish, () => finish(false));
|
|
14
|
+
if (signal.aborted) abort();
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var createAbortError = () => new DOMException("Session display resource load was aborted", "AbortError");
|
|
18
|
+
var waitForRetry = (delayMs, signal) => new Promise((resolve, reject) => {
|
|
19
|
+
if (signal?.aborted) {
|
|
20
|
+
reject(createAbortError());
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const timeoutId = setTimeout(() => {
|
|
24
|
+
signal?.removeEventListener("abort", handleAbort);
|
|
25
|
+
resolve();
|
|
26
|
+
}, delayMs);
|
|
27
|
+
const handleAbort = () => {
|
|
28
|
+
clearTimeout(timeoutId);
|
|
29
|
+
reject(createAbortError());
|
|
30
|
+
};
|
|
31
|
+
signal?.addEventListener("abort", handleAbort, { once: true });
|
|
32
|
+
});
|
|
33
|
+
var retrySessionDisplayResource = async (operation, options = {}) => {
|
|
34
|
+
const retryCount = Math.max(0, options.retryCount ?? SESSION_DISPLAY_RESOURCE_RETRY_COUNT);
|
|
35
|
+
const retryDelayMs = Math.max(0, options.retryDelayMs ?? SESSION_DISPLAY_RESOURCE_RETRY_DELAY_MS);
|
|
36
|
+
let attempt = 0;
|
|
37
|
+
while (true) {
|
|
38
|
+
if (options.signal?.aborted) throw createAbortError();
|
|
39
|
+
try {
|
|
40
|
+
return await operation();
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (options.signal?.aborted) throw createAbortError();
|
|
43
|
+
if (attempt >= retryCount) throw error;
|
|
44
|
+
attempt += 1;
|
|
45
|
+
await waitForRetry(retryDelayMs, options.signal);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/sessionDisplayPreload.ts
|
|
51
|
+
var normalizeBattlePreloadUrls = (urls) => {
|
|
52
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
53
|
+
urls.forEach((url) => {
|
|
54
|
+
const trimmed = url?.trim();
|
|
55
|
+
if (trimmed) {
|
|
56
|
+
normalized.add(trimmed);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return [...normalized];
|
|
60
|
+
};
|
|
61
|
+
var registerImagePreloadLink = (url) => {
|
|
62
|
+
if (typeof document === "undefined") {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const existing = [...document.head.querySelectorAll('link[rel="preload"]')].some(
|
|
66
|
+
(link2) => link2.as === "image" && link2.href === new URL(url, window.location.href).href
|
|
67
|
+
);
|
|
68
|
+
if (existing) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const link = document.createElement("link");
|
|
72
|
+
link.rel = "preload";
|
|
73
|
+
link.as = "image";
|
|
74
|
+
link.crossOrigin = "anonymous";
|
|
75
|
+
link.href = url;
|
|
76
|
+
link.dataset.owner = "battle-display-preload";
|
|
77
|
+
document.head.appendChild(link);
|
|
78
|
+
};
|
|
79
|
+
var loadImage = async (url) => {
|
|
80
|
+
await new Promise((resolve, reject) => {
|
|
81
|
+
const image = new Image();
|
|
82
|
+
image.decoding = "async";
|
|
83
|
+
image.loading = "eager";
|
|
84
|
+
image.crossOrigin = "anonymous";
|
|
85
|
+
image.onload = () => resolve();
|
|
86
|
+
image.onerror = () => reject(new Error(`\u56FE\u7247\u9884\u52A0\u8F7D\u5931\u8D25: ${url}`));
|
|
87
|
+
image.src = url;
|
|
88
|
+
if (typeof image.decode === "function") {
|
|
89
|
+
void image.decode().then(resolve).catch(() => void 0);
|
|
90
|
+
}
|
|
91
|
+
if (image.complete) {
|
|
92
|
+
resolve();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
var createBattleDisplayPreloadState = () => /* @__PURE__ */ new Map();
|
|
97
|
+
var preloadBattleDisplayImages = async (urls, state, options) => {
|
|
98
|
+
const tasks = normalizeBattlePreloadUrls(urls).map((url) => {
|
|
99
|
+
const existing = state.get(url);
|
|
100
|
+
if (existing?.status === "loaded") return Promise.resolve(true);
|
|
101
|
+
if (existing?.status === "loading" && existing.promise) return existing.promise;
|
|
102
|
+
if (existing?.status === "failed" && !options.retryFailed) return Promise.resolve(false);
|
|
103
|
+
registerImagePreloadLink(url);
|
|
104
|
+
const promise = loadImage(url).then(() => {
|
|
105
|
+
state.set(url, { status: "loaded" });
|
|
106
|
+
return true;
|
|
107
|
+
}).catch((err) => {
|
|
108
|
+
state.set(url, { status: "failed" });
|
|
109
|
+
options.logger.warn("[BattleDisplay] \u8D44\u6E90\u9884\u52A0\u8F7D\u5931\u8D25", { scope: options.scope, url, err });
|
|
110
|
+
return false;
|
|
111
|
+
});
|
|
112
|
+
state.set(url, { status: "loading", promise });
|
|
113
|
+
return promise;
|
|
114
|
+
});
|
|
115
|
+
if (tasks.length === 0) return true;
|
|
116
|
+
return (await Promise.all(tasks)).every(Boolean);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// src/DisplayAudioController.ts
|
|
120
|
+
var classifyDisplayAudioFailure = (error) => {
|
|
121
|
+
if (error instanceof DOMException && error.name === "NotAllowedError") return "blocked";
|
|
122
|
+
return "failed";
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// src/tuning/SessionTuningTypes.ts
|
|
126
|
+
var SESSION_TUNING_SCHEMA_VERSION = 11;
|
|
127
|
+
|
|
128
|
+
// src/tuning/SessionTuningResolver.ts
|
|
129
|
+
var clone = (value) => structuredClone(value);
|
|
130
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
131
|
+
var resolvePath = (bundle, path) => {
|
|
132
|
+
const [category, ...parts] = path.split(".");
|
|
133
|
+
let value = bundle[category].values;
|
|
134
|
+
for (const part of parts) {
|
|
135
|
+
if (!isRecord(value)) return void 0;
|
|
136
|
+
value = value[part];
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
};
|
|
140
|
+
var createSessionTuningResolver = (bundle) => ({
|
|
141
|
+
get: (path) => clone(resolvePath(bundle, path)),
|
|
142
|
+
getRect: (path) => {
|
|
143
|
+
const value = resolvePath(bundle, path);
|
|
144
|
+
if (!isRecord(value)) return null;
|
|
145
|
+
const { x, y, width, height } = value;
|
|
146
|
+
if (![x, y, width, height].every((item) => typeof item === "number" && Number.isFinite(item)))
|
|
147
|
+
return null;
|
|
148
|
+
return { x, y, width, height };
|
|
149
|
+
},
|
|
150
|
+
snapshot: () => clone(bundle)
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// src/tuning/SessionTuningSchema.ts
|
|
154
|
+
import { z } from "zod";
|
|
155
|
+
var hexColor = /^#[0-9a-fA-F]{6}(?:[0-9a-fA-F]{2})?$/;
|
|
156
|
+
var valueSchema = z.lazy(
|
|
157
|
+
() => z.union([
|
|
158
|
+
z.string(),
|
|
159
|
+
z.number().finite(),
|
|
160
|
+
z.boolean(),
|
|
161
|
+
z.null(),
|
|
162
|
+
z.array(valueSchema),
|
|
163
|
+
z.record(z.string(), valueSchema)
|
|
164
|
+
])
|
|
165
|
+
);
|
|
166
|
+
var sessionTuningDocumentSchema = z.object({
|
|
167
|
+
schemaVersion: z.literal(SESSION_TUNING_SCHEMA_VERSION),
|
|
168
|
+
values: z.record(z.string(), valueSchema)
|
|
169
|
+
});
|
|
170
|
+
var sessionTuningBundleSchema = z.object({
|
|
171
|
+
layout: sessionTuningDocumentSchema,
|
|
172
|
+
visual: sessionTuningDocumentSchema,
|
|
173
|
+
motion: sessionTuningDocumentSchema,
|
|
174
|
+
audio: sessionTuningDocumentSchema
|
|
175
|
+
});
|
|
176
|
+
var validateTuningColor = (value) => typeof value === "string" && hexColor.test(value);
|
|
177
|
+
var parseSessionTuningDocument = (value) => sessionTuningDocumentSchema.parse(value);
|
|
178
|
+
var parseSessionTuningBundle = (value) => sessionTuningBundleSchema.parse(value);
|
|
179
|
+
|
|
180
|
+
// src/tuning/SessionTuningMigration.ts
|
|
181
|
+
var migrateSessionTuningDocumentWithResult = (input) => {
|
|
182
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || input.schemaVersion !== SESSION_TUNING_SCHEMA_VERSION) {
|
|
183
|
+
throw new Error(`\u4E0D\u652F\u6301\u7248\u672C: Visual Lab \u4EC5\u652F\u6301 tuning v${SESSION_TUNING_SCHEMA_VERSION}`);
|
|
184
|
+
}
|
|
185
|
+
const document2 = parseSessionTuningDocument(input);
|
|
186
|
+
return {
|
|
187
|
+
document: document2,
|
|
188
|
+
fromVersion: SESSION_TUNING_SCHEMA_VERSION,
|
|
189
|
+
removedPaths: []
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
var migrateSessionTuningDocument = (input) => migrateSessionTuningDocumentWithResult(input).document;
|
|
193
|
+
|
|
194
|
+
// src/tuning/SessionTuningStore.ts
|
|
195
|
+
var clone2 = (value) => structuredClone(value);
|
|
196
|
+
var hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
197
|
+
var isTuningRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
198
|
+
var mergeTuningValue = (formal, workspace) => {
|
|
199
|
+
if (!isTuningRecord(formal) || !isTuningRecord(workspace)) return clone2(workspace);
|
|
200
|
+
const merged = {};
|
|
201
|
+
for (const [key, formalValue] of Object.entries(formal)) {
|
|
202
|
+
merged[key] = hasOwn(workspace, key) ? mergeTuningValue(formalValue, workspace[key]) : clone2(formalValue);
|
|
203
|
+
}
|
|
204
|
+
return merged;
|
|
205
|
+
};
|
|
206
|
+
var mergeWorkspaceWithFormal = (formal, workspace) => ({
|
|
207
|
+
layout: {
|
|
208
|
+
schemaVersion: formal.layout.schemaVersion,
|
|
209
|
+
values: mergeTuningValue(formal.layout.values, workspace.layout.values)
|
|
210
|
+
},
|
|
211
|
+
visual: {
|
|
212
|
+
schemaVersion: formal.visual.schemaVersion,
|
|
213
|
+
values: mergeTuningValue(formal.visual.values, workspace.visual.values)
|
|
214
|
+
},
|
|
215
|
+
motion: {
|
|
216
|
+
schemaVersion: formal.motion.schemaVersion,
|
|
217
|
+
values: mergeTuningValue(formal.motion.values, workspace.motion.values)
|
|
218
|
+
},
|
|
219
|
+
audio: {
|
|
220
|
+
schemaVersion: formal.audio.schemaVersion,
|
|
221
|
+
values: mergeTuningValue(formal.audio.values, workspace.audio.values)
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
var tuningValuesEqual = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
225
|
+
var setPath = (bundle, path, value) => {
|
|
226
|
+
const [category, ...parts] = path.split(".");
|
|
227
|
+
if (!["layout", "visual", "motion", "audio"].includes(category) || parts.length === 0 || parts.some((part) => !part || ["__proto__", "prototype", "constructor"].includes(part)))
|
|
228
|
+
throw new Error("Invalid tuning path");
|
|
229
|
+
let target = bundle[category].values;
|
|
230
|
+
for (const part of parts.slice(0, -1)) {
|
|
231
|
+
const child = target[part];
|
|
232
|
+
if (typeof child !== "object" || child === null || Array.isArray(child)) target[part] = {};
|
|
233
|
+
target = target[part];
|
|
234
|
+
}
|
|
235
|
+
const last = parts.at(-1);
|
|
236
|
+
if (!last) return;
|
|
237
|
+
if (value === void 0) delete target[last];
|
|
238
|
+
else target[last] = clone2(value);
|
|
239
|
+
};
|
|
240
|
+
var rebaseTuningBundle = (previousFormal, value, nextFormal, editablePaths) => {
|
|
241
|
+
const normalizedValue = mergeWorkspaceWithFormal(previousFormal, value);
|
|
242
|
+
const previousFormalResolver = createSessionTuningResolver(previousFormal);
|
|
243
|
+
const valueResolver = createSessionTuningResolver(normalizedValue);
|
|
244
|
+
const nextFormalResolver = createSessionTuningResolver(nextFormal);
|
|
245
|
+
const rebased = clone2(nextFormal);
|
|
246
|
+
for (const path of editablePaths) {
|
|
247
|
+
const previousFormalValue = previousFormalResolver.get(path);
|
|
248
|
+
const nextFormalValue = nextFormalResolver.get(path);
|
|
249
|
+
if (!tuningValuesEqual(previousFormalValue, nextFormalValue)) continue;
|
|
250
|
+
setPath(rebased, path, valueResolver.get(path));
|
|
251
|
+
}
|
|
252
|
+
return rebased;
|
|
253
|
+
};
|
|
254
|
+
var SessionTuningStore = class {
|
|
255
|
+
constructor(defaults, editablePaths = flatten(defaults)) {
|
|
256
|
+
this.editablePaths = editablePaths;
|
|
257
|
+
this.formal = clone2(defaults);
|
|
258
|
+
this.workspaceSaved = clone2(defaults);
|
|
259
|
+
this.working = clone2(defaults);
|
|
260
|
+
}
|
|
261
|
+
editablePaths;
|
|
262
|
+
formal;
|
|
263
|
+
workspaceSaved;
|
|
264
|
+
working;
|
|
265
|
+
undoStack = [];
|
|
266
|
+
redoStack = [];
|
|
267
|
+
listeners = /* @__PURE__ */ new Set();
|
|
268
|
+
subscribe(listener) {
|
|
269
|
+
this.listeners.add(listener);
|
|
270
|
+
return () => this.listeners.delete(listener);
|
|
271
|
+
}
|
|
272
|
+
getResolver() {
|
|
273
|
+
return createSessionTuningResolver(this.working);
|
|
274
|
+
}
|
|
275
|
+
getFormalResolver() {
|
|
276
|
+
return createSessionTuningResolver(this.formal);
|
|
277
|
+
}
|
|
278
|
+
getWorkspaceSavedResolver() {
|
|
279
|
+
return createSessionTuningResolver(this.workspaceSaved);
|
|
280
|
+
}
|
|
281
|
+
getSnapshot() {
|
|
282
|
+
return {
|
|
283
|
+
formal: clone2(this.formal),
|
|
284
|
+
workspaceSaved: clone2(this.workspaceSaved),
|
|
285
|
+
working: clone2(this.working),
|
|
286
|
+
formalDirtyPaths: this.diff().map((change) => change.path),
|
|
287
|
+
workspaceDirtyPaths: this.workspaceDiff().map((change) => change.path)
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
patch(changes, record = true) {
|
|
291
|
+
if (changes.length === 0) return;
|
|
292
|
+
const next = clone2(this.working);
|
|
293
|
+
for (const change of changes) setPath(next, change.path, change.after);
|
|
294
|
+
this.working = parseSessionTuningBundle(next);
|
|
295
|
+
if (record) {
|
|
296
|
+
this.undoStack.push(changes.map(clone2));
|
|
297
|
+
this.redoStack.length = 0;
|
|
298
|
+
}
|
|
299
|
+
this.emit();
|
|
300
|
+
}
|
|
301
|
+
set(path, value) {
|
|
302
|
+
this.patch([{ path, before: this.getResolver().get(path), after: value }]);
|
|
303
|
+
}
|
|
304
|
+
resetPath(path) {
|
|
305
|
+
this.patch([
|
|
306
|
+
{
|
|
307
|
+
path,
|
|
308
|
+
before: this.getResolver().get(path),
|
|
309
|
+
after: this.getWorkspaceSavedResolver().get(path)
|
|
310
|
+
}
|
|
311
|
+
]);
|
|
312
|
+
}
|
|
313
|
+
resetAll() {
|
|
314
|
+
this.working = clone2(this.workspaceSaved);
|
|
315
|
+
this.undoStack.length = 0;
|
|
316
|
+
this.redoStack.length = 0;
|
|
317
|
+
this.emit();
|
|
318
|
+
}
|
|
319
|
+
saveWorkspace() {
|
|
320
|
+
this.workspaceSaved = clone2(this.working);
|
|
321
|
+
this.undoStack.length = 0;
|
|
322
|
+
this.redoStack.length = 0;
|
|
323
|
+
this.emit();
|
|
324
|
+
}
|
|
325
|
+
markApplied(paths) {
|
|
326
|
+
if (paths.length === 0) return;
|
|
327
|
+
const resolver = this.getResolver();
|
|
328
|
+
for (const path of paths) setPath(this.formal, path, resolver.get(path));
|
|
329
|
+
this.undoStack.length = 0;
|
|
330
|
+
this.redoStack.length = 0;
|
|
331
|
+
this.emit();
|
|
332
|
+
}
|
|
333
|
+
loadWorkspace(value, formalBaseline) {
|
|
334
|
+
const workspace = formalBaseline ? rebaseTuningBundle(formalBaseline, value, this.formal, this.editablePaths) : clone2(this.formal);
|
|
335
|
+
this.working = clone2(workspace);
|
|
336
|
+
this.workspaceSaved = clone2(workspace);
|
|
337
|
+
this.undoStack.length = 0;
|
|
338
|
+
this.redoStack.length = 0;
|
|
339
|
+
this.emit();
|
|
340
|
+
}
|
|
341
|
+
rebaseFormal(nextFormal) {
|
|
342
|
+
if (JSON.stringify(this.formal) === JSON.stringify(nextFormal)) return;
|
|
343
|
+
const previousFormal = this.formal;
|
|
344
|
+
this.workspaceSaved = rebaseTuningBundle(
|
|
345
|
+
previousFormal,
|
|
346
|
+
this.workspaceSaved,
|
|
347
|
+
nextFormal,
|
|
348
|
+
this.editablePaths
|
|
349
|
+
);
|
|
350
|
+
this.working = rebaseTuningBundle(previousFormal, this.working, nextFormal, this.editablePaths);
|
|
351
|
+
this.formal = clone2(nextFormal);
|
|
352
|
+
this.undoStack.length = 0;
|
|
353
|
+
this.redoStack.length = 0;
|
|
354
|
+
this.emit();
|
|
355
|
+
}
|
|
356
|
+
undo() {
|
|
357
|
+
const changes = this.undoStack.pop();
|
|
358
|
+
if (!changes) return;
|
|
359
|
+
for (const change of changes) setPath(this.working, change.path, change.before);
|
|
360
|
+
this.redoStack.push(changes);
|
|
361
|
+
this.emit();
|
|
362
|
+
}
|
|
363
|
+
redo() {
|
|
364
|
+
const changes = this.redoStack.pop();
|
|
365
|
+
if (!changes) return;
|
|
366
|
+
for (const change of changes) setPath(this.working, change.path, change.after);
|
|
367
|
+
this.undoStack.push(changes);
|
|
368
|
+
this.emit();
|
|
369
|
+
}
|
|
370
|
+
diff() {
|
|
371
|
+
return this.diffFrom(this.formal);
|
|
372
|
+
}
|
|
373
|
+
workspaceDiff() {
|
|
374
|
+
return this.diffFrom(this.workspaceSaved);
|
|
375
|
+
}
|
|
376
|
+
diffFrom(baseline) {
|
|
377
|
+
const paths = /* @__PURE__ */ new Set([...flatten(baseline), ...flatten(this.working)]);
|
|
378
|
+
const saved = createSessionTuningResolver(baseline);
|
|
379
|
+
const working = this.getResolver();
|
|
380
|
+
const changes = [];
|
|
381
|
+
for (const path of paths) {
|
|
382
|
+
const before = saved.get(path);
|
|
383
|
+
const after = working.get(path);
|
|
384
|
+
if (JSON.stringify(before) !== JSON.stringify(after)) changes.push({ path, before, after });
|
|
385
|
+
}
|
|
386
|
+
return changes;
|
|
387
|
+
}
|
|
388
|
+
emit() {
|
|
389
|
+
for (const listener of this.listeners) listener();
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
var flatten = (bundle) => {
|
|
393
|
+
const paths = [];
|
|
394
|
+
const visit = (value, prefix) => {
|
|
395
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
396
|
+
paths.push(prefix);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
for (const [key, child] of Object.entries(value)) visit(child, `${prefix}.${key}`);
|
|
400
|
+
};
|
|
401
|
+
for (const [category, document2] of Object.entries(bundle)) visit(document2.values, category);
|
|
402
|
+
return paths;
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// src/MediaPreloader.ts
|
|
406
|
+
var normalizeUrls = (urls) => [
|
|
407
|
+
...new Set([...urls].map((url) => url?.trim()).filter((url) => Boolean(url)))
|
|
408
|
+
];
|
|
409
|
+
var preloadSessionAudio = async (urls, options) => {
|
|
410
|
+
const queue = normalizeUrls(urls);
|
|
411
|
+
const failedUrls = [];
|
|
412
|
+
let cursor = 0;
|
|
413
|
+
let completed = 0;
|
|
414
|
+
const worker = async () => {
|
|
415
|
+
while (!options.signal.aborted) {
|
|
416
|
+
const index = cursor;
|
|
417
|
+
cursor += 1;
|
|
418
|
+
const url = queue[index];
|
|
419
|
+
if (!url) return;
|
|
420
|
+
try {
|
|
421
|
+
await retrySessionDisplayResource(() => options.cache.load(url), {
|
|
422
|
+
signal: options.signal,
|
|
423
|
+
retryCount: options.retryCount,
|
|
424
|
+
retryDelayMs: options.retryDelayMs
|
|
425
|
+
});
|
|
426
|
+
} catch {
|
|
427
|
+
if (!options.signal.aborted) failedUrls.push(url);
|
|
428
|
+
} finally {
|
|
429
|
+
completed += 1;
|
|
430
|
+
options.onProgress(completed, queue.length);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
await Promise.all(
|
|
435
|
+
Array.from({ length: Math.min(Math.max(1, options.concurrency ?? 6), queue.length) }, worker)
|
|
436
|
+
);
|
|
437
|
+
return { failedUrls };
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// src/AudioByteCache.ts
|
|
441
|
+
var AudioByteCache = class {
|
|
442
|
+
constructor(fetchAudio = fetch) {
|
|
443
|
+
this.fetchAudio = fetchAudio;
|
|
444
|
+
}
|
|
445
|
+
fetchAudio;
|
|
446
|
+
records = /* @__PURE__ */ new Map();
|
|
447
|
+
async load(url) {
|
|
448
|
+
let record = this.records.get(url);
|
|
449
|
+
if (!record) {
|
|
450
|
+
const controller = new AbortController();
|
|
451
|
+
const promise = this.fetchAudio(url, { signal: controller.signal }).then(async (response) => {
|
|
452
|
+
if (!response.ok) throw new Error(`Audio request failed: ${response.status}`);
|
|
453
|
+
const bytes = await response.arrayBuffer();
|
|
454
|
+
if (controller.signal.aborted) throw new DOMException("Audio load cancelled", "AbortError");
|
|
455
|
+
return bytes;
|
|
456
|
+
});
|
|
457
|
+
record = { controller, promise };
|
|
458
|
+
this.records.set(url, record);
|
|
459
|
+
const owner = record;
|
|
460
|
+
void promise.catch(() => {
|
|
461
|
+
if (this.records.get(url) === owner) this.records.delete(url);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return (await record.promise).slice(0);
|
|
465
|
+
}
|
|
466
|
+
clear() {
|
|
467
|
+
for (const record of this.records.values()) record.controller.abort();
|
|
468
|
+
this.records.clear();
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
export {
|
|
472
|
+
AudioByteCache,
|
|
473
|
+
SESSION_DISPLAY_RESOURCE_RETRY_COUNT,
|
|
474
|
+
SESSION_DISPLAY_RESOURCE_RETRY_DELAY_MS,
|
|
475
|
+
SESSION_TUNING_SCHEMA_VERSION,
|
|
476
|
+
SessionTuningStore,
|
|
477
|
+
classifyDisplayAudioFailure,
|
|
478
|
+
createBattleDisplayPreloadState,
|
|
479
|
+
createSessionTuningResolver,
|
|
480
|
+
migrateSessionTuningDocument,
|
|
481
|
+
migrateSessionTuningDocumentWithResult,
|
|
482
|
+
normalizeBattlePreloadUrls,
|
|
483
|
+
parseSessionTuningBundle,
|
|
484
|
+
parseSessionTuningDocument,
|
|
485
|
+
preloadBattleDisplayImages,
|
|
486
|
+
preloadSessionAudio,
|
|
487
|
+
retrySessionDisplayResource,
|
|
488
|
+
sessionTuningBundleSchema,
|
|
489
|
+
sessionTuningDocumentSchema,
|
|
490
|
+
validateTuningColor,
|
|
491
|
+
waitForSessionMediaReady
|
|
492
|
+
};
|
|
493
|
+
//# sourceMappingURL=index.js.map
|