App can run in background and stream music from radio station.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-27 10:05:25 +00:00
co-authored by Copilot
parent a1afa68a29
commit 17cf20f323
3 changed files with 176 additions and 1 deletions
+37
View File
@@ -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%;
}
}
+10 -1
View File
@@ -3,13 +3,22 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; media-src 'self' https://kryz.out.airtime.pro"
/>
<title>kryz-go</title>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<div id="app">
<h1>kryz-go</h1>
<p id="stream-status" aria-live="polite">Stream is stopped.</p>
<div class="controls">
<button id="start-stream" type="button">Start Stream</button>
<button id="stop-stream" type="button" disabled>Stop Stream</button>
</div>
<audio id="stream-player" preload="none"></audio>
</div>
<script src="js/app.js"></script>
</body>
+129
View File
@@ -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);
}
}