Completed untested versions of playlist and playlist song classes.
Restructured for improved development and deployment pipeline. Added webpack and configurations for development and production. Began adding JSDocs. Added eslint.
This commit is contained in:
parent
7474a0f6f5
commit
ff3325b5db
35
.eslintrc.json
Normal file
35
.eslintrc.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2021": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:jsdoc/recommended"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"jsdoc"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"indent": [
|
||||||
|
"error",
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"linebreak-style": [
|
||||||
|
"error",
|
||||||
|
"unix"
|
||||||
|
],
|
||||||
|
"quotes": [
|
||||||
|
"error",
|
||||||
|
"double"
|
||||||
|
],
|
||||||
|
"semi": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"linebreak"
|
||||||
|
]
|
||||||
|
}
|
9
app.js
9
app.js
@ -1,9 +0,0 @@
|
|||||||
const express = require('express')
|
|
||||||
const app = express()
|
|
||||||
const port = process.env.PORT || 5000
|
|
||||||
|
|
||||||
app.use(express.static("pub"));
|
|
||||||
|
|
||||||
app.listen(port, () => {
|
|
||||||
console.log(`Static express server listening on port ${port}`)
|
|
||||||
});
|
|
6709
package-lock.json
generated
6709
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -9,7 +9,14 @@
|
|||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"devDependencies": {
|
||||||
"express": "^4.17.3"
|
"css-loader": "^6.7.1",
|
||||||
|
"eslint": "^8.13.0",
|
||||||
|
"eslint-plugin-jsdoc": "^39.2.1",
|
||||||
|
"style-loader": "^3.3.1",
|
||||||
|
"webpack": "^5.72.0",
|
||||||
|
"webpack-cli": "^4.9.2",
|
||||||
|
"webpack-dev-server": "^4.8.1",
|
||||||
|
"webpack-merge": "^5.8.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
Binary file not shown.
@ -1,54 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
function VisualizerCore(mediaSource, fftSize = 1024) {
|
|
||||||
this._stream = mediaSource;
|
|
||||||
this._analyzing = false;
|
|
||||||
this._updateListeners = [];
|
|
||||||
this._audioCtx = new window.AudioContext();
|
|
||||||
this._source = null; // Prepare for either a MediaStream or a MediaElement.
|
|
||||||
try {
|
|
||||||
this.source = this._audioCtx.createMediaStreamSource(this._stream);
|
|
||||||
} catch (e) {
|
|
||||||
this._source = this._audioCtx.createMediaElementSource(this._stream);
|
|
||||||
}
|
|
||||||
this._analyzer = this._audioCtx.createAnalyser();
|
|
||||||
this._analyzer.fftSize = fftSize;
|
|
||||||
this._source.connect(this._analyzer);
|
|
||||||
this._analyzer.connect(this._audioCtx.destination);
|
|
||||||
this._buffer = new Uint8Array(this._analyzer.frequencyBinCount);
|
|
||||||
|
|
||||||
this.lastUpdate = null;
|
|
||||||
|
|
||||||
this.analyze = function () {
|
|
||||||
if (this._analyzing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._analyzing = true;
|
|
||||||
requestAnimationFrame(this.update);
|
|
||||||
};
|
|
||||||
|
|
||||||
let vcore = this; // since calling from requestAnimationFrame means "this" is no longer set to produced object.
|
|
||||||
this.update = function (timestamp) {
|
|
||||||
if (!vcore._analyzing) return;
|
|
||||||
|
|
||||||
requestAnimationFrame(vcore.update);
|
|
||||||
if (!vcore.lastUpdate) {
|
|
||||||
vcore.lastUpdate = timestamp;
|
|
||||||
}
|
|
||||||
let delta = timestamp - vcore.lastUpdate;
|
|
||||||
vcore._analyzer.getByteFrequencyData(vcore._buffer);
|
|
||||||
|
|
||||||
vcore._updateListeners.forEach(listener => {
|
|
||||||
listener(delta, vcore._buffer);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
this.stop = function () {
|
|
||||||
this._analyzing = false;
|
|
||||||
};
|
|
||||||
this.addUpdateListener = function (listener) {
|
|
||||||
this._updateListeners.push(listener);
|
|
||||||
};
|
|
||||||
this.getNumberOfBins = function () {
|
|
||||||
return this._buffer.length;
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset='utf-8'>
|
|
||||||
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
|
|
||||||
<title>AudioShowKit visual test</title>
|
|
||||||
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
|
||||||
|
|
||||||
<script defer src="audioshowkit.js"></script>
|
|
||||||
<script defer src="patterns/HorizontalBar.js"></script>
|
|
||||||
<script defer src='examples.js'></script>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>Hit start to see demo!</h1>
|
|
||||||
<button id="startbtn">Start.</button>
|
|
||||||
|
|
||||||
<h2>Horizontal Bar Pattern</h2>
|
|
||||||
<p>Below is a canvas that has all bin values being drawn live using the built-in horizontal bar pattern.</p>
|
|
||||||
<canvas id="horizontal">Canvases don't seem to be supported! Please try a different browser.</canvas>
|
|
||||||
|
|
||||||
<h2>Binding frequency bin values to element styles</h2>
|
|
||||||
<p>Has never been this easy! Here we bind the width of a div to a frequency bin.</p>
|
|
||||||
<div id="widthDiv"></div>
|
|
||||||
|
|
||||||
<p>Here we bind a frequency to the colour of a div.</p>
|
|
||||||
<div id="colourDiv"></div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,32 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
// We will see if the visualizers core and event systems are working correctly.
|
|
||||||
let startBtn = document.getElementById("startbtn");
|
|
||||||
startBtn.addEventListener("click", async (ev) => {
|
|
||||||
let mediaStream = new Audio("Elektronomia - Collide.mp3");
|
|
||||||
mediaStream.addEventListener("canplaythrough", (ev) => {
|
|
||||||
let visCore = new VisualizerCore(mediaStream, 128);
|
|
||||||
let coreAndEventCanvas = document.getElementById("horizontal");
|
|
||||||
coreAndEventCanvas.width = 640;
|
|
||||||
coreAndEventCanvas.height = 200;
|
|
||||||
|
|
||||||
mediaStream.play();
|
|
||||||
visCore.analyze();
|
|
||||||
|
|
||||||
bindHorizontalBar(coreAndEventCanvas, visCore); // Pre-made simple horizontal bar visualizer.
|
|
||||||
|
|
||||||
let widthDiv = document.getElementById("widthDiv");
|
|
||||||
widthDiv.style.height = "20px";
|
|
||||||
widthDiv.style.backgroundColor = "orange";
|
|
||||||
visCore.addUpdateListener((delta, bins) => {
|
|
||||||
widthDiv.style.width = bins[3] + "px";
|
|
||||||
})
|
|
||||||
|
|
||||||
let colourDiv = document.getElementById("colourDiv");
|
|
||||||
colourDiv.style.height = "20px";
|
|
||||||
visCore.addUpdateListener((delta, bins) => {
|
|
||||||
colourDiv.style.backgroundColor = "rgb(" + bins[0] + "," + bins[1] + ", " + bins[4] + ")";
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -1,21 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
function bindHorizontalBar(canvasElement, visualizerCore) {
|
|
||||||
let _width = canvasElement.width;
|
|
||||||
let _height = canvasElement.height;
|
|
||||||
let _canvasCtx = canvasElement.getContext("2d");
|
|
||||||
let _visualizerCore = visualizerCore;
|
|
||||||
let update = function (delta, bins) {
|
|
||||||
_canvasCtx.clearRect(0, 0, _width, _height); // clear canvas.
|
|
||||||
let barWidth = Math.floor(_width / bins.length) - 1; // -1 for 1 pixel gap between bars.
|
|
||||||
let barIndex = 0;
|
|
||||||
bins.forEach(bin => {
|
|
||||||
let normalBin = bin / 255.0;
|
|
||||||
_canvasCtx.fillStyle = "rgb(" + 0 + "," + bin + "," + bin + ")";
|
|
||||||
let barHeight = _height * normalBin;
|
|
||||||
_canvasCtx.fillRect(barIndex * barWidth, _height - barHeight, barWidth, barHeight);
|
|
||||||
barIndex += 1;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
_visualizerCore.addUpdateListener(update);
|
|
||||||
}
|
|
@ -1 +1,52 @@
|
|||||||
// TODO: Reorganize such that audioshowkit.js uses VisualizerCore.js.
|
export default class VisualizerCore {
|
||||||
|
constructor(mediaSource, fftSize = 1024) {
|
||||||
|
this._stream = mediaSource;
|
||||||
|
this._analyzing = false;
|
||||||
|
this._updateListeners = [];
|
||||||
|
this._audioCtx = new window.AudioContext();
|
||||||
|
if (mediaSource instanceof HTMLMediaElement) {
|
||||||
|
this._source = this._audioCtx.createMediaElementSource(this._stream);
|
||||||
|
} else {
|
||||||
|
this._source = this._audioCtx.createMediaStreamSource(this._stream);
|
||||||
|
}
|
||||||
|
this._analyzer = this._audioCtx.createAnalyser();
|
||||||
|
this._analyzer.fftSize = fftSize;
|
||||||
|
this._source.connect(this._analyzer);
|
||||||
|
this._analyzer.connect(this._audioCtx.destination);
|
||||||
|
this._buffer = new Uint8Array(this._analyzer.frequencyBinCount);
|
||||||
|
|
||||||
|
this.lastUpdate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
analyze() {
|
||||||
|
if (this._analyzing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._analyzing = true;
|
||||||
|
let self = this; // since calling from requestAnimationFrame means "this" is no longer set to produced object.
|
||||||
|
requestAnimationFrame((timestamp) => {
|
||||||
|
if (!self._analyzing) return;
|
||||||
|
|
||||||
|
requestAnimationFrame(self.update);
|
||||||
|
if (!self.lastUpdate) {
|
||||||
|
self.lastUpdate = timestamp;
|
||||||
|
}
|
||||||
|
let delta = timestamp - self.lastUpdate;
|
||||||
|
self._analyzer.getByteFrequencyData(self._buffer);
|
||||||
|
|
||||||
|
self._updateListeners.forEach(listener => {
|
||||||
|
listener(delta, self._buffer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this._analyzing = false;
|
||||||
|
}
|
||||||
|
addUpdateListener(listener) {
|
||||||
|
this._updateListeners.push(listener);
|
||||||
|
}
|
||||||
|
getNumberOfBins() {
|
||||||
|
return this._buffer.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,54 +1,4 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
function VisualizerCore(mediaSource, fftSize = 1024) {
|
// TODO: Detect annotated elements.
|
||||||
this._stream = mediaSource;
|
// TODO: Set up global scoping.
|
||||||
this._analyzing = false;
|
|
||||||
this._updateListeners = [];
|
|
||||||
this._audioCtx = new window.AudioContext();
|
|
||||||
this._source = null; // Prepare for either a MediaStream or a MediaElement.
|
|
||||||
try {
|
|
||||||
this.source = this._audioCtx.createMediaStreamSource(this._stream);
|
|
||||||
} catch (e) {
|
|
||||||
this._source = this._audioCtx.createMediaElementSource(this._stream);
|
|
||||||
}
|
|
||||||
this._analyzer = this._audioCtx.createAnalyser();
|
|
||||||
this._analyzer.fftSize = fftSize;
|
|
||||||
this._source.connect(this._analyzer);
|
|
||||||
this._analyzer.connect(this._audioCtx.destination);
|
|
||||||
this._buffer = new Uint8Array(this._analyzer.frequencyBinCount);
|
|
||||||
|
|
||||||
this.lastUpdate = null;
|
|
||||||
|
|
||||||
this.analyze = function () {
|
|
||||||
if (this._analyzing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._analyzing = true;
|
|
||||||
requestAnimationFrame(this.update);
|
|
||||||
};
|
|
||||||
|
|
||||||
let vcore = this; // since calling from requestAnimationFrame means "this" is no longer set to produced object.
|
|
||||||
this.update = function (timestamp) {
|
|
||||||
if (!vcore._analyzing) return;
|
|
||||||
|
|
||||||
requestAnimationFrame(vcore.update);
|
|
||||||
if (!vcore.lastUpdate) {
|
|
||||||
vcore.lastUpdate = timestamp;
|
|
||||||
}
|
|
||||||
let delta = timestamp - vcore.lastUpdate;
|
|
||||||
vcore._analyzer.getByteFrequencyData(vcore._buffer);
|
|
||||||
|
|
||||||
vcore._updateListeners.forEach(listener => {
|
|
||||||
listener(delta, vcore._buffer);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
this.stop = function () {
|
|
||||||
this._analyzing = false;
|
|
||||||
};
|
|
||||||
this.addUpdateListener = function (listener) {
|
|
||||||
this._updateListeners.push(listener);
|
|
||||||
};
|
|
||||||
this.getNumberOfBins = function () {
|
|
||||||
return this._buffer.length;
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
import PlaylistSong from "./PlaylistSong";
|
|
||||||
|
|
||||||
export default function Playlist() {
|
|
||||||
this._list = [];
|
|
||||||
|
|
||||||
this.songAtIndex = function (index) {
|
|
||||||
return this.list[index];
|
|
||||||
};
|
|
||||||
|
|
||||||
this.songsWithName = function (name) {
|
|
||||||
return this._list.filter((item) => item.getDisplayName() == name);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.add = function (url, name, author) {
|
|
||||||
this._list.push(new PlaylistSong(url, name, author, this, this._list.length));
|
|
||||||
};
|
|
||||||
|
|
||||||
this.remove = function (index) {
|
|
||||||
let removed = this._list.splice(index, 1);
|
|
||||||
if (removed.length > 0) {
|
|
||||||
return removed[0];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.findSongIndex = function (name) {
|
|
||||||
return this._list.findIndex((item) => item.getDisplayName() == name);
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,26 +1,165 @@
|
|||||||
export default function PlaylistSong(url, name, author, playlist) {
|
import SongPlaylist from "./SongPlaylist";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A song with metadata that can be used as part of a {@link SongPlaylist}.
|
||||||
|
*/
|
||||||
|
export default class PlayListSong {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a song for a {@link SongPlaylist}.
|
||||||
|
*
|
||||||
|
* @param {string} url the url to fetch the song from.
|
||||||
|
* @param {string} name the name of the song.
|
||||||
|
* @param {string} author the author of the song.
|
||||||
|
* @param {SongPlaylist} playlist the {@link SongPlaylist} this song is part of.
|
||||||
|
*/
|
||||||
|
constructor(url, name, author, playlist) {
|
||||||
this._displayName = name;
|
this._displayName = name;
|
||||||
this._author = author;
|
this._author = author;
|
||||||
this._url = url;
|
this._url = url;
|
||||||
this._mediaStream = null;
|
this._audio = null;
|
||||||
this._playlist = playlist;
|
this._playlist = playlist;
|
||||||
|
|
||||||
this.getAudio = function () {
|
/**
|
||||||
if (this.mediaStream) {
|
* Whether or not this song is ready to be played.
|
||||||
return this.mediaStream;
|
*/
|
||||||
|
this.ready = false;
|
||||||
}
|
}
|
||||||
this.mediaStream = new Audio(url);
|
|
||||||
};
|
/**
|
||||||
this.getDisplayName = function () {
|
* @callback AudioEventCallback
|
||||||
|
* @param {HTMLAudioElement} audio
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @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) {
|
||||||
|
if (this.ready) {
|
||||||
|
if (onReady) onReady(this._audio);
|
||||||
|
return this._audio;
|
||||||
|
}
|
||||||
|
return this._audio;
|
||||||
|
}
|
||||||
|
this._audio = new Audio(this._url);
|
||||||
|
|
||||||
|
this._audio.addEventListener("canplaythrough", () => {
|
||||||
|
this.ready = true;
|
||||||
|
if (onReady) {
|
||||||
|
onReady(this._audio);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this._audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {string} representing the name of the song to be displayed.
|
||||||
|
*/
|
||||||
|
getDisplayName = function () {
|
||||||
return this._displayName;
|
return this._displayName;
|
||||||
};
|
};
|
||||||
this.getAuthor = function () {
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {string} representing the author of the song.
|
||||||
|
*/
|
||||||
|
getAuthor = function () {
|
||||||
return this._getAuthor;
|
return this._getAuthor;
|
||||||
};
|
};
|
||||||
this.getUrl = function () {
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {string} representing the url at which the file for this song can be found.
|
||||||
|
*/
|
||||||
|
getUrl = function () {
|
||||||
return this._url;
|
return this._url;
|
||||||
};
|
};
|
||||||
this.getPlaylist = function () {
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {SongPlaylist} the playlist this song is part of.
|
||||||
|
*/
|
||||||
|
getPlaylist = function () {
|
||||||
return this._playlist;
|
return this._playlist;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {boolean} true if and only if there is audio data that is either already loaded or is being loaded.
|
||||||
|
*/
|
||||||
|
audioInstantiated() {
|
||||||
|
return this._audio ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins audio playback as soon as possible.
|
||||||
|
*/
|
||||||
|
play() {
|
||||||
|
this.getAudio((audio) => {
|
||||||
|
audio.play();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pauses the audio playback, unless the audio data has yet to be instantiated.
|
||||||
|
*/
|
||||||
|
pause() {
|
||||||
|
if (!this.audioInstantiated()) return;
|
||||||
|
this.getAudio((audio) => {
|
||||||
|
audio.pause();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {number} the volume on a scale of 0 to 1.
|
||||||
|
*/
|
||||||
|
getVolume() {
|
||||||
|
return this.getAudio().volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} volume a normalized volume on a scale of 0 to 1.
|
||||||
|
*/
|
||||||
|
setVolume(volume) {
|
||||||
|
this.getAudio().volume = volume;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {number} the number of seconds into the song.
|
||||||
|
*/
|
||||||
|
getCurrentTime() {
|
||||||
|
return this.getAudio().currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} currentTime the time position in the song to jump to in seconds.
|
||||||
|
*/
|
||||||
|
setCurrentTime(currentTime) {
|
||||||
|
this.getAudio().currentTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {number} the duration of the song.
|
||||||
|
*/
|
||||||
|
getDuration() {
|
||||||
|
return this.getAudio().duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unloads the audio data.
|
||||||
|
*/
|
||||||
|
unloadAudio() {
|
||||||
|
if (!this.audioInstantiated()) return;
|
||||||
|
this._audio.pause();
|
||||||
|
this._audio = null;
|
||||||
|
this.ready = false;
|
||||||
|
}
|
||||||
}
|
}
|
78
src/player/SongPlaylist.js
Normal file
78
src/player/SongPlaylist.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import PlaylistSong from "./PlaylistSong";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A playlist that holds a multitude of songs.
|
||||||
|
*/
|
||||||
|
export default class SongPlaylist {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a playlist for songs.
|
||||||
|
*
|
||||||
|
* @param {string} name The name of the playlist.
|
||||||
|
*/
|
||||||
|
constructor(name) {
|
||||||
|
this._list = [];
|
||||||
|
this._name = name;
|
||||||
|
this._current = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {string} the name of the string.
|
||||||
|
*/
|
||||||
|
getName() {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} index the index of the song to retrieve.
|
||||||
|
* @returns {PlaylistSong} the song at the given index.
|
||||||
|
*/
|
||||||
|
songAtIndex(index) {
|
||||||
|
if (index >= this._list.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.list[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
songsWithName(name) {
|
||||||
|
return this._list.filter((item) => item.getDisplayName() == name);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(url, name, author) {
|
||||||
|
this._list.push(new PlaylistSong(url, name, author, this, this._list.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(index) {
|
||||||
|
if (index >= this._list.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let removed = this._list.splice(index, 1);
|
||||||
|
if (removed.length > 0) {
|
||||||
|
return removed[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findSongIndex(name) {
|
||||||
|
// TODO: Could probably be optimized.
|
||||||
|
return this._list.findIndex((item) => item.getDisplayName() == name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {number} total number of songs in this playlist.
|
||||||
|
*/
|
||||||
|
total() {
|
||||||
|
return this._list.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unloads the audio data of all songs in this playlist.
|
||||||
|
*/
|
||||||
|
unloadAllAudio() {
|
||||||
|
this._list.forEach(playlistSong => {
|
||||||
|
playlistSong.unloadAudio();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
21
webpack.common.js
Normal file
21
webpack.common.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
entry: {
|
||||||
|
audioshowkit: "./src/audioshowkit.js"
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.html$/i,
|
||||||
|
loader: "html-loader",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/i,
|
||||||
|
use: ['style-loader', 'css-loader'],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: "[name].js",
|
||||||
|
path: "./dist"
|
||||||
|
}
|
||||||
|
};
|
13
webpack.dev.js
Normal file
13
webpack.dev.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import merge from "webpack-merge";
|
||||||
|
import webpackCommon from "./webpack.common";
|
||||||
|
|
||||||
|
devConfig = {
|
||||||
|
mode: "development",
|
||||||
|
devtools: "inline-source-map",
|
||||||
|
|
||||||
|
devServer: {
|
||||||
|
static: "./dist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default merge(webpackCommon, devConfig);
|
12
webpack.prod.js
Normal file
12
webpack.prod.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import merge from "webpack-merge"
|
||||||
|
import webpackCommon from "./webpack.common"
|
||||||
|
|
||||||
|
prodConfig = {
|
||||||
|
mode: "production",
|
||||||
|
output: {
|
||||||
|
filename: "[name].js",
|
||||||
|
path: "./public"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default merge(webpackCommon, prodConfig);
|
Loading…
Reference in New Issue
Block a user