From 037b282e259270b6095706a6b09abd2e1915b15a Mon Sep 17 00:00:00 2001 From: NoobishSVK Date: Sun, 11 Feb 2024 20:16:51 +0100 Subject: [PATCH] ui changes, playback bugfixes --- console.js | 14 ++--- index.js | 15 ++++- web/css/modal.css | 3 +- web/index.ejs | 8 ++- .../formats/3las.formatreader.mpeg.js | 44 +++++++++++-- web/js/main.js | 62 +++++++++++++++++-- web/js/settings.js | 2 +- 7 files changed, 125 insertions(+), 23 deletions(-) diff --git a/console.js b/console.js index 4fe0a0c..6fe3fc9 100644 --- a/console.js +++ b/console.js @@ -25,9 +25,9 @@ const logDebug = (...messages) => { const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.DEBUG} ${messages.join(' ')}`; logs.push(logMessage); if (logs.length > maxLogLines) { - logs.shift(); // Remove the oldest log if the array exceeds the maximum number of lines + logs.shift(); } - console.log(logMessage); + console.log(logMessage); } }; @@ -35,7 +35,7 @@ const logError = (...messages) => { const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.ERROR} ${messages.join(' ')}`; logs.push(logMessage); if (logs.length > maxLogLines) { - logs.shift(); // Remove the oldest log if the array exceeds the maximum number of lines + logs.shift(); } console.log(logMessage); }; @@ -45,9 +45,9 @@ const logFfmpeg = (...messages) => { const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.FFMPEG} ${messages.join(' ')}`; logs.push(logMessage); if (logs.length > maxLogLines) { - logs.shift(); // Remove the oldest log if the array exceeds the maximum number of lines + logs.shift(); } - console.log(logMessage); + console.log(logMessage); } }; @@ -55,7 +55,7 @@ const logInfo = (...messages) => { const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.INFO} ${messages.join(' ')}`; logs.push(logMessage); if (logs.length > maxLogLines) { - logs.shift(); // Remove the oldest log if the array exceeds the maximum number of lines + logs.shift(); } console.log(logMessage); }; @@ -64,7 +64,7 @@ const logWarn = (...messages) => { const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.WARN} ${messages.join(' ')}`; logs.push(logMessage); if (logs.length > maxLogLines) { - logs.shift(); // Remove the oldest log if the array exceeds the maximum number of lines + logs.shift(); } console.log(logMessage); }; diff --git a/index.js b/index.js index f22eab3..82fcc80 100644 --- a/index.js +++ b/index.js @@ -121,7 +121,6 @@ function connectToXdrd() { const lines = receivedData.split('\n'); for (const line of lines) { - if (!authFlags.receivedPassword) { authFlags.receivedSalt = line.trim(); authenticateWithXdrd(client, authFlags.receivedSalt, serverConfig.xdrd.xdrdPassword); @@ -143,7 +142,7 @@ function connectToXdrd() { if (authFlags.authMsg && authFlags.firstClient) { client.write('T87500\n'); client.write('A0\n'); - client.write('G11\n'); + client.write('G00\n'); client.off('data', authDataHandler); return; } @@ -226,6 +225,17 @@ app.get('/static_data', (req, res) => { }); }); +app.get('/server_time', (req, res) => { + const serverTime = new Date().toISOString(); + res.json({ + serverTime + }); +}); + +app.get('/ping', (req, res) => { + res.send('pong'); +}); + /** * AUTHENTICATION BLOCK @@ -342,7 +352,6 @@ app.get('/getData', (req, res) => { app.get('/getDevices', (req, res) => { if (req.session.isAdminAuthenticated || !fs.existsSync('config.json')) { parseAudioDevice((result) => { - console.log(result); res.json(result); }); } else { diff --git a/web/css/modal.css b/web/css/modal.css index dc699c1..ec2c0bd 100644 --- a/web/css/modal.css +++ b/web/css/modal.css @@ -97,8 +97,7 @@ } .modal-panel-content .version-info { - position: absolute; - bottom: 0; + margin-top: 75px; width: 100%; } diff --git a/web/index.ejs b/web/index.ejs index e51913e..9e2d0b4 100644 --- a/web/index.ejs +++ b/web/index.ejs @@ -250,10 +250,16 @@ <% } %>
+ +

Time:

+

Server:

+

Local:

+ +

FM-DX WebServer
by Noobish & the OpenRadio community.

- v1.0.4 [8/2/2024] + v1.0.5 [10/2/2024]

librds & maps.fmdx.pl by Konrad Kosmatka
3LAS by JoJoBond
diff --git a/web/js/3las/fallback/formats/3las.formatreader.mpeg.js b/web/js/3las/fallback/formats/3las.formatreader.mpeg.js index d676238..c64f53f 100644 --- a/web/js/3las/fallback/formats/3las.formatreader.mpeg.js +++ b/web/js/3las/fallback/formats/3las.formatreader.mpeg.js @@ -74,13 +74,17 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) { // the full frame length by adding zeros. var bufferLength = 0; var expectedTotalPlayTime_1 = 0; - expectedTotalPlayTime_1 += this.Frames[0].SampleCount / this.Frames[0].SampleRate / 2.0; // Only half of data is usable due to overlap + var firstGranulePlayTime_1 = 0; + var lastGranulePlayTime_1 = 0; + firstGranulePlayTime_1 = this.Frames[0].SampleCount / this.Frames[0].SampleRate / 2.0; // Only half of data is usable due to overlap + expectedTotalPlayTime_1 += firstGranulePlayTime_1; bufferLength += this.Frames[0].Data.length; for (var i = 1; i < this.Frames.length - 1; i++) { expectedTotalPlayTime_1 += this.Frames[i].SampleCount / this.Frames[i].SampleRate; bufferLength += this.Frames[i].Data.length; } - expectedTotalPlayTime_1 += this.Frames[this.Frames.length - 1].SampleCount / this.Frames[this.Frames.length - 1].SampleRate / 2.0; // Only half of data is usable due to overlap + lastGranulePlayTime_1 = this.Frames[this.Frames.length - 1].SampleCount / this.Frames[this.Frames.length - 1].SampleRate / 2.0; // Only half of data is usable due to overlap + expectedTotalPlayTime_1 += lastGranulePlayTime_1; bufferLength += this.Frames[this.Frames.length - 1].Data.length; // If needed, add some space for the ID3v2 tag if (this.AddId3Tag) { @@ -110,7 +114,9 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) { this.Audio.decodeAudioData(decodeBuffer.buffer, (function (decodedData) { var _id = id_1; var _expectedTotalPlayTime = expectedTotalPlayTime_1; - this._OnDecodeSuccess(decodedData, _id, _expectedTotalPlayTime); + var _firstGranulePlayTime = firstGranulePlayTime_1; + var _lastGranulePlayTime = lastGranulePlayTime_1; + this._OnDecodeSuccess(decodedData, _id, _expectedTotalPlayTime, _firstGranulePlayTime, _lastGranulePlayTime); }).bind(this), this._OnDecodeError.bind(this)); } }; @@ -187,9 +193,10 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) { return new MPEGFrameInfo(new Uint8Array(frameArray), this.FrameSamples, this.FrameSampleRate); }; // Is called if the decoding of the window succeeded - AudioFormatReader_MPEG.prototype.OnDecodeSuccess = function (decodedData, id, expectedTotalPlayTime) { + AudioFormatReader_MPEG.prototype.OnDecodeSuccess = function (decodedData, id, expectedTotalPlayTime, firstGranulePlayTime, lastGranulePlayTime) { var extractSampleCount; var extractSampleOffset; + var delta = 0.001; // Check if we got the expected number of samples if (expectedTotalPlayTime > decodedData.duration) { // We got less samples than expect, we suspect that they were truncated equally at start and end. @@ -210,7 +217,34 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) { extractSampleCount += budgetSamples; this.TimeBudget -= (budgetSamples / decodedData.sampleRate); } - extractSampleOffset = Math.floor((decodedData.length - extractSampleCount) / 2); + var diff = decodedData.duration - expectedTotalPlayTime; + if ((diff - firstGranulePlayTime - lastGranulePlayTime) >= -delta) { + // Both first and last granule are present. Cut out middle section. + extractSampleOffset = Math.floor((decodedData.length - extractSampleCount) / 2); + } + if (Math.abs(firstGranulePlayTime - lastGranulePlayTime) <= delta) { + // First and last granule are equal. We need to make an educated guess which one is present. + if (isIOS || isIPadOS) { + // I don't know why, but Apple does things differently. + extractSampleOffset = Math.floor((decodedData.length - extractSampleCount) / 2); + } + else { + // Assume last granule is present. + extractSampleOffset = decodedData.length - extractSampleCount; + } + } + else if (Math.abs(diff - firstGranulePlayTime) <= delta) { + // Last granule is present. + extractSampleOffset = decodedData.length - extractSampleCount; + } + else if (Math.abs(diff - lastGranulePlayTime) <= delta) { + // First granule is present. + extractSampleOffset = 0; + } + else { + // The difference is not equal to neither the first, the last nor the sum of both granule. So we just use the middle. + extractSampleOffset = Math.floor((decodedData.length - extractSampleCount) / 2); + } } else { // We got the expected number of samples, no adaption needed diff --git a/web/js/main.js b/web/js/main.js index 7e743e8..605fd9a 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -123,6 +123,11 @@ $(document).ready(function () { return false; }); + setInterval(getServerTime, 10000); + getServerTime(); + setInterval(sendPingRequest, 5000); + sendPingRequest(); + var freqUpButton = $('#freq-up')[0]; var freqDownButton = $('#freq-down')[0]; var psContainer = $('#ps-container')[0]; @@ -167,6 +172,55 @@ function getInitialSettings() { }); } +function getLocalizedTime(serverTime) { + // Convert server time to a Date object + const serverDate = new Date(serverTime); + + // Get local time zone offset + const localOffset = serverDate.getTimezoneOffset() * 60000; // Convert minutes to milliseconds + + // Calculate local time by adding the offset + const localTime = new Date(serverDate.getTime() - localOffset); + + // Format local time + const options = { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }; + const formattedLocalTime = localTime.toLocaleString('en-US', options); + + return formattedLocalTime; +} + +function getServerTime() { + $.ajax({ + url: './server_time', + dataType: 'json', + success: function (data) { + // Convert server time to local time format + const localizedTimeServer = getLocalizedTime(data.serverTime); + const localizedTimeClient = getLocalizedTime(new Date().toISOString()); + + $('#server-time').text(localizedTimeServer); + $('#client-time').text(localizedTimeClient); + }, + error: function (error) { + console.error('Error:', error); + } + }); +} + +function sendPingRequest() { + const startTime = new Date().getTime(); + + fetch('/ping') + .then(response => { + const endTime = new Date().getTime(); + const pingTime = endTime - startTime; + $('#current-ping').text(`Ping: ${pingTime}ms`); + }) + .catch(error => { + console.error('Error fetching ping:', error); + }); +} + function initCanvas(parsedData) { signalToggle = $("#signal-units-toggle"); @@ -329,9 +383,8 @@ function getCurrentFreq() { function checkKey(e) { e = e || window.event; - // Check if any input element is focused using jQuery - if ($('input:focus').length > 0) { - return; // Do nothing if an input is focused + if ($('#password:focus').length > 0) { + return; } getCurrentFreq(); @@ -538,7 +591,8 @@ function updateDataElements(parsedData) { $('#data-ant input').val($('#data-ant li[data-value="' + parsedData.ant + '"]').text()); if (parsedData.txInfo.station.length > 1) { - $('#data-station-name').text(decodeURIComponent(parsedData.txInfo.station.replace(/\u009e/g, '\u017E'))); + const sanitizedStation = parsedData.txInfo.station.replace(/%/g, '%25'); + $('#data-station-name').text(decodeURIComponent(sanitizedStation.replace(/\u009e/g, '\u017E'))); $('#data-station-erp').text(parsedData.txInfo.erp); $('#data-station-city').text(parsedData.txInfo.city); $('#data-station-itu').text(parsedData.txInfo.itu); diff --git a/web/js/settings.js b/web/js/settings.js index 5c79858..a7a550d 100644 --- a/web/js/settings.js +++ b/web/js/settings.js @@ -6,7 +6,7 @@ theme4: ['#0c1c1b', '#68f7ee'], theme5: ['#171106', '#f5b642'], theme6: ['#21091d', '#ed51d3'], - theme7: ['#1d1838', '#8069fa'], + theme7: ['#0d0b1a', '#8069fa'], theme8: ['#000', '#888'], };