music-very-player/src/playlist.js

203 lines
4.4 KiB
JavaScript

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 <code>null</code> 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 <code>null</code> 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} <code>true</code> if the element was successfully removed, <code>false</code> 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} <code>true</code> if the playlist is empty, <code>false</code> 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 }