mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-07-29 16:29:19 +02:00
125 lines
3.8 KiB
JavaScript
125 lines
3.8 KiB
JavaScript
class WebSocketAudioPlayer {
|
|
constructor(url) {
|
|
this.url = url;
|
|
this.ws = null;
|
|
this.audio = null;
|
|
this.mediaSource = null;
|
|
this.sourceBuffer = null;
|
|
this.queue = [];
|
|
this.started = false;
|
|
}
|
|
|
|
_syncToLive() {
|
|
if (!this.audio || !this.sourceBuffer) return;
|
|
const buffered = this.sourceBuffer.buffered;
|
|
if (buffered.length === 0) return;
|
|
|
|
const liveEdge = buffered.end(buffered.length - 1);
|
|
const lag = liveEdge - this.audio.currentTime;
|
|
|
|
if (lag > .3) this.audio.currentTime = liveEdge - 0.1; // snap to near live edge
|
|
}
|
|
|
|
start() {
|
|
this.audio = new Audio();
|
|
this.mediaSource = new MediaSource();
|
|
this.audio.src = URL.createObjectURL(this.mediaSource);
|
|
|
|
this.mediaSource.addEventListener('sourceopen', () => {
|
|
this.sourceBuffer = this.mediaSource.addSourceBuffer('audio/mpeg');
|
|
this.sourceBuffer.addEventListener('updateend', () => this._appendNext());
|
|
this.audio.addEventListener('waiting', () => this._syncToLive());
|
|
this.audio.addEventListener('stalled', () => this._syncToLive());
|
|
|
|
this.ws = new WebSocket(this.url);
|
|
this.ws.binaryType = 'arraybuffer';
|
|
this.ws.onmessage = (event) => {
|
|
this.queue.push(event.data);
|
|
this._appendNext();
|
|
if (!this.started && this.queue.length === 0) { this.audio.play(); this.started = true; }
|
|
};
|
|
this.ws.onclose = () => {
|
|
if (this.mediaSource.readyState === 'open') this.mediaSource.endOfStream();
|
|
};
|
|
});
|
|
|
|
this.audio.addEventListener('canplay', () => {
|
|
if (!this.started) { this.audio.play(); this.started = true; }
|
|
});
|
|
}
|
|
|
|
stop() {
|
|
this.ws?.close();
|
|
this.audio?.pause();
|
|
this.queue = [];
|
|
this.started = false;
|
|
}
|
|
|
|
setVolume(v) {
|
|
if (this.audio) this.audio.volume = Math.max(0, Math.min(1, v));
|
|
}
|
|
|
|
_appendNext() {
|
|
if (this.sourceBuffer.updating || this.queue.length === 0) return;
|
|
this.sourceBuffer.appendBuffer(this.queue.shift());
|
|
this._syncToLive();
|
|
}
|
|
}
|
|
|
|
let Stream = null;
|
|
let shouldReconnect = true;
|
|
|
|
function Init(_ev) {
|
|
$(".playbutton").off('click').on('click', OnPlayButtonClick);
|
|
$("#volumeSlider").off("input").on("input", updateVolume);
|
|
}
|
|
|
|
function createStream() {
|
|
try {
|
|
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
Stream = new WebSocketAudioPlayer(`${wsProtocol}//${location.hostname}/audio`);
|
|
Stream.setVolume($('#volumeSlider').val());
|
|
} catch (error) {
|
|
console.error("Initialization Error: ", error);
|
|
}
|
|
}
|
|
|
|
function destroyStream() {
|
|
if (Stream) {
|
|
Stream.stop();
|
|
Stream = null;
|
|
}
|
|
}
|
|
|
|
function OnPlayButtonClick(_ev) {
|
|
const $playbutton = $('.playbutton');
|
|
const isAppleiOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
|
|
|
if (Stream) {
|
|
console.log("Stopping stream...");
|
|
shouldReconnect = false;
|
|
destroyStream();
|
|
$playbutton.find('.fa-solid')?.toggleClass('fa-stop fa-play');
|
|
if (isAppleiOS && 'audioSession' in navigator) navigator.audioSession.type = "none";
|
|
} else {
|
|
console.log("Starting stream...");
|
|
shouldReconnect = true;
|
|
createStream();
|
|
Stream.start();
|
|
$playbutton.find('.fa-solid')?.toggleClass('fa-play fa-stop');
|
|
if (isAppleiOS && 'audioSession' in navigator) navigator.audioSession.type = "playback";
|
|
}
|
|
|
|
$playbutton.addClass('bg-gray').prop('disabled', true);
|
|
setTimeout(() => $playbutton.removeClass('bg-gray').prop('disabled', false), 2000);
|
|
if (Stream) Stream.setVolume($("#volumeSlider").val());
|
|
}
|
|
|
|
function updateVolume() {
|
|
const newVolume = $(this).val();
|
|
if (Stream) Stream.setVolume(newVolume);
|
|
else console.warn("Stream is not initialized.");
|
|
}
|
|
|
|
$(document).ready(Init);
|
|
window.addEventListener('load', Init, false); |