diff --git a/.gitignore b/.gitignore index cd7396f52a329e17354895604ea35dea80b94bd0..a3e6499f579881a3c14f451bcfdf2d98acd55413 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules dist .cache out +.webpack diff --git a/css/style.css b/css/style.css index 2126fa335730162c8831711575fdb1fc358f1c40..db1d20e78d3ffa0e2003a95f3db76f267c8565e7 100644 --- a/css/style.css +++ b/css/style.css @@ -30,10 +30,10 @@ body { color: #f1f1f1; min-height: 100vh; - min-width: 100vw; + min-width: 100%; margin: 0; - padding: 0; + padding: 0 0 100px 0; } * { @@ -103,7 +103,10 @@ nav { bottom: 0; left: 0; - background: var(--gray-9-70); + background: url('../img/noise.png'), var(--gray-9-70); + -webkit-backdrop-filter: blur(40px); + backdrop-filter: blur(40px); + background-repeat: repeat; color: white; align-items: center; @@ -266,6 +269,24 @@ input[type=text], input[type=url] { flex: 1; } +.fw { + width: 100%; +} + +.fh { + height: 100%; +} + +.hw { + width: 50%; +} + +img.fw { + width: 50%; + max-width: 25vw; + object-fit: contain; +} + img.rounded { border-radius: 1rem; } @@ -281,12 +302,25 @@ img.appicon-settings { .subtitle { font-size: 0.9rem; opacity: 0.7; + z-index: -1; } .justify-center { justify-content: center; } +.align-center { + align-items: center; +} + +.align-space-between { + justify-content: space-between; +} + +.text-align-center { + text-align: center; +} + .chip.song > img { height: 128px; object-fit: contain; @@ -295,3 +329,30 @@ img.appicon-settings { #current-song-art { height: 200px; } + +#next-song-art { + height: 180px; +} + +#fullscreen { + position: absolute; + top: 0; + left: 0; + + z-index: 100; + + background: var(--gray-9); + + margin: 0; + padding: 1rem; + width: 100%; + height: 100%; + +} + +@media (prefers-color-scheme: light) { + input[type=text], input[type=url] { + background: var(--gray-1); + color: var(--gray-9); + } +} diff --git a/forge.config.js b/forge.config.js index 483d2d42ada31678a11b0eee1e7108e62b8fe1a8..0049582a394f43f5f10e5f13cbcb0b87d668417c 100644 --- a/forge.config.js +++ b/forge.config.js @@ -31,5 +31,6 @@ module.exports = { } } ], - plugins: [], + plugins: [ + ], }; diff --git a/index.html b/index.html index 1c929231b28901b011d927845439158f0c0b92b0..c777cc6c19bbf9b8bbb44c5f879d221e6471c808 100644 --- a/index.html +++ b/index.html @@ -147,10 +147,17 @@ <div class="stack v"> <div class="chip song nowplaying stack h"> <img src="img/radio-icon.png" alt="No album art" class="rounded" id="current-song-art"> - <div class="stack v nogap justify-center"> + <div class="stack v fw nogap justify-center"> <span class="subtitle">Now playing:</span> <span id="current-song-title" class="title">Loading...</span> <span id="current-song-artist" class="artist">Loading...</span> + <div class="stack hw npgap v"> + <progress id="progress"></progress> + <div class="stack fw nogap align-space-between h"> + <span id="elapsed">0:00</span> + <span id="duration">0:00</span> + </div> + </div> </div> </div> @@ -165,6 +172,10 @@ </div> </div> + <h2 style="margin: 0.5rem 0">History</h2> + <div id="history" class="stack v"> + No history :( + </div> </section> @@ -187,6 +198,30 @@ </section> + <section id="fullscreen" hidden="hidden" aria-hidden="true" > + <div class="stack spacer fh align-center"> + <div class="stack v align-center spacer"> + <img src="img/radio-icon.png" alt="No album art" class="rounded fw" id="full-current-song-art"> + <div class="stack v fw nogap align-center text-align-center"> + <span class="subtitle">Now playing:</span> + <span id="full-current-song-title" class="title">Loading...</span> + <span id="full-current-song-artist" class="artist">Loading...</span> + <div class="stack hw npgap v"> + <div class="stack fw nogap align-space-between h"> + <span id="full-elapsed">0:00</span> + <span id="full-duration">0:00</span> + </div> + <progress id="full-progress"></progress> + </div> + </div> + </div> + + <div class="spacer"></div> + + </div> + + </section> + </main> diff --git a/js/app.js b/js/app.js index bea1f6bd42600cba92bbd17697e946acb6ead886..4d56621f9a219093ac5979e5d10ce86e91c8cb01 100644 --- a/js/app.js +++ b/js/app.js @@ -5,9 +5,16 @@ const player = document.getElementById("player"); const audioSource = player.children.item(0); console.debug(audioSource); -const currentSongTitle = document.getElementById("current-song-title"); -const currentSongArtist = document.getElementById("current-song-artist"); -const currentSongArt = document.getElementById("current-song-art"); +let currentSongTitle = document.getElementById("current-song-title"); +let currentSongArtist = document.getElementById("current-song-artist"); +let currentSongArt = document.getElementById("current-song-art"); +let currentSongElapsed = document.getElementById("elapsed"); +let currentSongDuration = document.getElementById("duration"); +let currentSongProgress = document.getElementById("progress") + +const nextSongTitle = document.getElementById("next-song-title"); +const nextSongArtist = document.getElementById("next-song-artist"); +const nextSongArt = document.getElementById("next-song-art"); // Important init stuff @@ -18,6 +25,7 @@ let currentStation = null; runningInElectron = !!window.metadata; + if (runningInElectron) { console.error("Running under electron.") } @@ -105,7 +113,6 @@ function togglePlay() { if (player.paused) { audioSource.src += "&nocache=" + Math.random(); player.load(); - player.play(); setPlayPauseButtonIcon("playing") } else { player.pause(); @@ -115,11 +122,23 @@ function togglePlay() { playPauseButton.addEventListener("click", togglePlay); +player.addEventListener("loadeddata", _ =>{ + console.debug("Loading started.") + player.play() +}); + +function convertSecondsToMinutes(secs) { + const minutes = Math.floor(secs / 60); + const seconds = secs - minutes * 60; + return `${minutes}:${seconds}` +} // The following code handles giving browsers and operating systems media information -async function updateMetadata(title="", artist="", album="radio waves", artUrl="https://radio.byemc.xyz/assets/icon-300.png", startTime=Date.now(), duration=0, playingStatus="Stopped", id = Math.floor(Math.random() * 1000)) { +async function updateMetadata(title="", artist="", album="radio waves", artUrl="https://radio.byemc.xyz/assets/icon-300.png", startTime=Date.now(), duration=0, playingStatus="Stopped", elapsed=0, id = Math.floor(Math.random() * 1000)) { // Double-check the inputs to make sure they arent undefined + console.debug("Elapsed: ", elapsed) + if (!album) album = "radio waves"; if (!artUrl) artUrl = "https://radio.byemc.xyz/assets/icon-300.png"; if (!startTime) startTime = Date.now(); @@ -142,11 +161,29 @@ async function updateMetadata(title="", artist="", album="radio waves", artUrl=" playingStatus = playingStatus.toLowerCase() !== "playing" ? "paused" : "playing"; // or this navigator.mediaSession.playbackState = playingStatus; - navigator.mediaSession.setPositionState({ - duration: duration, - position: Math.round((Date.now() - startTime) / 1000), - playbackRate: 1 - }) + + currentSongDuration.innerText = `${convertSecondsToMinutes(duration)}` + currentSongProgress.max = (duration * 1000); + currentSongProgress.dataset.duration = duration; + currentSongProgress.dataset.startTime = startTime; + currentSongProgress.value = Date.now() - startTime + + if (elapsed) { + navigator.mediaSession.setPositionState({ + duration: duration, + position: elapsed, + playbackRate: 1 + }) + currentSongProgress.dataset.offset = Math.round(elapsed - ((Date.now() - startTime) / 1000)) + currentSongElapsed.innerText = `${convertSecondsToMinutes(elapsed)}`; + } else { + navigator.mediaSession.setPositionState({ + duration: duration, + position: Math.min(Math.round((Date.now() - startTime) / 1000), duration), + playbackRate: 1 + }) + currentSongElapsed.innerText = `${convertSecondsToMinutes(Math.min(Math.round((Date.now() - startTime) / 1000), duration))}`; + } } } @@ -216,7 +253,6 @@ function tuneRadio(station={url:"assets/audio/noise.wav"}) { audioSource.src = station.url + "?nocache=" + Math.random(); currentStation = station; player.load(); - player.play(); location.hash = "#station"; } @@ -299,6 +335,7 @@ const views = { "#add_station": document.getElementById("add_station"), "#add_station_from_azuracast": document.getElementById("add_station_from_azuracast"), "#station": document.getElementById("station"), + "#fullscreen": document.getElementById("fullscreen"), "#settings": document.getElementById("settings"), }; @@ -312,6 +349,13 @@ const viewFunctions = { // These run when a view is loaded. return; } + currentSongTitle = document.getElementById("current-song-title"); + currentSongArtist = document.getElementById("current-song-artist"); + currentSongArt = document.getElementById("current-song-art"); + currentSongElapsed = document.getElementById("elapsed"); + currentSongDuration = document.getElementById("duration"); + currentSongProgress = document.getElementById("progress"); + if (currentStation.metadata_type === "azuracast") { let request = await fetch(currentStation.azuracast_server_url + `/api/station/${currentStation.azuracast_station_shortcode}`); let json = await request.json(); @@ -321,6 +365,16 @@ const viewFunctions = { // These run when a view is loaded. } else { stationThingy.innerHTML = "Yeah this is icecast, metadata coming soon." } + }, + "#fullscreen": async function () { + currentSongTitle = document.getElementById("full-current-song-title"); + currentSongArtist = document.getElementById("full-current-song-artist"); + currentSongArt = document.getElementById("full-current-song-art"); + currentSongElapsed = document.getElementById("full-elapsed"); + currentSongDuration = document.getElementById("full-duration"); + currentSongProgress = document.getElementById("full-progress"); + + await updateLoop(); } } @@ -374,7 +428,7 @@ function updateView() { // Set an interval to poll the station for metadata async function getCurrentSongInfoFromAzuracastStation(server, shortcode) { - let response = await (await fetch(server + "/api/nowplaying/" + shortcode)).json(); + let response = await (await fetch(server + "/api/nowplaying/" + shortcode + "?nocache=" + Math.random())).json(); let album; if (response.now_playing.song.album) { @@ -383,10 +437,47 @@ async function getCurrentSongInfoFromAzuracastStation(server, shortcode) { album = response.station.name; } - document.getElementById("live_listeners").innerText = response.listeners.unique; + try { + nextSongTitle.innerText = response.playing_next.song.title; + nextSongArtist.innerText = response.playing_next.song.artist; + nextSongArt.src = response.playing_next.song.art; + + + document.getElementById("live_listeners").innerText = response.listeners.unique; + const history = document.getElementById("history"); + history.innerText = ""; + for (let song of response.song_history) { + const div = document.createElement("div"); + div.classList = "chip compact song stack h"; + const img = document.createElement("img"); + img.classList = "rounded" + img.src = song.song.art; + const stack = document.createElement("div"); + stack.classList = "stack v nogap justify-center"; + + //Strings + const title = document.createElement("span"); + const artist = document.createElement("span"); + const timeSince = document.createElement("span"); + + title.innerText = song.song.title; + artist.innerText = song.song.artist; + timeSince.innerText = "" + + stack.append(title, artist, timeSince); + + div.appendChild(img); + div.appendChild(stack); + history.appendChild(div) + } + } catch (e) { + console.error(e) + } + return {title: response.now_playing.song.title, artist: response.now_playing.song.artist, album: album, - art: response.now_playing.song.art, startTime: response.now_playing.played_at * 1000, duration: response.now_playing.duration} + art: response.now_playing.song.art, startTime: response.now_playing.played_at * 1000, duration: response.now_playing.duration, + elapsed: response.now_playing.elapsed} } async function getCurrentSongInfoFromIceCastStation(streamUrl) { @@ -406,20 +497,34 @@ async function updateLoop() { } const isPlaying = player.paused ? "paused" : "playing"; - await updateMetadata(metadata.title, metadata.artist, metadata.album, metadata.art, metadata.startTime, metadata.duration, isPlaying); + await updateMetadata(metadata.title, metadata.artist, metadata.album, metadata.art, metadata.startTime, metadata.duration, isPlaying, metadata.elapsed); currentSongTitle.innerText = metadata.title; currentSongArtist.innerText = metadata.artist; currentSongArt.src = metadata.art; } -setInterval(updateLoop, 3000) +setInterval(updateLoop, 3000); + +async function updateProgressBars() { + try { + currentSongProgress.value = Date.now() - currentSongProgress.dataset.startTime; + + } catch (e) { + currentSongProgress.value = 0 + } + currentSongElapsed.innerText = convertSecondsToMinutes(Math.round(((Date.now() - currentSongProgress.dataset.startTime) / 1000)) + Number(currentSongProgress.dataset.offset)) +} + +setInterval(updateProgressBars, 500); // Give user feedback on buffering player.addEventListener('playing', function() { console.log('Playback started.'); - noise.pause(); + if (noise !== null) { + noise.pause(); + } noise = null; setPlayPauseButtonIcon("playing"); updateLoop(); @@ -435,7 +540,11 @@ player.addEventListener('pause', function() { player.addEventListener('waiting', function() { console.log("Buffering..."); noise = new Audio("assets/audio/noise.wav"); - noise.play(); + try { + noise.play(); + } catch (e) { + console.error(e); + } noise.volume = player.volume / 4; noise.loop = true; setPlayPauseButtonIcon("buffer"); diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/webpack.config.prod.js b/webpack.config.prod.js index adc763e33a47f6348dc5df0f9a3ad2bdc2549083..9cad3db7909ed09a192d409efd98641f5006e21c 100644 --- a/webpack.config.prod.js +++ b/webpack.config.prod.js @@ -14,11 +14,12 @@ module.exports = merge(common, { { from: 'img', to: 'img' }, { from: 'css', to: 'css' }, { from: 'js/vendor', to: 'js/vendor' }, + { from: 'assets', to: 'assets' }, { from: 'icon.svg', to: 'icon.svg' }, { from: 'favicon.ico', to: 'favicon.ico' }, { from: 'robots.txt', to: 'robots.txt' }, { from: '404.html', to: '404.html' }, - { from: 'site.webmanifest', to: 'site.webmanifest' }, + { from: 'manifest.json', to: 'manifest.json' }, ], }), ],