webrtc test

This commit is contained in:
2026-04-07 11:16:36 +02:00
parent b749c8178f
commit b617f98aec
7 changed files with 53 additions and 85 deletions
+8
View File
@@ -339,6 +339,14 @@ router.get('/server_time', (req, res) => {
router.get('/ping', (req, res) => res.send('pong')); router.get('/ping', (req, res) => res.send('pong'));
// techkrzysiek's WebRTC plugin integration from the Miedzyzdroje server.
router.get('/webrtc-audio-flags.json', (req, res) => res.json({disable3las: false}));
// /webrtc-audio.conf should serve a list of supported WebRTC endpoints; like this "56000:https://webrtc.fmdx.pro/miedzyzdroje_56k/whep"; each entry is seperated by a semicolon
// We should be able to use mediamtx for SDP/WHEP and the WebRTC itself.
// Testing only! TODO: Add this to config
router.get('/webrtc-audio.conf', (req, res) => res.send("192000;https://batorego.flerken.pl.eu.org/webrtc/whep192/whep"));
const logHistory = {}; const logHistory = {};
// Function to check if the ID has been logged within the last 60 minutes // Function to check if the ID has been logged within the last 60 minutes
+1 -1
View File
@@ -114,7 +114,7 @@ wss.on('connection', (ws, request) => {
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '18', 'text', 16 * 1024); const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '18', 'text', 16 * 1024);
if (!clientIp.includes("127.0.0.1")) { if (!clientIp.includes("127.0.0.1")) {
if (!request.session.isAdminAuthenticated && (command.startsWith('X') || command.startsWith('Y') || command.startsWith("D") || command.startsWith("")) || if (((command.startsWith('X') || command.startsWith('Y')) && !request.session.isAdminAuthenticated) ||
((command.startsWith('F') || command.startsWith('W')) && serverConfig.bwSwitch === false)) { ((command.startsWith('F') || command.startsWith('W')) && serverConfig.bwSwitch === false)) {
logWarn(`User \x1b[90m${clientIp}\x1b[0m attempted to send a potentially dangerous command: ${command.slice(0, 64)}.`); logWarn(`User \x1b[90m${clientIp}\x1b[0m attempted to send a potentially dangerous command: ${command.slice(0, 64)}.`);
return; return;
+1 -1
View File
@@ -66,6 +66,6 @@
</head> </head>
<body> <body>
<button class="playbutton">Start / Stop</button> <button class="playbutton">Start / Stop</button>
<input type="range" id="volumeSlider" min="0" max="1" step="0.01" value="1"> <input type="range" id="volumeSlider" min="0" max="1" step="0.001" value="1">
</body> </body>
</html> </html>
+10 -28
View File
@@ -1,27 +1,16 @@
var audioStreamRestartInterval; var audioStreamRestartInterval;
var elapsedTimeConnectionWatchdog; 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 () { var _3LAS = /** @class */ (function () {
function _3LAS(logger, settings) { function _3LAS(url, logger) {
this.wsurl = url;
this.Logger = logger; this.Logger = logger;
if (!this.Logger) { if (!this.Logger) this.Logger = new Logging(null, null);
this.Logger = new Logging(null, null);
}
this.Settings = settings;
try { 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); this.Fallback.ActivityCallback = this.OnActivity.bind(this);
} } catch (_b) {
catch (_b) {
this.Fallback = null; this.Fallback = null;
} }
@@ -29,9 +18,7 @@ var _3LAS = /** @class */ (function () {
this.Logger.Log('3LAS: Browser does not support either media handling methods.'); this.Logger.Log('3LAS: Browser does not support either media handling methods.');
throw new Error(); throw new Error();
} }
if (isAndroid) { if (isAndroid) this.WakeLock = new WakeLock(this.Logger);
this.WakeLock = new WakeLock(this.Logger);
}
} }
Object.defineProperty(_3LAS.prototype, "Volume", { Object.defineProperty(_3LAS.prototype, "Volume", {
get: function () { get: function () {
@@ -52,9 +39,7 @@ var _3LAS = /** @class */ (function () {
// Restart audio stream if radio data connection was reestablished // Restart audio stream if radio data connection was reestablished
// to prevent stuttering audio in some cases // to prevent stuttering audio in some cases
if (audioStreamRestartInterval) { if (audioStreamRestartInterval) clearInterval(audioStreamRestartInterval);
clearInterval(audioStreamRestartInterval);
}
audioStreamRestartInterval = setInterval(() => { audioStreamRestartInterval = setInterval(() => {
if (requiresAudioStreamRestart) { if (requiresAudioStreamRestart) {
requiresAudioStreamRestart = false; requiresAudioStreamRestart = false;
@@ -90,12 +75,9 @@ var _3LAS = /** @class */ (function () {
}, 3000); }, 3000);
// This is stupid, but required for Android.... thanks Google :( // This is stupid, but required for Android.... thanks Google :(
if (this.WakeLock) if (this.WakeLock) this.WakeLock.Begin();
this.WakeLock.Begin();
try { try {
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; this.WebSocket = new WebSocketClient(this.Logger, this.wsurl , this.OnSocketError.bind(this), this.OnSocketConnect.bind(this), this.OnSocketDataReady.bind(this), this.OnSocketDisconnect.bind(this));
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.Logger.Log("Init of WebSocketClient succeeded"); this.Logger.Log("Init of WebSocketClient succeeded");
this.Logger.Log("Trying to connect to server."); 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 () { var Fallback = /** @class */ (function () {
function Fallback(logger, settings) { function Fallback(logger, settings) {
this.Logger = logger; this.Logger = logger;
if (!this.Logger) { if (!this.Logger) this.Logger = new Logging(null, null);
this.Logger = new Logging(null, null);
}
// Create audio context // Create audio context
if (typeof AudioContext !== "undefined") if (typeof AudioContext !== "undefined") this.Audio = new AudioContext();
this.Audio = new AudioContext(); else if (typeof webkitAudioContext !== "undefined") this.Audio = new webkitAudioContext();
else if (typeof webkitAudioContext !== "undefined") else if (typeof mozAudioContext !== "undefined") this.Audio = new mozAudioContext();
this.Audio = new webkitAudioContext();
else if (typeof mozAudioContext !== "undefined")
this.Audio = new mozAudioContext();
else { else {
this.Logger.Log('3LAS: Browser does not support "AudioContext".'); this.Logger.Log('3LAS: Browser does not support "AudioContext".');
throw new Error(); throw new Error();
@@ -120,17 +115,13 @@ var Fallback = /** @class */ (function () {
} }
}; };
// Callback function from socket connection // Callback function from socket connection
Fallback.prototype.OnSocketError = function (message) { Fallback.prototype.OnSocketError = function (message) {};
}; Fallback.prototype.OnSocketConnect = function () {};
Fallback.prototype.OnSocketConnect = function () { Fallback.prototype.OnSocketDisconnect = function () {};
};
Fallback.prototype.OnSocketDisconnect = function () {
};
Fallback.prototype.OnSocketDataReady = function (data) { Fallback.prototype.OnSocketDataReady = function (data) {
this.PacketModCounter++; this.PacketModCounter++;
if (this.PacketModCounter > 100) { if (this.PacketModCounter > 100) {
if (this.ActivityCallback) if (this.ActivityCallback) this.ActivityCallback();
this.ActivityCallback();
this.PacketModCounter = 0; this.PacketModCounter = 0;
} }
this.FormatReader.PushData(new Uint8Array(data)); this.FormatReader.PushData(new Uint8Array(data));
@@ -88,9 +88,7 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) {
expectedTotalPlayTime_1 += lastGranulePlayTime_1; expectedTotalPlayTime_1 += lastGranulePlayTime_1;
bufferLength += this.Frames[this.Frames.length - 1].Data.length; bufferLength += this.Frames[this.Frames.length - 1].Data.length;
// If needed, add some space for the ID3v2 tag // If needed, add some space for the ID3v2 tag
if (this.AddId3Tag) { if (this.AddId3Tag) bufferLength += AudioFormatReader_MPEG.Id3v2Tag.length;
bufferLength += AudioFormatReader_MPEG.Id3v2Tag.length;
}
// Create a buffer long enough to hold everything // Create a buffer long enough to hold everything
var decodeBuffer = new Uint8Array(bufferLength); var decodeBuffer = new Uint8Array(bufferLength);
var offset = 0; var offset = 0;
@@ -109,8 +107,7 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) {
// Increment Id // Increment Id
var id_1 = this.Id++; var id_1 = this.Id++;
// Check if decoded frames might be too far back in the past // Check if decoded frames might be too far back in the past
if (!this.OnBeforeDecode(id_1, expectedTotalPlayTime_1)) if (!this.OnBeforeDecode(id_1, expectedTotalPlayTime_1)) return;
return;
// Push window to the decoder // Push window to the decoder
this.Audio.decodeAudioData(decodeBuffer.buffer, (function (decodedData) { this.Audio.decodeAudioData(decodeBuffer.buffer, (function (decodedData) {
var _id = id_1; var _id = id_1;
@@ -133,8 +130,7 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) {
// Sync found, set frame start // Sync found, set frame start
this.FrameStartIdx = i; this.FrameStartIdx = i;
break; break;
} } i++;
i++;
} }
} }
// Find frame end // Find frame end
@@ -172,22 +168,17 @@ var AudioFormatReader_MPEG = /** @class */ (function (_super) {
}; };
// Checks if there is a frame ready to be extracted // Checks if there is a frame ready to be extracted
AudioFormatReader_MPEG.prototype.CanExtractFrame = function () { AudioFormatReader_MPEG.prototype.CanExtractFrame = function () {
if (this.FrameStartIdx < 0 || this.FrameEndIdx < 0) if (this.FrameStartIdx < 0 || this.FrameEndIdx < 0) return false;
return false; else if (this.FrameEndIdx <= this.DataBuffer.length) return true;
else if (this.FrameEndIdx <= this.DataBuffer.length) else return false;
return true;
else
return false;
}; };
// Extract a single frame from the buffer // Extract a single frame from the buffer
AudioFormatReader_MPEG.prototype.ExtractFrame = function () { AudioFormatReader_MPEG.prototype.ExtractFrame = function () {
// Extract frame data from buffer // Extract frame data from buffer
var frameArray = this.DataBuffer.buffer.slice(this.FrameStartIdx, this.FrameEndIdx); var frameArray = this.DataBuffer.buffer.slice(this.FrameStartIdx, this.FrameEndIdx);
// Remove frame from buffer // Remove frame from buffer
if ((this.FrameEndIdx + 1) < this.DataBuffer.length) if ((this.FrameEndIdx + 1) < this.DataBuffer.length) this.DataBuffer = new Uint8Array(this.DataBuffer.buffer.slice(this.FrameEndIdx));
this.DataBuffer = new Uint8Array(this.DataBuffer.buffer.slice(this.FrameEndIdx)); else this.DataBuffer = new Uint8Array(0);
else
this.DataBuffer = new Uint8Array(0);
// Reset Start/End indices // Reset Start/End indices
this.FrameStartIdx = 0; this.FrameStartIdx = 0;
this.FrameEndIdx = -1; this.FrameEndIdx = -1;
+15 -19
View File
@@ -7,6 +7,7 @@ class WebSocketAudioPlayer {
this.sourceBuffer = null; this.sourceBuffer = null;
this.queue = []; this.queue = [];
this.started = false; this.started = false;
this._volume = 1;
} }
supported() { supported() {
@@ -21,7 +22,7 @@ class WebSocketAudioPlayer {
const liveEdge = buffered.end(buffered.length - 1); const liveEdge = buffered.end(buffered.length - 1);
const lag = liveEdge - this.audio.currentTime; const lag = liveEdge - this.audio.currentTime;
if (lag > .9) this.audio.currentTime = liveEdge - 0.2; // snap to near live edge if (lag > .85) this.audio.currentTime = liveEdge - 0.2; // snap to near live edge
} }
Start() { Start() {
@@ -59,8 +60,13 @@ class WebSocketAudioPlayer {
this.started = false; this.started = false;
} }
get Volume() {
return this._volume;
}
set Volume(v) { set Volume(v) {
if (this.audio) this.audio.volume = Math.max(0, Math.min(1, v)); const actual = Math.max(0, Math.min(1, v))
if (this.audio) this.audio.volume = actual;
this._volume = actual;
} }
_appendNext() { _appendNext() {
@@ -87,16 +93,12 @@ function createStream() {
if (MediaSource.isTypeSupported("audio/mpeg")) { if (MediaSource.isTypeSupported("audio/mpeg")) {
firefox = false; firefox = false;
Stream = new WebSocketAudioPlayer(url); Stream = new WebSocketAudioPlayer(url);
Stream.setVolume($('#volumeSlider').val());
} else { } else {
firefox = true; 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") 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(url, null);
Stream = new _3LAS(null, settings);
Stream.Volume = $('#volumeSlider').val();
Stream.ConnectivityCallback = OnConnectivityCallback;
} }
Stream.Volume = $('#volumeSlider').val();
} catch (error) { } catch (error) {
console.error("Initialization Error: ", error); console.error("Initialization Error: ", error);
} }
@@ -130,22 +132,16 @@ function OnPlayButtonClick(_ev) {
$playbutton.addClass('bg-gray').prop('disabled', true); $playbutton.addClass('bg-gray').prop('disabled', true);
setTimeout(() => $playbutton.removeClass('bg-gray').prop('disabled', false), 2000); setTimeout(() => $playbutton.removeClass('bg-gray').prop('disabled', false), 2000);
if (Stream && firefox) Stream.setVolume($("#volumeSlider").val());
} }
function updateVolume() { function updateVolume() {
if(!firefox) {
const newVolume = $(this).val(); const newVolume = $(this).val();
if (Stream) Stream.setVolume(newVolume); if(!Stream) {
else console.warn("Stream is not initialized."); console.warn("Stream is not initialized.");
} else { return;
if(Stream) {
const newVolume = $(this).val();
newVolumeGlobal = newVolume;
console.log("Volume updated to:", newVolume);
Stream.Volume = newVolume;
} else console.warn("Stream is not initialized.");
} }
Stream.Volume = newVolume;
} }
$(document).ready(Init); $(document).ready(Init);