diff --git a/server/index.js b/server/index.js index cc44fdb..55b5235 100644 --- a/server/index.js +++ b/server/index.js @@ -80,11 +80,16 @@ if (serverConfig.xdrd.wirelessConnection === false) { serialport.open((err) => { if (err) { logError('Error opening port: ' + err.message); + setTimeout(() => { + connectToSerial(); + }, 5000); return; } logInfo('Using COM device: ' + serverConfig.xdrd.comPort); - serialport.write('x\n'); + setTimeout(() => { + serialport.write('x\n'); + }, 3000); setTimeout(() => { serialport.write('Q0\n'); @@ -107,7 +112,7 @@ if (serverConfig.xdrd.wirelessConnection === false) { serverConfig.audio.startupVolume ? serialport.write('Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n') : serialport.write('Y100\n'); - }, 3000); + }, 6000); serialport.on('data', (data) => { helpers.resolveDataBuffer(data, wss, rdsWss); @@ -118,6 +123,13 @@ if (serverConfig.xdrd.wirelessConnection === false) { }); }); + // Handle port closure + serialport.on('close', () => { + logWarn('Disconnected from ' + serverConfig.xdrd.comPort + '. Attempting to reconnect.'); + setTimeout(() => { + connectToSerial(); + }, 5000); + }); return serialport; } } diff --git a/web/css/breadcrumbs.css b/web/css/breadcrumbs.css index 8045d52..d3e1c65 100644 --- a/web/css/breadcrumbs.css +++ b/web/css/breadcrumbs.css @@ -244,14 +244,20 @@ label { display: none; } - .overlay { + #flags-container-phone, + #flags-container-desktop { + position: relative; /* Confine overlay within container which is necessary for iPhones */ + } + + #flags-container-phone .overlay, + #flags-container-desktop .overlay { position: absolute; top: 0; - bottom: 0; left: 0; right: 0; - pointer-events: auto; /* Ensure that the overlay captures clicks */ - opacity: 0; /* Make the overlay invisible */ + bottom: 0; + pointer-events: auto; + opacity: 0; } .admin-quick-dashboard { diff --git a/web/js/3las/3las.js b/web/js/3las/3las.js index bfc2f6c..23490f5 100644 --- a/web/js/3las/3las.js +++ b/web/js/3las/3las.js @@ -1,3 +1,4 @@ +var elapsedTimeConnectionWatchdog; var _3LAS_Settings = /** @class */ (function () { function _3LAS_Settings() { this.SocketHost = document.location.hostname ? document.location.hostname : "127.0.0.1"; @@ -47,6 +48,29 @@ var _3LAS = /** @class */ (function () { this.ConnectivityFlag = false; this.Stop(); // Attempt to mitigate the 0.5x speed/multiple stream bug + // Stream connection watchdog monitors mp3 frames + console.log("Stream connection watchdog active."); + let intervalReconnectWatchdog = setInterval(() => { + if (Stream) { + var endTimeConnectionWatchdog = performance.now(); + elapsedTimeConnectionWatchdog = endTimeConnectionWatchdog - window.startTimeConnectionWatchdog; + //console.log(`Stream frame elapsed time: ${elapsedTimeConnectionWatchdog} ms`); + if (elapsedTimeConnectionWatchdog > 2000 && shouldReconnect) { + clearInterval(intervalReconnectWatchdog); + setTimeout(() => { + clearInterval(intervalReconnectWatchdog); + console.log("Unstable internet connection detected, reconnecting (" + elapsedTimeConnectionWatchdog + " ms)..."); + this.Stop(); + this.Start(); + }, 2000); + } + } else { + clearInterval(intervalReconnectWatchdog); + this.Stop(); + console.log("Stream connection watchdog inactive."); + } + }, 3000); + // This is stupid, but required for Android.... thanks Google :( if (this.WakeLock) this.WakeLock.Begin(); @@ -54,8 +78,8 @@ var _3LAS = /** @class */ (function () { if (window.location.protocol === 'https:') { this.WebSocket = new WebSocketClient(this.Logger, 'wss://' + this.Settings.SocketHost + ':' + location.port.toString() + window.location.pathname + 'audio' , this.OnSocketError.bind(this), this.OnSocketConnect.bind(this), this.OnSocketDataReady.bind(this), this.OnSocketDisconnect.bind(this)); } - else { - this.WebSocket = new WebSocketClient(this.Logger, 'ws://' + this.Settings.SocketHost + ':' + location.port.toString() + window.location.pathname + 'audio' , this.OnSocketError.bind(this), this.OnSocketConnect.bind(this), this.OnSocketDataReady.bind(this), this.OnSocketDisconnect.bind(this)); + else { + this.WebSocket = new WebSocketClient(this.Logger, 'ws://' + this.Settings.SocketHost + ':' + location.port.toString() + window.location.pathname + 'audio' , this.OnSocketError.bind(this), this.OnSocketConnect.bind(this), this.OnSocketDataReady.bind(this), this.OnSocketDisconnect.bind(this)); } this.Logger.Log("Init of WebSocketClient succeeded"); this.Logger.Log("Trying to connect to server."); @@ -130,45 +154,6 @@ var _3LAS = /** @class */ (function () { if (this.ConnectivityCallback) this.ConnectivityCallback(false); } - - if (shouldReconnect) { - if (!this.ConnectivityFlag) { - console.log("Initial reconnect attempt..."); - this.Stop(); // Attempt to mitigate the 0.5x speed/multiple stream bug - this.Start(); - } - - // Delay launch of subsequent reconnect attempts by 3 seconds - setTimeout(() => { - - let streamReconnecting = false; - - let intervalReconnect = setInterval(() => { - if (this.ConnectivityFlag || typeof Stream === 'undefined' || Stream === null) { - console.log("Reconnect attempts aborted."); - clearInterval(intervalReconnect); - } else if (!streamReconnecting) { - streamReconnecting = true; - console.log("Attempting to restart stream..."); - this.Stop(); // Attempt to mitigate the 0.5x speed/multiple stream bug - this.Start(); - // Wait for reconnect attempt - setTimeout(() => { - streamReconnecting = false; - }, 3000); - } - // Restore user set volume - if (Stream && typeof newVolumeGlobal !== 'undefined' && newVolumeGlobal !== null) { - Stream.Volume = newVolumeGlobal; - console.log(`User volume restored: ${Math.round(newVolumeGlobal * 100)}%`); - } - }, 3000); - - }, 3000); - - } else { - this.Logger.Log("Reconnection is disabled."); - } }; _3LAS.prototype.OnSocketDataReady = function (data) { diff --git a/web/js/3las/fallback/formats/3las.formatreader.mpeg.js b/web/js/3las/fallback/formats/3las.formatreader.mpeg.js index c64f53f..2774f37 100644 --- a/web/js/3las/fallback/formats/3las.formatreader.mpeg.js +++ b/web/js/3las/fallback/formats/3las.formatreader.mpeg.js @@ -2,6 +2,7 @@ MPEG audio format reader is part of 3LAS (Low Latency Live Audio Streaming) https://github.com/JoJoBond/3LAS */ +window.startTimeConnectionWatchdog = performance.now(); var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || @@ -194,6 +195,7 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) { }; // Is called if the decoding of the window succeeded AudioFormatReader_MPEG.prototype.OnDecodeSuccess = function (decodedData, id, expectedTotalPlayTime, firstGranulePlayTime, lastGranulePlayTime) { + window.startTimeConnectionWatchdog = performance.now(); var extractSampleCount; var extractSampleOffset; var delta = 0.001; diff --git a/web/js/main.js b/web/js/main.js index 2ef47e6..cefecf5 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -63,9 +63,24 @@ $(document).ready(function () { } }); + // Check if device is an iPhone to prevent zoom on button press + if (/iPhone|iPod|iPad/.test(navigator.userAgent) && !window.MSStream) { + const buttons = document.querySelectorAll('button'); + buttons.forEach(button => { + button.addEventListener('touchstart', function(e) { + // Prevent default zoom behavior + e.preventDefault(); + // Allow default button action after short delay + setTimeout(() => { + e.target.click(); + }, 0); + }); + }); + } + const textInput = $('#commandinput'); - textInput.on('change', function (event) { + textInput.on('change blur', function (event) { const inputValue = Number(textInput.val()); // Check if the user agent contains 'iPhone' if (/iPhone/i.test(navigator.userAgent)) { @@ -219,8 +234,46 @@ function sendPingRequest() { .catch(error => { console.error('Error fetching ping:', error); }); + + // Automatic reconnection on WebSocket close + if (socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) { + socket = new WebSocket(socketAddress); + + socket.onopen = () => { + console.log("Main/UI reconnected successfully."); + }; + socket.onmessage = (event) => { + handleWebSocketMessage(event); + }; + socket.onerror = (error) => { + console.error("Main/UI WebSocket error during reconnection:", error); + }; + socket.onclose = () => { + console.warn("Main/UI WebSocket closed during reconnection. Will attempt to reconnect..."); + }; + } } +// Automatic UI resume on WebSocket reconnect +function handleWebSocketMessage(event) { + if (event.data == 'KICK') { + console.log('Kick initiated.') + setTimeout(() => { + window.location.href = '/403'; + }, 500); + return; + } + + parsedData = JSON.parse(event.data); + + updatePanels(parsedData); + const sum = signalData.reduce((acc, strNum) => acc + parseFloat(strNum), 0); + const averageSignal = sum / signalData.length; + data.push(averageSignal); +} +// Attach the message handler +socket.onmessage = handleWebSocketMessage; + function initCanvas(parsedData) { signalToggle = $("#signal-units-toggle"); @@ -370,7 +423,7 @@ function updateCanvas(parsedData, signalChart) { socket.onmessage = (event) => { if (event.data == 'KICK') { - console.log('Kick iniitiated.') + console.log('Kick initiated.') setTimeout(() => { window.location.href = '/403'; }, 500); @@ -908,6 +961,11 @@ function initTooltips() { posX -= tooltipWidth / 2; posY -= tooltipHeight + 10; tooltip.css({ top: posY, left: posX, opacity: 1 }); // Set opacity to 1 + // For touchscreen devices + if ((/Mobi|Android|iPhone|iPad|iPod|Opera Mini/i.test(navigator.userAgent)) && ('ontouchstart' in window || navigator.maxTouchPoints)) { + setTimeout(() => { $('.tooltiptext').remove(); }, 10000); + document.addEventListener('touchstart', function() { setTimeout(() => { $('.tooltiptext').remove(); }, 500); }); + } }, 500)); }, function() { // Clear the timeout if the mouse leaves before the delay completes