const { Track } = require('./track') const { from_seconds_to_string } = require('./helpers/duration') // side effect function shuffle_array(arr) { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } } /** * @class */ class Playlist { #current_index #tracks #name #touched /** * @constructor * @param {Object} parameters={} * @param {Track[]} [parameters.tracks=[]] The tracks that compose the playlist * @param {number} [parameters.current_index=0] The id of the currently played track */ constructor({ tracks = [], current_index = 0, name = null } = {}) { this.#current_index = current_index this.#tracks = tracks.map(t => t instanceof Track ? t : new Track(t)) this.#name = name this.#touched = false } /** * @method * @returns {Playlist} A copy of the current playlist */ clone() { return new Playlist({ current_index: this.#current_index, tracks: this.#tracks.map(t => t.clone()), name: this.#name }) } /** * @method * @desc Shuffle the track list * @returns {Playlist} The current playlist */ shuffle() { shuffle_array(this.#tracks) this.#touched = true return this } /** * @method * @returns {Object} A JSON object that describe the tracklist */ toJSON() { return this.#tracks } map_tracks(callback) { const current = this.get_current_track() return this.#tracks.map((t, idx, all) => callback(t, idx, all, current)) } next({ loop = 0 }) { this.#current_index = (this.#current_index + 1) if (loop > 0) { this.#current_index %= this.#tracks.length } return this } previous({ loop = 0 }) { this.#current_index = this.#current_index - 1 if (this.#current_index < 0) this.#current_index = 0 if (loop > 0) { this.#current_index %= this.#tracks.length } return this } goto(idx) { if (this.#tracks[idx - 1] == null) { return false } this.#current_index = idx - 1 return true } /** * @returns {Track|null} The track that is currenly played, or null if it doesn't exist */ get_current_track() { return this.#tracks[this.#current_index] ?? null } /** * @param {number} track_index The index of the track to be retrieved * @returns {Track|null} The track that is looked for, or null if it doesn't exist */ get_track_by_index(track_index) { return this.#tracks[track_index] ?? null } /** * @method * @desc Add a new track a the end of the playlist * @param {Track} * @returns {Playlist} The current playlist */ push(track) { this.#tracks.push(track) this.#touched = true return this } /** * @method * @desc Remove a track from the playlist * @param {number} The index of the track to remove * @returns {boolean} true if the element was successfully removed, false otherwise */ delete(track_index) { if (track_index >= this.#tracks.length) { return false } if (track_index < this.#current_index) { this.#current_index -= 1 } delete this.#tracks[track_index] this.#tracks = this.#tracks.filter(Boolean) this.#touched = true return true } /** * @method * @returns {boolean} true if the playlist is empty, false otherwise */ is_empty() { return this.#tracks.length === 0 } /** * @method * @returns {number} The number of tracks in the playlist */ get_length() { return this.#tracks.length } /** * @method * @return {number} The duration of the playlist, in seconds */ get_duration() { return this.#tracks.reduce((a, t) => a + t.get_length(), 0) } /** * @method * @return {durationString} The duration of the playlist, as a string */ get_duration_as_string() { return from_seconds_to_string(this.get_duration()) } /** * @method * @returns {string} a string representation of the playlist */ toString() { return this.#tracks.join(', ') } get_name() { return this.#name || "(unamed)" } set_name(name = null) { this.#name = name } is_named() { return Boolean(this.#name) } untouch() { this.#touched = false } is_touched() { return this.#touched } } module.exports = { Playlist }