diff --git a/package-lock.json b/package-lock.json index c1be744..564daf4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,23 +1,22 @@ { "name": "fm-dx-webserver", - "version": "1.1.8", + "version": "1.1.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fm-dx-webserver", - "version": "1.1.8", + "version": "1.1.9", "license": "ISC", "dependencies": { "@mapbox/node-pre-gyp": "1.0.11", - "body-parser": "1.20.2", + "body-parser": "^1.20.2", "ejs": "3.1.9", "express": "4.18.3", "express-session": "1.18.0", "ffmpeg-static": "5.2.0", "http": "0.0.1-security", "http-proxy": "1.18.1", - "https": "1.0.0", "koffi": "2.7.2", "net": "1.0.2", "serialport": "12.0.0", @@ -651,9 +650,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "engines": { "node": ">=8" } @@ -1078,11 +1077,6 @@ "@types/node": "^10.0.3" } }, - "node_modules/https": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", - "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==" - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1783,9 +1777,9 @@ } }, "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -1882,6 +1876,11 @@ "node": ">= 0.8" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -1891,11 +1890,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/whatwg-url/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", diff --git a/package.json b/package.json index 18065e9..815b6e5 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "ffmpeg-static": "5.2.0", "http": "0.0.1-security", "http-proxy": "1.18.1", - "https": "1.0.0", "koffi": "2.7.2", "net": "1.0.2", "serialport": "12.0.0", diff --git a/server/index.js b/server/index.js index f9c11a5..113b1c3 100644 --- a/server/index.js +++ b/server/index.js @@ -23,7 +23,7 @@ const dataHandler = require('./datahandler'); const fmdxList = require('./fmdx_list'); const { logDebug, logError, logInfo, logWarn, logChat } = require('./console'); const storage = require('./storage'); -const { serverConfig } = require('./server_config'); +const { serverConfig, configExists } = require('./server_config'); const pjson = require('../package.json'); console.log(`\x1b[32m @@ -445,7 +445,11 @@ app.use(express.static(path.join(__dirname, '../web'))); // Serve the entire web httpServer.listen(serverConfig.webserver.webserverPort, serverConfig.webserver.webserverIp, () => { let currentAddress = serverConfig.webserver.webserverIp; currentAddress == '0.0.0.0' ? currentAddress = 'localhost' : currentAddress = serverConfig.webserver.webserverIp; - logInfo(`Web server is running at \x1b[34mhttp://${currentAddress}:${serverConfig.webserver.webserverPort}\x1b[0m.`); + if(configExists()) { + logInfo(`Web server has started on address \x1b[34mhttp://${currentAddress}:${serverConfig.webserver.webserverPort}\x1b[0m.`); + } else { + logInfo(`Open your browser and proceed to \x1b[34mhttp://${currentAddress}:${serverConfig.webserver.webserverPort}\x1b[0m to continue with setup.`); + } }); fmdxList.update(); diff --git a/server/stream/index.js b/server/stream/index.js index b5fbb64..6c16348 100644 --- a/server/stream/index.js +++ b/server/stream/index.js @@ -1,12 +1,10 @@ const { spawn } = require('child_process'); -const consoleCmd = require('../console.js'); const ffmpeg = require('ffmpeg-static'); -const { configName, serverConfig, configUpdate, configSave } = require('../server_config'); +const { configName, serverConfig, configUpdate, configSave, configExists } = require('../server_config'); const { logDebug, logError, logInfo, logWarn, logFfmpeg } = require('../console'); function enableAudioStream() { - var ffmpegParams; - var ffmpegCommand; + var ffmpegParams, ffmpegCommand; serverConfig.webserver.webserverPort = Number(serverConfig.webserver.webserverPort); const flags = `-fflags +nobuffer+flush_packets -flags low_delay -rtbufsize 6192 -probesize 32`; @@ -16,7 +14,7 @@ function enableAudioStream() { if (process.platform === 'win32') { // Windows ffmpegCommand = "\"" + ffmpeg.replace(/\\/g, '\\\\') + "\""; - ffmpegParams = `${flags} -f dshow -audio_buffer_size 50 -i audio="${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node server/stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`; + ffmpegParams = `${flags} -f dshow -audio_buffer_size 200 -i audio="${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node server/stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`; } else { // Linux ffmpegCommand = 'ffmpeg'; @@ -56,4 +54,6 @@ function enableAudioStream() { } } -enableAudioStream(); \ No newline at end of file +if(configExists()) { + enableAudioStream(); +} \ No newline at end of file diff --git a/web/js/init.js b/web/js/init.js index 7f089c4..5c2a2c0 100644 --- a/web/js/init.js +++ b/web/js/init.js @@ -3,7 +3,7 @@ var day = currentDate.getDate(); var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1 var year = currentDate.getFullYear(); var formattedDate = day + '/' + month + '/' + year; -var currentVersion = 'v1.1.9 [' + formattedDate + ']'; +var currentVersion = 'v1.1.9a [' + formattedDate + ']'; getInitialSettings(); diff --git a/web/js/main.js b/web/js/main.js index 029aefa..68d6992 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -219,40 +219,73 @@ function initCanvas(parsedData) { // Check if signalChart is already initialized if (!signalChart) { + const canvas = $('#signal-canvas')[0]; + const context = canvas.getContext('2d'); + const maxDataPoints = 300; + const pointWidth = (canvas.width - 80) / maxDataPoints; + signalChart = { - canvas: $('#signal-canvas')[0], - context: $('#signal-canvas')[0].getContext('2d'), - parsedData: parsedData, - maxDataPoints: 300, - } - signalChart.pointWidth = (signalChart.canvas.width - 80) / signalChart.maxDataPoints; + canvas, + context, + parsedData, + maxDataPoints, + pointWidth, + color2: null, + color4: null, + signalUnit: localStorage.getItem('signalUnit'), + offset: 0, + }; + + // Initialize colors and signal unit + updateChartSettings(signalChart); + + // Periodically check for color and signal unit updates + setInterval(() => { + updateChartSettings(signalChart); + }, 1000); // Check every 1 second } updateCanvas(parsedData, signalChart); } -function updateCanvas(parsedData, signalChart) { - const color2 = getComputedStyle(document.documentElement).getPropertyValue('--color-2').trim(); - const color4 = getComputedStyle(document.documentElement).getPropertyValue('--color-4').trim(); - const { context, canvas, maxDataPoints, pointWidth } = signalChart; +function updateChartSettings(signalChart) { + // Update colors + const newColor2 = getComputedStyle(document.documentElement).getPropertyValue('--color-2').trim(); + const newColor4 = getComputedStyle(document.documentElement).getPropertyValue('--color-4').trim(); + if (newColor2 !== signalChart.color2 || newColor4 !== signalChart.color4) { + signalChart.color2 = newColor2; + signalChart.color4 = newColor4; + } - while (data.length >= signalChart.maxDataPoints) { - data.shift(); + // Update signal unit + const newSignalUnit = localStorage.getItem('signalUnit'); + if (newSignalUnit !== signalChart.signalUnit) { + signalChart.signalUnit = newSignalUnit; + // Adjust the offset based on the new signal unit + switch(newSignalUnit) { + case 'dbuv': signalChart.offset = 11.25; break; + case 'dbm': signalChart.offset = 120; break; + default: signalChart.offset = 0; + } + } +} + +function updateCanvas(parsedData, signalChart) { + const { context, canvas, maxDataPoints, pointWidth, color2, color4, offset } = signalChart; + + if (data.length > maxDataPoints) { + data = data.slice(data.length - maxDataPoints); } const actualLowestValue = Math.min(...data); const actualHighestValue = Math.max(...data); - zoomMinValue = actualLowestValue - ((actualHighestValue - actualLowestValue) / 2); - zoomMaxValue = actualHighestValue + ((actualHighestValue - actualLowestValue) / 2); - zoomAvgValue = (zoomMaxValue - zoomMinValue) / 2 + zoomMinValue; + const zoomMinValue = actualLowestValue - ((actualHighestValue - actualLowestValue) / 2); + const zoomMaxValue = actualHighestValue + ((actualHighestValue - actualLowestValue) / 2); + const zoomAvgValue = (zoomMaxValue - zoomMinValue) / 2 + zoomMinValue; // Clear the canvas - if (context) { - context.clearRect(0, 0, canvas.width, canvas.height); - - // Draw the signal graph with smooth shifting - context.beginPath(); - } + context.clearRect(0, 0, canvas.width, canvas.height); + context.beginPath(); const startingIndex = Math.max(0, data.length - maxDataPoints); @@ -265,8 +298,6 @@ function updateCanvas(parsedData, signalChart) { } else { const prevX = canvas.width - (data.length - i + 1) * pointWidth - 40; const prevY = canvas.height - (data[i - 1] - zoomMinValue) * (canvas.height / (zoomMaxValue - zoomMinValue)); - - // Interpolate between the current and previous points const interpolatedX = (x + prevX) / 2; const interpolatedY = (y + prevY) / 2; @@ -306,17 +337,6 @@ function updateCanvas(parsedData, signalChart) { context.fillStyle = color4; context.font = '12px Titillium Web'; - const signalUnit = localStorage.getItem('signalUnit'); - let offset; - - if (signalUnit === 'dbuv') { - offset = 11.25; - } else if (signalUnit === 'dbm') { - offset = 120; - } else { - offset = 0; - } - context.textAlign = 'right'; context.fillText(`${(zoomMinValue - offset).toFixed(1)}`, 35, lowestY - 14); context.fillText(`${(zoomMaxValue - offset).toFixed(1)}`, 35, highestY + 14); @@ -327,7 +347,9 @@ function updateCanvas(parsedData, signalChart) { context.fillText(`${(zoomMaxValue - offset).toFixed(1)}`, canvas.width - 35, highestY + 14); context.fillText(`${(zoomAvgValue - offset).toFixed(1)}`, canvas.width - 35, avgY - 3); - requestAnimationFrame(() => updateCanvas(parsedData, signalChart)); + setTimeout(() => { + requestAnimationFrame(() => updateCanvas(parsedData, signalChart)); + }, 1000 / 15); } socket.onmessage = (event) => { @@ -600,40 +622,85 @@ function updateSignalUnits(parsedData, averageSignal) { $('#data-signal-decimal').text('.' + decimalPart); } -function updateDataElements(parsedData) { - const $dataFrequency = $('#data-frequency'); - const $commandInput = $("#commandinput"); - const $dataPi = $('#data-pi'); - const $dataPs = $('#data-ps'); - const $dataSt = $('.data-st'); - const $dataRt0 = $('#data-rt0 span'); - const $dataRt1 = $('#data-rt1 span'); - const $dataAntInput = $('#data-ant input'); - const $dataBwInput = $('#data-bw input'); - const $dataStationContainer = $('#data-station-container'); - const $dataTp = $('.data-tp'); - const $dataTa = $('.data-ta'); - const $dataMs = $('.data-ms'); - const $flagDesktopCointainer = $('#flags-container-desktop'); - const $dataPty = $('.data-pty'); +// Cache jQuery selectors outside of the update function +const $dataFrequency = $('#data-frequency'); +const $commandInput = $("#commandinput"); +const $dataPi = $('#data-pi'); +const $dataPs = $('#data-ps'); +const $dataSt = $('.data-st'); +const $dataRt0 = $('#data-rt0 span'); +const $dataRt1 = $('#data-rt1 span'); +const $dataAntInput = $('#data-ant input'); +const $dataBwInput = $('#data-bw input'); +const $dataStationContainer = $('#data-station-container'); +const $dataTp = $('.data-tp'); +const $dataTa = $('.data-ta'); +const $dataMs = $('.data-ms'); +const $flagDesktopCointainer = $('#flags-container-desktop'); +const $dataPty = $('.data-pty'); - $dataFrequency.text(parsedData.freq); +// Throttling function to limit the frequency of updates +function throttle(fn, wait) { + let isThrottled = false, savedArgs, savedThis; + + function wrapper() { + if (isThrottled) { + savedArgs = arguments; + savedThis = this; + return; + } + + fn.apply(this, arguments); + isThrottled = true; + + setTimeout(function() { + isThrottled = false; + if (savedArgs) { + wrapper.apply(savedThis, savedArgs); + savedArgs = savedThis = null; + } + }, wait); + } + + return wrapper; +} + +// Utility function to update element's text if changed +function updateTextIfChanged($element, newText) { + if ($element.text() !== newText) { + $element.text(newText); + } +} + +// Utility function to update element's HTML content if changed +function updateHtmlIfChanged($element, newHtml) { + if ($element.html() !== newHtml) { + $element.html(newHtml); + } +} + +// Main function to update data elements, optimized +const updateDataElements = throttle(function(parsedData) { + updateTextIfChanged($dataFrequency, parsedData.freq); $commandInput.attr("aria-label", "Current frequency: " + parsedData.freq); - $dataPi.html(parsedData.pi === '?' ? "?" : parsedData.pi); + updateHtmlIfChanged($dataPi, parsedData.pi === '?' ? "?" : parsedData.pi); if (localStorage.getItem('psUnderscores') === 'true') { parsedData.ps = parsedData.ps.replace(/\s/g, '_'); } - $dataPs.html(parsedData.ps === '?' ? "?" : processString(parsedData.ps, parsedData.ps_errors)); - $dataSt.html(`${parsedData.st_forced ? 'MO' : 'ST'}`); - $dataRt0.html(processString(parsedData.rt0, parsedData.rt0_errors)); - $dataRt1.html(processString(parsedData.rt1, parsedData.rt1_errors)); - $dataPty.html(europe_programmes[parsedData.pty]); + updateHtmlIfChanged($dataPs, parsedData.ps === '?' ? "?" : processString(parsedData.ps, parsedData.ps_errors)); - if(parsedData.rds === true) { - $flagDesktopCointainer.css('background-color', 'var(--color-2'); + updateHtmlIfChanged($dataSt, `${parsedData.st_forced ? 'MO' : 'ST'}`); + + updateHtmlIfChanged($dataRt0, processString(parsedData.rt0, parsedData.rt0_errors)); + updateHtmlIfChanged($dataRt1, processString(parsedData.rt1, parsedData.rt1_errors)); + + updateTextIfChanged($dataPty, europe_programmes[parsedData.pty]); + + if (parsedData.rds === true) { + $flagDesktopCointainer.css('background-color', 'var(--color-2)'); } else { - $flagDesktopCointainer.css('background-color', 'var(--color-1'); + $flagDesktopCointainer.css('background-color', 'var(--color-1)'); } $('.data-flag').html(``); @@ -643,20 +710,20 @@ function updateDataElements(parsedData) { $dataBwInput.val($('#data-bw li[data-value="' + parsedData.bw + '"]').text()); if (parsedData.txInfo.station.length > 1) { - $('#data-station-name').text(parsedData.txInfo.station.replace(/%/g, '%25')); - $('#data-station-erp').text(parsedData.txInfo.erp); - $('#data-station-city').text(parsedData.txInfo.city); - $('#data-station-itu').text(parsedData.txInfo.itu); - $('#data-station-pol').text(parsedData.txInfo.pol); - $('#data-station-distance').text(parsedData.txInfo.distance); - $('#data-station-azimuth').text(parsedData.txInfo.azimuth); + updateTextIfChanged($('#data-station-name'), parsedData.txInfo.station.replace(/%/g, '%25')); + updateTextIfChanged($('#data-station-erp'), parsedData.txInfo.erp); + updateTextIfChanged($('#data-station-city'), parsedData.txInfo.city); + updateTextIfChanged($('#data-station-itu'), parsedData.txInfo.itu); + updateTextIfChanged($('#data-station-pol'), parsedData.txInfo.pol); + updateTextIfChanged($('#data-station-distance'), parsedData.txInfo.distance); + updateTextIfChanged($('#data-station-azimuth'), parsedData.txInfo.azimuth); $dataStationContainer.css('display', 'block'); } else { $dataStationContainer.removeAttr('style'); } updateCounter++; - if(updateCounter % 8 === 0) { + if (updateCounter % 8 === 0) { $dataTp.html(parsedData.tp === 0 ? "TP" : "TP"); $dataTa.html(parsedData.ta === 0 ? "TA" : "TA"); $dataMs.html(parsedData.ms === 0 @@ -673,7 +740,7 @@ function updateDataElements(parsedData) { $dataRt0.attr('aria-label', parsedData.rt0); $dataRt1.attr('aria-label', parsedData.rt1); } -} +}, 100); // Update at most once every 100 milliseconds let isEventListenerAdded = false;