From 17cf20f32396011bd69c2090571ae81d06a7f004 Mon Sep 17 00:00:00 2001 From: kfj001 Date: Mon, 27 Apr 2026 10:05:25 +0000 Subject: [PATCH] App can run in background and stream music from radio station. Co-authored-by: Copilot --- www/css/style.css | 37 +++++++++++++ www/index.html | 11 +++- www/js/app.js | 129 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 1 deletion(-) diff --git a/www/css/style.css b/www/css/style.css index 959d20a..96501e1 100644 --- a/www/css/style.css +++ b/www/css/style.css @@ -15,10 +15,47 @@ body { flex-direction: column; align-items: center; justify-content: center; + gap: 1rem; min-height: 100vh; padding: 1rem; + text-align: center; } h1 { font-size: 2rem; } + +#stream-status { + font-size: 1rem; +} + +.controls { + display: flex; + gap: 0.75rem; +} + +button { + border: none; + border-radius: 0.5rem; + padding: 0.75rem 1rem; + font-size: 1rem; + font-weight: 600; + color: #ffffff; + background: #1456a7; +} + +button:disabled { + background: #9da8b3; + color: #f5f5f5; +} + +@media (max-width: 480px) { + .controls { + width: 100%; + flex-direction: column; + } + + button { + width: 100%; + } +} diff --git a/www/index.html b/www/index.html index 9190c5e..fef0b4c 100644 --- a/www/index.html +++ b/www/index.html @@ -3,13 +3,22 @@ - + kryz-go

kryz-go

+

Stream is stopped.

+
+ + +
+
diff --git a/www/js/app.js b/www/js/app.js index 307998b..1f6a506 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -1,4 +1,133 @@ // kryz-go app entry point window.addEventListener('DOMContentLoaded', () => { console.log('kryz-go loaded'); + initializeStreamPlayer(); + initializeBackgroundMode(); }); + +function initializeStreamPlayer() { + const streamUrl = 'https://kryz.out.airtime.pro/kryz_b'; + const audio = document.getElementById('stream-player'); + const startButton = document.getElementById('start-stream'); + const stopButton = document.getElementById('stop-stream'); + const status = document.getElementById('stream-status'); + + if (!audio || !startButton || !stopButton || !status) { + console.error('Stream player controls are missing from the page.'); + return; + } + + const setStatus = (isPlaying, text) => { + startButton.disabled = isPlaying; + stopButton.disabled = !isPlaying; + status.textContent = text; + }; + + const ensureBackgroundPlaybackPermissions = async () => { + const backgroundMode = window.Capacitor?.Plugins?.BackgroundMode; + + if (!backgroundMode) { + return true; + } + + const microphonePermission = await backgroundMode.checkMicrophonePermission(); + if (microphonePermission.microphone === 'granted') { + return true; + } + + const requestedMicrophonePermission = await backgroundMode.requestMicrophonePermission(); + if (requestedMicrophonePermission.microphone === 'granted') { + return true; + } + + setStatus(false, 'Enable microphone permission in Android settings for screen-off playback.'); + console.warn('Background playback blocked: microphone permission not granted.'); + return false; + }; + + const startStream = async () => { + try { + const hasBackgroundPermissions = await ensureBackgroundPlaybackPermissions(); + if (!hasBackgroundPermissions) { + return; + } + + audio.src = streamUrl; + await audio.play(); + setStatus(true, 'Stream is playing.'); + } catch (error) { + setStatus(false, 'Unable to start stream.'); + console.error('Failed to start stream:', error); + } + }; + + const stopStream = () => { + audio.pause(); + audio.removeAttribute('src'); + audio.load(); + setStatus(false, 'Stream is stopped.'); + }; + + startButton.addEventListener('click', startStream); + stopButton.addEventListener('click', stopStream); + audio.addEventListener('ended', () => { + setStatus(false, 'Stream ended.'); + }); + audio.addEventListener('error', () => { + setStatus(false, 'Stream error.'); + }); +} + +async function initializeBackgroundMode() { + const backgroundMode = window.Capacitor?.Plugins?.BackgroundMode; + + if (!backgroundMode) { + console.warn('BackgroundMode plugin is not available in this runtime.'); + return; + } + + try { + const notificationPermission = await backgroundMode.checkNotificationsPermission(); + if (notificationPermission.notifications !== 'granted') { + await backgroundMode.requestNotificationsPermission(); + } + + const microphonePermission = await backgroundMode.checkMicrophonePermission(); + if (microphonePermission.microphone !== 'granted') { + const requestedMicrophonePermission = await backgroundMode.requestMicrophonePermission(); + if (requestedMicrophonePermission.microphone !== 'granted') { + console.warn('BackgroundMode microphone permission denied; background playback may stop when screen is off.'); + } + } + + const batteryOptimizations = await backgroundMode.checkBatteryOptimizations(); + if (batteryOptimizations.enabled) { + console.warn('Battery optimizations are enabled; requesting exemption for reliable background playback.'); + await backgroundMode.requestDisableBatteryOptimizations(); + } + + await backgroundMode.disableWebViewOptimizations(); + + await backgroundMode.enable({ + title: 'KRYZ-Go!', + text: 'Background mode is active', + channelName: 'kryz-go background', + channelDescription: 'Keeps kryz-go running while the app is in background', + resume: true, + silent: false, + disableWebViewOptimization: true, + }); + + await backgroundMode.addListener('appInBackground', () => { + console.log('kryz-go moved to background'); + }); + + await backgroundMode.addListener('appInForeground', () => { + console.log('kryz-go returned to foreground'); + }); + + console.log('BackgroundMode plugin enabled.'); + } catch (error) { + console.error('Failed to initialize BackgroundMode plugin:', error); + } +}