@nent/core
Version:
312 lines (311 loc) • 10.5 kB
JavaScript
/*!
* NENT 2022
*/
import { commonState, EventEmitter, onCommonStateChange, } from '../../../services/common';
import { debugIf } from '../../../services/common/logging';
import { addDataProvider, removeDataProvider, } from '../../../services/data/factory';
import { ROUTE_EVENTS } from '../../n-views/services/interfaces';
import { AudioType, AUDIO_COMMANDS, AUDIO_EVENTS, AUDIO_TOPIC, DiscardStrategy, } from './interfaces';
import { MusicPlayer } from './player-music';
import { SoundPlayer } from './player-sound';
import { AudioDataProvider } from './provider';
import { audioState, onAudioStateChange } from './state';
/* It listens for audio commands and events, and then plays the audio */
export 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 = onCommonStateChange('audioEnabled', enabled => {
if (enabled)
this.subscribe();
else
this.unsubscribe();
});
if (commonState.audioEnabled)
this.subscribe();
}
subscribe() {
const enableDataProvider = () => {
if (this.enableDataProvider && this.provider == undefined) {
this.provider = new AudioDataProvider(this);
addDataProvider('audio', this.provider);
}
};
if (commonState.audioEnabled)
enableDataProvider();
this.stateDataSubscription = onCommonStateChange('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 (audioState.muted)
this.mute();
this.stateMutedSubscription = onAudioStateChange('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 (!commonState.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 (!commonState.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) {
audioState.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;
}
default:
}
}
/**
* It unsubscribes from the stateEnabledSubscription and stateMutedSubscription.
*/
destroy() {
this.unsubscribe();
this.stateEnabledSubscription();
this.stateMutedSubscription();
}
}