const audio = document.querySelector("audio"); const playButton = document.querySelector(".play-stream"); const playButtonAnimation = document.getElementById("play-button-animation"); const artistBlock = document.querySelector(".now-playing_artist"); const titleBlock = document.querySelector(".now-playing_song"); const logoDot = document.querySelector(".logo_dot"); const STOP_STATE_POINTS = "650,0, 1350,0, 1350,500, 650,500"; const PLAY_STATE_POINTS = "0,0, 2000,250, 2000,250, 0,500"; const startIcebergsAnimation = () => { [1, 2, 3].map((i) => { const polygon = document.getElementById(`iceberg${i}`); polygon.classList.add("active"); }); }; const stopIcebergsAnimation = () => { [1, 2, 3].map((i) => { const polygon = document.getElementById(`iceberg${i}`); polygon.classList.remove("active"); }); }; const hideAudio = () => { audio.hidden = true; }; const playAudio = () => { audio?.play(); }; const pauseAudio = () => { audio?.pause(); }; const setButtonPlayState = () => { playButtonAnimation.setAttribute("from", PLAY_STATE_POINTS); playButtonAnimation.setAttribute("to", STOP_STATE_POINTS); playButtonAnimation.beginElement(); }; const setButtonStopState = () => { playButtonAnimation.setAttribute("from", STOP_STATE_POINTS); playButtonAnimation.setAttribute("to", PLAY_STATE_POINTS); playButtonAnimation.beginElement(); }; const handleAudioPlay = () => { playAudio(); setButtonPlayState(); startIcebergsAnimation(); }; const handleAudioStop = () => { pauseAudio(); setButtonStopState(); stopIcebergsAnimation(); }; const addPlayerInteractions = () => { playButton.addEventListener("click", () => { audio?.paused ? handleAudioPlay() : handleAudioStop(); }); }; const parseMessageFromString = (message) => { try { return JSON.parse(message); } catch { return {}; } }; /** * @typedef {Object} MessageLike * @property {string} action - every string valid for JS function name. * @property {Object} data - The user's last name. */ /** * @param {string|MessageLike} data - Message itself of JSON representation of Message */ function Message(data = {}) { switch(Object.prototype.toString.call(data)) { case "[object String]": const parsedMessage = parseMessageFromString(data); this.action = parsedMessage?.action; this.data = parsedMessage?.data; break; case "[object Object]": this.action = data?.action; this.data = data?.data; break; } } Message.prototype.isValid = function() { return ( this?.action !== undefined ); }; /** * @typedef {Object} MessageLike * @property {string} action - every string valid for JS function name. * @property {Object} data - The user's last name. */ /** * @param {string|MessageLike} message - Message itself of JSON representation of Message */ function processMessage(messageLike = {}) { const message = new Message(messageLike); if(!message.isValid()) { console.error("Got mallformed message:", message); return; } if (this.actions[message.action]) { this.actions[message.action](message.data); } else { this.fallbackAction(message); } } const messageProcessor = { // Use Map instead? actions: {}, processMessage, fallbackAction: function(x) { console.log("No actions for message:", x); } }; const socketProtocol = location.protocol === "http:" ? "ws:" : "wss:"; async function connectToServer() { const ws = new WebSocket(`${socketProtocol}//${location.host}/meta`); ws.addEventListener("message", (event) => messageProcessor.processMessage(event.data), ); } const changeTitles = ({ artist, title }) => { if (artist) { artistBlock.innerHTML = artist; } else { artistBlock.innerHTML = ""; } if (title) { titleBlock.innerHTML = title; document.title = `${title} on Radioiceberg`; } else { titleBlock.innerHTML = ""; } if (artist && title) { document.title = `${artist} - ${title} on Radioiceberg`; } }; const metadataChange = (data) => { changeTitles(data); }; const liveStarted = () => { logoDot.classList.add("live"); }; const liveEnded = () => { logoDot.classList.remove("live"); }; const setState = (data) => { const { lastPlayed, isOnline } = data; if (lastPlayed) changeTitles(lastPlayed); if (isOnline) liveStarted(); }; const actions = { metadataChange, liveStarted, liveEnded, setState, }; messageProcessor.actions = actions; const main = () => { hideAudio(); addPlayerInteractions(); connectToServer(); }; document.addEventListener("DOMContentLoaded", main);