Added Visualized song player and required playlist modifications (untested).
Class names and respective file names refactored. Added song player for basic playlist management and playback. Added support for visualizers per song in the playlist. Created a visualizer update manager that acts as a splitter for all the bins and their updates. Fixed potential bugs.
This commit is contained in:
@@ -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,3 +1,4 @@
|
||||
import Visualizer from "../Visualizer";
|
||||
import SongPlaylist from "./SongPlaylist";
|
||||
|
||||
/**
|
||||
@@ -17,8 +18,9 @@ export default class PlayListSong {
|
||||
this._displayName = name;
|
||||
this._author = author;
|
||||
this._url = url;
|
||||
this._audio = null;
|
||||
this._playlist = playlist;
|
||||
this._audio = null;
|
||||
this._visualizer = null;
|
||||
|
||||
/**
|
||||
* Whether or not this song is ready to be played.
|
||||
@@ -40,7 +42,6 @@ export default class PlayListSong {
|
||||
if (this._audio) {
|
||||
if (this.ready) {
|
||||
if (onReady) onReady(this._audio);
|
||||
return this._audio;
|
||||
}
|
||||
return this._audio;
|
||||
}
|
||||
@@ -91,7 +92,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.
|
||||
*/
|
||||
audioInstantiated() {
|
||||
isAudioInstantiated() {
|
||||
return this._audio ? true : false;
|
||||
}
|
||||
|
||||
@@ -108,7 +109,7 @@ export default class PlayListSong {
|
||||
* Pauses the audio playback, unless the audio data has yet to be instantiated.
|
||||
*/
|
||||
pause() {
|
||||
if (!this.audioInstantiated()) return;
|
||||
if (!this.isAudioInstantiated()) return;
|
||||
this.getAudio((audio) => {
|
||||
audio.pause();
|
||||
});
|
||||
@@ -157,9 +158,37 @@ export default class PlayListSong {
|
||||
* Unloads the audio data.
|
||||
*/
|
||||
unloadAudio() {
|
||||
if (!this.audioInstantiated()) return;
|
||||
if (!this.isAudioInstantiated()) return;
|
||||
this._audio.pause();
|
||||
this.unloadVisualizer();
|
||||
this._audio = null;
|
||||
this.ready = false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} [fftSize=1024] the size of the FFT window.
|
||||
* @returns {Visualizer} returns the visualizer.
|
||||
*/
|
||||
getVisualizer(fftSize = 1024) {
|
||||
if (this._visualizer && this._visualizer.getFftSize() === fftSize) return this._visualizer;
|
||||
this._visualizer = new Visualizer(this.getAudio(), fftSize);
|
||||
return this._visualizer;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {boolean} returns true if and only if the visualizer is instantiated.
|
||||
*/
|
||||
isVisualizerInstantiated() {
|
||||
return this._visualizer ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads the visualizer.
|
||||
*/
|
||||
unloadVisualizer() {
|
||||
this._visualizer.stop();
|
||||
this._visualizer = null;
|
||||
}
|
||||
}
|
144
src/player/SongPlayer.js
Normal file
144
src/player/SongPlayer.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import SongPlaylist from "./SongPlaylist";
|
||||
|
||||
/**
|
||||
* A player to play songs.
|
||||
*/
|
||||
export default class SongPlayer {
|
||||
|
||||
/**
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {SongPlaylist} playlist the new playlist of songs that this player is in charge of.
|
||||
*/
|
||||
setPlaylist(playlist) {
|
||||
this._playlist.unloadAllAudio();
|
||||
this._playlist = playlist;
|
||||
this._current = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {SongPlaylist} the current playlist of songs that this player is in charge of.
|
||||
*/
|
||||
getPlaylist() {
|
||||
return this._playlist;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {boolean} true if and only if successful in going to the next song.
|
||||
*/
|
||||
next() {
|
||||
if (this._current >= this._playlist.total() - 1) return false;
|
||||
this.getCurrentSong().unloadAudio();
|
||||
this._current += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* attempts to go to the previous song.
|
||||
*
|
||||
* @returns {boolean} true if and only if successful in going to the previous song.
|
||||
*/
|
||||
previous() {
|
||||
if (this._current <= 0) return false;
|
||||
this.getCurrentSong().unloadAudio();
|
||||
this._current -= 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} index the index of the song to jump to.
|
||||
* @returns {boolean} true if and only if successful jumping to the given index.
|
||||
*/
|
||||
changeCurrent(index) {
|
||||
if (index >= this._playlist.total()) return false;
|
||||
if (index <= 0) return false;
|
||||
this.getCurrentSong().unloadAudio();
|
||||
this._current = index;
|
||||
return true;
|
||||
}
|
||||
|
||||
playCurrent() {
|
||||
this.getCurrentSong().getAudio((audio) => {
|
||||
// TODO: May need to perform synchronization check to see if this is still the song to be played.
|
||||
audio.volume = this._volume;
|
||||
audio.play();
|
||||
});
|
||||
}
|
||||
|
||||
pauseCurrent() {
|
||||
this.getCurrentSong().getAudio().pause();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
setVolume(volume) {
|
||||
if (volume > 1) return false;
|
||||
if (volume < 0) return false;
|
||||
this._volume = volume;
|
||||
this.getCurrentSong().getAudio().volume = this._volume;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {number} the current volume of the player represented by a number between 0 and 1 inclusive.
|
||||
*/
|
||||
getVolume() {
|
||||
return this._volume;
|
||||
}
|
||||
|
||||
seek(position) {
|
||||
if (position > this.getCurrentSong().getAudio().duration || position < 0) return;
|
||||
this.getCurrentSong().getAudio(audio => {
|
||||
// TODO: May need to perform synchronization check to see if this is still the song to be played.
|
||||
audio.currentTime = position;
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentPosition() {
|
||||
return this.getCurrentSong().getAudio().currentTime;
|
||||
}
|
||||
|
||||
getCurrentDuration() {
|
||||
return this.getCurrentSong().getAudio().duration;
|
||||
}
|
||||
|
||||
generatePlayElement() {
|
||||
// TODO: Generates a play button in html.
|
||||
}
|
||||
|
||||
generateNextElement() {
|
||||
// TODO: generate a next button in html.
|
||||
}
|
||||
|
||||
generatePreviousElement() {
|
||||
// TODO: generate a previous button in html.
|
||||
}
|
||||
|
||||
generateSeeker() {
|
||||
// TODO: generate a seeker in html.
|
||||
}
|
||||
|
||||
generateVolumeSlider() {
|
||||
// TODO: generate a volume slider in html.
|
||||
}
|
||||
|
||||
getCurrentSong() {
|
||||
return this._playlist.songAtIndex(this._current);
|
||||
}
|
||||
}
|
@@ -48,14 +48,13 @@ export default class SongPlaylist {
|
||||
if (index >= this._list.length) {
|
||||
return null;
|
||||
}
|
||||
let removed = this._list.splice(index, 1);
|
||||
let removed = this._list.splice(index, 1)[0];
|
||||
if (removed.length > 0) {
|
||||
return removed[0];
|
||||
}
|
||||
}
|
||||
|
||||
findSongIndex(name) {
|
||||
// TODO: Could probably be optimized.
|
||||
return this._list.findIndex((item) => item.getDisplayName() == name);
|
||||
}
|
||||
|
||||
|
89
src/player/VisualizedSongPlayer.js
Normal file
89
src/player/VisualizedSongPlayer.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import Visualizer from "../Visualizer";
|
||||
import VisualizerUpdateManager from "../VisualizerUpdateManager";
|
||||
import SongPlayer from "./SongPlayer";
|
||||
import SongPlaylist from "./SongPlaylist";
|
||||
|
||||
/**
|
||||
* A song player that provides easier access to the current songs visualizer data.
|
||||
*
|
||||
* Additionally, automatically re-binds all the visualizer update listeners for song changes.
|
||||
*
|
||||
* Automatically loads the songs.
|
||||
*/
|
||||
export default class VisualizedSongPlayer extends SongPlayer {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {SongPlaylist} playlist the playlist this player manages.
|
||||
* @param {number} [fftSize=1024] the size of the fft window for analysis.
|
||||
*/
|
||||
constructor(playlist, fftSize = 1024) {
|
||||
super(playlist);
|
||||
this._fftSize = fftSize;
|
||||
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {boolean} true if and only if successful in changing to the next song.
|
||||
*/
|
||||
next() {
|
||||
const updateListeners = this._visualizerUpdateManager.getBinnedListeners();
|
||||
if (!super.next()) return false;
|
||||
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
this._visualizerUpdateManager.setBinnedListeners(updateListeners);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Jumps to the previous song if possible.
|
||||
*
|
||||
* @returns {boolean} true if and only if successful in switching to the previous song in the playlist.
|
||||
*/
|
||||
previous() {
|
||||
const updateListeners = this._visualizerUpdateManager.getBinnedListeners();
|
||||
if (!super.previous()) return false;
|
||||
|
||||
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
this._visualizerUpdateManager.setBinnedListeners(updateListeners);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} index the index of the song to change to.
|
||||
* @returns {boolean} true if and only if successful in jumping to the given index.
|
||||
*/
|
||||
changeCurrent(index) {
|
||||
const updateListeners = this.VisualizerUpdateManager.getBinnedListeners();
|
||||
if (!super.changeCurrent(index)) return false;
|
||||
this._visualizerUpdateManager = new VisualizerUpdateManager(this.getCurrentSongVisualizer());
|
||||
this._visualizerUpdateManager.setBinnedListeners = updateListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {VisualizerUpdateManager} the current visualizer update manager.
|
||||
*/
|
||||
getCurrentVisualizerUpdateManager() {
|
||||
return this._visualizerUpdateManager();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Visualizer} the current song's visualizer.
|
||||
*/
|
||||
getCurrentSongVisualizer() {
|
||||
return this.getCurrentSong().getVisualizer();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user