// HTML Elements const e_songDetails = { title: document.getElementById("current-title"), playlist: document.getElementById("current-playlist"), artist: document.getElementById("current-artist"), }; const e_playlist = document.getElementById("current-playlist"); const e_songListSection = document.getElementById("song-list"); const e_songList = { next: document.getElementById("list-next"), current: document.getElementById("list-current"), previous: document.getElementById("list-previous"), }; const e_listeners = { current: document.querySelector(".listeners.current"), total: document.querySelector(".listeners.total"), unique: document.querySelector(".listeners.unique"), }; const e_audio = document.getElementById("audio"); e_audio.volume = 0.2; const e_warningMessage = document.getElementById("warning-message"); const e_player = document.getElementById("player"); const e_progressBar = document.getElementById("progress-bar"); const e_volumeBar = document.getElementById("volume-bar"); const e_channels = document.querySelectorAll(".channel"); /** * Return a random integer between 0 and a given limit. * @param {int} limit - The maximum returnable integer */ const rRng = (limit) => Math.floor(Math.random() * limit); /** * Return a random item from a given array. * @param {Array} array - The maximum returnable integer */ const rArr = (array) => array[rRng(array.length)]; // :^) const lainon = { life: "https://radio.scuf.ru/", }; // Set a random background image const backgrounds = ["bg205.gif", "bg_307.gif", "bg_315.gif", "patternTV2.gif"]; document.body.style.setProperty( "background-image", `url("./public/asset/${rArr(backgrounds)}")` ); // Default settings let isPlaying = false; let currentChannel = "cafe"; let progressStep = 0; /** * Send a GET request to lainon.life, then do stuff with the reponse. * @returns {Object} data - Information about the present radio state */ async function fetchData() { // Since lainon.life doesn't allow CORS, the request must be // made through a proxy. This "crosscloak" is my fork of // cors-anywhere, and just in case you're thinking about it, // no, it's not public and your domain is not on the whitelist. const data = await fetch(`https://radio.scuf.ru/api/nowplaying/scuf_fm`) .then((response) => response.json()) .catch((ERROR) => { console.error(ERROR); e_warningMessage.classList.remove("hidden"); }); parseData(data); return data; } /** * Parse the reponse data from lainon.life and display it on the page. * @param {Object} radioData - Present state of the lainon.life radio. */ function parseData(radioData) { // Update listeners for (const listenerType in radioData.listeners) { e_listeners[listenerType].innerText = radioData.listeners[listenerType]; } // Go over the radioData object. // If the object exists, set the corresponding element's // innerText to its value, or otherwise to say that it // wasn't found. for (const itemType in e_songDetails) { e_songDetails[itemType].innerText = radioData.now_playing.song[itemType] ? radioData.now_playing.song[itemType] : `${itemType.toUpperCase()} NOT FOUND`; } e_playlist.innerText = radioData.now_playing.playlist; // Current song's length in seconds const songLength = radioData.now_playing.duration; // Increment for the progress bar as a percentage of 100 progressStep = (1 / songLength) * 100; // Set the progress bar to display the current song's elapsed time e_progressBar.style.setProperty( "width", progressStep * Math.floor(radioData.now_playing.elapsed) + "%" ); e_songListSection.style = `background-image: url(${radioData.now_playing.song.art})`; // Clear the entire song list for (const element in e_songList) e_songList[element].innerHTML = ""; // Fill in the upcoming songs const { song, duration } = radioData.playing_next; const e_songEntry = document.createElement("div"); e_songEntry.className = "song-entry"; e_songEntry.innerHTML = `
${song.artist} - ${song.title}
${toMinSec(duration).join(":")} `; e_songList.next.appendChild(e_songEntry); // Display the current song in the song list e_songList.current.innerHTML = `
${ radioData.now_playing.song.artist } - ${ radioData.now_playing.song.title }
${toMinSec( radioData.now_playing.duration ).join(":")}
`; // Fill in the previous songs for (const song of radioData.song_history) { const e_songEntry = document.createElement("div"); e_songEntry.className = "song-entry"; e_songEntry.innerHTML = `
${song.song.artist} - ${song.song.title}
${toMinSec(song.duration).join( ":" )} `; e_songList.previous.appendChild(e_songEntry); } } /** * Convert seconds to minutes. * @param {int} totalSeconds - Any amount of seconds * @returns {Array} [minutes, seconds] */ function toMinSec(totalSeconds) { return [padZero(Math.floor(totalSeconds / 60)), padZero(totalSeconds % 60)]; } /** * Prefixes single digits with a "0". * @param {} integer * @returns {String} Input number converted to a string */ function padZero(integer) { return integer < 10 ? "0" + integer : "" + integer; } /** * Change to a different radio channel. * @param {String} channelName */ function switchChannel(channelName) { e_player.classList.remove("paused"); // Remove active class from all DOM channel elements e_channels.forEach((el) => el.classList.remove("active")); // Add it to the clicked one document.getElementById(channelName).classList.add("active"); currentChannel = channelName; e_audio.src = `https://radio.scuf.ru/listen/scuf_fm/radio.mp3`; e_audio.play(); fetchData(); isPlaying = true; } function pauseRadio() { if (!isPlaying) { return; } e_player.classList.add("paused"); progressStep = 0; e_channels.forEach((el) => el.classList.remove("active")); document.getElementById("pause").classList.add("active"); e_audio.pause(); isPlaying = false; } function toggleMute() { if (e_audio.muted) { document.getElementById("mute").classList.remove("active"); e_volumeBar.classList.remove("muted"); e_audio.muted = false; } else { document.getElementById("mute").classList.add("active"); e_volumeBar.classList.add("muted"); e_audio.muted = true; } } // Change the volume let volumeUpdate; // Interval container function setVolume(mouseEvent) { // If anything other than LMB is pressed, return early if (mouseEvent.buttons != 1) return; // Horizontal coordinate of the click const clickPosition = mouseEvent.offsetX; // Calculate new bar position and volume let volumeBarWidth = (100 / mouseEvent.target.clientWidth) * clickPosition; let audioVolume = (1 / mouseEvent.target.clientWidth) * clickPosition; // Resize the volume bar to the new width e_volumeBar.style.setProperty("width", `${volumeBarWidth}%`); // Set the audio volume e_audio.volume = audioVolume; } e_player.onmousemove = setVolume; e_player.onmousedown = setVolume; // Refresh radio data when the tab is focused window.onfocus = () => { if (isPlaying) fetchData(); }; // Update the progress bar every second const updateProgressBar = setInterval(() => { // Current percentage const width = parseFloat(e_progressBar.style.width); // Future percentage const futureWidth = width + progressStep; // If the future percentage doesn't exceed 100% // increment the progress bar. if (futureWidth < 100) { e_progressBar.style.setProperty("width", futureWidth + "%"); } // Otherwise, if it does exceed 100%, that means // the song has changed, so fetch the new data. else if (futureWidth >= 100) fetchData(); }, 1000);