Compare commits

..
16 Commits
Author SHA1 Message Date
kuba 97b087bb5e i see 2026-04-07 12:09:02 +02:00
kuba 6f34ec23af more debug 2026-04-07 12:07:31 +02:00
kuba eee092abbe no results? 2026-04-07 12:06:09 +02:00
kuba be6aca5dc2 debug 2026-04-07 12:04:55 +02:00
kuba 5025b5cfb9 yep 2026-04-07 11:54:13 +02:00
kuba e28e258ce9 also color 2026-04-07 11:53:56 +02:00
kuba 5d0e5f92d2 this looks weird 2026-04-07 11:53:18 +02:00
kuba c9c05d8d01 complexify the audioonly 2026-04-07 11:50:39 +02:00
kuba 810965a682 this is either gonna be great, or cursed beyond any and all 2026-04-07 11:45:53 +02:00
kuba 7d9bda114a whoops 2026-04-07 11:31:47 +02:00
kuba 72a468d5fe config 2026-04-07 11:30:33 +02:00
kuba 561f002140 whoops 2026-04-07 11:20:39 +02:00
kuba b617f98aec webrtc test 2026-04-07 11:16:36 +02:00
kuba b749c8178f claude made this style 2026-04-07 10:10:28 +02:00
kuba 65f01462b0 fix? 2026-04-07 10:08:30 +02:00
kuba 94e108205d something 2026-04-07 10:07:24 +02:00
15 changed files with 1622 additions and 414 deletions
+2 -4
View File
@@ -209,8 +209,7 @@ function handleData(wss, receivedData, rdsWss) {
}
// Get the received TX info
fetchTx(parseFloat(dataToSend.freq).toFixed(1), dataToSend.pi, dataToSend.ps)
.then((currentTx) => {
fetchTx(parseFloat(dataToSend.freq).toFixed(1), dataToSend.pi, dataToSend.ps).then((currentTx) => {
if (currentTx && currentTx.station !== undefined && parseInt(currentTx.distance) < 4000) {
dataToSend.txInfo = {
tx: currentTx.station,
@@ -227,8 +226,7 @@ function handleData(wss, receivedData, rdsWss) {
score: currentTx.score,
};
}
})
.catch((error) => console.log("Error fetching Tx info:", error));
}).catch((error) => console.log("Error fetching Tx info:", error));
// Send the updated data to the client
const dataToSendJSON = JSON.stringify(dataToSend);
+2 -4
View File
@@ -42,9 +42,7 @@ function connectToSerial() {
serialport.open((err) => {
if (err) {
logError('Error opening port: ' + err.message);
setTimeout(() => {
connectToSerial();
}, 5000);
setTimeout(connectToSerial, 5000);
return;
}
@@ -78,7 +76,7 @@ function connectToSerial() {
serverConfig.audio.startupVolume
? serialport.write('Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n')
: serialport.write('Y100\n');
}, 6000);
}, 2000);
serialport.on('data', helpers.resolveDataBuffer);
serialport.on('error', (error) => logError(error.message));
+21 -47
View File
@@ -36,8 +36,7 @@ router.get('/', (req, res) => {
if (configExists() === false) {
let serialPorts;
SerialPort.list()
.then((deviceList) => {
SerialPort.list().then((deviceList) => {
serialPorts = deviceList.map(port => ({
path: port.path,
friendlyName: port.friendlyName,
@@ -49,7 +48,6 @@ router.get('/', (req, res) => {
videoDevices: result.audioDevices,
audioDevices: result.videoDevices,
serialPorts: serialPorts,
serialPorts: serialPorts,
tunerProfiles: tunerProfiles.map((profile) => ({
id: profile.id,
label: profile.label,
@@ -90,9 +88,7 @@ router.get('/403', (req, res) => {
res.render('403', { reason });
})
router.get('/audioonly', (req, res) => {
res.render('audioonly');
})
router.get('/audioonly', (req, res) => res.render('audioonly'))
router.get('/wizard', (req, res) => {
let serialPorts;
@@ -102,8 +98,7 @@ router.get('/wizard', (req, res) => {
return;
}
SerialPort.list()
.then((deviceList) => {
SerialPort.list().then((deviceList) => {
serialPorts = deviceList.map(port => ({
path: port.path,
friendlyName: port.friendlyName,
@@ -176,15 +171,6 @@ router.get('/setup', (req, res) => {
})
});
router.get('/rds', (req, res) => {
res.send('Please connect using a WebSocket compatible app to obtain the RDS stream.');
});
router.get('/rdsspy', (req, res) => {
res.send('Please connect using a WebSocket compatible app to obtain the RDS stream.');
});
router.get('/api', (req, res) => {
const { ps_errors, rt0_errors, rt1_errors, ims, eq, ant, st_forced, previousFreq, txInfo, rdsMode, ...dataToSend } = dataHandler.dataToSend;
res.json({
@@ -196,9 +182,8 @@ router.get('/api', (req, res) => {
});
});
const loginAttempts = {}; // Format: { 'ip': { count: 1, lastAttempt: 1234567890 } }
const MAX_ATTEMPTS = 10;
const MAX_ATTEMPTS = 8;
const WINDOW_MS = 15 * 60 * 1000;
const authenticate = (req, res, next) => {
@@ -242,9 +227,7 @@ router.post('/login', authenticate, (req, res) => {
router.get('/logout', (req, res) => {
// Clear the session and redirect to the main page
req.session.destroy(() => {
res.status(200).json({ message: 'Logged out successfully, refreshing the page...' });
});
req.session.destroy(() => res.status(200).json({ message: 'Logged out successfully, refreshing the page...' }));
});
router.get('/kick', (req, res) => {
@@ -254,9 +237,7 @@ router.get('/kick', (req, res) => {
res.status(403);
return;
}
setTimeout(() => {
res.redirect('/setup');
}, 500);
setTimeout(res.redirect, 500, "/setup");
});
router.get('/addToBanlist', (req, res) => {
@@ -300,7 +281,6 @@ router.get('/removeFromBanlist', (req, res) => {
res.json({ success: true, message: 'IP address removed from banlist.' });
});
router.post('/saveData', (req, res) => {
const data = req.body;
let firstSetup;
@@ -331,11 +311,8 @@ router.get('/getData', (req, res) => {
});
router.get('/getDevices', (req, res) => {
if (req.session.isAdminAuthenticated || !fs.existsSync(configName + '.json')) {
parseAudioDevice((result) => {
res.json(result);
});
} else res.status(403).json({ error: 'Unauthorized' });
if (req.session.isAdminAuthenticated || !fs.existsSync(configName + '.json')) parseAudioDevice(res.json);
else res.status(403).json({ error: 'Unauthorized' });
});
/* Static data are being sent through here on connection - these don't change when the server is running */
@@ -360,8 +337,15 @@ router.get('/server_time', (req, res) => {
res.json({ serverTime: serverTimeUTC });
});
router.get('/ping', (req, res) => {
res.send('pong');
router.get('/ping', (req, res) => res.send('pong'));
router.get('/webrtc-audio-flags.json', (req, res) => res.json(serverConfig.webRtc.flags));
router.get('/webrtc-audio.conf', (req, res) => {
let out = "";
for (const [endpoint, bitrate] of Object.entries(serverConfig.webRtc.audio)) {
out += `${bitrate * 1000}:${endpoint};`;
}
res.send(out);
});
const logHistory = {};
@@ -369,13 +353,11 @@ const logHistory = {};
// Function to check if the ID has been logged within the last 60 minutes
function canLog(id) {
const now = Date.now();
const sixtyMinutes = 60 * 60 * 1000; // 60 minutes in milliseconds
const sixtyMinutes = 60 * 60 * 1000;
// Remove expired entries
for (const [entryId, timestamp] of Object.entries(logHistory)) {
if ((now - timestamp) >= sixtyMinutes) {
delete logHistory[entryId];
}
if ((now - timestamp) >= sixtyMinutes) delete logHistory[entryId];
}
if (logHistory[id] && (now - logHistory[id]) < sixtyMinutes) return false; // Deny logging if less than 60 minutes have passed
@@ -440,16 +422,8 @@ router.get('/log_fmlist', (req, res) => {
const request = https.request(options, (response) => {
let data = '';
// Collect response chunks
response.on('data', (chunk) => {
data += chunk;
});
// Response ended
response.on('end', () => {
res.status(200).send(data);
});
response.on('data', (chunk) => data += chunk);
response.on('end', () => res.status(200).send(data));
});
// Handle errors in the request
+2 -4
View File
@@ -57,8 +57,7 @@ function authenticateWithXdrd(client, salt, password) {
sha1.update(saltBuffer);
sha1.update(passwordBuffer);
const hashedPassword = sha1.digest('hex');
client.write(hashedPassword + '\n');
client.write(sha1.digest('hex') + '\n');
client.write('x\n');
}
@@ -120,8 +119,7 @@ function handleConnect(clientIp, currentUsers, ws, callback) {
}
if (ipInfoInFlight.has(clientIp)) {
ipInfoInFlight
.get(clientIp)
ipInfoInFlight.get(clientIp)
.then((info) => processConnection(clientIp, info, currentUsers, ws, callback))
.catch(() => processConnection(clientIp, { country: undefined }, currentUsers, ws, callback));
return;
+3 -3
View File
@@ -11,15 +11,15 @@ tunnel.download();
require("./device");
require('./stream/index');
const startServer = require("./web");
{
const helpers = require('./helpers');
const plugins = helpers.findServerFiles(serverConfig.plugins);
if (plugins.length > 0) setTimeout(helpers.startPluginsWithDelay, 3000, plugins, 3000);
}
require('./stream/index');
const startServer = require("./web");
startServer(serverConfig.webserver.webserverIp === '0.0.0.0' ? 'localhost' : serverConfig.webserver.webserverIp);
tunnel.connect();
+118 -241
View File
@@ -222,271 +222,148 @@ var countries = [
]
var iso = [
"AL",
"EE",
"DZ",
"ET",
"AD",
"AO",
"FI",
"AM",
"FR",
"SH",
"GA",
"AT",
"GM",
"AZ",
"GE",
"DE",
"BH",
"GH",
"BY",
"GI",
"BE",
"GR",
"BJ",
"GN",
"BA",
"GW",
"BW",
"HU",
"BG",
"IS",
"BF",
"IQ",
"BI",
"IE",
"--",
"IL",
"CM",
"IT",
"JO",
"CV",
"KZ",
"CF",
"KE",
"TD",
"XK",
"KM",
"KW",
"CD",
"KG",
"CG",
"LV",
"CI",
"LB",
"HR",
"LS",
"CY",
"LR",
"CZ",
"LY",
"DK",
"LI",
"DJ",
"LT",
"EG",
"LU",
"GQ",
"MK",
"ER",
"MG",
"SC",
"MW",
"SL",
"ML",
"SK",
"MT",
"SI",
"MR",
"SO",
"MU",
"ZA",
"MD",
"SS",
"MC",
"ES",
"MN",
"SD",
"ME",
"SZ",
"MA",
"SE",
"MZ",
"CH",
"NA",
"SY",
"NL",
"TJ",
"NE",
"TZ",
"NG",
"TG",
"NO",
"TN",
"OM",
"TR",
"PS",
"TM",
"PL",
"UG",
"PT",
"UA",
"QA",
"AE",
"RO",
"GB",
"RU",
"UZ",
"RW",
"VA",
"SM",
"EH",
"ST",
"YE",
"SA",
"ZM",
"SN",
"ZW",
"RS",
"AI",
"GY",
"AG",
"HT",
"AR",
"HN",
"AW",
"JM",
"BS",
"MQ",
"BB",
"MX",
"BZ",
"MS",
"--",
"--",
"BO",
"NI",
"BR",
"PA",
"CA",
"PY",
"KY",
"PE",
"CL",
"--",
"CO",
"KN",
"CR",
"LC",
"CU",
"PM",
"DM",
"VC",
"DO",
"SR",
"SN",
"TT",
"TB",
"FK",
"GL",
"UY",
"GD",
"VE",
"GP",
"VG",
"GT",
"AF",
"KR",
"LA",
"AU",
"MO",
"AU",
"MY",
"AU",
"MV",
"AU",
"MH",
"AU",
"FM",
"AU",
"MM",
"AU",
"NR",
"AU",
"NP",
"BD",
"NZ",
"BT",
"PK",
"BN",
"PG",
"KH",
"PH",
"CN",
"WS",
"SG",
"SB",
"FJ",
"LK",
"HK",
"TW",
"IN",
"TH",
"ID",
"TO",
"IR",
"VU",
"JP",
"VN",
"KI",
"KP",
"--"
"AL", "EE",
"DZ", "ET",
"AD", "AO",
"FI", "AM",
"FR", "SH",
"GA", "AT",
"GM", "AZ",
"GE", "DE",
"BH", "GH",
"BY", "GI",
"BE", "GR",
"BJ", "GN",
"BA", "GW",
"BW", "HU",
"BG", "IS",
"BF", "IQ",
"BI", "IE",
"--", "IL",
"CM", "IT",
"JO", "CV",
"KZ", "CF",
"KE", "TD",
"XK", "KM",
"KW", "CD",
"KG", "CG",
"LV", "CI",
"LB", "HR",
"LS", "CY",
"LR", "CZ",
"LY", "DK",
"LI", "DJ",
"LT", "EG",
"LU", "GQ",
"MK", "ER",
"MG", "SC",
"MW", "SL",
"ML", "SK",
"MT", "SI",
"MR", "SO",
"MU", "ZA",
"MD", "SS",
"MC", "ES",
"MN", "SD",
"ME", "SZ",
"MA", "SE",
"MZ", "CH",
"NA", "SY",
"NL", "TJ",
"NE", "TZ",
"NG", "TG",
"NO", "TN",
"OM", "TR",
"PS", "TM",
"PL", "UG",
"PT", "UA",
"QA", "AE",
"RO", "GB",
"RU", "UZ",
"RW", "VA",
"SM", "EH",
"ST", "YE",
"SA", "ZM",
"SN", "ZW",
"RS", "AI",
"GY", "AG",
"HT", "AR",
"HN", "AW",
"JM", "BS",
"MQ", "BB",
"MX", "BZ",
"MS", "--",
"--", "BO",
"NI", "BR",
"PA", "CA",
"PY", "KY",
"PE", "CL",
"--", "CO",
"KN", "CR",
"LC", "CU",
"PM", "DM",
"VC", "DO",
"SR", "SN",
"TT", "TB",
"FK", "GL",
"UY", "GD",
"VE", "GP",
"VG", "GT",
"AF", "KR",
"LA", "AU",
"MO", "AU",
"MY", "AU",
"MV", "AU",
"MH", "AU",
"FM", "AU",
"MM", "AU",
"NR", "AU",
"NP", "BD",
"NZ", "BT",
"PK", "BN",
"PG", "KH",
"PH", "CN",
"WS", "SG",
"SB", "FJ",
"LK", "HK",
"TW", "IN",
"TH", "ID",
"TO", "IR",
"VU", "JP",
"VN", "KI",
"KP", "--"
]
const rdsEccA0A6Lut = [
// A0
[
"USA/VI/PR", "USA/VI/PR", "USA/VI/PR", "USA/VI/PR", "USA/VI/PR",
[ "USA/VI/PR", "USA/VI/PR", "USA/VI/PR", "USA/VI/PR", "USA/VI/PR",
"USA/VI/PR", "USA/VI/PR", "USA/VI/PR", "USA/VI/PR", "USA/VI/PR",
"USA/VI/PR", "", "USA/VI/PR", "USA/VI/PR", ""
],
// A1
[
"", "", "", "", "",
"", "", "", "", "",
[ "", "", "", "", "", "", "", "", "", "",
"Canada", "Canada", "Canada", "Canada", "Greenland"
],
// A2
[
"Anguilla", "Antigua and Barbuda", "Brazil/Equador", "Falkland Islands", "Barbados",
[ "Anguilla", "Antigua and Barbuda", "Brazil/Equador", "Falkland Islands", "Barbados",
"Belize", "Cayman Islands", "Costa Rica", "Cuba", "Argentina",
"Brazil", "Brazil/Bermuda", "Brazil/AN", "Guadeloupe", "Bahamas"
],
// A3
[
"Bolivia", "Colombia", "Jamaica", "Martinique", "",
[ "Bolivia", "Colombia", "Jamaica", "Martinique", "",
"Paraguay", "Nicaragua", "", "Panama", "Dominica",
"Dominican Republic", "Chile", "Grenada", "Turks and Caicos islands", "Guyana"
],
// A4
[
"Guatemala", "Honduras", "Aruba", "", "Montserrat",
[ "Guatemala", "Honduras", "Aruba", "", "Montserrat",
"Trinidad and Tobago", "Peru", "Suriname", "Uruguay", "St. Kitts",
"St. Lucia", "El Salvador", "Haiti", "Venezuela", "Virgin Islands"
],
// A5
[
"", "", "", "", "",
"", "", "", "", "",
"Mexico", "St. Vincent", "Mexico", "Mexico", "Mexico"
],
[ "", "", "", "", "", "", "", "", "", "",
"Mexico", "St. Vincent", "Mexico", "Mexico", "Mexico"],
// A6
[
"", "", "", "", "",
"", "", "", "", "",
"", "", "", "", "St. Pierre and Miquelon"
]
["", "", "", "", "", "", "", "", "", "", "", "", "", "", "St. Pierre and Miquelon"]
];
const rdsEccD0D4Lut = [
+7 -1
View File
@@ -123,7 +123,13 @@ let serverConfig = {
antennaStartup: "0",
antennaNoUsers: "0",
antennaNoUsersDelay: false,
trustedProxies: []
trustedProxies: [],
webRtc: {
flags: {
disable3las: false
},
audio: {}
}
};
// Function to add missing fields without overwriting existing values
+21 -24
View File
@@ -61,15 +61,12 @@ let currentUsers = 0;
let timeoutAntenna;
wss.on('connection', (ws, request) => {
const output = storage.ctl_output;
let clientIp = helpers.getIpAddress(request);
const userCommandHistory = {};
// Per-IP limit connection open
if (clientIp) {
const isLocalIp = (
clientIp === '127.0.0.1' || clientIp === '::1' ||
clientIp.startsWith('192.168.') || clientIp.startsWith('10.') || clientIp.startsWith('172.16.'));
const isLocalIp = (clientIp === '127.0.0.1' || clientIp === '::1' || clientIp.startsWith('192.168.') || clientIp.startsWith('10.') || clientIp.startsWith('172.16.'));
if (!isLocalIp) {
if (!ipConnectionCounts.has(clientIp)) ipConnectionCounts.set(clientIp, 0);
const currentCount = ipConnectionCounts.get(clientIp);
@@ -160,7 +157,7 @@ wss.on('connection', (ws, request) => {
if (tuningLimit && (tuneFreq < tuningLowerLimit || tuneFreq > tuningUpperLimit) || isNaN(tuneFreq)) return;
}
if ((serverConfig.publicTuner && !serverConfig.lockToAdmin) || isAdminAuthenticated || (!serverConfig.publicTuner && !serverConfig.lockToAdmin && isTuneAuthenticated)) output.write(`${command}\n`);
if ((serverConfig.publicTuner && !serverConfig.lockToAdmin) || isAdminAuthenticated || (!serverConfig.publicTuner && !serverConfig.lockToAdmin && isTuneAuthenticated)) storage.ctl_output.write(`${command}\n`);
});
ws.on('close', (code) => {
@@ -191,35 +188,34 @@ wss.on('connection', (ws, request) => {
if (currentUsers === 0) {
storage.connectedUsers = [];
if (serverConfig.bwAutoNoUsers === "1") output.write("W0\n"); // Auto BW 'Enabled'
if (serverConfig.bwAutoNoUsers === "1") storage.ctl_output.write("W0\n"); // Auto BW 'Enabled'
// cEQ and iMS combinations
if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "1") output.write("G00\n"); // Both Disabled
else if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "0") output.write(`G0${dataHandler.dataToSend.ims}\n`);
else if (serverConfig.ceqNoUsers === "0" && serverConfig.imsNoUsers === "1") output.write(`G${dataHandler.dataToSend.eq}0\n`);
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "0") output.write(`G1${dataHandler.dataToSend.ims}\n`);
else if (serverConfig.ceqNoUsers === "0" && serverConfig.imsNoUsers === "2") output.write(`G${dataHandler.dataToSend.eq}1\n`);
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "1") output.write("G10\n"); // Only cEQ enabled
else if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "2") output.write("G01\n"); // Only iMS enabled
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "2") output.write("G11\n"); // Both Enabled
if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "1") storage.ctl_output.write("G00\n"); // Both Disabled
else if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "0") storage.ctl_output.write(`G0${dataHandler.dataToSend.ims}\n`);
else if (serverConfig.ceqNoUsers === "0" && serverConfig.imsNoUsers === "1") storage.ctl_output.write(`G${dataHandler.dataToSend.eq}0\n`);
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "0") storage.ctl_output.write(`G1${dataHandler.dataToSend.ims}\n`);
else if (serverConfig.ceqNoUsers === "0" && serverConfig.imsNoUsers === "2") storage.ctl_output.write(`G${dataHandler.dataToSend.eq}1\n`);
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "1") storage.ctl_output.write("G10\n"); // Only cEQ enabled
else if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "2") storage.ctl_output.write("G01\n"); // Only iMS enabled
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "2") storage.ctl_output.write("G11\n"); // Both Enabled
// Handle stereo mode
if (serverConfig.stereoNoUsers === "1") output.write("B0\n");
else if (serverConfig.stereoNoUsers === "2") output.write("B1\n");
if (serverConfig.stereoNoUsers === "1") storage.ctl_output.write("B0\n");
else if (serverConfig.stereoNoUsers === "2") storage.ctl_output.write("B1\n");
// Handle Antenna selection
if (timeoutAntenna) clearTimeout(timeoutAntenna);
timeoutAntenna = setTimeout(() => {
if (serverConfig.antennaNoUsers === "1") output.write("Z0\n");
else if (serverConfig.antennaNoUsers === "2") output.write("Z1\n");
else if (serverConfig.antennaNoUsers === "3") output.write("Z2\n");
else if (serverConfig.antennaNoUsers === "4") output.write("Z3\n");
if (serverConfig.antennaNoUsers === "1") storage.ctl_output.write("Z0\n");
else if (serverConfig.antennaNoUsers === "2") storage.ctl_output.write("Z1\n");
else if (serverConfig.antennaNoUsers === "3") storage.ctl_output.write("Z2\n");
else if (serverConfig.antennaNoUsers === "4") storage.ctl_output.write("Z3\n");
}, serverConfig.antennaNoUsersDelay ? 15000 : 0);
}
if (tunerLockTracker.has(ws)) {
logInfo(`User who locked the tuner left. Unlocking the tuner.`);
output.write('wT0\n') // Why are we telling the tuner that?
tunerLockTracker.delete(ws);
serverConfig.publicTuner = true;
}
@@ -228,7 +224,7 @@ wss.on('connection', (ws, request) => {
serverConfig.autoShutdown !== true && serverConfig.xdrd.wirelessConnection === true) {
setTimeout(function() {
if (currentUsers === 0) {
output.write('T' + Math.round(serverConfig.defaultFreq * 1000) + '\n');
storage.ctl_output.write('T' + Math.round(serverConfig.defaultFreq * 1000) + '\n');
dataHandler.resetToDefault(dataHandler.dataToSend);
dataHandler.dataToSend.freq = Number(serverConfig.defaultFreq).toFixed(3);
dataHandler.initialData.freq = Number(serverConfig.defaultFreq).toFixed(3);
@@ -254,10 +250,11 @@ pluginsWss.on('connection', (ws, request) => {
ws.on('message', message => {
// Anti-spam
helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '10', 'data_plugins');
const msg2 = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '10', 'data_plugins');
if(!msg2) return;
try {
let messageData = JSON.parse(message); // Attempt to parse the JSON. This code will attemp to parse json using the JSON.parse which coincidentally actually parses the json
let messageData = JSON.parse(msg2); // Attempt to parse the JSON. This code will attemp to parse json using the JSON.parse which coincidentally actually parses the json
if (messageData.type === "GPS" && messageData.value) {
const gpsData = messageData.value;
+65 -2
View File
@@ -18,9 +18,72 @@
<script src="js/3las/fallback/3las.fallback.js"></script>
<script src="js/3las/3las.js"></script>
<script src="js/audio.js"></script>
<script src="js/rtc-audio.js"></script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: #111;
color: #eee;
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
gap: 20px;
}
.playbutton {
background-color: #222;
color: #eee;
border: 1px solid #444;
padding: 10px 24px;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: background 0.2s;
}
.playbutton:hover {
background-color: #333;
}
.playbutton:active {
background-color: #444;
}
#volumeSlider {
width: 100%;
max-width: 800px;
accent-color: #4af;
cursor: pointer;
}
.fmdx-webrtc-playbutton-host .fmdx-webrtc-playbutton {
background-color: #222;
color: #eee;
height: auto !important;
min-height: unset !important;
padding: 10px 24px !important;
}
</style>
</head>
<body>
<button class="playbutton">Start / Stop</button>
<input type="range" id="volumeSlider" min="0" max="1" step="0.01" value="1">
<div class="hide-phone">
<div class="panel-10">
<button class="playbutton">
<i class="fa fa-play"></i>
</button>
</div>
</div>
<input type="range" id="volumeSlider" min="0" max="1" step="0.001" value="1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
</body>
</html>
+2 -1
View File
@@ -18,7 +18,7 @@
<meta property="og:title" content="FM-DX WebServer [<%= tunerName %>]">
<meta property="og:type" content="website">
<meta property="og:image" content="https://fmdx.org/img/webserver_icon.png">
<meta property="og:image" content="https://flerken.zapto.org:5000/img/webserver_icon.png">
<meta property="og:description" content="Server description: <%= tunerDescMeta %>.">
<script src="js/3las/util/3las.helpers.js"></script>
@@ -31,6 +31,7 @@
<script src="js/3las/fallback/3las.fallback.js"></script>
<script src="js/3las/3las.js"></script>
<script src="js/audio.js"></script>
<script src="js/rtc-audio.js"></script>
<script type="text/javascript">
document.ontouchmove = function(e) {
e.preventDefault();
+10 -28
View File
@@ -1,27 +1,16 @@
var audioStreamRestartInterval;
var elapsedTimeConnectionWatchdog;
var _3LAS_Settings = /** @class */ (function () {
function _3LAS_Settings() {
this.SocketHost = location.hostname ? location.hostname : "127.0.0.1";
this.SocketPort = location.port;
this.SocketPath = "/";
this.Fallback = new Fallback_Settings();
}
return _3LAS_Settings;
}());
var _3LAS = /** @class */ (function () {
function _3LAS(logger, settings) {
function _3LAS(url, logger) {
this.wsurl = url;
this.Logger = logger;
if (!this.Logger) {
this.Logger = new Logging(null, null);
}
this.Settings = settings;
if (!this.Logger) this.Logger = new Logging(null, null);
try {
this.Fallback = new Fallback(this.Logger, this.Settings.Fallback);
this.Fallback_Settings = new Fallback_Settings();
this.Fallback = new Fallback(this.Logger, this.Fallback_Settings);
this.Fallback.ActivityCallback = this.OnActivity.bind(this);
}
catch (_b) {
} catch (_b) {
this.Fallback = null;
}
@@ -29,9 +18,7 @@ var _3LAS = /** @class */ (function () {
this.Logger.Log('3LAS: Browser does not support either media handling methods.');
throw new Error();
}
if (isAndroid) {
this.WakeLock = new WakeLock(this.Logger);
}
if (isAndroid) this.WakeLock = new WakeLock(this.Logger);
}
Object.defineProperty(_3LAS.prototype, "Volume", {
get: function () {
@@ -52,9 +39,7 @@ var _3LAS = /** @class */ (function () {
// Restart audio stream if radio data connection was reestablished
// to prevent stuttering audio in some cases
if (audioStreamRestartInterval) {
clearInterval(audioStreamRestartInterval);
}
if (audioStreamRestartInterval) clearInterval(audioStreamRestartInterval);
audioStreamRestartInterval = setInterval(() => {
if (requiresAudioStreamRestart) {
requiresAudioStreamRestart = false;
@@ -90,12 +75,9 @@ var _3LAS = /** @class */ (function () {
}, 3000);
// This is stupid, but required for Android.... thanks Google :(
if (this.WakeLock)
this.WakeLock.Begin();
if (this.WakeLock) this.WakeLock.Begin();
try {
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${wsProtocol}//${location.hostname}/audio`;
this.WebSocket = new WebSocketClient(this.Logger, url , this.OnSocketError.bind(this), this.OnSocketConnect.bind(this), this.OnSocketDataReady.bind(this), this.OnSocketDisconnect.bind(this));
this.WebSocket = new WebSocketClient(this.Logger, this.wsurl , 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.");
}
+8 -17
View File
@@ -17,16 +17,11 @@ var Fallback_Settings = /** @class */ (function () {
var Fallback = /** @class */ (function () {
function Fallback(logger, settings) {
this.Logger = logger;
if (!this.Logger) {
this.Logger = new Logging(null, null);
}
if (!this.Logger) this.Logger = new Logging(null, null);
// Create audio context
if (typeof AudioContext !== "undefined")
this.Audio = new AudioContext();
else if (typeof webkitAudioContext !== "undefined")
this.Audio = new webkitAudioContext();
else if (typeof mozAudioContext !== "undefined")
this.Audio = new mozAudioContext();
if (typeof AudioContext !== "undefined") this.Audio = new AudioContext();
else if (typeof webkitAudioContext !== "undefined") this.Audio = new webkitAudioContext();
else if (typeof mozAudioContext !== "undefined") this.Audio = new mozAudioContext();
else {
this.Logger.Log('3LAS: Browser does not support "AudioContext".');
throw new Error();
@@ -120,17 +115,13 @@ var Fallback = /** @class */ (function () {
}
};
// Callback function from socket connection
Fallback.prototype.OnSocketError = function (message) {
};
Fallback.prototype.OnSocketConnect = function () {
};
Fallback.prototype.OnSocketDisconnect = function () {
};
Fallback.prototype.OnSocketError = function (message) {};
Fallback.prototype.OnSocketConnect = function () {};
Fallback.prototype.OnSocketDisconnect = function () {};
Fallback.prototype.OnSocketDataReady = function (data) {
this.PacketModCounter++;
if (this.PacketModCounter > 100) {
if (this.ActivityCallback)
this.ActivityCallback();
if (this.ActivityCallback) this.ActivityCallback();
this.PacketModCounter = 0;
}
this.FormatReader.PushData(new Uint8Array(data));
@@ -88,9 +88,7 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) {
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) {
bufferLength += AudioFormatReader_MPEG.Id3v2Tag.length;
}
if (this.AddId3Tag) bufferLength += AudioFormatReader_MPEG.Id3v2Tag.length;
// Create a buffer long enough to hold everything
var decodeBuffer = new Uint8Array(bufferLength);
var offset = 0;
@@ -109,8 +107,7 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) {
// Increment Id
var id_1 = this.Id++;
// Check if decoded frames might be too far back in the past
if (!this.OnBeforeDecode(id_1, expectedTotalPlayTime_1))
return;
if (!this.OnBeforeDecode(id_1, expectedTotalPlayTime_1)) return;
// Push window to the decoder
this.Audio.decodeAudioData(decodeBuffer.buffer, (function (decodedData) {
var _id = id_1;
@@ -133,8 +130,7 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) {
// Sync found, set frame start
this.FrameStartIdx = i;
break;
}
i++;
} i++;
}
}
// Find frame end
@@ -172,22 +168,17 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) {
};
// Checks if there is a frame ready to be extracted
AudioFormatReader_MPEG.prototype.CanExtractFrame = function () {
if (this.FrameStartIdx < 0 || this.FrameEndIdx < 0)
return false;
else if (this.FrameEndIdx <= this.DataBuffer.length)
return true;
else
return false;
if (this.FrameStartIdx < 0 || this.FrameEndIdx < 0) return false;
else if (this.FrameEndIdx <= this.DataBuffer.length) return true;
else return false;
};
// Extract a single frame from the buffer
AudioFormatReader_MPEG.prototype.ExtractFrame = function () {
// Extract frame data from buffer
var frameArray = this.DataBuffer.buffer.slice(this.FrameStartIdx, this.FrameEndIdx);
// Remove frame from buffer
if ((this.FrameEndIdx + 1) < this.DataBuffer.length)
this.DataBuffer = new Uint8Array(this.DataBuffer.buffer.slice(this.FrameEndIdx));
else
this.DataBuffer = new Uint8Array(0);
if ((this.FrameEndIdx + 1) < this.DataBuffer.length) this.DataBuffer = new Uint8Array(this.DataBuffer.buffer.slice(this.FrameEndIdx));
else this.DataBuffer = new Uint8Array(0);
// Reset Start/End indices
this.FrameStartIdx = 0;
this.FrameEndIdx = -1;
+16 -20
View File
@@ -7,6 +7,7 @@ class WebSocketAudioPlayer {
this.sourceBuffer = null;
this.queue = [];
this.started = false;
this._volume = 1;
}
supported() {
@@ -21,7 +22,7 @@ class WebSocketAudioPlayer {
const liveEdge = buffered.end(buffered.length - 1);
const lag = liveEdge - this.audio.currentTime;
if (lag > .9) this.audio.currentTime = liveEdge - 0.2; // snap to near live edge
if (lag > 1) this.audio.currentTime = liveEdge - 0.25; // snap to near live edge
}
Start() {
@@ -59,8 +60,13 @@ class WebSocketAudioPlayer {
this.started = false;
}
setVolume(v) {
if (this.audio) this.audio.volume = Math.max(0, Math.min(1, v));
get Volume() {
return this._volume;
}
set Volume(v) {
const actual = Math.max(0, Math.min(1, v))
if (this.audio) this.audio.volume = actual;
this._volume = actual;
}
_appendNext() {
@@ -87,16 +93,12 @@ function createStream() {
if (MediaSource.isTypeSupported("audio/mpeg")) {
firefox = false;
Stream = new WebSocketAudioPlayer(url);
Stream.setVolume($('#volumeSlider').val());
} else {
firefox = true;
alert("Your browser does not support MP3 over MSE, meaning we have to use 3LAS which is terrible and gives terrible audio quality. If you want better sound, switch to anything chromium based")
const settings = new _3LAS_Settings();
Stream = new _3LAS(null, settings);
Stream.Volume = $('#volumeSlider').val();
Stream.ConnectivityCallback = OnConnectivityCallback;
Stream = new _3LAS(url, null);
}
Stream.Volume = $('#volumeSlider').val();
} catch (error) {
console.error("Initialization Error: ", error);
}
@@ -130,22 +132,16 @@ function OnPlayButtonClick(_ev) {
$playbutton.addClass('bg-gray').prop('disabled', true);
setTimeout(() => $playbutton.removeClass('bg-gray').prop('disabled', false), 2000);
if (Stream && firefox) Stream.setVolume($("#volumeSlider").val());
}
function updateVolume() {
if(!firefox) {
const newVolume = $(this).val();
if (Stream) Stream.setVolume(newVolume);
else console.warn("Stream is not initialized.");
} else {
if(Stream) {
const newVolume = $(this).val();
newVolumeGlobal = newVolume;
console.log("Volume updated to:", newVolume);
Stream.Volume = newVolume;
} else console.warn("Stream is not initialized.");
if(!Stream) {
console.warn("Stream is not initialized.");
return;
}
Stream.Volume = newVolume;
}
$(document).ready(Init);
+1336
View File
File diff suppressed because it is too large Load Diff