diff --git a/android/README.md b/android/README.md new file mode 100644 index 0000000..5e361fc --- /dev/null +++ b/android/README.md @@ -0,0 +1,11 @@ +This folder is not a full Android Studio project. + +The buildable Android Studio/Gradle project is generated by Tauri at: + +- src-tauri/gen/android + +If you haven't generated it yet, run from the repo root: + +- .\node_modules\.bin\tauri.cmd android init --ci + +Then open `src-tauri/gen/android` in Android Studio and build the APK/AAB. diff --git a/android/app/src/main/assets/assets/appIcon.png b/android/app/src/main/assets/assets/appIcon.png new file mode 100644 index 0000000..d789348 Binary files /dev/null and b/android/app/src/main/assets/assets/appIcon.png differ diff --git a/android/app/src/main/assets/assets/favicon_io.zip b/android/app/src/main/assets/assets/favicon_io.zip new file mode 100644 index 0000000..15adffb Binary files /dev/null and b/android/app/src/main/assets/assets/favicon_io.zip differ diff --git a/android/app/src/main/assets/assets/favicon_io/android-chrome-192x192.png b/android/app/src/main/assets/assets/favicon_io/android-chrome-192x192.png new file mode 100644 index 0000000..4757781 Binary files /dev/null and b/android/app/src/main/assets/assets/favicon_io/android-chrome-192x192.png differ diff --git a/android/app/src/main/assets/assets/favicon_io/android-chrome-512x512.png b/android/app/src/main/assets/assets/favicon_io/android-chrome-512x512.png new file mode 100644 index 0000000..8f1299a Binary files /dev/null and b/android/app/src/main/assets/assets/favicon_io/android-chrome-512x512.png differ diff --git a/android/app/src/main/assets/assets/favicon_io/app-icon.png b/android/app/src/main/assets/assets/favicon_io/app-icon.png new file mode 100644 index 0000000..8f1299a Binary files /dev/null and b/android/app/src/main/assets/assets/favicon_io/app-icon.png differ diff --git a/android/app/src/main/assets/assets/favicon_io/apple-touch-icon.png b/android/app/src/main/assets/assets/favicon_io/apple-touch-icon.png new file mode 100644 index 0000000..f29ebcf Binary files /dev/null and b/android/app/src/main/assets/assets/favicon_io/apple-touch-icon.png differ diff --git a/android/app/src/main/assets/assets/favicon_io/favicon-16x16.png b/android/app/src/main/assets/assets/favicon_io/favicon-16x16.png new file mode 100644 index 0000000..4eb99da Binary files /dev/null and b/android/app/src/main/assets/assets/favicon_io/favicon-16x16.png differ diff --git a/android/app/src/main/assets/assets/favicon_io/favicon-32x32.png b/android/app/src/main/assets/assets/favicon_io/favicon-32x32.png new file mode 100644 index 0000000..73c330b Binary files /dev/null and b/android/app/src/main/assets/assets/favicon_io/favicon-32x32.png differ diff --git a/android/app/src/main/assets/assets/favicon_io/icon.ico b/android/app/src/main/assets/assets/favicon_io/icon.ico new file mode 100644 index 0000000..586e969 Binary files /dev/null and b/android/app/src/main/assets/assets/favicon_io/icon.ico differ diff --git a/android/app/src/main/assets/assets/favicon_io/site.webmanifest b/android/app/src/main/assets/assets/favicon_io/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/android/app/src/main/assets/assets/favicon_io/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/android/app/src/main/assets/assets/javascript.svg b/android/app/src/main/assets/assets/javascript.svg new file mode 100644 index 0000000..f9abb2b --- /dev/null +++ b/android/app/src/main/assets/assets/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/android/app/src/main/assets/assets/tauri.svg b/android/app/src/main/assets/assets/tauri.svg new file mode 100644 index 0000000..0c0e6aa --- /dev/null +++ b/android/app/src/main/assets/assets/tauri.svg @@ -0,0 +1,4 @@ + + + + diff --git a/android/app/src/main/assets/index.html b/android/app/src/main/assets/index.html new file mode 100644 index 0000000..3ea45e7 --- /dev/null +++ b/android/app/src/main/assets/index.html @@ -0,0 +1,158 @@ + + + + + + + Radio1 Player + + + + + +
+
+
+ +
+
+ +
+ Radio1 Player + + Ready + +
+
+ + +
+
+ +
+
+
+ + + + + + + + + + + + + + + + 1 +
+
+
+ +
+

Radio 1 MB

+

Live Stream

+
+ + +
+
+
+
+
+
+ +
+ + + + + +
+ +
+ +
+ +
+ 50% +
+ + + + +
+
+ + + \ No newline at end of file diff --git a/android/app/src/main/assets/main.js b/android/app/src/main/assets/main.js new file mode 100644 index 0000000..50c6b53 --- /dev/null +++ b/android/app/src/main/assets/main.js @@ -0,0 +1,355 @@ +const { invoke } = window.__TAURI__.core; +const { getCurrentWindow } = window.__TAURI__.window; + +// State +let stations = []; +let currentIndex = 0; +let isPlaying = false; +let currentMode = 'local'; // 'local' | 'cast' +let currentCastDevice = null; +const audio = new Audio(); + +// UI Elements +const stationNameEl = document.getElementById('station-name'); +const stationSubtitleEl = document.getElementById('station-subtitle'); +const statusTextEl = document.getElementById('status-text'); +const statusDotEl = document.querySelector('.status-dot'); +const playBtn = document.getElementById('play-btn'); +const iconPlay = document.getElementById('icon-play'); +const iconStop = document.getElementById('icon-stop'); +const prevBtn = document.getElementById('prev-btn'); +const nextBtn = document.getElementById('next-btn'); +const volumeSlider = document.getElementById('volume-slider'); +const volumeValue = document.getElementById('volume-value'); +const castBtn = document.getElementById('cast-toggle-btn'); +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() { + await loadStations(); + setupEventListeners(); + updateUI(); +} + +async function loadStations() { + try { + const resp = await fetch('stations.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); + } + } catch (e) { + console.error('Failed to load stations', e); + statusTextEl.textContent = 'Error loading stations'; + } +} + +function setupEventListeners() { + playBtn.addEventListener('click', togglePlay); + prevBtn.addEventListener('click', playPrev); + nextBtn.addEventListener('click', playNext); + + volumeSlider.addEventListener('input', handleVolumeInput); + + castBtn.addEventListener('click', openCastOverlay); + closeOverlayBtn.addEventListener('click', closeCastOverlay); + + // Close overlay on background click + castOverlay.addEventListener('click', (e) => { + if (e.target === castOverlay) closeCastOverlay(); + }); + + // Close button + document.getElementById('close-btn').addEventListener('click', async () => { + const appWindow = getCurrentWindow(); + await appWindow.close(); + }); + + // Menu button - explicit functionality or placeholder? + // For now just log or maybe show about + document.getElementById('menu-btn').addEventListener('click', () => { + openStationsOverlay(); + }); + + // Hotkeys? +} + +function loadStation(index) { + if (index < 0 || index >= stations.length) return; + const station = stations[index]; + + stationNameEl.textContent = station.name; + stationSubtitleEl.textContent = currentMode === 'cast' ? `Casting to ${currentCastDevice}` : 'Live Stream'; + + // 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() { + if (isPlaying) { + await stop(); + } else { + await play(); + } +} + +async function play() { + const station = stations[currentIndex]; + if (!station) return; + + statusTextEl.textContent = 'Buffering...'; + statusDotEl.style.backgroundColor = 'var(--text-muted)'; // Grey/Yellow while loading + + if (currentMode === 'local') { + audio.src = station.url; + audio.volume = volumeSlider.value / 100; + try { + await audio.play(); + isPlaying = true; + updateUI(); + } catch (e) { + console.error('Playback failed', e); + statusTextEl.textContent = 'Error'; + } + } else if (currentMode === 'cast' && currentCastDevice) { + // Cast logic + try { + await invoke('cast_play', { deviceName: currentCastDevice, url: station.url }); + isPlaying = true; + // Sync volume + const vol = volumeSlider.value / 100; + invoke('cast_set_volume', { deviceName: currentCastDevice, volume: vol }); + updateUI(); + } catch (e) { + console.error('Cast failed', e); + statusTextEl.textContent = 'Cast Error'; + currentMode = 'local'; // Fallback + updateUI(); + } + } +} + +async function stop() { + if (currentMode === 'local') { + audio.pause(); + audio.src = ''; + } else if (currentMode === 'cast' && currentCastDevice) { + try { + await invoke('cast_stop', { deviceName: currentCastDevice }); + } catch (e) { + console.error(e); + } + } + + isPlaying = false; + updateUI(); +} + +async function playNext() { + if (stations.length === 0) return; + + // If playing, stop first? Or seamless? + // For radio, seamless switch requires stop then play new URL + const wasPlaying = isPlaying; + + if (wasPlaying) await stop(); + + currentIndex = (currentIndex + 1) % stations.length; + loadStation(currentIndex); + + if (wasPlaying) await play(); +} + +async function playPrev() { + if (stations.length === 0) return; + + const wasPlaying = isPlaying; + + if (wasPlaying) await stop(); + + currentIndex = (currentIndex - 1 + stations.length) % stations.length; + loadStation(currentIndex); + + if (wasPlaying) await play(); +} + +function updateUI() { + // Play/Stop Button + if (isPlaying) { + iconPlay.classList.add('hidden'); + iconStop.classList.remove('hidden'); + playBtn.classList.add('playing'); // Add pulsing ring animation + statusTextEl.textContent = 'Playing'; + statusDotEl.style.backgroundColor = 'var(--success)'; + stationSubtitleEl.textContent = currentMode === 'cast' ? `Casting to ${currentCastDevice}` : 'Live Stream'; + } else { + iconPlay.classList.remove('hidden'); + iconStop.classList.add('hidden'); + playBtn.classList.remove('playing'); // Remove pulsing ring + statusTextEl.textContent = 'Ready'; + statusDotEl.style.backgroundColor = 'var(--text-muted)'; + stationSubtitleEl.textContent = currentMode === 'cast' ? `Connected to ${currentCastDevice}` : 'Live Stream'; + } +} + +function handleVolumeInput() { + const val = volumeSlider.value; + volumeValue.textContent = `${val}%`; + const decimals = val / 100; + + if (currentMode === 'local') { + audio.volume = decimals; + } else if (currentMode === 'cast' && currentCastDevice) { + invoke('cast_set_volume', { deviceName: currentCastDevice, volume: decimals }); + } +} + +// Cast Logic +async function openCastOverlay() { + castOverlay.classList.remove('hidden'); + castOverlay.setAttribute('aria-hidden', 'false'); + deviceListEl.innerHTML = '
  • Scanning...
    Searching for speakers
  • '; + + try { + const devices = await invoke('list_cast_devices'); + deviceListEl.innerHTML = ''; + + // Add "This Computer" option + const localLi = document.createElement('li'); + localLi.className = 'device' + (currentMode === 'local' ? ' selected' : ''); + localLi.innerHTML = '
    This Computer
    Local Playback
    '; + localLi.onclick = () => selectCastDevice(null); + deviceListEl.appendChild(localLi); + + if (devices.length > 0) { + devices.forEach(d => { + const li = document.createElement('li'); + li.className = 'device' + (currentMode === 'cast' && currentCastDevice === d ? ' selected' : ''); + li.innerHTML = `
    ${d}
    Google Cast Speaker
    `; + li.onclick = () => selectCastDevice(d); + deviceListEl.appendChild(li); + }); + } + } catch (e) { + deviceListEl.innerHTML = `
  • Error
    ${e}
  • `; + } +} + +function closeCastOverlay() { + castOverlay.classList.add('hidden'); + castOverlay.setAttribute('aria-hidden', 'true'); +} + +async function selectCastDevice(deviceName) { + closeCastOverlay(); + + // If checking same device, do nothing + if (deviceName === currentCastDevice) return; + + // If switching mode, stop current playback + if (isPlaying) { + await stop(); + } + + if (deviceName) { + currentMode = 'cast'; + currentCastDevice = deviceName; + castBtn.style.color = 'var(--success)'; + } else { + currentMode = 'local'; + currentCastDevice = null; + castBtn.style.color = 'var(--text-main)'; + } + + updateUI(); + + // Auto-play if we were playing? Let's stay stopped to be safe/explicit + // Or auto-play for better UX? + // Let's prompt user to play. +} + +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 = '
  • Loading...
    Preparing stations
  • '; + + // If stations not loaded yet, show message + if (!stations || stations.length === 0) { + deviceListEl.innerHTML = '
  • No stations found
    Check your stations.json
  • '; + 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 = `
    ${s.name}
    ${subtitle}
    `; + 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); + }); +} diff --git a/android/app/src/main/assets/stations.json b/android/app/src/main/assets/stations.json new file mode 100644 index 0000000..51086d7 --- /dev/null +++ b/android/app/src/main/assets/stations.json @@ -0,0 +1,1342 @@ +[ + { + "id": "Radio1", + "title": "Radio 1", + "slogan": "Več dobre glasbe", + "logo": "http://datacache.radio.si/api/radiostations/logo/radio1.svg", + "liveAudio": "http://live.radio1.si/Radio1", + "liveVideo": null, + "poster": "", + "lastSongs": "http://data.radio.si/api/lastsongsxml/radio1/json", + "epg": "http://spored.radio.si/api/now/radio1", + "defaultText": "www.radio1.si", + "www": "https://www.radio1.si", + "mountPoints": [ + "Radio1", + "Radio1BK", + "Radio1CE", + "Radio1GOR", + "Radio1KOR", + "Radio1LI", + "Radio1MB", + "Radio1NM", + "Radio1OB", + "Radio1PO", + "Radio1PR", + "Radio1PRI", + "Radio1PT", + "Radio1RIB", + "Radio1VE", + "Radio1VR", + "Radio1SAV" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38651300300" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "http://m.radio1.si" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "http://www.youtube.com/user/radio1slovenia" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "http://facebook.com/RadioEna" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "http://www.instagram.com/radio1slo" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radio1?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=50668", + "rpUid": "705167", + "dabUser": "radio1", + "dabPass": "sUbSGhmzdwKQT", + "dabDefaultImg": "http://media.radio.si/logo/dns/radio1/320x240.png", + "small": false + }, + { + "id": "Aktual", + "title": "Radio Aktual", + "slogan": "Narejen za vaša ušesa", + "logo": "http://datacache.radio.si/api/radiostations/logo/aktual.svg", + "liveAudio": "http://live.radio.si/Aktual", + "liveVideo": "https://radio.serv.si/AktualTV/video.m3u8", + "poster": "https://cdn1.radio.si/900/screenaktual_90c0280a8.jpg", + "lastSongs": "http://data.radio.si/api/lastsongsxml/aktual/json", + "epg": null, + "defaultText": "", + "www": "https://radioaktual.si", + "mountPoints": [ + "Aktual" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+386158801430" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radioaktual.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/user/raktual?sub_confirmation=1" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radioaktual/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705160", + "dabUser": "aktual", + "dabPass": "GB31GZd5st0M", + "dabDefaultImg": "http://media.radio.si/logo/dns/aktual/RadioAktual_DAB.jpg", + "small": false + }, + { + "id": "Veseljak", + "title": "Radio Veseljak", + "slogan": "Najboljša domača glasba", + "logo": "http://datacache.radio.si/api/radiostations/logo/veseljak.svg", + "liveAudio": "http://live.radio.si/Veseljak", + "liveVideo": "https://radio.serv.si/VeseljakGolicaTV/video.m3u8", + "poster": "https://cdn1.radio.si/900/screenveseljak_166218c26.jpg", + "lastSongs": "http://data.radio.si/api/lastsongsxml/veseljak/json", + "epg": null, + "defaultText": "www.veseljak.si", + "www": "https://veseljak.si/", + "mountPoints": [ + "Veseljak", + "VeseljakPO" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38615880110" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://veseljak.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/c/VESELJAKNAJBOLJSADOMACAGLASBA" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RadioVeseljak" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/veseljak.si/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705166", + "dabUser": "veseljak", + "dabPass": "sLRDCAX9j3k2", + "dabDefaultImg": "http://media.radio.si/logo/dns/veseljak/RadioVeseljak_DAB.jpg", + "small": false + }, + { + "id": "Radio1Rock", + "title": "Radio 1 ROCK", + "slogan": "100% Rock", + "logo": "http://datacache.radio.si/api/radiostations/logo/radio1rock.svg", + "liveAudio": "http://live.radio.si/Radio1Rock", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/radio1rock/json", + "epg": "http://spored.radio.si/api/now/radio1rock", + "defaultText": "www.radio1rock.si", + "www": "https://radio1rock.si/", + "mountPoints": [ + "Radio1Rock" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38683879300" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radio1rock.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/R1Rock" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/R1rock.si/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiobob?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=61109", + "rpUid": "705162", + "dabUser": "radiobob", + "dabPass": "cjT24PpyVxit6", + "dabDefaultImg": "http://media.radio.si/logo/dns/radio1rock/320x240.png", + "small": false + }, + { + "id": "Radio80", + "title": "Radio 1 80-a", + "slogan": "Samo hiti 80-ih", + "logo": "http://datacache.radio.si/api/radiostations/logo/radio80.svg", + "liveAudio": "http://live.radio.si/Radio80", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/radio80/json", + "epg": "http://spored.radio.si/api/now/radio80", + "defaultText": "www.radio80.si", + "www": "https://radio80.si/", + "mountPoints": [ + "Radio80" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38615008875" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radio80.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/radio1slovenia" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radioena" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radio180-a?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=89760", + "rpUid": "705102", + "dabUser": "radio80", + "dabPass": "nc6da2LolcBXC", + "dabDefaultImg": "http://media.radio.si/logo/dns/radio80/320x240.png", + "small": false + }, + { + "id": "Radio90", + "title": "Radio 1 90-a", + "slogan": "Samo hiti 90-ih", + "logo": "http://datacache.radio.si/api/radiostations/logo/radio90.svg", + "liveAudio": "http://live.radio.si/Radio90", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/radio90/json", + "epg": null, + "defaultText": "www.radio1.si", + "www": "https://radio1.si/", + "mountPoints": [ + "Radio90" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38615008875" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radio1.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/radio1slovenia" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radioena" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705172", + "dabUser": "radio90", + "dabPass": "P2RyUrHcyq7M", + "dabDefaultImg": "http://media.radio.si/logo/dns/radio90/320x240.png", + "small": false + }, + { + "id": "Toti", + "title": "Toti radio", + "slogan": "Toti hudi hiti", + "logo": "http://datacache.radio.si/api/radiostations/logo/toti.svg", + "liveAudio": "http://live.radio.si/Toti", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/toti/json", + "epg": "http://spored.radio.si/api/now/toti", + "defaultText": "www.totiradio.si", + "www": "https://totiradio.si/", + "mountPoints": [ + "Maxi", + "Toti" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38651220220" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://totiradio.si/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/totiradio?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=91414", + "rpUid": "705108", + "dabUser": "toti", + "dabPass": "wmAos05tECsmf", + "dabDefaultImg": "http://media.radio.si/logo/dns/toti/320x240.png", + "small": false + }, + { + "id": "Antena", + "title": "Radio Antena", + "slogan": "Največ hitov, najmanj govora", + "logo": "http://datacache.radio.si/api/radiostations/logo/antena.svg", + "liveAudio": "http://live.radio.si/Antena", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/antena/json", + "epg": "http://spored.radio.si/api/now/antena", + "defaultText": "www.radioantena.si", + "www": "https://radioantena.si/", + "mountPoints": [ + "Antena" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38612425630 " + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radioantena.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/user/radioantenaslo" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/HitradioAntena" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radioantena.si/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radioantena?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=37864", + "rpUid": "705161", + "dabUser": "radioantena", + "dabPass": "nGkMhFk77jnBQ", + "dabDefaultImg": "http://media.radio.si/logo/dns/antena/320x240.png", + "small": false + }, + { + "id": "BestFM", + "title": "BestFM", + "slogan": "Muska, muska, muska", + "logo": "http://datacache.radio.si/api/radiostations/logo/bestfm.svg", + "liveAudio": "http://live.radio.si/BestFM", + "liveVideo": "https://radio.serv.si/BestTV/video.m3u8", + "poster": "https://cdn1.radio.si/900/screenbest_6559e3ac8.jpg", + "lastSongs": "http://data.radio.si/api/lastsongsxml/bestfm/json", + "epg": null, + "defaultText": "www.bestfm.si", + "www": "https://bestfm.si/", + "mountPoints": [ + "BestFM" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38673372030" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://bestfm.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/profile.php?id=100086776586975" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/bestfm.si/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705115", + "dabUser": "bestfm", + "dabPass": "momo911x", + "dabDefaultImg": "http://media.radio.si/logo/dns/bestfm/BestFM_DAB.jpg", + "small": false + }, + { + "id": "Krka", + "title": "Radio Krka", + "slogan": "Dolenjska v srcu", + "logo": "http://datacache.radio.si/api/radiostations/logo/krka.svg", + "liveAudio": "http://live.radio.si/Krka", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/krka/json", + "epg": "", + "defaultText": "www.radiokrka.si", + "www": "https://radiokrka.si/", + "mountPoints": [ + "Krka" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38673372030" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radiokrka.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/user/radiokrka" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiokrka" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiokrka/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705120", + "dabUser": "krka", + "dabPass": "qBi6z!um2Gm", + "dabDefaultImg": "http://media.radio.si/logo/dns/krka/RadioKrka_DAB.jpg", + "small": false + }, + { + "id": "Klasik", + "title": "Klasik radio", + "slogan": "Glasba, ki vas sprosti", + "logo": "https://data.radio.si/api/radiostations/logo/klasik.svg", + "liveAudio": "http://live.radio.si/Klasik", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/klasik/json", + "epg": "", + "defaultText": "www.klasikradio.si", + "www": "https://www.klasikradio.si/", + "mountPoints": [ + "Klasik" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38612425630" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.klasikradio.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/profile.php?id=100064736766638" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705176", + "dabUser": "klasik", + "dabPass": "mQTpTR9XEbiF", + "dabDefaultImg": "http://media.radio.si/logo/dns/klasik/320x240.png", + "small": false + }, + { + "id": "Maxi", + "title": "Toti Maxi", + "slogan": "Sama dobra glasba", + "logo": "https://data.radio.si/api/radiostations/logo/maxi.svg", + "liveAudio": "http://live.radio.si/Maxi", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/toti/json", + "epg": "", + "defaultText": "www.totimaxi.si", + "www": "https://www.radiomaxi.si/", + "mountPoints": [ + "Maxi" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38631628444" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radiomaxi.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/profile.php?id=100064736766638" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiosalomon" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiosalomon/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/totiradio?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=37998", + "rpUid": "705109", + "dabUser": "salomon", + "dabPass": "a1bfadd8b8ut", + "dabDefaultImg": "http://media.radio.si/logo/dns/salomon/RadioSalomon_DAB.jpg", + "small": false + }, + { + "id": "Salomon", + "title": "Radio Salomon", + "slogan": "Izbrana urbana glasba", + "logo": "http://datacache.radio.si/api/radiostations/logo/salomon.svg", + "liveAudio": "http://live.radio.si/Salomon", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/salomon/json", + "epg": "", + "defaultText": "www.radiosalomon.si", + "www": "https://radiosalomon.si/", + "mountPoints": [ + "Salomon" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+386015880111" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radiosalomon.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UCd7OpUbSIoZarJgwFf4aIxw" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiosalomon" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiosalomon/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705116", + "dabUser": "salomon", + "dabPass": "a1bfadd8b8ut", + "dabDefaultImg": "http://media.radio.si/logo/dns/salomon/RadioSalomon_DAB.jpg", + "small": false + }, + { + "id": "Ptuj", + "title": "Radio Ptuj", + "slogan": "Največje uspešnice vseh časov", + "logo": "https://data.radio.si/api/radiostations/logo/ptuj.svg", + "liveAudio": "http://live.radio.si/Ptuj", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/ptuj/json", + "epg": "", + "defaultText": "www.radio-ptuj.si", + "www": "https://www.radio-ptuj.si/", + "mountPoints": [ + "Ptuj" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38627493420" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radio-ptuj.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/@RadioPtuj" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RadioPtuj" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radio_ptuj/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705119", + "dabUser": "ptuj", + "dabPass": "cwv4jXVKMYT", + "dabDefaultImg": "http://media.radio.si/logo/dns/ptuj/RadioPtuj_DAB.jpg", + "small": false + }, + { + "id": "Fantasy", + "title": "Radio Fantasy", + "slogan": "Same dobre vibracije", + "logo": "https://data.radio.si/api/radiostations/logo/fantasy.svg", + "liveAudio": "http://live.radio.si/Fantasy", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/fantasy/json", + "epg": "http://spored.radio.si/api/now/robin", + "defaultText": "", + "www": "https://rfantasy.si/", + "mountPoints": [ + "Fantasy" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38634903921" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.rfantasy.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/c/RadioFantasyTv" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RadioFantasySlo" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiofantasyslo/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiofantasy?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=61118", + "rpUid": "", + "dabUser": "radiorobin", + "dabPass": "rt5mo9b9", + "dabDefaultImg": "http://media.radio.si/logo/dns/robin/320x240.png", + "small": false + }, + { + "id": "Robin", + "title": "Radio Robin", + "slogan": "Brez tebe ni mene", + "logo": "https://data.radio.si/api/radiostations/logo/robin.svg", + "liveAudio": "http://live.radio.si/Robin", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/robin/json", + "epg": "http://spored.radio.si/api/now/robin", + "defaultText": "www.robin.si", + "www": "https://www.robin.si/", + "mountPoints": [ + "Robin" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38653302822" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.robin.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UCACfPObotnJAnVXfCZNMlUg" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/Radio.Robin.goriski" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radio_robin/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiorobin?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=37984", + "rpUid": "705103", + "dabUser": "radiorobin", + "dabPass": "rt5mo9b9", + "dabDefaultImg": "http://media.radio.si/logo/dns/robin/320x240.png", + "small": false + }, + { + "id": "Koroski", + "title": "Koroški radio", + "slogan": "Ritem Koroške", + "logo": "https://data.radio.si/api/radiostations/logo/koroski.svg", + "liveAudio": "http://live.radio.si/Koroski", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/koroski/json", + "epg": "http://spored.radio.si/api/now/koroski", + "defaultText": "www.koroski-radio.si", + "www": "https://www.koroski-radio.si/", + "mountPoints": [ + "Koroski" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38628841245" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.koroski-radio.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UCLwH6lX4glK4o1N77JkeaJw" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/KoroskiRadio" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/koroski_r/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705105", + "dabUser": "koroski", + "dabPass": "num87dhket", + "dabDefaultImg": "http://media.radio.si/logo/dns/koroski/320x240.png", + "small": true + }, + { + "id": "VeseljakZlatiZvoki", + "title": "Veseljak Zlati zvoki", + "slogan": "Najvecja zakladnica slovenske domace glasbe", + "logo": "https://data.radio.si/api/radiostations/logo/veseljakzlatizvoki.svg", + "liveAudio": "http://live.radio.si/VeseljakZlatiZvoki", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/veseljakzlatizvoki/json", + "epg": "", + "defaultText": "www.veseljak.si", + "www": "https://www.veseljak.si/", + "mountPoints": [ + "VeseljakZlatiZvoki" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38615880110" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://veseljak.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/c/VESELJAKNAJBOLJSADOMACAGLASBA" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RadioVeseljak" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/veseljak.si/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705175", + "dabUser": "zlatizvoki", + "dabPass": "4jeeUnjA4qYV", + "dabDefaultImg": "http://media.radio.si/logo/dns/veseljakzlatizvoki/RadioVeseljakZlatiZvoki_DAB.jpg", + "small": false + }, + { + "id": "RockMB", + "title": "Rock Maribor", + "slogan": "100% Rock", + "logo": "https://data.radio.si/api/radiostations/logo/rockmb.svg", + "liveAudio": "http://live.radio.si/RockMB", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/radio1rock/json", + "epg": "", + "defaultText": "www.rockmaribor.si", + "www": "https://rockmaribor.si/", + "mountPoints": [ + "RockMB" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://rockmaribor.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RockMaribor.si" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UC99aNwZXokG6nnJLfSn5DSw" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiocelje" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiocelje/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/rockmaribor?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=61116", + "rpUid": "705107", + "dabUser": "celje", + "dabPass": "dunk7815g", + "dabDefaultImg": "http://media.radio.si/logo/dns/celje/RadioCelje_DAB.jpg", + "small": false + }, + { + "id": "Kranj", + "title": "Radio Kranj", + "slogan": "", + "logo": "https://data.radio.si/api/radiostations/logo/kranj.svg", + "liveAudio": "http://live.radio.si/Kranj", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/kranj/json", + "epg": "http://spored.radio.si/api/now/kranj", + "defaultText": "www.radio-kranj.si", + "www": "https://radio-kranj.si/", + "mountPoints": [ + "Kranj" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38651303505" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radio-kranj.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UCe_Ze0SEHCSLLNUbWM0aBgA" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/pages/Radio-Kranj/1760816170864847" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiokranj/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiokranj?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=61102", + "rpUid": "705104", + "dabUser": "kranj", + "dabPass": "ui8z3Ezzosyxw", + "dabDefaultImg": "http://media.radio.si/logo/dns/kranj/320x240.png", + "small": false + }, + { + "id": "Celje", + "title": "Radio Celje", + "slogan": "Vedno z menoj", + "logo": "https://data.radio.si/api/radiostations/logo/celje.svg", + "liveAudio": "http://live.radio.si/Celje", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/celje/json", + "epg": "", + "defaultText": "www.radiocelje.si", + "www": "https://www.radiocelje.si/", + "mountPoints": [ + "Celje" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+386034225100" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radiocelje.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UC99aNwZXokG6nnJLfSn5DSw" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiocelje" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiocelje/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705117", + "dabUser": "celje", + "dabPass": "dunk7815g", + "dabDefaultImg": "http://media.radio.si/logo/dns/celje/RadioCelje_DAB.jpg", + "small": false + }, + { + "id": "Triglav", + "title": "Radio Triglav", + "slogan": "Radio za radovedne", + "logo": "https://data.radio.si/api/radiostations/logo/triglav.svg", + "liveAudio": "http://live.radio.si/Triglav", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/triglav/json", + "epg": "http://spored.radio.si/api/now/triglav", + "defaultText": "www.radiotriglav.si", + "www": "https://radiotriglav.si/", + "mountPoints": [ + "Triglav" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38651654064" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radiotriglav.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RadioTriglav" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiotriglav/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiotriglav?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=38020", + "rpUid": "705106", + "dabUser": "triglav", + "dabPass": "ogFLUKodMUCB5", + "dabDefaultImg": "http://media.radio.si/logo/dns/triglav/320x240.png", + "small": false + }, + { + "id": "Velenje", + "title": "Radio Velenje", + "slogan": "Ker smo radi na kamot", + "logo": "http://datacache.radio.si/api/radiostations/logo/velenje.svg", + "liveAudio": "http://live.radio.si/Velenje", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/velenje/json", + "epg": "", + "defaultText": "www.veseljak.si", + "www": "https://veseljak.si/", + "mountPoints": [ + "Velenje" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38615880110" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://veseljak.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/c/VESELJAKNAJBOLJSADOMACAGLASBA" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RadioVeseljak" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/veseljak.si/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705118", + "dabUser": "velenje", + "dabPass": "e9mopbu11", + "dabDefaultImg": "http://media.radio.si/logo/dns/velenje/RadioVelenje_DAB.jpg", + "small": false + }, + { + "id": "AktualK", + "title": "Radio Aktual Kum", + "slogan": "Narejen za vaša ušesa", + "logo": "http://datacache.radio.si/api/radiostations/logo/aktualk.svg", + "liveAudio": "http://live.radio.si/AktualK", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/aktualk/json", + "epg": "", + "defaultText": "", + "www": null, + "mountPoints": [ + "AktualK" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+386158801430" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radioaktual.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/user/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/stories/radioaktual/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "", + "dabUser": null, + "dabPass": null, + "dabDefaultImg": null, + "small": false + }, + { + "id": "AktualRomantika", + "title": "Radio Aktual - Romantika", + "slogan": "Kot nezna dlan, ki boza te", + "logo": "http://datacache.radio.si/api/radiostations/logo/aktualromantika.svg", + "liveAudio": "http://live.radio.si/AktualRomantika", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/aktualromantika/json", + "epg": "", + "defaultText": "", + "www": null, + "mountPoints": [ + "AktualRomantika" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+386158801430" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radioaktual.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/user/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/stories/radioaktual/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705174", + "dabUser": "romantika", + "dabPass": "Z75biJ5t7CpK", + "dabDefaultImg": "http://media.radio.si/logo/dns/aktualromantika/RadioAktualRomnatika_DAB.jpg", + "small": false + }, + { + "id": "Stop", + "title": "Stop", + "slogan": "Revija Stop: Več kot pol stoletja ob vaši strani!", + "logo": "http://datacache.radio.si/api/radiostations/logo/stop.svg", + "liveAudio": "http://live.radio.si/Stop", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/stop/json", + "epg": "", + "defaultText": "www.revijastop.si", + "www": "https://revijastop.si/", + "mountPoints": [ + "Stop" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://revijastop.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://totiradio.si/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "", + "dabUser": null, + "dabPass": null, + "dabDefaultImg": null, + "small": false + }, + { + "id": "Radio1SLO", + "title": "Radio 1 slovenski hiti", + "slogan": "Sama dobra slovenska glasba", + "logo": "http://datacache.radio.si/api/radiostations/logo/radio1slo.svg", + "liveAudio": "http://live.radio.si/Radio1SLO", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/radio1slo/json", + "epg": "", + "defaultText": "www.radio1.si", + "www": "https://www.radio1.si", + "mountPoints": [ + "Radio1SLO" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38651300300" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "http://radio1.si" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "http://www.youtube.com/user/radio1slovenia" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "http://facebook.com/RadioEna" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "http://www.instagram.com/radio1slo" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705110", + "dabUser": "1slovenske", + "dabPass": "ionb9hkd48", + "dabDefaultImg": "http://media.radio.si/logo/dns/radio1slo/320x240.png", + "small": false + }, + { + "id": "RockCE", + "title": "Rock Celje", + "slogan": "100% Rock", + "logo": "https://data.radio.si/api/radiostations/logo/rockce.svg", + "liveAudio": "http://live.radio.si/RockCE", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/rockce/json", + "epg": null, + "defaultText": "www.rock-celje.si", + "www": "https://rock-celje.si/", + "mountPoints": [ + "RockCE" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38628841245" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.rock-celje.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RockCelje" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiosalomon" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiosalomon/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "", + "dabUser": null, + "dabPass": null, + "dabDefaultImg": null, + "small": false + }, + { + "id": "Radio1Bozicne", + "title": "ROŠKARJEVE BOŽIČNE", + "slogan": "100% Božične", + "logo": "https://data.radio.si/api/radiostations/logo/radio1bozicne.svg", + "liveAudio": "http://live.radio1.si/Radio1Bozicne", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/radio1bozicne/json", + "epg": null, + "defaultText": "www.radio1.si", + "www": "https://radio1.si/", + "mountPoints": [ + "Radio1Bozicne" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radio1.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radiosalomon.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UCd7OpUbSIoZarJgwFf4aIxw" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiosalomon" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiosalomon/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "", + "dabUser": null, + "dabPass": null, + "dabDefaultImg": null, + "small": false + }, + { + "id": "TotiBozicni", + "title": "TOTI BOŽIČNI RADIO", + "slogan": "Tvoje mesto, tvoj radio", + "logo": "http://datacache.radio.si/api/radiostations/logo/totibozicne.svg", + "liveAudio": "http://live.radio.si/TotiBozicne", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/totibozicni/json", + "epg": null, + "defaultText": "www.totiradio.si", + "www": "https://totiradio.si/", + "mountPoints": [ + "TotiBozicni" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38651220220" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://totiradio.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/user/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/stories/radioaktual/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "", + "dabUser": null, + "dabPass": null, + "dabDefaultImg": null, + "small": false + }, + { + "id": "Hit", + "title": "Radio HIT", + "slogan": "Samo nostalgija", + "logo": "http://datacache.radio.si/api/radiostations/logo/hit.svg", + "liveAudio": "http://live.radio.si/Hit", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/hit/json", + "epg": "http://spored.radio.si/api/now/hit", + "defaultText": "www.radiohit.si", + "www": "https://radiohit.si/", + "mountPoints": [ + "Hit" + ], + "social": [], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiohit?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=61120", + "rpUid": "705141", + "dabUser": null, + "dabPass": null, + "dabDefaultImg": "http://media.radio.si/logo/dns/hit/320x240.png", + "small": false + } +] \ No newline at end of file diff --git a/android/app/src/main/assets/styles.css b/android/app/src/main/assets/styles.css new file mode 100644 index 0000000..37e8c62 --- /dev/null +++ b/android/app/src/main/assets/styles.css @@ -0,0 +1,606 @@ +:root { + --bg-gradient: linear-gradient(135deg, #7b7fd8, #b57cf2); + --glass-bg: rgba(255, 255, 255, 0.1); + --glass-border: rgba(255, 255, 255, 0.2); + --accent: #dfa6ff; + --accent-glow: rgba(223, 166, 255, 0.5); + --text-main: #ffffff; + --text-muted: rgba(255, 255, 255, 0.7); + --danger: #cf6679; + --success: #7dffb3; + --card-radius: 10px; +} + +* { + box-sizing: border-box; + user-select: none; + -webkit-user-drag: none; + cursor: default; +} + +/* Hide Scrollbars */ +::-webkit-scrollbar { + display: none; +} + +body { + margin: 0; + padding: 0; + height: 100vh; + width: 100vw; + background: linear-gradient(-45deg, #7b7fd8, #b57cf2, #8b5cf6, #6930c3, #7b7fd8); + background-size: 400% 400%; + animation: gradientShift 12s ease-in-out infinite; + font-family: 'Segoe UI', system-ui, sans-serif; + color: var(--text-main); + overflow: hidden; + display: flex; + justify-content: center; + align-items: center; +} + +@keyframes gradientShift { + 0% { + background-position: 0% 50%; + } + 25% { + background-position: 100% 50%; + } + 50% { + background-position: 50% 100%; + } + 75% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} + +/* Background Blobs */ +.bg-shape { + position: absolute; + border-radius: 50%; + filter: blur(60px); + z-index: 0; + opacity: 0.6; + animation: float 10s infinite alternate; +} + +.shape-1 { + width: 300px; + height: 300px; + background: #5e60ce; + top: -50px; + left: -50px; +} + +.shape-2 { + width: 250px; + height: 250px; + background: #ff6bf0; + bottom: -50px; + right: -50px; + animation-delay: -5s; +} + +@keyframes float { + 0% { transform: translate(0, 0); } + 100% { transform: translate(30px, 30px); } +} + +.app-container { + width: 100%; + height: 100%; + position: relative; + padding: 10px; /* Slight padding from window edges if desired, or 0 */ +} + +.glass-card { + position: relative; + z-index: 1; + width: 100%; + height: 100%; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + backdrop-filter: blur(24px); + border-radius: var(--card-radius); + display: flex; + flex-direction: column; + padding: 24px; + box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2); +} + +/* Header */ +header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + -webkit-app-region: drag; /* Draggable area */ +} + +.header-info { + text-align: center; + flex: 1; + display: flex; + flex-direction: column; + align-items: center; +} + +.app-title { + font-weight: 600; + font-size: 1rem; + color: var(--text-main); +} + +.status-indicator { + font-size: 0.8rem; + color: var(--success); + margin-top: 4px; + display: flex; + align-items: center; + gap: 6px; +} + +.status-dot { + width: 6px; + height: 6px; + background-color: var(--success); + border-radius: 50%; + box-shadow: 0 0 8px var(--success); +} + +.icon-btn { + background: none; + border: none; + color: var(--text-main); + padding: 8px; + cursor: pointer; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s; + -webkit-app-region: no-drag; /* Buttons clickable */ +} + +.icon-btn:hover { + background: rgba(255, 255, 255, 0.1); +} + +.header-buttons { + display: flex; + gap: 4px; + align-items: center; + -webkit-app-region: no-drag; +} + +.close-btn:hover { + background: rgba(207, 102, 121, 0.3) !important; + color: var(--danger); +} + +/* Artwork */ +.artwork-section { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 20px; +} + +.artwork-container { + width: 220px; + height: 220px; + border-radius: 24px; + padding: 6px; /* spacing for ring */ + background: linear-gradient(135deg, rgba(255,255,255,0.1), rgba(255,255,255,0)); + box-shadow: 5px 5px 15px rgba(0,0,0,0.1), inset 1px 1px 2px rgba(255,255,255,0.3); +} + +.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); +} + +.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.18; + /* slightly smaller blur for subtle definition */ + filter: blur(6px); +} + +.b1 { width: 110px; height: 110px; left: 8%; top: 20%; background: radial-gradient(circle at 30% 30%, #c77dff, #8b5cf6); animation: float1 6s ease-in-out infinite; } +.b2 { width: 85px; height: 85px; right: 6%; top: 10%; background: radial-gradient(circle at 30% 30%, #7bffd1, #7dffb3); animation: float2 5.5s ease-in-out infinite; } +.b3 { width: 95px; height: 95px; left: 20%; bottom: 12%; background: radial-gradient(circle at 20% 20%, #ffd07a, #ff6bf0); animation: float3 7s ease-in-out infinite; } +.b4 { width: 70px; height: 70px; right: 24%; bottom: 18%; background: radial-gradient(circle at 30% 30%, #6bd3ff, #4ea8de); animation: float4 6.5s ease-in-out infinite; } +.b5 { width: 50px; height: 50px; left: 46%; top: 36%; background: radial-gradient(circle at 40% 40%, #ffa6d6, #c77dff); animation: float5 8s ease-in-out infinite; } + +/* Additional blobs */ +.b6 { width: 75px; height: 75px; left: 12%; top: 48%; background: radial-gradient(circle at 30% 30%, #bde7ff, #6bd3ff); animation: float6 6.8s ease-in-out infinite; } +.b7 { width: 42px; height: 42px; right: 10%; top: 42%; background: radial-gradient(circle at 40% 40%, #ffd9b3, #ffd07a); animation: float7 7.2s ease-in-out infinite; } +.b8 { width: 70px; height: 70px; left: 34%; bottom: 8%; background: radial-gradient(circle at 30% 30%, #e3b6ff, #c77dff); animation: float8 6.4s ease-in-out infinite; } +.b9 { width: 36px; height: 36px; right: 34%; bottom: 6%; background: radial-gradient(circle at 30% 30%, #9ef7d3, #7bffd1); animation: float9 8.4s ease-in-out infinite; } +.b10 { width: 30px; height: 30px; 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 { + text-align: center; + margin-bottom: 20px; +} + +.track-info h2 { + margin: 0; + font-size: 1.5rem; + font-weight: 600; + text-shadow: 0 2px 4px rgba(0,0,0,0.2); +} + +.track-info p { + margin: 6px 0 0; + color: var(--text-muted); + font-size: 0.95rem; +} + +/* Progress Bar (Visual) */ +.progress-container { + width: 100%; + height: 4px; + background: rgba(255,255,255,0.1); + border-radius: 2px; + margin-bottom: 30px; + position: relative; +} + +.progress-fill { + width: 100%; /* Live always full or pulsing */ + height: 100%; + background: linear-gradient(90deg, var(--accent), #fff); + border-radius: 2px; + opacity: 0.8; + box-shadow: 0 0 10px var(--accent-glow); +} + +.progress-handle { + position: absolute; + right: 0; + top: 50%; + transform: translate(50%, -50%); + width: 12px; + height: 12px; + background: #fff; + border-radius: 50%; + box-shadow: 0 0 10px rgba(255,255,255,0.8); +} + +/* Controls */ +.controls-section { + display: flex; + justify-content: center; + align-items: center; + gap: 30px; + margin-bottom: 30px; +} + +.control-btn { + background: none; + border: none; + color: var(--text-main); + cursor: pointer; + transition: transform 0.1s, opacity 0.2s; + display: flex; + align-items: center; + justify-content: center; +} + +.control-btn:active { + transform: scale(0.9); +} + +.control-btn.secondary { + width: 48px; + height: 48px; + border-radius: 50%; + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.1); + box-shadow: 0 4px 10px rgba(0,0,0,0.1); +} + +.control-btn.primary { + width: 72px; + height: 72px; + border-radius: 50%; + background: linear-gradient(135deg, rgba(255,255,255,0.2), rgba(255,255,255,0.05)); + border: 1px solid rgba(255,255,255,0.3); + box-shadow: 0 8px 20px rgba(0,0,0,0.2), inset 0 0 10px rgba(255,255,255,0.1); + color: #fff; +} + +.control-btn.primary svg { + filter: drop-shadow(0 0 5px var(--accent-glow)); +} + +/* Playing state - pulsing glow ring */ +.control-btn.primary.playing { + animation: pulse-ring 2s ease-in-out infinite; +} + +@keyframes pulse-ring { + 0%, 100% { + box-shadow: 0 8px 20px rgba(0,0,0,0.2), + inset 0 0 10px rgba(255,255,255,0.1), + 0 0 0 0 rgba(223, 166, 255, 0.7); + } + 50% { + box-shadow: 0 8px 20px rgba(0,0,0,0.2), + inset 0 0 10px rgba(255,255,255,0.1), + 0 0 0 8px rgba(223, 166, 255, 0); + } +} + +/* Icon container prevents layout jump */ +.icon-container { + position: relative; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; +} + +.icon-container svg { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.hidden { + display: none !important; +} + +/* Volume */ +.volume-section { + display: flex; + align-items: center; + gap: 12px; + margin-top: auto; + padding: 0 10px; +} + +.slider-container { + flex: 1; +} + +input[type=range] { + width: 100%; + background: transparent; + -webkit-appearance: none; + appearance: none; +} + +input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 4px; + cursor: pointer; + background: rgba(255,255,255,0.2); + border-radius: 2px; +} + +input[type=range]::-webkit-slider-thumb { + height: 16px; + width: 16px; + border-radius: 50%; + background: #ffffff; + cursor: pointer; + -webkit-appearance: none; + margin-top: -6px; /* align with track */ + box-shadow: 0 0 10px rgba(0,0,0,0.2); +} + +#volume-value { + font-size: 0.8rem; + font-weight: 500; + width: 30px; + text-align: right; +} + +.icon-btn.small { + padding: 0; + width: 24px; + height: 24px; +} + +/* Cast Overlay (Beautified as per layout2_plan.md) */ +.overlay { + position: fixed; + inset: 0; + background: rgba(20, 10, 35, 0.45); + backdrop-filter: blur(14px); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s; +} + +.overlay:not(.hidden) { + opacity: 1; + pointer-events: auto; +} + +/* Modal */ +.modal { + width: min(420px, calc(100vw - 48px)); + padding: 22px; + border-radius: 22px; + background: rgba(30, 30, 40, 0.82); + border: 1px solid rgba(255,255,255,0.12); + box-shadow: 0 30px 80px rgba(0,0,0,0.6); + color: #fff; + animation: pop 0.22s ease; + -webkit-app-region: no-drag; +} + +@keyframes pop { + from { transform: scale(0.94); opacity: 0; } + to { transform: scale(1); opacity: 1; } +} + +.modal h2 { + margin: 0 0 14px; + text-align: center; + font-size: 20px; +} + +/* Device list */ +.device-list { + list-style: none; + padding: 10px 5px; + margin: 0 0 18px; + max-height: 360px; + overflow-y: auto; +} + +/* Device row */ +.device { + padding: 12px 14px; + border-radius: 14px; + margin-bottom: 8px; + cursor: pointer; + background: rgba(255,255,255,0.05); + transition: transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease; + text-align: left; +} + +.device:hover { + background: rgba(255,255,255,0.10); + transform: translateY(-1px); +} + +.device .device-main { + font-size: 15px; + font-weight: 600; + color: var(--text-main); +} + +.device .device-sub { + margin-top: 3px; + font-size: 12px; + opacity: 0.7; + color: var(--text-muted); +} + +/* Selected device */ +.device.selected { + background: linear-gradient(135deg, #c77dff, #8b5cf6); + box-shadow: 0 0 18px rgba(199,125,255,0.65); + color: #111; +} + +.device.selected .device-main, +.device.selected .device-sub { + color: #111; +} + +.device.selected .device-sub { + opacity: 0.85; +} + +/* Cancel button */ +.btn.cancel { + width: 100%; + padding: 12px; + border-radius: 999px; + border: none; + background: #d16b7d; + color: #fff; + font-size: 15px; + cursor: pointer; + transition: transform 0.15s ease, background 0.2s; + font-weight: 600; +} + +.btn.cancel:hover { + transform: scale(1.02); + background: #e17c8d; +} diff --git a/android/app/src/main/jniLibs/arm64-v8a/libradio_tauri_lib.so b/android/app/src/main/jniLibs/arm64-v8a/libradio_tauri_lib.so new file mode 100644 index 0000000..c5f1aea Binary files /dev/null and b/android/app/src/main/jniLibs/arm64-v8a/libradio_tauri_lib.so differ diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libradio_tauri_lib.so b/android/app/src/main/jniLibs/armeabi-v7a/libradio_tauri_lib.so new file mode 100644 index 0000000..3e48392 Binary files /dev/null and b/android/app/src/main/jniLibs/armeabi-v7a/libradio_tauri_lib.so differ diff --git a/scripts/build-android.ps1 b/scripts/build-android.ps1 new file mode 100644 index 0000000..0d32bef --- /dev/null +++ b/scripts/build-android.ps1 @@ -0,0 +1,206 @@ +<# +Build helper for Android (Windows PowerShell) + +What it does: +- Checks for required commands (`npm`, `rustup`, `cargo`, `cargo-ndk`) +- Builds frontend (runs `npm run build` if `dist`/`build` not present) +- Copies frontend files from `dist` or `src` into `android/app/src/main/assets` +- Builds Rust native libs using `cargo-ndk` (if available) for `aarch64` and `armv7` +- Copies produced `.so` files into `android/app/src/main/jniLibs/*` + +Note: This script prepares the Android project. To produce the APK, open `android/` in Android Studio and run Build -> Assemble, or run `gradlew assembleDebug` locally. +#> + +Set-StrictMode -Version Latest + +function Check-Command($name) { + $which = Get-Command $name -ErrorAction SilentlyContinue + return $which -ne $null +} + +Write-Output "Starting Android prep script..." + +if (-not (Check-Command npm)) { Write-Warning "npm not found in PATH. Install Node.js to build frontend." } +if (-not (Check-Command rustup)) { Write-Warning "rustup not found in PATH. Install Rust toolchain." } +if (-not (Check-Command cargo)) { Write-Warning "cargo not found in PATH." } + +$cargoNdkAvailable = Check-Command cargo-ndk +if (-not $cargoNdkAvailable) { Write-Warning "cargo-ndk not found. Native libs will not be built. Install via 'cargo install cargo-ndk'" } + +# Determine repository root (parent of the scripts folder) +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$root = Split-Path -Parent $scriptDir +Push-Location $root + +# Prefer Tauri-generated Android Studio project (tauri android init) +$androidRoot = Join-Path $root 'src-tauri\gen\android' +if (-not (Test-Path $androidRoot)) { + # Legacy fallback (non-Tauri project) + $androidRoot = Join-Path $root 'android' +} + +function Escape-LocalPropertiesPath([string]$p) { + # local.properties expects ':' escaped and backslashes doubled on Windows. + # Use plain string replacements to avoid regex escaping pitfalls. + return ($p.Replace('\', '\\').Replace(':', '\:')) +} + +# Ensure Android SDK/NDK locations are set for Gradle (local.properties) +$sdkRoot = $env:ANDROID_SDK_ROOT +if (-not $sdkRoot) { $sdkRoot = $env:ANDROID_HOME } +if (-not $sdkRoot) { $sdkRoot = Join-Path $env:LOCALAPPDATA 'Android\Sdk' } + +$ndkRoot = $env:ANDROID_NDK_ROOT +if (-not $ndkRoot) { $ndkRoot = $env:ANDROID_NDK_HOME } +if (-not $ndkRoot -and (Test-Path (Join-Path $sdkRoot 'ndk'))) { + $ndkVersions = Get-ChildItem -Path (Join-Path $sdkRoot 'ndk') -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending + if ($ndkVersions -and (@($ndkVersions)).Count -gt 0) { $ndkRoot = @($ndkVersions)[0].FullName } +} + +if (Test-Path $androidRoot) { + $localPropsPath = Join-Path $androidRoot 'local.properties' + $lines = @() + if ($sdkRoot) { $lines += "sdk.dir=$(Escape-LocalPropertiesPath $sdkRoot)" } + if ($ndkRoot) { $lines += "ndk.dir=$(Escape-LocalPropertiesPath $ndkRoot)" } + if ($lines.Count -gt 0) { + Set-Content -Path $localPropsPath -Value ($lines -join "`n") -Encoding ASCII + Write-Output "Wrote Android SDK/NDK config to: $localPropsPath" + } +} + +# Build frontend (optional) +Write-Output "Preparing frontend files..." +$distDirs = @('dist','build') +$foundDist = $null +foreach ($d in $distDirs) { + if (Test-Path (Join-Path $root $d)) { $foundDist = $d; break } +} + +if (-not $foundDist) { + # IMPORTANT: `npm run build` in this repo runs `tauri build`, which is a desktop bundling step. + # For Android prep we only need web assets, so we fall back to copying `src/` as assets. + Write-Warning "No dist/build output found — copying `src/` as assets (skipping `npm run build` to avoid desktop bundling)." +} + +$assetsDst = Join-Path $androidRoot 'app\src\main\assets' +if (-not (Test-Path $assetsDst)) { New-Item -ItemType Directory -Path $assetsDst -Force | Out-Null } + +if ($foundDist) { + Write-Output "Copying frontend from '$foundDist' to Android assets..." + robocopy (Join-Path $root $foundDist) $assetsDst /MIR | Out-Null +} else { + Write-Output "Copying raw 'src' to Android assets..." + robocopy (Join-Path $root 'src') $assetsDst /MIR | Out-Null +} + +# Build native libs if cargo-ndk available +if ($cargoNdkAvailable) { + Write-Output "Building Rust native libs via cargo-ndk from project root: $root" + try { + # Build from the Rust crate directory `src-tauri` + $crateDir = Join-Path $root 'src-tauri' + if (-not (Test-Path (Join-Path $crateDir 'Cargo.toml'))) { + Write-Warning "Cargo.toml not found in src-tauri; skipping native build." + } else { + # Prefer Ninja generator for CMake if available (avoids Visual Studio generator issues) + # Restore env vars at the end so we don't pollute the current PowerShell session. + $oldCmakeGenerator = $env:CMAKE_GENERATOR + $oldCmakeMakeProgram = $env:CMAKE_MAKE_PROGRAM + $ninjaCmd = Get-Command ninja -ErrorAction SilentlyContinue + if ($ninjaCmd) { + Write-Output "Ninja detected at $($ninjaCmd.Source); setting CMake generator to Ninja." + $env:CMAKE_GENERATOR = 'Ninja' + $env:CMAKE_MAKE_PROGRAM = $ninjaCmd.Source + } else { + Write-Warning "Ninja not found in PATH. Installing Ninja or adding it to PATH is strongly recommended to avoid Visual Studio CMake generator on Windows." + } + + # Attempt to locate Android NDK if environment variables are not set + if (-not $env:ANDROID_NDK_ROOT -and -not $env:ANDROID_NDK_HOME) { + $candidates = @() + if ($env:ANDROID_SDK_ROOT) { $candidates += Join-Path $env:ANDROID_SDK_ROOT 'ndk' } + if ($env:ANDROID_HOME) { $candidates += Join-Path $env:ANDROID_HOME 'ndk' } + $candidates += Join-Path $env:LOCALAPPDATA 'Android\sdk\ndk' + $candidates += Join-Path $env:USERPROFILE 'AppData\Local\Android\sdk\ndk' + $candidates += 'C:\Program Files (x86)\Android\AndroidNDK' + + foreach ($cand in $candidates) { + if (Test-Path $cand) { + $versions = Get-ChildItem -Path $cand -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending + if ($versions -and (@($versions)).Count -gt 0) { + $ndkPath = @($versions)[0].FullName + Write-Output "Detected Android NDK at: $ndkPath" + $env:ANDROID_NDK_ROOT = $ndkPath + $env:ANDROID_NDK = $ndkPath + break + } + } + } + if (-not $env:ANDROID_NDK_ROOT) { Write-Warning "ANDROID_NDK_ROOT/ANDROID_NDK not set and no NDK found in common locations. Set ANDROID_NDK_ROOT to your NDK path." } + } else { + Write-Output "Using existing ANDROID_NDK_ROOT: $($env:ANDROID_NDK_ROOT)" + if (-not $env:ANDROID_NDK) { $env:ANDROID_NDK = $env:ANDROID_NDK_ROOT } + } + + # Ensure expected external binary placeholders exist so Tauri bundling doesn't fail + $binariesDir = Join-Path $crateDir 'binaries' + if (-not (Test-Path $binariesDir)) { New-Item -ItemType Directory -Path $binariesDir -Force | Out-Null } + $placeholder1 = Join-Path $binariesDir 'RadioPlayer-aarch64-linux-android' + $placeholder2 = Join-Path $binariesDir 'RadioPlayer-armv7-linux-androideabi' + if (-not (Test-Path $placeholder1)) { New-Item -ItemType File -Path $placeholder1 -Force | Out-Null; Write-Output "Created placeholder: $placeholder1" } + if (-not (Test-Path $placeholder2)) { New-Item -ItemType File -Path $placeholder2 -Force | Out-Null; Write-Output "Created placeholder: $placeholder2" } + + # If a previous build used a different CMake generator (e.g., Visual Studio), aws-lc-sys can fail with + # "Does not match the generator used previously". Clean only the aws-lc-sys CMake build dirs. + $awsLcBuildDirs = Get-ChildItem -Path (Join-Path $crateDir 'target') -Recurse -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.Name -like 'aws-lc-sys-*' } + foreach ($d in @($awsLcBuildDirs)) { + $cmakeBuildDir = Join-Path $d.FullName 'out\build' + $cmakeCache = Join-Path $cmakeBuildDir 'CMakeCache.txt' + if (Test-Path $cmakeCache) { + Write-Output "Cleaning stale CMake cache for aws-lc-sys: $cmakeBuildDir" + Remove-Item -Path $cmakeBuildDir -Recurse -Force -ErrorAction SilentlyContinue + } + } + + Push-Location $crateDir + try { + # Use API 24 to ensure libc symbols like getifaddrs/freeifaddrs are available. + # Build only the library to avoid linking the desktop binary for Android. + Write-Output "Running: cargo ndk -t arm64-v8a -t armeabi-v7a -P 24 build --release --lib (in $crateDir)" + cargo ndk -t arm64-v8a -t armeabi-v7a -P 24 build --release --lib + } finally { + Pop-Location + if ($null -eq $oldCmakeGenerator) { Remove-Item Env:\CMAKE_GENERATOR -ErrorAction SilentlyContinue } else { $env:CMAKE_GENERATOR = $oldCmakeGenerator } + if ($null -eq $oldCmakeMakeProgram) { Remove-Item Env:\CMAKE_MAKE_PROGRAM -ErrorAction SilentlyContinue } else { $env:CMAKE_MAKE_PROGRAM = $oldCmakeMakeProgram } + } + + # Search for produced .so files under src-tauri/target + $soFiles = Get-ChildItem -Path (Join-Path $crateDir 'target') -Recurse -Filter "*.so" -ErrorAction SilentlyContinue + if (-not $soFiles) { + Write-Warning "No .so files found after build. Check cargo-ndk output above for errors." + } else { + foreach ($f in @($soFiles)) { + $full = $f.FullName + if ($full -match 'aarch64|aarch64-linux-android|arm64-v8a') { $abi = 'arm64-v8a' } + elseif ($full -match 'armv7|armv7-linux-androideabi|armeabi-v7a') { $abi = 'armeabi-v7a' } + else { continue } + + $dst = Join-Path $androidRoot "app\src\main\jniLibs\$abi" + if (-not (Test-Path $dst)) { New-Item -ItemType Directory -Path $dst -Force | Out-Null } + Copy-Item $full -Destination $dst -Force + Write-Output "Copied $($f.Name) -> $dst" + } + } + } + } catch { + Write-Warning "cargo-ndk build failed. Exception: $($_.Exception.Message)" + if ($_.ScriptStackTrace) { Write-Output $_.ScriptStackTrace } + } +} else { + Write-Warning "Skipping native lib build (cargo-ndk missing)." +} + +Write-Output "Android prep complete. Open '$androidRoot' in Android Studio and build the APK (or run './gradlew assembleDebug' in that folder)." + +Pop-Location diff --git a/scripts/build-android.sh b/scripts/build-android.sh new file mode 100644 index 0000000..769c9e9 --- /dev/null +++ b/scripts/build-android.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Cross-platform helper for Unix-like shells +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +echo "Preparing Android assets and native libs..." + +if command -v npm >/dev/null 2>&1; then + echo "Running npm install & build" + npm install + npm run build || true +fi + +DIST_DIR="dist" +if [ ! -d "$DIST_DIR" ]; then DIST_DIR="build"; fi +if [ -d "$DIST_DIR" ]; then + echo "Copying $DIST_DIR -> android/app/src/main/assets" + mkdir -p android/app/src/main/assets + rsync -a --delete "$DIST_DIR/" android/app/src/main/assets/ +else + echo "No dist/build found, copying src/ -> android assets" + mkdir -p android/app/src/main/assets + rsync -a --delete src/ android/app/src/main/assets/ +fi + +if command -v cargo-ndk >/dev/null 2>&1; then + echo "Building native libs with cargo-ndk" + cargo-ndk -t aarch64 -t armv7 build --release || true + # copy so files + find target -type f -name "*.so" | while read -r f; do + if [[ "$f" =~ aarch64|aarch64-linux-android ]]; then abi=arm64-v8a; fi + if [[ "$f" =~ armv7|armv7-linux-androideabi ]]; then abi=armeabi-v7a; fi + if [ -n "${abi-}" ]; then + mkdir -p android/app/src/main/jniLibs/$abi + cp "$f" android/app/src/main/jniLibs/$abi/ + echo "Copied $f -> android/app/src/main/jniLibs/$abi/" + fi + done +else + echo "cargo-ndk not found; skipping native lib build" +fi + +echo "Prepared Android project. Open android/ in Android Studio to build the APK (or run ./gradlew assembleDebug)." diff --git a/src-tauri/binaries/RadioPlayer-aarch64-linux-android b/src-tauri/binaries/RadioPlayer-aarch64-linux-android new file mode 100644 index 0000000..e69de29 diff --git a/src-tauri/binaries/RadioPlayer-armv7-linux-androideabi b/src-tauri/binaries/RadioPlayer-armv7-linux-androideabi new file mode 100644 index 0000000..e69de29 diff --git a/src-tauri/gen/android/.editorconfig b/src-tauri/gen/android/.editorconfig new file mode 100644 index 0000000..ebe51d3 --- /dev/null +++ b/src-tauri/gen/android/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false \ No newline at end of file diff --git a/src-tauri/gen/android/.gitignore b/src-tauri/gen/android/.gitignore new file mode 100644 index 0000000..b248203 --- /dev/null +++ b/src-tauri/gen/android/.gitignore @@ -0,0 +1,19 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +build +/captures +.externalNativeBuild +.cxx +local.properties +key.properties + +/.tauri +/tauri.settings.gradle \ No newline at end of file diff --git a/src-tauri/gen/android/app/.gitignore b/src-tauri/gen/android/app/.gitignore new file mode 100644 index 0000000..dc372eb --- /dev/null +++ b/src-tauri/gen/android/app/.gitignore @@ -0,0 +1,6 @@ +/src/main/java/si/klevze/radioPlayer/generated +/src/main/jniLibs/**/*.so +/src/main/assets/tauri.conf.json +/tauri.build.gradle.kts +/proguard-tauri.pro +/tauri.properties \ No newline at end of file diff --git a/src-tauri/gen/android/app/build.gradle.kts b/src-tauri/gen/android/app/build.gradle.kts new file mode 100644 index 0000000..7083e3f --- /dev/null +++ b/src-tauri/gen/android/app/build.gradle.kts @@ -0,0 +1,64 @@ +import java.util.Properties + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") +} + +val tauriProperties = Properties().apply { + val propFile = file("tauri.properties") + if (propFile.exists()) { + propFile.inputStream().use { load(it) } + } +} + +android { + compileSdk = 36 + namespace = "si.klevze.radioPlayer" + defaultConfig { + manifestPlaceholders["usesCleartextTraffic"] = "false" + applicationId = "si.klevze.radioPlayer" + minSdk = 24 + targetSdk = 36 + versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() + versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") + } + buildTypes { + getByName("debug") { + manifestPlaceholders["usesCleartextTraffic"] = "true" + isDebuggable = true + isJniDebuggable = true + isMinifyEnabled = false + packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so") + jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so") + jniLibs.keepDebugSymbols.add("*/x86/*.so") + jniLibs.keepDebugSymbols.add("*/x86_64/*.so") + } + } + getByName("release") { + isMinifyEnabled = true + proguardFiles( + *fileTree(".") { include("**/*.pro") } + .plus(getDefaultProguardFile("proguard-android-optimize.txt")) + .toList().toTypedArray() + ) + } + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + } +} + +dependencies { + implementation("androidx.constraintlayout:constraintlayout:2.2.1") + implementation("androidx.webkit:webkit:1.14.0") + implementation("androidx.appcompat:appcompat:1.7.1") + implementation("androidx.activity:activity-ktx:1.10.1") + implementation("com.google.android.material:material:1.12.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.4") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") +} diff --git a/src-tauri/gen/android/app/proguard-rules.pro b/src-tauri/gen/android/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/src-tauri/gen/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/src-tauri/gen/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5a5c9a8 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-tauri/gen/android/app/src/main/assets/assets/appIcon.png b/src-tauri/gen/android/app/src/main/assets/assets/appIcon.png new file mode 100644 index 0000000..d789348 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/assets/assets/appIcon.png differ diff --git a/src-tauri/gen/android/app/src/main/assets/assets/favicon_io.zip b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io.zip new file mode 100644 index 0000000..15adffb Binary files /dev/null and b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io.zip differ diff --git a/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/android-chrome-192x192.png b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/android-chrome-192x192.png new file mode 100644 index 0000000..4757781 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/android-chrome-192x192.png differ diff --git a/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/android-chrome-512x512.png b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/android-chrome-512x512.png new file mode 100644 index 0000000..8f1299a Binary files /dev/null and b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/android-chrome-512x512.png differ diff --git a/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/app-icon.png b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/app-icon.png new file mode 100644 index 0000000..8f1299a Binary files /dev/null and b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/app-icon.png differ diff --git a/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/apple-touch-icon.png b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/apple-touch-icon.png new file mode 100644 index 0000000..f29ebcf Binary files /dev/null and b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/apple-touch-icon.png differ diff --git a/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/favicon-16x16.png b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/favicon-16x16.png new file mode 100644 index 0000000..4eb99da Binary files /dev/null and b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/favicon-16x16.png differ diff --git a/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/favicon-32x32.png b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/favicon-32x32.png new file mode 100644 index 0000000..73c330b Binary files /dev/null and b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/favicon-32x32.png differ diff --git a/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/icon.ico b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/icon.ico new file mode 100644 index 0000000..586e969 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/icon.ico differ diff --git a/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/site.webmanifest b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/assets/assets/favicon_io/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/assets/assets/javascript.svg b/src-tauri/gen/android/app/src/main/assets/assets/javascript.svg new file mode 100644 index 0000000..f9abb2b --- /dev/null +++ b/src-tauri/gen/android/app/src/main/assets/assets/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/assets/assets/tauri.svg b/src-tauri/gen/android/app/src/main/assets/assets/tauri.svg new file mode 100644 index 0000000..0c0e6aa --- /dev/null +++ b/src-tauri/gen/android/app/src/main/assets/assets/tauri.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src-tauri/gen/android/app/src/main/assets/index.html b/src-tauri/gen/android/app/src/main/assets/index.html new file mode 100644 index 0000000..3ea45e7 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/assets/index.html @@ -0,0 +1,158 @@ + + + + + + + Radio1 Player + + + + + +
    +
    +
    + +
    +
    + +
    + Radio1 Player + + Ready + +
    +
    + + +
    +
    + +
    +
    +
    + + + + + + + + + + + + + + + + 1 +
    +
    +
    + +
    +

    Radio 1 MB

    +

    Live Stream

    +
    + + +
    +
    +
    +
    +
    +
    + +
    + + + + + +
    + +
    + +
    + +
    + 50% +
    + + + + +
    +
    + + + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/assets/main.js b/src-tauri/gen/android/app/src/main/assets/main.js new file mode 100644 index 0000000..50c6b53 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/assets/main.js @@ -0,0 +1,355 @@ +const { invoke } = window.__TAURI__.core; +const { getCurrentWindow } = window.__TAURI__.window; + +// State +let stations = []; +let currentIndex = 0; +let isPlaying = false; +let currentMode = 'local'; // 'local' | 'cast' +let currentCastDevice = null; +const audio = new Audio(); + +// UI Elements +const stationNameEl = document.getElementById('station-name'); +const stationSubtitleEl = document.getElementById('station-subtitle'); +const statusTextEl = document.getElementById('status-text'); +const statusDotEl = document.querySelector('.status-dot'); +const playBtn = document.getElementById('play-btn'); +const iconPlay = document.getElementById('icon-play'); +const iconStop = document.getElementById('icon-stop'); +const prevBtn = document.getElementById('prev-btn'); +const nextBtn = document.getElementById('next-btn'); +const volumeSlider = document.getElementById('volume-slider'); +const volumeValue = document.getElementById('volume-value'); +const castBtn = document.getElementById('cast-toggle-btn'); +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() { + await loadStations(); + setupEventListeners(); + updateUI(); +} + +async function loadStations() { + try { + const resp = await fetch('stations.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); + } + } catch (e) { + console.error('Failed to load stations', e); + statusTextEl.textContent = 'Error loading stations'; + } +} + +function setupEventListeners() { + playBtn.addEventListener('click', togglePlay); + prevBtn.addEventListener('click', playPrev); + nextBtn.addEventListener('click', playNext); + + volumeSlider.addEventListener('input', handleVolumeInput); + + castBtn.addEventListener('click', openCastOverlay); + closeOverlayBtn.addEventListener('click', closeCastOverlay); + + // Close overlay on background click + castOverlay.addEventListener('click', (e) => { + if (e.target === castOverlay) closeCastOverlay(); + }); + + // Close button + document.getElementById('close-btn').addEventListener('click', async () => { + const appWindow = getCurrentWindow(); + await appWindow.close(); + }); + + // Menu button - explicit functionality or placeholder? + // For now just log or maybe show about + document.getElementById('menu-btn').addEventListener('click', () => { + openStationsOverlay(); + }); + + // Hotkeys? +} + +function loadStation(index) { + if (index < 0 || index >= stations.length) return; + const station = stations[index]; + + stationNameEl.textContent = station.name; + stationSubtitleEl.textContent = currentMode === 'cast' ? `Casting to ${currentCastDevice}` : 'Live Stream'; + + // 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() { + if (isPlaying) { + await stop(); + } else { + await play(); + } +} + +async function play() { + const station = stations[currentIndex]; + if (!station) return; + + statusTextEl.textContent = 'Buffering...'; + statusDotEl.style.backgroundColor = 'var(--text-muted)'; // Grey/Yellow while loading + + if (currentMode === 'local') { + audio.src = station.url; + audio.volume = volumeSlider.value / 100; + try { + await audio.play(); + isPlaying = true; + updateUI(); + } catch (e) { + console.error('Playback failed', e); + statusTextEl.textContent = 'Error'; + } + } else if (currentMode === 'cast' && currentCastDevice) { + // Cast logic + try { + await invoke('cast_play', { deviceName: currentCastDevice, url: station.url }); + isPlaying = true; + // Sync volume + const vol = volumeSlider.value / 100; + invoke('cast_set_volume', { deviceName: currentCastDevice, volume: vol }); + updateUI(); + } catch (e) { + console.error('Cast failed', e); + statusTextEl.textContent = 'Cast Error'; + currentMode = 'local'; // Fallback + updateUI(); + } + } +} + +async function stop() { + if (currentMode === 'local') { + audio.pause(); + audio.src = ''; + } else if (currentMode === 'cast' && currentCastDevice) { + try { + await invoke('cast_stop', { deviceName: currentCastDevice }); + } catch (e) { + console.error(e); + } + } + + isPlaying = false; + updateUI(); +} + +async function playNext() { + if (stations.length === 0) return; + + // If playing, stop first? Or seamless? + // For radio, seamless switch requires stop then play new URL + const wasPlaying = isPlaying; + + if (wasPlaying) await stop(); + + currentIndex = (currentIndex + 1) % stations.length; + loadStation(currentIndex); + + if (wasPlaying) await play(); +} + +async function playPrev() { + if (stations.length === 0) return; + + const wasPlaying = isPlaying; + + if (wasPlaying) await stop(); + + currentIndex = (currentIndex - 1 + stations.length) % stations.length; + loadStation(currentIndex); + + if (wasPlaying) await play(); +} + +function updateUI() { + // Play/Stop Button + if (isPlaying) { + iconPlay.classList.add('hidden'); + iconStop.classList.remove('hidden'); + playBtn.classList.add('playing'); // Add pulsing ring animation + statusTextEl.textContent = 'Playing'; + statusDotEl.style.backgroundColor = 'var(--success)'; + stationSubtitleEl.textContent = currentMode === 'cast' ? `Casting to ${currentCastDevice}` : 'Live Stream'; + } else { + iconPlay.classList.remove('hidden'); + iconStop.classList.add('hidden'); + playBtn.classList.remove('playing'); // Remove pulsing ring + statusTextEl.textContent = 'Ready'; + statusDotEl.style.backgroundColor = 'var(--text-muted)'; + stationSubtitleEl.textContent = currentMode === 'cast' ? `Connected to ${currentCastDevice}` : 'Live Stream'; + } +} + +function handleVolumeInput() { + const val = volumeSlider.value; + volumeValue.textContent = `${val}%`; + const decimals = val / 100; + + if (currentMode === 'local') { + audio.volume = decimals; + } else if (currentMode === 'cast' && currentCastDevice) { + invoke('cast_set_volume', { deviceName: currentCastDevice, volume: decimals }); + } +} + +// Cast Logic +async function openCastOverlay() { + castOverlay.classList.remove('hidden'); + castOverlay.setAttribute('aria-hidden', 'false'); + deviceListEl.innerHTML = '
  • Scanning...
    Searching for speakers
  • '; + + try { + const devices = await invoke('list_cast_devices'); + deviceListEl.innerHTML = ''; + + // Add "This Computer" option + const localLi = document.createElement('li'); + localLi.className = 'device' + (currentMode === 'local' ? ' selected' : ''); + localLi.innerHTML = '
    This Computer
    Local Playback
    '; + localLi.onclick = () => selectCastDevice(null); + deviceListEl.appendChild(localLi); + + if (devices.length > 0) { + devices.forEach(d => { + const li = document.createElement('li'); + li.className = 'device' + (currentMode === 'cast' && currentCastDevice === d ? ' selected' : ''); + li.innerHTML = `
    ${d}
    Google Cast Speaker
    `; + li.onclick = () => selectCastDevice(d); + deviceListEl.appendChild(li); + }); + } + } catch (e) { + deviceListEl.innerHTML = `
  • Error
    ${e}
  • `; + } +} + +function closeCastOverlay() { + castOverlay.classList.add('hidden'); + castOverlay.setAttribute('aria-hidden', 'true'); +} + +async function selectCastDevice(deviceName) { + closeCastOverlay(); + + // If checking same device, do nothing + if (deviceName === currentCastDevice) return; + + // If switching mode, stop current playback + if (isPlaying) { + await stop(); + } + + if (deviceName) { + currentMode = 'cast'; + currentCastDevice = deviceName; + castBtn.style.color = 'var(--success)'; + } else { + currentMode = 'local'; + currentCastDevice = null; + castBtn.style.color = 'var(--text-main)'; + } + + updateUI(); + + // Auto-play if we were playing? Let's stay stopped to be safe/explicit + // Or auto-play for better UX? + // Let's prompt user to play. +} + +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 = '
  • Loading...
    Preparing stations
  • '; + + // If stations not loaded yet, show message + if (!stations || stations.length === 0) { + deviceListEl.innerHTML = '
  • No stations found
    Check your stations.json
  • '; + 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 = `
    ${s.name}
    ${subtitle}
    `; + 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); + }); +} diff --git a/src-tauri/gen/android/app/src/main/assets/stations.json b/src-tauri/gen/android/app/src/main/assets/stations.json new file mode 100644 index 0000000..51086d7 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/assets/stations.json @@ -0,0 +1,1342 @@ +[ + { + "id": "Radio1", + "title": "Radio 1", + "slogan": "Več dobre glasbe", + "logo": "http://datacache.radio.si/api/radiostations/logo/radio1.svg", + "liveAudio": "http://live.radio1.si/Radio1", + "liveVideo": null, + "poster": "", + "lastSongs": "http://data.radio.si/api/lastsongsxml/radio1/json", + "epg": "http://spored.radio.si/api/now/radio1", + "defaultText": "www.radio1.si", + "www": "https://www.radio1.si", + "mountPoints": [ + "Radio1", + "Radio1BK", + "Radio1CE", + "Radio1GOR", + "Radio1KOR", + "Radio1LI", + "Radio1MB", + "Radio1NM", + "Radio1OB", + "Radio1PO", + "Radio1PR", + "Radio1PRI", + "Radio1PT", + "Radio1RIB", + "Radio1VE", + "Radio1VR", + "Radio1SAV" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38651300300" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "http://m.radio1.si" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "http://www.youtube.com/user/radio1slovenia" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "http://facebook.com/RadioEna" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "http://www.instagram.com/radio1slo" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radio1?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=50668", + "rpUid": "705167", + "dabUser": "radio1", + "dabPass": "sUbSGhmzdwKQT", + "dabDefaultImg": "http://media.radio.si/logo/dns/radio1/320x240.png", + "small": false + }, + { + "id": "Aktual", + "title": "Radio Aktual", + "slogan": "Narejen za vaša ušesa", + "logo": "http://datacache.radio.si/api/radiostations/logo/aktual.svg", + "liveAudio": "http://live.radio.si/Aktual", + "liveVideo": "https://radio.serv.si/AktualTV/video.m3u8", + "poster": "https://cdn1.radio.si/900/screenaktual_90c0280a8.jpg", + "lastSongs": "http://data.radio.si/api/lastsongsxml/aktual/json", + "epg": null, + "defaultText": "", + "www": "https://radioaktual.si", + "mountPoints": [ + "Aktual" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+386158801430" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radioaktual.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/user/raktual?sub_confirmation=1" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radioaktual/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705160", + "dabUser": "aktual", + "dabPass": "GB31GZd5st0M", + "dabDefaultImg": "http://media.radio.si/logo/dns/aktual/RadioAktual_DAB.jpg", + "small": false + }, + { + "id": "Veseljak", + "title": "Radio Veseljak", + "slogan": "Najboljša domača glasba", + "logo": "http://datacache.radio.si/api/radiostations/logo/veseljak.svg", + "liveAudio": "http://live.radio.si/Veseljak", + "liveVideo": "https://radio.serv.si/VeseljakGolicaTV/video.m3u8", + "poster": "https://cdn1.radio.si/900/screenveseljak_166218c26.jpg", + "lastSongs": "http://data.radio.si/api/lastsongsxml/veseljak/json", + "epg": null, + "defaultText": "www.veseljak.si", + "www": "https://veseljak.si/", + "mountPoints": [ + "Veseljak", + "VeseljakPO" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38615880110" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://veseljak.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/c/VESELJAKNAJBOLJSADOMACAGLASBA" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RadioVeseljak" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/veseljak.si/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705166", + "dabUser": "veseljak", + "dabPass": "sLRDCAX9j3k2", + "dabDefaultImg": "http://media.radio.si/logo/dns/veseljak/RadioVeseljak_DAB.jpg", + "small": false + }, + { + "id": "Radio1Rock", + "title": "Radio 1 ROCK", + "slogan": "100% Rock", + "logo": "http://datacache.radio.si/api/radiostations/logo/radio1rock.svg", + "liveAudio": "http://live.radio.si/Radio1Rock", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/radio1rock/json", + "epg": "http://spored.radio.si/api/now/radio1rock", + "defaultText": "www.radio1rock.si", + "www": "https://radio1rock.si/", + "mountPoints": [ + "Radio1Rock" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38683879300" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radio1rock.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/R1Rock" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/R1rock.si/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiobob?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=61109", + "rpUid": "705162", + "dabUser": "radiobob", + "dabPass": "cjT24PpyVxit6", + "dabDefaultImg": "http://media.radio.si/logo/dns/radio1rock/320x240.png", + "small": false + }, + { + "id": "Radio80", + "title": "Radio 1 80-a", + "slogan": "Samo hiti 80-ih", + "logo": "http://datacache.radio.si/api/radiostations/logo/radio80.svg", + "liveAudio": "http://live.radio.si/Radio80", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/radio80/json", + "epg": "http://spored.radio.si/api/now/radio80", + "defaultText": "www.radio80.si", + "www": "https://radio80.si/", + "mountPoints": [ + "Radio80" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38615008875" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radio80.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/radio1slovenia" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radioena" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radio180-a?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=89760", + "rpUid": "705102", + "dabUser": "radio80", + "dabPass": "nc6da2LolcBXC", + "dabDefaultImg": "http://media.radio.si/logo/dns/radio80/320x240.png", + "small": false + }, + { + "id": "Radio90", + "title": "Radio 1 90-a", + "slogan": "Samo hiti 90-ih", + "logo": "http://datacache.radio.si/api/radiostations/logo/radio90.svg", + "liveAudio": "http://live.radio.si/Radio90", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/radio90/json", + "epg": null, + "defaultText": "www.radio1.si", + "www": "https://radio1.si/", + "mountPoints": [ + "Radio90" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38615008875" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radio1.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/radio1slovenia" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radioena" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705172", + "dabUser": "radio90", + "dabPass": "P2RyUrHcyq7M", + "dabDefaultImg": "http://media.radio.si/logo/dns/radio90/320x240.png", + "small": false + }, + { + "id": "Toti", + "title": "Toti radio", + "slogan": "Toti hudi hiti", + "logo": "http://datacache.radio.si/api/radiostations/logo/toti.svg", + "liveAudio": "http://live.radio.si/Toti", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/toti/json", + "epg": "http://spored.radio.si/api/now/toti", + "defaultText": "www.totiradio.si", + "www": "https://totiradio.si/", + "mountPoints": [ + "Maxi", + "Toti" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38651220220" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://totiradio.si/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/totiradio?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=91414", + "rpUid": "705108", + "dabUser": "toti", + "dabPass": "wmAos05tECsmf", + "dabDefaultImg": "http://media.radio.si/logo/dns/toti/320x240.png", + "small": false + }, + { + "id": "Antena", + "title": "Radio Antena", + "slogan": "Največ hitov, najmanj govora", + "logo": "http://datacache.radio.si/api/radiostations/logo/antena.svg", + "liveAudio": "http://live.radio.si/Antena", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/antena/json", + "epg": "http://spored.radio.si/api/now/antena", + "defaultText": "www.radioantena.si", + "www": "https://radioantena.si/", + "mountPoints": [ + "Antena" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38612425630 " + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radioantena.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/user/radioantenaslo" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/HitradioAntena" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radioantena.si/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radioantena?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=37864", + "rpUid": "705161", + "dabUser": "radioantena", + "dabPass": "nGkMhFk77jnBQ", + "dabDefaultImg": "http://media.radio.si/logo/dns/antena/320x240.png", + "small": false + }, + { + "id": "BestFM", + "title": "BestFM", + "slogan": "Muska, muska, muska", + "logo": "http://datacache.radio.si/api/radiostations/logo/bestfm.svg", + "liveAudio": "http://live.radio.si/BestFM", + "liveVideo": "https://radio.serv.si/BestTV/video.m3u8", + "poster": "https://cdn1.radio.si/900/screenbest_6559e3ac8.jpg", + "lastSongs": "http://data.radio.si/api/lastsongsxml/bestfm/json", + "epg": null, + "defaultText": "www.bestfm.si", + "www": "https://bestfm.si/", + "mountPoints": [ + "BestFM" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38673372030" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://bestfm.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/profile.php?id=100086776586975" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/bestfm.si/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705115", + "dabUser": "bestfm", + "dabPass": "momo911x", + "dabDefaultImg": "http://media.radio.si/logo/dns/bestfm/BestFM_DAB.jpg", + "small": false + }, + { + "id": "Krka", + "title": "Radio Krka", + "slogan": "Dolenjska v srcu", + "logo": "http://datacache.radio.si/api/radiostations/logo/krka.svg", + "liveAudio": "http://live.radio.si/Krka", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/krka/json", + "epg": "", + "defaultText": "www.radiokrka.si", + "www": "https://radiokrka.si/", + "mountPoints": [ + "Krka" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38673372030" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radiokrka.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/user/radiokrka" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiokrka" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiokrka/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705120", + "dabUser": "krka", + "dabPass": "qBi6z!um2Gm", + "dabDefaultImg": "http://media.radio.si/logo/dns/krka/RadioKrka_DAB.jpg", + "small": false + }, + { + "id": "Klasik", + "title": "Klasik radio", + "slogan": "Glasba, ki vas sprosti", + "logo": "https://data.radio.si/api/radiostations/logo/klasik.svg", + "liveAudio": "http://live.radio.si/Klasik", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/klasik/json", + "epg": "", + "defaultText": "www.klasikradio.si", + "www": "https://www.klasikradio.si/", + "mountPoints": [ + "Klasik" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38612425630" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.klasikradio.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/profile.php?id=100064736766638" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705176", + "dabUser": "klasik", + "dabPass": "mQTpTR9XEbiF", + "dabDefaultImg": "http://media.radio.si/logo/dns/klasik/320x240.png", + "small": false + }, + { + "id": "Maxi", + "title": "Toti Maxi", + "slogan": "Sama dobra glasba", + "logo": "https://data.radio.si/api/radiostations/logo/maxi.svg", + "liveAudio": "http://live.radio.si/Maxi", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/toti/json", + "epg": "", + "defaultText": "www.totimaxi.si", + "www": "https://www.radiomaxi.si/", + "mountPoints": [ + "Maxi" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38631628444" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radiomaxi.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/profile.php?id=100064736766638" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiosalomon" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiosalomon/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/totiradio?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=37998", + "rpUid": "705109", + "dabUser": "salomon", + "dabPass": "a1bfadd8b8ut", + "dabDefaultImg": "http://media.radio.si/logo/dns/salomon/RadioSalomon_DAB.jpg", + "small": false + }, + { + "id": "Salomon", + "title": "Radio Salomon", + "slogan": "Izbrana urbana glasba", + "logo": "http://datacache.radio.si/api/radiostations/logo/salomon.svg", + "liveAudio": "http://live.radio.si/Salomon", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/salomon/json", + "epg": "", + "defaultText": "www.radiosalomon.si", + "www": "https://radiosalomon.si/", + "mountPoints": [ + "Salomon" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+386015880111" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radiosalomon.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UCd7OpUbSIoZarJgwFf4aIxw" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiosalomon" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiosalomon/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705116", + "dabUser": "salomon", + "dabPass": "a1bfadd8b8ut", + "dabDefaultImg": "http://media.radio.si/logo/dns/salomon/RadioSalomon_DAB.jpg", + "small": false + }, + { + "id": "Ptuj", + "title": "Radio Ptuj", + "slogan": "Največje uspešnice vseh časov", + "logo": "https://data.radio.si/api/radiostations/logo/ptuj.svg", + "liveAudio": "http://live.radio.si/Ptuj", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/ptuj/json", + "epg": "", + "defaultText": "www.radio-ptuj.si", + "www": "https://www.radio-ptuj.si/", + "mountPoints": [ + "Ptuj" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38627493420" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radio-ptuj.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/@RadioPtuj" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RadioPtuj" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radio_ptuj/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705119", + "dabUser": "ptuj", + "dabPass": "cwv4jXVKMYT", + "dabDefaultImg": "http://media.radio.si/logo/dns/ptuj/RadioPtuj_DAB.jpg", + "small": false + }, + { + "id": "Fantasy", + "title": "Radio Fantasy", + "slogan": "Same dobre vibracije", + "logo": "https://data.radio.si/api/radiostations/logo/fantasy.svg", + "liveAudio": "http://live.radio.si/Fantasy", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/fantasy/json", + "epg": "http://spored.radio.si/api/now/robin", + "defaultText": "", + "www": "https://rfantasy.si/", + "mountPoints": [ + "Fantasy" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38634903921" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.rfantasy.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/c/RadioFantasyTv" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RadioFantasySlo" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiofantasyslo/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiofantasy?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=61118", + "rpUid": "", + "dabUser": "radiorobin", + "dabPass": "rt5mo9b9", + "dabDefaultImg": "http://media.radio.si/logo/dns/robin/320x240.png", + "small": false + }, + { + "id": "Robin", + "title": "Radio Robin", + "slogan": "Brez tebe ni mene", + "logo": "https://data.radio.si/api/radiostations/logo/robin.svg", + "liveAudio": "http://live.radio.si/Robin", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/robin/json", + "epg": "http://spored.radio.si/api/now/robin", + "defaultText": "www.robin.si", + "www": "https://www.robin.si/", + "mountPoints": [ + "Robin" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38653302822" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.robin.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UCACfPObotnJAnVXfCZNMlUg" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/Radio.Robin.goriski" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radio_robin/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiorobin?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=37984", + "rpUid": "705103", + "dabUser": "radiorobin", + "dabPass": "rt5mo9b9", + "dabDefaultImg": "http://media.radio.si/logo/dns/robin/320x240.png", + "small": false + }, + { + "id": "Koroski", + "title": "Koroški radio", + "slogan": "Ritem Koroške", + "logo": "https://data.radio.si/api/radiostations/logo/koroski.svg", + "liveAudio": "http://live.radio.si/Koroski", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/koroski/json", + "epg": "http://spored.radio.si/api/now/koroski", + "defaultText": "www.koroski-radio.si", + "www": "https://www.koroski-radio.si/", + "mountPoints": [ + "Koroski" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38628841245" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.koroski-radio.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UCLwH6lX4glK4o1N77JkeaJw" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/KoroskiRadio" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/koroski_r/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705105", + "dabUser": "koroski", + "dabPass": "num87dhket", + "dabDefaultImg": "http://media.radio.si/logo/dns/koroski/320x240.png", + "small": true + }, + { + "id": "VeseljakZlatiZvoki", + "title": "Veseljak Zlati zvoki", + "slogan": "Najvecja zakladnica slovenske domace glasbe", + "logo": "https://data.radio.si/api/radiostations/logo/veseljakzlatizvoki.svg", + "liveAudio": "http://live.radio.si/VeseljakZlatiZvoki", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/veseljakzlatizvoki/json", + "epg": "", + "defaultText": "www.veseljak.si", + "www": "https://www.veseljak.si/", + "mountPoints": [ + "VeseljakZlatiZvoki" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38615880110" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://veseljak.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/c/VESELJAKNAJBOLJSADOMACAGLASBA" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RadioVeseljak" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/veseljak.si/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705175", + "dabUser": "zlatizvoki", + "dabPass": "4jeeUnjA4qYV", + "dabDefaultImg": "http://media.radio.si/logo/dns/veseljakzlatizvoki/RadioVeseljakZlatiZvoki_DAB.jpg", + "small": false + }, + { + "id": "RockMB", + "title": "Rock Maribor", + "slogan": "100% Rock", + "logo": "https://data.radio.si/api/radiostations/logo/rockmb.svg", + "liveAudio": "http://live.radio.si/RockMB", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/radio1rock/json", + "epg": "", + "defaultText": "www.rockmaribor.si", + "www": "https://rockmaribor.si/", + "mountPoints": [ + "RockMB" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://rockmaribor.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RockMaribor.si" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UC99aNwZXokG6nnJLfSn5DSw" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiocelje" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiocelje/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/rockmaribor?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=61116", + "rpUid": "705107", + "dabUser": "celje", + "dabPass": "dunk7815g", + "dabDefaultImg": "http://media.radio.si/logo/dns/celje/RadioCelje_DAB.jpg", + "small": false + }, + { + "id": "Kranj", + "title": "Radio Kranj", + "slogan": "", + "logo": "https://data.radio.si/api/radiostations/logo/kranj.svg", + "liveAudio": "http://live.radio.si/Kranj", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/kranj/json", + "epg": "http://spored.radio.si/api/now/kranj", + "defaultText": "www.radio-kranj.si", + "www": "https://radio-kranj.si/", + "mountPoints": [ + "Kranj" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38651303505" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radio-kranj.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UCe_Ze0SEHCSLLNUbWM0aBgA" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/pages/Radio-Kranj/1760816170864847" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiokranj/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiokranj?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=61102", + "rpUid": "705104", + "dabUser": "kranj", + "dabPass": "ui8z3Ezzosyxw", + "dabDefaultImg": "http://media.radio.si/logo/dns/kranj/320x240.png", + "small": false + }, + { + "id": "Celje", + "title": "Radio Celje", + "slogan": "Vedno z menoj", + "logo": "https://data.radio.si/api/radiostations/logo/celje.svg", + "liveAudio": "http://live.radio.si/Celje", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/celje/json", + "epg": "", + "defaultText": "www.radiocelje.si", + "www": "https://www.radiocelje.si/", + "mountPoints": [ + "Celje" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+386034225100" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radiocelje.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UC99aNwZXokG6nnJLfSn5DSw" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiocelje" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiocelje/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705117", + "dabUser": "celje", + "dabPass": "dunk7815g", + "dabDefaultImg": "http://media.radio.si/logo/dns/celje/RadioCelje_DAB.jpg", + "small": false + }, + { + "id": "Triglav", + "title": "Radio Triglav", + "slogan": "Radio za radovedne", + "logo": "https://data.radio.si/api/radiostations/logo/triglav.svg", + "liveAudio": "http://live.radio.si/Triglav", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/triglav/json", + "epg": "http://spored.radio.si/api/now/triglav", + "defaultText": "www.radiotriglav.si", + "www": "https://radiotriglav.si/", + "mountPoints": [ + "Triglav" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38651654064" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.radiotriglav.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RadioTriglav" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiotriglav/" + } + ], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiotriglav?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=38020", + "rpUid": "705106", + "dabUser": "triglav", + "dabPass": "ogFLUKodMUCB5", + "dabDefaultImg": "http://media.radio.si/logo/dns/triglav/320x240.png", + "small": false + }, + { + "id": "Velenje", + "title": "Radio Velenje", + "slogan": "Ker smo radi na kamot", + "logo": "http://datacache.radio.si/api/radiostations/logo/velenje.svg", + "liveAudio": "http://live.radio.si/Velenje", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/velenje/json", + "epg": "", + "defaultText": "www.veseljak.si", + "www": "https://veseljak.si/", + "mountPoints": [ + "Velenje" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38615880110" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://veseljak.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/c/VESELJAKNAJBOLJSADOMACAGLASBA" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RadioVeseljak" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/veseljak.si/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705118", + "dabUser": "velenje", + "dabPass": "e9mopbu11", + "dabDefaultImg": "http://media.radio.si/logo/dns/velenje/RadioVelenje_DAB.jpg", + "small": false + }, + { + "id": "AktualK", + "title": "Radio Aktual Kum", + "slogan": "Narejen za vaša ušesa", + "logo": "http://datacache.radio.si/api/radiostations/logo/aktualk.svg", + "liveAudio": "http://live.radio.si/AktualK", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/aktualk/json", + "epg": "", + "defaultText": "", + "www": null, + "mountPoints": [ + "AktualK" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+386158801430" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radioaktual.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/user/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/stories/radioaktual/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "", + "dabUser": null, + "dabPass": null, + "dabDefaultImg": null, + "small": false + }, + { + "id": "AktualRomantika", + "title": "Radio Aktual - Romantika", + "slogan": "Kot nezna dlan, ki boza te", + "logo": "http://datacache.radio.si/api/radiostations/logo/aktualromantika.svg", + "liveAudio": "http://live.radio.si/AktualRomantika", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/aktualromantika/json", + "epg": "", + "defaultText": "", + "www": null, + "mountPoints": [ + "AktualRomantika" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+386158801430" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radioaktual.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/user/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/stories/radioaktual/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705174", + "dabUser": "romantika", + "dabPass": "Z75biJ5t7CpK", + "dabDefaultImg": "http://media.radio.si/logo/dns/aktualromantika/RadioAktualRomnatika_DAB.jpg", + "small": false + }, + { + "id": "Stop", + "title": "Stop", + "slogan": "Revija Stop: Več kot pol stoletja ob vaši strani!", + "logo": "http://datacache.radio.si/api/radiostations/logo/stop.svg", + "liveAudio": "http://live.radio.si/Stop", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/stop/json", + "epg": "", + "defaultText": "www.revijastop.si", + "www": "https://revijastop.si/", + "mountPoints": [ + "Stop" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://revijastop.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://totiradio.si/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "", + "dabUser": null, + "dabPass": null, + "dabDefaultImg": null, + "small": false + }, + { + "id": "Radio1SLO", + "title": "Radio 1 slovenski hiti", + "slogan": "Sama dobra slovenska glasba", + "logo": "http://datacache.radio.si/api/radiostations/logo/radio1slo.svg", + "liveAudio": "http://live.radio.si/Radio1SLO", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/radio1slo/json", + "epg": "", + "defaultText": "www.radio1.si", + "www": "https://www.radio1.si", + "mountPoints": [ + "Radio1SLO" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38651300300" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "http://radio1.si" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "http://www.youtube.com/user/radio1slovenia" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "http://facebook.com/RadioEna" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "http://www.instagram.com/radio1slo" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "705110", + "dabUser": "1slovenske", + "dabPass": "ionb9hkd48", + "dabDefaultImg": "http://media.radio.si/logo/dns/radio1slo/320x240.png", + "small": false + }, + { + "id": "RockCE", + "title": "Rock Celje", + "slogan": "100% Rock", + "logo": "https://data.radio.si/api/radiostations/logo/rockce.svg", + "liveAudio": "http://live.radio.si/RockCE", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/rockce/json", + "epg": null, + "defaultText": "www.rock-celje.si", + "www": "https://rock-celje.si/", + "mountPoints": [ + "RockCE" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38628841245" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://www.rock-celje.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/RockCelje" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiosalomon" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiosalomon/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "", + "dabUser": null, + "dabPass": null, + "dabDefaultImg": null, + "small": false + }, + { + "id": "Radio1Bozicne", + "title": "ROŠKARJEVE BOŽIČNE", + "slogan": "100% Božične", + "logo": "https://data.radio.si/api/radiostations/logo/radio1bozicne.svg", + "liveAudio": "http://live.radio1.si/Radio1Bozicne", + "liveVideo": null, + "poster": null, + "lastSongs": "https://data.radio.si/api/lastsongsxml/radio1bozicne/json", + "epg": null, + "defaultText": "www.radio1.si", + "www": "https://radio1.si/", + "mountPoints": [ + "Radio1Bozicne" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radio1.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://radiosalomon.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/channel/UCd7OpUbSIoZarJgwFf4aIxw" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/radiosalomon" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/radiosalomon/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "", + "dabUser": null, + "dabPass": null, + "dabDefaultImg": null, + "small": false + }, + { + "id": "TotiBozicni", + "title": "TOTI BOŽIČNI RADIO", + "slogan": "Tvoje mesto, tvoj radio", + "logo": "http://datacache.radio.si/api/radiostations/logo/totibozicne.svg", + "liveAudio": "http://live.radio.si/TotiBozicne", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/totibozicni/json", + "epg": null, + "defaultText": "www.totiradio.si", + "www": "https://totiradio.si/", + "mountPoints": [ + "TotiBozicni" + ], + "social": [ + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg", + "link": "tel:+38651220220" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg", + "link": "https://totiradio.si/" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg", + "link": "https://www.youtube.com/user/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg", + "link": "https://www.facebook.com/raktual" + }, + { + "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg", + "link": "https://www.instagram.com/stories/radioaktual/" + } + ], + "enabled": true, + "radioApiIO": "", + "rpUid": "", + "dabUser": null, + "dabPass": null, + "dabDefaultImg": null, + "small": false + }, + { + "id": "Hit", + "title": "Radio HIT", + "slogan": "Samo nostalgija", + "logo": "http://datacache.radio.si/api/radiostations/logo/hit.svg", + "liveAudio": "http://live.radio.si/Hit", + "liveVideo": null, + "poster": null, + "lastSongs": "http://data.radio.si/api/lastsongsxml/hit/json", + "epg": "http://spored.radio.si/api/now/hit", + "defaultText": "www.radiohit.si", + "www": "https://radiohit.si/", + "mountPoints": [ + "Hit" + ], + "social": [], + "enabled": true, + "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiohit?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=61120", + "rpUid": "705141", + "dabUser": null, + "dabPass": null, + "dabDefaultImg": "http://media.radio.si/logo/dns/hit/320x240.png", + "small": false + } +] \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/assets/styles.css b/src-tauri/gen/android/app/src/main/assets/styles.css new file mode 100644 index 0000000..37e8c62 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/assets/styles.css @@ -0,0 +1,606 @@ +:root { + --bg-gradient: linear-gradient(135deg, #7b7fd8, #b57cf2); + --glass-bg: rgba(255, 255, 255, 0.1); + --glass-border: rgba(255, 255, 255, 0.2); + --accent: #dfa6ff; + --accent-glow: rgba(223, 166, 255, 0.5); + --text-main: #ffffff; + --text-muted: rgba(255, 255, 255, 0.7); + --danger: #cf6679; + --success: #7dffb3; + --card-radius: 10px; +} + +* { + box-sizing: border-box; + user-select: none; + -webkit-user-drag: none; + cursor: default; +} + +/* Hide Scrollbars */ +::-webkit-scrollbar { + display: none; +} + +body { + margin: 0; + padding: 0; + height: 100vh; + width: 100vw; + background: linear-gradient(-45deg, #7b7fd8, #b57cf2, #8b5cf6, #6930c3, #7b7fd8); + background-size: 400% 400%; + animation: gradientShift 12s ease-in-out infinite; + font-family: 'Segoe UI', system-ui, sans-serif; + color: var(--text-main); + overflow: hidden; + display: flex; + justify-content: center; + align-items: center; +} + +@keyframes gradientShift { + 0% { + background-position: 0% 50%; + } + 25% { + background-position: 100% 50%; + } + 50% { + background-position: 50% 100%; + } + 75% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} + +/* Background Blobs */ +.bg-shape { + position: absolute; + border-radius: 50%; + filter: blur(60px); + z-index: 0; + opacity: 0.6; + animation: float 10s infinite alternate; +} + +.shape-1 { + width: 300px; + height: 300px; + background: #5e60ce; + top: -50px; + left: -50px; +} + +.shape-2 { + width: 250px; + height: 250px; + background: #ff6bf0; + bottom: -50px; + right: -50px; + animation-delay: -5s; +} + +@keyframes float { + 0% { transform: translate(0, 0); } + 100% { transform: translate(30px, 30px); } +} + +.app-container { + width: 100%; + height: 100%; + position: relative; + padding: 10px; /* Slight padding from window edges if desired, or 0 */ +} + +.glass-card { + position: relative; + z-index: 1; + width: 100%; + height: 100%; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + backdrop-filter: blur(24px); + border-radius: var(--card-radius); + display: flex; + flex-direction: column; + padding: 24px; + box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2); +} + +/* Header */ +header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + -webkit-app-region: drag; /* Draggable area */ +} + +.header-info { + text-align: center; + flex: 1; + display: flex; + flex-direction: column; + align-items: center; +} + +.app-title { + font-weight: 600; + font-size: 1rem; + color: var(--text-main); +} + +.status-indicator { + font-size: 0.8rem; + color: var(--success); + margin-top: 4px; + display: flex; + align-items: center; + gap: 6px; +} + +.status-dot { + width: 6px; + height: 6px; + background-color: var(--success); + border-radius: 50%; + box-shadow: 0 0 8px var(--success); +} + +.icon-btn { + background: none; + border: none; + color: var(--text-main); + padding: 8px; + cursor: pointer; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s; + -webkit-app-region: no-drag; /* Buttons clickable */ +} + +.icon-btn:hover { + background: rgba(255, 255, 255, 0.1); +} + +.header-buttons { + display: flex; + gap: 4px; + align-items: center; + -webkit-app-region: no-drag; +} + +.close-btn:hover { + background: rgba(207, 102, 121, 0.3) !important; + color: var(--danger); +} + +/* Artwork */ +.artwork-section { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 20px; +} + +.artwork-container { + width: 220px; + height: 220px; + border-radius: 24px; + padding: 6px; /* spacing for ring */ + background: linear-gradient(135deg, rgba(255,255,255,0.1), rgba(255,255,255,0)); + box-shadow: 5px 5px 15px rgba(0,0,0,0.1), inset 1px 1px 2px rgba(255,255,255,0.3); +} + +.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); +} + +.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.18; + /* slightly smaller blur for subtle definition */ + filter: blur(6px); +} + +.b1 { width: 110px; height: 110px; left: 8%; top: 20%; background: radial-gradient(circle at 30% 30%, #c77dff, #8b5cf6); animation: float1 6s ease-in-out infinite; } +.b2 { width: 85px; height: 85px; right: 6%; top: 10%; background: radial-gradient(circle at 30% 30%, #7bffd1, #7dffb3); animation: float2 5.5s ease-in-out infinite; } +.b3 { width: 95px; height: 95px; left: 20%; bottom: 12%; background: radial-gradient(circle at 20% 20%, #ffd07a, #ff6bf0); animation: float3 7s ease-in-out infinite; } +.b4 { width: 70px; height: 70px; right: 24%; bottom: 18%; background: radial-gradient(circle at 30% 30%, #6bd3ff, #4ea8de); animation: float4 6.5s ease-in-out infinite; } +.b5 { width: 50px; height: 50px; left: 46%; top: 36%; background: radial-gradient(circle at 40% 40%, #ffa6d6, #c77dff); animation: float5 8s ease-in-out infinite; } + +/* Additional blobs */ +.b6 { width: 75px; height: 75px; left: 12%; top: 48%; background: radial-gradient(circle at 30% 30%, #bde7ff, #6bd3ff); animation: float6 6.8s ease-in-out infinite; } +.b7 { width: 42px; height: 42px; right: 10%; top: 42%; background: radial-gradient(circle at 40% 40%, #ffd9b3, #ffd07a); animation: float7 7.2s ease-in-out infinite; } +.b8 { width: 70px; height: 70px; left: 34%; bottom: 8%; background: radial-gradient(circle at 30% 30%, #e3b6ff, #c77dff); animation: float8 6.4s ease-in-out infinite; } +.b9 { width: 36px; height: 36px; right: 34%; bottom: 6%; background: radial-gradient(circle at 30% 30%, #9ef7d3, #7bffd1); animation: float9 8.4s ease-in-out infinite; } +.b10 { width: 30px; height: 30px; 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 { + text-align: center; + margin-bottom: 20px; +} + +.track-info h2 { + margin: 0; + font-size: 1.5rem; + font-weight: 600; + text-shadow: 0 2px 4px rgba(0,0,0,0.2); +} + +.track-info p { + margin: 6px 0 0; + color: var(--text-muted); + font-size: 0.95rem; +} + +/* Progress Bar (Visual) */ +.progress-container { + width: 100%; + height: 4px; + background: rgba(255,255,255,0.1); + border-radius: 2px; + margin-bottom: 30px; + position: relative; +} + +.progress-fill { + width: 100%; /* Live always full or pulsing */ + height: 100%; + background: linear-gradient(90deg, var(--accent), #fff); + border-radius: 2px; + opacity: 0.8; + box-shadow: 0 0 10px var(--accent-glow); +} + +.progress-handle { + position: absolute; + right: 0; + top: 50%; + transform: translate(50%, -50%); + width: 12px; + height: 12px; + background: #fff; + border-radius: 50%; + box-shadow: 0 0 10px rgba(255,255,255,0.8); +} + +/* Controls */ +.controls-section { + display: flex; + justify-content: center; + align-items: center; + gap: 30px; + margin-bottom: 30px; +} + +.control-btn { + background: none; + border: none; + color: var(--text-main); + cursor: pointer; + transition: transform 0.1s, opacity 0.2s; + display: flex; + align-items: center; + justify-content: center; +} + +.control-btn:active { + transform: scale(0.9); +} + +.control-btn.secondary { + width: 48px; + height: 48px; + border-radius: 50%; + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.1); + box-shadow: 0 4px 10px rgba(0,0,0,0.1); +} + +.control-btn.primary { + width: 72px; + height: 72px; + border-radius: 50%; + background: linear-gradient(135deg, rgba(255,255,255,0.2), rgba(255,255,255,0.05)); + border: 1px solid rgba(255,255,255,0.3); + box-shadow: 0 8px 20px rgba(0,0,0,0.2), inset 0 0 10px rgba(255,255,255,0.1); + color: #fff; +} + +.control-btn.primary svg { + filter: drop-shadow(0 0 5px var(--accent-glow)); +} + +/* Playing state - pulsing glow ring */ +.control-btn.primary.playing { + animation: pulse-ring 2s ease-in-out infinite; +} + +@keyframes pulse-ring { + 0%, 100% { + box-shadow: 0 8px 20px rgba(0,0,0,0.2), + inset 0 0 10px rgba(255,255,255,0.1), + 0 0 0 0 rgba(223, 166, 255, 0.7); + } + 50% { + box-shadow: 0 8px 20px rgba(0,0,0,0.2), + inset 0 0 10px rgba(255,255,255,0.1), + 0 0 0 8px rgba(223, 166, 255, 0); + } +} + +/* Icon container prevents layout jump */ +.icon-container { + position: relative; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; +} + +.icon-container svg { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.hidden { + display: none !important; +} + +/* Volume */ +.volume-section { + display: flex; + align-items: center; + gap: 12px; + margin-top: auto; + padding: 0 10px; +} + +.slider-container { + flex: 1; +} + +input[type=range] { + width: 100%; + background: transparent; + -webkit-appearance: none; + appearance: none; +} + +input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 4px; + cursor: pointer; + background: rgba(255,255,255,0.2); + border-radius: 2px; +} + +input[type=range]::-webkit-slider-thumb { + height: 16px; + width: 16px; + border-radius: 50%; + background: #ffffff; + cursor: pointer; + -webkit-appearance: none; + margin-top: -6px; /* align with track */ + box-shadow: 0 0 10px rgba(0,0,0,0.2); +} + +#volume-value { + font-size: 0.8rem; + font-weight: 500; + width: 30px; + text-align: right; +} + +.icon-btn.small { + padding: 0; + width: 24px; + height: 24px; +} + +/* Cast Overlay (Beautified as per layout2_plan.md) */ +.overlay { + position: fixed; + inset: 0; + background: rgba(20, 10, 35, 0.45); + backdrop-filter: blur(14px); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s; +} + +.overlay:not(.hidden) { + opacity: 1; + pointer-events: auto; +} + +/* Modal */ +.modal { + width: min(420px, calc(100vw - 48px)); + padding: 22px; + border-radius: 22px; + background: rgba(30, 30, 40, 0.82); + border: 1px solid rgba(255,255,255,0.12); + box-shadow: 0 30px 80px rgba(0,0,0,0.6); + color: #fff; + animation: pop 0.22s ease; + -webkit-app-region: no-drag; +} + +@keyframes pop { + from { transform: scale(0.94); opacity: 0; } + to { transform: scale(1); opacity: 1; } +} + +.modal h2 { + margin: 0 0 14px; + text-align: center; + font-size: 20px; +} + +/* Device list */ +.device-list { + list-style: none; + padding: 10px 5px; + margin: 0 0 18px; + max-height: 360px; + overflow-y: auto; +} + +/* Device row */ +.device { + padding: 12px 14px; + border-radius: 14px; + margin-bottom: 8px; + cursor: pointer; + background: rgba(255,255,255,0.05); + transition: transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease; + text-align: left; +} + +.device:hover { + background: rgba(255,255,255,0.10); + transform: translateY(-1px); +} + +.device .device-main { + font-size: 15px; + font-weight: 600; + color: var(--text-main); +} + +.device .device-sub { + margin-top: 3px; + font-size: 12px; + opacity: 0.7; + color: var(--text-muted); +} + +/* Selected device */ +.device.selected { + background: linear-gradient(135deg, #c77dff, #8b5cf6); + box-shadow: 0 0 18px rgba(199,125,255,0.65); + color: #111; +} + +.device.selected .device-main, +.device.selected .device-sub { + color: #111; +} + +.device.selected .device-sub { + opacity: 0.85; +} + +/* Cancel button */ +.btn.cancel { + width: 100%; + padding: 12px; + border-radius: 999px; + border: none; + background: #d16b7d; + color: #fff; + font-size: 15px; + cursor: pointer; + transition: transform 0.15s ease, background 0.2s; + font-weight: 600; +} + +.btn.cancel:hover { + transform: scale(1.02); + background: #e17c8d; +} diff --git a/src-tauri/gen/android/app/src/main/java/si/klevze/radioPlayer/MainActivity.kt b/src-tauri/gen/android/app/src/main/java/si/klevze/radioPlayer/MainActivity.kt new file mode 100644 index 0000000..0165eaf --- /dev/null +++ b/src-tauri/gen/android/app/src/main/java/si/klevze/radioPlayer/MainActivity.kt @@ -0,0 +1,27 @@ +package si.klevze.radioPlayer + +import android.os.Bundle +import android.webkit.WebChromeClient +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + + setContentView(R.layout.activity_main) + + val webView = findViewById(R.id.webview) + webView.webViewClient = WebViewClient() + webView.webChromeClient = WebChromeClient() + webView.settings.javaScriptEnabled = true + webView.settings.domStorageEnabled = true + webView.settings.allowFileAccess = true + + // Load the bundled web UI from assets/. + webView.loadUrl("file:///android_asset/index.html") + } +} diff --git a/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml b/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml b/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..9170623 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..28f1aa1 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..85d0c88 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..28f1aa1 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..73e48db Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..13dd214 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..73e48db Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..1d98044 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a888b33 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..1d98044 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..0818324 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a2a838e Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..0818324 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..b18bceb Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3f8a57f Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b18bceb Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/values-night/themes.xml b/src-tauri/gen/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..80ec7e3 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/src-tauri/gen/android/app/src/main/res/values/colors.xml b/src-tauri/gen/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/res/values/strings.xml b/src-tauri/gen/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..41ef4ea --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + RadioPlayer + RadioPlayer + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/res/values/themes.xml b/src-tauri/gen/android/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..80ec7e3 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/values/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml b/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..782d63b --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src-tauri/gen/android/build.gradle.kts b/src-tauri/gen/android/build.gradle.kts new file mode 100644 index 0000000..607240b --- /dev/null +++ b/src-tauri/gen/android/build.gradle.kts @@ -0,0 +1,22 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.11.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +tasks.register("clean").configure { + delete("build") +} + diff --git a/src-tauri/gen/android/buildSrc/build.gradle.kts b/src-tauri/gen/android/buildSrc/build.gradle.kts new file mode 100644 index 0000000..5c55bba --- /dev/null +++ b/src-tauri/gen/android/buildSrc/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + `kotlin-dsl` +} + +gradlePlugin { + plugins { + create("pluginsForCoolKids") { + id = "rust" + implementationClass = "RustPlugin" + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + compileOnly(gradleApi()) + implementation("com.android.tools.build:gradle:8.11.0") +} + diff --git a/src-tauri/gen/android/buildSrc/src/main/java/si/klevze/radioPlayer/kotlin/BuildTask.kt b/src-tauri/gen/android/buildSrc/src/main/java/si/klevze/radioPlayer/kotlin/BuildTask.kt new file mode 100644 index 0000000..5bc2e2b --- /dev/null +++ b/src-tauri/gen/android/buildSrc/src/main/java/si/klevze/radioPlayer/kotlin/BuildTask.kt @@ -0,0 +1,68 @@ +import java.io.File +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.logging.LogLevel +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction + +open class BuildTask : DefaultTask() { + @Input + var rootDirRel: String? = null + @Input + var target: String? = null + @Input + var release: Boolean? = null + + @TaskAction + fun assemble() { + val executable = """node"""; + try { + runTauriCli(executable) + } catch (e: Exception) { + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + // Try different Windows-specific extensions + val fallbacks = listOf( + "$executable.exe", + "$executable.cmd", + "$executable.bat", + ) + + var lastException: Exception = e + for (fallback in fallbacks) { + try { + runTauriCli(fallback) + return + } catch (fallbackException: Exception) { + lastException = fallbackException + } + } + throw lastException + } else { + throw e; + } + } + } + + fun runTauriCli(executable: String) { + val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null") + val target = target ?: throw GradleException("target cannot be null") + val release = release ?: throw GradleException("release cannot be null") + val args = listOf("tauri", "android", "android-studio-script"); + + project.exec { + workingDir(File(project.projectDir, rootDirRel)) + executable(executable) + args(args) + if (project.logger.isEnabled(LogLevel.DEBUG)) { + args("-vv") + } else if (project.logger.isEnabled(LogLevel.INFO)) { + args("-v") + } + if (release) { + args("--release") + } + args(listOf("--target", target)) + }.assertNormalExitValue() + } +} \ No newline at end of file diff --git a/src-tauri/gen/android/buildSrc/src/main/java/si/klevze/radioPlayer/kotlin/RustPlugin.kt b/src-tauri/gen/android/buildSrc/src/main/java/si/klevze/radioPlayer/kotlin/RustPlugin.kt new file mode 100644 index 0000000..4aa7fca --- /dev/null +++ b/src-tauri/gen/android/buildSrc/src/main/java/si/klevze/radioPlayer/kotlin/RustPlugin.kt @@ -0,0 +1,85 @@ +import com.android.build.api.dsl.ApplicationExtension +import org.gradle.api.DefaultTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.get + +const val TASK_GROUP = "rust" + +open class Config { + lateinit var rootDirRel: String +} + +open class RustPlugin : Plugin { + private lateinit var config: Config + + override fun apply(project: Project) = with(project) { + config = extensions.create("rust", Config::class.java) + + val defaultAbiList = listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64"); + val abiList = (findProperty("abiList") as? String)?.split(',') ?: defaultAbiList + + val defaultArchList = listOf("arm64", "arm", "x86", "x86_64"); + val archList = (findProperty("archList") as? String)?.split(',') ?: defaultArchList + + val targetsList = (findProperty("targetList") as? String)?.split(',') ?: listOf("aarch64", "armv7", "i686", "x86_64") + + extensions.configure { + @Suppress("UnstableApiUsage") + flavorDimensions.add("abi") + productFlavors { + create("universal") { + dimension = "abi" + ndk { + abiFilters += abiList + } + } + defaultArchList.forEachIndexed { index, arch -> + create(arch) { + dimension = "abi" + ndk { + abiFilters.add(defaultAbiList[index]) + } + } + } + } + } + + afterEvaluate { + for (profile in listOf("debug", "release")) { + val profileCapitalized = profile.replaceFirstChar { it.uppercase() } + val buildTask = tasks.maybeCreate( + "rustBuildUniversal$profileCapitalized", + DefaultTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for all targets" + } + + tasks["mergeUniversal${profileCapitalized}JniLibFolders"].dependsOn(buildTask) + + for (targetPair in targetsList.withIndex()) { + val targetName = targetPair.value + val targetArch = archList[targetPair.index] + val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() } + val targetBuildTask = project.tasks.maybeCreate( + "rustBuild$targetArchCapitalized$profileCapitalized", + BuildTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for $targetArch" + rootDirRel = config.rootDirRel + target = targetName + release = profile == "release" + } + + buildTask.dependsOn(targetBuildTask) + tasks["merge$targetArchCapitalized${profileCapitalized}JniLibFolders"].dependsOn( + targetBuildTask + ) + } + } + } + } +} \ No newline at end of file diff --git a/src-tauri/gen/android/gradle.properties b/src-tauri/gen/android/gradle.properties new file mode 100644 index 0000000..2a7ec69 --- /dev/null +++ b/src-tauri/gen/android/gradle.properties @@ -0,0 +1,24 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c5f9a53 --- /dev/null +++ b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue May 10 19:22:52 CST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/src-tauri/gen/android/gradlew b/src-tauri/gen/android/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/src-tauri/gen/android/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/src-tauri/gen/android/gradlew.bat b/src-tauri/gen/android/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/src-tauri/gen/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src-tauri/gen/android/settings.gradle b/src-tauri/gen/android/settings.gradle new file mode 100644 index 0000000..44ba5db --- /dev/null +++ b/src-tauri/gen/android/settings.gradle @@ -0,0 +1,3 @@ +include ':app' + +rootProject.name = 'radio_tauri' diff --git a/tools/post-build-rcedit.js b/tools/post-build-rcedit.js index d2fe892..992ab39 100644 --- a/tools/post-build-rcedit.js +++ b/tools/post-build-rcedit.js @@ -19,14 +19,26 @@ if (!fs.existsSync(iconPath)) { console.log('Patching EXE icon with rcedit...'); -// Prefer local installed binary (node_modules/.bin/rcedit) to avoid relying on npx in some CI/envs -const localBin = path.join(repoRoot, 'node_modules', '.bin', process.platform === 'win32' ? 'rcedit.exe' : 'rcedit'); +// Prefer local installed binary (node_modules/.bin) to avoid relying on npx. +// On Windows, npm typically creates a .cmd shim, which Node can execute. +const binDir = path.join(repoRoot, 'node_modules', '.bin'); +const localCandidates = process.platform === 'win32' + ? [ + path.join(binDir, 'rcedit.cmd'), + path.join(binDir, 'rcedit.exe'), + path.join(binDir, 'rcedit'), + ] + : [path.join(binDir, 'rcedit')]; + +const localBin = localCandidates.find(p => fs.existsSync(p)); + let cmd, args; -if (fs.existsSync(localBin)) { +if (localBin) { cmd = localBin; args = [exePath, '--set-icon', iconPath]; } else { - // fallback to npx + // Fallback to npx. Note: Node can't execute PowerShell shims (npx.ps1), so this may fail + // in environments that only provide .ps1 launchers. cmd = 'npx'; args = ['rcedit', exePath, '--set-icon', iconPath]; } @@ -35,7 +47,8 @@ const res = spawnSync(cmd, args, { stdio: 'inherit' }); if (res.error) { console.error(`Failed to run ${cmd}:`, res.error.message); - console.error('Ensure rcedit is installed (npm install --save-dev rcedit) or that npx is available.'); + console.error('Ensure rcedit is installed and available as a .cmd/.exe in node_modules/.bin (run `npm install`).'); + console.error('If you rely on npx, make sure you have npx.cmd on PATH (PowerShell-only shims like npx.ps1 will not work with Node spawn).'); process.exit(1); }