beautify
This commit is contained in:
@@ -54,6 +54,14 @@ function play(ip, url) {
|
||||
activeClient.getSessions((err, sessions) => {
|
||||
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
|
||||
const session = sessions.find(s => s.appId === 'CC1AD845');
|
||||
|
||||
@@ -62,6 +70,7 @@ function play(ip, url) {
|
||||
activeClient.join(session, DefaultMediaReceiver, (err, player) => {
|
||||
if (err) {
|
||||
log('Join failed, attempting launch...');
|
||||
log(`Join error: ${err && err.message ? err.message : String(err)}`);
|
||||
launchPlayer(url);
|
||||
} else {
|
||||
activePlayer = player;
|
||||
@@ -88,7 +97,10 @@ function launchPlayer(url) {
|
||||
activeClient.launch(DefaultMediaReceiver, (err, player) => {
|
||||
if (err) {
|
||||
// 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;
|
||||
loadMedia(url);
|
||||
|
||||
@@ -51,6 +51,32 @@
|
||||
<section class="artwork-section">
|
||||
<div class="artwork-container">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -112,7 +138,7 @@
|
||||
<!-- Hidden Cast Overlay (Beautified) -->
|
||||
<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">
|
||||
<h2 id="deviceTitle">Connect to Device</h2>
|
||||
<h2 id="deviceTitle">Choose</h2>
|
||||
|
||||
<ul id="device-list" class="device-list">
|
||||
<!-- Render device items here -->
|
||||
|
||||
79
src/main.js
79
src/main.js
@@ -26,6 +26,7 @@ const castOverlay = document.getElementById('cast-overlay');
|
||||
const closeOverlayBtn = document.getElementById('close-overlay');
|
||||
const deviceListEl = document.getElementById('device-list');
|
||||
const logoTextEl = document.querySelector('.station-logo-text');
|
||||
const logoImgEl = document.getElementById('station-logo-img');
|
||||
|
||||
// Init
|
||||
async function init() {
|
||||
@@ -37,7 +38,30 @@ async function init() {
|
||||
async function loadStations() {
|
||||
try {
|
||||
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) {
|
||||
currentIndex = 0;
|
||||
loadStation(currentIndex);
|
||||
@@ -72,8 +96,7 @@ function setupEventListeners() {
|
||||
// Menu button - explicit functionality or placeholder?
|
||||
// For now just log or maybe show about
|
||||
document.getElementById('menu-btn').addEventListener('click', () => {
|
||||
// Future: Settings menu
|
||||
console.log('Menu clicked');
|
||||
openStationsOverlay();
|
||||
});
|
||||
|
||||
// Hotkeys?
|
||||
@@ -88,12 +111,23 @@ function loadStation(index) {
|
||||
|
||||
// Update Logo Text (First letter or number)
|
||||
// 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+/);
|
||||
if (numberMatch) {
|
||||
logoTextEl.textContent = numberMatch[0];
|
||||
} else {
|
||||
logoTextEl.textContent = station.name.charAt(0).toUpperCase();
|
||||
}
|
||||
logoTextEl.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
async function togglePlay() {
|
||||
@@ -280,3 +314,42 @@ async function selectCastDevice(deviceName) {
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
1346
src/stations.json
1346
src/stations.json
File diff suppressed because it is too large
Load Diff
@@ -212,12 +212,92 @@ header {
|
||||
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 {
|
||||
font-size: 5rem;
|
||||
font-weight: 800;
|
||||
font-style: italic;
|
||||
color: rgba(255,255,255,0.9);
|
||||
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 */
|
||||
|
||||
Reference in New Issue
Block a user