This commit is contained in:
2025-12-30 19:09:50 +01:00
parent b2f1b48d06
commit eec1cff25f
5 changed files with 1541 additions and 16 deletions

View File

@@ -54,6 +54,14 @@ function play(ip, url) {
activeClient.getSessions((err, sessions) => { activeClient.getSessions((err, sessions) => {
if (err) return error(`GetSessions error: ${err.message}`); if (err) return error(`GetSessions error: ${err.message}`);
// Log sessions for debugging (appId/sessionId if available)
try {
const sessInfo = sessions.map(s => ({ appId: s.appId, sessionId: s.sessionId, displayName: s.displayName }));
log(`Sessions: ${JSON.stringify(sessInfo)}`);
} catch (e) {
log('Sessions: (unable to stringify)');
}
// DefaultMediaReceiver App ID is CC1AD845 // DefaultMediaReceiver App ID is CC1AD845
const session = sessions.find(s => s.appId === 'CC1AD845'); const session = sessions.find(s => s.appId === 'CC1AD845');
@@ -62,6 +70,7 @@ function play(ip, url) {
activeClient.join(session, DefaultMediaReceiver, (err, player) => { activeClient.join(session, DefaultMediaReceiver, (err, player) => {
if (err) { if (err) {
log('Join failed, attempting launch...'); log('Join failed, attempting launch...');
log(`Join error: ${err && err.message ? err.message : String(err)}`);
launchPlayer(url); launchPlayer(url);
} else { } else {
activePlayer = player; activePlayer = player;
@@ -88,7 +97,10 @@ function launchPlayer(url) {
activeClient.launch(DefaultMediaReceiver, (err, player) => { activeClient.launch(DefaultMediaReceiver, (err, player) => {
if (err) { if (err) {
// If launch fails with NOT_ALLOWED, it sometimes means we MUST join or something else is occupying it // If launch fails with NOT_ALLOWED, it sometimes means we MUST join or something else is occupying it
return error(`Launch error: ${err.message}`); const details = `Launch error: ${err && err.message ? err.message : String(err)}${err && err.code ? ` (code: ${err.code})` : ''}`;
error(details);
try { error(`Launch error full: ${JSON.stringify(err)}`); } catch (e) { /* ignore */ }
return;
} }
activePlayer = player; activePlayer = player;
loadMedia(url); loadMedia(url);

View File

@@ -51,6 +51,32 @@
<section class="artwork-section"> <section class="artwork-section">
<div class="artwork-container"> <div class="artwork-container">
<div class="artwork-placeholder"> <div class="artwork-placeholder">
<!-- Gooey SVG filter for fluid blob blending -->
<svg width="0" height="0" style="position:absolute">
<defs>
<filter id="goo">
<!-- increased blur for smoother, more transparent blending -->
<feGaussianBlur in="SourceGraphic" stdDeviation="18" result="blur" />
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7" result="goo" />
<feBlend in="SourceGraphic" in2="goo" />
</filter>
</defs>
</svg>
<div class="logo-blobs" aria-hidden="true">
<span class="blob b1"></span>
<span class="blob b2"></span>
<span class="blob b3"></span>
<span class="blob b4"></span>
<span class="blob b5"></span>
<span class="blob b6"></span>
<span class="blob b7"></span>
<span class="blob b8"></span>
<span class="blob b9"></span>
<span class="blob b10"></span>
</div>
<img id="station-logo-img" class="station-logo-img hidden" alt="station logo">
<span class="station-logo-text">1</span> <span class="station-logo-text">1</span>
</div> </div>
</div> </div>
@@ -112,7 +138,7 @@
<!-- Hidden Cast Overlay (Beautified) --> <!-- Hidden Cast Overlay (Beautified) -->
<div id="cast-overlay" class="overlay hidden" aria-hidden="true" data-tauri-drag-region> <div id="cast-overlay" class="overlay hidden" aria-hidden="true" data-tauri-drag-region>
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="deviceTitle"> <div class="modal" role="dialog" aria-modal="true" aria-labelledby="deviceTitle">
<h2 id="deviceTitle">Connect to Device</h2> <h2 id="deviceTitle">Choose</h2>
<ul id="device-list" class="device-list"> <ul id="device-list" class="device-list">
<!-- Render device items here --> <!-- Render device items here -->

View File

@@ -26,6 +26,7 @@ const castOverlay = document.getElementById('cast-overlay');
const closeOverlayBtn = document.getElementById('close-overlay'); const closeOverlayBtn = document.getElementById('close-overlay');
const deviceListEl = document.getElementById('device-list'); const deviceListEl = document.getElementById('device-list');
const logoTextEl = document.querySelector('.station-logo-text'); const logoTextEl = document.querySelector('.station-logo-text');
const logoImgEl = document.getElementById('station-logo-img');
// Init // Init
async function init() { async function init() {
@@ -37,7 +38,30 @@ async function init() {
async function loadStations() { async function loadStations() {
try { try {
const resp = await fetch('stations.json'); const resp = await fetch('stations.json');
stations = await resp.json(); const raw = await resp.json();
// Normalize station objects so the rest of the app can rely on `name` and `url`.
stations = raw
.map((s) => {
// If already in the old format, keep as-is
if (s.name && s.url) return s;
const name = s.title || s.id || s.name || 'Unknown';
// Prefer liveAudio, fall back to liveVideo or any common fields
const url = s.liveAudio || s.liveVideo || s.liveStream || s.url || '';
return {
id: s.id || name,
name,
url,
logo: s.logo || s.poster || '',
enabled: typeof s.enabled === 'boolean' ? s.enabled : true,
raw: s,
};
})
// Filter out disabled stations and those without a stream URL
.filter((s) => s.enabled !== false && s.url && s.url.length > 0);
if (stations.length > 0) { if (stations.length > 0) {
currentIndex = 0; currentIndex = 0;
loadStation(currentIndex); loadStation(currentIndex);
@@ -72,8 +96,7 @@ function setupEventListeners() {
// Menu button - explicit functionality or placeholder? // Menu button - explicit functionality or placeholder?
// For now just log or maybe show about // For now just log or maybe show about
document.getElementById('menu-btn').addEventListener('click', () => { document.getElementById('menu-btn').addEventListener('click', () => {
// Future: Settings menu openStationsOverlay();
console.log('Menu clicked');
}); });
// Hotkeys? // Hotkeys?
@@ -88,12 +111,23 @@ function loadStation(index) {
// Update Logo Text (First letter or number) // Update Logo Text (First letter or number)
// Simple heuristic: if name has a number, use it, else first letter // Simple heuristic: if name has a number, use it, else first letter
// If station has a logo URL, show the image; otherwise show the text fallback
if (station.logo && station.logo.length > 0) {
logoImgEl.src = station.logo;
logoImgEl.classList.remove('hidden');
logoTextEl.classList.add('hidden');
} else {
// Fallback to single-letter/logo text
logoImgEl.src = '';
logoImgEl.classList.add('hidden');
const numberMatch = station.name.match(/\d+/); const numberMatch = station.name.match(/\d+/);
if (numberMatch) { if (numberMatch) {
logoTextEl.textContent = numberMatch[0]; logoTextEl.textContent = numberMatch[0];
} else { } else {
logoTextEl.textContent = station.name.charAt(0).toUpperCase(); logoTextEl.textContent = station.name.charAt(0).toUpperCase();
} }
logoTextEl.classList.remove('hidden');
}
} }
async function togglePlay() { async function togglePlay() {
@@ -280,3 +314,42 @@ async function selectCastDevice(deviceName) {
} }
window.addEventListener('DOMContentLoaded', init); window.addEventListener('DOMContentLoaded', init);
// Open overlay and show list of stations (used by menu/hamburger)
function openStationsOverlay() {
castOverlay.classList.remove('hidden');
castOverlay.setAttribute('aria-hidden', 'false');
deviceListEl.innerHTML = '<li class="device"><div class="device-main">Loading...</div><div class="device-sub">Preparing stations</div></li>';
// If stations not loaded yet, show message
if (!stations || stations.length === 0) {
deviceListEl.innerHTML = '<li class="device"><div class="device-main">No stations found</div><div class="device-sub">Check your stations.json</div></li>';
return;
}
deviceListEl.innerHTML = '';
stations.forEach((s, idx) => {
const li = document.createElement('li');
li.className = 'device' + (currentIndex === idx ? ' selected' : '');
const subtitle = (s.raw && s.raw.www) ? s.raw.www : (s.id || '');
li.innerHTML = `<div class="device-main">${s.name}</div><div class="device-sub">${subtitle}</div>`;
li.onclick = async () => {
// Always switch to local playback when selecting from stations menu
currentMode = 'local';
currentCastDevice = null;
castBtn.style.color = 'var(--text-main)';
// Select and play
currentIndex = idx;
loadStation(currentIndex);
closeCastOverlay();
try {
await play();
} catch (e) {
console.error('Failed to play station from menu', e);
}
};
deviceListEl.appendChild(li);
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -212,12 +212,92 @@ header {
box-shadow: inset 0 0 20px rgba(0,0,0,0.2); box-shadow: inset 0 0 20px rgba(0,0,0,0.2);
} }
.artwork-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #4ea8de, #6930c3);
border-radius: 20px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
box-shadow: inset 0 0 20px rgba(0,0,0,0.2);
}
.station-logo-text { .station-logo-text {
font-size: 5rem; font-size: 5rem;
font-weight: 800; font-weight: 800;
font-style: italic; font-style: italic;
color: rgba(255,255,255,0.9); color: rgba(255,255,255,0.9);
text-shadow: 0 4px 10px rgba(0,0,0,0.3); text-shadow: 0 4px 10px rgba(0,0,0,0.3);
position: relative;
z-index: 3;
}
.station-logo-img {
/* Fill the artwork placeholder while keeping aspect ratio and inner padding */
width: 100%;
height: 100%;
object-fit: contain;
display: block;
padding: 12px; /* inner spacing from rounded edges */
box-sizing: border-box;
border-radius: 12px;
box-shadow: 0 8px 20px rgba(0,0,0,0.35);
position: relative;
z-index: 3;
}
/* Logo blobs container sits behind logo but inside artwork placeholder */
.logo-blobs {
position: absolute;
inset: 0;
filter: url(#goo);
z-index: 1;
pointer-events: none;
}
.blob {
position: absolute;
border-radius: 50%;
/* more transparent overall */
opacity: 0.6;
/* stronger blur for softer, more subtle blobs */
filter: blur(8px);
}
.b1 { width: 140px; height: 140px; left: 8%; top: 20%; background: radial-gradient(circle at 30% 30%, #c77dff, #8b5cf6); animation: float1 6s ease-in-out infinite; }
.b2 { width: 110px; height: 110px; right: 6%; top: 10%; background: radial-gradient(circle at 30% 30%, #7bffd1, #7dffb3); animation: float2 5.5s ease-in-out infinite; }
.b3 { width: 120px; height: 120px; left: 20%; bottom: 12%; background: radial-gradient(circle at 20% 20%, #ffd07a, #ff6bf0); animation: float3 7s ease-in-out infinite; }
.b4 { width: 90px; height: 90px; right: 24%; bottom: 18%; background: radial-gradient(circle at 30% 30%, #6bd3ff, #4ea8de); animation: float4 6.5s ease-in-out infinite; }
.b5 { width: 70px; height: 70px; left: 46%; top: 36%; background: radial-gradient(circle at 40% 40%, #ffa6d6, #c77dff); animation: float5 8s ease-in-out infinite; }
/* Additional blobs */
.b6 { width: 100px; height: 100px; left: 12%; top: 48%; background: radial-gradient(circle at 30% 30%, #bde7ff, #6bd3ff); animation: float6 6.8s ease-in-out infinite; }
.b7 { width: 60px; height: 60px; right: 10%; top: 42%; background: radial-gradient(circle at 40% 40%, #ffd9b3, #ffd07a); animation: float7 7.2s ease-in-out infinite; }
.b8 { width: 95px; height: 95px; left: 34%; bottom: 8%; background: radial-gradient(circle at 30% 30%, #e3b6ff, #c77dff); animation: float8 6.4s ease-in-out infinite; }
.b9 { width: 50px; height: 50px; right: 34%; bottom: 6%; background: radial-gradient(circle at 30% 30%, #9ef7d3, #7bffd1); animation: float9 8.4s ease-in-out infinite; }
.b10 { width: 42px; height: 42px; left: 52%; bottom: 28%; background: radial-gradient(circle at 30% 30%, #ffd0f0, #ffa6d6); animation: float10 5.8s ease-in-out infinite; }
@keyframes float1 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(12px) translateX(8px) scale(1.06); } 100% { transform: translateY(0) translateX(0) scale(1); } }
@keyframes float2 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(-10px) translateX(-6px) scale(1.04); } 100% { transform: translateY(0) translateX(0) scale(1); } }
@keyframes float3 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(8px) translateX(-10px) scale(1.05); } 100% { transform: translateY(0) translateX(0) scale(1); } }
@keyframes float4 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(-6px) translateX(10px) scale(1.03); } 100% { transform: translateY(0) translateX(0) scale(1); } }
@keyframes float5 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(-12px) translateX(4px) scale(1.07); } 100% { transform: translateY(0) translateX(0) scale(1); } }
@keyframes float6 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(-8px) translateX(6px) scale(1.05); } 100% { transform: translateY(0) translateX(0) scale(1); } }
@keyframes float7 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(10px) translateX(-6px) scale(1.04); } 100% { transform: translateY(0) translateX(0) scale(1); } }
@keyframes float8 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(-6px) translateX(10px) scale(1.03); } 100% { transform: translateY(0) translateX(0) scale(1); } }
@keyframes float9 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(12px) translateX(-4px) scale(1.06); } 100% { transform: translateY(0) translateX(0) scale(1); } }
@keyframes float10 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(-10px) translateX(2px) scale(1.04); } 100% { transform: translateY(0) translateX(0) scale(1); } }
/* Slightly darken backdrop gradient so blobs read better */
.artwork-placeholder::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(0,0,0,0.06), rgba(0,0,0,0.12));
z-index: 0;
} }
/* Track Info */ /* Track Info */