Files
AAS_NewGeneration/src/Main.kt
2026-01-29 22:58:20 +07:00

300 lines
9.8 KiB
Kotlin

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 codes.configFile
import codes.configKeys
import com.sun.jna.Native
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 securedonglex.DongleChecker
import web.WebApp
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.concurrent.fixedRateTimer
import kotlin.io.path.absolutePathString
import kotlin.io.path.exists
import kotlin.system.exitProcess
lateinit var db: MariaDB
lateinit var audioPlayer: AudioPlayer
val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap()
lateinit var udpreceiver: UDPReceiver
lateinit var tcpreceiver: TCPReceiver
const val version = "0.0.23 (29/01/2026)"
// AAS 64 channels
const val max_channel = 64
// 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")
Somecodes.Soundbank_directory = Paths.get(config.Get(configKeys.SOUNDBANK_DIRECTORY.key))
Somecodes.SoundbankResult_directory = Somecodes.Soundbank_directory.resolve("SoundbankResult")
Somecodes.PagingResult_directory = Somecodes.Soundbank_directory.resolve("PagingResult")
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) )
}
}
}
}
private fun Extract_Libraries() {
// extract from source root folder to current user dir
val libs = listOf("bass", "bassenc", "bassenc_mp3","bassenc_ogg","bassenc_opus","bassmix","bassopus","sdx")
try{
val targetfolder = File(Somecodes.current_directory)
Logger.info {"target to extract libraries : $targetfolder"}
libs.forEach { ff ->
val x = Native.extractFromResourcePath(ff)
val y = System.mapLibraryName(ff)
val z = x.copyTo(targetfolder.resolve(y),overwrite = true)
Logger.info {"Extracted libraries : $z"}
}
} catch (e : Exception){
Logger.error { "Error extracting libraries, msg : ${e.message}" }
}
}
/**
* 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 { ff ->
Somecodes.extractWav(ff)
val fd = Somecodes.Soundbank_directory.resolve(ff)
if (fd.exists()){
Logger.info{"File ${fd.absolutePathString()} found, processing to content cache" }
val afi = audioPlayer.LoadAudioFile(fd.absolutePathString())
if (afi.isValid()){
val key = Somecodes.FilenameWithoutExtension(fd.toFile())
contentCache.addAudioFile(Somecodes.FilenameWithoutExtension(fd.toFile()), afi)
Logger.info{"Loaded file ${fd.absolutePathString()} to content cache as $key" }
} else {
Logger.error{"Failed to load file ${fd.absolutePathString()} to content cache" }
}
} else {
Logger.error{"File ${fd.absolutePathString()} not found after extraction" }
}
}
}
lateinit var config : configFile
//val sdx = DongleChecker()
// 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" }
config = configFile()
folder_preparation()
Extract_Libraries()
// if (!sdx.CheckDongle()){
// Logger.error { "Dongle check failed. Application will exit." }
// exitProcess(1)
// } else {
// sdx.startChecking {
// Logger.error { "Dongle removed. Application will exit." }
// exitProcess(1)
// }
// }
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(
config.Get(configKeys.WEBAPP_PORT.key).toInt(),
listOf(
Pair(config.Get(configKeys.WEBAPP_ADMIN_USERNAME.key), config.Get(configKeys.WEBAPP_ADMIN_PASSWORD.key)),
Pair(config.Get(configKeys.WEBAPP_VIEWER_USERNAME.key), config.Get(configKeys.WEBAPP_VIEWER_PASSWORD.key))
), config)
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 ->
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)
// cmd.vu 0 - 32767, kita convert ke 0 - 100
_bc.vu = ((1.0 * cmd.vu / 32767.0)* 100.0).toInt()
_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
}
// cmd.vu 0 - 32767, kita convert ke 0 - 100
_streamer.vu = ((1.0 * cmd.vu / 32767.0)* 100.0).toInt()
_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()
StreamerOutputs.values.forEach { it.close() }
audioPlayer.Close()
db.close()
//sdx.stopChecking()
Logger.info { "All services stopped, exiting application." }
ProviderRegistry.getLoggingProvider().shutdown()
},"ShutdownHook") )
}