300 lines
9.8 KiB
Kotlin
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") )
|
|
|
|
|
|
}
|
|
|
|
|