1
0
mirror of https://github.com/KubaPro010/fm-dx-webserver.git synced 2026-02-26 22:13:53 +01:00
This commit is contained in:
NoobishSVK
2024-07-15 22:21:47 +02:00
7 changed files with 223 additions and 41 deletions

View File

@@ -22,24 +22,41 @@ function parsePluginConfig(filePath) {
// Check if pluginConfig has frontEndPath defined
if (pluginConfig.frontEndPath) {
const sourcePath = path.join(path.dirname(filePath), pluginConfig.frontEndPath);
const destinationDir = path.join(path.dirname(filePath), '../web/js/plugins', pluginConfig.frontEndPath, '..');
const destinationDir = path.join(__dirname, '../web/js/plugins', path.dirname(pluginConfig.frontEndPath));
// Check if the source path exists
if (!fs.existsSync(sourcePath)) {
console.error(`Error: source path ${sourcePath} does not exist.`);
return pluginConfig;
}
// Check if the destination directory exists, if not, create it
if (!fs.existsSync(destinationDir)) {
fs.mkdirSync(destinationDir, { recursive: true }); // Create directory recursively
}
// Copy the file to the destination directory
const destinationFile = path.join(destinationDir, path.basename(sourcePath));
fs.copyFileSync(sourcePath, destinationFile);
setTimeout(function() {
consoleCmd.logInfo(`Plugin ${pluginConfig.name} ${pluginConfig.version} initialized successfully.`);
}, 500)
// Platform-specific handling for symlinks/junctions
if (process.platform !== 'win32') {
// On Linux, create a symlink
try {
if (fs.existsSync(destinationFile)) {
fs.unlinkSync(destinationFile); // Remove existing file/symlink
}
fs.symlinkSync(sourcePath, destinationFile);
setTimeout(function() {
consoleCmd.logInfo(`Plugin ${pluginConfig.name} ${pluginConfig.version} initialized successfully.`);
}, 500)
} catch (err) {
console.error(`Error creating symlink at ${destinationFile}: ${err.message}`);
}
}
} else {
console.error(`Error: frontEndPath is not defined in ${filePath}`);
}
} catch (err) {
console.error(`Error parsing plugin config from ${filePath}: ${err}`);
console.error(`Error parsing plugin config from ${filePath}: ${err.message}`);
}
return pluginConfig;
@@ -62,9 +79,37 @@ function collectPluginConfigs() {
return pluginConfigs;
}
// Ensure the web/js/plugins directory exists
const webJsPluginsDir = path.join(__dirname, '../web/js/plugins');
if (!fs.existsSync(webJsPluginsDir)) {
fs.mkdirSync(webJsPluginsDir, { recursive: true });
}
// Main function to create symlinks/junctions for plugins
function createLinks() {
const pluginsDir = path.join(__dirname, '../plugins');
const destinationPluginsDir = path.join(__dirname, '../web/js/plugins');
if (process.platform === 'win32') {
// On Windows, create a junction
try {
if (fs.existsSync(destinationPluginsDir)) {
fs.rmSync(destinationPluginsDir, { recursive: true });
}
fs.symlinkSync(pluginsDir, destinationPluginsDir, 'junction');
setTimeout(function() {
consoleCmd.logInfo(`Plugin ${pluginConfig.name} ${pluginConfig.version} initialized successfully.`);
}, 500)
} catch (err) {
console.error(`Error creating junction at ${destinationPluginsDir}: ${err.message}`);
}
}
}
// Usage example
const allPluginConfigs = collectPluginConfigs();
createLinks();
module.exports = {
allPluginConfigs
}
};

View File

@@ -6,6 +6,9 @@ let cachedData = {};
let lastFetchTime = 0;
const fetchInterval = 3000;
const esSwitchCache = {"lastCheck":0, "esSwitch":false};
const esFetchInterval = 300000;
// Fetch data from maps
function fetchTx(freq, piCode, rdsPs) {
const now = Date.now();
@@ -46,6 +49,7 @@ function processData(data, piCode, rdsPs) {
let maxScore = -Infinity; // Initialize maxScore with a very low value
let txAzimuth;
let maxDistance;
let esMode = checkEs();
for (const cityId in data.locations) {
const city = data.locations[cityId];
@@ -53,7 +57,11 @@ function processData(data, piCode, rdsPs) {
for (const station of city.stations) {
if (station.pi === piCode.toUpperCase() && !station.extra && station.ps && station.ps.toLowerCase().includes(rdsPs.replace(/ /g, '_').replace(/^_*(.*?)_*$/, '$1').toLowerCase())) {
const distance = haversine(serverConfig.identification.lat, serverConfig.identification.lon, city.lat, city.lon);
const score = (10*Math.log10(station.erp*1000)) / distance.distanceKm; // Calculate score
let weightDistance = distance.distanceKm
if (esMode && (distance.distanceKm > 200)) {
weightDistance = Math.abs(distance.distanceKm-1500);
}
const score = (10*Math.log10(station.erp*1000)) / weightDistance; // Calculate score
if (score > maxScore) {
maxScore = score;
txAzimuth = distance.azimuth;
@@ -82,6 +90,37 @@ function processData(data, piCode, rdsPs) {
}
}
function checkEs() {
const now = Date.now();
const url = "https://fmdx.org/includes/tools/get_muf.php";
let esSwitch = false;
if (now - esSwitchCache.lastCheck < esFetchInterval) {
esSwitch = esSwitchCache.esSwitch;
} else {
esSwitchCache.lastCheck = now;
fetch(url)
.then(response => response.json())
.then(data => {
if (serverConfig.identification.lon < -32) {
if (data.north_america.max_frequency != "No data") {
esSwitch = true;
}
} else {
if (data.europe.max_frequency != "No data") {
esSwitch = true;
}
}
esSwitchCache.esSwitch = esSwitch;
})
.catch(error => {
console.error("Error fetching data:", error);
});
}
return esSwitch;
}
function haversine(lat1, lon1, lat2, lon2) {
const R = 6371; // Earth radius in kilometers
const dLat = deg2rad(lat2 - lat1);

View File

@@ -433,7 +433,7 @@
FM-DX Webserver <span style="color: var(--color-3);" class="version-string"></span>
</p>
<p class="text-small m-0 color-3">by <a href="https://noobish.eu" target="_blank">Noobish</a>, <a href="https://fmdx.pl" target="_blank">kkonradpl</a> & the OpenRadio community.</p>
<span class="text-small" style="color: var(--color-3);">[<a href="https://list.fmdx.pl" target="_blank">Receiver Map</a>]</span>
<span class="text-small" style="color: var(--color-3);">[<a href="https://servers.fmdx.org/" target="_blank">Receiver Map</a>]</span>
<br>
<br>
<% if(ownerContact){ %>
@@ -486,6 +486,7 @@
</div>
</div>
<script src="js/websocket.js"></script>
<script src="js/webserver.js"></script>
<% plugins?.forEach(function(plugin) { %>
<script src="js/plugins/<%= plugin %>"></script>

View File

@@ -45,6 +45,7 @@ var _3LAS = /** @class */ (function () {
};
_3LAS.prototype.Start = function () {
this.ConnectivityFlag = false;
this.Stop(); // Attempt to mitigate the 0.5x speed/multiple stream bug
// This is stupid, but required for Android.... thanks Google :(
if (this.WakeLock)
@@ -128,11 +129,50 @@ var _3LAS = /** @class */ (function () {
this.ConnectivityFlag = false;
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();
}
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) {
this.Fallback.OnSocketDataReady(data);
};
return _3LAS;
}());
}());

View File

@@ -1,43 +1,69 @@
const DefaultVolume = 0.5;
let Stream;
let shouldReconnect = true;
let newVolumeGlobal = 1;
function Init(_ev) {
try {
const settings = new _3LAS_Settings();
if (!Stream) { // Ensure Stream is not re-initialized
Stream = new _3LAS(null, settings);
}
} catch (error) {
console.log(error);
return;
}
Stream.ConnectivityCallback = OnConnectivityCallback;
$(".playbutton").off('click').on('click', OnPlayButtonClick); // Ensure only one event handler is attached
$("#volumeSlider").off("input").on("input", updateVolume); // Ensure only one event handler is attached
}
function createStream() {
try {
const settings = new _3LAS_Settings();
Stream = new _3LAS(null, settings);
Stream.ConnectivityCallback = OnConnectivityCallback;
} catch (error) {
console.error("Initialization Error: ", error);
}
}
function destroyStream() {
if (Stream) {
Stream.Stop();
Stream = null;
}
}
function OnConnectivityCallback(isConnected) {
Stream.Volume = isConnected ? 1.0 : DefaultVolume;
console.log("Connectivity changed:", isConnected);
if (Stream) {
Stream.Volume = isConnected ? 1.0 : DefaultVolume;
} else {
console.warn("Stream is not initialized.");
}
}
function OnPlayButtonClick(_ev) {
const $playbutton = $('.playbutton');
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop');
if (Stream.ConnectivityFlag) {
Stream.Stop();
if (Stream) {
console.log("Stopping stream...");
shouldReconnect = false;
destroyStream();
$playbutton.find('.fa-solid').toggleClass('fa-stop fa-play');
} else {
console.log("Starting stream...");
shouldReconnect = true;
createStream();
Stream.Start();
$playbutton.addClass('bg-gray').prop('disabled', true);
setTimeout(() => {
$playbutton.removeClass('bg-gray').prop('disabled', false);
}, 3000);
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop');
}
$playbutton.addClass('bg-gray').prop('disabled', true);
setTimeout(() => {
$playbutton.removeClass('bg-gray').prop('disabled', false);
}, 3000);
}
function updateVolume() {
Stream.Volume = $(this).val();
if (Stream) {
const newVolume = $(this).val();
newVolumeGlobal = newVolume;
console.log("Volume updated to:", newVolume);
Stream.Volume = newVolume;
} else {
console.warn("Stream is not initialized.");
}
}
$(document).ready(Init);
$(document).ready(Init);

View File

@@ -1,7 +1,7 @@
var url = new URL('text', window.location.href);
url.protocol = url.protocol.replace('http', 'ws');
var socketAddress = url.href;
var socket = new WebSocket(socketAddress);
// WebSocket connection located in ./websocket.js
var parsedData, signalChart, previousFreq;
var signalData = [];
var data = [];
@@ -10,7 +10,7 @@ let updateCounter = 0;
const europe_programmes = [
"No PTY", "News", "Current Affairs", "Info",
"Sport", "Education", "Drama", "Culture", "Science", "Varied",
"Pop M", "Rock M", "Easy Listening", "Light Classical",
"Pop Music", "Rock Music", "Easy Listening", "Light Classical",
"Serious Classical", "Other Music", "Weather", "Finance",
"Children's Programmes", "Social Affairs", "Religion", "Phone-in",
"Travel", "Leisure", "Jazz Music", "Country Music", "National Music",
@@ -873,7 +873,7 @@ function initTooltips() {
// Add a delay of 500 milliseconds before creating and appending the tooltip
$(this).data('timeout', setTimeout(() => {
var tooltip = $('<div class="tooltiptext"></div>').html(tooltipText);
$('body').append(tooltip);
if ($('.tooltiptext').length === 0) { $('body').append(tooltip); } // Don't allow more than one tooltip
var posX = e.pageX;
var posY = e.pageY;
@@ -888,6 +888,7 @@ function initTooltips() {
// Clear the timeout if the mouse leaves before the delay completes
clearTimeout($(this).data('timeout'));
$('.tooltiptext').remove();
setTimeout(() => { $('.tooltiptext').remove(); }, 500); // Ensure no tooltips remain stuck
}).mousemove(function(e){
var tooltipWidth = $('.tooltiptext').outerWidth();
var tooltipHeight = $('.tooltiptext').outerHeight();

30
web/js/websocket.js Normal file
View File

@@ -0,0 +1,30 @@
var url = new URL('text', window.location.href);
url.protocol = url.protocol.replace('http', 'ws');
var socketAddress = url.href;
var socket = new WebSocket(socketAddress);
const socketPromise = new Promise((resolve, reject) => {
// Event listener for when the WebSocket connection is open
socket.addEventListener('open', () => {
console.log('WebSocket connection open');
resolve(socket); // Resolve the promise with the WebSocket instance
});
// Event listener for WebSocket errors
socket.addEventListener('error', (error) => {
console.error('WebSocket error', error);
reject(error); // Reject the promise on error
});
// Event listener for WebSocket connection closure
socket.addEventListener('close', () => {
console.warn('WebSocket connection closed');
reject(new Error('WebSocket connection closed')); // Reject with closure warning
});
});
// Assign the socketPromise to window.socketPromise for global access
window.socketPromise = socketPromise;
// Assign the socket instance to window.socket for global access
window.socket = socket;