beautify
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
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 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
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);
|
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 */
|
||||||
|
|||||||
Reference in New Issue
Block a user