audioonly

This commit is contained in:
2026-04-03 12:44:53 +02:00
parent ffd7b9cf8b
commit dab1a35abd
5 changed files with 48 additions and 35 deletions
+30 -26
View File
@@ -22,9 +22,9 @@ router.get('/', (req, res) => {
let requestIp = helpers.getIpAddress(req); let requestIp = helpers.getIpAddress(req);
const ipList = (requestIp || '').split(',').map(ip => ip.trim()).filter(Boolean); // in case there are multiple IPs (proxy), we need to check all of them (No we don't) const ipList = (requestIp || '').split(',').map(ip => ip.trim()).filter(Boolean); // in case there are multiple IPs (proxy), we need to check all of them (No we don't)
const banEntry = serverConfig.webserver.banlist.find(banEntry => ipList.includes(banEntry[0])); const banEntry = serverConfig.webserver.banlist.find(banEntry => ipList.includes(banEntry[0]));
if (banEntry) { if (banEntry) {
const reason = banEntry[3]; const reason = banEntry[3];
res.render('403', { reason }); res.render('403', { reason });
@@ -36,14 +36,14 @@ router.get('/', (req, res) => {
if (configExists() === false) { if (configExists() === false) {
let serialPorts; let serialPorts;
SerialPort.list() SerialPort.list()
.then((deviceList) => { .then((deviceList) => {
serialPorts = deviceList.map(port => ({ serialPorts = deviceList.map(port => ({
path: port.path, path: port.path,
friendlyName: port.friendlyName, friendlyName: port.friendlyName,
})); }));
parseAudioDevice((result) => { parseAudioDevice((result) => {
res.render('wizard', { // Magical utility wizard res.render('wizard', { // Magical utility wizard
isAdminAuthenticated: true, isAdminAuthenticated: true,
@@ -91,9 +91,13 @@ router.get('/403', (req, res) => {
res.render('403', { reason }); res.render('403', { reason });
}) })
router.get('/audioonly', (req, res) => {
res.render('audioonly');
})
router.get('/wizard', (req, res) => { router.get('/wizard', (req, res) => {
let serialPorts; let serialPorts;
if(!req.session.isAdminAuthenticated) { if(!req.session.isAdminAuthenticated) {
res.render('login'); res.render('login');
return; return;
@@ -105,7 +109,7 @@ router.get('/wizard', (req, res) => {
path: port.path, path: port.path,
friendlyName: port.friendlyName, friendlyName: port.friendlyName,
})); }));
parseAudioDevice((result) => { parseAudioDevice((result) => {
res.render('wizard', { res.render('wizard', {
isAdminAuthenticated: req.session.isAdminAuthenticated, isAdminAuthenticated: req.session.isAdminAuthenticated,
@@ -121,9 +125,9 @@ router.get('/wizard', (req, res) => {
}); });
}) })
}) })
router.get('/setup', (req, res) => { router.get('/setup', (req, res) => {
let serialPorts; let serialPorts;
function loadConfig() { function loadConfig() {
if (fs.existsSync(configPath)) { if (fs.existsSync(configPath)) {
const configFileContents = fs.readFileSync(configPath, 'utf8'); const configFileContents = fs.readFileSync(configPath, 'utf8');
@@ -136,18 +140,18 @@ router.get('/setup', (req, res) => {
res.render('login'); res.render('login');
return; return;
} }
SerialPort.list() SerialPort.list()
.then((deviceList) => { .then((deviceList) => {
serialPorts = deviceList.map(port => ({ serialPorts = deviceList.map(port => ({
path: port.path, path: port.path,
friendlyName: port.friendlyName, friendlyName: port.friendlyName,
})); }));
parseAudioDevice((result) => { parseAudioDevice((result) => {
const processUptimeInSeconds = Math.floor(process.uptime()); const processUptimeInSeconds = Math.floor(process.uptime());
const formattedProcessUptime = helpers.formatUptime(processUptimeInSeconds); const formattedProcessUptime = helpers.formatUptime(processUptimeInSeconds);
const updatedConfig = loadConfig(); // Reload the config every time const updatedConfig = loadConfig(); // Reload the config every time
res.render('setup', { res.render('setup', {
isAdminAuthenticated: req.session.isAdminAuthenticated, isAdminAuthenticated: req.session.isAdminAuthenticated,
@@ -171,9 +175,9 @@ router.get('/setup', (req, res) => {
})) }))
}); });
}); });
}) })
}); });
router.get('/rds', (req, res) => { router.get('/rds', (req, res) => {
res.send('Please connect using a WebSocket compatible app to obtain the RDS stream.'); res.send('Please connect using a WebSocket compatible app to obtain the RDS stream.');
@@ -197,7 +201,7 @@ router.get('/api', (req, res) => {
const loginAttempts = {}; // Format: { 'ip': { count: 1, lastAttempt: 1234567890 } } const loginAttempts = {}; // Format: { 'ip': { count: 1, lastAttempt: 1234567890 } }
const MAX_ATTEMPTS = 10; const MAX_ATTEMPTS = 10;
const WINDOW_MS = 15 * 60 * 1000; const WINDOW_MS = 15 * 60 * 1000;
const authenticate = (req, res, next) => { const authenticate = (req, res, next) => {
const ip = helpers.getIpAddress(req); const ip = helpers.getIpAddress(req);
@@ -305,7 +309,7 @@ router.post('/saveData', (req, res) => {
if(req.session.isAdminAuthenticated || !configExists()) { if(req.session.isAdminAuthenticated || !configExists()) {
configUpdate(data); configUpdate(data);
fmdxList.update(); fmdxList.update();
if(!configExists()) firstSetup = true; if(!configExists()) firstSetup = true;
logInfo('Server config changed successfully.'); logInfo('Server config changed successfully.');
if(firstSetup === true) res.status(200).send('Data saved successfully!\nPlease, restart the server to load your configuration.'); if(firstSetup === true) res.status(200).send('Data saved successfully!\nPlease, restart the server to load your configuration.');
@@ -313,9 +317,9 @@ router.post('/saveData', (req, res) => {
} }
}); });
router.get('/getData', (req, res) => { router.get('/getData', (req, res) => {
if (configExists() === false) res.json(serverConfig); if (configExists() === false) res.json(serverConfig);
if(req.session.isAdminAuthenticated) { if(req.session.isAdminAuthenticated) {
// Check if the file exists // Check if the file exists
fs.access(configPath, fs.constants.F_OK, (err) => { fs.access(configPath, fs.constants.F_OK, (err) => {
@@ -360,7 +364,7 @@ router.get('/server_time', (req, res) => {
router.get('/ping', (req, res) => { router.get('/ping', (req, res) => {
res.send('pong'); res.send('pong');
}); });
const logHistory = {}; const logHistory = {};
@@ -368,27 +372,27 @@ const logHistory = {};
function canLog(id) { function canLog(id) {
const now = Date.now(); const now = Date.now();
const sixtyMinutes = 60 * 60 * 1000; // 60 minutes in milliseconds const sixtyMinutes = 60 * 60 * 1000; // 60 minutes in milliseconds
// Remove expired entries // Remove expired entries
for (const [entryId, timestamp] of Object.entries(logHistory)) { for (const [entryId, timestamp] of Object.entries(logHistory)) {
if ((now - timestamp) >= sixtyMinutes) { if ((now - timestamp) >= sixtyMinutes) {
delete logHistory[entryId]; delete logHistory[entryId];
} }
} }
if (logHistory[id] && (now - logHistory[id]) < sixtyMinutes) return false; // Deny logging if less than 60 minutes have passed if (logHistory[id] && (now - logHistory[id]) < sixtyMinutes) return false; // Deny logging if less than 60 minutes have passed
logHistory[id] = now; // Update with the current timestamp logHistory[id] = now; // Update with the current timestamp
return true; return true;
} }
router.get('/log_fmlist', (req, res) => { router.get('/log_fmlist', (req, res) => {
if (dataHandler.dataToSend.txInfo.tx.length === 0) { if (serverConfig.extras.fmlistIntegration === false || (serverConfig.extras.fmlistAdminOnly && !req.session.isTuneAuthenticated)) {
res.status(500).send('No suitable transmitter to log.'); res.status(500).send('FMLIST Integration is not available.');
return; return;
} }
if (serverConfig.extras.fmlistIntegration === false || (serverConfig.extras.fmlistAdminOnly && !req.session.isTuneAuthenticated)) { if (dataHandler.dataToSend.txInfo.tx.length === 0) {
res.status(500).send('FMLIST Integration is not available.'); res.status(500).send('No suitable transmitter to log.');
return; return;
} }
@@ -468,7 +472,7 @@ router.get('/tunnelservers', async (req, res) => {
{ value: "sg", host: "sg.fmtuner.org", label: "Asia & Oceania" }, { value: "sg", host: "sg.fmtuner.org", label: "Asia & Oceania" },
{ value: "pldx", host: "pldx.duckdns.org", label: "Poland (k201)" }, { value: "pldx", host: "pldx.duckdns.org", label: "Poland (k201)" },
]; ];
const results = await Promise.all( const results = await Promise.all(
servers.map(async s => { servers.map(async s => {
const latency = await helpers.checkLatency(s.host); const latency = await helpers.checkLatency(s.host);
@@ -478,7 +482,7 @@ router.get('/tunnelservers', async (req, res) => {
}; };
}) })
); );
res.json(results); res.json(results);
}); });
+1 -2
View File
@@ -319,7 +319,7 @@ wss.on('connection', (ws, request) => {
} }
} }
if (clientIp !== '::ffff:127.0.0.1' || if (clientIp !== '127.0.0.1' ||
(request.socket && request.socket.remoteAddress && request.socket.remoteAddress !== '::ffff:127.0.0.1') || (request.socket && request.socket.remoteAddress && request.socket.remoteAddress !== '::ffff:127.0.0.1') ||
(request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) { (request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
currentUsers++; currentUsers++;
@@ -392,7 +392,6 @@ wss.on('connection', (ws, request) => {
const isLocalIp = ( const isLocalIp = (
clientIp === '127.0.0.1' || clientIp === '127.0.0.1' ||
clientIp === '::1' || clientIp === '::1' ||
clientIp === '::ffff:127.0.0.1' ||
clientIp.startsWith('192.168.') || clientIp.startsWith('192.168.') ||
clientIp.startsWith('10.') || clientIp.startsWith('10.') ||
clientIp.startsWith('172.16.') clientIp.startsWith('172.16.')
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>FM-DX Webserver (Audio)</title>
<link rel="icon" type="image/svg+xml" href="favicon.svg" id="favicon" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="js/audio.js"></script>
</head>
<body>
<button class="playbutton">start</button>
<input type="range" id="volumeSlider" min="0" max="1" step="0.01" value="1">
</body>
</html>
-3
View File
@@ -539,9 +539,6 @@
</div> </div>
<div class="modal-panel-footer flex-container flex-phone"> <div class="modal-panel-footer flex-container flex-phone">
<div class="modal-panel-sidebar" style="font-size: 22px;"> <div class="modal-panel-sidebar" style="font-size: 22px;">
<div class="flex-center" style="height: 50px">
<i class="fa-solid fa-hand-holding-medical"></i>
</div>
<div class="flex-center" style="height: 50px"> <div class="flex-center" style="height: 50px">
<i class="fa-brands fa-discord"></i> <i class="fa-brands fa-discord"></i>
</div> </div>
+3 -4
View File
@@ -77,8 +77,7 @@ function Init(_ev) {
function createStream() { function createStream() {
try { try {
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const audioUrl = `${wsProtocol}//${location.hostname}/audio`; Stream = new WebSocketAudioPlayer(`${wsProtocol}//${location.hostname}/audio`);
Stream = new WebSocketAudioPlayer(audioUrl);
Stream.setVolume($('#volumeSlider').val()); Stream.setVolume($('#volumeSlider').val());
} catch (error) { } catch (error) {
console.error("Initialization Error: ", error); console.error("Initialization Error: ", error);
@@ -100,14 +99,14 @@ function OnPlayButtonClick(_ev) {
console.log("Stopping stream..."); console.log("Stopping stream...");
shouldReconnect = false; shouldReconnect = false;
destroyStream(); destroyStream();
$playbutton.find('.fa-solid').toggleClass('fa-stop fa-play'); $playbutton.find('.fa-solid')?.toggleClass('fa-stop fa-play');
if (isAppleiOS && 'audioSession' in navigator) navigator.audioSession.type = "none"; if (isAppleiOS && 'audioSession' in navigator) navigator.audioSession.type = "none";
} else { } else {
console.log("Starting stream..."); console.log("Starting stream...");
shouldReconnect = true; shouldReconnect = true;
createStream(); createStream();
Stream.start(); Stream.start();
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop'); $playbutton.find('.fa-solid')?.toggleClass('fa-play fa-stop');
if (isAppleiOS && 'audioSession' in navigator) navigator.audioSession.type = "playback"; if (isAppleiOS && 'audioSession' in navigator) navigator.audioSession.type = "playback";
} }