commit 06/11/2025
This commit is contained in:
@@ -401,7 +401,7 @@ class MainExtension01 {
|
||||
|
||||
"[ETAD]" -> {
|
||||
val values = variables["ETAD"].orEmpty().split(":").map { it.trim() }.filter { IsNumber(it) }
|
||||
println("ETAD values: $values")
|
||||
//println("ETAD values: $values")
|
||||
if (values.size == 2) {
|
||||
if (IsNumber(values[0]) && IsNumber(values[1])) {
|
||||
val _h = values[0].toInt()
|
||||
|
||||
@@ -16,9 +16,12 @@ import contentCache
|
||||
import org.tinylog.Logger
|
||||
|
||||
@Suppress("unused")
|
||||
class AudioPlayer (var samplingrate: Int) {
|
||||
class AudioPlayer (var samplingrate: Int = 44100) {
|
||||
val bass: Bass = Bass.Instance
|
||||
val bassenc : BassEnc = BassEnc.Instance
|
||||
val bassencmp3: BassEncMP3 = BassEncMP3.Instance
|
||||
val bassencopus: BassEncOpus = BassEncOpus.Instance
|
||||
val bassencogg : BassEncOGG = BassEncOGG.Instance
|
||||
var initedDevice = -1
|
||||
|
||||
|
||||
@@ -28,6 +31,9 @@ class AudioPlayer (var samplingrate: Int) {
|
||||
if (samplingrate<1) samplingrate = 44100 // Default sampling rate
|
||||
Logger.info {"Bass version ${Integer.toHexString(bass.BASS_GetVersion())}"}
|
||||
Logger.info { "BassEnc version ${Integer.toHexString(bassenc.BASS_Encode_GetVersion())}" }
|
||||
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())}"}
|
||||
InitAudio(0) // Audio 0 is No Sound, use for reading and writing wav silently
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ public interface BassEnc extends Library {
|
||||
* @param length number of bytes
|
||||
* @param user the user pointer passed to BASS_Encode_Start
|
||||
*/
|
||||
void ENCODEPROC(int encoderhandle, int channelhandle, Memory encodedData, int length, Pointer user);
|
||||
void ENCODEPROC(int encoderhandle, int channelhandle, Pointer encodedData, int length, Pointer user);
|
||||
}
|
||||
|
||||
interface ENCODEPROCEX extends Callback {
|
||||
@@ -101,7 +101,7 @@ public interface BassEnc extends Library {
|
||||
* @param offset file offset of the data
|
||||
* @param user the user pointer passed to BASS_Encode_Start
|
||||
*/
|
||||
void ENCODEPROCEX(int handle, int channel, Memory buffer, int length, long offset, Object user);
|
||||
void ENCODEPROCEX(int handle, int channel, Pointer buffer, int length, long offset, Pointer user);
|
||||
}
|
||||
|
||||
interface ENCODERPROC extends Callback {
|
||||
@@ -115,7 +115,7 @@ public interface BassEnc extends Library {
|
||||
* @param user the user pointer passed to BASS_Encode_Start
|
||||
* @return the amount of encoded data (-1 = stop)
|
||||
*/
|
||||
int ENCODERPROC(int encoderHandle, int channelHandle, Memory encodedData, int length, int maxOut, Pointer user);
|
||||
int ENCODERPROC(int encoderHandle, int channelHandle, Pointer encodedData, int length, int maxOut, Pointer user);
|
||||
}
|
||||
|
||||
|
||||
|
||||
13
src/audio/BassEncMP3.java
Normal file
13
src/audio/BassEncMP3.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package audio;
|
||||
|
||||
import com.sun.jna.Library;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public interface BassEncMP3 extends Library {
|
||||
|
||||
BassEncMP3 Instance = (BassEncMP3) com.sun.jna.Native.load("bassenc_mp3", BassEncMP3.class);
|
||||
int BASS_Encode_MP3_GetVersion();
|
||||
int BASS_Encode_MP3_Start(int handle, String options, int flags, BassEnc.ENCODEPROCEX proc, Object user);
|
||||
int BASS_Encode_MP3_StartFile(int handle, String options, int flags, String filename);
|
||||
|
||||
}
|
||||
15
src/audio/BassEncOGG.java
Normal file
15
src/audio/BassEncOGG.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package audio;
|
||||
|
||||
import com.sun.jna.Library;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public interface BassEncOGG extends Library {
|
||||
|
||||
BassEncOGG Instance = (BassEncOGG) com.sun.jna.Native.load("bassenc_ogg", BassEncOGG.class);
|
||||
int BASS_ENCODE_OGG_RESET = 0x1000000;
|
||||
int BASS_Encode_OGG_GetVersion();
|
||||
int BASS_Encode_OGG_Start(int handle, String options, int flags, BassEnc.ENCODEPROC proc, Object user);
|
||||
int BASS_Encode_OGG_StartFile(int handle, String options, int flags, String filename);
|
||||
boolean BASS_Encode_OGG_NewStream(int handle, String options, int flags);
|
||||
|
||||
}
|
||||
18
src/audio/BassEncOpus.java
Normal file
18
src/audio/BassEncOpus.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package audio;
|
||||
|
||||
import com.sun.jna.Library;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public interface BassEncOpus extends Library {
|
||||
BassEncOpus Instance = (BassEncOpus) com.sun.jna.Native.load("bassenc_opus", BassEncOpus.class);
|
||||
int BASS_ENCODE_OPUS_RESET = 0x1000000;
|
||||
int BASS_ENCODE_OPUS_CTLONLY = 0x2000000;
|
||||
|
||||
|
||||
int BASS_Encode_OPUS_GetVersion();
|
||||
int BASS_Encode_OPUS_Start(int handle, String options, int flags, BassEnc.ENCODEPROC proc, Object user);
|
||||
int BASS_Encode_OPUS_StartFile(int handle, String options, int flags, String filename);
|
||||
boolean BASS_Encode_OPUS_NewStream(int handle, String options, int flags);
|
||||
|
||||
|
||||
}
|
||||
126
src/audio/Mp3Encoder.kt
Normal file
126
src/audio/Mp3Encoder.kt
Normal file
@@ -0,0 +1,126 @@
|
||||
package audio
|
||||
|
||||
import audioPlayer
|
||||
import com.sun.jna.Memory
|
||||
import com.sun.jna.Pointer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
import org.tinylog.Logger
|
||||
import java.util.function.Consumer
|
||||
|
||||
@Suppress("unused")
|
||||
class Mp3Encoder(val samplingrate: Int=44100, val channels: Int=1) {
|
||||
var push_handle: Int = 0
|
||||
var mp3_handle: Int = 0
|
||||
private val bass = audioPlayer.bass
|
||||
private val bassencmp3 = audioPlayer.bassencmp3
|
||||
var callback : Consumer<ByteArray>? = null
|
||||
|
||||
/**
|
||||
* Check if the encoder is started
|
||||
* @return true if started, false otherwise
|
||||
*/
|
||||
fun isStarted() : Boolean {
|
||||
return push_handle!=0 && mp3_handle!=0
|
||||
}
|
||||
|
||||
val proc = BassEnc.ENCODEPROCEX{
|
||||
enchandle, sourcehandle, buffer, length, offset, user ->
|
||||
if (enchandle == mp3_handle) {
|
||||
if (sourcehandle == push_handle) {
|
||||
val data = ByteArray(length)
|
||||
buffer.read(0, data, 0, length)
|
||||
callback?.accept(data)
|
||||
} else Logger.error { "MP3 Encoder callback called with unknown source handle: $sourcehandle" }
|
||||
} else Logger.error { "MP3 Encoder callback called with unknown encoder handle: $enchandle" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the MP3 encoder
|
||||
* @param cb Function to receive encoded MP3 data
|
||||
*/
|
||||
fun Start(cb: Consumer<ByteArray>){
|
||||
callback = null
|
||||
push_handle = bass.BASS_StreamCreate(samplingrate, channels, Bass.BASS_STREAM_DECODE, Pointer(-1), null)
|
||||
if (push_handle!=0){
|
||||
Logger.info{"MP3 Encoder initialized with sampling rate $samplingrate Hz and $channels channel(s)" }
|
||||
|
||||
mp3_handle = bassencmp3.BASS_Encode_MP3_Start(push_handle, null, BassEnc.BASS_ENCODE_AUTOFREE,proc, null)
|
||||
|
||||
if (mp3_handle!=0){
|
||||
callback = cb
|
||||
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val readsize = 4*1024 // 4 K buffer
|
||||
Logger.info{"MP3 Encoder started successfully." }
|
||||
while(isActive && push_handle!=0){
|
||||
val p = Memory(readsize.toLong())
|
||||
val read = bass.BASS_ChannelGetData(push_handle, p, readsize)
|
||||
|
||||
if (read==-1){
|
||||
val err = bass.BASS_ErrorGetCode()
|
||||
Logger.error{"Failed to read data from MP3 Encoder stream. BASS error code: $err" }
|
||||
break
|
||||
}
|
||||
delay(2)
|
||||
}
|
||||
Logger.info{"MP3 Encoder finished successfully." }
|
||||
}
|
||||
|
||||
} else {
|
||||
val err = bass.BASS_ErrorGetCode()
|
||||
Logger.error{"Failed to start MP3 Encoder. BASS error code: $err"}
|
||||
}
|
||||
} else {
|
||||
val err = bass.BASS_ErrorGetCode()
|
||||
Logger.error{"Failed to initialize MP3 Encoder. BASS error code: $err" }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push PCM data to be encoded
|
||||
* @param data PCM data in ByteArray
|
||||
* @return Number of bytes written, or 0 if failed
|
||||
*/
|
||||
fun PushData(data: ByteArray): Int {
|
||||
if (push_handle==0){
|
||||
Logger.error{"MP3 Encoder is not started." }
|
||||
return 0
|
||||
}
|
||||
val mem = Memory(data.size.toLong())
|
||||
mem.write(0, data, 0, data.size)
|
||||
val written = bass.BASS_StreamPutData(push_handle, mem, data.size)
|
||||
if (written==-1){
|
||||
val err = bass.BASS_ErrorGetCode()
|
||||
Logger.error{"Failed to push data to MP3 Encoder. BASS error code: $err" }
|
||||
return 0
|
||||
}
|
||||
//println("MP3 Encoder: Pushed $written bytes of PCM data.")
|
||||
return written
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the MP3 encoder and free resources
|
||||
*/
|
||||
fun Stop(){
|
||||
|
||||
if (push_handle!=0){
|
||||
val res = bass.BASS_StreamFree(push_handle)
|
||||
if (res){
|
||||
Logger.info{"MP3 Encoder stream freed successfully." }
|
||||
} else {
|
||||
val err = bass.BASS_ErrorGetCode()
|
||||
Logger.error{"Failed to free MP3 Encoder stream. BASS error code: $err"}
|
||||
}
|
||||
push_handle = 0
|
||||
}
|
||||
// auto close by BASS_ENCODE_AUTOFREE
|
||||
mp3_handle = 0
|
||||
callback = null
|
||||
println("MP3 Encoder: Stopped.")
|
||||
}
|
||||
}
|
||||
127
src/audio/OpusEncoder.kt
Normal file
127
src/audio/OpusEncoder.kt
Normal file
@@ -0,0 +1,127 @@
|
||||
package audio
|
||||
|
||||
import audioPlayer
|
||||
import com.sun.jna.Memory
|
||||
import com.sun.jna.Pointer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
import org.tinylog.Logger
|
||||
import java.util.function.Consumer
|
||||
|
||||
@Suppress("unused")
|
||||
class OpusEncoder(val samplingrate: Int=44100, val channels: Int=1) {
|
||||
var push_handle: Int = 0
|
||||
var opus_handle: Int = 0
|
||||
private val bass = audioPlayer.bass
|
||||
private val bassencopus = audioPlayer.bassencopus
|
||||
var callback : Consumer<ByteArray>? = null
|
||||
|
||||
/**
|
||||
* Check if the encoder is started
|
||||
* @return true if started, false otherwise
|
||||
*/
|
||||
fun isStarted() : Boolean {
|
||||
return push_handle!=0 && opus_handle!=0
|
||||
}
|
||||
|
||||
val proc = BassEnc.ENCODEPROC{
|
||||
enchandle, sourcehandle, buffer, length, user ->
|
||||
if (enchandle == opus_handle) {
|
||||
if (sourcehandle == push_handle) {
|
||||
val data = ByteArray(length)
|
||||
buffer.read(0, data, 0, length)
|
||||
callback?.accept(data)
|
||||
//println("MP3 Encoder callback: Sent $length bytes")
|
||||
} else Logger.error { "Opus Encoder callback called with unknown source handle: $sourcehandle" }
|
||||
} else Logger.error { "Opus Encoder callback called with unknown encoder handle: $enchandle" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the Opus encoder
|
||||
* @param cb Function to receive encoded MP3 data
|
||||
*/
|
||||
fun Start(cb: Consumer<ByteArray>){
|
||||
callback = null
|
||||
push_handle = bass.BASS_StreamCreate(samplingrate, channels, Bass.BASS_STREAM_DECODE, Pointer(-1), null)
|
||||
if (push_handle!=0){
|
||||
Logger.info{"Opus Encoder initialized with sampling rate $samplingrate Hz and $channels channel(s)" }
|
||||
|
||||
opus_handle = bassencopus.BASS_Encode_OPUS_Start(push_handle, null, BassEnc.BASS_ENCODE_AUTOFREE, proc, null)
|
||||
|
||||
if (opus_handle!=0){
|
||||
callback = cb
|
||||
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val readsize = 8 * 1024 * 1024 // 8 MB buffer
|
||||
Logger.info{"Opus Encoder started successfully." }
|
||||
while(isActive && push_handle!=0){
|
||||
delay(2)
|
||||
val p = Memory(readsize.toLong())
|
||||
val read = bass.BASS_ChannelGetData(push_handle, p, readsize)
|
||||
|
||||
if (read==-1){
|
||||
val err = bass.BASS_ErrorGetCode()
|
||||
Logger.error{"Failed to read data from Opus Encoder stream. BASS error code: $err" }
|
||||
break
|
||||
}
|
||||
}
|
||||
Logger.info{"Opus Encoder finished successfully." }
|
||||
}
|
||||
|
||||
} else {
|
||||
val err = bass.BASS_ErrorGetCode()
|
||||
Logger.error{"Failed to start Opus Encoder. BASS error code: $err"}
|
||||
}
|
||||
} else {
|
||||
val err = bass.BASS_ErrorGetCode()
|
||||
Logger.error{"Failed to initialize Opus Encoder. BASS error code: $err" }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push PCM data to be encoded
|
||||
* @param data PCM data in ByteArray
|
||||
* @return Number of bytes written, or 0 if failed
|
||||
*/
|
||||
fun PushData(data: ByteArray): Int {
|
||||
if (push_handle==0){
|
||||
Logger.error{"Opus Encoder is not started." }
|
||||
return 0
|
||||
}
|
||||
val mem = Memory(data.size.toLong())
|
||||
mem.write(0, data, 0, data.size)
|
||||
val written = bass.BASS_StreamPutData(push_handle, mem, data.size)
|
||||
if (written==-1){
|
||||
val err = bass.BASS_ErrorGetCode()
|
||||
Logger.error{"Failed to push data to Opus Encoder. BASS error code: $err" }
|
||||
return 0
|
||||
}
|
||||
//println("Opus Encoder: Pushed $written bytes of PCM data.")
|
||||
return written
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the Opus encoder and free resources
|
||||
*/
|
||||
fun Stop(){
|
||||
|
||||
if (push_handle!=0){
|
||||
val res = bass.BASS_StreamFree(push_handle)
|
||||
if (res){
|
||||
Logger.info{"Opus Encoder stream freed successfully." }
|
||||
} else {
|
||||
val err = bass.BASS_ErrorGetCode()
|
||||
Logger.error{"Failed to free Opus Encoder stream. BASS error code: $err"}
|
||||
}
|
||||
push_handle = 0
|
||||
}
|
||||
// auto close by BASS_ENCODE_AUTOFREE
|
||||
opus_handle = 0
|
||||
callback = null
|
||||
println("Opus Encoder: Stopped.")
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package barix
|
||||
|
||||
import audio.Mp3Encoder
|
||||
import audio.OpusEncoder
|
||||
import codes.Somecodes
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -24,37 +26,37 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
|
||||
private val inet = InetSocketAddress(ipaddress, port)
|
||||
private val maxUDPsize = 1000
|
||||
private var _tcp: Socket? = null
|
||||
private val PipeOuts = mutableMapOf<String,PipedOutputStream>()
|
||||
private val pipeOuts = mutableMapOf<String,PipedOutputStream>()
|
||||
private val mp3encoder = Mp3Encoder()
|
||||
|
||||
fun AddPipeOut(key: String, pipeOut: PipedOutputStream) {
|
||||
RemovePipeOut(key)
|
||||
PipeOuts[key] = pipeOut
|
||||
pipeOuts[key] = pipeOut
|
||||
println("Added pipeOut $key to BarixConnection $channel ($ipaddress)")
|
||||
}
|
||||
|
||||
fun RemovePipeOut(key: String) {
|
||||
println("Removing pipeOut $key")
|
||||
if (PipeOuts.contains(key)){
|
||||
val pipe = PipeOuts[key]
|
||||
if (pipeOuts.contains(key)){
|
||||
val pipe = pipeOuts[key]
|
||||
try {
|
||||
pipe?.close()
|
||||
} catch (e: Exception) {
|
||||
// ignore
|
||||
}
|
||||
println("Removed pipeOut $key from BarixConnection $channel ($ipaddress)")
|
||||
PipeOuts.remove(key)
|
||||
pipeOuts.remove(key)
|
||||
}
|
||||
}
|
||||
|
||||
fun ClearPipeOuts() {
|
||||
PipeOuts.values.forEach { piped ->
|
||||
pipeOuts.values.forEach { piped ->
|
||||
try {
|
||||
piped.close()
|
||||
} catch (e: Exception) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
PipeOuts.clear()
|
||||
pipeOuts.clear()
|
||||
}
|
||||
/**
|
||||
* Buffer remain in bytes
|
||||
@@ -137,34 +139,40 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
DatagramSocket().use{ udp ->
|
||||
val bb = ByteBuffer.wrap(data)
|
||||
if (!mp3encoder.isStarted()) {
|
||||
mp3encoder.Start { data ->
|
||||
pipeOuts.keys.forEach { kk ->
|
||||
val pp = pipeOuts[kk]
|
||||
try {
|
||||
pp?.write(data)
|
||||
pp?.flush()
|
||||
println("Written ${data.size} bytes to pipeOut $kk")
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Failed to write to pipeOut $kk, message: ${e.message}" }
|
||||
pp?.close()
|
||||
pipeOuts.remove(kk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while(bb.hasRemaining()){
|
||||
try {
|
||||
val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining())
|
||||
bb.get(chunk)
|
||||
//println("Buffer remain: $bufferRemain, sending chunk size: ${chunk.size}")
|
||||
while(bufferRemain<chunk.size){
|
||||
delay(10)
|
||||
}
|
||||
udp.send(DatagramPacket(chunk, chunk.size, inet))
|
||||
mp3encoder.PushData(chunk)
|
||||
delay(2)
|
||||
PipeOuts.keys.forEach { kk ->
|
||||
val pp = PipeOuts[kk]
|
||||
try {
|
||||
pp?.write(chunk)
|
||||
pp?.flush()
|
||||
println("Written ${chunk.size} bytes to pipeOut $kk")
|
||||
} catch (e: Exception) {
|
||||
Logger.error { "Failed to write to pipeOut $kk, message: ${e.message}" }
|
||||
pp?.close()
|
||||
PipeOuts.remove(kk)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} catch (e: Exception) {
|
||||
cbFail.accept("SendData to $ipaddress failed, message: ${e.message}")
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
mp3encoder.Stop()
|
||||
cbOK.accept("SendData to $channel ($ipaddress) succeeded, ${data.size} bytes sent")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,13 +49,14 @@ import java.io.File
|
||||
import java.io.PipedInputStream
|
||||
import java.io.PipedOutputStream
|
||||
import java.nio.file.Path
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val _config: configFile) {
|
||||
|
||||
lateinit var app: Javalin
|
||||
lateinit var semiauto : Javalin
|
||||
lateinit var semiauto: Javalin
|
||||
val objectmapper = jacksonObjectMapper()
|
||||
|
||||
private fun SendReply(context: WsMessageContext, command: String, value: String) {
|
||||
@@ -73,7 +74,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
|
||||
}
|
||||
|
||||
private fun Start_WebServer(){
|
||||
private fun Start_WebServer() {
|
||||
Logger.info { "Starting Web Application on port $listenPort" }
|
||||
app = Javalin.create { config ->
|
||||
config.useVirtualThreads = true
|
||||
@@ -253,15 +254,26 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
if (param.isNotEmpty()) {
|
||||
val bc = Get_Barix_Connection_by_ZoneName(param)
|
||||
if (bc != null) {
|
||||
val key = ctx.req().remoteAddr + ":" + ctx.req().remotePort
|
||||
|
||||
|
||||
val key = ctx.cookie("client-id") ?: UUID.randomUUID().toString()
|
||||
.also { ctx.cookie("client-id", it) }
|
||||
val pipeIN = PipedInputStream(8192)
|
||||
val pipeOUT = PipedOutputStream(pipeIN)
|
||||
// write WAV Header 44 bytes contains samplingrate 44100 Hz, 16 bit, mono
|
||||
pipeOUT.write(Generate_WAV_Header())
|
||||
// pipeOUT.write(Generate_WAV_Header())
|
||||
bc.AddPipeOut(key, pipeOUT)
|
||||
ctx.contentType("audio/wav")
|
||||
ctx.header("Cache-Control", "no-cache")
|
||||
ctx.contentType("audio/mpeg")
|
||||
ctx.header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
ctx.header("Pragma", "no-cache")
|
||||
ctx.header("Expires", "0")
|
||||
|
||||
// 🔥 This one is critical — tells Jetty to send data in HTTP chunks as it becomes available:
|
||||
ctx.header("Transfer-Encoding", "chunked")
|
||||
|
||||
// Keeps the TCP socket open so the browser doesn’t close after initial data
|
||||
ctx.header("Connection", "keep-alive")
|
||||
|
||||
ctx.result(pipeIN)
|
||||
println("LiveAudio Open for zone $param SUCCESS")
|
||||
} else ctx.status(400)
|
||||
@@ -273,17 +285,23 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
get("Close/{broadcastzone}") { ctx ->
|
||||
val param = ctx.pathParam("broadcastzone")
|
||||
println("LiveAudio Close for zone $param")
|
||||
if (param.isNotEmpty()) {
|
||||
val bc = Get_Barix_Connection_by_ZoneName(param)
|
||||
if (bc != null) {
|
||||
val key = ctx.req().remoteAddr + ":" + ctx.req().remotePort
|
||||
bc.RemovePipeOut(key)
|
||||
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
|
||||
println("LiveAudio Close for zone $param SUCCESS")
|
||||
val key = ctx.cookie("client-id")
|
||||
if (key != null && key.isNotEmpty()) {
|
||||
if (param.isNotEmpty()) {
|
||||
val bc = Get_Barix_Connection_by_ZoneName(param)
|
||||
if (bc != null) {
|
||||
|
||||
bc.RemovePipeOut(key)
|
||||
ctx.result(objectmapper.writeValueAsString(resultMessage("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("Broadcastzone not found")))
|
||||
} else ctx.status(400)
|
||||
.result(objectmapper.writeValueAsString(resultMessage("Invalid broadcastzone")))
|
||||
.result(objectmapper.writeValueAsString(resultMessage("Invalid broadcastzone")))
|
||||
} else {
|
||||
ctx.status(400)
|
||||
.result(objectmapper.writeValueAsString(resultMessage("No client-id cookie found")))
|
||||
}
|
||||
}
|
||||
}
|
||||
path("VoiceType") {
|
||||
@@ -2043,55 +2061,55 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
}.start(listenPort)
|
||||
}
|
||||
|
||||
private fun Start_SemiAutoServer(){
|
||||
Logger.info {"Starting SemiAuto web server at port ${listenPort+1}"}
|
||||
semiauto = Javalin.create{ config ->
|
||||
private fun Start_SemiAutoServer() {
|
||||
Logger.info { "Starting SemiAuto web server at port ${listenPort + 1}" }
|
||||
semiauto = Javalin.create { config ->
|
||||
config.useVirtualThreads = true
|
||||
config.staticFiles.add ("/semiauto")
|
||||
config.staticFiles.add("/semiauto")
|
||||
config.jsonMapper(JavalinJackson(jacksonObjectMapper()))
|
||||
config.router.apiBuilder {
|
||||
path("/"){
|
||||
path("/") {
|
||||
get {
|
||||
it.cookie("semiauto-user","")
|
||||
it.cookie("semiauto-user", "")
|
||||
it.redirect("login.html")
|
||||
}
|
||||
}
|
||||
path("logout"){
|
||||
path("logout") {
|
||||
get {
|
||||
it.cookie("semiauto-user","")
|
||||
it.cookie("semiauto-user", "")
|
||||
it.redirect("login.html")
|
||||
}
|
||||
}
|
||||
path("login.html"){
|
||||
path("login.html") {
|
||||
post {
|
||||
val formuser = it.formParam("username") ?: ""
|
||||
val formpass = it.formParam("password") ?: ""
|
||||
if (formuser.isNotEmpty() && formpass.isNotEmpty()){
|
||||
val user = db.userDB.List.find { u -> u.username==formuser && u.password==formpass }
|
||||
if (user!=null){
|
||||
it.cookie("semiauto-user",user.username)
|
||||
if (formuser.isNotEmpty() && formpass.isNotEmpty()) {
|
||||
val user = db.userDB.List.find { u -> u.username == formuser && u.password == formpass }
|
||||
if (user != null) {
|
||||
it.cookie("semiauto-user", user.username)
|
||||
it.redirect("index.html")
|
||||
} else ResultMessageString(it, 400, "Invalid username or password")
|
||||
} else ResultMessageString(it, 400, "Username or password cannot be empty")
|
||||
}
|
||||
}
|
||||
path("index.html"){
|
||||
path("index.html") {
|
||||
before { CheckSemiAutoUsers(it) }
|
||||
}
|
||||
path("log.html"){
|
||||
path("log.html") {
|
||||
before { CheckSemiAutoUsers(it) }
|
||||
}
|
||||
path("api"){
|
||||
path("Initialize"){
|
||||
path("api") {
|
||||
path("Initialize") {
|
||||
get { ctx ->
|
||||
val username = ctx.cookie("semiauto-user") ?: ""
|
||||
if (username.isNotEmpty()){
|
||||
val user = db.userDB.List.find { u -> u.username==username }
|
||||
if (user!=null){
|
||||
if (username.isNotEmpty()) {
|
||||
val user = db.userDB.List.find { u -> u.username == username }
|
||||
if (user != null) {
|
||||
val result = SemiAutoInitData(username)
|
||||
// messages
|
||||
String_To_List(user.messagebank_ann_id).forEach { msg ->
|
||||
db.messageDB.List.filter{it.ANN_ID==msg.toUInt()}.forEach {xx ->
|
||||
db.messageDB.List.filter { it.ANN_ID == msg.toUInt() }.forEach { xx ->
|
||||
result.messages.add("${xx.ANN_ID};${xx.Description};${xx.Language};${xx.Message_Detail}")
|
||||
}
|
||||
}
|
||||
@@ -2100,47 +2118,55 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
// cities
|
||||
String_To_List(user.city_tags).forEach { ct ->
|
||||
db.soundDB.List.firstOrNull { it.TAG == ct && it.Category == Category.City.name }
|
||||
?.let{
|
||||
result.cities.add("${it.TAG};${it.Description}")
|
||||
}
|
||||
?.let {
|
||||
result.cities.add("${it.TAG};${it.Description}")
|
||||
}
|
||||
}
|
||||
// airplane names
|
||||
String_To_List(user.airline_tags).forEach { at ->
|
||||
db.soundDB.List.firstOrNull { it.TAG == at && it.Category == Category.Airplane_Name.name }
|
||||
?.let{
|
||||
result.airlines.add("${it.TAG};${it.Description}")
|
||||
}
|
||||
?.let {
|
||||
result.airlines.add("${it.TAG};${it.Description}")
|
||||
}
|
||||
}
|
||||
// places
|
||||
db.soundDB.List.filter{it.Category==Category.Places.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.places.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
db.soundDB.List.filter { it.Category == Category.Places.name }.distinctBy { it.TAG }
|
||||
.forEach { xx ->
|
||||
result.places.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// shalat
|
||||
db.soundDB.List.filter{it.Category==Category.Shalat.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.shalat.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
db.soundDB.List.filter { it.Category == Category.Shalat.name }.distinctBy { it.TAG }
|
||||
.forEach { xx ->
|
||||
result.shalat.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// sequences
|
||||
db.soundDB.List.filter{it.Category==Category.Sequence.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
db.soundDB.List.filter { it.Category == Category.Sequence.name }
|
||||
.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.sequences.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// reasons
|
||||
db.soundDB.List.filter{it.Category==Category.Reason.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.reasons.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
db.soundDB.List.filter { it.Category == Category.Reason.name }.distinctBy { it.TAG }
|
||||
.forEach { xx ->
|
||||
result.reasons.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// procedures
|
||||
db.soundDB.List.filter{it.Category==Category.Procedure.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
db.soundDB.List.filter { it.Category == Category.Procedure.name }
|
||||
.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.procedures.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// gates
|
||||
db.soundDB.List.filter{it.Category==Category.Gate.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.gates.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
db.soundDB.List.filter { it.Category == Category.Gate.name }.distinctBy { it.TAG }
|
||||
.forEach { xx ->
|
||||
result.gates.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// compensation
|
||||
db.soundDB.List.filter{it.Category==Category.Compensation.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
db.soundDB.List.filter { it.Category == Category.Compensation.name }
|
||||
.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.compensation.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
// greetings
|
||||
db.soundDB.List.filter{it.Category==Category.Greeting.name}.distinctBy { it.TAG }.forEach { xx ->
|
||||
db.soundDB.List.filter { it.Category == Category.Greeting.name }
|
||||
.distinctBy { it.TAG }.forEach { xx ->
|
||||
result.greetings.add("${xx.TAG};${xx.Description}")
|
||||
}
|
||||
|
||||
@@ -2149,18 +2175,18 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
} else ResultMessageString(ctx, 400, "Username is empty")
|
||||
}
|
||||
}
|
||||
path("SemiAuto"){
|
||||
path("SemiAuto") {
|
||||
post { ctx ->
|
||||
val json : JsonNode = objectmapper.readTree(ctx.body())
|
||||
val json: JsonNode = objectmapper.readTree(ctx.body())
|
||||
// butuh description, languages, tags, dan broadcastzones
|
||||
val description = json.get("description").asText("")
|
||||
val languages = json.get("languages").asText("")
|
||||
val tags = json.get("tags").asText("")
|
||||
val broadcastzones = json.get("broadcastzones").asText("")
|
||||
if (description.isNotEmpty()){
|
||||
if (languages.isNotEmpty()){
|
||||
if (tags.isNotEmpty()){
|
||||
if (broadcastzones.isNotEmpty()){
|
||||
if (description.isNotEmpty()) {
|
||||
if (languages.isNotEmpty()) {
|
||||
if (tags.isNotEmpty()) {
|
||||
if (broadcastzones.isNotEmpty()) {
|
||||
val qt = QueueTable(
|
||||
0u,
|
||||
LocalDateTime.now().format(datetimeformat1),
|
||||
@@ -2172,10 +2198,10 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
1u,
|
||||
languages
|
||||
)
|
||||
if (db.queuetableDB.Add(qt)){
|
||||
if (db.queuetableDB.Add(qt)) {
|
||||
db.queuetableDB.Resort()
|
||||
Logger.info{"SemiAutoWeb added to queue table: $qt" }
|
||||
println("SemiAuto added to queue table: ${objectmapper.writeValueAsString(qt)}")
|
||||
Logger.info { "SemiAutoWeb added to queue table: $qt" }
|
||||
//println("SemiAuto added to queue table: ${objectmapper.writeValueAsString(qt)}")
|
||||
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
|
||||
} else ResultMessageString(ctx, 500, "Failed to add to queue table")
|
||||
} else ResultMessageString(ctx, 400, "Broadcast zones cannot be empty")
|
||||
@@ -2185,13 +2211,12 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
|
||||
}
|
||||
}
|
||||
path("Log"){
|
||||
get("/{datelog}"){ ctx ->
|
||||
path("Log") {
|
||||
get("/{datelog}") { ctx ->
|
||||
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)
|
||||
println("Log HTML for date $datelog: $resultstring")
|
||||
ctx.result(resultstring)
|
||||
@@ -2202,18 +2227,19 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start(listenPort+1)
|
||||
}.start(listenPort + 1)
|
||||
}
|
||||
|
||||
fun ResultMessageString(ctx: Context, code: Int, message: String) {
|
||||
ctx.status(code).result(objectmapper.writeValueAsString(resultMessage(message)))
|
||||
}
|
||||
|
||||
fun CheckSemiAutoUsers(ctx: Context){
|
||||
fun CheckSemiAutoUsers(ctx: Context) {
|
||||
val user = ctx.cookie("semiauto-user")
|
||||
if (user == null) {
|
||||
ctx.redirect("login.html")
|
||||
}
|
||||
val foundUser = db.userDB.List.find { u -> u.username==user }
|
||||
val foundUser = db.userDB.List.find { u -> u.username == user }
|
||||
if (foundUser == null) {
|
||||
ctx.redirect("login.html")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user