diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml index d23208fbb7168875464dd7aaff4169c37be615ea..7fd062d4fa102bf5190a0df0dcbc71d6590f0810 100644 --- a/.idea/jsLibraryMappings.xml +++ b/.idea/jsLibraryMappings.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="JavaScriptLibraryMappings"> - <includedPredefinedLibrary name="Node.js Core" /> + <file url="PROJECT" libraries="{script}" /> </component> </project> \ No newline at end of file diff --git a/.idea/radio.iml b/.idea/radio.iml index 24643cc37449b4bde54411a80b8ed61258225e34..a73c701858a48797c394de8e91b146391dd0ac02 100644 --- a/.idea/radio.iml +++ b/.idea/radio.iml @@ -8,5 +8,6 @@ </content> <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" name="script" level="application" /> </component> </module> \ No newline at end of file diff --git a/index.html b/index.html index 5e3285961cf46a3f10f6e8e408d76ed661e28157..09cff5ffc2dd2b1b71bebc94d8db9e50fb1d29c9 100644 --- a/index.html +++ b/index.html @@ -14,12 +14,13 @@ <link rel="icon" href="/favicon.ico" sizes="any"> <link rel="icon" href="/icon.svg" type="image/svg+xml"> - <link rel="manifest" href="site.webmanifest"> + <link rel="manifest" href="manifest.json"> <meta name="theme-color" content="#77B19D"> <script defer src="assets/font-awesome/js/solid.js"></script> <script defer src="assets/font-awesome/js/fontawesome.js"></script> + <script defer data-domain="radio.byemc.xyz" src="https://analytics.byecorps.com/js/script.js"></script> </head> diff --git a/js/app.js b/js/app.js index eece690dc2163115b9e9530ec2ecb090dba4a1d4..964d6b8f4ff07627a9fcc409fe8fbc118a415179 100644 --- a/js/app.js +++ b/js/app.js @@ -7,9 +7,15 @@ console.debug(audioSource); // Important init stuff -audioSource.src = "/assets/audio/noise.wav"; +audioSource.src = "assets/audio/noise.wav"; let currentStation = null; +runningInElectron = !!window.metadata; + +if (runningInElectron) { + console.error("Running under electron.") +} + // Play/Pause button const playPauseButton = document.getElementById("playpause"); @@ -116,13 +122,67 @@ player.addEventListener('pause', function() { player.addEventListener('waiting', function() { console.log("Buffering..."); - noise = new Audio("/assets/audio/noise.wav"); + noise = new Audio("assets/audio/noise.wav"); noise.play(); noise.volume = 0.4; noise.loop = true; setPlayPauseButtonIcon("buffer"); }); +// 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)) { + // Double-check the inputs to make sure they arent undefined + if (!album) album = "radio waves"; + if (!artUrl) artUrl = "https://radio.byemc.xyz/assets/icon-300.png"; + if (!startTime) startTime = Date.now(); + if (!duration) duration = 0; + if (!playingStatus) playingStatus = "Stopped"; + if (!id) id = Math.floor(Math.random() * 1000); + + // First the MediaMetadata API (covers every current browser [Chrome, Firefox and __ESPECIALLY__ iOS Safari]) + if ("mediaSession" in navigator) { + navigator.mediaSession.metadata = new MediaMetadata({ + title, + artist, + album, + artwork: [ + { + src: artUrl + } + ] + }) + + console.log({ + duration: duration, + position: Math.round((Date.now() - startTime) / 1000), + playbackRate: 1 + }) + + playingStatus = playingStatus !== "Playing" ? "paused" : "playing"; + navigator.mediaSession.playbackState = playingStatus; + navigator.mediaSession.setPositionState({ + duration: duration, + position: Math.round((Date.now() - startTime) / 1000), + playbackRate: 1 + }) + + console.debug(navigator.mediaSession.playbackState); + } +} + +const actionHandlers = [ + ['play', togglePlay], + ['pause', togglePlay], +]; + +for (const [action, handler] of actionHandlers) { + try { + navigator.mediaSession.setActionHandler(action, handler); + } catch (error) { + console.log(`The media session action "${action}" is not supported yet.`); + } +} + // Read saved variables from localStorage function getLocalStorageItem (item, fallback = null) { if (localStorage.getItem(item)) { @@ -169,7 +229,7 @@ function removeStation(station) { renderStationListInTuner(); } -function tuneRadio(station={url:"/assets/audio/noise.wav"}) { +function tuneRadio(station={url:"assets/audio/noise.wav"}) { player.pause(); console.log(station) audioSource.src = station.url; @@ -251,7 +311,6 @@ document.getElementById("add_azcast_search").addEventListener("click", _=>{ - // The following deals with routing between the different Views. const views = { @@ -269,7 +328,6 @@ const viewFunctions = { // These run when a view is loaded. if (!currentStation) { stationThingy.innerHTML = `<span class="fa-fw fa-solid fa-warning"></span> Not tuned.`; - alert("You need to tune the radio first."); location.hash = "#tuner"; return; } @@ -280,12 +338,11 @@ const viewFunctions = { // These run when a view is loaded. stationThingy.innerHTML = ` <h2>${json.name}</h2> - <code>${json.shortcode}</code> + <div class="info"><code>${json.shortcode}</code> / <span class="fa-fw fa-solid fa-people"></span> <span id="live_listeners"></span></div> <p>${json.description}</p>` } else { stationThingy.innerHTML = "Yeah this is icecast, metadata coming soon." } - } } @@ -336,5 +393,43 @@ function updateView() { selectedView.ariaHidden = "false"; } +// 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 album; + if (response.now_playing.song.album) { + album = response.now_playing.song.album; + } else { + album = response.station.name; + } + + document.getElementById("live_listeners").innerText = response.listeners.unique; + + 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} +} + +async function getCurrentSongInfoFromIceCastStation(streamUrl) { + return; +} + +setInterval(async function () { + let metadata = {} + if (!currentStation) { + await updateMetadata("Not tuned", "radio waves") + return; + } + if (currentStation.metadata_type === "azuracast") { + metadata = await getCurrentSongInfoFromAzuracastStation(currentStation.azuracast_server_url, currentStation.azuracast_station_shortcode); + } else { + metadata = await getCurrentSongInfoFromIceCastStation(currentStation.url); + } + + await updateMetadata(metadata.title, metadata.artist, metadata.album, metadata.art, metadata.startTime, metadata.duration, "Playing"); +}, 3000) + + window.addEventListener("hashchange", updateView); updateView(); diff --git a/main.js b/main.js index 817c284dd9f18f6db34e7240de72c01391c0ac41..a7d3a90731c7c7f58bac8433117448c987dbe7dd 100644 --- a/main.js +++ b/main.js @@ -1,33 +1,38 @@ const { app, BrowserWindow, ipcMain, Notification } = require("electron"); const path = require('path'); -const Player = require('mpris-service'); +const Player = require("mpris-service"); -const player = Player({ - name: "pirateradio", - identity: "Radio Player", - supportedInterfaces: ['player'] -}); +if (process.platform === "linux"){ + const Player = require('mpris-service'); + const player = Player({ + name: "waves", + identity: "radio waves", + supportedInterfaces: ['player'] + }); -player.canSeek = false; -player.canGoNext = false; -player.canGoPrevious = false; + player.canSeek = false; + player.canGoNext = false; + player.canGoPrevious = false; +} function updatePlayer (event, args) { console.log(args) - player.metadata = { - 'mpris:trackid': player.objectPath('track/') + args.id, - 'mpris:length': args.duration * 1000 * 1000, - 'mpris:artUrl': args.artUrl, - 'xesam:title': args.title, - 'xesam:album': args.album, - 'xesam:artist': [args.artist] - }; - - player.getPosition = function () { - return (Date.now() - args.startTime) * 1000 - } + if (process.platform === "linux") { + player.metadata = { + 'mpris:trackid': player.objectPath('track/') + args.id, + 'mpris:length': args.duration * 1000 * 1000, + 'mpris:artUrl': args.artUrl, + 'xesam:title': args.title, + 'xesam:album': args.album, + 'xesam:artist': [args.artist] + }; + + player.getPosition = function () { + return (Date.now() - args.startTime) * 1000 + } - player.playbackStatus = args.playingStatus; + player.playbackStatus = args.playingStatus; + } } @@ -55,6 +60,8 @@ app.whenReady().then(() => { }) }) -player.on('quit', function () { - app.exit(); -}); +if (process.platform === "linux") { + player.on('quit', function () { + app.exit(); + }); +} diff --git a/package-lock.json b/package-lock.json index 93a0d9eacf4b279761c9168a5f47a477fbde4ac3..7b7d4d4d6f23802acb231a3d620b123ef8880fc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,11 @@ "version": "3.0.0", "dependencies": { "@fortawesome/fontawesome-free": "^6.5.1", - "livereload": "^0.9.3", "mpris-service": "^2.1.2" }, "devDependencies": { "copy-webpack-plugin": "^11.0.0", - "electron": "^28.0.0", + "electron": "^28.1.0", "html-webpack-plugin": "^5.5.3", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", @@ -789,6 +788,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -819,6 +819,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, "engines": { "node": ">=8" } @@ -904,6 +905,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -1055,6 +1057,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, "funding": [ { "type": "individual", @@ -1081,6 +1084,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -1581,9 +1585,9 @@ "dev": true }, "node_modules/electron": { - "version": "28.0.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-28.0.0.tgz", - "integrity": "sha512-eDhnCFBvG0PGFVEpNIEdBvyuGUBsFdlokd+CtuCe2ER3P+17qxaRfWRxMmksCOKgDHb5Wif5UxqOkZSlA4snlw==", + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-28.1.0.tgz", + "integrity": "sha512-82Y7o4PSWPn1o/aVwYPsgmBw6Gyf2lVHpaBu3Ef8LrLWXxytg7ZRZr/RtDqEMOzQp3+mcuy3huH84MyjdmP50Q==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2014,6 +2018,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2134,6 +2139,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -2732,6 +2738,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -2784,6 +2791,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -2792,6 +2800,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -2803,6 +2812,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "engines": { "node": ">=0.12.0" } @@ -2972,48 +2982,6 @@ "shell-quote": "^1.8.1" } }, - "node_modules/livereload": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.3.tgz", - "integrity": "sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==", - "dependencies": { - "chokidar": "^3.5.0", - "livereload-js": "^3.3.1", - "opts": ">= 1.2.0", - "ws": "^7.4.3" - }, - "bin": { - "livereload": "bin/livereload.js" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/livereload-js": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.4.1.tgz", - "integrity": "sha512-5MP0uUeVCec89ZbNOT/i97Mc+q3SxXmiUGhRFOTmhrGPn//uWVQdCvcLJDy64MSBR5MidFdOR7B9viumoavy6g==" - }, - "node_modules/livereload/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -3307,6 +3275,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -3447,11 +3416,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/opts": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/opts/-/opts-2.0.2.tgz", - "integrity": "sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg==" - }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -3611,6 +3575,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "engines": { "node": ">=8.6" }, @@ -3803,6 +3768,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -4692,6 +4658,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "dependencies": { "is-number": "^7.0.0" }, diff --git a/package.json b/package.json index 4ef2e3e6c41fc6ff87ab3c9b8bae36775755e27c..645c8991ceb2a2246faab10ff3713a0f37d8a30b 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,12 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "live-server", - "electron_start": "electron .", + "electron_start": "DBUS_SESSION_BUS_ADDRESS=\"autolaunch\" electron .", "build": "webpack --config webpack.config.prod.js" }, "devDependencies": { "copy-webpack-plugin": "^11.0.0", - "electron": "^28.0.0", + "electron": "^28.1.0", "html-webpack-plugin": "^5.5.3", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", diff --git a/site.webmanifest b/site.webmanifest deleted file mode 100644 index 2aa5c9e4b44ca50b5b6513e67aaf1014a3cd2bf1..0000000000000000000000000000000000000000 --- a/site.webmanifest +++ /dev/null @@ -1,8 +0,0 @@ -{ - "short_name": "waves", - "name": "radio waves", - "id": "xyz.byemc.waves", - "start_url": "/?utm_source=homescreen", - "background_color": "#1f1f1f", - "theme_color": "#77B19D" -}