Changed all classes to using private fields and properties.
This commit is contained in:
parent
b5a4a7dcb4
commit
2cb4ad2652
@ -1,5 +1,4 @@
|
||||
import { parseColor, rgbaToHexRgba } from "../support/colors.js";
|
||||
import VisualizerUpdateManager from "../visualization/VisualizerUpdateManager.js";
|
||||
import { mapBinNumerical, mapRangedAvgNumerical } from "./numeric.js";
|
||||
|
||||
|
||||
@ -14,7 +13,7 @@ import { mapBinNumerical, mapRangedAvgNumerical } from "./numeric.js";
|
||||
* @param {interpolator} rgbaMapConfiguration.interpolator The interpolation function to use.
|
||||
* @param {number} [rgbaMapConfiguration.upperBin=undefined] The upper bound of the bins to be mapped. If left undefined, then only the bin defined by the min value is used.
|
||||
* @param {boolean} [rgbaMapConfiguration.reversed=true] If true, then the quieter, the greater the red value.
|
||||
* @returns {{bin: number, listener: VisualizerUpdateManager.visualizerBinUpdateListener}|{lower: number, upper: number, listener: VisualizerUpdateManager.visualizerRangedUpdateListener}} The ranged listener that was added.
|
||||
* @returns {{bin: number, listener: VisualizerUpdateManager.visualizerBinUpdateListener}|{lower: number, upper: number, listener: visualizerRangedUpdateListener}} The ranged listener that was added.
|
||||
*/
|
||||
export function mapRgba({ element, color, lowerBin, visUpdateManager, interpolator, upperBin = undefined, reversed = false }) {
|
||||
const rgbaStr = "rgba";
|
||||
|
@ -5,6 +5,12 @@ import Visualizer from "../visualization/Visualizer.js";
|
||||
*
|
||||
*/
|
||||
export default class PlayListSong {
|
||||
#displayName;
|
||||
#author;
|
||||
#url;
|
||||
#audio;
|
||||
#visualizer;
|
||||
#ready = false;
|
||||
|
||||
/**
|
||||
* Constructs a song for a {@link SongPlaylist}.
|
||||
@ -14,20 +20,13 @@ export default class PlayListSong {
|
||||
* @param {string} author the author of the song.
|
||||
*/
|
||||
constructor(url, name, author) {
|
||||
this._displayName = name;
|
||||
this._author = author;
|
||||
this._url = url;
|
||||
this._audio = null;
|
||||
this._visualizer = null;
|
||||
|
||||
/**
|
||||
* Whether or not this song is ready to be played.
|
||||
*/
|
||||
this.ready = false;
|
||||
this.#displayName = name;
|
||||
this.#author = author;
|
||||
this.#url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback AudioEventCallback
|
||||
* @typedef {function} AudioEventCallback
|
||||
* @param {HTMLAudioElement} audio
|
||||
*/
|
||||
|
||||
@ -36,45 +35,45 @@ export default class PlayListSong {
|
||||
* @param {AudioEventCallback} [onReady] called when the song is ready, including right away if the song is already ready.
|
||||
* @returns {HTMLAudioElement} The audio element that represents this song.
|
||||
*/
|
||||
getAudio(onReady) {
|
||||
if (this._audio) {
|
||||
fetchAudio(onReady) {
|
||||
if (this.#audio) {
|
||||
if (this.ready) {
|
||||
if (onReady) onReady(this._audio);
|
||||
if (onReady) onReady(this.#audio);
|
||||
}
|
||||
return this._audio;
|
||||
return this.#audio;
|
||||
}
|
||||
this._audio = new Audio(this._url);
|
||||
this.#audio = new Audio(this._url);
|
||||
|
||||
this._audio.addEventListener("canplaythrough", () => {
|
||||
this.#audio.addEventListener("canplaythrough", () => {
|
||||
this.ready = true;
|
||||
if (onReady && this.isAudioInstantiated()) {
|
||||
onReady(this._audio);
|
||||
onReady(this.#audio);
|
||||
}
|
||||
});
|
||||
return this._audio;
|
||||
return this.#audio;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {string} representing the name of the song to be displayed.
|
||||
* The name of the song to be displayed.
|
||||
*/
|
||||
getDisplayName() {
|
||||
get displayName() {
|
||||
return this._displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {string} representing the author of the song.
|
||||
* The author of the song.
|
||||
*/
|
||||
getAuthor() {
|
||||
get author() {
|
||||
return this._getAuthor;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {string} representing the url at which the file for this song can be found.
|
||||
* The url at which the file for this song can be found.
|
||||
*/
|
||||
getUrl() {
|
||||
get url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
@ -83,7 +82,7 @@ export default class PlayListSong {
|
||||
* @returns {boolean} true if and only if there is audio data that is either already loaded or is being loaded.
|
||||
*/
|
||||
isAudioInstantiated() {
|
||||
return this._audio ? true : false;
|
||||
return this.#audio ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,7 +91,7 @@ export default class PlayListSong {
|
||||
* This function uses the {@link PlaylistSong#getAudio} function and specifically, the {@link AudioEventCallback}.
|
||||
*/
|
||||
play() {
|
||||
this.getAudio((audio) => {
|
||||
this.fetchAudio((audio) => {
|
||||
audio.play();
|
||||
});
|
||||
}
|
||||
@ -102,48 +101,48 @@ export default class PlayListSong {
|
||||
*/
|
||||
pause() {
|
||||
if (!this.isAudioInstantiated()) return;
|
||||
this.getAudio((audio) => {
|
||||
this.fetchAudio((audio) => {
|
||||
audio.pause();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {number} the volume on a scale of 0 to 1.
|
||||
* The volume on a scale of 0 to 1.
|
||||
*/
|
||||
getVolume() {
|
||||
return this.getAudio().volume;
|
||||
get volume() {
|
||||
return this.fetchAudio().volume;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} volume a normalized volume on a scale of 0 to 1.
|
||||
* The normalized volume on a scale of 0 to 1.
|
||||
*/
|
||||
setVolume(volume) {
|
||||
this.getAudio().volume = volume;
|
||||
set volume(volume) {
|
||||
this.fetchAudio().volume = volume;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @returns {number} the number of seconds into the song.
|
||||
* The number of seconds into the song.
|
||||
*/
|
||||
getCurrentTime() {
|
||||
return this.getAudio().currentTime;
|
||||
get currentTime() {
|
||||
return this.fetchAudio().currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} currentTime the time position in the song to jump to in seconds.
|
||||
* The time position in the song to jump to in seconds.
|
||||
*/
|
||||
setCurrentTime(currentTime) {
|
||||
this.getAudio().currentTime = currentTime;
|
||||
set currentTime(currentTime) {
|
||||
this.fetchAudio().currentTime = currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {number} the duration of the song.
|
||||
* The duration of the song.
|
||||
*/
|
||||
getDuration() {
|
||||
return this.getAudio().duration;
|
||||
get duration() {
|
||||
return this.fetchAudio().duration;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,9 +153,9 @@ export default class PlayListSong {
|
||||
*/
|
||||
unloadAudio() {
|
||||
if (!this.isAudioInstantiated()) return;
|
||||
this._audio.pause();
|
||||
this.#audio.pause();
|
||||
this.unloadVisualizer();
|
||||
this._audio = null;
|
||||
this.#audio = null;
|
||||
this.ready = false;
|
||||
}
|
||||
|
||||
@ -167,7 +166,7 @@ export default class PlayListSong {
|
||||
*/
|
||||
getVisualizer(fftSize = 1024) {
|
||||
if (this._visualizer && this._visualizer.getFftSize() === fftSize) return this._visualizer;
|
||||
this._visualizer = new Visualizer(this.getAudio(), fftSize);
|
||||
this._visualizer = new Visualizer(this.fetchAudio(), fftSize);
|
||||
return this._visualizer;
|
||||
}
|
||||
|
||||
|
@ -4,45 +4,43 @@ import "../styles/songplayer.css";
|
||||
* A player that keeps track of global properties for playback as well as traversing a playlist. Additionally keeps track of song audio data and attempts to reduce memory usage when possible.
|
||||
*/
|
||||
export default class SongPlayer {
|
||||
#playlist;
|
||||
#current = 0;
|
||||
#volume = 1;
|
||||
#playing = true;
|
||||
#playlistChangeListeners = [];
|
||||
#currentSongChangeListeners = [];
|
||||
#volumeChangeListeners = [];
|
||||
#playingChangeListeners = [];
|
||||
#allAudioEventListeners = {};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {SongPlaylist} playlist the playlist of songs that this player is in charge of.
|
||||
*/
|
||||
constructor(playlist) {
|
||||
this._playlist = playlist;
|
||||
this._current = 0;
|
||||
this._volume = 1;
|
||||
this._playing = true;
|
||||
|
||||
this._playlistChangeListeners = [];
|
||||
this._currentSongChangeListeners = [];
|
||||
this._volumeChangeListeners = [];
|
||||
this._playingChangeListener = [];
|
||||
|
||||
this._allAudioEventListeners = {};
|
||||
this.#playlist = playlist;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {SongPlaylist} playlist the new playlist of songs that this player is in charge of.
|
||||
* The new playlist of songs that this player is in charge of.
|
||||
*/
|
||||
setPlaylist(playlist) {
|
||||
this._playlist.unloadAllAudio();
|
||||
const old = this._playlist;
|
||||
this._playlist = playlist;
|
||||
this._current = 0;
|
||||
this._playlistChangeListeners.forEach(playlistChangeListener => {
|
||||
set playlist(playlist) {
|
||||
this.#playlist.unloadAllAudio();
|
||||
const old = this.#playlist;
|
||||
this.#playlist = playlist;
|
||||
this.#current = 0;
|
||||
this.#playlistChangeListeners.forEach(playlistChangeListener => {
|
||||
playlistChangeListener(old, this.getPlaylist());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {SongPlaylist} the current playlist of songs that this player is in charge of.
|
||||
* The current playlist of songs that this player is in charge of.
|
||||
*/
|
||||
getPlaylist() {
|
||||
return this._playlist;
|
||||
get playlist() {
|
||||
return this.#playlist;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,27 +66,27 @@ export default class SongPlayer {
|
||||
* @returns {boolean} true if and only if successful jumping to the given index.
|
||||
*/
|
||||
changeCurrentSongIndex(index) {
|
||||
if (index >= this._playlist.total()) return false;
|
||||
if (index >= this.#playlist.total()) return false;
|
||||
if (index <= 0) return false;
|
||||
Object.keys(this._allAudioEventListeners).forEach(key => {
|
||||
const listeners = this._allAudioEventListeners[key];
|
||||
Object.keys(this.#allAudioEventListeners).forEach(key => {
|
||||
const listeners = this.#allAudioEventListeners[key];
|
||||
listeners.forEach(listener => {
|
||||
this.getCurrentSong().getAudio().removeEventListener(key, listener);
|
||||
});
|
||||
});
|
||||
this.getCurrentSong().unloadAudio();
|
||||
const old = this.getCurrentSong();
|
||||
this._current = index;
|
||||
this._currentSongChangeListeners.forEach(currentChangeListener => {
|
||||
this.#current = index;
|
||||
this.#currentSongChangeListeners.forEach(currentChangeListener => {
|
||||
currentChangeListener(old, this.getCurrentSong());
|
||||
});
|
||||
Object.keys(this._allAudioEventListeners).forEach(key => {
|
||||
const listeners = this._allAudioEventListeners[key];
|
||||
Object.keys(this.#allAudioEventListeners).forEach(key => {
|
||||
const listeners = this.#allAudioEventListeners[key];
|
||||
listeners.forEach(listener => {
|
||||
this.getCurrentSong().getAudio().addEventListener(key, listener);
|
||||
});
|
||||
});
|
||||
if (this._playing) {
|
||||
if (this.#playing) {
|
||||
this.playCurrent();
|
||||
}
|
||||
return true;
|
||||
@ -96,30 +94,30 @@ export default class SongPlayer {
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {number} the current song's index in the playlist.
|
||||
* The current song's index in the playlist.
|
||||
*/
|
||||
getCurrentSongIndex() {
|
||||
return this._current;
|
||||
get currentSongIndex() {
|
||||
return this.#current;
|
||||
}
|
||||
|
||||
playCurrent() {
|
||||
this.getCurrentSong().getAudio((audio) => {
|
||||
audio.volume = this._volume;
|
||||
audio.volume = this.#volume;
|
||||
audio.play();
|
||||
});
|
||||
this._playing = true;
|
||||
this.#playing = true;
|
||||
}
|
||||
|
||||
pauseCurrent() {
|
||||
this.getCurrentSong().getAudio().pause();
|
||||
this._playing = true;
|
||||
this.#playing = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles whether or not the current song is playing.
|
||||
*/
|
||||
togglePlay() {
|
||||
if (this._playing) {
|
||||
if (this.#playing) {
|
||||
this.pauseCurrent();
|
||||
} else {
|
||||
this.playCurrent();
|
||||
@ -127,24 +125,21 @@ export default class SongPlayer {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} volume a number between 0 to 1 inclusive representing the volume of the player.
|
||||
* @returns {boolean} true if and only if the volume was successfully set.
|
||||
* A number between 0 to 1 inclusive representing the volume of the player. Values out of these bounds will be rounded to the nearest bound.
|
||||
*/
|
||||
setVolume(volume) {
|
||||
if (volume > 1) return false;
|
||||
if (volume < 0) return false;
|
||||
this._volume = volume;
|
||||
this.getCurrentSong().getAudio().volume = this._volume;
|
||||
return true;
|
||||
set volume(volume) {
|
||||
if (volume > 1) volume = 1;
|
||||
if (volume < 0) volume = 0;
|
||||
this.#volume = volume;
|
||||
this.getCurrentSong().getAudio().volume = this.#volume;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {number} the current volume of the player represented by a number between 0 and 1 inclusive.
|
||||
* The current volume of the player represented by a number between 0 and 1 inclusive.
|
||||
*/
|
||||
getVolume() {
|
||||
return this._volume;
|
||||
get volume() {
|
||||
return this.#volume;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -163,17 +158,17 @@ export default class SongPlayer {
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {number} how many seconds into the audio track has been played in seconds.
|
||||
* How many seconds into the audio track has been played in seconds.
|
||||
*/
|
||||
getCurrentPosition() {
|
||||
get currentPosition() {
|
||||
return this.getCurrentSong().getAudio().currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {number} the total length of the song, or NaN if this information has not loaded yet.
|
||||
* The total length of the song, or NaN if this information has not loaded yet.
|
||||
*/
|
||||
getCurrentDuration() {
|
||||
get currentDuration() {
|
||||
return this.getCurrentSong().getAudio().duration;
|
||||
}
|
||||
|
||||
@ -188,7 +183,7 @@ export default class SongPlayer {
|
||||
const playButton = document.createElement("button");
|
||||
playButton.classList.add("player");
|
||||
playButton.classList.add("play-btn");
|
||||
if (!this._playing) playButton.classList.add("paused");
|
||||
if (!this.#playing) playButton.classList.add("paused");
|
||||
|
||||
playButton.addEventListener("click", () => {
|
||||
this.togglePlay();
|
||||
@ -224,8 +219,8 @@ export default class SongPlayer {
|
||||
return previousButton;
|
||||
}
|
||||
|
||||
getCurrentSong() {
|
||||
return this._playlist.songAtIndex(this._current);
|
||||
get currentSong() {
|
||||
return this.#playlist.songAtIndex(this.#current);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,8 +235,8 @@ export default class SongPlayer {
|
||||
* @returns {boolean} true if and only if successfully added the listener.
|
||||
*/
|
||||
addCurrentSongChangeListener(listener) {
|
||||
if (this._currentSongChangeListeners.includes(listener)) return false;
|
||||
this._currentChangeListener.push(listener);
|
||||
if (this.#currentSongChangeListeners.includes(listener)) return false;
|
||||
this.#currentSongChangeListeners.push(listener);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -251,10 +246,10 @@ export default class SongPlayer {
|
||||
* @returns {boolean} true if and only if the song change listener given was successfully removed.
|
||||
*/
|
||||
removeCurrentSongChangeListener(listener) {
|
||||
const removeIndex = this._currentSongChangeListeners.indexOf(listener);
|
||||
const removeIndex = this.#currentSongChangeListeners.indexOf(listener);
|
||||
if (removeIndex < 0) return false;
|
||||
|
||||
this._currentSongChangeListeners.splice(removeIndex, 1);
|
||||
this.#currentSongChangeListeners.splice(removeIndex, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -264,8 +259,8 @@ export default class SongPlayer {
|
||||
* @returns {boolean} true if and only if the given listener was successfully registered.
|
||||
*/
|
||||
addPlaylistChangeListener(listener) {
|
||||
if (this._playlistChangeListeners.includes(listener)) return false;
|
||||
this._playlistChangeListeners.push(listener);
|
||||
if (this.#playlistChangeListeners.includes(listener)) return false;
|
||||
this.#playlistChangeListeners.push(listener);
|
||||
return true;
|
||||
|
||||
}
|
||||
@ -276,7 +271,7 @@ export default class SongPlayer {
|
||||
* @returns {boolean} true if and only if a listener was successfully removed from the callback list.
|
||||
*/
|
||||
removePlaylistChangeListener(listener) {
|
||||
const removeIndex = this._playlistChangeListeners.indexOf(listener);
|
||||
const removeIndex = this.#playlistChangeListeners.indexOf(listener);
|
||||
if (removeIndex < 0) return false;
|
||||
|
||||
this.playlistChangeListener.splice(removeIndex, 1);
|
||||
@ -289,8 +284,8 @@ export default class SongPlayer {
|
||||
* @returns {boolean} true if and only if the listener was successfully added.
|
||||
*/
|
||||
addVolumeChangeListener(listener) {
|
||||
if (this._volumeChangeListeners.includes(listener)) return false;
|
||||
this._volumeChangeListeners.push(listener);
|
||||
if (this.#volumeChangeListeners.includes(listener)) return false;
|
||||
this.#volumeChangeListeners.push(listener);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -300,10 +295,10 @@ export default class SongPlayer {
|
||||
* @returns {boolean} true if and only if a listener was successfully removed from the callback list.
|
||||
*/
|
||||
removeVolumeChangeListener(listener) {
|
||||
const removeIndex = this._volumeChangeListeners.indexOf(listener);
|
||||
const removeIndex = this.#volumeChangeListeners.indexOf(listener);
|
||||
if (removeIndex < 0) return false;
|
||||
|
||||
this._volumeChangeListeners.splice(removeIndex, 1);
|
||||
this.#volumeChangeListeners.splice(removeIndex, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -314,8 +309,8 @@ export default class SongPlayer {
|
||||
* @returns {boolean} true if and only if the listener was successfully added.
|
||||
*/
|
||||
addPlayChangeListener(listener) {
|
||||
if (this._playingChangeListener.includes(listener)) return false;
|
||||
this._playingChangeListener.push(listener);
|
||||
if (this.#playingChangeListeners.includes(listener)) return false;
|
||||
this.#playingChangeListeners.push(listener);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -325,10 +320,10 @@ export default class SongPlayer {
|
||||
* @returns {boolean} true if and only if a listener was successfully removed from the callback list.
|
||||
*/
|
||||
removePlayChangeListener(listener) {
|
||||
const removeIndex = this._playingChangeListener.indexOf(listener);
|
||||
const removeIndex = this.#playingChangeListeners.indexOf(listener);
|
||||
if (removeIndex < 0) return false;
|
||||
|
||||
this._playingChangeListener.splice(removeIndex, 1);
|
||||
this.#playingChangeListeners.splice(removeIndex, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -339,10 +334,10 @@ export default class SongPlayer {
|
||||
* @returns {boolean} true if and only if successfully registered event listener.
|
||||
*/
|
||||
addEventListenerToCurrentAudio(type, eventListener) {
|
||||
let typeListeners = this._allAudioEventListeners[type];
|
||||
let typeListeners = this.#allAudioEventListeners[type];
|
||||
if (!typeListeners) {
|
||||
typeListeners = [];
|
||||
this._allAudioEventListeners[type] = typeListeners;
|
||||
this.#allAudioEventListeners[type] = typeListeners;
|
||||
}
|
||||
if (typeListeners.includes(eventListener)) return false;
|
||||
typeListeners.push(eventListener);
|
||||
@ -357,7 +352,7 @@ export default class SongPlayer {
|
||||
* @returns {boolean} true if and only if the event listener was successfully added.
|
||||
*/
|
||||
removeEventListenerFromCurrentAudio(type, eventListener) {
|
||||
let typeListeners = this._allAudioEventListeners[type];
|
||||
let typeListeners = this.#allAudioEventListeners[type];
|
||||
if (!typeListeners) return false;
|
||||
const removeIndex = typeListeners.indexOf(eventListener);
|
||||
if (removeIndex < 0) return false;
|
||||
|
@ -4,6 +4,8 @@ import PlaylistSong from "./PlaylistSong.js";
|
||||
* A playlist that holds a multitude of songs in the form of {@see PlaylistSong}.
|
||||
*/
|
||||
export default class SongPlaylist {
|
||||
#list = [];
|
||||
#name;
|
||||
|
||||
/**
|
||||
* Instantiates a playlist for songs.
|
||||
@ -11,17 +13,15 @@ export default class SongPlaylist {
|
||||
* @param {string} name The name of the playlist.
|
||||
*/
|
||||
constructor(name) {
|
||||
this._list = [];
|
||||
this._name = name;
|
||||
this._current = 0;
|
||||
this.#name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {string} the name of the string.
|
||||
* The name of the playlist.
|
||||
*/
|
||||
getName() {
|
||||
return this._name;
|
||||
get name() {
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -30,7 +30,7 @@ export default class SongPlaylist {
|
||||
* @returns {PlaylistSong} the song at the given index.
|
||||
*/
|
||||
songAtIndex(index) {
|
||||
if (index >= this._list.length) {
|
||||
if (index >= this.#list.length) {
|
||||
return null;
|
||||
}
|
||||
return this.list[index];
|
||||
@ -44,7 +44,7 @@ export default class SongPlaylist {
|
||||
* @param {string} author the author(s) of the song.
|
||||
*/
|
||||
add(url, name, author) {
|
||||
this._list.push(new PlaylistSong(url, name, author, this));
|
||||
this.#list.push(new PlaylistSong(url, name, author, this));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,10 +54,10 @@ export default class SongPlaylist {
|
||||
* @returns {PlaylistSong} the song that was removed, or null if the index of was invalid.
|
||||
*/
|
||||
remove(index) {
|
||||
if (index >= this._list.length) {
|
||||
if (index >= this.#list.length) {
|
||||
return null;
|
||||
}
|
||||
let removed = this._list.splice(index, 1)[0];
|
||||
let removed = this.#list.splice(index, 1)[0];
|
||||
if (removed.length > 0) {
|
||||
return removed[0];
|
||||
}
|
||||
@ -70,7 +70,7 @@ export default class SongPlaylist {
|
||||
* @returns {number} the index of the song found, or -1 if it was not found.
|
||||
*/
|
||||
findSongIndex(name) {
|
||||
return this._list.findIndex((item) => item.getDisplayName() == name);
|
||||
return this.#list.findIndex((item) => item.getDisplayName() == name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,14 +78,14 @@ export default class SongPlaylist {
|
||||
* @returns {number} total number of songs in this playlist.
|
||||
*/
|
||||
total() {
|
||||
return this._list.length;
|
||||
return this.#list.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads the audio data of all songs in this playlist.
|
||||
*/
|
||||
unloadAllAudio() {
|
||||
this._list.forEach(playlistSong => {
|
||||
this.#list.forEach(playlistSong => {
|
||||
playlistSong.unloadAudio();
|
||||
});
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import SongPlayer from "./SongPlayer.js";
|
||||
* Automatically loads the songs.
|
||||
*/
|
||||
export default class VisualizedSongPlayer extends SongPlayer {
|
||||
#fftSize = 1024;
|
||||
#visualizerUpdateManager;
|
||||
|
||||
/**
|
||||
* Instantiates a song player with visualization features.
|
||||
@ -18,18 +20,16 @@ export default class VisualizedSongPlayer extends SongPlayer {
|
||||
*/
|
||||
constructor(playlist, fftSize = 1024) {
|
||||
super(playlist);
|
||||
this._fftSize = fftSize;
|
||||
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
this.#fftSize = fftSize;
|
||||
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the the playlist.
|
||||
*
|
||||
* @param {SongPlaylist} playlist the new playlist of songs that this player is in charge of.
|
||||
*/
|
||||
setPlaylist(playlist) {
|
||||
super.setPlaylist(playlist);
|
||||
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
set playlist(playlist) {
|
||||
super.playlist = playlist;
|
||||
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,10 +37,10 @@ export default class VisualizedSongPlayer extends SongPlayer {
|
||||
* @returns {boolean} true if and only if successful in changing to the next song.
|
||||
*/
|
||||
next() {
|
||||
const updateListeners = this._visualizerUpdateManager.getBinnedListeners();
|
||||
const updateListeners = this.#visualizerUpdateManager.getBinnedListeners();
|
||||
if (!super.next()) return false;
|
||||
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
this._visualizerUpdateManager.setBinnedListeners(updateListeners);
|
||||
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
this.#visualizerUpdateManager.setBinnedListeners(updateListeners);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -50,11 +50,11 @@ export default class VisualizedSongPlayer extends SongPlayer {
|
||||
* @returns {boolean} true if and only if successful in switching to the previous song in the playlist.
|
||||
*/
|
||||
previous() {
|
||||
const updateListeners = this._visualizerUpdateManager.getBinnedListeners();
|
||||
const updateListeners = this.#visualizerUpdateManager.getBinnedListeners();
|
||||
if (!super.previous()) return false;
|
||||
|
||||
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
this._visualizerUpdateManager.setBinnedListeners(updateListeners);
|
||||
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
this.#visualizerUpdateManager.setBinnedListeners(updateListeners);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -66,24 +66,22 @@ export default class VisualizedSongPlayer extends SongPlayer {
|
||||
changeCurrent(index) {
|
||||
const updateListeners = this.VisualizerUpdateManager.getBinnedListeners();
|
||||
if (!super.changeCurrent(index)) return false;
|
||||
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
this._visualizerUpdateManager.setBinnedListeners = updateListeners();
|
||||
this.#visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
this.#visualizerUpdateManager.setBinnedListeners = updateListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {VisualizerUpdateManager} the current visualizer update manager.
|
||||
* The current visualizer update manager.
|
||||
*/
|
||||
getCurrentVisualizerUpdateManager() {
|
||||
return this._visualizerUpdateManager();
|
||||
get currentVisualizerUpdateManager() {
|
||||
return this.#visualizerUpdateManager();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Visualizer} the current song's visualizer.
|
||||
* The current song's visualizer.
|
||||
*/
|
||||
getCurrentSongVisualizer() {
|
||||
get currentSongVisualizer() {
|
||||
return this.getCurrentSong().getVisualizer();
|
||||
}
|
||||
}
|
@ -2,6 +2,14 @@
|
||||
* Provides a simplified access point to the frequency bins in the form of a visualization update listener.
|
||||
*/
|
||||
export default class Visualizer {
|
||||
#stream;
|
||||
#analyzing = false;
|
||||
#updateListeners = [];
|
||||
#audioCtx;
|
||||
#source;
|
||||
#analyzer;
|
||||
#buffer;
|
||||
#lastUpdate;
|
||||
|
||||
/**
|
||||
* @callback visualizerUpdateListener
|
||||
@ -15,22 +23,22 @@ export default class Visualizer {
|
||||
* @param {number} [fftSize = 1024] the size of the fft window.
|
||||
*/
|
||||
constructor(mediaSource, fftSize = 1024) {
|
||||
this._stream = mediaSource;
|
||||
this._analyzing = false;
|
||||
this._updateListeners = [];
|
||||
this._audioCtx = new window.AudioContext();
|
||||
this.#stream = mediaSource;
|
||||
this.#analyzing = false;
|
||||
this.#updateListeners = [];
|
||||
this.#audioCtx = new window.AudioContext();
|
||||
if (mediaSource instanceof HTMLMediaElement) {
|
||||
this._source = this._audioCtx.createMediaElementSource(this._stream);
|
||||
this.#source = this.#audioCtx.createMediaElementSource(this.#stream);
|
||||
} else {
|
||||
this._source = this._audioCtx.createMediaStreamSource(this._stream);
|
||||
this.#source = this.#audioCtx.createMediaStreamSource(this.#stream);
|
||||
}
|
||||
this._analyzer = this._audioCtx.createAnalyser();
|
||||
this._analyzer.fftSize = fftSize;
|
||||
this._buffer = new Uint8Array(this._analyzer.frequencyBinCount);
|
||||
this._source.connect(this._analyzer);
|
||||
this._analyzer.connect(this._audioCtx.destination);
|
||||
this.#analyzer = this.#audioCtx.createAnalyser();
|
||||
this.#analyzer.fftSize = fftSize;
|
||||
this.#buffer = new Uint8Array(this.#analyzer.frequencyBinCount);
|
||||
this.#source.connect(this.#analyzer);
|
||||
this.#analyzer.connect(this.#audioCtx.destination);
|
||||
|
||||
this.lastUpdate = null;
|
||||
this.#lastUpdate = null;
|
||||
}
|
||||
|
||||
|
||||
@ -38,22 +46,22 @@ export default class Visualizer {
|
||||
* Begins analyzing and sending out update pings.
|
||||
*/
|
||||
analyze() {
|
||||
if (this._analyzing) {
|
||||
if (this.#analyzing) {
|
||||
return;
|
||||
}
|
||||
this._analyzing = true;
|
||||
this.#analyzing = true;
|
||||
let self = this; // since calling from requestAnimationFrame means "this" is no longer set to produced object.
|
||||
const update = (timestamp) => {
|
||||
if (!self._analyzing) return;
|
||||
if (!self.#analyzing) return;
|
||||
|
||||
if (!self.lastUpdate) {
|
||||
self.lastUpdate = timestamp;
|
||||
if (!self.#lastUpdate) {
|
||||
self.#lastUpdate = timestamp;
|
||||
}
|
||||
let delta = timestamp - self.lastUpdate;
|
||||
self._analyzer.getByteFrequencyData(self._buffer);
|
||||
let delta = timestamp - self.#lastUpdate;
|
||||
self.#analyzer.getByteFrequencyData(self.#buffer);
|
||||
|
||||
self._updateListeners.forEach(listener => {
|
||||
listener(delta, self._buffer);
|
||||
self.#updateListeners.forEach(listener => {
|
||||
listener(delta, self.#buffer);
|
||||
});
|
||||
requestAnimationFrame(update);
|
||||
};
|
||||
@ -64,7 +72,7 @@ export default class Visualizer {
|
||||
* Stops the analysis. Listeners will stop receiving bins.
|
||||
*/
|
||||
stop() {
|
||||
this._analyzing = false;
|
||||
this.#analyzing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,8 +81,8 @@ export default class Visualizer {
|
||||
* @returns {boolean} true if and only if the listener was successfully added.
|
||||
*/
|
||||
addUpdateListener(listener) {
|
||||
if (this._updateListeners.includes(listener));
|
||||
this._updateListeners.push(listener);
|
||||
if (this.#updateListeners.includes(listener));
|
||||
this.#updateListeners.push(listener);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -84,27 +92,26 @@ export default class Visualizer {
|
||||
* @returns {boolean} true if and only if the removal of the listener was a success.
|
||||
*/
|
||||
removeUpdateListener(listener) {
|
||||
const removeIndex = this._updateListeners.indexOf(listener);
|
||||
const removeIndex = this.#updateListeners.indexOf(listener);
|
||||
if (removeIndex < 0) return false;
|
||||
|
||||
this._updateListeners.splice(removeIndex, 1);
|
||||
this.#updateListeners.splice(removeIndex, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {number} the number of bins based on the size of the FFT window.
|
||||
* The number of bins based on the size of the FFT window.
|
||||
*/
|
||||
getNumberOfBins() {
|
||||
return this._buffer.length;
|
||||
get numberOfBins() {
|
||||
return this.#buffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {number} the fft window size.
|
||||
* The fft window size.
|
||||
*/
|
||||
getFftSize() {
|
||||
return this._analyzer.fftSize;
|
||||
get fftSize() {
|
||||
return this.#analyzer.fftSize;
|
||||
}
|
||||
}
|
@ -5,24 +5,31 @@
|
||||
* In the rare event that a bin has not changed, then it will not receive an update call.
|
||||
*/
|
||||
export default class VisualizerUpdateManager {
|
||||
#binnedListeners = [];
|
||||
#rangedListeners = [];
|
||||
#lastBins;
|
||||
#visualizer;
|
||||
#visualizerListener;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Visualizer} visualizer the visualizer this manager obtains data from.
|
||||
*/
|
||||
constructor(visualizer) {
|
||||
this._binnedListeners = [];
|
||||
this._rangedListeners = [];
|
||||
this.#binnedListeners = [];
|
||||
this.#rangedListeners = [];
|
||||
for (let i = 0; i < visualizer.getNumberOfBins(); i++) {
|
||||
this._binnedListeners.push([]);
|
||||
this.#binnedListeners.push([]);
|
||||
}
|
||||
this._lastBins = new Uint8Array(this._binnedListeners.length);
|
||||
this._visualizer = visualizer;
|
||||
this._visualizerListener = (delta, bins) => {
|
||||
const sortedCopyOfRangedListeners = [... this._rangedListeners]; // We assume this is sorted properly. A priority queue could be better.
|
||||
for (let binInd = 0; binInd < this._lastBins.length; binInd++) {
|
||||
const lastBin = this._lastBins[binInd];
|
||||
this.#lastBins = new Uint8Array(this.#binnedListeners.length);
|
||||
this.#visualizer = visualizer;
|
||||
this.#visualizerListener = (delta, bins) => {
|
||||
const sortedCopyOfRangedListeners = [... this.#rangedListeners]; // We assume this is sorted properly. A priority queue could be better.
|
||||
for (let binInd = 0; binInd < this.#lastBins.length; binInd++) {
|
||||
const lastBin = this.#lastBins[binInd];
|
||||
if (lastBin !== bins[binInd]) {
|
||||
this._binnedListeners[binInd].forEach(listener => {
|
||||
this.#binnedListeners[binInd].forEach(listener => {
|
||||
listener(delta, bins[binInd], bins[binInd] - lastBin);
|
||||
});
|
||||
for (let rangedInd = 0; rangedInd < sortedCopyOfRangedListeners.length; rangedInd++) { // Could switch to a while loop.
|
||||
@ -34,22 +41,22 @@ export default class VisualizerUpdateManager {
|
||||
rangedInd--;
|
||||
}
|
||||
}
|
||||
this._lastBins[binInd] = bins[binInd];
|
||||
this.#lastBins[binInd] = bins[binInd];
|
||||
}
|
||||
}
|
||||
};
|
||||
visualizer.addUpdateListener(this._visualizerListener);
|
||||
visualizer.addUpdateListener(this.#visualizerListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback VisualizerUpdateManager.visualizerBinUpdateListener
|
||||
* @typedef {function} visualizerBinUpdateListener
|
||||
* @param {number} timeDelta elapsed time since last update.
|
||||
* @param {number} amplitude The amplitude of the associated bin.
|
||||
* @param {number} ampDelta change in amplitude of the frequency bin.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback VisualizerUpdateManager.visualizerRangedUpdateListener
|
||||
* @typedef {function} visualizerRangedUpdateListener
|
||||
* @param {number} timeDelta elapsed time since last update.
|
||||
* @param {number} bins the bins of the range.
|
||||
*/
|
||||
@ -60,12 +67,12 @@ export default class VisualizerUpdateManager {
|
||||
*
|
||||
* @param {object} freqBinListener the listener for a specific frequency bin.
|
||||
* @param {number} freqBinListener.freqBin the frequency bin this update listener should listen to.
|
||||
* @param {VisualizerUpdateManager.visualizerBinUpdateListener} freqBinListener.listener the listener itself that will be called upon the bin updating.
|
||||
* @param {visualizerBinUpdateListener} freqBinListener.listener the listener itself that will be called upon the bin updating.
|
||||
* @returns {boolean} true if and only if the updater was added successfully.
|
||||
*/
|
||||
AddVisualizerBinUpdateListener({ freqBin, listener }) {
|
||||
if (this._binnedListeners[freqBin].includes(listener)) return false;
|
||||
this._binnedListeners[freqBin].push(listener);
|
||||
if (this.#binnedListeners[freqBin].includes(listener)) return false;
|
||||
this.#binnedListeners[freqBin].push(listener);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -75,7 +82,7 @@ export default class VisualizerUpdateManager {
|
||||
* @param {object} rangedUpdateListener The ranged update listener to add.
|
||||
* @param {number} rangedUpdateListener.lower The lower bound of the bins to listen to (inclusive).
|
||||
* @param {number} rangedUpdateListener.upper The upper bound of the bins to listen to (inclusive).
|
||||
* @param {VisualizerUpdateManager.visualizerRangedUpdateListener} rangedUpdateListener.listener The listener to register to the range.
|
||||
* @param {visualizerRangedUpdateListener} rangedUpdateListener.listener The listener to register to the range.
|
||||
* @returns {boolean} True if and only if the ranged listener was added successfully.
|
||||
*/
|
||||
addVisualizerRangedUpdateListener({ lower, upper, listener }) {
|
||||
@ -84,9 +91,9 @@ export default class VisualizerUpdateManager {
|
||||
upper: upper,
|
||||
listener: listener
|
||||
};
|
||||
if (this._rangedListeners.includes(rangedListener)) return false;
|
||||
this._rangedListeners.push(rangedListener);
|
||||
this._rangedListeners.sort((a, b) => a.lower - b.lower);
|
||||
if (this.#rangedListeners.includes(rangedListener)) return false;
|
||||
this.#rangedListeners.push(rangedListener);
|
||||
this.#rangedListeners.sort((a, b) => a.lower - b.lower);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -94,13 +101,13 @@ export default class VisualizerUpdateManager {
|
||||
*
|
||||
* @param {object} binFreqListener The bin frequency listener to remove.
|
||||
* @param {number} binFreqListener.freqBin the frequency bin the update listener to be removed from is in.
|
||||
* @param {VisualizerUpdateManager.visualizerBinUpdateListener} binFreqListener.listener the listener that is to be removed.
|
||||
* @param {visualizerBinUpdateListener} binFreqListener.listener the listener that is to be removed.
|
||||
* @returns {boolean} true if and only if the listener was successfully removed.
|
||||
*/
|
||||
removeVisualizerBinUpdateListener({ freqBin, listener }) {
|
||||
const removeIndex = this._binnedListeners[freqBin].indexOf(listener);
|
||||
const removeIndex = this.#binnedListeners[freqBin].indexOf(listener);
|
||||
if (removeIndex < 0) return false;
|
||||
this._binnedListeners[freqBin].splice(removeIndex, 1);
|
||||
this.#binnedListeners[freqBin].splice(removeIndex, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -110,7 +117,7 @@ export default class VisualizerUpdateManager {
|
||||
* @param {object} rangedListener The ranged listener to remove.
|
||||
* @param {number} rangedListener.lower The lower bound of bins to remove the listener from (inclusive).
|
||||
* @param {number} rangedListener.upper The upper bound of bin to remove the listener from (inclusive.)
|
||||
* @param {VisualizerUpdateManager.visualizerRangedUpdateListener} rangedListener.listener The update listener to remove from the bins.
|
||||
* @param {visualizerRangedUpdateListener} rangedListener.listener The update listener to remove from the bins.
|
||||
* @returns {boolean} True if and only if the given listener was removed.
|
||||
*/
|
||||
removeVisualizerRangedUpdateListener({ lower, upper, listener }) {
|
||||
@ -119,36 +126,36 @@ export default class VisualizerUpdateManager {
|
||||
upper: upper,
|
||||
listener: listener
|
||||
};
|
||||
const removeIndex = this._rangedListeners.indexOf(rangedListener);
|
||||
const removeIndex = this.#rangedListeners.indexOf(rangedListener);
|
||||
if (removeIndex < 0) return false;
|
||||
this._binnedListeners.splice(removeIndex, 1);
|
||||
this.#binnedListeners.splice(removeIndex, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} listeners The listeners that this visualizer update manager should be set to use.
|
||||
* @param {VisualizerUpdateManager.visualizerBinUpdateListener[][]} listeners.binned The bin listeners.
|
||||
* @param {visualizerBinUpdateListener[][]} listeners.binned The bin listeners.
|
||||
* @param {object[]} listeners.ranged The array of ranged listeners.
|
||||
* @param {number} listeners.ranged[].lower The lower bound of the listener.
|
||||
* @param {number} listeners.ranged[].upper The upper bound of the listener.
|
||||
* @param {VisualizerUpdateManager.visualizerRangedUpdateListener} listeners.ranged[].listener The listener for the previously mentioned ranges.
|
||||
* @param {visualizerRangedUpdateListener} listeners.ranged[].listener The listener for the previously mentioned ranges.
|
||||
* @returns {boolean} true if and only if successfully loaded the new listeners.
|
||||
*/
|
||||
setBinnedListeners(listeners) {
|
||||
if (listeners.binned.length !== this._binnedListeners.length) return false;
|
||||
this._binnedListeners = listeners.binned;
|
||||
this._rangedListeners = listeners.ranged.sort((a, b) => a.lower - b.lower);
|
||||
loadListeners(listeners) {
|
||||
if (listeners.binned.length !== this.#binnedListeners.length) return false;
|
||||
this.#binnedListeners = listeners.binned;
|
||||
this.#rangedListeners = listeners.ranged.sort((a, b) => a.lower - b.lower);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {object} All the listeners, both for binned, and ranged listeners. See {@link VisualizerUpdateManager#setBinnedListeners} to see the structure of the returned object.
|
||||
* @returns {object} All the listeners, both for binned, and ranged listeners. See {@link VisualizerUpdateManager#loadListeners} to see the structure of the returned object.
|
||||
*/
|
||||
getAllListeners() {
|
||||
retrieveListeners() {
|
||||
return {
|
||||
binned: this._binnedListeners,
|
||||
ranged: this._rangedListeners
|
||||
binned: this.#binnedListeners,
|
||||
ranged: this.#rangedListeners
|
||||
};
|
||||
}
|
||||
|
||||
@ -156,7 +163,7 @@ export default class VisualizerUpdateManager {
|
||||
* Clears this manager of all listeners.
|
||||
*/
|
||||
clearBinnedListeners() {
|
||||
this._binnedListeners.forEach(bin => {
|
||||
this.#binnedListeners.forEach(bin => {
|
||||
bin.length = 0;
|
||||
});
|
||||
}
|
||||
@ -165,6 +172,6 @@ export default class VisualizerUpdateManager {
|
||||
* Unbinds this update manager from the initial visualizer. Effectively meaning this manager will no longer be used.
|
||||
*/
|
||||
unbindVisualizer() {
|
||||
this._visualizer.removeUpdateListener(this._visualizerListener);
|
||||
this.#visualizer.removeUpdateListener(this.#visualizerListener);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user