Files
AAS_NewGeneration/src/Main.kt
2025-09-24 16:03:07 +07:00

303 lines
12 KiB
Kotlin

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<String, BarixConnection> = 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<String>): 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<String>): 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<Messagebank>{
val mb_list = ArrayList<Messagebank>()
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."}
} )
}