import audio.AudioPlayer import audio.ContentCache import audio.TCPReceiver import audio.UDPReceiver import barix.BarixConnection import barix.TCP_Barix_Command_Server import codes.Somecodes import com.sun.jna.Platform import commandServer.TCP_Android_Command_Server import content.Category import content.Language import content.VoiceType import database.Log import database.MariaDB 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 org.tinylog.provider.ProviderRegistry import oshi.util.GlobalConfig import web.WebApp import java.nio.file.Files import java.nio.file.Paths import kotlin.concurrent.fixedRateTimer import kotlin.io.path.absolutePathString lateinit var db: MariaDB lateinit var audioPlayer: AudioPlayer val StreamerOutputs: MutableMap = HashMap() lateinit var udpreceiver: UDPReceiver lateinit var tcpreceiver: TCPReceiver const val version = "0.0.6 (14/10/2025)" // AAS 64 channels const val max_channel = 64 // dipakai untuk pilih voice type, bisa diganti via web nanti var selected_voice = VoiceType.VOICE_1.name // 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 ) val contentCache = ContentCache() /** * Create necessary folders if not exist */ fun folder_preparation(){ // sementara diset begini, nanti pake config file Somecodes.Soundbank_directory = Paths.get("c:\\soundbank") Files.createDirectories(Somecodes.SoundbankResult_directory) Files.createDirectories(Somecodes.PagingResult_directory) Files.createDirectories(Somecodes.Soundbank_directory) Language.entries.forEach { language -> VoiceType.entries.forEach { voice -> Category.entries.forEach { category -> Files.createDirectories(Somecodes.SoundbankDirectory(language, voice, category) ) } } } } /** * Extract necessary wav files from classpath to soundbank directory * and Load them */ fun files_preparation(){ val list = listOf("chimeup.wav", "chimedown.wav", "silence1s.wav", "silencehalf.wav") list.forEach { Somecodes.ExtractFilesFromClassPath("/$it", Somecodes.Soundbank_directory) val pp = Somecodes.Soundbank_directory.resolve(it) if (Files.isRegularFile(pp)){ val afi = audioPlayer.LoadAudioFile(pp.absolutePathString()) if (afi.isValid()){ Logger.info { "Common audio $it loaded from ${pp.toAbsolutePath()}" } val key = it.substring(0, it.length - 4) // buang .wav contentCache.addAudioFile(key, afi) } else { Logger.error { "Failed to load common audio $it from ${pp.toAbsolutePath()}" } } } else { Logger.error { "Common audio $it not found at ${pp.toAbsolutePath()}" } } } } // Application start here fun main() { 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" } folder_preparation() audioPlayer = AudioPlayer(44100) // 44100 Hz sampling rate audioPlayer.InitAudio(1) files_preparation() db = MariaDB() val subcode01 = MainExtension01() // Coroutine untuk cek Paging Queue dan AAS Queue setiap detik CoroutineScope(Dispatchers.Default).launch { while (isActive) { delay(1000) // prioritas 1 , habisin queue paging subcode01.Read_Queue_Paging() // prioritas 2, habisin queue shalat subcode01.Read_Queue_Shalat() // prioritas 3, habisin queue timer subcode01.Read_Queue_Timer() // prioritas 4, habisin queue soundbank subcode01.Read_Queue_Soundbank() } } // Coroutine untuk cek Schedulebank tiap menit saat detik 00 CoroutineScope(Dispatchers.Default).launch { while (isActive) { delay(1000) subcode01.Read_Schedule_Table() } } val web = WebApp( 3030, listOf( Pair("admin", "password"), Pair("user", "password") )) web.Start() udpreceiver = UDPReceiver() if (udpreceiver.Start()) { Logger.info { "UDP Receiver started on port 5002" } } else { Logger.error { "Failed to start UDP Receiver on port 5002" } } tcpreceiver = TCPReceiver() if (tcpreceiver.Start()) { Logger.info { "TCP Receiver started on port 5002" } } else { Logger.error { "Failed to start TCP Receiver on port 5002" } } val androidserver = TCP_Android_Command_Server() androidserver.StartTcpServer(5003){ Logger.info { it } db.logDB.Add(Log.NewLog("ANDROID", it)) } val barixserver = TCP_Barix_Command_Server() barixserver.StartTcpServer { cmd -> //Logger.info { cmd } val _tcp = barixserver.getSocket(cmd.ipaddress) val _streamer = StreamerOutputs[cmd.ipaddress] val _sc = db.soundchannelDB.List.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 _bc.commandsocket = _tcp StreamerOutputs[cmd.ipaddress] = _bc Logger.info { "Created new Streamer Output for channel ${_sc.channel} with IP ${cmd.ipaddress}" } } } 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 // cek apakah koneksi TCP nya ganti if (_streamer.commandsocket == null) { _streamer.commandsocket = _tcp } else { if (_streamer.commandsocket != _tcp) { // ganti koneksi try { _streamer.commandsocket?.close() } catch (ex: Exception) { Logger.error(ex) { "Error closing previous TCP command socket for ${cmd.ipaddress}" } } _streamer.commandsocket = _tcp } } } } val onlinechecker = fixedRateTimer(name = "onlinecheck", initialDelay = 1000, period = 1000) { // cek setiap 1 detik, decrement online counter semua BarixConnection StreamerOutputs.values.forEach { it.decrementOnlineCounter() } } db.Add_Log("AAS"," Application started") // shutdown hook Runtime.getRuntime().addShutdownHook(Thread { db.Add_Log("AAS"," Application stopping") Logger.info { "Shutdown hook called, stopping services..." } barixserver.StopTcpCommand() androidserver.StopTcpCommand() onlinechecker.cancel() web.Stop() udpreceiver.Stop() tcpreceiver.Stop() audioPlayer.Close() db.close() Logger.info { "All services stopped, exiting application." } ProviderRegistry.getLoggingProvider().shutdown() }) }