331 lines
9.7 KiB
JavaScript
331 lines
9.7 KiB
JavaScript
var player;
|
|
var audio_ctx;
|
|
var analyser;
|
|
var audio_data;
|
|
var canvas_ctx;
|
|
var draw_loop;
|
|
var vol_prev;
|
|
|
|
function init() {
|
|
player = document.querySelector('#player');
|
|
init_analyzer();
|
|
init_canvas();
|
|
|
|
player.addEventListener('ended', nextTrack);
|
|
player.addEventListener('loadedmetadata', display_duration);
|
|
player.addEventListener('timeupdate', update_position);
|
|
|
|
navigator.mediaSession.setActionHandler('play', playTrack);
|
|
navigator.mediaSession.setActionHandler('pause', pauseTrack);
|
|
navigator.mediaSession.setActionHandler('stop', stopTrack);
|
|
navigator.mediaSession.setActionHandler('previoustrack', prevTrack);
|
|
navigator.mediaSession.setActionHandler('nexttrack', nextTrack);
|
|
|
|
let seek = document.querySelector('#seek-slider');
|
|
seek.addEventListener('input', function(){
|
|
player.currentTime = seek.value;
|
|
display_current_time();
|
|
});
|
|
|
|
let vol = document.querySelector('#volume-slider');
|
|
vol.addEventListener('input', function(){
|
|
player.volume = vol.value/100;
|
|
if (vol.value == 0) {
|
|
document.querySelector('#vol-btn').style.display = 'none';
|
|
document.querySelector('#vol-off-btn').style.display = 'initial';
|
|
} else {
|
|
document.querySelector('#vol-btn').style.display = 'initial';
|
|
document.querySelector('#vol-off-btn').style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
async function get_rand_track() {
|
|
let playlist = document.querySelector('#playlist').value;
|
|
|
|
let q = new URLSearchParams();
|
|
q.set('playlist', playlist);
|
|
|
|
let res = await fetch("./rand_track/?" + q.toString())
|
|
let data = await res.json();
|
|
return data;
|
|
}
|
|
|
|
async function select_artist(event) {
|
|
let artist = event.target.value;
|
|
let playlist = document.querySelector('#playlist').value;
|
|
|
|
let q = new URLSearchParams();
|
|
q.set('albumartist', artist);
|
|
q.set('playlist', playlist);
|
|
let res = await fetch("./albums/?" + q.toString());
|
|
let data = await res.json();
|
|
|
|
populate_albums(data);
|
|
}
|
|
|
|
async function select_album(event) {
|
|
let artist = document.querySelector('input[name=artist]:checked').value;
|
|
let album = document.querySelector('input[name=album]:checked').value;
|
|
let date = event.srcElement.parentElement.querySelector('.album_year').textContent;
|
|
let playlist = document.querySelector('#playlist').value;
|
|
|
|
let q = new URLSearchParams();
|
|
q.set('albumartist', artist);
|
|
q.set('album', album);
|
|
q.set('date', date);
|
|
q.set('playlist', playlist);
|
|
let res = await fetch("./tracks/?" + q.toString());
|
|
let data = await res.json();
|
|
|
|
populate_tracks(data);
|
|
}
|
|
|
|
function populate_tracks(data) {
|
|
clear_tracks();
|
|
for (let i = 0; i < data.length; i++) {
|
|
let track = document.importNode(document.querySelector('#track_template').content, true);
|
|
track.querySelector('label').setAttribute('for', 'track_' + i);
|
|
track.querySelector('input').setAttribute('id', 'track_' + i);
|
|
track.querySelector('input').setAttribute('value', data[i]['title']);
|
|
track.querySelector('.track_num').textContent = data[i]['discnumber'] + '.' + String(data[i]['tracknumber']).padStart(2, '0');
|
|
track.querySelector('.track_title').textContent = data[i]['title'];
|
|
let min = Math.floor(Math.round(data[i]['duration']) / 60);
|
|
let sec = Math.round(data[i]['duration']) % 60;
|
|
sec = String(sec).padStart(2, '0');
|
|
track.querySelector('.track_length').textContent = min + 'm' + sec + 's';
|
|
track.querySelector('.track').dataset.url = data[i]['filepath'];
|
|
|
|
document.querySelector('#track_list').appendChild(track);
|
|
}
|
|
}
|
|
|
|
function populate_albums(data) {
|
|
clear_albums();
|
|
clear_tracks();
|
|
for (let i = 0; i < data.length; i++) {
|
|
let album = document.importNode(document.querySelector('#album_template').content, true);
|
|
album.querySelector('label').setAttribute('for', 'album_' + i);
|
|
album.querySelector('input').setAttribute('id', 'album_' + i);
|
|
album.querySelector('input').setAttribute('value', data[i]['album']);
|
|
album.querySelector('img').setAttribute('src', data[i]['cover_art_path']);
|
|
album.querySelector('.album_name').textContent = data[i]['album'];
|
|
album.querySelector('.album_year').textContent = data[i]['date'];
|
|
|
|
document.querySelector('#album_list').appendChild(album);
|
|
}
|
|
}
|
|
|
|
function select_track(event) {
|
|
let track = {}
|
|
track['artist'] = document.querySelector('input[name=artist]:checked').value;
|
|
track['album'] = document.querySelector('input[name=album]:checked').value;
|
|
track['title'] = document.querySelector('input[name=track]:checked').value;
|
|
|
|
let art_url = document.querySelector('input[name=album]:checked').parentElement.querySelector('img').src;
|
|
art_url = art_url.replace('64x64', '256x256');
|
|
let artwork = [{'src': art_url, 'sizes': '256x256', 'type': 'image/jpeg'}];
|
|
track['artwork'] = artwork;
|
|
|
|
let track_url = new URL(event.srcElement.parentElement.querySelector('.track').dataset.url, window.location);
|
|
|
|
update_now_playing(track);
|
|
player.src = track_url;
|
|
playTrack();
|
|
}
|
|
|
|
function select_playlist(event) {
|
|
let playlist = document.querySelector('#playlist').value;
|
|
document.cookie = "playlist=" + playlist;
|
|
}
|
|
|
|
function clear_tracks() {
|
|
let tracks = document.querySelector('#track_list');
|
|
while (tracks.firstChild) {
|
|
tracks.removeChild(tracks.lastChild);
|
|
}
|
|
}
|
|
|
|
function clear_albums() {
|
|
let albums = document.querySelector('#album_list');
|
|
while (albums.firstChild) {
|
|
albums.removeChild(albums.lastChild);
|
|
}
|
|
}
|
|
|
|
function clear_artists() {
|
|
let artists = document.querySelector('#artist_list');
|
|
while (artists.firstChild) {
|
|
artists.removeChild(artists.lastChild);
|
|
}
|
|
}
|
|
|
|
async function nextTrack() {
|
|
track = await get_rand_track();
|
|
player.src = track.source;
|
|
update_now_playing(track);
|
|
playTrack();
|
|
}
|
|
|
|
function prevTrack() {
|
|
// TODO: implement a history array
|
|
}
|
|
|
|
async function playTrack() {
|
|
if (!player.src) {
|
|
return nextTrack();
|
|
}
|
|
player.play();
|
|
navigator.mediaSession.playbackState = "playing";
|
|
|
|
if (document.querySelector("#visualizer-select").value === 'bar_graph') {
|
|
bar_graph();
|
|
}
|
|
|
|
document.querySelector("#play-btn").style.display = "none";
|
|
document.querySelector("#pause-btn").style.display = "initial";
|
|
}
|
|
|
|
function pauseTrack() {
|
|
player.pause();
|
|
navigator.mediaSession.playbackState = "paused";
|
|
|
|
document.querySelector("#play-btn").style.display = "initial";
|
|
document.querySelector("#pause-btn").style.display = "none";
|
|
}
|
|
|
|
function stopTrack() {
|
|
player.pause();
|
|
player.load();
|
|
navigator.mediaSession.playbackState = "none";
|
|
|
|
document.querySelector("#play-btn").style.display = "initial";
|
|
document.querySelector("#pause-btn").style.display = "none";
|
|
}
|
|
|
|
function setMediaSession(track) {
|
|
navigator.mediaSession.metadata = new MediaMetadata({
|
|
title: track.title,
|
|
artist: track.artist,
|
|
album: track.album,
|
|
artwork: track.artwork
|
|
});
|
|
}
|
|
|
|
function update_now_playing(track) {
|
|
document.querySelector('#now_playing_artist').textContent = track.artist;
|
|
document.querySelector('#now_playing_album').textContent = track.album;
|
|
document.querySelector('#now_playing_title').textContent = track.title;
|
|
document.querySelector('#cover_art').firstChild.src = track.artwork[0].src;
|
|
setMediaSession(track);
|
|
}
|
|
|
|
function update_position() {
|
|
if (!player.duration){return;}
|
|
navigator.mediaSession.setPositionState({
|
|
duration: Math.floor(player.duration),
|
|
playbackRate: player.playbackRate,
|
|
position: Math.floor(player.currentTime)
|
|
});
|
|
|
|
document.querySelector('#seek-slider').value = Math.floor(player.currentTime);
|
|
display_current_time();
|
|
}
|
|
|
|
function calculate_time(raw_secs) {
|
|
let mins = Math.floor(raw_secs / 60);
|
|
let secs = Math.round(raw_secs % 60);
|
|
let ret = mins + ':' + String(secs).padStart(2,'0');
|
|
return ret;
|
|
}
|
|
|
|
function display_duration() {
|
|
let dur = document.querySelector('#duration');
|
|
dur.textContent = calculate_time(player.duration);
|
|
|
|
document.querySelector('#seek-slider').max = Math.floor(player.duration);
|
|
}
|
|
|
|
function display_current_time() {
|
|
cur = document.querySelector('#current-time');
|
|
cur.textContent = calculate_time(player.currentTime);
|
|
}
|
|
|
|
function volume_off() {
|
|
let vol = document.querySelector('#volume-slider');
|
|
vol_prev = vol.value;
|
|
vol.value = 0;
|
|
player.volume = 0;
|
|
|
|
document.querySelector('#vol-btn').style.display = 'none';
|
|
document.querySelector('#vol-off-btn').style.display = 'initial';
|
|
}
|
|
|
|
function volume_restore() {
|
|
let vol = document.querySelector('#volume-slider');
|
|
vol.value = vol_prev;
|
|
player.volume = vol_prev / 100;
|
|
|
|
document.querySelector('#vol-btn').style.display = 'initial';
|
|
document.querySelector('#vol-off-btn').style.display = 'none';
|
|
}
|
|
|
|
// --- visualizer ---
|
|
function select_visualizer(event) {
|
|
if (event.target.value == 'bar_graph') {
|
|
bar_graph();
|
|
} else if (event.target.value == 'disabled') {
|
|
cancelAnimationFrame(draw_loop);
|
|
let canvas = document.querySelector('#visualizer');
|
|
canvas_ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
}
|
|
}
|
|
|
|
function init_analyzer() {
|
|
audio_ctx = new AudioContext();
|
|
analyser = audio_ctx.createAnalyser();
|
|
let source = audio_ctx.createMediaElementSource(player);
|
|
|
|
source.connect(analyser);
|
|
source.connect(audio_ctx.destination);
|
|
}
|
|
|
|
function init_canvas() {
|
|
let canvas = document.querySelector('#visualizer');
|
|
canvas_ctx = canvas.getContext('2d');
|
|
canvas_ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
}
|
|
|
|
function bar_graph() {
|
|
analyser.fftSize = 256;
|
|
analyser.minDecibels = -90;
|
|
analyser.maxDecibels = -10;
|
|
analyser.smoothingTimeConstant = 0.85;
|
|
audio_data = new Uint8Array(analyser.frequencyBinCount);
|
|
|
|
draw();
|
|
}
|
|
|
|
function draw() {
|
|
draw_loop = requestAnimationFrame(draw);
|
|
|
|
analyser.getByteTimeDomainData(audio_data);
|
|
let canvas = document.querySelector('#visualizer');
|
|
|
|
canvas_ctx.fillStyle = "rgb(0 0 0)";
|
|
canvas_ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
const barWidth = (canvas.width / analyser.frequencyBinCount) * 2.5;
|
|
let barHeight;
|
|
let x = 0;
|
|
|
|
for (let i = 0; i < analyser.frequencyBinCount; i++) {
|
|
barHeight = audio_data[i];
|
|
|
|
canvas_ctx.fillStyle = `rgb(${barHeight + 100} 50 50)`;
|
|
canvas_ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight);
|
|
|
|
x += barWidth + 1;
|
|
}
|
|
}
|