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);
+ }
+}