App can run in background and stream music from radio station.
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -15,10 +15,47 @@ body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2rem;
|
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
@@ -3,13 +3,22 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
<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>
|
<title>kryz-go</title>
|
||||||
<link rel="stylesheet" href="css/style.css" />
|
<link rel="stylesheet" href="css/style.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<h1>kryz-go</h1>
|
<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>
|
</div>
|
||||||
<script src="js/app.js"></script>
|
<script src="js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+129
@@ -1,4 +1,133 @@
|
|||||||
// kryz-go app entry point
|
// kryz-go app entry point
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
console.log('kryz-go loaded');
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user