Fork!
20
LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2017, Michael Walker <mike@barrucadu.co.uk>
|
||||
Copyright (c) 2022, hosma <hosma@protonmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
17
README.md
Normal file
@ -0,0 +1,17 @@
|
||||
# fauuxon.life
|
||||
|
||||
An alternative, [fauux](https://fauux.neocities.org)-inspired interface for the [lainon.life](https://lainon.life) web radio.
|
||||
|
||||
**NOTE:** lainon.life does not allow CORS, ~~so you will need to somehow enforce it yourself~~. Currently, a fork of [cors-anywhere](https://github.com/Rob--W/cors-anywhere) is being used to proxy the requests. If fetching the necessary data fails, a warning will be displayed.
|
||||
|
||||
![Screenshot](./screenshots/Screenshot.png)
|
||||
|
||||
## Credit
|
||||
|
||||
- Radio, API: **Michael Walker** (barrucadu)
|
||||
- [GitHub](https://github.com/barrucadu/lainonlife)
|
||||
- [Website](https://www.barrucadu.co.uk)
|
||||
- [PayPal](https://www.paypal.com/paypalme/barrucadu)
|
||||
|
||||
- Backgrounds, color scheme: **fauux**
|
||||
- [Website](https://fauux.neocities.org)
|
105
index.html
Normal file
@ -0,0 +1,105 @@
|
||||
<!DOCTYPE html><html lang="en"><head>
|
||||
|
||||
<!-- This website is not affiliated with either lainon.life or fauux. -->
|
||||
<!-- https://github.com/debil03311/fauuxonlife -->
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>fauuxon.life</title>
|
||||
<link rel="shortcut icon" href="./public/asset/favicon.ico" type="image/x-icon">
|
||||
|
||||
<!-- Regular -->
|
||||
<meta name="theme-color" content="#d2738a">
|
||||
<meta name="image" content="https://debil03311.github.io/fauuxonlife/public/asset/embed.png">
|
||||
<meta name="title" content="lainon.life interface">
|
||||
<meta name="description" content="fauux-inspired, unaffiliated, third party interface for the lainon.life web radio">
|
||||
|
||||
<!-- OpenGraph -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://debil03311.github.io/fauuxonlife/public/asset/embed.png">
|
||||
<meta property="og:site_name" content="fauuxon.life">
|
||||
<meta property="og:title" content="lainon.life interface">
|
||||
<meta property="og:description" content="fauux-inspired, unaffiliated, third party interface for the lainon.life web radio">
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary">
|
||||
<meta property="twitter:image" content="https://debil03311.github.io/fauuxonlife/public/asset/embed.png">
|
||||
<meta property="twitter:title" content="lainon.life interface">
|
||||
<meta property="twitter:description" content="fauux-inspired, unaffiliated, third party interface for the lainon.life web radio">
|
||||
|
||||
<link rel="stylesheet" href="./public/asset/icofont/icofont.min.css">
|
||||
<link rel="stylesheet" href="./public/css/index.css">
|
||||
<link rel="stylesheet" href="./public/css/index.m.css">
|
||||
</head><body>
|
||||
<div id="darken" class="overlay no-select"></div>
|
||||
<div id="scanlines" class="overlay no-select"></div>
|
||||
<div id="vignette" class="overlay no-select"></div>
|
||||
|
||||
<div id="warning-message" class="overlay hidden" onclick="this.classList.add('hidden')">
|
||||
<div id="warning-content">
|
||||
<h2>Something went wrong.</h2>
|
||||
|
||||
<p>
|
||||
Failed to fetch the necessary data. Check to see if <a href="https://lainon.life">lainon.life</a> is down.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If it's not down then there's an issue with CORS, but you can probably it yourself! All you have to do is get an extension/add-on for your browser that enables CORS anywhere. If you're Chromium-based, check <a href="https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf">this one</a> out. If you're on FireFox, I have no idea, maybe <a href="https://addons.mozilla.org/en-US/firefox/addon/cors-everywhere/">this one</a>? Do keep in mind that enforcing CORS at all times could break some other sites you may frequent.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<audio id="audio" class="hidden"
|
||||
preload="none"
|
||||
src="https://lainon.life/radio/cafe.ogg"
|
||||
></audio>
|
||||
|
||||
<header>
|
||||
fauux(<a href="https://lainon.life/">lainon.life</a>): [<span class="listeners current" title="Listening">0</span>, <span class="listeners unique" title="UNIQUYE">0</span>, <span class="listeners total" title="Peak Listeners">0</span>]
|
||||
</header>
|
||||
|
||||
<section id="player">
|
||||
<div id="progress-bar"
|
||||
style="width: 0%"
|
||||
class="no-select">
|
||||
</div>
|
||||
|
||||
<div id="volume-bar"
|
||||
style="width: 20%"
|
||||
class="no-select">
|
||||
</div>
|
||||
|
||||
<div id="current-details" class="no-select">
|
||||
<span id="current-song">
|
||||
<span id="current-title">NOT PLAYING</span>
|
||||
<span id="current-playlist">NULL</span>
|
||||
</span>
|
||||
|
||||
|
||||
<span id="current-artist">NULL</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="channels">
|
||||
<i id="swing"
|
||||
title="swing"
|
||||
class="channel icofont-ui-play"
|
||||
onclick="switchChannel(this.id)"
|
||||
></i>
|
||||
</section>
|
||||
|
||||
<section id="song-list">
|
||||
<div id="list-previous"></div>
|
||||
|
||||
<div id="list-current">
|
||||
Select one of the channels above to start listening.<br>
|
||||
This project is <a href="https://github.com/debil03311/fauuxonlife">open source</a> and unaffiliated with either lainon.life or fauux.
|
||||
</div>
|
||||
|
||||
<div id="list-next"></div>
|
||||
</section>
|
||||
|
||||
<script defer src="./public/js/index.js"></script>
|
||||
</body></html>
|
BIN
public/asset/bg205.gif
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
public/asset/bg_307.gif
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
public/asset/bg_315.gif
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
public/asset/embed.png
Normal file
After Width: | Height: | Size: 318 KiB |
BIN
public/asset/favicon.ico
Normal file
After Width: | Height: | Size: 198 B |
BIN
public/asset/icofont/fonts/icofont.eot
Normal file
2105
public/asset/icofont/fonts/icofont.svg
Normal file
After Width: | Height: | Size: 2.3 MiB |
BIN
public/asset/icofont/fonts/icofont.ttf
Normal file
BIN
public/asset/icofont/fonts/icofont.woff
Normal file
BIN
public/asset/icofont/fonts/icofont.woff2
Normal file
10757
public/asset/icofont/icofont.css
Normal file
7
public/asset/icofont/icofont.min.css
vendored
Normal file
BIN
public/asset/patternTV2.gif
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
public/asset/scanlines.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
255
public/css/index.css
Normal file
@ -0,0 +1,255 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Zen+Maru+Gothic:wght@400;500;700&display=swap');
|
||||
|
||||
:root {
|
||||
--pink: #d2738a;
|
||||
--pale: #c1b492;
|
||||
--bgcolor: #1e1b1e;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: var(--pink);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
--size: 8px;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
background-color: var(--bgcolor);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background-color: var(--bgcolor);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--pink);
|
||||
box-shadow: inset 0 0 0 1px var(--bgcolor);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.no-select {
|
||||
pointer-events: none !important;
|
||||
user-select: none !important;
|
||||
-moz-user-select: none !important;
|
||||
-webkit-user-select: none !important;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
background: #000 url("../asset/bg205.gif");
|
||||
color: var(--pale);
|
||||
font: 500 1em "Share Tech Mono", "Zen Maru Gothic", monospace;
|
||||
line-height: 1.4em;
|
||||
text-shadow: 0 0 10px #c1b49264;
|
||||
margin: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a,
|
||||
.listeners
|
||||
{
|
||||
color: var(--pink);
|
||||
text-decoration: none;
|
||||
text-shadow: 0 0 10px #d2738a64;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#darken {
|
||||
background-color: #0006;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#scanlines {
|
||||
background: url("../asset/scanlines.png");
|
||||
opacity: .6;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#vignette {
|
||||
box-shadow: inset 0 0 64px #000;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#warning-message {
|
||||
background-color: #040004f4;
|
||||
backdrop-filter: blur(4px);
|
||||
padding: 2em;
|
||||
cursor: pointer;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#warning-message h1 {
|
||||
color: var(--pink);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#warning-content {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
header,
|
||||
section
|
||||
{
|
||||
width: clamp(256px, 896px, 90%);
|
||||
background-color: var(--bgcolor);
|
||||
box-shadow: 0 0 16px #d2738a32;
|
||||
padding: 1rem;
|
||||
margin: .8rem;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
font-size: 2.4rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
#player {
|
||||
position: relative;
|
||||
background-color: var(--pale);
|
||||
color: #000;
|
||||
text-shadow: 0 0 10px #0004;
|
||||
font-size: 1.4rem;
|
||||
cursor: e-resize;
|
||||
|
||||
/* display: flex;
|
||||
align-items: center; */
|
||||
}
|
||||
|
||||
#progress-bar {
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
height: 100%;
|
||||
background-color: var(--pink);
|
||||
}
|
||||
|
||||
/* #progress-bar::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 3px;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: var(--pale);
|
||||
} */
|
||||
|
||||
#volume-bar {
|
||||
position: absolute;
|
||||
height: 3px;
|
||||
top: 6px;
|
||||
left: 0;
|
||||
background-color: var(--pale);
|
||||
background-color: #000;
|
||||
box-shadow: 0 0 10px var(--pink);
|
||||
}
|
||||
|
||||
#current-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#current-details span {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#current-song {
|
||||
margin: 8px 0;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#current-title,
|
||||
#current-date
|
||||
{
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
#current-date {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* #current-date::before {
|
||||
content: "(";
|
||||
}
|
||||
|
||||
#current-date::after {
|
||||
content: ")";
|
||||
} */
|
||||
|
||||
#current-album::before {
|
||||
content: "from ";
|
||||
}
|
||||
|
||||
#current-artist::before {
|
||||
content: "by ";
|
||||
}
|
||||
|
||||
#song-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#list-previous {
|
||||
text-shadow: none;
|
||||
opacity: .6;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
#list-current {
|
||||
color: #DDD;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.song-entry {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#channels {
|
||||
font-size: 2rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.channel.active {
|
||||
color: #DDD;
|
||||
text-shadow: 0 0 32px var(--pink);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.channel:not(.active):hover {
|
||||
color: var(--pink);
|
||||
cursor: pointer;
|
||||
}
|
14
public/css/index.m.css
Normal file
@ -0,0 +1,14 @@
|
||||
@media only screen and (max-width: 800px) {
|
||||
header {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
#current-song {
|
||||
line-height: 1.4em;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#current-date {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
300
public/js/index.js
Normal file
@ -0,0 +1,300 @@
|
||||
// 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_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)) + "%"
|
||||
);
|
||||
|
||||
// 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 = `
|
||||
<div class="entry-details">
|
||||
<span>Next FLEX: </span>
|
||||
<span class="entry-artist">${song.artist}</span>
|
||||
-
|
||||
<span class="entry-title">${song.title}</span>
|
||||
</div>
|
||||
<span class="entry-length">${toMinSec(duration).join(":")}</span>
|
||||
`;
|
||||
|
||||
e_songList.next.appendChild(e_songEntry);
|
||||
|
||||
|
||||
// Display the current song in the song list
|
||||
e_songList.current.innerHTML = `
|
||||
<div class="song-entry">
|
||||
<div class="entry-details">
|
||||
<span>Current FLEX: </span>
|
||||
<span class="entry-artist">${radioData.now_playing.song.artist}</span>
|
||||
-
|
||||
<span class="entry-title">${radioData.now_playing.song.title}</span>
|
||||
</div>
|
||||
<span class="entry-length">${toMinSec(radioData.now_playing.duration).join(":")}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 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 = `
|
||||
<div class="entry-details">
|
||||
|
||||
<span class="entry-artist">${song.song.artist}</span>
|
||||
-
|
||||
<span class="entry-title">${song.song.title}</span>
|
||||
</div>
|
||||
<span class="entry-length">${toMinSec(song.duration).join(":")}</span>
|
||||
`;
|
||||
|
||||
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) {
|
||||
console.log(e_audio.paused,isPlaying)
|
||||
if (e_audio.paused === false) {
|
||||
document.getElementById(channelName).classList.remove("active");
|
||||
e_audio.pause()
|
||||
isPlaying = false
|
||||
currentChannel = "cafe"
|
||||
e_audio.src = undefined
|
||||
return
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
BIN
screenshots/Screenshot.png
Normal file
After Width: | Height: | Size: 242 KiB |