commit 21/11/2025
This commit is contained in:
@@ -55,7 +55,6 @@ function UpdateStreamerCard(values) {
|
|||||||
const v = Number(value ?? 0);
|
const v = Number(value ?? 0);
|
||||||
const pct = Math.max(0, Math.min(100, Math.round((v / max) * 100)));
|
const pct = Math.max(0, Math.min(100, Math.round((v / max) * 100)));
|
||||||
//if (index!==1) return; // only update index 1 for testing
|
//if (index!==1) return; // only update index 1 for testing
|
||||||
//console.log(`setProgress: index=${index}, value=${v}, pct=${pct}`);
|
|
||||||
$bar
|
$bar
|
||||||
.attr('aria-valuenow', v) // semantic value
|
.attr('aria-valuenow', v) // semantic value
|
||||||
.css('width', pct + '%') // visual width
|
.css('width', pct + '%') // visual width
|
||||||
@@ -162,7 +161,6 @@ function fill_automaticqueuetablebody(vv) {
|
|||||||
if (!Array.isArray(vv) || vv.length === 0) return;
|
if (!Array.isArray(vv) || vv.length === 0) return;
|
||||||
vv.forEach(item => {
|
vv.forEach(item => {
|
||||||
// fill index and description columns using item properties
|
// fill index and description columns using item properties
|
||||||
//console.log("fill_automaticqueuetablebody: item", item);
|
|
||||||
$('#automaticqueuetable').append(`<tr>
|
$('#automaticqueuetable').append(`<tr>
|
||||||
<td>${item.index}</td>
|
<td>${item.index}</td>
|
||||||
<td>${item.date_Time}</td>
|
<td>${item.date_Time}</td>
|
||||||
@@ -218,7 +216,6 @@ function reloadAutomaticQueue(APIURL = "QueueTable/") {
|
|||||||
|
|
||||||
function RemovePagingQueueByIndex(index, APIURL = "QueuePaging/") {
|
function RemovePagingQueueByIndex(index, APIURL = "QueuePaging/") {
|
||||||
fetchAPI(APIURL + "DeleteByIndex/" + index, "DELETE", {}, null, (okdata) => {
|
fetchAPI(APIURL + "DeleteByIndex/" + index, "DELETE", {}, null, (okdata) => {
|
||||||
console.log("RemovePagingQueueByIndex: okdata", okdata);
|
|
||||||
reloadPagingQueue(APIURL);
|
reloadPagingQueue(APIURL);
|
||||||
}, (errdata) => {
|
}, (errdata) => {
|
||||||
console.log("RemovePagingQueueByIndex: errdata", errdata);
|
console.log("RemovePagingQueueByIndex: errdata", errdata);
|
||||||
@@ -227,7 +224,6 @@ function RemovePagingQueueByIndex(index, APIURL = "QueuePaging/") {
|
|||||||
|
|
||||||
function RemoveAutomaticQueueByIndex(index, APIURL = "QueueTable/") {
|
function RemoveAutomaticQueueByIndex(index, APIURL = "QueueTable/") {
|
||||||
fetchAPI(APIURL + "DeleteByIndex/" + index, "DELETE", {}, null, (okdata) => {
|
fetchAPI(APIURL + "DeleteByIndex/" + index, "DELETE", {}, null, (okdata) => {
|
||||||
console.log("RemoveAutomaticQueueByIndex: okdata", okdata);
|
|
||||||
reloadAutomaticQueue(APIURL);
|
reloadAutomaticQueue(APIURL);
|
||||||
}, (errdata) => {
|
}, (errdata) => {
|
||||||
console.log("RemoveAutomaticQueueByIndex: errdata", errdata);
|
console.log("RemoveAutomaticQueueByIndex: errdata", errdata);
|
||||||
@@ -258,16 +254,16 @@ function GetListeningZones() {
|
|||||||
* @param {Function} cbFail callback function on failure
|
* @param {Function} cbFail callback function on failure
|
||||||
*/
|
*/
|
||||||
function LiveAudioCommand(command, bz, cbOK = null, cbFail = null) {
|
function LiveAudioCommand(command, bz, cbOK = null, cbFail = null) {
|
||||||
function raise_cbOK(value=null){
|
function raise_cbOK(value = null) {
|
||||||
if (cbOK) cbOK(value);
|
if (cbOK) cbOK(value);
|
||||||
}
|
}
|
||||||
function raise_cbFail(value){
|
function raise_cbFail(value) {
|
||||||
if (cbFail) cbFail(value);
|
if (cbFail) cbFail(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command && command.length>0){
|
if (command && command.length > 0) {
|
||||||
if (bz && bz.length>0){
|
if (bz && bz.length > 0) {
|
||||||
if (command === 'Open' || command === 'Close'){
|
if (command === 'Open' || command === 'Close') {
|
||||||
let url = `/api/LiveAudio`;
|
let url = `/api/LiveAudio`;
|
||||||
let payload = {
|
let payload = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -281,22 +277,19 @@ function LiveAudioCommand(command, bz, cbOK = null, cbFail = null) {
|
|||||||
};
|
};
|
||||||
fetch(url, payload)
|
fetch(url, payload)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
console.log(`Fetch response for Live Audio Command ${command}:`, JSON.stringify(response));
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.log(`Live Audio Command ${command} for Broadcast Zone: ${bz} executed on server.`, data);
|
|
||||||
raise_cbOK(data);
|
raise_cbOK(data);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.log(`Error executing Live Audio Command ${command} for Broadcast Zone: ${bz} on server. ${error}`);
|
|
||||||
raise_cbFail(error);
|
raise_cbFail(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
} else raise_cbFail("LiveAudioCommand: Unknown command "+command);
|
} else raise_cbFail("LiveAudioCommand: Unknown command " + command);
|
||||||
} else raise_cbFail("LiveAudioCommand: Broadcast Zone is empty");
|
} else raise_cbFail("LiveAudioCommand: Broadcast Zone is empty");
|
||||||
} else raise_cbFail("LiveAudioCommand: command is empty");
|
} else raise_cbFail("LiveAudioCommand: command is empty");
|
||||||
}
|
}
|
||||||
@@ -308,7 +301,6 @@ let streamws = null;
|
|||||||
let mediasource = null;
|
let mediasource = null;
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
console.log("overview.js loaded");
|
|
||||||
|
|
||||||
|
|
||||||
GetListeningZones();
|
GetListeningZones();
|
||||||
@@ -316,8 +308,10 @@ $(document).ready(function () {
|
|||||||
let bz = $("#listenzone").val();
|
let bz = $("#listenzone").val();
|
||||||
let $icon = $(this).find('svg');
|
let $icon = $(this).find('svg');
|
||||||
if ($icon.hasClass('fa-stop')) {
|
if ($icon.hasClass('fa-stop')) {
|
||||||
console.log("Stopping Live Audio for Broadcast Zone:", bz);
|
LiveAudioCommand('Close', bz, (okdata) => {
|
||||||
LiveAudioCommand('Close', bz, (okdata) =>{
|
if (okdata.message && okdata.message.length > 0) {
|
||||||
|
console.log("Live Audio Session Closed:", okdata.message);
|
||||||
|
}
|
||||||
$icon.toggleClass('fa-stop fa-play');
|
$icon.toggleClass('fa-stop fa-play');
|
||||||
$("#listenzone").prop('disabled', false);
|
$("#listenzone").prop('disabled', false);
|
||||||
if (streamws) {
|
if (streamws) {
|
||||||
@@ -331,34 +325,37 @@ $(document).ready(function () {
|
|||||||
let audio = document.getElementById('listenaudio');
|
let audio = document.getElementById('listenaudio');
|
||||||
audio.src = "";
|
audio.src = "";
|
||||||
|
|
||||||
}, (errdata) =>{
|
}, (errdata) => {
|
||||||
alert("Error stopping Live Audio: " + errdata);
|
alert("Error stopping Live Audio: " + errdata);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("Starting Live Audio for Broadcast Zone:", bz);
|
LiveAudioCommand('Open', bz, (okdata) => {
|
||||||
LiveAudioCommand('Open', bz, (okdata) =>{
|
if (okdata.message && okdata.message.length > 0) {
|
||||||
|
let uuid = okdata.message;
|
||||||
|
console.log("Live Audio Session UUID:", uuid);
|
||||||
|
}
|
||||||
$icon.toggleClass('fa-stop fa-play');
|
$icon.toggleClass('fa-stop fa-play');
|
||||||
$("#listenzone").prop('disabled', true);
|
$("#listenzone").prop('disabled', true);
|
||||||
streamws = new WebSocket(`ws://${window.location.host}/LiveAudio/ws`);
|
streamws = new WebSocket(`ws://${window.location.host}/api/LiveAudio/ws`);
|
||||||
|
streamws.binaryType = 'arraybuffer';
|
||||||
mediasource = new MediaSource();
|
mediasource = new MediaSource();
|
||||||
let audio = document.getElementById('listenaudio');
|
let audio = document.getElementById('listenaudio');
|
||||||
audio.src = URL.createObjectURL(mediasource);
|
audio.src = URL.createObjectURL(mediasource);
|
||||||
mediasource.addEventListener('sourceopen', () => {
|
mediasource.addEventListener('sourceopen', () => {
|
||||||
const sourceBuffer = mediasource.addSourceBuffer('audio/mpeg; codecs="mp3"');
|
const sourceBuffer = mediasource.addSourceBuffer('audio/mpeg');
|
||||||
streamws.binaryType = 'arraybuffer';
|
|
||||||
streamws.onmessage = (event) => {
|
streamws.onmessage = (event) => {
|
||||||
if (event.data instanceof ArrayBuffer) {
|
if (event.data instanceof ArrayBuffer) {
|
||||||
const chunk = new Uint8Array(event.data);
|
const chunk = new Uint8Array(event.data);
|
||||||
sourceBuffer.appendBuffer(chunk);
|
sourceBuffer.appendBuffer(chunk);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
}, (errdata) =>{
|
}, (errdata) => {
|
||||||
alert("Error starting Live Audio: " + errdata);
|
alert("Error starting Live Audio: " + errdata);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
$('#clearpagingqueue').off('click').on('click', function () {
|
$('#clearpagingqueue').off('click').on('click', function () {
|
||||||
DoClear("QueuePaging/", "Paging Queue", (okdata) => {
|
DoClear("QueuePaging/", "Paging Queue", (okdata) => {
|
||||||
@@ -445,7 +442,6 @@ $(document).ready(function () {
|
|||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case "getPagingQueue":
|
case "getPagingQueue":
|
||||||
let pq = JSON.parse(data);
|
let pq = JSON.parse(data);
|
||||||
//console.log("getPagingQueue:", pq);
|
|
||||||
window.PagingQueue = [];
|
window.PagingQueue = [];
|
||||||
if (Array.isArray(pq) && pq.length > 0) {
|
if (Array.isArray(pq) && pq.length > 0) {
|
||||||
window.PagingQueue.push(...pq);
|
window.PagingQueue.push(...pq);
|
||||||
@@ -454,7 +450,6 @@ $(document).ready(function () {
|
|||||||
break;
|
break;
|
||||||
case "getAASQueue":
|
case "getAASQueue":
|
||||||
let aq = JSON.parse(data);
|
let aq = JSON.parse(data);
|
||||||
//console.log("getAASQueue:", aq);
|
|
||||||
window.QueueTable = [];
|
window.QueueTable = [];
|
||||||
if (Array.isArray(aq) && aq.length > 0) {
|
if (Array.isArray(aq) && aq.length > 0) {
|
||||||
window.QueueTable.push(...aq);
|
window.QueueTable.push(...aq);
|
||||||
|
|||||||
@@ -231,7 +231,8 @@ fun main() {
|
|||||||
db.Add_Log("AAS"," Application started")
|
db.Add_Log("AAS"," Application started")
|
||||||
|
|
||||||
// shutdown hook
|
// shutdown hook
|
||||||
Runtime.getRuntime().addShutdownHook(Thread {
|
Runtime.getRuntime().addShutdownHook(Thread ({
|
||||||
|
|
||||||
db.Add_Log("AAS"," Application stopping")
|
db.Add_Log("AAS"," Application stopping")
|
||||||
Logger.info { "Shutdown hook called, stopping services..." }
|
Logger.info { "Shutdown hook called, stopping services..." }
|
||||||
barixserver.StopTcpCommand()
|
barixserver.StopTcpCommand()
|
||||||
@@ -240,11 +241,12 @@ fun main() {
|
|||||||
web.Stop()
|
web.Stop()
|
||||||
udpreceiver.Stop()
|
udpreceiver.Stop()
|
||||||
tcpreceiver.Stop()
|
tcpreceiver.Stop()
|
||||||
|
StreamerOutputs.values.forEach { it.close() }
|
||||||
audioPlayer.Close()
|
audioPlayer.Close()
|
||||||
db.close()
|
db.close()
|
||||||
Logger.info { "All services stopped, exiting application." }
|
Logger.info { "All services stopped, exiting application." }
|
||||||
ProviderRegistry.getLoggingProvider().shutdown()
|
ProviderRegistry.getLoggingProvider().shutdown()
|
||||||
})
|
},"ShutdownHook") )
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,9 +48,10 @@ class Mp3Encoder(val samplingrate: Int=44100, val channels: Int=1) {
|
|||||||
push_handle = bass.BASS_StreamCreate(samplingrate, channels, Bass.BASS_STREAM_DECODE, Pointer(-1), null)
|
push_handle = bass.BASS_StreamCreate(samplingrate, channels, Bass.BASS_STREAM_DECODE, Pointer(-1), null)
|
||||||
if (push_handle!=0){
|
if (push_handle!=0){
|
||||||
Logger.info{"MP3 Encoder initialized with sampling rate $samplingrate Hz and $channels channel(s)" }
|
Logger.info{"MP3 Encoder initialized with sampling rate $samplingrate Hz and $channels channel(s)" }
|
||||||
//val options = "lame -b 128 --cbr --write-xing 0 --nohist --noid3v2 - -"
|
val options = "lame -b 128 --cbr --write-xing 0 --nohist --noid3v2 - -"
|
||||||
val flag = BassEnc.BASS_ENCODE_AUTOFREE or BassEnc.BASS_ENCODE_NOHEAD
|
//val flag = BassEnc.BASS_ENCODE_AUTOFREE or BassEnc.BASS_ENCODE_NOHEAD
|
||||||
mp3_handle = bassencmp3.BASS_Encode_MP3_Start(push_handle, null, flag,proc, null)
|
val flag = BassEnc.BASS_ENCODE_AUTOFREE
|
||||||
|
mp3_handle = bassencmp3.BASS_Encode_MP3_Start(push_handle, options, flag,proc, null)
|
||||||
|
|
||||||
if (mp3_handle!=0){
|
if (mp3_handle!=0){
|
||||||
callback = cb
|
callback = cb
|
||||||
@@ -100,7 +101,6 @@ class Mp3Encoder(val samplingrate: Int=44100, val channels: Int=1) {
|
|||||||
Logger.error{"Failed to push data to MP3 Encoder. BASS error code: $err" }
|
Logger.error{"Failed to push data to MP3 Encoder. BASS error code: $err" }
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
//println("MP3 Encoder: Pushed $written bytes of PCM data.")
|
|
||||||
return written
|
return written
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +122,5 @@ class Mp3Encoder(val samplingrate: Int=44100, val channels: Int=1) {
|
|||||||
// auto close by BASS_ENCODE_AUTOFREE
|
// auto close by BASS_ENCODE_AUTOFREE
|
||||||
mp3_handle = 0
|
mp3_handle = 0
|
||||||
callback = null
|
callback = null
|
||||||
println("MP3 Encoder: Stopped.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ import java.nio.ByteBuffer
|
|||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class BarixConnection(val index: UInt, var channel: String, val ipaddress: String, val port: Int = 5002) {
|
class BarixConnection(val index: UInt, var channel: String, val ipaddress: String, val port: Int = 5002) : AutoCloseable {
|
||||||
private var _bR: Int = 0
|
private var _bR: Int = 0
|
||||||
private var _sd: Int = 0
|
private var _sd: Int = 0
|
||||||
private var _vu: Int = 0
|
private var _vu: Int = 0
|
||||||
@@ -26,6 +26,13 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
|
|||||||
private var _tcp: Socket? = null
|
private var _tcp: Socket? = null
|
||||||
private val mp3encoder = Mp3Encoder()
|
private val mp3encoder = Mp3Encoder()
|
||||||
private val mp3Consumer = mutableMapOf<String, Consumer<ByteArray>>()
|
private val mp3Consumer = mutableMapOf<String, Consumer<ByteArray>>()
|
||||||
|
private val udp = DatagramSocket()
|
||||||
|
|
||||||
|
init {
|
||||||
|
mp3encoder.Start { data ->
|
||||||
|
mp3Consumer.values.forEach { it.accept(data) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Add_Mp3_Consumer(key: String, cb: Consumer<ByteArray>) {
|
fun Add_Mp3_Consumer(key: String, cb: Consumer<ByteArray>) {
|
||||||
mp3Consumer[key] = cb
|
mp3Consumer[key] = cb
|
||||||
@@ -118,33 +125,26 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
|
|||||||
fun SendData(data: ByteArray, cbOK: Consumer<String>, cbFail: Consumer<String>) {
|
fun SendData(data: ByteArray, cbOK: Consumer<String>, cbFail: Consumer<String>) {
|
||||||
if (data.isNotEmpty()) {
|
if (data.isNotEmpty()) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
DatagramSocket().use{ udp ->
|
val bb = ByteBuffer.wrap(data)
|
||||||
val bb = ByteBuffer.wrap(data)
|
|
||||||
if (!mp3encoder.isStarted()) {
|
while(bb.hasRemaining()){
|
||||||
mp3encoder.Start { data ->
|
try {
|
||||||
mp3Consumer.values.forEach { it.accept(data) }
|
val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining())
|
||||||
|
bb.get(chunk)
|
||||||
|
while(bufferRemain<chunk.size){
|
||||||
|
delay(10)
|
||||||
}
|
}
|
||||||
}
|
udp.send(DatagramPacket(chunk, chunk.size, inet))
|
||||||
while(bb.hasRemaining()){
|
mp3encoder.PushData(chunk)
|
||||||
try {
|
delay(2)
|
||||||
val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining())
|
|
||||||
bb.get(chunk)
|
|
||||||
while(bufferRemain<chunk.size){
|
|
||||||
delay(10)
|
|
||||||
}
|
|
||||||
udp.send(DatagramPacket(chunk, chunk.size, inet))
|
|
||||||
mp3encoder.PushData(chunk)
|
|
||||||
delay(2)
|
|
||||||
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
cbFail.accept("SendData to $ipaddress failed, message: ${e.message}")
|
cbFail.accept("SendData to $ipaddress failed, message: ${e.message}")
|
||||||
return@launch
|
return@launch
|
||||||
}
|
|
||||||
}
|
}
|
||||||
mp3encoder.Stop()
|
|
||||||
cbOK.accept("SendData to $channel ($ipaddress) succeeded, ${data.size} bytes sent")
|
|
||||||
}
|
}
|
||||||
|
cbOK.accept("SendData to $channel ($ipaddress) succeeded, ${data.size} bytes sent")
|
||||||
}
|
}
|
||||||
|
|
||||||
} else cbFail.accept("SendData to $ipaddress failed, data is empty")
|
} else cbFail.accept("SendData to $ipaddress failed, data is empty")
|
||||||
@@ -248,4 +248,18 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
try{
|
||||||
|
udp.close()
|
||||||
|
} catch (_ : Exception){
|
||||||
|
Logger.error { "Failed to close udp" }
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
_tcp?.close()
|
||||||
|
} catch (_ : Exception){
|
||||||
|
Logger.error { "Failed to close tcp socket to $ipaddress" }
|
||||||
|
}
|
||||||
|
mp3encoder.Stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -94,11 +94,11 @@ class TCP_Android_Command_Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
logcb.accept("Failed accepting TCP Socket, Message : ${ex.message}")
|
logcb.accept("Android TCP Server Failed accepting TCP Socket, Message : ${ex.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
logcb.accept("TCP server stopped")
|
logcb.accept("Android TCP Command server stopped")
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
6
src/web/LiveListenData.kt
Normal file
6
src/web/LiveListenData.kt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import barix.BarixConnection
|
||||||
|
import io.javalin.websocket.WsContext
|
||||||
|
|
||||||
|
data class LiveListenData(val key: String, var bc : BarixConnection?, var ws : WsContext?)
|
||||||
@@ -43,9 +43,10 @@ import java.nio.file.Files
|
|||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import codes.configKeys
|
import codes.configKeys
|
||||||
import database.QueueTable
|
import database.QueueTable
|
||||||
import io.javalin.websocket.WsContext
|
import io.javalin.websocket.WsCloseStatus
|
||||||
import org.tinylog.Logger
|
import org.tinylog.Logger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
lateinit var app: Javalin
|
lateinit var app: Javalin
|
||||||
lateinit var semiauto: Javalin
|
lateinit var semiauto: Javalin
|
||||||
val objectmapper = jacksonObjectMapper()
|
val objectmapper = jacksonObjectMapper()
|
||||||
val WsContextMap = mutableMapOf<String, WsContext>()
|
val WsContextMap = mutableMapOf<String, LiveListenData>()
|
||||||
|
|
||||||
private fun SendReply(context: WsMessageContext, command: String, value: String) {
|
private fun SendReply(context: WsMessageContext, command: String, value: String) {
|
||||||
try {
|
try {
|
||||||
@@ -247,79 +248,100 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
path("api") {
|
path("api") {
|
||||||
//TODO https://stackoverflow.com/questions/70002015/streaming-into-audio-element
|
//TODO https://stackoverflow.com/questions/70002015/streaming-into-audio-element
|
||||||
path("LiveAudio") {
|
path("LiveAudio") {
|
||||||
post{
|
post{ ctx->
|
||||||
val json : JsonNode = objectmapper.readTree(it.body())
|
val json : JsonNode = objectmapper.readTree(ctx.body())
|
||||||
val broadcastzone = json.get("broadcastzone")?.asText("") ?: ""
|
val broadcastzone = json.get("broadcastzone")?.asText("") ?: ""
|
||||||
val command = json.get("command")?.asText("") ?: ""
|
val command = json.get("command")?.asText("") ?: ""
|
||||||
println("LiveAudio command=$command for zone $broadcastzone from ${it.host()}" )
|
if (command == "Open" || command == "Close"){
|
||||||
|
if (broadcastzone.isNotEmpty()){
|
||||||
|
val bc = Get_Barix_Connection_by_ZoneName(broadcastzone)
|
||||||
|
if (bc!=null){
|
||||||
|
val key = ctx.cookie("client-stream-id")
|
||||||
|
if (command == "Open"){
|
||||||
|
// open command
|
||||||
|
if (key!=null && key.isNotEmpty()){
|
||||||
|
// ada connection sebelumnya, kemungkinan reconnect
|
||||||
|
val prev = WsContextMap[key]
|
||||||
|
if (prev!=null){
|
||||||
|
prev.bc?.Remove_Mp3_Consumer(key)
|
||||||
|
prev.ws?.closeSession(WsCloseStatus.NORMAL_CLOSURE, "Reopen Live Audio Stream")
|
||||||
|
}
|
||||||
|
WsContextMap.remove(key)
|
||||||
|
ctx.cookie("client-stream-id", "")
|
||||||
|
}
|
||||||
|
val newkey = UUID.randomUUID().toString()
|
||||||
|
.also { ctx.cookie("client-stream-id", it) }
|
||||||
|
WsContextMap[newkey] = LiveListenData(newkey, bc, null)
|
||||||
|
ResultMessageString(ctx, 200, newkey)
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// close command
|
||||||
|
if (key!=null && key.isNotEmpty()){
|
||||||
|
// close connection
|
||||||
|
val prev = WsContextMap[key]
|
||||||
|
if (prev!=null){
|
||||||
|
prev.bc?.Remove_Mp3_Consumer(key)
|
||||||
|
prev.ws?.closeSession(WsCloseStatus.NORMAL_CLOSURE, "Close Live Audio Stream")
|
||||||
|
}
|
||||||
|
WsContextMap.remove(key)
|
||||||
|
ctx.cookie("client-stream-id", "")
|
||||||
|
ResultMessageString(ctx, 200, "OK")
|
||||||
|
} else ResultMessageString(ctx, 400, "No client-id cookie found")
|
||||||
|
}
|
||||||
|
} else ResultMessageString(ctx, 400, "Broadcastzone not found")
|
||||||
|
} else ResultMessageString(ctx, 400, "Invalid broadcastzone")
|
||||||
|
} else ResultMessageString(ctx, 400, "Invalid command")
|
||||||
|
|
||||||
}
|
}
|
||||||
ws("/ws/{uuid}"){ws ->
|
ws("ws"){ wscontext ->
|
||||||
ws.onConnect {
|
wscontext.onConnect {
|
||||||
ctx ->
|
val key = it.cookie("client-stream-id")
|
||||||
val uuid = ctx.pathParam("uuid")
|
if (key!=null && key.isNotEmpty()){
|
||||||
val cookieresult = ctx.cookie("client-stream-id")
|
val lld = WsContextMap[key]
|
||||||
println("Ws connected with uuid=$uuid, cookie=$cookieresult")
|
if (lld!=null){
|
||||||
WsContextMap[uuid] = ctx
|
it.enableAutomaticPings()
|
||||||
}
|
lld.ws = it
|
||||||
|
lld.bc?.Add_Mp3_Consumer(key){ mp3data->
|
||||||
ws.onClose {
|
try {
|
||||||
ctx ->
|
if (it.session.isOpen){
|
||||||
val uuid = ctx.pathParam("uuid")
|
it.send(ByteBuffer.wrap(mp3data))
|
||||||
println("Ws close on uuid=$uuid")
|
}
|
||||||
WsContextMap.remove(uuid)
|
} catch (e: Exception){
|
||||||
}
|
Logger.error {"Error sending LiveAudio mp3 data for key $key, Message: ${e.message}"}
|
||||||
ws.onError {
|
}
|
||||||
ctx ->
|
}
|
||||||
val uuid = ctx.pathParam("uuid")
|
} else {
|
||||||
println("Ws error on uuid=$uuid")
|
it.closeSession(WsCloseStatus.POLICY_VIOLATION, "WsContextMap key not found")
|
||||||
WsContextMap.remove(uuid)
|
Logger.info{"LiveAudio WebSocket connection rejected, WsContextMap key not found"}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get("Open/{broadcastzone}") { ctx ->
|
|
||||||
val param = ctx.pathParam("broadcastzone")
|
|
||||||
println("LiveAudio Open for zone $param from ${ctx.host()}" )
|
|
||||||
if (param.isNotEmpty()) {
|
|
||||||
val bc = Get_Barix_Connection_by_ZoneName(param)
|
|
||||||
if (bc != null) {
|
|
||||||
|
|
||||||
val key = ctx.cookie("client-stream-id") ?: UUID.randomUUID().toString()
|
|
||||||
.also { ctx.cookie("client-stream-id", it) }
|
|
||||||
|
|
||||||
bc.Add_Mp3_Consumer(key){ mp3data ->
|
|
||||||
WsContextMap[key]?.send(mp3data)
|
|
||||||
println("Send ${mp3data.size} to $key")
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
ResultMessageString(ctx, 200, key)
|
it.closeSession(WsCloseStatus.POLICY_VIOLATION, "Invalid client-stream-id")
|
||||||
} else ctx.status(400)
|
Logger.info{"LiveAudio WebSocket connection rejected, invalid client-stream-id"}
|
||||||
.result(objectmapper.writeValueAsString(resultMessage("Broadcastzone not found")))
|
}
|
||||||
} else ctx.status(400)
|
|
||||||
.result(objectmapper.writeValueAsString(resultMessage("Invalid broadcastzone")))
|
|
||||||
|
|
||||||
}
|
|
||||||
get("Close/{broadcastzone}") { ctx ->
|
|
||||||
val param = ctx.pathParam("broadcastzone")
|
|
||||||
println("LiveAudio Close for zone $param")
|
|
||||||
val key = ctx.cookie("client-stream-id")
|
|
||||||
if (key != null && key.isNotEmpty()) {
|
|
||||||
if (param.isNotEmpty()) {
|
|
||||||
val bc = Get_Barix_Connection_by_ZoneName(param)
|
|
||||||
if (bc != null) {
|
|
||||||
bc.Remove_Mp3_Consumer(key)
|
|
||||||
WsContextMap.remove(key)
|
|
||||||
ResultMessageString(ctx, 200, "OK")
|
|
||||||
println("LiveAudio Close for zone $param SUCCESS")
|
|
||||||
} else ctx.status(400)
|
|
||||||
.result(objectmapper.writeValueAsString(resultMessage("Broadcastzone not found")))
|
|
||||||
} else ctx.status(400)
|
|
||||||
.result(objectmapper.writeValueAsString(resultMessage("Invalid broadcastzone")))
|
|
||||||
} else {
|
|
||||||
ctx.status(400)
|
|
||||||
.result(objectmapper.writeValueAsString(resultMessage("No client-id cookie found")))
|
|
||||||
}
|
}
|
||||||
|
wscontext.onClose {
|
||||||
|
val key = it.cookie("client-stream-id")
|
||||||
|
if (key!=null && key.isNotEmpty()){
|
||||||
|
val lld = WsContextMap[key]
|
||||||
|
lld?.bc?.Remove_Mp3_Consumer(key)
|
||||||
|
lld?.ws?.closeSession()
|
||||||
|
lld?.bc = null
|
||||||
|
lld?.ws= null
|
||||||
|
WsContextMap.remove(key)
|
||||||
|
Logger.info{"LiveAudio WebSocket closed for key $key"}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
wscontext.onError {
|
||||||
|
val key = it.cookie("client-stream-id")
|
||||||
|
val msg = it.error()?.message ?: ""
|
||||||
|
Logger.info{"LiveAudio WebSocket error for key $key, Message: $msg"}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
path("VoiceType") {
|
path("VoiceType") {
|
||||||
get {
|
get {
|
||||||
@@ -1876,7 +1898,6 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
val language = it.pathParam("language")
|
val language = it.pathParam("language")
|
||||||
val voice = it.pathParam("voice")
|
val voice = it.pathParam("voice")
|
||||||
val category = it.pathParam("category")
|
val category = it.pathParam("category")
|
||||||
//println("ListSoundbank called with language=$language, voice=$voice, category=$category")
|
|
||||||
if (ValidString(language) && Language.entries.any { lang -> lang.name == language }) {
|
if (ValidString(language) && Language.entries.any { lang -> lang.name == language }) {
|
||||||
if (ValidString(voice) && VoiceType.entries.any { vtype -> vtype.name == voice }) {
|
if (ValidString(voice) && VoiceType.entries.any { vtype -> vtype.name == voice }) {
|
||||||
if (ValidString(category) && Category.entries.any { cat -> cat.name == category }) {
|
if (ValidString(category) && Category.entries.any { cat -> cat.name == category }) {
|
||||||
@@ -1889,7 +1910,6 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
// just the filename, without path
|
// just the filename, without path
|
||||||
mm.substring(mm.lastIndexOf(File.separator) + 1)
|
mm.substring(mm.lastIndexOf(File.separator) + 1)
|
||||||
}
|
}
|
||||||
//println("ListSoundbank result: $result")
|
|
||||||
it.result(objectmapper.writeValueAsString(result))
|
it.result(objectmapper.writeValueAsString(result))
|
||||||
} else {
|
} else {
|
||||||
it.status(400)
|
it.status(400)
|
||||||
@@ -1911,7 +1931,6 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
val category = it.pathParam("category")
|
val category = it.pathParam("category")
|
||||||
|
|
||||||
val uploaded = it.uploadedFiles()
|
val uploaded = it.uploadedFiles()
|
||||||
println("UploadSoundbank called with language=$language, voice=$voice, category=$category, uploaded files count=${uploaded.size}")
|
|
||||||
if (ValidString(language) && Language.entries.any { lang -> lang.name == language }) {
|
if (ValidString(language) && Language.entries.any { lang -> lang.name == language }) {
|
||||||
if (ValidString(voice) && VoiceType.entries.any { vtype -> vtype.name == voice }) {
|
if (ValidString(voice) && VoiceType.entries.any { vtype -> vtype.name == voice }) {
|
||||||
if (ValidString(category) && Category.entries.any { cat -> cat.name == category }) {
|
if (ValidString(category) && Category.entries.any { cat -> cat.name == category }) {
|
||||||
@@ -2218,7 +2237,6 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
if (db.queuetableDB.Add(qt)) {
|
if (db.queuetableDB.Add(qt)) {
|
||||||
db.queuetableDB.Resort()
|
db.queuetableDB.Resort()
|
||||||
Logger.info { "SemiAutoWeb added to queue table: $qt" }
|
Logger.info { "SemiAutoWeb added to queue table: $qt" }
|
||||||
//println("SemiAuto added to queue table: ${objectmapper.writeValueAsString(qt)}")
|
|
||||||
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
|
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
|
||||||
} else ResultMessageString(ctx, 500, "Failed to add to queue table")
|
} else ResultMessageString(ctx, 500, "Failed to add to queue table")
|
||||||
} else ResultMessageString(ctx, 400, "Broadcast zones cannot be empty")
|
} else ResultMessageString(ctx, 400, "Broadcast zones cannot be empty")
|
||||||
@@ -2232,10 +2250,8 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
get("/{datelog}") { ctx ->
|
get("/{datelog}") { ctx ->
|
||||||
val datelog = ctx.pathParam("datelog")
|
val datelog = ctx.pathParam("datelog")
|
||||||
if (ValidDate(datelog)) {
|
if (ValidDate(datelog)) {
|
||||||
println("SemiAuto Get Log for date $datelog")
|
|
||||||
db.GetLogForHtml(datelog) { loghtml ->
|
db.GetLogForHtml(datelog) { loghtml ->
|
||||||
val resultstring = objectmapper.writeValueAsString(loghtml)
|
val resultstring = objectmapper.writeValueAsString(loghtml)
|
||||||
println("Log HTML for date $datelog: $resultstring")
|
|
||||||
ctx.result(resultstring)
|
ctx.result(resultstring)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user