UNPKG

@nent/core

Version:

Functional elements to add routing, data-binding, dynamic HTML, declarative actions, audio, video, and so much more. Supercharge static HTML files into web apps without script or builds.

987 lines (974 loc) 32.2 kB
/*! * NENT 2022 */ import { r as registerInstance, h, H as Host, a as getElement } from './index-916ca544.js'; import { E as EventEmitter, e as eventBus, a as actionBus } from './index-f7016b94.js'; import { w as warn, f as debugIf } from './logging-5a93c8af.js'; import { o as onChange, a as state } from './state-27a8a5bc.js'; import { r as removeDataProvider, a as addDataProvider, g as getDataProvider } from './factory-acbf0d3d.js'; import { R as ROUTE_EVENTS } from './interfaces-3b78db83.js'; import { D as DiscardStrategy, A as AUDIO_EVENTS, a as AUDIO_TOPIC, b as AUDIO_COMMANDS, c as AudioType } from './interfaces-13ff7aec.js'; import { r as requireValue } from './values-ddfac998.js'; import { m as markTrack } from './tracks-667892d6.js'; import { D as DATA_EVENTS } from './interfaces-8c5cd1b8.js'; import { d as debounce } from './promises-584c4ece.js'; import { s as state$1, o as onChange$1 } from './state-627a24e0.js'; import './index-4bfabbbd.js'; import './mutex-e5645c85.js'; import './memory-0d63dacd.js'; /* It's a list of audio tracks that can be played, stopped, and destroyed */ class AudioList { constructor() { this.items = []; } /** * It returns true if the length of the items array is equal to 0 * @returns The length of the items array. */ isEmpty() { return this.items.length == 0; } /** * If the length of the items array is greater than 0, return true * @returns The length of the items array. */ hasItems() { return this.items.length > 0; } /** * Find the first item in the items array that has a trackId property that matches the trackId * parameter. If no item is found, return null * @param {string} trackId - The trackId of the track you want to find. * @returns The trackId of the track that is being searched for. */ findTrack(trackId) { return this.items.find(a => a.trackId == trackId) || null; } /** * It stops all the items in the items array. */ stop() { this.items.forEach(t => { if (t.playing()) t.stop(); }); } /** * It destroys all the items in the array. */ destroy() { this.items.forEach(a => a.destroy()); } /** * It filters out the audio tracks that have a discard strategy that matches one of the reasons * passed in * @param {DiscardStrategy[]} reasons - DiscardStrategy[] */ discard(...reasons) { const eligibleAudio = (audio) => !reasons.includes(audio.discard); this.items = this.items.filter(i => eligibleAudio(i)) || null; } } /* It's a wrapper around the Howler library that allows us to play audio files */ class AudioTrack { /** * It creates a new Howl instance, and assigns it to the sound property of the AudioTrack instance * @param {AudioInfo} audio - AudioInfo * @param {Listener} [onEnd] - A callback function that is called when the audio track ends. */ constructor(audio, onEnd) { this.discard = DiscardStrategy.route; this.loop = false; const { trackId, src, type, loop } = audio; requireValue(trackId, 'trackId', 'n-audio: track'); requireValue(src, 'src', 'n-audio: track'); requireValue(type, 'type', 'n-audio: track'); Object.assign(this, audio, { onEnd, }); const sound = new Howl({ src, loop: type === 'music' ? loop : false, onload: () => { var _a; (_a = this.onLoad) === null || _a === void 0 ? void 0 : _a.call(this, this); }, onend: () => { onEnd === null || onEnd === void 0 ? void 0 : onEnd.call(this, this); }, onloaderror: (_id, error) => { warn(`n-audio: An error occurred for audio track ${trackId}: ${error}`); onEnd === null || onEnd === void 0 ? void 0 : onEnd.call(this); }, onplayerror: () => { sound.once('unlock', () => { sound.play(); }); }, preload: true, autoplay: false, html5: false, }); this.sound = sound; } /** * It returns a boolean value that indicates whether the sound is currently playing * @returns The sound is being returned. */ playing() { return this.sound.playing(); } /** * It returns true if the volume of the sound is 0, and false otherwise * @returns The volume of the sound. */ muted() { return this.sound.volume() == 0; } /** * It returns the state of the sound * @returns The state of the sound object. */ state() { return this.sound.state(); } /** * If the sound is loaded, play it. If it's loading, wait for it to load and then play it */ start() { if (this.sound.state() === 'loaded') { this.play(); } else if (this.sound.state() === 'loading') { this.sound.once(AUDIO_EVENTS.Loaded, () => { this.play(); }); } } /** * "Play the sound, but fade it in over half a second." * * The first line sets the volume to 0. This is important because if we don't do this, the sound will * play at full volume for a split second before fading in */ play() { var _a; this.sound.volume(0); this.sound.play(); this.sound.fade(0, ((_a = window.Howler) === null || _a === void 0 ? void 0 : _a.volume()) || 0.5, 500); } /** * It pauses the sound */ pause() { this.sound.pause(); } /** * It fades the volume of the sound to 0 over 500 milliseconds, then stops the sound */ stop() { var _a; this.sound.fade(((_a = window.Howler) === null || _a === void 0 ? void 0 : _a.volume()) || 0.5, 0, 500); this.sound.stop(); } /** * It sets the mute property of the sound object to the value of the mute parameter * @param {boolean} mute - boolean - true to mute, false to unmute */ mute(mute) { this.sound.mute(mute); } /** * The resume function plays the sound */ resume() { this.sound.play(); } /** * This function sets the volume of the sound to the number passed in * @param {number} set - The volume you want to set the sound to. */ setVolume(set) { this.sound.volume(set); } /** * This function seeks to a specific time in the audio file * @param {number} time - The time in seconds to seek to. */ seek(time) { this.sound.seek(time); } /** * It unloads the sound from the game. */ destroy() { this.sound.unload(); } } /* It loads audio tracks and keeps them in memory until they are no longer needed */ class AudioLoader extends AudioList { /** * It loads an audio track into the player * @param {AudioInfo} info - AudioInfo - This is the audio info object that contains the track id, * the track name, the track url, and the track duration. * @param {Listener} onChanged - This is a callback function that is called when the track is loaded. * @returns The track is being returned. */ load(info, onChanged) { const found = this.findTrack(info.trackId); if (found) return; const track = new AudioTrack(info, () => { if (track.discard != DiscardStrategy.none) { track === null || track === void 0 ? void 0 : track.destroy(); } onChanged(); }); this.items.push(track); } } /* It's a list of audio tracks that can be queued and played in order */ class AudioQueue extends AudioList { /** * It adds a new track to the queue * @param {AudioInfo} info - AudioInfo - This is the audio info object that is passed to the * queueAudio function. * @param {Listener} onEnd - Listener - This is a function that is called when the track is finished * playing. * @returns The length of the array. */ queueAudio(info, onEnd) { const found = this.findTrack(info.trackId); if (found) return; const track = new AudioTrack(info, () => { if (track.discard != DiscardStrategy.none) { track === null || track === void 0 ? void 0 : track.destroy(); } onEnd(); }); return this.items.push(track); } /** * It takes an AudioTrack and a Listener, and returns the index of the inserted track * @param {AudioTrack} track - AudioTrack - The track to insert * @param {Listener} onEnd - Listener * @returns The index of the inserted item. */ insertTrack(track, onEnd) { const originalOnEnd = track.onEnd; track.onEnd = () => { originalOnEnd === null || originalOnEnd === void 0 ? void 0 : originalOnEnd.call(track, track); onEnd(); }; return this.items.unshift(track); } /** * "If there are items in the queue, return the first one, otherwise return null." * * The first line of the function is a TypeScript type annotation. It's saying that the function will * return either an AudioTrack or null * @returns The first item in the array or null if the array is empty. */ getNext() { return this.items.shift() || null; } } /* Exporting the class so that it can be used in other files. */ class PlayerBase { constructor() { this.active = null; } /** * If the active audio is not null, pause it */ pause() { var _a; (_a = this.active) === null || _a === void 0 ? void 0 : _a.pause(); } /** * If the active audio is not null, then call the play() method on it */ play() { var _a; (_a = this.active) === null || _a === void 0 ? void 0 : _a.play(); } /** * If the active audio is not null, then call the stop() method on it */ stop() { var _a; (_a = this.active) === null || _a === void 0 ? void 0 : _a.stop(); } /** * If the active audio is not null, then call the resume() method on it */ resume() { var _a; (_a = this.active) === null || _a === void 0 ? void 0 : _a.resume(); } /** * It sets the mute state of the active player * @param {boolean} mute - boolean - Whether to mute or unmute the video */ mute(mute) { var _a; (_a = this.active) === null || _a === void 0 ? void 0 : _a.mute(mute); } /** * If the active strategy is being discarded, destroy it * @param {DiscardStrategy[]} reasons - DiscardStrategy[] */ discard(...reasons) { var _a; if (this.active && reasons.includes(this.active.discard)) { (_a = this.active) === null || _a === void 0 ? void 0 : _a.destroy(); this.active = null; } } } /* It's a class that manages the loading, playing, and queuing of audio */ class MusicPlayer extends PlayerBase { /** * The constructor function is a special function that is called when an object is created from a * class * @param {Listener} changed - Listener - This is the listener that will be called when the value of * the property changes. */ constructor(changed) { super(); this.changed = changed; this.loader = new AudioLoader(); this.queue = new AudioQueue(); } /** * It queues an audio file, and if there is no active audio file, it plays the queued audio file * @param {AudioInfo} info - AudioInfo - The audio info of the audio you want to queue. */ queueAudio(info) { var _a; this.queue.queueAudio(info, () => { var _a; this.active = this.queue.getNext(); (_a = this.active) === null || _a === void 0 ? void 0 : _a.play(); this.changed(); }); if (this.active == null) { this.active = this.queue.getNext(); (_a = this.active) === null || _a === void 0 ? void 0 : _a.play(); } this.changed(); } /** * It loads an audio file, and then calls the changed function * @param {AudioInfo} info - AudioInfo */ load(info) { this.loader.load(info, this.changed); this.changed(); } /** * It plays a track * @param {string} trackId - The id of the track to play */ async playTrack(trackId) { var _a; const track = this.loader.findTrack(trackId); if (track) { this.loader.stop(); this.queue.insertTrack(track, () => { var _a; this.active = this.queue.getNext(); (_a = this.active) === null || _a === void 0 ? void 0 : _a.play(); this.changed(); }); this.discard(DiscardStrategy.next); this.active = this.queue.getNext(); (_a = this.active) === null || _a === void 0 ? void 0 : _a.play(); await markTrack(trackId); this.changed(); } } /** * If the loader is discarded, discard the queue and notify the world that the queue has changed. * @param {DiscardStrategy[]} reasons - DiscardStrategy[] */ discard(...reasons) { super.discard(...reasons); this.loader.discard(...reasons); this.queue.discard(...reasons); this.changed(); } /** * If the active audio is not null, or the loader has items, or the queue has items, then return true * @returns A boolean value. */ hasAudio() { return (this.active != null || this.loader.hasItems() || this.queue.hasItems()); } /** * It destroys the active scene, the loader, and the queue. */ destroy() { var _a; (_a = this.active) === null || _a === void 0 ? void 0 : _a.destroy(); this.loader.destroy(); this.queue.destroy(); } } /* It's a wrapper around an AudioLoader that keeps track of the currently playing track and allows you to play a new track */ class SoundPlayer extends PlayerBase { /** * The constructor function is a special function that is called when an object is created from a * class * @param {Listener} changed - Listener - This is the listener that will be called when the value of * the property changes. */ constructor(changed) { super(); this.changed = changed; this.loader = new AudioLoader(); } /** * It loads an audio file, and when it's done loading, it sets the active audio to null and calls the * changed function * @param {AudioInfo} info - AudioInfo */ load(info) { this.loader.load(info, () => { this.active = null; this.changed(); }); this.changed(); } /** * It plays a track * @param {string} trackId - The id of the track to play. */ async playTrack(trackId) { const track = this.loader.findTrack(trackId); if (track) { this.loader.stop(); this.active = null; this.discard(DiscardStrategy.next); this.active = track; track.play(); await markTrack(trackId); this.changed(); } } /** * If the active strategy is being discarded, destroy it and set it to null * @param {DiscardStrategy[]} reasons - DiscardStrategy[] */ discard(...reasons) { var _a; if (this.active && reasons.includes(this.active.discard)) { (_a = this.active) === null || _a === void 0 ? void 0 : _a.destroy(); this.active = null; } this.loader.discard(...reasons); this.changed(); } /** * If the active item is not null, or the loader has items, then return true * @returns A boolean value. */ hasAudio() { return this.active != null || this.loader.hasItems(); } /** * It destroys the active scene and the loader. */ destroy() { var _a; (_a = this.active) === null || _a === void 0 ? void 0 : _a.destroy(); this.loader.destroy(); } } /* It listens to the audio listener and emits a `changed` event when the audio listener changes */ class AudioDataProvider { /** * A constructor function that takes in an audioListener as a parameter. It then creates a new * EventEmitter and assigns it to the changed property. It then creates a change function that is * debounced and emits a changed event. It then subscribes to the audioListener's changed event and * calls the change function. * @param {AudioActionListener} audioListener - AudioActionListener - this is the service that * listens for changes to the audio data. */ constructor(audioListener) { this.audioListener = audioListener; this.changed = new EventEmitter(); const change = debounce(1000, () => { this.changed.emit(DATA_EVENTS.DataChanged, { provider: 'audio', }); }, true); this.listenerSubscription = this.audioListener.changed.on('changed', () => { change(); }); } async get(key) { switch (key) { // Global case 'hasAudio': return this.audioListener.hasAudio().toString(); case 'isPlaying': return this.audioListener.isPlaying().toString(); // Music files case 'loadedMusic': return this.audioListener.music ? JSON.stringify(this.audioListener.music.loader.items) : null; case 'queuedMusic': return this.audioListener.music ? JSON.stringify(this.audioListener.music.queue.items) : null; case 'currentMusic': return this.audioListener.music.active ? JSON.stringify(this.audioListener.music.active) : null; // Sound files case 'loadedSounds': return this.audioListener.sound.loader.items ? JSON.stringify(this.audioListener.sound.loader.items) : null; case 'currentSound': return this.audioListener.sound.active ? JSON.stringify(this.audioListener.sound.active) : null; default: return null; } } async set(_key, _value) { // do nothing } /** * It destroys the listener subscription. */ destroy() { this.listenerSubscription(); } } /* It listens for audio commands and events, and then plays the audio */ class AudioActionListener { /** * "This function is called when the class is instantiated, and it sets up the music and sound * players, and subscribes to the event bus." * * The first thing we do is create a new EventEmitter called "changed". This is used to notify the * rest of the app that the audio state has changed * @param {Window} window - Window - the window object * @param {IEventEmitter} eventBus - This is the event bus that the game uses to communicate with the * game engine. * @param {IEventEmitter} actionBus - IEventEmitter - this is the event bus that is used to send * actions to the game. * @param {boolean} enableDataProvider - boolean - whether to enable the data provider * @param {boolean} [debug=false] - boolean - if true, will log all events to the console */ constructor(window, eventBus, actionBus, enableDataProvider, debug = false) { this.window = window; this.eventBus = eventBus; this.actionBus = actionBus; this.enableDataProvider = enableDataProvider; this.debug = debug; this.muted = false; this.volume = 0; this.changed = new EventEmitter(); this.music = new MusicPlayer(() => { this.changed.emit('changed'); }); this.sound = new SoundPlayer(() => { this.changed.emit('changed'); }); this.volume = 1; this.stateEnabledSubscription = onChange('audioEnabled', enabled => { if (enabled) this.subscribe(); else this.unsubscribe(); }); if (state.audioEnabled) this.subscribe(); } subscribe() { const enableDataProvider = () => { if (this.enableDataProvider && this.provider == undefined) { this.provider = new AudioDataProvider(this); addDataProvider('audio', this.provider); } }; if (state.audioEnabled) enableDataProvider(); this.stateDataSubscription = onChange('dataEnabled', enabled => { var _a; if (enabled) { enableDataProvider(); } else { removeDataProvider('audio'); (_a = this.provider) === null || _a === void 0 ? void 0 : _a.destroy(); this.provider = undefined; } }); if (state$1.muted) this.mute(); this.stateMutedSubscription = onChange$1('muted', mute => { if (mute) this.mute(); else this.play(); }); this.actionSubscription = this.actionBus.on(AUDIO_TOPIC, async (ev) => { var _a, _b; debugIf(this.debug, `audio-listener: action received ${ev.command}${((_a = ev.data) === null || _a === void 0 ? void 0 : _a.type) || ''}:${((_b = ev.data) === null || _b === void 0 ? void 0 : _b.trackId) || ''}`); await this.commandReceived(ev.command, ev.data); }); this.eventSubscription = this.eventBus.on(ROUTE_EVENTS.RouteChanged, () => { debugIf(this.debug, 'audio-listener: route changed received'); this.music.discard(DiscardStrategy.route, DiscardStrategy.next); this.sound.discard(DiscardStrategy.route, DiscardStrategy.next); }); this.changed.emit('changed'); } unsubscribe() { var _a, _b, _c, _d; (_a = this.eventSubscription) === null || _a === void 0 ? void 0 : _a.call(this); (_b = this.actionSubscription) === null || _b === void 0 ? void 0 : _b.call(this); (_c = this.stateDataSubscription) === null || _c === void 0 ? void 0 : _c.call(this); (_d = this.stateMutedSubscription) === null || _d === void 0 ? void 0 : _d.call(this); this.music.destroy(); this.sound.destroy(); if (this.provider) { removeDataProvider('audio'); this.provider.destroy(); } this.changed.emit('changed'); } // Public Members /** * `return Boolean(this.music.active?.playing() || this.sound.active?.playing() || false)` * * The `Boolean()` function is a JavaScript function that returns a boolean value. * * The `this.music.active?.playing()` is a TypeScript null-safe operator. It's a way to check if the * `active` property of the `music` object is not null. If it's not null, then it will return the * `playing()` function. If it is null, then it will return null. * * The `this.sound.active?.playing()` is the same as the above, but for the `sound` object. * * The `||` is a JavaScript operator that returns the first value that is not null. * * The `false` is a JavaScript boolean value. * * So, if the `music` object is not * @returns Boolean */ isPlaying() { var _a, _b; return Boolean(((_a = this.music.active) === null || _a === void 0 ? void 0 : _a.playing()) || ((_b = this.sound.active) === null || _b === void 0 ? void 0 : _b.playing()) || false); } /** * It returns true if the music or sound has audio. * @returns A boolean value. */ hasAudio() { return this.music.hasAudio() || this.sound.hasAudio(); } /** * It pauses the music and sound, and then emits a changed event */ pause() { this.music.pause(); this.sound.pause(); this.changed.emit('changed'); } /** * If the audio is enabled, play the music and sound, and emit a changed event * @returns the value of the function. */ play() { if (!state.audioEnabled) return; this.music.play(); this.sound.play(); this.changed.emit('changed'); } /** * It stops the music and sound, and then emits a changed event */ stop() { this.music.stop(); this.sound.stop(); this.changed.emit('changed'); } /** * It resumes the music and sound effects if audio is enabled * @returns the value of the expression. */ resume() { if (!state.audioEnabled) return; this.music.resume(); this.sound.resume(); this.changed.emit('changed'); } /** * It sets the audioState.muted property to the value of the mute parameter, and then sets the muted * property of the music and sound objects to the value of the mute parameter, and then sets the * muted property of the AudioManager object to the value of the mute parameter, and then emits the * changed event * @param mute - boolean - Whether to mute or unmute the audio. */ mute(mute = !this.muted) { state$1.muted = mute; this.music.mute(mute); this.sound.mute(mute); this.muted = mute; this.changed.emit('changed'); } /** * If the current track is the one we want to seek, then seek it * @param {AudioType} type - AudioType - This is the type of audio you want to seek. * @param {string} trackId - The trackId of the audio track to seek. * @param {number} seek - The time in seconds to seek to. */ seek(type, trackId, seek) { const current = type == AudioType.music ? this.music.active : this.sound.active; if (current && current.trackId === trackId) { current.seek(seek); this.changed.emit('changed'); } } setVolume(value) { var _a; (_a = this.window.Howler) === null || _a === void 0 ? void 0 : _a.volume(value); this.muted = value == 0; this.volume = value; this.changed.emit('changed'); } // Private members async commandReceived(command, data) { switch (command) { case AUDIO_COMMANDS.load: { const audio = data; const { type } = audio; if (type == AudioType.sound) { this.sound.load(audio); } else if (type == AudioType.music) { this.music.load(audio); } else { return; } this.eventBus.emit(AUDIO_TOPIC, AUDIO_EVENTS.Loaded, audio.trackId); break; } case AUDIO_COMMANDS.play: { const audio = data; const { type, trackId, src } = audio; if (type == AudioType.music) { if (src) this.music.load(audio); this.music.playTrack(trackId); } else { if (src) this.sound.load(audio); this.sound.playTrack(trackId); } this.eventBus.emit(AUDIO_TOPIC, AUDIO_EVENTS.Played, trackId); break; } case AUDIO_COMMANDS.queue: { const audio = data; const { type, trackId } = audio; if (type != AudioType.music) return; this.music.queueAudio(audio); this.eventBus.emit(AUDIO_TOPIC, AUDIO_EVENTS.Queued, trackId); break; } case AUDIO_COMMANDS.start: { const audio = data; const { type, trackId } = audio; if (type == AudioType.music) { await this.music.playTrack(trackId); } else if (type == AudioType.sound) { await this.sound.playTrack(trackId); } else { return; } this.eventBus.emit(AUDIO_TOPIC, AUDIO_EVENTS.Started, trackId); break; } case AUDIO_COMMANDS.pause: { this.pause(); break; } case AUDIO_COMMANDS.resume: { this.resume(); break; } case AUDIO_COMMANDS.mute: { const { value } = data; this.mute(value); break; } case AUDIO_COMMANDS.seek: { const audio = data; const { type, trackId, value } = audio; if (trackId) { this.seek(type, trackId, value); } break; } case AUDIO_COMMANDS.stop: { this.stop(); break; } case AUDIO_COMMANDS.volume: { const { value } = data; this.setVolume(value); break; } } } /** * It unsubscribes from the stateEnabledSubscription and stateMutedSubscription. */ destroy() { this.unsubscribe(); this.stateEnabledSubscription(); this.stateMutedSubscription(); } } const audioCss = ":host{--display:inline-block;--width:200px;--color:white;--background-color:#000;--border:1px solid white;--fill:white;--icon-size:1rem}:host(.hidden){display:none}div{display:var(--display);max-width:var(--width);background-color:var(--background-color);border:var(--border);fill:var(--fill);padding:0.5em;color:var(--color)}.button{cursor:pointer}.button svg{display:flex;height:var(--icon-size);width:var(--icon-size)}p{margin:0}"; const Audio = class { constructor(hostRef) { registerInstance(this, hostRef); this.loaded = false; this.error = null; this.stats = { m: 0, ml: 0, mq: 0, s: 0, sl: 0, }; /** * The Howler.js Script Reference */ this.howlerVersion = '2.2.3'; /** * The display mode enabled shows player state and stats. * No track information or duration is to be displayed. */ this.display = false; /** * Use debug for verbose logging. Useful for figuring * things out. */ this.debug = false; /** * Experimental support for providing audio-data in the * data-provider system. */ this.dataProvider = false; } enableAudio() { state.audioEnabled = true; } async componentWillLoad() { debugIf(this.debug, 'n-audio: loading'); state$1.debug = this.debug; if (state$1.hasAudioComponent) { this.error = `Duplicate Audio Player`; return; } if (state.dataEnabled) { const storage = (await getDataProvider('storage')); const storedValue = await (storage === null || storage === void 0 ? void 0 : storage.get('audio-enabled')); if (storedValue) { state.audioEnabled = storedValue != 'false'; } state$1.muted = (await (storage === null || storage === void 0 ? void 0 : storage.get('audio-muted'))) == 'true'; this.audioStateSubscription = onChange$1('muted', async (m) => { await (storage === null || storage === void 0 ? void 0 : storage.set('audio-muted', m.toString())); }); this.commonStateSubscription = onChange('audioEnabled', async (m) => { await (storage === null || storage === void 0 ? void 0 : storage.set('audio-enabled', m.toString())); }); } } registerServices() { if (this.loaded) return; debugIf(this.debug, `n-audio: loading listener`); this.actions = new AudioActionListener(window, eventBus, actionBus, this.dataProvider, this.debug); this.actionSubscription = this.actions.changed.on('changed', () => { this.updateState(); }); state$1.hasAudioComponent = true; this.loaded = true; } updateState() { var _a, _b, _c, _d, _e; this.stats = { m: ((_a = this.actions) === null || _a === void 0 ? void 0 : _a.music.active) ? 1 : 0, ml: ((_b = this.actions) === null || _b === void 0 ? void 0 : _b.music.loader.items.length) || 0, mq: ((_c = this.actions) === null || _c === void 0 ? void 0 : _c.music.queue.items.length) || 0, s: ((_d = this.actions) === null || _d === void 0 ? void 0 : _d.sound.active) ? 1 : 0, sl: ((_e = this.actions) === null || _e === void 0 ? void 0 : _e.sound.loader.items.length) || 0, }; } Error() { return (h(Host, { hidden: !this.display }, h("div", null, h("p", { class: "error" }, this.error)))); } Disabled() { return (h(Host, { hidden: !this.display }, h("div", null, h("p", null, "Audio Disabled"), h("button", { onClick: () => { this.enableAudio(); } }, "Enable")))); } Audio() { if (!this.display) return null; return (h("div", null, h("p", null, "Audio ", this.actions.isPlaying() ? 'Playing' : 'Ready'), h("span", { title: "m=music s=sound l=loaded q=queued" }, "M:", this.stats.m, "\u00A0MQ:", this.stats.mq, "\u00A0ML:", this.stats.ml, "\u00A0S:", this.stats.s, "\u00A0SL:", this.stats.sl))); } NoAudio() { if (!this.display) return null; return (h("div", null, h("p", null, "No Audio"))); } render() { var _a; if (this.error) return this.Error(); if (!state.audioEnabled) return this.Disabled(); return (h(Host, { hidden: !this.display }, h("n-content-reference", { inline: true, onReferenced: () => { this.registerServices(); }, "script-src": `https://cdn.jsdelivr.net/npm/howler@${this.howlerVersion}/dist/howler.core.min.js` }), ((_a = this.actions) === null || _a === void 0 ? void 0 : _a.hasAudio()) ? this.Audio() : this.NoAudio())); } disconnectedCallback() { var _a, _b, _c, _d, _e; state$1.hasAudioComponent = false; (_a = this.stateSubscription) === null || _a === void 0 ? void 0 : _a.call(this); (_b = this.actionSubscription) === null || _b === void 0 ? void 0 : _b.call(this); (_c = this.audioStateSubscription) === null || _c === void 0 ? void 0 : _c.call(this); (_d = this.commonStateSubscription) === null || _d === void 0 ? void 0 : _d.call(this); (_e = this.actions) === null || _e === void 0 ? void 0 : _e.destroy(); } get el() { return getElement(this); } }; Audio.style = audioCss; export { Audio as n_audio };