commit 07/11/2025
This commit is contained in:
Binary file not shown.
Binary file not shown.
BIN
libs/linux-aarch64/libbassmix.so
Normal file
BIN
libs/linux-aarch64/libbassmix.so
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
libs/linux-armhf/libbassmix.so
Normal file
BIN
libs/linux-armhf/libbassmix.so
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
libs/linux-x86-64/libbassmix.so
Normal file
BIN
libs/linux-x86-64/libbassmix.so
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
libs/linux-x86/libbassmix.so
Normal file
BIN
libs/linux-x86/libbassmix.so
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
libs/win32-x86-64/bassmix.dll
Normal file
BIN
libs/win32-x86-64/bassmix.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
libs/win32-x86/bassmix.dll
Normal file
BIN
libs/win32-x86/bassmix.dll
Normal file
Binary file not shown.
118
src/audio/BassMix.java
Normal file
118
src/audio/BassMix.java
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package audio;
|
||||||
|
|
||||||
|
import com.sun.jna.Library;
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public interface BassMix extends Library {
|
||||||
|
|
||||||
|
BassMix Instance = (BassMix) Native.load("bassmix", BassMix.class);
|
||||||
|
|
||||||
|
// Envelope node
|
||||||
|
class BASS_MIXER_NODE {
|
||||||
|
public BASS_MIXER_NODE() {}
|
||||||
|
public BASS_MIXER_NODE(long _pos, float _value) { pos=_pos; value=_value; }
|
||||||
|
public long pos;
|
||||||
|
public float value;
|
||||||
|
}
|
||||||
|
// Additional BASS_SetConfig options
|
||||||
|
int BASS_CONFIG_MIXER_BUFFER = 0x10601;
|
||||||
|
int BASS_CONFIG_MIXER_POSEX = 0x10602;
|
||||||
|
int BASS_CONFIG_SPLIT_BUFFER = 0x10610;
|
||||||
|
|
||||||
|
// BASS_Mixer_StreamCreate flags
|
||||||
|
int BASS_MIXER_RESUME = 0x1000; // resume stalled immediately upon new/unpaused source
|
||||||
|
int BASS_MIXER_POSEX = 0x2000; // enable BASS_Mixer_ChannelGetPositionEx support
|
||||||
|
int BASS_MIXER_NOSPEAKER = 0x4000; // ignore speaker arrangement
|
||||||
|
int BASS_MIXER_QUEUE = 0x8000; // queue sources
|
||||||
|
int BASS_MIXER_END = 0x10000; // end the stream when there are no sources
|
||||||
|
int BASS_MIXER_NONSTOP = 0x20000; // don't stall when there are no sources
|
||||||
|
|
||||||
|
// BASS_Mixer_StreamAddChannel/Ex flags
|
||||||
|
int BASS_MIXER_CHAN_ABSOLUTE = 0x1000; // start is an absolute position
|
||||||
|
int BASS_MIXER_CHAN_BUFFER = 0x2000; // buffer data for BASS_Mixer_ChannelGetData/Level
|
||||||
|
int BASS_MIXER_CHAN_LIMIT = 0x4000; // limit mixer processing to the amount available from this source
|
||||||
|
int BASS_MIXER_CHAN_MATRIX = 0x10000; // matrix mixing
|
||||||
|
int BASS_MIXER_CHAN_PAUSE = 0x20000; // don't process the source
|
||||||
|
int BASS_MIXER_CHAN_DOWNMIX = 0x400000; // downmix to stereo/mono
|
||||||
|
int BASS_MIXER_CHAN_NORAMPIN = 0x800000; // don't ramp-in the start
|
||||||
|
int BASS_MIXER_BUFFER = BASS_MIXER_CHAN_BUFFER;
|
||||||
|
int BASS_MIXER_LIMIT = BASS_MIXER_CHAN_LIMIT;
|
||||||
|
int BASS_MIXER_MATRIX = BASS_MIXER_CHAN_MATRIX;
|
||||||
|
int BASS_MIXER_PAUSE = BASS_MIXER_CHAN_PAUSE;
|
||||||
|
int BASS_MIXER_DOWNMIX = BASS_MIXER_CHAN_DOWNMIX;
|
||||||
|
int BASS_MIXER_NORAMPIN = BASS_MIXER_CHAN_NORAMPIN;
|
||||||
|
|
||||||
|
// Mixer attributes
|
||||||
|
int BASS_ATTRIB_MIXER_LATENCY = 0x15000;
|
||||||
|
int BASS_ATTRIB_MIXER_THREADS = 0x15001;
|
||||||
|
int BASS_ATTRIB_MIXER_VOL = 0x15002;
|
||||||
|
|
||||||
|
// Additional BASS_Mixer_ChannelIsActive return values
|
||||||
|
int BASS_ACTIVE_WAITING = 5;
|
||||||
|
int BASS_ACTIVE_QUEUED = 6;
|
||||||
|
|
||||||
|
// BASS_Split_StreamCreate flags
|
||||||
|
int BASS_SPLIT_SLAVE = 0x1000; // only read buffered data
|
||||||
|
int BASS_SPLIT_POS = 0x2000;
|
||||||
|
|
||||||
|
// Splitter attributes
|
||||||
|
int BASS_ATTRIB_SPLIT_ASYNCBUFFER = 0x15010;
|
||||||
|
int BASS_ATTRIB_SPLIT_ASYNCPERIOD = 0x15011;
|
||||||
|
|
||||||
|
// Envelope types
|
||||||
|
int BASS_MIXER_ENV_FREQ = 1;
|
||||||
|
int BASS_MIXER_ENV_VOL = 2;
|
||||||
|
int BASS_MIXER_ENV_PAN = 3;
|
||||||
|
int BASS_MIXER_ENV_LOOP = 0x10000; // flag: loop
|
||||||
|
int BASS_MIXER_ENV_REMOVE = 0x20000; // flag: remove at end
|
||||||
|
|
||||||
|
// Additional sync types
|
||||||
|
int BASS_SYNC_MIXER_ENVELOPE = 0x10200;
|
||||||
|
int BASS_SYNC_MIXER_ENVELOPE_NODE = 0x10201;
|
||||||
|
int BASS_SYNC_MIXER_QUEUE = 0x10202;
|
||||||
|
|
||||||
|
// Additional BASS_Mixer_ChannelSetPosition flag
|
||||||
|
int BASS_POS_MIXER_RESET = 0x10000; // flag: clear mixer's playback buffer
|
||||||
|
|
||||||
|
// Additional BASS_Mixer_ChannelGetPosition mode
|
||||||
|
int BASS_POS_MIXER_DELAY = 5;
|
||||||
|
|
||||||
|
// BASS_CHANNELINFO types
|
||||||
|
int BASS_CTYPE_STREAM_MIXER = 0x10800;
|
||||||
|
int BASS_CTYPE_STREAM_SPLIT = 0x10801;
|
||||||
|
|
||||||
|
int BASS_Mixer_GetVersion();
|
||||||
|
|
||||||
|
int BASS_Mixer_StreamCreate(int freq, int chans, int flags);
|
||||||
|
boolean BASS_Mixer_StreamAddChannel(int handle, int channel, int flags);
|
||||||
|
boolean BASS_Mixer_StreamAddChannelEx(int handle, int channel, int flags, long start, long length);
|
||||||
|
int BASS_Mixer_StreamGetChannels(int handle, int[] channels, int count);
|
||||||
|
|
||||||
|
int BASS_Mixer_ChannelGetMixer(int handle);
|
||||||
|
int BASS_Mixer_ChannelIsActive(int handle);
|
||||||
|
int BASS_Mixer_ChannelFlags(int handle, int flags, int mask);
|
||||||
|
boolean BASS_Mixer_ChannelRemove(int handle);
|
||||||
|
boolean BASS_Mixer_ChannelSetPosition(int handle, long pos, int mode);
|
||||||
|
long BASS_Mixer_ChannelGetPosition(int handle, int mode);
|
||||||
|
long BASS_Mixer_ChannelGetPositionEx(int channel, int mode, int delay);
|
||||||
|
int BASS_Mixer_ChannelGetLevel(int handle);
|
||||||
|
boolean BASS_Mixer_ChannelGetLevelEx(int handle, float[] levels, float length, int flags);
|
||||||
|
int BASS_Mixer_ChannelGetData(int handle, Pointer buffer, int length);
|
||||||
|
int BASS_Mixer_ChannelSetSync(int handle, int type, long param, Bass.SYNCPROC proc, Object user);
|
||||||
|
boolean BASS_Mixer_ChannelRemoveSync(int channel, int sync);
|
||||||
|
boolean BASS_Mixer_ChannelSetMatrix(int handle, float[][] matrix);
|
||||||
|
boolean BASS_Mixer_ChannelSetMatrixEx(int handle, float[][] matrix, float time);
|
||||||
|
boolean BASS_Mixer_ChannelGetMatrix(int handle, float[][] matrix);
|
||||||
|
boolean BASS_Mixer_ChannelSetEnvelope(int handle, int type, BASS_MIXER_NODE[] nodes, int count);
|
||||||
|
boolean BASS_Mixer_ChannelSetEnvelopePos(int handle, int type, long pos);
|
||||||
|
long BASS_Mixer_ChannelGetEnvelopePos(int handle, int type, Bass.FloatValue value);
|
||||||
|
|
||||||
|
int BASS_Split_StreamCreate(int channel, int flags, int[] chanmap);
|
||||||
|
int BASS_Split_StreamGetSource(int handle);
|
||||||
|
int BASS_Split_StreamGetSplits(int handle, int[] splits, int count);
|
||||||
|
boolean BASS_Split_StreamReset(int handle);
|
||||||
|
boolean BASS_Split_StreamResetEx(int handle, int offset);
|
||||||
|
int BASS_Split_StreamGetAvailable(int handle);
|
||||||
|
}
|
||||||
@@ -48,8 +48,9 @@ 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 - -"
|
||||||
mp3_handle = bassencmp3.BASS_Encode_MP3_Start(push_handle, null, BassEnc.BASS_ENCODE_AUTOFREE,proc, null)
|
val flag = BassEnc.BASS_ENCODE_AUTOFREE or BassEnc.BASS_ENCODE_NOHEAD
|
||||||
|
mp3_handle = bassencmp3.BASS_Encode_MP3_Start(push_handle, null, flag,proc, null)
|
||||||
|
|
||||||
if (mp3_handle!=0){
|
if (mp3_handle!=0){
|
||||||
callback = cb
|
callback = cb
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package barix
|
package barix
|
||||||
|
|
||||||
import audio.Mp3Encoder
|
import audio.Mp3Encoder
|
||||||
import audio.OpusEncoder
|
|
||||||
import codes.Somecodes
|
import codes.Somecodes
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -9,7 +8,6 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.tinylog.Logger
|
import org.tinylog.Logger
|
||||||
import java.io.PipedOutputStream
|
|
||||||
import java.net.DatagramPacket
|
import java.net.DatagramPacket
|
||||||
import java.net.DatagramSocket
|
import java.net.DatagramSocket
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
@@ -26,38 +24,21 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
|
|||||||
private val inet = InetSocketAddress(ipaddress, port)
|
private val inet = InetSocketAddress(ipaddress, port)
|
||||||
private val maxUDPsize = 1000
|
private val maxUDPsize = 1000
|
||||||
private var _tcp: Socket? = null
|
private var _tcp: Socket? = null
|
||||||
private val pipeOuts = mutableMapOf<String,PipedOutputStream>()
|
|
||||||
private val mp3encoder = Mp3Encoder()
|
private val mp3encoder = Mp3Encoder()
|
||||||
|
private val mp3Consumer = mutableMapOf<String, Consumer<ByteArray>>()
|
||||||
|
|
||||||
fun AddPipeOut(key: String, pipeOut: PipedOutputStream) {
|
fun Add_Mp3_Consumer(key: String, cb: Consumer<ByteArray>) {
|
||||||
RemovePipeOut(key)
|
mp3Consumer[key] = cb
|
||||||
pipeOuts[key] = pipeOut
|
|
||||||
println("Added pipeOut $key to BarixConnection $channel ($ipaddress)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RemovePipeOut(key: String) {
|
fun Remove_Mp3_Consumer(key: String){
|
||||||
if (pipeOuts.contains(key)){
|
mp3Consumer.remove(key)
|
||||||
val pipe = pipeOuts[key]
|
|
||||||
try {
|
|
||||||
pipe?.close()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
println("Removed pipeOut $key from BarixConnection $channel ($ipaddress)")
|
|
||||||
pipeOuts.remove(key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ClearPipeOuts() {
|
fun Exists_Mp3_Consumer(key: String) : Boolean{
|
||||||
pipeOuts.values.forEach { piped ->
|
return mp3Consumer.containsKey(key)
|
||||||
try {
|
|
||||||
piped.close()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pipeOuts.clear()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Buffer remain in bytes
|
* Buffer remain in bytes
|
||||||
*/
|
*/
|
||||||
@@ -141,18 +122,7 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
|
|||||||
val bb = ByteBuffer.wrap(data)
|
val bb = ByteBuffer.wrap(data)
|
||||||
if (!mp3encoder.isStarted()) {
|
if (!mp3encoder.isStarted()) {
|
||||||
mp3encoder.Start { data ->
|
mp3encoder.Start { data ->
|
||||||
pipeOuts.keys.forEach { kk ->
|
mp3Consumer.values.forEach { it.accept(data) }
|
||||||
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()){
|
while(bb.hasRemaining()){
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package web
|
|||||||
import StreamerOutputs
|
import StreamerOutputs
|
||||||
import barix.BarixConnection
|
import barix.BarixConnection
|
||||||
import codes.Somecodes
|
import codes.Somecodes
|
||||||
import codes.Somecodes.Companion.Generate_WAV_Header
|
|
||||||
import codes.Somecodes.Companion.GetSensorsInfo
|
import codes.Somecodes.Companion.GetSensorsInfo
|
||||||
import codes.Somecodes.Companion.GetUptime
|
import codes.Somecodes.Companion.GetUptime
|
||||||
import codes.Somecodes.Companion.ListAudioFiles
|
import codes.Somecodes.Companion.ListAudioFiles
|
||||||
@@ -44,10 +43,9 @@ 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 org.tinylog.Logger
|
import org.tinylog.Logger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.PipedInputStream
|
|
||||||
import java.io.PipedOutputStream
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@@ -58,6 +56,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>()
|
||||||
|
|
||||||
private fun SendReply(context: WsMessageContext, command: String, value: String) {
|
private fun SendReply(context: WsMessageContext, command: String, value: String) {
|
||||||
try {
|
try {
|
||||||
@@ -246,43 +245,47 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
before { CheckUsers(it) }
|
before { CheckUsers(it) }
|
||||||
}
|
}
|
||||||
path("api") {
|
path("api") {
|
||||||
//TODO gak jalan
|
//TODO https://stackoverflow.com/questions/70002015/streaming-into-audio-element
|
||||||
path("LiveAudio") {
|
path("LiveAudio") {
|
||||||
|
ws("/ws/{uuid}"){ws ->
|
||||||
|
ws.onConnect {
|
||||||
|
ctx ->
|
||||||
|
val uuid = ctx.pathParam("uuid")
|
||||||
|
val cookieresult = ctx.cookie("client-stream-id")
|
||||||
|
println("Ws connected with uuid=$uuid, cookie=$cookieresult")
|
||||||
|
WsContextMap[uuid] = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onClose {
|
||||||
|
ctx ->
|
||||||
|
val uuid = ctx.pathParam("uuid")
|
||||||
|
println("Ws close on uuid=$uuid")
|
||||||
|
WsContextMap.remove(uuid)
|
||||||
|
}
|
||||||
|
ws.onError {
|
||||||
|
ctx ->
|
||||||
|
val uuid = ctx.pathParam("uuid")
|
||||||
|
println("Ws error on uuid=$uuid")
|
||||||
|
WsContextMap.remove(uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get("Open/{broadcastzone}") { ctx ->
|
get("Open/{broadcastzone}") { ctx ->
|
||||||
val param = ctx.pathParam("broadcastzone")
|
val param = ctx.pathParam("broadcastzone")
|
||||||
println("LiveAudio Open for zone $param")
|
println("LiveAudio Open for zone $param from ${ctx.host()}" )
|
||||||
if (param.isNotEmpty()) {
|
if (param.isNotEmpty()) {
|
||||||
val bc = Get_Barix_Connection_by_ZoneName(param)
|
val bc = Get_Barix_Connection_by_ZoneName(param)
|
||||||
if (bc != null) {
|
if (bc != null) {
|
||||||
ctx.contentType("audio/mpeg")
|
|
||||||
ctx.header("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
||||||
ctx.header("Pragma", "no-cache")
|
|
||||||
ctx.header("Expires", "0")
|
|
||||||
ctx.header("Transfer-Encoding", "chunked")
|
|
||||||
ctx.header("Connection", "keep-alive")
|
|
||||||
|
|
||||||
val key = ctx.cookie("client-id") ?: UUID.randomUUID().toString()
|
val key = ctx.cookie("client-stream-id") ?: UUID.randomUUID().toString()
|
||||||
.also { ctx.cookie("client-id", it) }
|
.also { ctx.cookie("client-stream-id", it) }
|
||||||
|
|
||||||
val responseStream = ctx.res().outputStream
|
bc.Add_Mp3_Consumer(key){ mp3data ->
|
||||||
ctx.async {
|
WsContextMap[key]?.send(mp3data)
|
||||||
|
println("Send ${mp3data.size} to $key")
|
||||||
val pipeIN = PipedInputStream(8192)
|
|
||||||
val pipeOUT = PipedOutputStream(pipeIN)
|
|
||||||
bc.AddPipeOut(key, pipeOUT)
|
|
||||||
println("LiveAudio Open for zone $param SUCCESS")
|
|
||||||
try{
|
|
||||||
pipeIN.transferTo(responseStream)
|
|
||||||
responseStream.flush()
|
|
||||||
} catch(e: Exception){
|
|
||||||
|
|
||||||
} finally{
|
|
||||||
println("Cleaning up stream for $key")
|
|
||||||
bc.RemovePipeOut(key)
|
|
||||||
pipeIN.close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResultMessageString(ctx, 200, key)
|
||||||
} else ctx.status(400)
|
} else ctx.status(400)
|
||||||
.result(objectmapper.writeValueAsString(resultMessage("Broadcastzone not found")))
|
.result(objectmapper.writeValueAsString(resultMessage("Broadcastzone not found")))
|
||||||
} else ctx.status(400)
|
} else ctx.status(400)
|
||||||
@@ -292,14 +295,14 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
get("Close/{broadcastzone}") { ctx ->
|
get("Close/{broadcastzone}") { ctx ->
|
||||||
val param = ctx.pathParam("broadcastzone")
|
val param = ctx.pathParam("broadcastzone")
|
||||||
println("LiveAudio Close for zone $param")
|
println("LiveAudio Close for zone $param")
|
||||||
val key = ctx.cookie("client-id")
|
val key = ctx.cookie("client-stream-id")
|
||||||
if (key != null && key.isNotEmpty()) {
|
if (key != null && key.isNotEmpty()) {
|
||||||
if (param.isNotEmpty()) {
|
if (param.isNotEmpty()) {
|
||||||
val bc = Get_Barix_Connection_by_ZoneName(param)
|
val bc = Get_Barix_Connection_by_ZoneName(param)
|
||||||
if (bc != null) {
|
if (bc != null) {
|
||||||
|
bc.Remove_Mp3_Consumer(key)
|
||||||
bc.RemovePipeOut(key)
|
WsContextMap.remove(key)
|
||||||
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
|
ResultMessageString(ctx, 200, "OK")
|
||||||
println("LiveAudio Close for zone $param SUCCESS")
|
println("LiveAudio Close for zone $param SUCCESS")
|
||||||
} else ctx.status(400)
|
} else ctx.status(400)
|
||||||
.result(objectmapper.writeValueAsString(resultMessage("Broadcastzone not found")))
|
.result(objectmapper.writeValueAsString(resultMessage("Broadcastzone not found")))
|
||||||
|
|||||||
Reference in New Issue
Block a user