diff --git a/src/Main.kt b/src/Main.kt index 6bc8c55..6918f1c 100644 --- a/src/Main.kt +++ b/src/Main.kt @@ -240,6 +240,7 @@ fun main(args: Array) { _bc.bufferRemain = cmd.buffremain _bc.statusData = cmd.statusdata _bc.commandsocket = _tcp + _bc.BarixMode = cmd.isBarix StreamerOutputs[cmd.ipaddress] = _bc Logger.info { "Created new Streamer Output for channel ${_sc.channel} with IP ${cmd.ipaddress}" } diff --git a/src/MainExtension01.kt b/src/MainExtension01.kt index 61a4e8e..e158f6e 100644 --- a/src/MainExtension01.kt +++ b/src/MainExtension01.kt @@ -21,6 +21,7 @@ import database.BroadcastZones import database.Messagebank import database.QueueTable import database.Soundbank + import org.tinylog.Logger import java.time.DayOfWeek import java.time.LocalDate @@ -37,60 +38,94 @@ import java.time.LocalTime */ class MainExtension01 { + data class InvalidZoneDetail(val zonename: String, val reason: String) + class CheckBroadcastZoneResult{ + var allvalid: Boolean = false + var message : String? = null + var invalidzones: MutableList = mutableListOf() + } + /** * Fungsi untuk cek apakah semua broadcast zone valid * Valid berarti nama broadcast zone ada di tabel BroadcastZones, dan SoundChannel-nya ada di tabel SoundChannel, dan IP-nya valid * @param bz List of broadcast zone (SoundChannel) - * @return true jika semua valid, false jika ada yang tidak valid + * @return CheckBroadcastZoneResult object containing allvalid flag and list of invalid zones */ - fun AllBroadcastZonesValid(bz: List): Boolean { + fun AllBroadcastZonesValid(bz: List): CheckBroadcastZoneResult { + val result = CheckBroadcastZoneResult() if (bz.isNotEmpty()) { val validbz = mutableListOf() - db.broadcastDB.List.forEach { xx -> - if (ValidString(xx.description) && ValidString(xx.SoundChannel)){ - if (xx.description in bz) { - db.soundchannelDB.List.forEach { sc -> - if (sc.channel == xx.SoundChannel){ - if (ValidIPV4(sc.ip)){ - validbz.add(xx) - } - } - } - } - } - + bz.forEach { zz -> + if (ValidString(zz)){ // string tidak kosong + val findzone = db.broadcastDB.List.find { ValidString(it.description) && ValidString(it.SoundChannel) && zz.equals(it.description, true) } + if (findzone!=null){ // ketemu zona dengan deskripsi sesuai + val findsc = db.soundchannelDB.List.find { findzone.SoundChannel.equals(it.channel, true) && ValidIPV4(it.ip) } + if (findsc!=null){ // ketemu soundchannel dengan channel sesuai dan IP valid + // check apakah offline atau online + if (StreamerOutputs.containsKey(findsc.ip)){ + val bc = StreamerOutputs[findsc.ip] + if (bc!=null && bc.isOnline()){ + validbz.add(findzone) + } else result.invalidzones.add(InvalidZoneDetail(zz,"SoundChannel ${findzone.SoundChannel} with IP ${findsc.ip} is offline") ) + } else result.invalidzones.add(InvalidZoneDetail(zz,"SoundChannel ${findzone.SoundChannel} with IP ${findsc.ip} is not connected in StreamerOutputs") ) + } else result.invalidzones.add(InvalidZoneDetail(zz,"SoundChannel ${findzone.SoundChannel} not found or has invalid IP in SoundChannel table") ) + } else result.invalidzones.add(InvalidZoneDetail(zz,"Zone $zz not found in BroadcastZones table") ) + } else result.invalidzones.add(InvalidZoneDetail(zz,"Invalid broadcast zone string") ) } - //TODO buat log lebih informatif + +// db.broadcastDB.List.forEach { xx -> +// if (ValidString(xx.description) && ValidString(xx.SoundChannel)){ +// if (xx.description in bz) { +// db.soundchannelDB.List.forEach { sc -> +// if (sc.channel == xx.SoundChannel){ +// if (ValidIPV4(sc.ip)){ +// validbz.add(xx) +// } +// } +// } +// } +// } else result.invalidzones.add(InvalidZoneDetail(xx.description,"Invalid description or SoundChannel") ) +// } if (validbz.size == bz.size) { - return true - } else Logger.error { "Some requested broadcast zones are not registered in BroadcastZone table" } - } else Logger.error { "No Broadcast Zones checked for validity" } - return false + result.allvalid = true + result.message = "All requested broadcast zones are valid" + } else { + result.message = "Some requested broadcast zones are not registered in BroadcastZone table" + } + } else { + result.message = "No Broadcast Zones checked for validity" + } + return result } + /** * Fungsi untuk cek apakah semua Streamer Output idle * @param bz List of ip address - * @return true jika semua idle, false jika ada yang tidak idle + * @return ChannelIdleResult object containing allidle flag and message */ - fun AllStreamerOutputIdle(bz: List): Boolean { + fun AllStreamerOutputIdle(bz: List) : Boolean { if (bz.isNotEmpty()) { - if (StreamerOutputs.isNotEmpty()){ - val idlebz = mutableListOf() - bz.forEach { z1 -> - val so = StreamerOutputs.values.find { it.ipaddress == z1 } - if (so != null) { - if (so.isIdle()) { - idlebz.add(z1) - } - } - } - if (idlebz.size == bz.size) { - return true - } + val idlebz = mutableSetOf() + val missingbz = mutableSetOf() + fun add_idle(ip: String) { + idlebz.add(ip) + missingbz.remove(ip) } - } - return false + fun add_missing(ip: String) { + idlebz.remove(ip) + missingbz.add(ip) + } + + bz.forEach { z1 -> + val so = StreamerOutputs.values.find { it.ipaddress == z1 } + if (so != null) { + if (so.isIdle()) add_idle(z1) else add_missing(z1) + } else add_missing(z1) + } + + return idlebz.size == bz.size + } else return false } /** @@ -561,7 +596,7 @@ class MainExtension01 { return Result_GetSoundbankFiles(true, "Success", files) } - fun Read_Queue_Paging() : Boolean{ + fun Read_Queue_Paging() { db.queuepagingDB.Get() val list = db.queuepagingDB.List .filter { it.Type.equals("PAGING",true) } @@ -571,49 +606,94 @@ class MainExtension01 { if (qp.BroadcastZones.isNotBlank()){ if (ValidFile(qp.Message)){ val zz = qp.BroadcastZones.split(";") - if (AllBroadcastZonesValid(zz)){ - val ips = BroadcastZones_to_SoundChannel_IP(zz) - if (AllStreamerOutputIdle(ips)){ - val afi = audioPlayer.LoadAudioFile(qp.Message) - if (afi.isValid()){ - // file bisa di load, kirim ke masing-masing Streamer Output by IP address - Activate_Relays(zz) - ips.forEach { ip -> - // send byte array to streamer output - StreamerOutputs[ip]?.SendData(afi.bytes, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) }, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) }) - } + AllBroadcastZonesValid(zz).let{ checkresult -> + if (checkresult.allvalid){ + val ips = BroadcastZones_to_SoundChannel_IP(zz) + if (AllStreamerOutputIdle(ips)){ + val afi = audioPlayer.LoadAudioFile(qp.Message) + if (afi.isValid()){ + // file bisa di load, kirim ke masing-masing Streamer Output by IP address + Activate_Relays(zz) + ips.forEach { ip -> + // send byte array to streamer output + StreamerOutputs[ip]?.SendData(afi.bytes, + { + Deactivate_Relays(zz) + db.logDB.Add("AAS", it) }, + { + Deactivate_Relays(zz) + db.logDB.Add("AAS", it) }) + } - val logmessage = - "Broadcast started PAGING with Filename '${qp.Message}' to zones: ${qp.BroadcastZones}" - Logger.info { logmessage } - db.logDB.Add("AAS", logmessage) - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - return true - } else { - // file tidak valid, delete from queue paging - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - db.logDB.Add( - "AAS", - "Cancelled paging message $qp due to invalid audio file" - ) - Logger.error { "Cancelled paging message $qp due to invalid audio file" } + val logmessage = + "Broadcast started PAGING with Filename '${qp.Message}' to zones: ${qp.BroadcastZones}" + Logger.info { logmessage } + db.logDB.Add("AAS", logmessage) + db.queuepagingDB.DeleteByIndex(qp.index.toInt()) + db.queuepagingDB.Resort() + } else { + // file tidak valid, delete from queue paging + db.queuepagingDB.DeleteByIndex(qp.index.toInt()) + db.queuepagingDB.Resort() + db.logDB.Add( + "AAS", + "Canceled paging message $qp due to invalid audio file" + ) + Logger.error { "Canceled paging message $qp due to invalid audio file" } + } + } // kalau enggak semua streamer idle, skip dulu + } else { + checkresult.invalidzones.forEach { iz -> + Logger.error { "Cancelled paging message $qp due to invalid broadcast zone '${iz.zonename}': ${iz.reason}" } } - } // kalau enggak semua streamer idle, skip dulu - } else { - // ada broadcast zone yang tidak valid, delete from queue paging - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - db.logDB.Add("AAS", "Cancelled paging message $qp due to invalid broadcast zone") - Logger.error { "Cancelled paging message $qp due to invalid broadcast zone" } + db.queuepagingDB.DeleteByIndex(qp.index.toInt()) + db.queuepagingDB.Resort() + return@forEach + } } +// if (AllBroadcastZonesValid(zz)){ +// val ips = BroadcastZones_to_SoundChannel_IP(zz) +// if (AllStreamerOutputIdle(ips)){ +// val afi = audioPlayer.LoadAudioFile(qp.Message) +// if (afi.isValid()){ +// // file bisa di load, kirim ke masing-masing Streamer Output by IP address +// Activate_Relays(zz) +// ips.forEach { ip -> +// // send byte array to streamer output +// StreamerOutputs[ip]?.SendData(afi.bytes, +// { +// Deactivate_Relays(zz) +// db.logDB.Add("AAS", it) }, +// { +// Deactivate_Relays(zz) +// db.logDB.Add("AAS", it) }) +// } +// +// val logmessage = +// "Broadcast started PAGING with Filename '${qp.Message}' to zones: ${qp.BroadcastZones}" +// Logger.info { logmessage } +// db.logDB.Add("AAS", logmessage) +// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) +// db.queuepagingDB.Resort() +// return true +// } else { +// // file tidak valid, delete from queue paging +// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) +// db.queuepagingDB.Resort() +// db.logDB.Add( +// "AAS", +// "Canceled paging message $qp due to invalid audio file" +// ) +// Logger.error { "Canceled paging message $qp due to invalid audio file" } +// } +// } // kalau enggak semua streamer idle, skip dulu +// } else { +// // ada broadcast zone yang tidak valid, delete from queue paging +// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) +// db.queuepagingDB.Resort() +// db.logDB.Add("AAS", "Cancelled paging message $qp due to invalid broadcast zone") +// Logger.error { "Canceled paging message $qp due to invalid broadcast zone" } +// } } else { // file tidak valid, delete from queue paging db.queuepagingDB.DeleteByIndex(qp.index.toInt()) @@ -632,10 +712,9 @@ class MainExtension01 { Logger.error { "Cancelled paging message $qp due to empty broadcast zone" } } } - return false } - fun Read_Queue_Shalat() : Boolean{ + fun Read_Queue_Shalat() { db.queuepagingDB.Get() val list = db.queuepagingDB.List .filter { it.Type.equals("SHALAT",true) } @@ -643,106 +722,208 @@ class MainExtension01 { //println("Processing $qp") if (qp.BroadcastZones.isNotBlank()){ val zz = qp.BroadcastZones.split(";") - if (AllBroadcastZonesValid(zz)){ - val ann_id = Get_ANN_ID(qp.Message) - if (ann_id>0){ - val ips = BroadcastZones_to_SoundChannel_IP(zz) - if (AllStreamerOutputIdle(ips)){ - val mblist = Get_MessageBank_by_id(ann_id, listOf(Language.INDONESIA.name)) - if (mblist.isNotEmpty()) { - mblist.forEach { mb -> - val result = Get_Soundbank_Files(mb, emptyMap()) - if (result.success){ - val listafi = mutableListOf() - result.files.forEach { filenya -> - val afi = contentCache.getAudioFile(filenya) - if (afi!=null && afi.isValid()){ - listafi.add(afi) - } else { - val afi = audioPlayer.LoadAudioFile(filenya) - if (afi.isValid()) { + AllBroadcastZonesValid(zz).let { checkresult -> + if (checkresult.allvalid){ + val ann_id = Get_ANN_ID(qp.Message) + if (ann_id>0){ + val ips = BroadcastZones_to_SoundChannel_IP(zz) + + if (AllStreamerOutputIdle(ips)){ + val mblist = Get_MessageBank_by_id(ann_id, listOf(Language.INDONESIA.name)) + if (mblist.isNotEmpty()) { + mblist.forEach { mb -> + val result = Get_Soundbank_Files(mb, emptyMap()) + if (result.success){ + val listafi = mutableListOf() + result.files.forEach { filenya -> + val afi = contentCache.getAudioFile(filenya) + if (afi!=null && afi.isValid()){ listafi.add(afi) - contentCache.addAudioFile(filenya, afi) + } else { + val afi = audioPlayer.LoadAudioFile(filenya) + if (afi.isValid()) { + listafi.add(afi) + contentCache.addAudioFile(filenya, afi) + } } } - } - val targetfile = SoundbankResult_directory.resolve( - Make_WAV_FileName( - "Shalat", - "" - ) - ).toString() - - val result =audioPlayer.WavWriter( - listafi, - targetfile, true, - ) - db.logDB.Add("AAS", result.message) - if (result.success) { - // file siap broadcast - val targetafi = audioPlayer.LoadAudioFile(targetfile) - if (targetafi.isValid()) { - // activate relays from broadcast zone - Activate_Relays(zz) - ips.forEach { ip -> - // send byte array to streamer output - StreamerOutputs[ip]?.SendData(targetafi.bytes, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) }, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) }) - } - - val logmsg = - "Broadcast started SHALAT message with generated file '$targetfile' to zones: ${qp.BroadcastZones}" - Logger.info { logmsg } - db.logDB.Add("AAS", logmsg) - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - return true - } else { - db.logDB.Add( - "AAS", - "Failed to load generated Shalat WAV file $targetfile" + val targetfile = SoundbankResult_directory.resolve( + Make_WAV_FileName( + "Shalat", + "" ) + ).toString() + + val result =audioPlayer.WavWriter( + listafi, + targetfile, true, + ) + db.logDB.Add("AAS", result.message) + if (result.success) { + // file siap broadcast + val targetafi = audioPlayer.LoadAudioFile(targetfile) + if (targetafi.isValid()) { + // activate relays from broadcast zone + Activate_Relays(zz) + ips.forEach { ip -> + // send byte array to streamer output + StreamerOutputs[ip]?.SendData(targetafi.bytes, + { + Deactivate_Relays(zz) + db.logDB.Add("AAS", it) }, + { + Deactivate_Relays(zz) + db.logDB.Add("AAS", it) }) + } + + val logmsg = + "Broadcast started SHALAT message with generated file '$targetfile' to zones: ${qp.BroadcastZones}" + Logger.info { logmsg } + db.logDB.Add("AAS", logmsg) + db.queuepagingDB.DeleteByIndex(qp.index.toInt()) + db.queuepagingDB.Resort() + } else { + db.logDB.Add( + "AAS", + "Failed to load generated Shalat WAV file $targetfile" + ) + } } + } else{ + db.queuepagingDB.DeleteByIndex(qp.index.toInt()) + db.queuepagingDB.Resort() + db.logDB.Add("AAS", result.message) } - } else{ - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - db.logDB.Add("AAS", result.message) } + + + } else { + // tidak ada messagebank dengan ann_id ini, delete from queue paging + db.queuepagingDB.DeleteByIndex(qp.index.toInt()) + db.queuepagingDB.Resort() + db.logDB.Add( + "AAS", + "Cancelled Shalat message $qp due to ANN_ID $ann_id not found in Messagebank" + ) + Logger.error { "Cancelled Shalat message $qp due to ANN_ID $ann_id not found in Messagebank" } } + } // kalau enggak semua streamer idle, skip dulu - - } else { - // tidak ada messagebank dengan ann_id ini, delete from queue paging - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - db.logDB.Add( - "AAS", - "Cancelled Shalat message $qp due to ANN_ID $ann_id not found in Messagebank" - ) - Logger.error { "Cancelled Shalat message $qp due to ANN_ID $ann_id not found in Messagebank" } - } - } // kalau enggak semua streamer idle, skip dulu - + } else { + // invalid ann_id, delete from queue paging + db.queuepagingDB.DeleteByIndex(qp.index.toInt()) + db.queuepagingDB.Resort() + db.logDB.Add("AAS", "Cancelled shalat message $qp due to invalid ANN_ID") + Logger.error { "Cancelled shalat message $qp due to invalid ANN_ID" } + } } else { - // invalid ann_id, delete from queue paging + checkresult.invalidzones.forEach { iz -> + Logger.error { "Cancelled shalat message $qp due to invalid broadcast zone '${iz.zonename}': ${iz.reason}" } + } db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.Resort() - db.logDB.Add("AAS", "Cancelled shalat message $qp due to invalid ANN_ID") - Logger.error { "Cancelled shalat message $qp due to invalid ANN_ID" } } - } else { - // ada broadcast zone yang tidak valid, delete from queue paging - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - db.logDB.Add("AAS", "Cancelled shalat message $qp due to invalid broadcast zone") - Logger.error { "Cancelled shalat message $qp due to invalid broadcast zone" } } +// if (AllBroadcastZonesValid(zz)){ +// val ann_id = Get_ANN_ID(qp.Message) +// if (ann_id>0){ +// val ips = BroadcastZones_to_SoundChannel_IP(zz) +// if (AllStreamerOutputIdle(ips)){ +// val mblist = Get_MessageBank_by_id(ann_id, listOf(Language.INDONESIA.name)) +// if (mblist.isNotEmpty()) { +// mblist.forEach { mb -> +// val result = Get_Soundbank_Files(mb, emptyMap()) +// if (result.success){ +// val listafi = mutableListOf() +// result.files.forEach { filenya -> +// val afi = contentCache.getAudioFile(filenya) +// if (afi!=null && afi.isValid()){ +// listafi.add(afi) +// } else { +// val afi = audioPlayer.LoadAudioFile(filenya) +// if (afi.isValid()) { +// listafi.add(afi) +// contentCache.addAudioFile(filenya, afi) +// } +// } +// } +// val targetfile = SoundbankResult_directory.resolve( +// Make_WAV_FileName( +// "Shalat", +// "" +// ) +// ).toString() +// +// val result =audioPlayer.WavWriter( +// listafi, +// targetfile, true, +// ) +// db.logDB.Add("AAS", result.message) +// if (result.success) { +// // file siap broadcast +// val targetafi = audioPlayer.LoadAudioFile(targetfile) +// if (targetafi.isValid()) { +// // activate relays from broadcast zone +// Activate_Relays(zz) +// ips.forEach { ip -> +// // send byte array to streamer output +// StreamerOutputs[ip]?.SendData(targetafi.bytes, +// { +// Deactivate_Relays(zz) +// db.logDB.Add("AAS", it) }, +// { +// Deactivate_Relays(zz) +// db.logDB.Add("AAS", it) }) +// } +// +// val logmsg = +// "Broadcast started SHALAT message with generated file '$targetfile' to zones: ${qp.BroadcastZones}" +// Logger.info { logmsg } +// db.logDB.Add("AAS", logmsg) +// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) +// db.queuepagingDB.Resort() +// return true +// } else { +// db.logDB.Add( +// "AAS", +// "Failed to load generated Shalat WAV file $targetfile" +// ) +// } +// } +// } else{ +// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) +// db.queuepagingDB.Resort() +// db.logDB.Add("AAS", result.message) +// } +// } +// +// +// } else { +// // tidak ada messagebank dengan ann_id ini, delete from queue paging +// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) +// db.queuepagingDB.Resort() +// db.logDB.Add( +// "AAS", +// "Canceled Shalat message $qp due to ANN_ID $ann_id not found in Messagebank" +// ) +// Logger.error { "Canceled Shalat message $qp due to ANN_ID $ann_id not found in Messagebank" } +// } +// } // kalau enggak semua streamer idle, skip dulu +// +// } else { +// // invalid ann_id, delete from queue paging +// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) +// db.queuepagingDB.Resort() +// db.logDB.Add("AAS", "Cancelled shalat message $qp due to invalid ANN_ID") +// Logger.error { "Canceled shalat message $qp due to invalid ANN_ID" } +// } +// } else { +// // ada broadcast zone yang tidak valid, delete from queue paging +// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) +// db.queuepagingDB.Resort() +// db.logDB.Add("AAS", "Cancelled shalat message $qp due to invalid broadcast zone") +// Logger.error { "Canceled shalat message $qp due to invalid broadcast zone" } +// } } else { // invalid broadcast zone, delete from queue paging db.queuepagingDB.DeleteByIndex(qp.index.toInt()) @@ -751,107 +932,200 @@ class MainExtension01 { Logger.error { "Cancelled shalat message $qp due to empty broadcast zone" } } } - return false } - fun Read_Queue_Timer() : Boolean{ + fun Read_Queue_Timer() { db.queuetableDB.Get() val list = db.queuetableDB.List.filter { it.Type.equals("TIMER",true) } list.forEach { qa -> //println("Processing $qa") if (qa.BroadcastZones.isNotEmpty()){ val zz = qa.BroadcastZones.split(";") - if (AllBroadcastZonesValid(zz)){ - val ips = BroadcastZones_to_SoundChannel_IP(zz) - val ann_id = Get_ANN_ID(qa.SB_TAGS) - if (ann_id>0){ - if (AllStreamerOutputIdle(ips)){ - val mblist = Get_MessageBank_by_id(ann_id, qa.Language.split(";")) - if (mblist.isNotEmpty()) { - mblist.forEach { - mb -> - val result = Get_Soundbank_Files(mb, emptyMap()) - if (result.success){ - val listafi = mutableListOf() - result.files.forEach { filenya -> - val afi = contentCache.getAudioFile(filenya) - if (afi!=null && afi.isValid()){ - listafi.add(afi) - } else { - val afi = audioPlayer.LoadAudioFile(filenya) - if (afi.isValid()) { - listafi.add(afi) - contentCache.addAudioFile(filenya, afi) - } - } + AllBroadcastZonesValid(zz).let { checkresult -> + if (checkresult.allvalid){ + val ips = BroadcastZones_to_SoundChannel_IP(zz) + val ann_id = Get_ANN_ID(qa.SB_TAGS) + if (ann_id>0){ - } - val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("Timer","")).toString() - val result = audioPlayer.WavWriter(listafi, targetfile, true) - db.logDB.Add("AAS", result.message) + if (AllStreamerOutputIdle(ips)){ + val mblist = Get_MessageBank_by_id(ann_id, qa.Language.split(";")) + if (mblist.isNotEmpty()) { + mblist.forEach { + mb -> + val result = Get_Soundbank_Files(mb, emptyMap()) if (result.success){ - // file siap broadcast - val targetafi = audioPlayer.LoadAudioFile(targetfile) - if (targetafi.isValid()) { - // activate relays from broadcast zone - Activate_Relays(zz) - ips.forEach { ip -> - // send byte array to streamer output - StreamerOutputs[ip]?.SendData(targetafi.bytes, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) }, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) }) + val listafi = mutableListOf() + result.files.forEach { filenya -> + val afi = contentCache.getAudioFile(filenya) + if (afi!=null && afi.isValid()){ + listafi.add(afi) + } else { + val afi = audioPlayer.LoadAudioFile(filenya) + if (afi.isValid()) { + listafi.add(afi) + contentCache.addAudioFile(filenya, afi) + } } - val logmsg = - "Broadcast started TIMER message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" - Logger.info { logmsg } - db.logDB.Add("AAS", logmsg) + } + val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("Timer","")).toString() + val result = audioPlayer.WavWriter(listafi, targetfile, true) + db.logDB.Add("AAS", result.message) + if (result.success){ + // file siap broadcast + val targetafi = audioPlayer.LoadAudioFile(targetfile) + if (targetafi.isValid()) { + // activate relays from broadcast zone + Activate_Relays(zz) + ips.forEach { ip -> + // send byte array to streamer output + StreamerOutputs[ip]?.SendData(targetafi.bytes, + { + Deactivate_Relays(zz) + db.logDB.Add("AAS", it) }, + { + Deactivate_Relays(zz) + db.logDB.Add("AAS", it) }) + } + + val logmsg = + "Broadcast started TIMER message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" + Logger.info { logmsg } + db.logDB.Add("AAS", logmsg) + db.queuetableDB.DeleteByIndex(qa.index.toInt()) + db.queuetableDB.Resort() + } + } else{ db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.Resort() - return true + db.logDB.Add("AAS", result.message) } - } else{ + } else { db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.Resort() db.logDB.Add("AAS", result.message) } - } else { - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add("AAS", result.message) } + + } else { + // tidak ada messagebank dengan ann_id ini, delete from queue table + db.queuetableDB.DeleteByIndex(qa.index.toInt()) + db.queuetableDB.Resort() + db.logDB.Add( + "AAS", + "Cancelled TIMER $qa due to ANN_ID $ann_id not found in Messagebank" + ) + Logger.error { "Cancelled TIMER $qa due to ANN_ID $ann_id not found in Messagebank" } } + } // kalau enggak semua streamer idle, skip dulu - } else { - // tidak ada messagebank dengan ann_id ini, delete from queue table - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add( - "AAS", - "Cancelled TIMER $qa due to ANN_ID $ann_id not found in Messagebank" - ) - Logger.error { "Cancelled TIMER $qa due to ANN_ID $ann_id not found in Messagebank" } - } - } // kalau enggak semua streamer idle, skip dulu - + } else { + // invalid ann_id, delete from queue table + db.queuetableDB.DeleteByIndex(qa.index.toInt()) + db.queuetableDB.Resort() + db.logDB.Add("AAS", "Cancelled TIMER message $qa due to invalid ANN_ID") + Logger.error { "Cancelled TIMER message $qa due to invalid ANN_ID" } + } } else { - // invalid ann_id, delete from queue table + checkresult.invalidzones.forEach { iz -> + Logger.error { "Cancelled TIMER message $qa due to invalid broadcast zone '${iz.zonename}': ${iz.reason}" } + } db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.Resort() - db.logDB.Add("AAS", "Cancelled TIMER message $qa due to invalid ANN_ID") - Logger.error { "Cancelled TIMER message $qa due to invalid ANN_ID" } + return@forEach } - } else { - // ada broadcast zone yang tidak valid, delete from queue table - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add("AAS", "Cancelled TIMER message $qa due to invalid broadcast zone") - Logger.error {"Cancelled TIMER message $qa due to invalid broadcast zone"} } +// if (AllBroadcastZonesValid(zz)){ +// val ips = BroadcastZones_to_SoundChannel_IP(zz) +// val ann_id = Get_ANN_ID(qa.SB_TAGS) +// if (ann_id>0){ +// if (AllStreamerOutputIdle(ips)){ +// val mblist = Get_MessageBank_by_id(ann_id, qa.Language.split(";")) +// if (mblist.isNotEmpty()) { +// mblist.forEach { +// mb -> +// val result = Get_Soundbank_Files(mb, emptyMap()) +// if (result.success){ +// val listafi = mutableListOf() +// result.files.forEach { filenya -> +// val afi = contentCache.getAudioFile(filenya) +// if (afi!=null && afi.isValid()){ +// listafi.add(afi) +// } else { +// val afi = audioPlayer.LoadAudioFile(filenya) +// if (afi.isValid()) { +// listafi.add(afi) +// contentCache.addAudioFile(filenya, afi) +// } +// } +// +// } +// val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("Timer","")).toString() +// val result = audioPlayer.WavWriter(listafi, targetfile, true) +// db.logDB.Add("AAS", result.message) +// if (result.success){ +// // file siap broadcast +// val targetafi = audioPlayer.LoadAudioFile(targetfile) +// if (targetafi.isValid()) { +// // activate relays from broadcast zone +// Activate_Relays(zz) +// ips.forEach { ip -> +// // send byte array to streamer output +// StreamerOutputs[ip]?.SendData(targetafi.bytes, +// { +// Deactivate_Relays(zz) +// db.logDB.Add("AAS", it) }, +// { +// Deactivate_Relays(zz) +// db.logDB.Add("AAS", it) }) +// } +// +// val logmsg = +// "Broadcast started TIMER message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" +// Logger.info { logmsg } +// db.logDB.Add("AAS", logmsg) +// db.queuetableDB.DeleteByIndex(qa.index.toInt()) +// db.queuetableDB.Resort() +// return true +// } +// } else{ +// db.queuetableDB.DeleteByIndex(qa.index.toInt()) +// db.queuetableDB.Resort() +// db.logDB.Add("AAS", result.message) +// } +// } else { +// db.queuetableDB.DeleteByIndex(qa.index.toInt()) +// db.queuetableDB.Resort() +// db.logDB.Add("AAS", result.message) +// } +// } +// +// } else { +// // tidak ada messagebank dengan ann_id ini, delete from queue table +// db.queuetableDB.DeleteByIndex(qa.index.toInt()) +// db.queuetableDB.Resort() +// db.logDB.Add( +// "AAS", +// "Cancelled TIMER $qa due to ANN_ID $ann_id not found in Messagebank" +// ) +// Logger.error { "Cancelled TIMER $qa due to ANN_ID $ann_id not found in Messagebank" } +// } +// } // kalau enggak semua streamer idle, skip dulu +// +// } else { +// // invalid ann_id, delete from queue table +// db.queuetableDB.DeleteByIndex(qa.index.toInt()) +// db.queuetableDB.Resort() +// db.logDB.Add("AAS", "Cancelled TIMER message $qa due to invalid ANN_ID") +// Logger.error { "Cancelled TIMER message $qa due to invalid ANN_ID" } +// } +// } else { +// // ada broadcast zone yang tidak valid, delete from queue table +// db.queuetableDB.DeleteByIndex(qa.index.toInt()) +// db.queuetableDB.Resort() +// db.logDB.Add("AAS", "Cancelled TIMER message $qa due to invalid broadcast zone") +// Logger.error {"Cancelled TIMER message $qa due to invalid broadcast zone"} +// } } else { // invalid broadcast zone, delete from queue table db.queuetableDB.DeleteByIndex(qa.index.toInt()) @@ -860,7 +1134,6 @@ class MainExtension01 { Logger.error { "Cancelled TIMER message $qa due to empty broadcast zone" } } } - return false } fun Activate_Relays(zz: List){ @@ -897,165 +1170,337 @@ class MainExtension01 { /** * Read and process Queue_Table table for SOUNDBANK type messages. */ - fun Read_Queue_Soundbank() : Boolean{ + fun Read_Queue_Soundbank() { db.queuetableDB.Get() val list = db.queuetableDB.List.filter { it.Type.equals("SOUNDBANK",true) } list.forEach { qa -> //println("Processing $qa") if (qa.BroadcastZones.isNotEmpty()){ val zz = qa.BroadcastZones.split(";") - if (AllBroadcastZonesValid(zz)){ - val ips = BroadcastZones_to_SoundChannel_IP(zz) - if (AllStreamerOutputIdle(ips)) { - val variables = Get_Soundbank_Data(qa.SB_TAGS) - val languages = qa.Language.split(";") - // cek apakah ANN_ID ada di SB_TAGS - if (variables!=null){ - Logger.info{"SB_TAGS variables: $variables"} - } else{ - Logger.info{"No SB_TAGS variables found"} - } + AllBroadcastZonesValid(zz).let { checkresult -> + if (checkresult.allvalid){ + val ips = BroadcastZones_to_SoundChannel_IP(zz) - var ann_id = variables?.get("ANN_ID")?.toIntOrNull() ?: 0 - - if (ann_id==0){ - // not available from variables, try to get from Message column - // ada ini, karena protokol FIS dulu tidak ada ANN_ID tapi pake Remark - val remark = variables?.get("REMARK").orEmpty() - db.logDB.Add("AAS", "Trying to get ANN_ID from REMARK field: $remark") - Logger.info{ "Trying to get ANN_ID from REMARK field: $remark" } - when(remark){ - "GOP" -> { - val remarkMsg = config.Get(configKeys.REMARK_GOP.key) - Logger.info{"Remark message for GOP: $remarkMsg"} - ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 - } - "GBD" ->{ - val remarkMsg = config.Get(configKeys.REMARK_GBD.key) - Logger.info{"Remark message for GBD: $remarkMsg"} - ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 - } - "GFC" ->{ - val remarkMsg = config.Get(configKeys.REMARK_GFC.key) - Logger.info{"Remark message for GFC: $remarkMsg"} - ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 - } - "FLD" ->{ - val remarkMsg = config.Get(configKeys.REMARK_FLD.key) - Logger.info{"Remark message for FLD: $remarkMsg"} - ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 - } + if (AllStreamerOutputIdle(ips)) { + val variables = Get_Soundbank_Data(qa.SB_TAGS) + val languages = qa.Language.split(";") + // cek apakah ANN_ID ada di SB_TAGS + if (variables!=null){ + Logger.info{"SB_TAGS variables: $variables"} + } else{ + Logger.info{"No SB_TAGS variables found"} } - Logger.info{"Found ANN_ID from REMARK field: $ann_id" } - db.logDB.Add("AAS", "Found ANN_ID from REMARK field: $ann_id") - } else { - db.logDB.Add("AAS", "Found ANN_ID from SB_TAGS variables: $ann_id") - Logger.info{ "Found ANN_ID from SB_TAGS variables: $ann_id" } - } - // recheck again - if (ann_id == 0) { - db.logDB.Add( - "AAS", - "Cancelled SOUNDBANK message $qa due to missing or invalid ANN_ID in SB_TAGS" - ) - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - return@forEach - } - // sampe sini punya ann_id valid - - val mblist = Get_MessageBank_by_id(ann_id, languages) - if (mblist.isNotEmpty()) { - val listafi = mutableListOf() - - mblist.forEach { mb -> - val result = Get_Soundbank_Files(mb, variables ?: emptyMap()) - if (result.success){ - result.files.forEach { filenya -> - val afi = contentCache.getAudioFile(filenya) - if (afi!=null && afi.isValid()){ - listafi.add(afi) - } else { - val afi = audioPlayer.LoadAudioFile(filenya) - if (afi.isValid()) { - listafi.add(afi) - contentCache.addAudioFile(filenya, afi) - } - } + var ann_id = variables?.get("ANN_ID")?.toIntOrNull() ?: 0 + if (ann_id==0){ + // not available from variables, try to get from Message column + // ada ini, karena protokol FIS dulu tidak ada ANN_ID tapi pake Remark + val remark = variables?.get("REMARK").orEmpty() + db.logDB.Add("AAS", "Trying to get ANN_ID from REMARK field: $remark") + Logger.info{ "Trying to get ANN_ID from REMARK field: $remark" } + when(remark){ + "GOP" -> { + val remarkMsg = config.Get(configKeys.REMARK_GOP.key) + Logger.info{"Remark message for GOP: $remarkMsg"} + ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 + } + "GBD" ->{ + val remarkMsg = config.Get(configKeys.REMARK_GBD.key) + Logger.info{"Remark message for GBD: $remarkMsg"} + ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 + } + "GFC" ->{ + val remarkMsg = config.Get(configKeys.REMARK_GFC.key) + Logger.info{"Remark message for GFC: $remarkMsg"} + ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 + } + "FLD" ->{ + val remarkMsg = config.Get(configKeys.REMARK_FLD.key) + Logger.info{"Remark message for FLD: $remarkMsg"} + ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 } - } else { - db.logDB.Add("AAS", result.message) - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() } - + Logger.info{"Found ANN_ID from REMARK field: $ann_id" } + db.logDB.Add("AAS", "Found ANN_ID from REMARK field: $ann_id") + } else { + db.logDB.Add("AAS", "Found ANN_ID from SB_TAGS variables: $ann_id") + Logger.info{ "Found ANN_ID from SB_TAGS variables: $ann_id" } } - if (listafi.isNotEmpty()){ + // recheck again + if (ann_id == 0) { + db.logDB.Add( + "AAS", + "Cancelled SOUNDBANK message $qa due to missing or invalid ANN_ID in SB_TAGS" + ) db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.Resort() - // bikin nama file ada postifx nya dari SB_TAGS, tapi spasi diganti underscore - // dan titik dua diganti dash - val postfix = qa.SB_TAGS.replace(" ","_").replace(":","-") - val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("${qa.Source}_${qa.Type}",postfix)).toString() - //println("Writing to target WAV file: $targetfile") - val result = audioPlayer.WavWriter(listafi, targetfile, true) - if (result.success){ - // file siap broadcast - //println("Successfully wrote WAV file: $targetfile") - val targetafi = audioPlayer.LoadAudioFile(targetfile) - if (targetafi.isValid()) { - // activate relays from broadcast zone - Activate_Relays(zz) - ips.forEach { ip -> - // send byte array to streamer output - StreamerOutputs[ip]?.SendData(targetafi.bytes, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) + return@forEach + } + // sampe sini punya ann_id valid + + val mblist = Get_MessageBank_by_id(ann_id, languages) + if (mblist.isNotEmpty()) { + val listafi = mutableListOf() + + mblist.forEach { mb -> + val result = Get_Soundbank_Files(mb, variables ?: emptyMap()) + if (result.success){ + result.files.forEach { filenya -> + val afi = contentCache.getAudioFile(filenya) + if (afi!=null && afi.isValid()){ + listafi.add(afi) + } else { + val afi = audioPlayer.LoadAudioFile(filenya) + if (afi.isValid()) { + listafi.add(afi) + contentCache.addAudioFile(filenya, afi) + } + } - }, { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) - }) } + } else { + Logger.error{"Failed to get soundbank files for ANN_ID $ann_id: ${result.message}"} + db.logDB.Add("AAS", result.message) - val logmsg = - "Broadcast started SOUNDBANK message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" - Logger.info { logmsg } - db.logDB.Add("AAS", logmsg) - - return true } - } else { - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add("AAS", result.message) + } + db.queuetableDB.DeleteByIndex(qa.index.toInt()) + db.queuetableDB.Resort() + + if (listafi.isNotEmpty()){ + + // bikin nama file ada postifx nya dari SB_TAGS, tapi spasi diganti underscore + // dan titik dua diganti dash + val postfix = qa.SB_TAGS.replace(" ","_").replace(":","-") + val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("${qa.Source}_${qa.Type}",postfix)).toString() + //println("Writing to target WAV file: $targetfile") + val result = audioPlayer.WavWriter(listafi, targetfile, true) + if (result.success){ + // file siap broadcast + //println("Successfully wrote WAV file: $targetfile") + val targetafi = audioPlayer.LoadAudioFile(targetfile) + if (targetafi.isValid()) { + // activate relays from broadcast zone + Activate_Relays(zz) + ips.forEach { ip -> + // send byte array to streamer output + StreamerOutputs[ip]?.SendData(targetafi.bytes, + { + Deactivate_Relays(zz) + db.logDB.Add("AAS", it) + + }, { + Deactivate_Relays(zz) + db.logDB.Add("AAS", it) + }) + } + + val logmsg = + "Broadcast started SOUNDBANK message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" + Logger.info { logmsg } + db.logDB.Add("AAS", logmsg) + + } + } else { + db.queuetableDB.DeleteByIndex(qa.index.toInt()) + db.queuetableDB.Resort() + db.logDB.Add("AAS", result.message) + } + + } else { + db.logDB.Add( + "AAS", + "Cancelled SOUNDBANK message $qa due to no valid soundbank files generated for ANN_ID $ann_id" + ) + Logger.error { "Cancelled SOUNDBANK message $qa due to no valid soundbank files generated for ANN_ID $ann_id" } + } + } else { + // tidak ada messagebank dengan ann_id ini, delete from queue table + db.queuetableDB.DeleteByIndex(qa.index.toInt()) + db.queuetableDB.Resort() + db.logDB.Add( + "AAS", + "Cancelled SOUNDBANK message $qa due to ANN_ID $ann_id not found in Messagebank" + ) + Logger.error { "Cancelled SOUNDBANK message $qa due to ANN_ID $ann_id not found in Messagebank" } + return@forEach } - } else { - // tidak ada messagebank dengan ann_id ini, delete from queue table - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add( - "AAS", - "Cancelled SOUNDBANK message $qa due to ANN_ID $ann_id not found in Messagebank" - ) - Logger.error { "Cancelled SOUNDBANK message $qa due to ANN_ID $ann_id not found in Messagebank" } + } - + } else { + checkresult.invalidzones.forEach { iz -> + Logger.error { "Cancelled SOUNDBANK message $qa due to invalid broadcast zone '${iz.zonename}': ${iz.reason}" } + } + db.queuetableDB.DeleteByIndex(qa.index.toInt()) + db.queuetableDB.Resort() + return@forEach } - - } else { - // ada broadcast zone yang tidak valid, delete from queue table - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add("AAS", "Cancelled SOUNDBANK message $qa due to invalid broadcast zone") - Logger.error { "Cancelled SOUNDBANK message $qa due to invalid broadcast zone" } } +// if (AllBroadcastZonesValid(zz)){ +// val ips = BroadcastZones_to_SoundChannel_IP(zz) +// if (AllStreamerOutputIdle(ips)) { +// val variables = Get_Soundbank_Data(qa.SB_TAGS) +// val languages = qa.Language.split(";") +// // cek apakah ANN_ID ada di SB_TAGS +// if (variables!=null){ +// Logger.info{"SB_TAGS variables: $variables"} +// } else{ +// Logger.info{"No SB_TAGS variables found"} +// } +// +// var ann_id = variables?.get("ANN_ID")?.toIntOrNull() ?: 0 +// +// if (ann_id==0){ +// // not available from variables, try to get from Message column +// // ada ini, karena protokol FIS dulu tidak ada ANN_ID tapi pake Remark +// val remark = variables?.get("REMARK").orEmpty() +// db.logDB.Add("AAS", "Trying to get ANN_ID from REMARK field: $remark") +// Logger.info{ "Trying to get ANN_ID from REMARK field: $remark" } +// when(remark){ +// "GOP" -> { +// val remarkMsg = config.Get(configKeys.REMARK_GOP.key) +// Logger.info{"Remark message for GOP: $remarkMsg"} +// ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 +// } +// "GBD" ->{ +// val remarkMsg = config.Get(configKeys.REMARK_GBD.key) +// Logger.info{"Remark message for GBD: $remarkMsg"} +// ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 +// } +// "GFC" ->{ +// val remarkMsg = config.Get(configKeys.REMARK_GFC.key) +// Logger.info{"Remark message for GFC: $remarkMsg"} +// ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 +// } +// "FLD" ->{ +// val remarkMsg = config.Get(configKeys.REMARK_FLD.key) +// Logger.info{"Remark message for FLD: $remarkMsg"} +// ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 +// } +// } +// Logger.info{"Found ANN_ID from REMARK field: $ann_id" } +// db.logDB.Add("AAS", "Found ANN_ID from REMARK field: $ann_id") +// } else { +// db.logDB.Add("AAS", "Found ANN_ID from SB_TAGS variables: $ann_id") +// Logger.info{ "Found ANN_ID from SB_TAGS variables: $ann_id" } +// } +// +// // recheck again +// if (ann_id == 0) { +// db.logDB.Add( +// "AAS", +// "Cancelled SOUNDBANK message $qa due to missing or invalid ANN_ID in SB_TAGS" +// ) +// db.queuetableDB.DeleteByIndex(qa.index.toInt()) +// db.queuetableDB.Resort() +// return@forEach +// } +// // sampe sini punya ann_id valid +// +// val mblist = Get_MessageBank_by_id(ann_id, languages) +// if (mblist.isNotEmpty()) { +// val listafi = mutableListOf() +// +// mblist.forEach { mb -> +// val result = Get_Soundbank_Files(mb, variables ?: emptyMap()) +// if (result.success){ +// result.files.forEach { filenya -> +// val afi = contentCache.getAudioFile(filenya) +// if (afi!=null && afi.isValid()){ +// listafi.add(afi) +// } else { +// val afi = audioPlayer.LoadAudioFile(filenya) +// if (afi.isValid()) { +// listafi.add(afi) +// contentCache.addAudioFile(filenya, afi) +// } +// } +// +// } +// } else { +// Logger.error{"Failed to get soundbank files for ANN_ID $ann_id: ${result.message}"} +// db.logDB.Add("AAS", result.message) +// +// } +// +// } +// +// db.queuetableDB.DeleteByIndex(qa.index.toInt()) +// db.queuetableDB.Resort() +// +// if (listafi.isNotEmpty()){ +// +// // bikin nama file ada postifx nya dari SB_TAGS, tapi spasi diganti underscore +// // dan titik dua diganti dash +// val postfix = qa.SB_TAGS.replace(" ","_").replace(":","-") +// val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("${qa.Source}_${qa.Type}",postfix)).toString() +// //println("Writing to target WAV file: $targetfile") +// val result = audioPlayer.WavWriter(listafi, targetfile, true) +// if (result.success){ +// // file siap broadcast +// //println("Successfully wrote WAV file: $targetfile") +// val targetafi = audioPlayer.LoadAudioFile(targetfile) +// if (targetafi.isValid()) { +// // activate relays from broadcast zone +// Activate_Relays(zz) +// ips.forEach { ip -> +// // send byte array to streamer output +// StreamerOutputs[ip]?.SendData(targetafi.bytes, +// { +// Deactivate_Relays(zz) +// db.logDB.Add("AAS", it) +// +// }, { +// Deactivate_Relays(zz) +// db.logDB.Add("AAS", it) +// }) +// } +// +// val logmsg = +// "Broadcast started SOUNDBANK message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" +// Logger.info { logmsg } +// db.logDB.Add("AAS", logmsg) +// +// return true +// } +// } else { +// db.queuetableDB.DeleteByIndex(qa.index.toInt()) +// db.queuetableDB.Resort() +// db.logDB.Add("AAS", result.message) +// } +// +// } else { +// db.logDB.Add( +// "AAS", +// "Cancelled SOUNDBANK message $qa due to no valid soundbank files generated for ANN_ID $ann_id" +// ) +// Logger.error { "Cancelled SOUNDBANK message $qa due to no valid soundbank files generated for ANN_ID $ann_id" } +// } +// } else { +// // tidak ada messagebank dengan ann_id ini, delete from queue table +// db.queuetableDB.DeleteByIndex(qa.index.toInt()) +// db.queuetableDB.Resort() +// db.logDB.Add( +// "AAS", +// "Cancelled SOUNDBANK message $qa due to ANN_ID $ann_id not found in Messagebank" +// ) +// Logger.error { "Cancelled SOUNDBANK message $qa due to ANN_ID $ann_id not found in Messagebank" } +// return@forEach +// } +// +// } +// +// } else { +// // ada broadcast zone yang tidak valid, delete from queue table +// db.queuetableDB.DeleteByIndex(qa.index.toInt()) +// db.queuetableDB.Resort() +// db.logDB.Add("AAS", "Cancelled SOUNDBANK message $qa due to invalid broadcast zone") +// Logger.error { "Cancelled SOUNDBANK message $qa due to invalid broadcast zone" } +// } } else { // invalid broadcast zone, delete from queue table db.queuetableDB.DeleteByIndex(qa.index.toInt()) @@ -1064,7 +1509,6 @@ class MainExtension01 { Logger.error { "Cancelled SOUNDBANK message $qa due to empty broadcast zone" } } } - return false } /** diff --git a/src/barix/BarixConnection.kt b/src/barix/BarixConnection.kt index 7cee83c..d6a3a0f 100644 --- a/src/barix/BarixConnection.kt +++ b/src/barix/BarixConnection.kt @@ -14,6 +14,7 @@ import java.net.InetSocketAddress import java.net.Socket import java.nio.ByteBuffer import java.util.function.Consumer +import kotlin.experimental.or @Suppress("unused") class BarixConnection(val index: UInt, var channel: String, val ipaddress: String, val port: Int = 5002) : AutoCloseable { @@ -27,6 +28,16 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin private val mp3encoder = Mp3Encoder() private val mp3Consumer = mutableMapOf>() private val udp = DatagramSocket() + private var _barixmode: Boolean = false + + /** + * Barix mode flag + */ + var BarixMode: Boolean + get() = _barixmode + set(value) { + _barixmode = value + } init { mp3encoder.Start { data -> @@ -132,11 +143,12 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining()) bb.get(chunk) while(bufferRemain){ + if (relays.isNotEmpty()){ + var value : Byte = 0 + for (r in relays){ + if (r in 1..8){ + value = value or (1 shl (r - 1)).toByte() + } + } + SendSimpleCommand(byteArrayOf(0x1A, value, 0x61)) + } + } + /** * Activate relay on Barix device * @param relays The relay numbers to activate (1-8) - * @return true if successful */ fun ActivateRelay(vararg relays: Int){ - val command = StringBuilder("RELAY;") - var binary = 0 - relays.forEach { - if (it in 1..8) { - binary = binary or (1 shl (it - 1)) + if (relays.isNotEmpty()){ + var value : Byte = 0 + for (r in relays){ + if (r in 1..8){ + value = value or (1 shl (r - 1)).toByte() + } } + SendSimpleCommand(byteArrayOf(0x1A, value, 0x61)) } - command.append(binary.toString()).append("@") - SendCommand(command.toString()) - } - - fun ActivateRelay(relays: List){ - val command = StringBuilder("RELAY;") - var binary = 0 - relays.forEach { - if (it in 1..8) { - binary = binary or (1 shl (it - 1)) - } - } - command.append(binary.toString()).append("@") - SendCommand(command.toString()) } /** * Deactivate relay on Barix device */ fun DeactivateRelay(){ - SendCommand("RELAY;0@") + SendSimpleCommand(byteArrayOf(0x1A, 0, 0x61)) + } + + /** + * Send simple command to Barix device + * @param command The command to send + * @return true if successful + */ + fun SendSimpleCommand(command: ByteArray) : Boolean { + try { + if (_tcp !=null){ + if (_tcp!!.isConnected){ + val out = _tcp!!.getOutputStream() + out.write(command) + out.flush() + return true + } else throw Exception("Socket to $ipaddress is not connected") + } else throw Exception("Socket to $ipaddress is null") + } catch (e: Exception) { + if (e.message != null && e.message!!.isNotEmpty()) { + Logger.error { "Failed to send command, message : ${e.message}" } + } + return false + } } /** diff --git a/src/barix/BarixStatus.kt b/src/barix/BarixStatus.kt index 5dbbe51..d666754 100644 --- a/src/barix/BarixStatus.kt +++ b/src/barix/BarixStatus.kt @@ -1,6 +1,6 @@ package barix @Suppress("unused") -data class BarixStatus(val ipaddress: String, val vu: Int, val buffremain: Int, val statusdata: Int){ +data class BarixStatus(val ipaddress: String, val vu: Int, val buffremain: Int, val statusdata: Int, val isBarix: Boolean){ override fun toString(): String { return "BarixStatus(ipaddress='$ipaddress', vu=$vu, buffremain=$buffremain, statusdata=$statusdata)" } diff --git a/src/barix/TCP_Barix_Command_Server.kt b/src/barix/TCP_Barix_Command_Server.kt index 6c01518..46c1fb5 100644 --- a/src/barix/TCP_Barix_Command_Server.kt +++ b/src/barix/TCP_Barix_Command_Server.kt @@ -1,5 +1,6 @@ package barix +import codes.Somecodes.Companion.LitteEndianToInt import codes.Somecodes.Companion.ValidString import kotlinx.coroutines.* import org.tinylog.Logger @@ -15,7 +16,7 @@ class TCP_Barix_Command_Server { lateinit var job: Job private val socketMap = mutableMapOf() - private val regex = """STATUSBARIX;(\d+);(\d+);?(\d)?""" + private val regex = """STATUSBARIX;(\d+);(\d+)(;(\d+))?""" private val pattern = Regex(regex) /** @@ -43,15 +44,25 @@ class TCP_Barix_Command_Server { val din = DataInputStream(socket.getInputStream()) while (isActive) { - val length = ByteArray(4) - din.readFully(length) - val readlength = ByteBuffer.wrap(length).getInt() - //println("Read Length : $readlength") - val bb = ByteArray(readlength) - din.readFully(bb) - // B4A format, 4 bytes di depan adalah size - val str = String(bb) - //println("Received from $key : $str") + + val bb = ByteArray(128) + val readbytes = din.read(bb) + if (readbytes == -1) { + Logger.info { "Connection closed by Streamer Output with IP $key" } + break + } + if (readbytes == 0) continue + var stringlength = 0 + try{ + stringlength = LitteEndianToInt(bb[0], bb[1], bb[2], bb[3]) + if (stringlength<1 || stringlength>bb.size-4) throw Exception("Invalid string length $stringlength") + } catch (ex:Exception){ + Logger.error { "Error reading length from Streamer Output with IP $key, Message : ${ex.message}" } + continue + } + val str = String(bb,4, stringlength).trim() + if (str.isBlank()) continue + if (!str.startsWith("STATUSBARIX")) continue if (ValidString(str)) { // Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$ pattern.find(str)?.let { matchResult -> @@ -60,7 +71,8 @@ class TCP_Barix_Command_Server { socket.inetAddress.hostAddress, vu.toInt(), buffremain.toInt(), - statusdata.toIntOrNull() ?: 0 + statusdata.toIntOrNull() ?: 0, + statusdata.isNullOrEmpty() // barix tidak ada statusdata , Q-AG1 ada ) //Logger.info { "Received valid command from $key : $status" } cb.accept(status) diff --git a/src/codes/Somecodes.kt b/src/codes/Somecodes.kt index 86441a3..40b9994 100644 --- a/src/codes/Somecodes.kt +++ b/src/codes/Somecodes.kt @@ -522,6 +522,22 @@ class Somecodes { return value is String && value.isNotBlank() } + fun LitteEndianToInt(vararg bb: Byte): Int { + var result = 0 + for (i in bb.indices) { + result = result or ((bb[i].toInt() and 0xFF) shl (8 * i)) + } + return result + } + + fun BigEndianToInt(vararg bb: Byte): Int { + var result = 0 + for (i in bb.indices) { + result = result or ((bb[i].toInt() and 0xFF) shl (8 * (bb.size - 1 - i))) + } + return result + } + /** * Check if all strings in a list are valid non-blank strings. * @param values The list of strings to check. diff --git a/src/database/QueuePaging.kt b/src/database/QueuePaging.kt index e26dfa6..9b9fb97 100644 --- a/src/database/QueuePaging.kt +++ b/src/database/QueuePaging.kt @@ -8,4 +8,22 @@ data class QueuePaging(var index: UInt, var Date_Time: String, var Source: Strin override fun toString(): String { return "QueuePaging(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', BroadcastZones='$BroadcastZones')" } + + companion object{ + + /** + * Check if the QueuePaging entry is expired (older than 5 seconds) + * @param qt QueuePaging entry to check + * @return true if expired, false otherwise + */ + fun isExpired(qt: QueuePaging) : Boolean{ + try{ + val t1 = java.time.LocalDateTime.parse(qt.Date_Time, codes.Somecodes.datetimeformat1) + val delta = java.time.Duration.between(t1, java.time.LocalDateTime.now()) + return delta.seconds > 5 + } catch (_: Exception){ + return false + } + } + } } diff --git a/src/database/QueueTable.kt b/src/database/QueueTable.kt index 2aae77f..5a82d5b 100644 --- a/src/database/QueueTable.kt +++ b/src/database/QueueTable.kt @@ -1,5 +1,8 @@ package database +import codes.Somecodes.Companion.datetimeformat1 +import java.time.LocalDateTime + data class QueueTable(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var SB_TAGS: String, var BroadcastZones: String, var Repeat: UInt, var Language: String){ /** @@ -11,4 +14,23 @@ data class QueueTable(var index: UInt, var Date_Time: String, var Source: String override fun toString(): String { return "QueueTable(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', SB_TAGS='$SB_TAGS', BroadcastZones='$BroadcastZones', Repeat=$Repeat, Language='$Language')" } + + companion object{ + + /** + * Check if the QueueTable entry is expired (older than 5 seconds) + * @param qt QueueTable entry to check + * @return true if expired, false otherwise + */ + fun isExpired(qt: QueueTable) : Boolean{ + try{ + val t1 = LocalDateTime.parse(qt.Date_Time, datetimeformat1) + val delta = java.time.Duration.between(t1, LocalDateTime.now()) + // expired if more than 5 seconds + return delta.seconds > 5 + } catch (_: Exception){ + return false + } + } + } }