diff --git a/html/webpage/assets/js/overview.js b/html/webpage/assets/js/overview.js
index 72dc879..83a9e68 100644
--- a/html/webpage/assets/js/overview.js
+++ b/html/webpage/assets/js/overview.js
@@ -251,94 +251,61 @@ function GetListeningZones() {
}
/**
- * Starts live audio for the selected broadcast zone.
- * @param {String} bz Broadcast Zone
+ * Open or Close Live Audio for the selected Broadcast Zone.
+ * @param {string} command either 'Open' or 'Close'
+ * @param {string} bz Broadcast Zone
+ * @param {Function} cbOK callback function on success
+ * @param {Function} cbFail callback function on failure
*/
-function StartLiveAudio(bz, cbOK = null, cbFail = null) {
- if (bz && bz.length > 0) {
- let playurl = `/api/LiveAudio/Open/${bz}`;
- const listenaudio = document.getElementById('listenaudio');
- if (listenaudio) {
- fetch(playurl, { method: 'GET' })
- .then(response => {
- console.log("Fetch response for Live Audio:", JSON.stringify(response));
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- return response.blob();
- })
- .then(blob => {
- console.log(`Received audio stream for Broadcast Zone: ${bz}`);
- const url = window.URL.createObjectURL(blob);
-
- if (listenaudio) {
- listenaudio.pause();
- listenaudio.src = url;
- listenaudio.load();
- listenaudio.play();
- console.log(`Started Live Audio for Broadcast Zone: ${bz}`);
- listenaudio.setAttribute('visibility', 'visible');
- if (cbOK) cbOK();
- } else new Error("Listening audio element not found.");
- })
- .catch(error => {
- alert(`Error starting Live Audio for Broadcast Zone: ${bz}. ${error}`);
- if (cbFail) cbFail();
- });
- } else {
- alert("Listening audio element not found.");
- if (cbFail) cbFail();
- }
- } else {
- alert("Please select a Broadcast Zone to start Live Audio.");
- if (cbFail) cbFail();
+function LiveAudioCommand(command, bz, cbOK = null, cbFail = null) {
+ function raise_cbOK(value=null){
+ if (cbOK) cbOK(value);
}
+ function raise_cbFail(value){
+ if (cbFail) cbFail(value);
+ }
+
+ if (command && command.length>0){
+ if (bz && bz.length>0){
+ if (command === 'Open' || command === 'Close'){
+ let url = `/api/LiveAudio`;
+ let payload = {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ command: command,
+ broadcastzone: bz
+ })
+ };
+ fetch(url, payload)
+ .then(response => {
+ console.log(`Fetch response for Live Audio Command ${command}:`, JSON.stringify(response));
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return response.json();
+ })
+ .then(data => {
+ console.log(`Live Audio Command ${command} for Broadcast Zone: ${bz} executed on server.`, data);
+ raise_cbOK(data);
+ })
+ .catch(error => {
+ console.log(`Error executing Live Audio Command ${command} for Broadcast Zone: ${bz} on server. ${error}`);
+ raise_cbFail(error);
+ });
+
+ } else raise_cbFail("LiveAudioCommand: Unknown command "+command);
+ } else raise_cbFail("LiveAudioCommand: Broadcast Zone is empty");
+ } else raise_cbFail("LiveAudioCommand: command is empty");
}
/**
- * Stops live audio for the selected broadcast zone.
- * @param {String} bz Broadcast Zone
- * @param {Function} cbOK Callback function on success
- * @param {Function} cbFail Callback function on failure
+ * Websocket for streaming
*/
-function StopLiveAudio(bz, cbOK = null, cbFail = null) {
- if (bz && bz.length > 0) {
- const listenaudio = document.getElementById('listenaudio');
- if (listenaudio) {
- listenaudio.pause();
- listenaudio.src = "";
- console.log("Stopped Live Audio.");
-
- let url = `/api/LiveAudio/Close/${bz}`;
- fetch(url, { method: 'GET' })
- .then(response => {
- console.log("Fetch response for closing Live Audio:", JSON.stringify(response));
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- return response.json();
- })
- .then(data => {
- console.log(`Live Audio for Broadcast Zone: ${bz} closed on server.`, data);
- listenaudio.setAttribute('visibility', 'hidden');
- if (cbOK) cbOK();
- })
- .catch(error => {
- console.log(`Error closing Live Audio for Broadcast Zone: ${bz} on server. ${error}`);
- if (cbFail) cbFail();
- });
-
-
-
- } else {
- alert("Listening audio element not found.");
- if (cbFail) cbFail();
- }
- } else {
- alert("Please select a Broadcast Zone to stop Live Audio.");
- if (cbFail) cbFail();
- }
-}
+let streamws = null;
+let mediasource = null;
$(document).ready(function () {
console.log("overview.js loaded");
@@ -350,12 +317,48 @@ $(document).ready(function () {
let $icon = $(this).find('svg');
if ($icon.hasClass('fa-stop')) {
console.log("Stopping Live Audio for Broadcast Zone:", bz);
- StopLiveAudio(bz);
+ LiveAudioCommand('Close', bz, (okdata) =>{
+ $icon.toggleClass('fa-stop fa-play');
+ $("#listenzone").prop('disabled', false);
+ if (streamws) {
+ streamws.close();
+ streamws = null;
+ }
+ if (mediasource) {
+ mediasource.endOfStream();
+ mediasource = null;
+ }
+ let audio = document.getElementById('listenaudio');
+ audio.src = "";
+
+ }, (errdata) =>{
+ alert("Error stopping Live Audio: " + errdata);
+ });
} else {
console.log("Starting Live Audio for Broadcast Zone:", bz);
- StartLiveAudio(bz);
+ LiveAudioCommand('Open', bz, (okdata) =>{
+ $icon.toggleClass('fa-stop fa-play');
+ $("#listenzone").prop('disabled', true);
+ streamws = new WebSocket(`ws://${window.location.host}/LiveAudio/ws`);
+ mediasource = new MediaSource();
+ let audio = document.getElementById('listenaudio');
+ audio.src = URL.createObjectURL(mediasource);
+ mediasource.addEventListener('sourceopen', () => {
+ const sourceBuffer = mediasource.addSourceBuffer('audio/mpeg; codecs="mp3"');
+ streamws.binaryType = 'arraybuffer';
+ streamws.onmessage = (event) => {
+ if (event.data instanceof ArrayBuffer) {
+ const chunk = new Uint8Array(event.data);
+ sourceBuffer.appendBuffer(chunk);
+ }
+ };
+ });
+
+ }, (errdata) =>{
+ alert("Error starting Live Audio: " + errdata);
+ });
}
- $icon.toggleClass('fa-stop fa-play');
+
});
$('#clearpagingqueue').off('click').on('click', function () {
DoClear("QueuePaging/", "Paging Queue", (okdata) => {
diff --git a/libs/linux-aarch64/libbassopus.so b/libs/linux-aarch64/libbassopus.so
new file mode 100644
index 0000000..6cebe42
Binary files /dev/null and b/libs/linux-aarch64/libbassopus.so differ
diff --git a/libs/linux-armhf/libbassopus.so b/libs/linux-armhf/libbassopus.so
new file mode 100644
index 0000000..c21b493
Binary files /dev/null and b/libs/linux-armhf/libbassopus.so differ
diff --git a/libs/linux-x86-64/libbassopus.so b/libs/linux-x86-64/libbassopus.so
new file mode 100644
index 0000000..20c3b9e
Binary files /dev/null and b/libs/linux-x86-64/libbassopus.so differ
diff --git a/libs/linux-x86/libbassopus.so b/libs/linux-x86/libbassopus.so
new file mode 100644
index 0000000..f9906a0
Binary files /dev/null and b/libs/linux-x86/libbassopus.so differ
diff --git a/libs/win32-x86-64/bassopus.dll b/libs/win32-x86-64/bassopus.dll
new file mode 100644
index 0000000..9743836
Binary files /dev/null and b/libs/win32-x86-64/bassopus.dll differ
diff --git a/libs/win32-x86/bassopus.dll b/libs/win32-x86/bassopus.dll
new file mode 100644
index 0000000..6297357
Binary files /dev/null and b/libs/win32-x86/bassopus.dll differ
diff --git a/src/MainExtension01.kt b/src/MainExtension01.kt
index d95bf23..988afed 100644
--- a/src/MainExtension01.kt
+++ b/src/MainExtension01.kt
@@ -901,22 +901,18 @@ class MainExtension01 {
val remark = variables?.get("REMARK").orEmpty()
when(remark){
"GOP" -> {
- //TODO Combobox First_Call_Message_Chooser.
val remarkMsg = config.Get(configKeys.REMARK_GOP.key)
ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0
}
"GBD" ->{
- // TODO Combobox Second_Call_Message_Chooser
val remarkMsg = config.Get(configKeys.REMARK_GBD.key)
ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0
}
"GFC" ->{
- // TODO Combobox Final_Call_Message_Chooser
val remarkMsg = config.Get(configKeys.REMARK_GFC.key)
ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0
}
"FLD" ->{
- // TODO Combobox Landed_Message_Chooser
val remarkMsg = config.Get(configKeys.REMARK_FLD.key)
ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0
}
diff --git a/src/audio/AudioPlayer.kt b/src/audio/AudioPlayer.kt
index 17ee231..1a723dc 100644
--- a/src/audio/AudioPlayer.kt
+++ b/src/audio/AudioPlayer.kt
@@ -22,6 +22,7 @@ class AudioPlayer (var samplingrate: Int = 44100) {
val bassencmp3: BassEncMP3 = BassEncMP3.Instance
val bassencopus: BassEncOpus = BassEncOpus.Instance
val bassencogg : BassEncOGG = BassEncOGG.Instance
+ val bassmix : BassMix = BassMix.Instance
var initedDevice = -1
@@ -34,6 +35,7 @@ class AudioPlayer (var samplingrate: Int = 44100) {
Logger.info { "BassEncMP3 version ${Integer.toHexString(bassencmp3.BASS_Encode_MP3_GetVersion())}" }
Logger.info { "BassEncOpus version ${Integer.toHexString(bassencopus.BASS_Encode_OPUS_GetVersion())}" }
Logger.info {" BassEncOGG version ${Integer.toHexString(bassencogg.BASS_Encode_OGG_GetVersion())}"}
+ Logger.info {" BassMix version ${Integer.toHexString(bassmix.BASS_Mixer_GetVersion())}"}
InitAudio(0) // Audio 0 is No Sound, use for reading and writing wav silently
}
diff --git a/src/audio/BassOpus.java b/src/audio/BassOpus.java
new file mode 100644
index 0000000..1be5d5b
--- /dev/null
+++ b/src/audio/BassOpus.java
@@ -0,0 +1,37 @@
+package audio;
+
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Pointer;
+
+@SuppressWarnings("unused")
+public interface BassOpus extends Library {
+ BassOpus Instance = (BassOpus) Native.load("bassopus", BassOpus.class);
+ // BASS_CHANNELINFO type
+ int BASS_CTYPE_STREAM_OPUS = 0x11200;
+
+ // Additional attributes
+ int BASS_ATTRIB_OPUS_ORIGFREQ = 0x13000;
+ int BASS_ATTRIB_OPUS_GAIN = 0x13001;
+
+ class BASS_OPUS_HEAD {
+ public byte version;
+ public byte channels;
+ public short preskip;
+ public int inputrate;
+ public short gain;
+ public byte mapping;
+ public byte streams;
+ public byte coupled;
+ public byte[] chanmap;
+ }
+ int BASS_STREAMPROC_OPUS_LOSS = 0x40000000;
+
+ int BASS_OPUS_StreamCreate(BASS_OPUS_HEAD head, int flags, Bass.STREAMPROC proc, Object user);
+ int BASS_OPUS_StreamCreate(BASS_OPUS_HEAD head, int flags, int proc, Object user);
+ int BASS_OPUS_StreamCreateFile(String file, long offset, long length, int flags);
+ int BASS_OPUS_StreamCreateFile(Pointer file, long offset, long length, int flags);
+ int BASS_OPUS_StreamCreateURL(String url, int offset, int flags, Bass.DOWNLOADPROC proc, Object user);
+ int BASS_OPUS_StreamCreateFileUser(int system, int flags, Bass.BASS_FILEPROCS procs, Object user);
+ int BASS_OPUS_StreamPutData(int handle, Pointer buffer, int length);
+}
diff --git a/src/web/WebApp.kt b/src/web/WebApp.kt
index e761e0b..073f2df 100644
--- a/src/web/WebApp.kt
+++ b/src/web/WebApp.kt
@@ -247,6 +247,13 @@ class WebApp(val listenPort: Int, val userlist: List>, val
path("api") {
//TODO https://stackoverflow.com/questions/70002015/streaming-into-audio-element
path("LiveAudio") {
+ post{
+ val json : JsonNode = objectmapper.readTree(it.body())
+ val broadcastzone = json.get("broadcastzone")?.asText("") ?: ""
+ val command = json.get("command")?.asText("") ?: ""
+ println("LiveAudio command=$command for zone $broadcastzone from ${it.host()}" )
+
+ }
ws("/ws/{uuid}"){ws ->
ws.onConnect {
ctx ->