Compare commits

...

4 Commits

Author SHA1 Message Date
aa02cf9fa4 visualizer controls 2025-01-14 13:34:29 -05:00
eabec44490 add settings ui 2025-01-08 12:41:45 -05:00
3f9048a395 visualizer 2024-12-28 19:37:15 -05:00
8238288df6 update ui with track info 2024-12-20 23:35:25 -05:00
3 changed files with 106 additions and 12 deletions

View File

@ -79,6 +79,18 @@ input[type="radio"]:checked+div {
"L D D R"; "L D D R";
} }
#settings {
grid-area: L;
display: flex;
flex-direction: column;
gap: 0.5em;
padding: 1em;
}
#settings select {
width: 100%;
}
#cover_art { #cover_art {
grid-area: A; grid-area: A;
padding-right: 1em; padding-right: 1em;
@ -94,6 +106,11 @@ img {
grid-area: B; grid-area: B;
} }
#visualizer {
width: 90%;
height: 4em;
}
#player_controls { #player_controls {
grid-area: C; grid-area: C;
} }

View File

@ -1,11 +1,15 @@
var player; var player;
var pos_int; var audio_ctx;
var analyser; var analyser;
var audio_data; var audio_data;
var canvas_ctx;
var draw_loop;
var vol_prev; var vol_prev;
function init() { function init() {
player = document.querySelector('#player'); player = document.querySelector('#player');
init_analyzer();
init_canvas();
player.addEventListener('ended', nextTrack); player.addEventListener('ended', nextTrack);
player.addEventListener('loadedmetadata', display_duration); player.addEventListener('loadedmetadata', display_duration);
@ -34,8 +38,6 @@ function init() {
document.querySelector('#vol-off-btn').style.display = 'none'; document.querySelector('#vol-off-btn').style.display = 'none';
} }
}); });
init_analyzer();
} }
async function get_rand_track() { async function get_rand_track() {
@ -47,6 +49,7 @@ async function get_rand_track() {
async function nextTrack() { async function nextTrack() {
track = await get_rand_track(); track = await get_rand_track();
player.src = track.source; player.src = track.source;
update_now_playing(track);
playTrack(); playTrack();
} }
@ -60,7 +63,10 @@ async function playTrack() {
} }
player.play(); player.play();
navigator.mediaSession.playbackState = "playing"; navigator.mediaSession.playbackState = "playing";
setMediaSession(track);
if (document.querySelector("#visualizer-select").value === 'bar_graph') {
bar_graph();
}
document.querySelector("#play-btn").style.display = "none"; document.querySelector("#play-btn").style.display = "none";
document.querySelector("#pause-btn").style.display = "initial"; document.querySelector("#pause-btn").style.display = "initial";
@ -92,15 +98,23 @@ function setMediaSession(track) {
}); });
} }
function update_now_playing(track) {
track.artwork.pop(1); // remove lower resolution option
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() { function update_position() {
if (!player.duration){return;}
navigator.mediaSession.setPositionState({ navigator.mediaSession.setPositionState({
duration: Math.floor(player.duration), duration: Math.floor(player.duration),
playbackRate: player.playbackRate, playbackRate: player.playbackRate,
position: Math.floor(player.currentTime) position: Math.floor(player.currentTime)
}); });
analyser.getByteTimeDomainData(audio_data);
document.querySelector('#seek-slider').value = Math.floor(player.currentTime); document.querySelector('#seek-slider').value = Math.floor(player.currentTime);
display_current_time(); display_current_time();
} }
@ -143,13 +157,60 @@ function volume_restore() {
document.querySelector('#vol-off-btn').style.display = 'none'; document.querySelector('#vol-off-btn').style.display = 'none';
} }
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() { function init_analyzer() {
let audioCtx = new AudioContext(); audio_ctx = new AudioContext();
analyser = audioCtx.createAnalyser(); analyser = audio_ctx.createAnalyser();
let source = audioCtx.createMediaElementSource(player); let source = audio_ctx.createMediaElementSource(player);
source.connect(analyser); source.connect(analyser);
source.connect(audioCtx.destination); source.connect(audio_ctx.destination);
}
audio_data = new Uint8Array(analyser.frequencyBinCount);
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;
}
} }

View File

@ -27,10 +27,26 @@
<div id="track_list" class="list"> <div id="track_list" class="list">
</div> </div>
<div id="player_container"> <div id="player_container">
<div id="settings">
<div>
<label for="playlist-select">Playlist:</label>
<select id="playlist-select">
<option>All Tracks</option>
</select>
</div>
<div>
<label for="visualizer-select">Visualizer:</label>
<select id="visualizer-select" onchange="select_visualizer(event)">
<option value="disabled" selected>Disabled</option>
<option value="bar_graph">Bar Graph</option>
</select>
</div>
</div>
<div id="cover_art"><img alt="Album cover art" src="./static/pyrite.jpg"></div> <div id="cover_art"><img alt="Album cover art" src="./static/pyrite.jpg"></div>
<div id="title"> <div id="title">
<h4><span id="now_playing_artist">Artist</span> - <span id="now_playing_album">Album</span></h4> <h4><span id="now_playing_artist">Artist</span> - <span id="now_playing_album">Album</span></h4>
<h3 id="now_playing_title">Title</h3> <h3 id="now_playing_title">Title</h3>
<canvas id="visualizer" height="300" width="1000"></canvas>
</div> </div>
<div id="player_controls"> <div id="player_controls">
<audio id="player" style="display:none"></audio> <audio id="player" style="display:none"></audio>