import audio.AudioPlayer import barix.BarixConnection import barix.TCP_Barix_Command_Server import codes.Somecodes.Companion.Get_ANN_ID import codes.Somecodes.Companion.ValidFile import codes.Somecodes.Companion.dateformat1 import codes.Somecodes.Companion.timeformat2 import com.sun.jna.Platform import content.ContentCache import content.Language import content.ScheduleDay import content.VoiceType import database.MariaDB import database.Messagebank import database.QueuePaging 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 oshi.util.GlobalConfig import web.WebApp import java.time.DayOfWeek import java.time.LocalDate import java.time.LocalTime import kotlin.concurrent.fixedRateTimer fun main() { val version = "0.0.1 (23/09/2025)" val StreamerOutputs : MutableMap = HashMap() if (Platform.isWindows()) { // supaya OSHI bisa mendapatkan CPU usage di Windows seperti di Task Manager GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_CPU_UTILITY, true) } Logger.info{"Starting AAS New Generation version $version"} val audioPlayer = AudioPlayer(44100) // 44100 Hz sampling rate audioPlayer.InitAudio(1) val content = ContentCache() val db = MariaDB() // Coroutine untuk cek Paging Queue dan AAS Queue setiap detik CoroutineScope(Dispatchers.Default).launch { /** * Fungsi untuk cek apakah semua broadcast zone valid * @param bz List of broadcast zone (SoundChannel) * @return true jika semua valid, false jika ada yang tidak valid */ fun AllBroadcastZonesValid(bz: List): Boolean { if (bz.isNotEmpty()){ val validchannels = bz // check apakah tiap zone ada di database broadcast zones .filter { z1 -> db.BroadcastZoneList.find { z2 -> z2.SoundChannel==z1 } != null } // check apakah tiap zone ada di SoundChannelList dan Online .filter { z3 -> StreamerOutputs.any { sc -> sc.value.channel==z3 && sc.value.isOnline() } } // kalau jumlah valid channel sama dengan jumlah broadcast zone, berarti semua valid return validchannels.size==bz.size } return false } /** * Fungsi untuk cek apakah semua broadcast zone idle * @param bz List of broadcast zone (SoundChannel) * @return true jika semua idle, false jika ada yang tidak idle */ fun AllBroadcastZoneIdle(bz: List): Boolean { if (bz.isNotEmpty()){ return bz.all { z1 -> StreamerOutputs.any { sc -> sc.value.channel==z1 && sc.value.isIdle() } } } return false } // dipakai untuk ambil messagebank berdasarkan id val urutan_bahasa = listOf( Language.INDONESIA.name, Language.LOCAL.name, Language.ENGLISH.name, Language.CHINESE.name, Language.JAPANESE.name, Language.ARABIC.name ) // dipakai untuk pilih voice type, bisa diganti via web nanti var selected_voice = VoiceType.VOICE_1.name /** * Fungsi untuk ambil messagebank berdasarkan ANN_ID, diurutkan berdasarkan urutan bahasa di urutan_bahasa * @param id ANN_ID dari messagebank * @return List of Messagebank */ fun Get_MessageBank_by_id(id: Int) : ArrayList{ val mb_list = ArrayList() urutan_bahasa.forEach { lang -> db.MessagebankList.find { mb -> mb.ANN_ID==id.toUInt() && mb.Language==lang && mb.Voice_Type==selected_voice }?.let { mb_list.add(it) } } return mb_list } jobloop@ while (isActive) { delay(1000) // prioritas 1 , habisin queue paging for(it in db.Read_Queue_Paging()){ if (it.BroadcastZones.isNotBlank()){ val zz = it.BroadcastZones.split(";") if (AllBroadcastZonesValid(zz)){ if (AllBroadcastZoneIdle(zz)){ if (it.Source=="PAGING"){ // nama file ada di Message if (ValidFile(it.Message)){ val afi = audioPlayer.LoadAudioFile(it.Message) zz.forEach { z1 -> StreamerOutputs.values.find { it.channel==z1 }?.SendData(afi.bytes, {db.Add_Log("AAS", it) }, {db.Add_Log("AAS", it)} ) } val logmessage = "Broadcast started PAGING with Filename '${it.Message}' to zones: ${it.BroadcastZones}" Logger.info { logmessage} db.Add_Log("AAS", logmessage) db.Delete_Queue_Paging_by_index(it.index) continue@jobloop } else { // file tidak valid, delete from queue paging db.Delete_Queue_Paging_by_index(it.index) db.Add_Log("AAS", "Cancelled paging message with index ${it.index} due to invalid audio file" ) } } else if (it.Source=="SHALAT"){ val ann_id = Get_ANN_ID(it.Message) if (ann_id>0){ Get_MessageBank_by_id(ann_id).forEach { } } else{ // invalid ann_id, delete from queue paging db.Delete_Queue_Paging_by_index(it.index) db.Add_Log("AAS", "Cancelled Shalat message with index ${it.index} due to invalid ANN_ID" ) } } } } else { // ada broadcast zone yang tidak valid, delete from queue paging db.Delete_Queue_Paging_by_index(it.index) db.Add_Log("AAS", "Cancelled paging message with index ${it.index} due to invalid broadcast zone" ) } } else { // invalid broadcast zone, delete from queue paging db.Delete_Queue_Paging_by_index(it.index) db.Add_Log("AAS", "Cancelled paging message with index ${it.index} due to empty broadcast zone") } } // prioritas 2, habisin queue table db.Read_Queue_Table().forEach { if (it.BroadcastZones.isNotEmpty()){ val zz = it.BroadcastZones.split(";") if (AllBroadcastZonesValid(zz)){ if (AllBroadcastZoneIdle(zz)){ if (it.Type=="SOUNDBANK"){ } else if (it.Type=="TIMER"){ } } } else { // ada broadcast zone yang tidak valid, delete from queue table db.Delete_Queue_Table_by_index(it.index) db.Add_Log("AAS", "Cancelled table message with index ${it.index} due to invalid broadcast zone") } } else { // invalid broadcast zone, delete from queue table db.Delete_Queue_Table_by_index(it.index) db.Add_Log("AAS", "Cancelled table message with index ${it.index} due to empty broadcast zone") } } } } // Coroutine untuk cek Schedulebank tiap menit saat detik 00 CoroutineScope(Dispatchers.Default).launch { while (isActive) { delay(1000) val localtime = LocalTime.now() // detik harus 00 if (localtime.second!=0) continue val timestring = timeformat2.format(localtime) val sch = db.SchedulebankList.filter{ it.Time==timestring && it.Enable } // tidak ada schedule dengan time sekarang dan enable=true if (sch.isEmpty()) continue val localdate = LocalDate.now() val ddmmyyyy = dateformat1.format(localdate) // check special date dulu val specialdate = sch.find { it.Day==ddmmyyyy } if (specialdate!=null) { // TODO Masukin ke queue table sebagai schedule special date } // cek weekly schedule val weekly = sch.find { it.Day == when(localdate.dayOfWeek){ DayOfWeek.MONDAY -> ScheduleDay.Monday.name DayOfWeek.TUESDAY -> ScheduleDay.Tuesday.name DayOfWeek.WEDNESDAY -> ScheduleDay.Wednesday.name DayOfWeek.THURSDAY -> ScheduleDay.Thursday.name DayOfWeek.FRIDAY -> ScheduleDay.Friday.name DayOfWeek.SATURDAY -> ScheduleDay.Saturday.name DayOfWeek.SUNDAY -> ScheduleDay.Sunday.name } } if (weekly!=null) { // TODO Masukin ke queue table sebagai schedule weekly } // check daily schedule val daily = sch.find { it.Day == ScheduleDay.Everyday.name } if (daily!=null) { // TODO Masukin ke queue table sebagai schedule daily } } } val web = WebApp( 3030, listOf( Pair("admin", "password"), Pair("user", "password") ), db, StreamerOutputs ) web.Start() val barixserver = TCP_Barix_Command_Server () barixserver.StartTcpServer { cmd -> Logger.info{cmd} val _streamer = StreamerOutputs[cmd.ipaddress] val _sc = db.SoundChannelList.find { it.ip == cmd.ipaddress } if (_streamer==null){ // belum create BarixConnection untuk ipaddress ini Logger.info{"New Streamer Output connection from ${cmd.ipaddress}"} if (_sc!=null){ val _bc = BarixConnection(_sc.index,_sc.channel,cmd.ipaddress) _bc.vu = cmd.vu _bc.bufferRemain = cmd.buffremain _bc.statusData = cmd.statusdata StreamerOutputs[cmd.ipaddress] = _bc } } else { // sudah ada, update data if (_sc !=null && _sc.channel != _streamer.channel) { _streamer.channel = _sc.channel } _streamer.vu = cmd.vu _streamer.bufferRemain = cmd.buffremain _streamer.statusData = cmd.statusdata } } val onlinechecker = fixedRateTimer(name="onlinecheck", initialDelay = 1000, period = 1000) { // cek setiap 1 detik, decrement online counter semua BarixConnection StreamerOutputs.values.forEach { it.decrementOnlineCounter() } } // shutdown hook Runtime.getRuntime().addShutdownHook(Thread { Logger.info{"Shutdown hook called, stopping services..."} barixserver.StopTcpCommand() onlinechecker.cancel() web.Stop() audioPlayer.Close() db.close() Logger.info{"All services stopped, exiting application."} } ) }