251 lines
7.7 KiB
JavaScript
251 lines
7.7 KiB
JavaScript
import "../styles/music-playlist.css";
|
|
import Music from "./Music.js";
|
|
|
|
/**
|
|
* A playlist that holds a multitude of musics in the form of {@link Music}.
|
|
*/
|
|
export default class MusicPlaylist {
|
|
#list = [];
|
|
#name;
|
|
#current = 0;
|
|
#positionChangeListeners = [];
|
|
#lengthChangeListeners = [];
|
|
|
|
|
|
/**
|
|
* @callback MusicPlaylist~positionChangeListener
|
|
* @param {number} old the previous position.
|
|
* @param {number} current the the current (new) position.
|
|
*/
|
|
|
|
/**
|
|
* @callback MusicPlaylist~lengthChangeListener
|
|
* @param {number} changed The index of the music that has changed.
|
|
* @param {boolean} added True if and only if the change was caused by adding music.
|
|
* @param {Music} music The music that was added or removed.
|
|
*/
|
|
|
|
/**
|
|
* Instantiates a playlist for musics.
|
|
*
|
|
* @param {string} name The name of the playlist.
|
|
*/
|
|
constructor(name) {
|
|
this.#name = name;
|
|
}
|
|
|
|
/**
|
|
* @returns {string} The name of the playlist.
|
|
*/
|
|
get name() {
|
|
return this.#name;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {number} index the index of the music to retrieve.
|
|
* @returns {Music} the music at the given index.
|
|
*/
|
|
musicAtIndex(index) {
|
|
if (index >= this.total) {
|
|
return null;
|
|
}
|
|
return this.#list[index];
|
|
}
|
|
|
|
/**
|
|
* Automatically creates and adds a {@link Music} to this playlist.
|
|
*
|
|
* @param {string} url where the audio data can be found.
|
|
* @param {string} name the name of the music.
|
|
* @param {string} author the author(s) of the music.
|
|
*/
|
|
add(url, name, author) {
|
|
const music = new Music(url, name, author, this);
|
|
this.#list.push(music);
|
|
this.#onAdd(this.total - 1, music);
|
|
}
|
|
|
|
/**
|
|
* Adds an existing {@link Music} object to this playlist. Alternatively, use {@link MusicPlaylist#add} to automatically instantiate a new one.
|
|
*
|
|
* @param {Music} music The existing music to add.
|
|
*/
|
|
addExisting(music) {
|
|
this.#list.push(music);
|
|
this.#onAdd(this.total - 1, music);
|
|
}
|
|
|
|
/**
|
|
* Calls all playlist length change listeners.
|
|
*
|
|
* @param {number} index The index at which the new music was added.
|
|
* @param {Music} music The music that was added.
|
|
*/
|
|
#onAdd(index, music) {
|
|
this.#lengthChangeListeners.forEach(listener => {
|
|
listener(index, true, music);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Removes a {@link Music} from this playlist.
|
|
*
|
|
* @param {number} index the index of the music to be removed.
|
|
* @returns {Music} the music that was removed, or null if the index of was invalid.
|
|
*/
|
|
remove(index) {
|
|
if (index >= this.#list.length) {
|
|
return null;
|
|
}
|
|
let removed = this.#list.splice(index, 1)[0];
|
|
this.#lengthChangeListeners.forEach(listener => {
|
|
listener(index, false, removed);
|
|
});
|
|
if (removed.length > 0) {
|
|
return removed[0];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempts to find a {@link Music} given a name. Returns the first one found.
|
|
*
|
|
* @param {string} name the name of the music to be found.
|
|
* @returns {number} the index of the music found, or -1 if it was not found.
|
|
*/
|
|
findMusicIndex(name) {
|
|
return this.#list.findIndex((item) => item.getDisplayName() == name);
|
|
}
|
|
|
|
/**
|
|
* The number of {@link Music} in this playlist.
|
|
*
|
|
* @returns {number} total number of musics in this playlist.
|
|
*/
|
|
get total() {
|
|
return this.#list.length;
|
|
}
|
|
|
|
/**
|
|
* @returns {number} The current position in the playlist.
|
|
*/
|
|
get currentPosition() {
|
|
return this.#current;
|
|
}
|
|
|
|
/**
|
|
* The current position in the playlist.
|
|
*/
|
|
set currentPosition(position) {
|
|
if (typeof position !== "number") throw new Error("Given position is invalid.");
|
|
if (position >= this.#list.length || position < 0) return;
|
|
const old = this.#current;
|
|
this.#current = position;
|
|
this.#positionChangeListeners.forEach(positionChangeListener => {
|
|
positionChangeListener(old, position);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @returns {Music} The current music in the playlist being used.
|
|
*/
|
|
get currentMusic() {
|
|
return this.musicAtIndex(this.currentPosition);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {MusicPlaylist~positionChangeListener} listener the listener to receive the updates.
|
|
* @returns {boolean} true if and only if successfully added the listener.
|
|
*/
|
|
addPositionChangeListener(listener) {
|
|
if (this.#positionChangeListeners.includes(listener)) return false;
|
|
this.#positionChangeListeners.push(listener);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {MusicPlaylist~positionChangeListener} listener the music change listener to remove.
|
|
* @returns {boolean} true if and only if the music change listener given was successfully removed.
|
|
*/
|
|
removePositionChangeListener(listener) {
|
|
const removeIndex = this.#positionChangeListeners.indexOf(listener);
|
|
if (removeIndex < 0) return false;
|
|
|
|
this.#positionChangeListeners.splice(removeIndex, 1);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Adds a listener for whether music was added or removed to the playlist.
|
|
*
|
|
* @param {MusicPlaylist~lengthChangeListener} listener The listener to be added.
|
|
* @returns {boolean} True if and only if the listener was successfully added.
|
|
*/
|
|
addLengthChangeListener(listener) {
|
|
if (this.#lengthChangeListeners.includes(listener)) return false;
|
|
this.#lengthChangeListeners.push(listener);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Removes a listener for whether music is added or removed to the playlist.
|
|
*
|
|
* @param {MusicPlaylist~lengthChangeListener} listener The listener to be removed.
|
|
* @returns {boolean} True if and only if the provided listener was successfully removed.
|
|
*/
|
|
removeLengthChangeListener(listener) {
|
|
const removeIndex = this.#lengthChangeListeners.indexOf(listener);
|
|
if (removeIndex < 0) return false;
|
|
this.#lengthChangeListeners.splice(removeIndex, 1);
|
|
return true;
|
|
}
|
|
|
|
generatePlaylistElement() {
|
|
const element = document.createElement("table");
|
|
const headers = element.insertRow();
|
|
const musicHeader = document.createElement("th");
|
|
musicHeader.innerText = "Music";
|
|
headers.appendChild(musicHeader);
|
|
const authorHeader = document.createElement("th");
|
|
authorHeader.innerText = "Author";
|
|
headers.appendChild(authorHeader);
|
|
|
|
const insertMusic = (music, musicPos = undefined) => {
|
|
if (musicPos) musicPos++;
|
|
const row = element.insertRow(musicPos);
|
|
row.insertCell().innerText = music.displayName;
|
|
row.insertCell().innerText = music.author;
|
|
};
|
|
for (const music of this) {
|
|
insertMusic(music);
|
|
}
|
|
|
|
this.addLengthChangeListener((changed, added, music) => {
|
|
if (added) {
|
|
insertMusic(music, changed);
|
|
} else {
|
|
element.deleteRow(changed);
|
|
}
|
|
});
|
|
|
|
element.rows[this.currentPosition + 1].classList.add("active");
|
|
|
|
this.addPositionChangeListener((old, current) => {
|
|
element.rows[old + 1].classList.remove("active");
|
|
element.rows[current + 1].classList.add("active");
|
|
});
|
|
|
|
element.classList.add("music-playlist");
|
|
return element;
|
|
}
|
|
|
|
[Symbol.iterator]() {
|
|
let index = -1;
|
|
const data = this.#list;
|
|
return {
|
|
next: () => ({ value: data[++index], done: index >= data.length }) // Brackets so not read as a body of anonymous function.
|
|
};
|
|
}
|
|
} |