Add files via upload
This commit is contained in:
parent
69a3a80566
commit
fc40346e71
7 changed files with 1094 additions and 0 deletions
BIN
HelveticaNeueMedium.ttf
Normal file
BIN
HelveticaNeueMedium.ttf
Normal file
Binary file not shown.
544
index.html
Normal file
544
index.html
Normal file
|
|
@ -0,0 +1,544 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Music Choice</title>
|
||||
|
||||
<style>
|
||||
|
||||
* {
|
||||
cursor: none !important;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Helvetica Neue Medium';
|
||||
src: url('./HelveticaNeueMedium.ttf') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
*{
|
||||
margin:0;
|
||||
padding:0;
|
||||
box-sizing:border-box;
|
||||
font-family:Helvetica Neue Medium,sans-serif;
|
||||
}
|
||||
|
||||
body{
|
||||
background:#000;
|
||||
color:#fff;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
#screen{
|
||||
width:100vw;
|
||||
height:100vh;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
}
|
||||
|
||||
#top{
|
||||
flex:0;
|
||||
display:flex;
|
||||
}
|
||||
|
||||
#left{
|
||||
flex:2;
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
#left img{
|
||||
width:100%;
|
||||
height:100%;
|
||||
object-fit:cover;
|
||||
}
|
||||
|
||||
#right{
|
||||
width:34%;
|
||||
background:#111;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
}
|
||||
|
||||
.header{
|
||||
background:#a946b0;
|
||||
color:black;
|
||||
padding:10px 30px;
|
||||
font-weight:bold;
|
||||
text-transform:uppercase;
|
||||
font-size:23px;
|
||||
}
|
||||
|
||||
.fact-box{
|
||||
padding:15px;
|
||||
font-size:1.1rem;
|
||||
line-height:1.5;
|
||||
}
|
||||
|
||||
#bottom{
|
||||
background:#000;
|
||||
border-top:0px solid #a946b0;
|
||||
}
|
||||
|
||||
#genre{
|
||||
background:#a946b0;
|
||||
color:black;
|
||||
padding:0px 15px;
|
||||
font-weight:bold;
|
||||
text-transform:uppercase;
|
||||
font-size:2.0rem;
|
||||
}
|
||||
|
||||
#nowPlaying{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
padding:15px;
|
||||
gap:15px;
|
||||
}
|
||||
|
||||
.songInfo{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
}
|
||||
|
||||
.artist{
|
||||
font-size:1.2rem;
|
||||
}
|
||||
|
||||
.title{
|
||||
font-size:1.4rem;
|
||||
margin-top:5px;
|
||||
}
|
||||
|
||||
.channel{
|
||||
color:#999;
|
||||
margin-top:4px;
|
||||
}
|
||||
|
||||
#left {
|
||||
width: 1280px;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#slideImage {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: fill !important;
|
||||
object-position: fill;
|
||||
display: block;
|
||||
padding-right: 5px;
|
||||
background: #462233;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 150px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px;
|
||||
overflow: hidden;
|
||||
#border-radius: 10px; /* optional rounded corners */
|
||||
background: #462233;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
audio{
|
||||
display:none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="screen">
|
||||
|
||||
<div id="top">
|
||||
|
||||
<div id="left">
|
||||
<img id="slideImage"
|
||||
src="#"
|
||||
alt="">
|
||||
</div>
|
||||
|
||||
<div id="right">
|
||||
<div class="header">Did You Know?</div>
|
||||
|
||||
<div class="fact-box" id="factBox">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="bottom">
|
||||
|
||||
<div id="genre">Connecting...</div>
|
||||
|
||||
<div id="nowPlaying">
|
||||
|
||||
<div class="logo"><img src="logo.png" alt="Logo"></div>
|
||||
|
||||
<div class="songInfo">
|
||||
<div class="artist" id="artist">
|
||||
Loading...
|
||||
</div>
|
||||
|
||||
<div class="title" id="title">
|
||||
Connecting...
|
||||
</div>
|
||||
|
||||
<div class="channel" id="channel">
|
||||
Channel
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<audio id="player" controls autoplay></audio>
|
||||
|
||||
<script>
|
||||
const M3U_URL = "./venith.m3u";
|
||||
const API =
|
||||
"http://192.168.1.7:5000/api/nowplaying/veltron_radio";
|
||||
|
||||
/* -----------------------------
|
||||
NOW PLAYING
|
||||
------------------------------*/
|
||||
async function updateNowPlaying(){
|
||||
|
||||
try{
|
||||
|
||||
const res = await fetch(API);
|
||||
const data = await res.json();
|
||||
|
||||
const song = data.now_playing.song || {};
|
||||
const station = data.station || {};
|
||||
|
||||
// 🔥 Use text as primary source (most reliable in your setup)
|
||||
const rawText = song.text || "";
|
||||
|
||||
let artist = "";
|
||||
let title = rawText;
|
||||
|
||||
if(rawText.includes(" - ")){
|
||||
|
||||
const parts = rawText.split(" - ");
|
||||
|
||||
artist = parts[0].trim();
|
||||
title = parts.slice(1).join(" - ").trim();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// fallback cleanup
|
||||
if(!artist){
|
||||
artist = song.artist || "Unknown Artist";
|
||||
}
|
||||
|
||||
title = title.replace(/\[.*?\]/g, "").trim();
|
||||
|
||||
document.getElementById("artist").textContent =
|
||||
artist;
|
||||
|
||||
document.getElementById("title").textContent =
|
||||
title || "Unknown Track";
|
||||
|
||||
document.getElementById("channel").textContent =
|
||||
station.name || "Veltron Radio";
|
||||
|
||||
document.getElementById("genre").textContent =
|
||||
data.now_playing.playlist || "Music Channel";
|
||||
|
||||
if(song.art){
|
||||
document.getElementById("slideImage").src =
|
||||
"./musicchoice.png";
|
||||
}
|
||||
|
||||
}catch(err){
|
||||
|
||||
console.error("AzuraCast error:", err);
|
||||
|
||||
document.getElementById("artist").textContent =
|
||||
"Connection Error";
|
||||
|
||||
document.getElementById("title").textContent =
|
||||
"Cannot reach AzuraCast";
|
||||
|
||||
document.getElementById("channel").textContent =
|
||||
"Offline";
|
||||
}
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
START
|
||||
------------------------------*/
|
||||
updateNowPlaying();
|
||||
setInterval(updateNowPlaying, 15000);
|
||||
</script>
|
||||
<script>
|
||||
/*
|
||||
=========================================
|
||||
PARSE M3U
|
||||
=========================================
|
||||
*/
|
||||
async function loadM3U(){
|
||||
|
||||
const txt = await fetch(M3U_URL).then(r=>r.text());
|
||||
|
||||
const lines = txt.split("\n");
|
||||
|
||||
let name = "Music Channel";
|
||||
let streamUrl = "";
|
||||
|
||||
for(let i=0;i<lines.length;i++){
|
||||
|
||||
if(lines[i].startsWith("#EXTINF")){
|
||||
const parts = lines[i].split(",");
|
||||
if(parts[1]) name = parts[1].trim();
|
||||
}
|
||||
|
||||
if(
|
||||
lines[i].startsWith("http://") ||
|
||||
lines[i].startsWith("https://")
|
||||
){
|
||||
streamUrl = lines[i].trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("channel").textContent = name;
|
||||
|
||||
if(streamUrl){
|
||||
document.getElementById("player").src = streamUrl;
|
||||
startMetadataPolling(streamUrl);
|
||||
}
|
||||
}
|
||||
loadM3U();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
const artistEl = document.getElementById("artist");
|
||||
const factBox = document.getElementById("factBox");
|
||||
const songFactCache = new Map();
|
||||
let artistFacts = [];
|
||||
let factIndexArtist = 0;
|
||||
let factInterval = null;
|
||||
let factsLoaded = false;
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
GENERIC FALLBACK FACTS
|
||||
--------------------------------------------------------- */
|
||||
function genericFallbackFacts(name) {
|
||||
return [
|
||||
`${name} is part of the global music library. Information for this artist is limited.`,
|
||||
`Veltron Radio features music across many cultures and languages.`
|
||||
];
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
SONG → ARTIST DETECTION
|
||||
--------------------------------------------------------- */
|
||||
function detectArtistFromSong(title) {
|
||||
const lower = title.toLowerCase();
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
SOUNDTRACK / SCORE DETECTION
|
||||
--------------------------------------------------------- */
|
||||
function detectComposerFromTitle(title) {
|
||||
const lower = title.toLowerCase();
|
||||
|
||||
if (lower.includes("ost") || lower.includes("soundtrack") || lower.includes("score") || lower.includes("from")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
WIKIPEDIA LOOKUP (ARTIST-ONLY)
|
||||
--------------------------------------------------------- */
|
||||
async function getWikipediaSummary(name) {
|
||||
const languages = ["en"];
|
||||
const allowedTypes = ["bio", "artist", "musician", "music", "standard"];
|
||||
|
||||
for (let lang of languages) {
|
||||
const url = `https://${lang}.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(name)}`;
|
||||
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) continue;
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (!allowedTypes.includes(data.type)) continue;
|
||||
|
||||
if (data.type === "standard") {
|
||||
const text = (data.extract || "").toLowerCase();
|
||||
|
||||
const bandHints = [
|
||||
"band", "musical group", "singer", "rapper", "musician",
|
||||
"composer", "dj", "producer", "rock band", "punk band",
|
||||
"pop group", "duo", "trio", "game"
|
||||
];
|
||||
|
||||
const isBand = bandHints.some(h => text.includes(h));
|
||||
if (!isBand) continue;
|
||||
}
|
||||
|
||||
if (data.extract_html || data.extract) {
|
||||
return data.extract_html || data.extract;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
HTML → TEXT CLEANER
|
||||
--------------------------------------------------------- */
|
||||
function htmlToText(html) {
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = html;
|
||||
return div.innerText;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
FACT ROTATION
|
||||
--------------------------------------------------------- */
|
||||
function startArtistFactRotation() {
|
||||
if (factInterval) clearInterval(factInterval);
|
||||
|
||||
if (artistFacts.length === 0) {
|
||||
factBox.textContent = "No artist information available.";
|
||||
return;
|
||||
}
|
||||
|
||||
factIndexArtist = 0;
|
||||
factBox.textContent = artistFacts[0];
|
||||
|
||||
factInterval = setInterval(() => {
|
||||
factIndexArtist = (factIndexArtist + 1) % artistFacts.length;
|
||||
factBox.textContent = artistFacts[factIndexArtist];
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
MAIN FACT FETCHER
|
||||
--------------------------------------------------------- */
|
||||
async function updateFactBox() {
|
||||
const name = artistEl.textContent.trim();
|
||||
const title = document.getElementById("title").textContent.trim();
|
||||
|
||||
if (!name || name === "Loading..." || name === "Connecting...") return;
|
||||
|
||||
if (name === "Station Offline") {
|
||||
artistFacts = genericFallbackFacts("This track");
|
||||
startArtistFactRotation();
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ SONG-LEVEL CACHE CHECK
|
||||
if (songFactCache.has(title)) {
|
||||
artistFacts = songFactCache.get(title);
|
||||
factsLoaded = true;
|
||||
startArtistFactRotation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!factsLoaded) {
|
||||
factBox.textContent = "Loading artist info...";
|
||||
}
|
||||
|
||||
let lookupName = name;
|
||||
|
||||
if (name === "Unknown Artist" || name.trim() === "") {
|
||||
lookupName = title;
|
||||
}
|
||||
|
||||
const composer = detectComposerFromTitle(title);
|
||||
if (composer) lookupName = composer;
|
||||
|
||||
const detectedArtist = detectArtistFromSong(title);
|
||||
if (detectedArtist) lookupName = detectedArtist;
|
||||
|
||||
try {
|
||||
let summaryHTML = await getWikipediaSummary(lookupName);
|
||||
|
||||
if (!summaryHTML) {
|
||||
try {
|
||||
const searchURL =
|
||||
`https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(lookupName)}&format=json&origin=*`;
|
||||
|
||||
const searchRes = await fetch(searchURL);
|
||||
const searchData = await searchRes.json();
|
||||
|
||||
if (searchData.query.search.length > 0) {
|
||||
const bestMatch = searchData.query.search[0].title;
|
||||
summaryHTML = await getWikipediaSummary(bestMatch);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (!summaryHTML) {
|
||||
artistFacts = genericFallbackFacts(lookupName);
|
||||
} else {
|
||||
const clean = htmlToText(summaryHTML);
|
||||
artistFacts = clean
|
||||
.split(/\. |\.$/)
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 5)
|
||||
.slice(0, 4)
|
||||
.map(s => s + ".");
|
||||
}
|
||||
|
||||
// ✅ STORE IN SONG CACHE
|
||||
songFactCache.set(title, artistFacts);
|
||||
|
||||
factsLoaded = true;
|
||||
startArtistFactRotation();
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
artistFacts = genericFallbackFacts(name);
|
||||
startArtistFactRotation();
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
WATCH ARTIST FIELD FOR CHANGES
|
||||
--------------------------------------------------------- */
|
||||
const observer = new MutationObserver(updateFactBox);
|
||||
observer.observe(artistEl, { childList: true, subtree: true, characterData: true });
|
||||
|
||||
updateFactBox();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
544
index2.html
Normal file
544
index2.html
Normal file
|
|
@ -0,0 +1,544 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Music Choice</title>
|
||||
|
||||
<style>
|
||||
|
||||
* {
|
||||
cursor: none !important;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Helvetica Neue Medium';
|
||||
src: url('./HelveticaNeueMedium.ttf') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
*{
|
||||
margin:0;
|
||||
padding:0;
|
||||
box-sizing:border-box;
|
||||
font-family:Helvetica Neue Medium,sans-serif;
|
||||
}
|
||||
|
||||
body{
|
||||
background:#000;
|
||||
color:#fff;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
#screen{
|
||||
width:100vw;
|
||||
height:100vh;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
}
|
||||
|
||||
#top{
|
||||
flex:0;
|
||||
display:flex;
|
||||
}
|
||||
|
||||
#left{
|
||||
flex:2;
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
#left img{
|
||||
width:100%;
|
||||
height:100%;
|
||||
object-fit:cover;
|
||||
}
|
||||
|
||||
#right{
|
||||
width:34%;
|
||||
background:#111;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
}
|
||||
|
||||
.header{
|
||||
background:#a946b0;
|
||||
color:black;
|
||||
padding:10px 30px;
|
||||
font-weight:bold;
|
||||
text-transform:uppercase;
|
||||
font-size:23px;
|
||||
}
|
||||
|
||||
.fact-box{
|
||||
padding:15px;
|
||||
font-size:1.1rem;
|
||||
line-height:1.5;
|
||||
}
|
||||
|
||||
#bottom{
|
||||
background:#000;
|
||||
border-top:0px solid #a946b0;
|
||||
}
|
||||
|
||||
#genre{
|
||||
background:#a946b0;
|
||||
color:black;
|
||||
padding:0px 15px;
|
||||
font-weight:bold;
|
||||
text-transform:uppercase;
|
||||
font-size:2.0rem;
|
||||
}
|
||||
|
||||
#nowPlaying{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
padding:15px;
|
||||
gap:15px;
|
||||
}
|
||||
|
||||
.songInfo{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
}
|
||||
|
||||
.artist{
|
||||
font-size:1.2rem;
|
||||
}
|
||||
|
||||
.title{
|
||||
font-size:1.4rem;
|
||||
margin-top:5px;
|
||||
}
|
||||
|
||||
.channel{
|
||||
color:#999;
|
||||
margin-top:4px;
|
||||
}
|
||||
|
||||
#left {
|
||||
width: 1280px;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#slideImage {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: fill !important;
|
||||
object-position: fill;
|
||||
display: block;
|
||||
padding-right: 5px;
|
||||
background: #462233;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 150px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px;
|
||||
overflow: hidden;
|
||||
#border-radius: 10px; /* optional rounded corners */
|
||||
background: #462233;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
audio{
|
||||
display:none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="screen">
|
||||
|
||||
<div id="top">
|
||||
|
||||
<div id="left">
|
||||
<img id="slideImage"
|
||||
src="#"
|
||||
alt="">
|
||||
</div>
|
||||
|
||||
<div id="right">
|
||||
<div class="header">Did You Know?</div>
|
||||
|
||||
<div class="fact-box" id="factBox">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="bottom">
|
||||
|
||||
<div id="genre">Connecting...</div>
|
||||
|
||||
<div id="nowPlaying">
|
||||
|
||||
<div class="logo"><img src="logo.png" alt="Logo"></div>
|
||||
|
||||
<div class="songInfo">
|
||||
<div class="artist" id="artist">
|
||||
Loading...
|
||||
</div>
|
||||
|
||||
<div class="title" id="title">
|
||||
Connecting...
|
||||
</div>
|
||||
|
||||
<div class="channel" id="channel">
|
||||
Channel
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<audio id="player" controls autoplay></audio>
|
||||
|
||||
<script>
|
||||
const M3U_URL = "venith2.m3u";
|
||||
const API =
|
||||
"http://192.168.1.7:5000/api/nowplaying/veltron_radio_2";
|
||||
|
||||
/* -----------------------------
|
||||
NOW PLAYING
|
||||
------------------------------*/
|
||||
async function updateNowPlaying(){
|
||||
|
||||
try{
|
||||
|
||||
const res = await fetch(API);
|
||||
const data = await res.json();
|
||||
|
||||
const song = data.now_playing.song || {};
|
||||
const station = data.station || {};
|
||||
|
||||
// 🔥 Use text as primary source (most reliable in your setup)
|
||||
const rawText = song.text || "";
|
||||
|
||||
let artist = "";
|
||||
let title = rawText;
|
||||
|
||||
if(rawText.includes(" - ")){
|
||||
|
||||
const parts = rawText.split(" - ");
|
||||
|
||||
artist = parts[0].trim();
|
||||
title = parts.slice(1).join(" - ").trim();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// fallback cleanup
|
||||
if(!artist){
|
||||
artist = song.artist || "Unknown Artist";
|
||||
}
|
||||
|
||||
title = title.replace(/\[.*?\]/g, "").trim();
|
||||
|
||||
document.getElementById("artist").textContent =
|
||||
artist;
|
||||
|
||||
document.getElementById("title").textContent =
|
||||
title || "Unknown Track";
|
||||
|
||||
document.getElementById("channel").textContent =
|
||||
station.name || "Veltron Radio";
|
||||
|
||||
document.getElementById("genre").textContent =
|
||||
data.now_playing.playlist || "Music Channel";
|
||||
|
||||
if(song.art){
|
||||
document.getElementById("slideImage").src =
|
||||
"./musicchoice.png";
|
||||
}
|
||||
|
||||
}catch(err){
|
||||
|
||||
console.error("AzuraCast error:", err);
|
||||
|
||||
document.getElementById("artist").textContent =
|
||||
"Connection Error";
|
||||
|
||||
document.getElementById("title").textContent =
|
||||
"Cannot reach AzuraCast";
|
||||
|
||||
document.getElementById("channel").textContent =
|
||||
"Offline";
|
||||
}
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
START
|
||||
------------------------------*/
|
||||
updateNowPlaying();
|
||||
setInterval(updateNowPlaying, 15000);
|
||||
</script>
|
||||
<script>
|
||||
/*
|
||||
=========================================
|
||||
PARSE M3U
|
||||
=========================================
|
||||
*/
|
||||
async function loadM3U(){
|
||||
|
||||
const txt = await fetch(M3U_URL).then(r=>r.text());
|
||||
|
||||
const lines = txt.split("\n");
|
||||
|
||||
let name = "Music Channel";
|
||||
let streamUrl = "";
|
||||
|
||||
for(let i=0;i<lines.length;i++){
|
||||
|
||||
if(lines[i].startsWith("#EXTINF")){
|
||||
const parts = lines[i].split(",");
|
||||
if(parts[1]) name = parts[1].trim();
|
||||
}
|
||||
|
||||
if(
|
||||
lines[i].startsWith("http://") ||
|
||||
lines[i].startsWith("https://")
|
||||
){
|
||||
streamUrl = lines[i].trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("channel").textContent = name;
|
||||
|
||||
if(streamUrl){
|
||||
document.getElementById("player").src = streamUrl;
|
||||
startMetadataPolling(streamUrl);
|
||||
}
|
||||
}
|
||||
loadM3U();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
const artistEl = document.getElementById("artist");
|
||||
const factBox = document.getElementById("factBox");
|
||||
const songFactCache = new Map();
|
||||
let artistFacts = [];
|
||||
let factIndexArtist = 0;
|
||||
let factInterval = null;
|
||||
let factsLoaded = false;
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
GENERIC FALLBACK FACTS
|
||||
--------------------------------------------------------- */
|
||||
function genericFallbackFacts(name) {
|
||||
return [
|
||||
`${name} is part of the global music library. Information for this artist is limited.`,
|
||||
`Veltron Radio features music across many cultures and languages.`
|
||||
];
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
SONG → ARTIST DETECTION
|
||||
--------------------------------------------------------- */
|
||||
function detectArtistFromSong(title) {
|
||||
const lower = title.toLowerCase();
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
SOUNDTRACK / SCORE DETECTION
|
||||
--------------------------------------------------------- */
|
||||
function detectComposerFromTitle(title) {
|
||||
const lower = title.toLowerCase();
|
||||
|
||||
if (lower.includes("ost") || lower.includes("soundtrack") || lower.includes("score") || lower.includes("from")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
WIKIPEDIA LOOKUP (ARTIST-ONLY)
|
||||
--------------------------------------------------------- */
|
||||
async function getWikipediaSummary(name) {
|
||||
const languages = ["en"];
|
||||
const allowedTypes = ["bio", "artist", "musician", "music", "standard"];
|
||||
|
||||
for (let lang of languages) {
|
||||
const url = `https://${lang}.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(name)}`;
|
||||
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) continue;
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (!allowedTypes.includes(data.type)) continue;
|
||||
|
||||
if (data.type === "standard") {
|
||||
const text = (data.extract || "").toLowerCase();
|
||||
|
||||
const bandHints = [
|
||||
"band", "musical group", "singer", "rapper", "musician",
|
||||
"composer", "dj", "producer", "rock band", "punk band",
|
||||
"pop group", "duo", "trio", "game"
|
||||
];
|
||||
|
||||
const isBand = bandHints.some(h => text.includes(h));
|
||||
if (!isBand) continue;
|
||||
}
|
||||
|
||||
if (data.extract_html || data.extract) {
|
||||
return data.extract_html || data.extract;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
HTML → TEXT CLEANER
|
||||
--------------------------------------------------------- */
|
||||
function htmlToText(html) {
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = html;
|
||||
return div.innerText;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
FACT ROTATION
|
||||
--------------------------------------------------------- */
|
||||
function startArtistFactRotation() {
|
||||
if (factInterval) clearInterval(factInterval);
|
||||
|
||||
if (artistFacts.length === 0) {
|
||||
factBox.textContent = "No artist information available.";
|
||||
return;
|
||||
}
|
||||
|
||||
factIndexArtist = 0;
|
||||
factBox.textContent = artistFacts[0];
|
||||
|
||||
factInterval = setInterval(() => {
|
||||
factIndexArtist = (factIndexArtist + 1) % artistFacts.length;
|
||||
factBox.textContent = artistFacts[factIndexArtist];
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
MAIN FACT FETCHER
|
||||
--------------------------------------------------------- */
|
||||
async function updateFactBox() {
|
||||
const name = artistEl.textContent.trim();
|
||||
const title = document.getElementById("title").textContent.trim();
|
||||
|
||||
if (!name || name === "Loading..." || name === "Connecting...") return;
|
||||
|
||||
if (name === "Station Offline") {
|
||||
artistFacts = genericFallbackFacts("This track");
|
||||
startArtistFactRotation();
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ SONG-LEVEL CACHE CHECK
|
||||
if (songFactCache.has(title)) {
|
||||
artistFacts = songFactCache.get(title);
|
||||
factsLoaded = true;
|
||||
startArtistFactRotation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!factsLoaded) {
|
||||
factBox.textContent = "Loading artist info...";
|
||||
}
|
||||
|
||||
let lookupName = name;
|
||||
|
||||
if (name === "Unknown Artist" || name.trim() === "") {
|
||||
lookupName = title;
|
||||
}
|
||||
|
||||
const composer = detectComposerFromTitle(title);
|
||||
if (composer) lookupName = composer;
|
||||
|
||||
const detectedArtist = detectArtistFromSong(title);
|
||||
if (detectedArtist) lookupName = detectedArtist;
|
||||
|
||||
try {
|
||||
let summaryHTML = await getWikipediaSummary(lookupName);
|
||||
|
||||
if (!summaryHTML) {
|
||||
try {
|
||||
const searchURL =
|
||||
`https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(lookupName)}&format=json&origin=*`;
|
||||
|
||||
const searchRes = await fetch(searchURL);
|
||||
const searchData = await searchRes.json();
|
||||
|
||||
if (searchData.query.search.length > 0) {
|
||||
const bestMatch = searchData.query.search[0].title;
|
||||
summaryHTML = await getWikipediaSummary(bestMatch);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (!summaryHTML) {
|
||||
artistFacts = genericFallbackFacts(lookupName);
|
||||
} else {
|
||||
const clean = htmlToText(summaryHTML);
|
||||
artistFacts = clean
|
||||
.split(/\. |\.$/)
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 5)
|
||||
.slice(0, 4)
|
||||
.map(s => s + ".");
|
||||
}
|
||||
|
||||
// ✅ STORE IN SONG CACHE
|
||||
songFactCache.set(title, artistFacts);
|
||||
|
||||
factsLoaded = true;
|
||||
startArtistFactRotation();
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
artistFacts = genericFallbackFacts(name);
|
||||
startArtistFactRotation();
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
WATCH ARTIST FIELD FOR CHANGES
|
||||
--------------------------------------------------------- */
|
||||
const observer = new MutationObserver(updateFactBox);
|
||||
observer.observe(artistEl, { childList: true, subtree: true, characterData: true });
|
||||
|
||||
updateFactBox();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
logo.png
Normal file
BIN
logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 KiB |
BIN
musicchoice.png
Normal file
BIN
musicchoice.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 369 KiB |
3
venith.m3u
Normal file
3
venith.m3u
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#EXTM3U
|
||||
#EXTINF:0,VENITH RADIO 1
|
||||
http://192.168.1.7:5000/listen/veltron_radio/radio.mp3
|
||||
3
venith2.m3u
Normal file
3
venith2.m3u
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#EXTM3U
|
||||
#EXTINF:0,VENITH RADIO 2
|
||||
http://192.168.1.7:5000/listen/veltron_radio_2/radio.mp3
|
||||
Loading…
Add table
Add a link
Reference in a new issue