303 lines
12 KiB
Kotlin
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."}
|
|
} )
|
|
|
|
|
|
}
|