From 179085224214c8b2ea30595b57893533a6533bb0 Mon Sep 17 00:00:00 2001 From: rdkartono Date: Sat, 7 Feb 2026 17:23:41 +0700 Subject: [PATCH] commit 07/02/2026 --- html/webpage/assets/css/bss-overrides.css | 4 - html/webpage/assets/js/overview.js | 176 +- html/webpage/overview.html | 4768 ++++++++++++++--- html/webpage/streamerstatus.html | 70 +- src/Main.kt | 24 +- src/MainExtension01.kt | 1735 +++--- src/audio/AudioFileInfo.kt | 35 + src/barix/BarixConnection.kt | 236 +- src/barix/TCP_Barix_Command_Server.kt | 68 +- src/codes/Somecodes.kt | 8 + .../TCP_Android_Command_Server.kt | 5 +- src/database/BroadcastZones.kt | 24 + src/web/StreamerOutputData.kt | 4 +- 13 files changed, 5223 insertions(+), 1934 deletions(-) diff --git a/html/webpage/assets/css/bss-overrides.css b/html/webpage/assets/css/bss-overrides.css index 9f8ae72..abf218c 100644 --- a/html/webpage/assets/css/bss-overrides.css +++ b/html/webpage/assets/css/bss-overrides.css @@ -58,10 +58,6 @@ margin-right: .5rem!important; } -.mb-2 { - margin-bottom: .5rem!important; -} - .mb-3 { margin-bottom: 1rem!important; } diff --git a/html/webpage/assets/js/overview.js b/html/webpage/assets/js/overview.js index 644237d..8043bdc 100644 --- a/html/webpage/assets/js/overview.js +++ b/html/webpage/assets/js/overview.js @@ -25,22 +25,38 @@ * @property {JQuery | null} ip - The jQuery result should be
element. * @property {JQuery | null} buffer - The jQuery result should be
element. * @property {JQuery | null} status - The jQuery result should be

element. + * @property {JQuery | null} filename - The jQuery result should be

element. + * @property {JQuery | null} duration - The jQuery result should be
element. + * @property {JQuery | null} elapsed - The jQuery result should be
element. + * @property {JQuery | null} broadcastzones - The jQuery result should be
element. * @property {JQuery | null} vu - The jQuery result should be element. */ function getCardByIndex(index) { + let cardname = "ch" + index.toString().padStart(2, '0'); let obj = { // title is

element wiht id `streamertitle${index}`, with index as two digit number, e.g. 01, 02, 03 - title: $(`#streamertitle${index.toString().padStart(2, '0')}`), + //title: $(`#streamertitle${index.toString().padStart(2, '0')}`), // ip is

element with id `streamerip${index}`, with index as two digit number, e.g. 01, 02, 03 - ip: $(`#streamerip${index.toString().padStart(2, '0')}`), + //ip: $(`#streamerip${index.toString().padStart(2, '0')}`), // buffer is
element with id `streamerbuffer${index}`, with index as two digit number, e.g. 01, 02, 03 - buffer: $(`#streamerbuffer${index.toString().padStart(2, '0')}`), + //buffer: $(`#streamerbuffer${index.toString().padStart(2, '0')}`), // status is

element with id `streamerstatus${index}`, with index as two digit number, e.g. 01, 02, 03 - status: $(`#streamerstatus${index.toString().padStart(2, '0')}`), + //status: $(`#streamerstatus${index.toString().padStart(2, '0')}`), // vu is element with id `streamervu${index}`, with index as two digit number, e.g. 01, 02, 03 - vu: $(`#streamervu${index.toString().padStart(2, '0')} .progress-bar`), + //vu: $(`#streamervu${index.toString().padStart(2, '0')} .progress-bar`), + card: $(`#${cardname}`), + title: $(`#${cardname} .streamertitle`), + ip: $(`#${cardname} .streamerip`), + buffer: $(`#${cardname} .streamerbuffer`), + status: $(`#${cardname} .streamerstatus`), + vu: $(`#${cardname} .streamervu .progress-bar`), + filename: $(`#${cardname} .streamerfile`), + duration: $(`#${cardname} .streamerduration`), + elapsed: $(`#${cardname} .streamerelapsed`), + broadcastzones: $(`#${cardname} .streamerzones`), } + return obj; } @@ -64,29 +80,71 @@ function UpdateStreamerCard(values) { values = []; } + let visiblilitychanged = false; + for (let i = 1; i <= 64; i++) { - let vv = values.find(v => v.index === i); - let card = getCardByIndex(i); + const vv = values.find(v => v.index === i); + const cardname = "ch" + i.toString().padStart(2, '0'); + const $card = $(`#${cardname}`); + + if (vv) { - // there is value for this index - if (card.title) card.title.text(vv.channel ? vv.channel : `Channel ${i.toString().padStart(2, '0')}`); - if (card.ip) card.ip.text(`IP Address: ${vv.ipaddress ? vv.ipaddress : 'N/A'}`); - if (card.buffer) card.buffer.text(`Buffer: ${vv.bufferRemain !== undefined && vv.bufferRemain !== null ? vv.bufferRemain.toString() : 'N/A'}`); - if (card.status) card.status.text(`Status: ${vv.isPlaying ? 'Playing' : 'Idle'}`); - if (card.vu) { - setProgress(i, card.vu, vv.vu, 100); + // ada data untuk index i + if ($card.length > 0) { + // ada card untuk index i, show card + if ($card.hasClass('d-none')) { + visiblilitychanged = true; + $card.removeClass('d-none'); + $card.closest('.streamercol').removeClass('d-none'); // show the column as well + } + const $title = $(`#${cardname} .streamertitle`); + const $ip = $(`#${cardname} .streamerip`); + const $buffer = $(`#${cardname} .streamerbuffer`); + const $status = $(`#${cardname} .streamerstatus`); + const $vu = $(`#${cardname} .streamervu .progress-bar`); + const $filename = $(`#${cardname} .streamerfile`); + const $duration = $(`#${cardname} .streamerduration`); + const $elapsed = $(`#${cardname} .streamerelapsed`); + const $broadcastzones = $(`#${cardname} .streamerzones`); + + //console.log(`Updating card for index ${i}`, vv); + $title.text(vv.channel ? vv.channel : `Channel ${i.toString().padStart(2, '0')}`); + $ip.text(vv.ipaddress ? vv.ipaddress : 'N/A'); + $buffer.text(vv.bufferRemain !== undefined && vv.bufferRemain !== null ? vv.bufferRemain.toString() : 'N/A'); + $status.text(vv.isPlaying ? 'Playing' : 'Idle'); + setProgress(i, $vu, vv.vu ? vv.vu : 0, 100); + $filename.text(vv.filename ? vv.filename : 'N/A'); + $duration.text(vv.duration ? vv.duration : 'N/A'); + $elapsed.text(vv.elapsed ? vv.elapsed : 'N/A'); + $broadcastzones.text(vv.broadcastzones ? vv.broadcastzones : 'N/A'); } } else { - // no value for this index, disable the card - if (card.title) card.title.text(`Channel ${i.toString().padStart(2, '0')}`); - if (card.ip) card.ip.text(`IP Address: N/A`); - if (card.buffer) card.buffer.text(`Buffer: N/A`); - if (card.status) card.status.text(`Status: Disconnected`); - if (card.vu) { - setProgress(i, card.vu, 0, 100); + // tidak ada data untuk index i, hide card + if ($card.length > 0) { + // ada card untuk index i, hide card + if (!$card.hasClass('d-none')) { + visiblilitychanged = true; + $card.addClass('d-none'); + $card.closest('.streamercol').addClass('d-none'); // hide the column as well + } } } + } + + // hide rows that have all cards hidden + if (visiblilitychanged) { + $('.streamerrow').each(function () { + const $row = $(this); + const visiblecards = $row.find('.streamercard:not(.d-none)'); + if (visiblecards.length === 0) { + $row.addClass('d-none'); + } else { + $row.removeClass('d-none'); + } + }); + } + } /** @@ -428,51 +486,51 @@ $(document).ready(function () { runIntervalJob(); - - - window.addEventListener('ws_connected', () =>{ + + + window.addEventListener('ws_connected', () => { console.log("overview.js ws_connected event triggered"); - runIntervalJob(); + runIntervalJob(); }); - window.addEventListener('ws_disconnected', ()=>{ + window.addEventListener('ws_disconnected', () => { console.log("overview.js ws_disconnected event triggered"); - if (intervaljob1) clearInterval(intervaljob1); - if (intervaljob2) clearInterval(intervaljob2); - intervaljob1 = null; - intervaljob2 = null; + if (intervaljob1) clearInterval(intervaljob1); + if (intervaljob2) clearInterval(intervaljob2); + intervaljob1 = null; + intervaljob2 = null; }); - window.addEventListener('ws_message', ()=>{ + window.addEventListener('ws_message', () => { let rep = event.detail; - let cmd = rep.reply; - let data = rep.data; - if (cmd && cmd.length > 0) { - switch (cmd) { - case "getPagingQueue": - let pq = JSON.parse(data); - window.PagingQueue = []; - if (Array.isArray(pq) && pq.length > 0) { - window.PagingQueue.push(...pq); - } - fill_pagingqueuetablebody(window.PagingQueue); - break; - case "getAASQueue": - let aq = JSON.parse(data); - window.QueueTable = []; - if (Array.isArray(aq) && aq.length > 0) { - window.QueueTable.push(...aq); - } - fill_automaticqueuetablebody(window.QueueTable); - break; - case "getStreamerOutputs": - /** - * @type {StreamerOutputData[]} - */ - let so = JSON.parse(data); - UpdateStreamerCard(so); - break; - } + let cmd = rep.reply; + let data = rep.data; + if (cmd && cmd.length > 0) { + switch (cmd) { + case "getPagingQueue": + let pq = JSON.parse(data); + window.PagingQueue = []; + if (Array.isArray(pq) && pq.length > 0) { + window.PagingQueue.push(...pq); + } + fill_pagingqueuetablebody(window.PagingQueue); + break; + case "getAASQueue": + let aq = JSON.parse(data); + window.QueueTable = []; + if (Array.isArray(aq) && aq.length > 0) { + window.QueueTable.push(...aq); + } + fill_automaticqueuetablebody(window.QueueTable); + break; + case "getStreamerOutputs": + /** + * @type {StreamerOutputData[]} + */ + let so = JSON.parse(data); + UpdateStreamerCard(so); + break; } + } }); $(window).on('beforeunload', function () { diff --git a/html/webpage/overview.html b/html/webpage/overview.html index 2572f7d..c8877a2 100644 --- a/html/webpage/overview.html +++ b/html/webpage/overview.html @@ -31,1249 +31,4577 @@

-
-
-
-
-

Channel 01

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 02

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 03

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 04

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 05

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 06

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 07

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 08

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 09

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 10

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 11

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 12

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 13

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 14

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 15

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 16

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 17

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 18

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 19

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 20

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 21

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 22

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 23

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 24

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 25

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 26

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 27

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 28

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 29

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 30

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 31

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 32

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 33

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 34

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 35

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 36

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 37

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 38

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 39

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 40

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 41

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 42

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 43

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 44

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 45

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 46

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 47

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 48

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 49

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 50

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 51

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 52

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 53

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 54

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 55

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 56

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 57

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 58

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 59

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 60

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-
-

Channel 61

+
+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 62

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 63

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
-
-
-
-

Channel 64

+
+
+
+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
diff --git a/html/webpage/streamerstatus.html b/html/webpage/streamerstatus.html index 02b53e6..ee2e103 100644 --- a/html/webpage/streamerstatus.html +++ b/html/webpage/streamerstatus.html @@ -20,20 +20,72 @@ -
+
-

Channel 01

+

Channel 01

-
-
IP : 192.168.10.10
+
+

IP

-
-
Free : 64KB
+
+

N/A

-

Status : Idle

-
-
50%
+
+
+

Buffer

+
+
+

N/A

+
+
+
+
+

Status

+
+
+

N/A

+
+
+
+
+

File

+
+
+

N/A

+
+
+
+
+

Zones

+
+
+

N/A

+
+
+
+
+

Duration

+
+
+

N/A

+
+
+

Elapsed

+
+
+

N/A

+
+
+
+
+

VU

+
+
+
+
50%
+
+
diff --git a/src/Main.kt b/src/Main.kt index 6918f1c..20fd374 100644 --- a/src/Main.kt +++ b/src/Main.kt @@ -177,13 +177,29 @@ fun main(args: Array) { while (isActive) { delay(1000) // prioritas 1 , habisin queue paging - subcode01.Read_Queue_Paging() + if (subcode01.Read_Queue_Paging()){ + // processing paging, skip selanjutnya + delay(2000) + continue + } // prioritas 2, habisin queue shalat - subcode01.Read_Queue_Shalat() + if (subcode01.Read_Queue_Shalat()){ + // processing shalat, skip selanjutnya + delay(2000) + continue + } // prioritas 3, habisin queue timer - subcode01.Read_Queue_Timer() + if (subcode01.Read_Queue_Timer()){ + // processing timer, skip selanjutnya + delay(2000) + continue + } // prioritas 4, habisin queue soundbank - subcode01.Read_Queue_Soundbank() + if (subcode01.Read_Queue_Soundbank()){ + // processing soundbank, skip selanjutnya + delay(2000) + continue + } } } diff --git a/src/MainExtension01.kt b/src/MainExtension01.kt index e158f6e..8345036 100644 --- a/src/MainExtension01.kt +++ b/src/MainExtension01.kt @@ -1,6 +1,6 @@ import audio.AudioFileInfo -import barix.BarixConnection import codes.Result_GetSoundbankFiles +import codes.Somecodes import codes.Somecodes.Companion.Get_ANN_ID import codes.Somecodes.Companion.IsAlphabethic import codes.Somecodes.Companion.IsNumber @@ -17,7 +17,6 @@ import content.Category import content.Language import content.ScheduleDay import content.VoiceType -import database.BroadcastZones import database.Messagebank import database.QueueTable import database.Soundbank @@ -39,10 +38,19 @@ import java.time.LocalTime class MainExtension01 { data class InvalidZoneDetail(val zonename: String, val reason: String) - class CheckBroadcastZoneResult{ + data class ValidZoneDetail( + val zonename: String, + val soundchanel: String, + val ip: String, + val boxid: String, + val contacts: String + ) + + class CheckBroadcastZoneResult { var allvalid: Boolean = false - var message : String? = null - var invalidzones: MutableList = mutableListOf() + var message: String? = null + var validzones = mutableListOf() + var invalidzones = mutableListOf() } /** @@ -54,39 +62,58 @@ class MainExtension01 { fun AllBroadcastZonesValid(bz: List): CheckBroadcastZoneResult { val result = CheckBroadcastZoneResult() if (bz.isNotEmpty()) { - val validbz = mutableListOf() bz.forEach { zz -> - if (ValidString(zz)){ // string tidak kosong - val findzone = db.broadcastDB.List.find { ValidString(it.description) && ValidString(it.SoundChannel) && zz.equals(it.description, true) } - if (findzone!=null){ // ketemu zona dengan deskripsi sesuai - val findsc = db.soundchannelDB.List.find { findzone.SoundChannel.equals(it.channel, true) && ValidIPV4(it.ip) } - if (findsc!=null){ // ketemu soundchannel dengan channel sesuai dan IP valid + if (ValidString(zz)) { // string tidak kosong + val findzone = db.broadcastDB.List.find { + ValidString(it.description) && ValidString(it.SoundChannel) && zz.equals( + it.description, + true + ) + } + if (findzone != null) { // ketemu zona dengan deskripsi sesuai + val findsc = db.soundchannelDB.List.find { + findzone.SoundChannel.equals( + it.channel, + true + ) && ValidIPV4(it.ip) + } + if (findsc != null) { // ketemu soundchannel dengan channel sesuai dan IP valid // check apakah offline atau online - if (StreamerOutputs.containsKey(findsc.ip)){ + if (StreamerOutputs.containsKey(findsc.ip)) { val bc = StreamerOutputs[findsc.ip] - if (bc!=null && bc.isOnline()){ - validbz.add(findzone) - } else result.invalidzones.add(InvalidZoneDetail(zz,"SoundChannel ${findzone.SoundChannel} with IP ${findsc.ip} is offline") ) - } else result.invalidzones.add(InvalidZoneDetail(zz,"SoundChannel ${findzone.SoundChannel} with IP ${findsc.ip} is not connected in StreamerOutputs") ) - } else result.invalidzones.add(InvalidZoneDetail(zz,"SoundChannel ${findzone.SoundChannel} not found or has invalid IP in SoundChannel table") ) - } else result.invalidzones.add(InvalidZoneDetail(zz,"Zone $zz not found in BroadcastZones table") ) - } else result.invalidzones.add(InvalidZoneDetail(zz,"Invalid broadcast zone string") ) + if (bc != null && bc.isOnline()) { + result.validzones.add( + ValidZoneDetail( + zz, + findzone.SoundChannel, + findsc.ip, + findzone.id, + findzone.bp + ) + ) + } else result.invalidzones.add( + InvalidZoneDetail( + zz, + "SoundChannel ${findzone.SoundChannel} with IP ${findsc.ip} is offline" + ) + ) + } else result.invalidzones.add( + InvalidZoneDetail( + zz, + "SoundChannel ${findzone.SoundChannel} with IP ${findsc.ip} is not connected in StreamerOutputs" + ) + ) + } else result.invalidzones.add( + InvalidZoneDetail( + zz, + "SoundChannel ${findzone.SoundChannel} not found or has invalid IP in SoundChannel table" + ) + ) + } else result.invalidzones.add(InvalidZoneDetail(zz, "Zone $zz not found in BroadcastZones table")) + } else result.invalidzones.add(InvalidZoneDetail(zz, "Invalid broadcast zone string")) } -// db.broadcastDB.List.forEach { xx -> -// if (ValidString(xx.description) && ValidString(xx.SoundChannel)){ -// if (xx.description in bz) { -// db.soundchannelDB.List.forEach { sc -> -// if (sc.channel == xx.SoundChannel){ -// if (ValidIPV4(sc.ip)){ -// validbz.add(xx) -// } -// } -// } -// } -// } else result.invalidzones.add(InvalidZoneDetail(xx.description,"Invalid description or SoundChannel") ) -// } - if (validbz.size == bz.size) { + if (result.validzones.size == bz.size) { result.allvalid = true result.message = "All requested broadcast zones are valid" } else { @@ -104,7 +131,7 @@ class MainExtension01 { * @param bz List of ip address * @return ChannelIdleResult object containing allidle flag and message */ - fun AllStreamerOutputIdle(bz: List) : Boolean { + fun AllStreamerOutputIdle(bz: List): Boolean { if (bz.isNotEmpty()) { val idlebz = mutableSetOf() val missingbz = mutableSetOf() @@ -112,6 +139,7 @@ class MainExtension01 { idlebz.add(ip) missingbz.remove(ip) } + fun add_missing(ip: String) { idlebz.remove(ip) missingbz.add(ip) @@ -128,43 +156,6 @@ class MainExtension01 { } else return false } - /** - * Find BarixConnection by BroadcastZones description - * @param zonename BroadcastZones description - * @return BarixConnection if found, null if not found or invalid - */ - fun Get_Barix_Connection_by_ZoneName(zonename: String) : BarixConnection? { - if (ValidString(zonename)){ - val bz = db.broadcastDB.List.find{ it.description.equals(zonename,true) } - val sc = if (bz!=null) db.soundchannelDB.List.find { it.channel == bz.SoundChannel } else null - val ip = sc?.ip ?: "" - if (ValidIPV4(ip)){ - // ketemu ip-nya - return StreamerOutputs[ip] - - } - } - return null - } - - /** - * find SoundChannel IP from BroadcastZones description - * @param bz List of BroadcastZones description - * @return List of SoundChannel IP - */ - fun BroadcastZones_to_SoundChannel_IP(bz: List) : List{ - val result = mutableListOf() - if (bz.isNotEmpty()) { - val l1 = db.broadcastDB.List.filter { bz.contains(it.description) } - if (l1.size==bz.size){ - l1.forEach { xx -> - val yy = db.soundchannelDB.List.find { it.channel == xx.SoundChannel } - if (yy!=null) result.add(yy.ip) - } - } else Logger.error { "Some requested broadcast zones are not registered in BroadcastZone table" } - } else Logger.error { "No Broadcast Zones to convert to SoundChannel IP" } - return result - } /** * Fungsi untuk ambil messagebank berdasarkan ANN_ID, diurutkan berdasarkan urutan bahasa di urutan_bahasa @@ -178,7 +169,12 @@ class MainExtension01 { if (selected_voice.isEmpty()) selected_voice = VoiceType.VOICE_1.name languages.forEach { lang -> db.messageDB.List - .find { mb -> mb.ANN_ID == id.toUInt() && mb.Language.equals(lang, true) && mb.Voice_Type.equals(selected_voice, true) } + .find { mb -> + mb.ANN_ID == id.toUInt() && mb.Language.equals(lang, true) && mb.Voice_Type.equals( + selected_voice, + true + ) + } ?.let { mb_list.add(it) } @@ -198,18 +194,27 @@ class MainExtension01 { if (sb.isNotEmpty()) { val regex = Regex("[A-Z0-9]") val match = regex.findAll(value) - match.forEach { - mm -> + match.forEach { mm -> if (IsNumber(mm.value)) { - val num =sb.firstOrNull { s1 -> s1.Category.equals(Category.AlphabetNumeric.name, true) && s1.TAG.equals("N${mm.value}", true) } - if (num!=null){ - if (ValidFile(num.Path)){ + val num = sb.firstOrNull { s1 -> + s1.Category.equals( + Category.AlphabetNumeric.name, + true + ) && s1.TAG.equals("N${mm.value}", true) + } + if (num != null) { + if (ValidFile(num.Path)) { result.add(num.Path) } } } else if (IsAlphabethic(mm.value)) { val alp = - sb.firstOrNull { s1 -> s1.Category.equals(Category.AlphabetNumeric.name, true) && s1.TAG.equals(mm.value,true) } + sb.firstOrNull { s1 -> + s1.Category.equals( + Category.AlphabetNumeric.name, + true + ) && s1.TAG.equals(mm.value, true) + } if (alp != null) { if (ValidFile(alp.Path)) { result.add(alp.Path) @@ -224,39 +229,59 @@ class MainExtension01 { return null } - fun Get_Soundbank_Hour_Minute(sb: List, value: Int) : String { - if (value in 0..59){ - val tag = if (value<10) "N0${value}" else "N${value}" - val hm = sb.firstOrNull { it.Category.equals(Category.AlphabetNumeric.name,true) && it.TAG.equals(tag, true) } - if (hm!=null){ + fun Get_Soundbank_Hour_Minute(sb: List, value: Int): String { + if (value in 0..59) { + val tag = if (value < 10) "N0${value}" else "N${value}" + val hm = + sb.firstOrNull { it.Category.equals(Category.AlphabetNumeric.name, true) && it.TAG.equals(tag, true) } + if (hm != null) { if (ValidFile(hm.Path)) return hm.Path } } return "" } - val SoundbankKeywords = listOf("ANN_ID","AL","FLNUM","A_D","I_D","ORIGIN","CITY","SHALAT","PLACES","DESTINATION","ETAD","STANDCODE","GATECODE","REMARK","BCB","PLATNOMOR","REASON","PROCEDURE") + val SoundbankKeywords = listOf( + "ANN_ID", + "AL", + "FLNUM", + "A_D", + "I_D", + "ORIGIN", + "CITY", + "SHALAT", + "PLACES", + "DESTINATION", + "ETAD", + "STANDCODE", + "GATECODE", + "REMARK", + "BCB", + "PLATNOMOR", + "REASON", + "PROCEDURE" + ) /** * Parse soundbank data from string value in format "KEY:VALUE KEY:VALUE ..." * @param value String value to parse * @return Map of key-value pairs if valid, null if invalid */ - fun Get_Soundbank_Data(value: String) : Map? { - if (ValidString(value)){ + fun Get_Soundbank_Data(value: String): Map? { + if (ValidString(value)) { //println("Parsing soundbank data from value: $value") val values = value.split(" ").map { it.trim() }.filter { ValidString(it) } //println("Split into values: $values") - if (values.isNotEmpty()){ + if (values.isNotEmpty()) { val result = mutableMapOf() values.forEach { val separatorindex = it.indexOf(":") - if (separatorindex!=-1){ + if (separatorindex != -1) { // ada separator val key = it.substring(0, separatorindex).trim().uppercase() - val value = it.substring(separatorindex+1).trim().uppercase() + val value = it.substring(separatorindex + 1).trim().uppercase() //println("Got $key : $value") - if (ValidString(key) && ValidString(value)){ + if (ValidString(key) && ValidString(value)) { if (SoundbankKeywords.contains(key)) result[key] = value } } @@ -278,7 +303,7 @@ class MainExtension01 { mb: Messagebank, variables: Map - ) : Result_GetSoundbankFiles { + ): Result_GetSoundbankFiles { val tags = mb.Message_TAGS.split(" ") if (tags.isEmpty()) { return Result_GetSoundbankFiles(false, "No tags found in messagebank id ${mb.ANN_ID}") @@ -287,9 +312,12 @@ class MainExtension01 { // dapatkan soundbank array berdasarkan VoiceType dan Language val sb = db.soundDB.List .filter { it.VoiceType.equals(mb.Voice_Type, true) } - .filter { it.Language.equals(mb.Language, true) } + .filter { it.Language.equals(mb.Language, true) } if (sb.isEmpty()) { - return Result_GetSoundbankFiles(false, "No soundbank found for voice type ${mb.Voice_Type} and language ${mb.Language}") + return Result_GetSoundbankFiles( + false, + "No soundbank found for voice type ${mb.Voice_Type} and language ${mb.Language}" + ) } @@ -301,20 +329,34 @@ class MainExtension01 { val value = variables["AL"].orEmpty() if (ValidString(value)) { val airplane = - sb.firstOrNull { it.Category.equals(Category.Airplane_Name.name,true) && it.TAG.equals(value,true) } + sb.firstOrNull { + it.Category.equals(Category.Airplane_Name.name, true) && it.TAG.equals( + value, + true + ) + } if (airplane != null) { if (ValidFile(airplane.Path)) { files.add(airplane.Path) } else { - return Result_GetSoundbankFiles(false, "Invalid soundbank file ${airplane.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "Invalid soundbank file ${airplane.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "No Airplane_Name found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "No Airplane_Name found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "AIRPLANE_NAME variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "AIRPLANE_NAME variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}" + ) } @@ -324,28 +366,45 @@ class MainExtension01 { val alcode = variables["AL"].orEmpty() val fncode = variables["FLNUM"].orEmpty() if (ValidString(alcode) && ValidString(fncode)) { - val val1 = sb.firstOrNull { it.Category.equals(Category.Airline_Code.name,true) && it.TAG.equals(alcode, true) } + val val1 = sb.firstOrNull { + it.Category.equals(Category.Airline_Code.name, true) && it.TAG.equals( + alcode, + true + ) + } val val2 = Get_Soundbank_AlpabethNumeric(sb, fncode) if (val1 != null) { if (ValidFile(val1.Path)) { files.add(val1.Path) } else { - return Result_GetSoundbankFiles(false, "Invalid soundbank file ${val1.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "Invalid soundbank file ${val1.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "No Airline_Code found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "No Airline_Code found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } if (val2 != null && val2.isNotEmpty()) { files.addAll(val2) } else { - return Result_GetSoundbankFiles(false, "No valid soundbank files found for FLIGHT_NUMBER value '$fncode' for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "No valid soundbank files found for FLIGHT_NUMBER value '$fncode' for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "FLIGHT_NUMBER or AIRLINE_CODE variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "FLIGHT_NUMBER or AIRLINE_CODE variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}" + ) } @@ -383,11 +442,17 @@ class MainExtension01 { } } } else { - return Result_GetSoundbankFiles(false, "PLATNOMOR variable has invalid format for value '$value' in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "PLATNOMOR variable has invalid format for value '$value' in messagebank id ${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "PLATNOMOR variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "PLATNOMOR variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}" + ) } } @@ -396,19 +461,33 @@ class MainExtension01 { val values = variables["CITY"].orEmpty().split(";").map { it.trim() }.filter { ValidString(it) } if (values.isNotEmpty()) { values.forEach { vv -> - val city = sb.firstOrNull { it.Category.equals(Category.City.name,true) && it.TAG.equals(vv,true) } + val city = sb.firstOrNull { + it.Category.equals(Category.City.name, true) && it.TAG.equals( + vv, + true + ) + } if (city != null) { if (ValidFile(city.Path)) { files.add(city.Path) } else { - return Result_GetSoundbankFiles(false, "Invalid soundbank file ${city.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "Invalid soundbank file ${city.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "No City found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "No City found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } } else { - return Result_GetSoundbankFiles(false, "CITY variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "CITY variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}" + ) } } @@ -416,20 +495,34 @@ class MainExtension01 { "[PLACES]" -> { val value = variables["PLACES"].orEmpty() if (ValidString(value)) { - val places = sb.firstOrNull { it.Category.equals(Category.Places.name,true) && it.TAG.equals(value,true) } + val places = sb.firstOrNull { + it.Category.equals(Category.Places.name, true) && it.TAG.equals( + value, + true + ) + } if (places != null) { if (ValidFile(places.Path)) { files.add(places.Path) } else { - return Result_GetSoundbankFiles(false, "Invalid soundbank file ${places.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "Invalid soundbank file ${places.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "No Places found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "No Places found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "PLACES variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "PLACES variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}" + ) } @@ -446,22 +539,31 @@ class MainExtension01 { // valid val hh = Get_Soundbank_Hour_Minute(sb, _h) val mm = Get_Soundbank_Hour_Minute(sb, _m) - if (hh.isNotEmpty() && mm.isNotEmpty()){ + if (hh.isNotEmpty() && mm.isNotEmpty()) { files.add(hh) files.add(mm) } else { - return Result_GetSoundbankFiles(false, "ETAD variable has invalid soundbank for hour or minute for tag $_tag in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "ETAD variable has invalid soundbank for hour or minute for tag $_tag in messagebank id ${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "ETAD variable has invalid hour or minute for tag $_tag in messagebank id ${mb.ANN_ID}, hour must be 0-23 and minute must be 0-59") + return Result_GetSoundbankFiles( + false, + "ETAD variable has invalid hour or minute for tag $_tag in messagebank id ${mb.ANN_ID}, hour must be 0-23 and minute must be 0-59" + ) } } } else { - return Result_GetSoundbankFiles(false, "ETAD variable has invalid format for tag $_tag in messagebank id ${mb.ANN_ID}, expected format HH:MM") + return Result_GetSoundbankFiles( + false, + "ETAD variable has invalid format for tag $_tag in messagebank id ${mb.ANN_ID}, expected format HH:MM" + ) } @@ -470,20 +572,34 @@ class MainExtension01 { "[SHALAT]" -> { val value = variables["SHALAT"].orEmpty() if (ValidString(value)) { - val shalat = sb.firstOrNull { it.Category.equals(Category.Shalat.name,true) && it.TAG.equals(value,true) } + val shalat = sb.firstOrNull { + it.Category.equals(Category.Shalat.name, true) && it.TAG.equals( + value, + true + ) + } if (shalat != null) { if (ValidFile(shalat.Path)) { files.add(shalat.Path) } else { - return Result_GetSoundbankFiles(false, "Invalid soundbank file ${shalat.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "Invalid soundbank file ${shalat.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "No Shalat found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "No Shalat found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "SHALAT variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "SHALAT variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}" + ) } @@ -497,12 +613,15 @@ class MainExtension01 { if (path != null) { files.addAll(path) } else { - return Result_GetSoundbankFiles(false, "BCB variable doesn't have valid soundbank for value '$value' in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "BCB variable doesn't have valid soundbank for value '$value' in messagebank id ${mb.ANN_ID}" + ) } } - "[GATENUMBER]","[GATE]" -> { + "[GATENUMBER]", "[GATE]" -> { // gate number bisa angka saja, misalnya 1,2,3 // atau huruf dan angka, misalnya A1, B2, C3, 1A, 2B, 3C val value = variables["GATECODE"].orEmpty() @@ -514,16 +633,25 @@ class MainExtension01 { if (path != null) { files.addAll(path) } else { - return Result_GetSoundbankFiles(false, "GATENUMBER variable doesn't have valid soundbank for value '$vv' in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "GATENUMBER variable doesn't have valid soundbank for value '$vv' in messagebank id ${mb.ANN_ID}" + ) } } } else { - return Result_GetSoundbankFiles(false, "GATENUMBER variable is empty after split for tag $_tag in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "GATENUMBER variable is empty after split for tag $_tag in messagebank id ${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "GATENUMBER variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "GATENUMBER variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}" + ) } @@ -532,20 +660,34 @@ class MainExtension01 { "[REASON]" -> { val value = variables["REASON"].orEmpty() if (ValidString(value)) { - val reason = sb.firstOrNull { it.Category.equals(Category.Reason.name,true) && it.TAG.equals(value,true) } + val reason = sb.firstOrNull { + it.Category.equals(Category.Reason.name, true) && it.TAG.equals( + value, + true + ) + } if (reason != null) { if (ValidFile(reason.Path)) { files.add(reason.Path) } else { - return Result_GetSoundbankFiles(false, "Invalid soundbank file ${reason.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "Invalid soundbank file ${reason.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "No Reason found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "No Reason found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "REASON variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "REASON variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}" + ) } @@ -554,20 +696,34 @@ class MainExtension01 { "[PROCEDURE]" -> { val value = variables["PROCEDURE"].orEmpty() if (ValidString(value)) { - val procedure = sb.firstOrNull { it.Category.equals(Category.Procedure.name, true) && it.TAG.equals(value, true) } + val procedure = sb.firstOrNull { + it.Category.equals(Category.Procedure.name, true) && it.TAG.equals( + value, + true + ) + } if (procedure != null) { if (ValidFile(procedure.Path)) { files.add(procedure.Path) } else { - return Result_GetSoundbankFiles(false, "Invalid soundbank file ${procedure.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "Invalid soundbank file ${procedure.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "No Procedure found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "No Procedure found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false,"PROCEDURE variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "PROCEDURE variable is missing or empty for tag $_tag in messagebank id ${mb.ANN_ID}" + ) } @@ -575,16 +731,23 @@ class MainExtension01 { else -> { // Phrase - val phrase = sb.firstOrNull { it.Category.equals(Category.Phrase.name,true) && it.TAG.equals(_tag, true) } + val phrase = + sb.firstOrNull { it.Category.equals(Category.Phrase.name, true) && it.TAG.equals(_tag, true) } if (phrase != null) { if (ValidFile(phrase.Path)) { files.add(phrase.Path) } else { - return Result_GetSoundbankFiles(false, "Invalid soundbank file ${phrase.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "Invalid soundbank file ${phrase.Path} for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } } else { - return Result_GetSoundbankFiles(false, "No Phrase found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}") + return Result_GetSoundbankFiles( + false, + "No Phrase found for tag=$_tag voicetype=${mb.Voice_Type} language=${mb.Language} ANN_ID=${mb.ANN_ID}" + ) } @@ -596,919 +759,433 @@ class MainExtension01 { return Result_GetSoundbankFiles(true, "Success", files) } - fun Read_Queue_Paging() { + /** + * Read and process Queue_Paging + * @return true if a paging job is processed and started streaming, false otherwise + */ + fun Read_Queue_Paging(): Boolean { db.queuepagingDB.Get() - val list = db.queuepagingDB.List - .filter { it.Type.equals("PAGING",true) } + val qp = db.queuepagingDB.List.firstOrNull { it.Type.equals("PAGING", true) } + if (qp == null) return false + try { + if (!ValidFile(qp.Message)) throw Exception("Invalid audio file ${qp.Message}") + if (qp.BroadcastZones.isEmpty()) throw Exception("Empty broadcast zones") + val zz = qp.BroadcastZones.split(";", ",").map { it.trim() }.filter { ValidString(it) } + AllBroadcastZonesValid(zz).let { checkresult -> + if (!checkresult.allvalid) { + val reasons = + checkresult.invalidzones.joinToString("; ") { iz -> "Zone '${iz.zonename}': ${iz.reason}" } + throw Exception("Some broadcast zones are invalid: $reasons") + } - list.forEach { qp -> - //println("Processing $qp") - if (qp.BroadcastZones.isNotBlank()){ - if (ValidFile(qp.Message)){ - val zz = qp.BroadcastZones.split(";") - AllBroadcastZonesValid(zz).let{ checkresult -> - if (checkresult.allvalid){ - val ips = BroadcastZones_to_SoundChannel_IP(zz) - if (AllStreamerOutputIdle(ips)){ - val afi = audioPlayer.LoadAudioFile(qp.Message) - if (afi.isValid()){ - // file bisa di load, kirim ke masing-masing Streamer Output by IP address - Activate_Relays(zz) - ips.forEach { ip -> - // send byte array to streamer output - StreamerOutputs[ip]?.SendData(afi.bytes, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) }, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) }) - } + val ips = checkresult.validzones.map { it.ip } + if (!AllStreamerOutputIdle(ips)) return false // kalau enggak semua streamer idle, skip dulu - val logmessage = - "Broadcast started PAGING with Filename '${qp.Message}' to zones: ${qp.BroadcastZones}" - Logger.info { logmessage } - db.logDB.Add("AAS", logmessage) - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - } else { - // file tidak valid, delete from queue paging - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - db.logDB.Add( - "AAS", - "Canceled paging message $qp due to invalid audio file" - ) - Logger.error { "Canceled paging message $qp due to invalid audio file" } + val afi = audioPlayer.LoadAudioFile(qp.Message) + if (afi.isValid()) { + // file bisa di load, kirim ke masing-masing Streamer Output by IP address + ips.forEach { ip -> + // send byte array to streamer output + val relays = Somecodes.StringToListInt(checkresult.validzones.find { it.ip == ip }?.contacts) + StreamerOutputs[ip]?.let { so -> + so.SetAudioFileInfo(afi) + so.AddUsedByBroadcastZone(zz) + so.ActivateRelay(relays) + so.SendData(afi.bytes, cbOK = { + so.SetAudioFileInfo(null) + db.logDB.Add("AAS", it) + }, cbFail = { + so.SetAudioFileInfo(null) + db.logDB.Add("AAS", it) + }, cbPlaying = { isplaying -> + if (!isplaying) { + so.ClearUsedByBroadcastZones() + so.DeactivateRelay() } - } // kalau enggak semua streamer idle, skip dulu - } else { - checkresult.invalidzones.forEach { iz -> - Logger.error { "Cancelled paging message $qp due to invalid broadcast zone '${iz.zonename}': ${iz.reason}" } - } - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - return@forEach + }) } } -// if (AllBroadcastZonesValid(zz)){ -// val ips = BroadcastZones_to_SoundChannel_IP(zz) -// if (AllStreamerOutputIdle(ips)){ -// val afi = audioPlayer.LoadAudioFile(qp.Message) -// if (afi.isValid()){ -// // file bisa di load, kirim ke masing-masing Streamer Output by IP address -// Activate_Relays(zz) -// ips.forEach { ip -> -// // send byte array to streamer output -// StreamerOutputs[ip]?.SendData(afi.bytes, -// { -// Deactivate_Relays(zz) -// db.logDB.Add("AAS", it) }, -// { -// Deactivate_Relays(zz) -// db.logDB.Add("AAS", it) }) -// } -// -// val logmessage = -// "Broadcast started PAGING with Filename '${qp.Message}' to zones: ${qp.BroadcastZones}" -// Logger.info { logmessage } -// db.logDB.Add("AAS", logmessage) -// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) -// db.queuepagingDB.Resort() -// return true -// } else { -// // file tidak valid, delete from queue paging -// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) -// db.queuepagingDB.Resort() -// db.logDB.Add( -// "AAS", -// "Canceled paging message $qp due to invalid audio file" -// ) -// Logger.error { "Canceled paging message $qp due to invalid audio file" } -// } -// } // kalau enggak semua streamer idle, skip dulu -// } else { -// // ada broadcast zone yang tidak valid, delete from queue paging -// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) -// db.queuepagingDB.Resort() -// db.logDB.Add("AAS", "Cancelled paging message $qp due to invalid broadcast zone") -// Logger.error { "Canceled paging message $qp due to invalid broadcast zone" } -// } - } else { - // file tidak valid, delete from queue paging + + val logmessage = + "Broadcast started PAGING with Filename '${qp.Message}' to zones: ${qp.BroadcastZones}" + Logger.info { logmessage } + db.logDB.Add("AAS", logmessage) db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.Resort() - db.logDB.Add( - "AAS", - "Cancelled paging message $qp due to invalid audio file" - ) - Logger.error { "Cancelled paging message $qp due to invalid audio file" } - } - } else { - // invalid broadcast zone, delete from queue paging - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - db.logDB.Add("AAS", "Cancelled paging message $qp due to empty broadcast zone") - Logger.error { "Cancelled paging message $qp due to empty broadcast zone" } + return true + } else throw Exception("Audio file ${qp.Message} is not valid") } + } catch (e: Exception) { + db.queuepagingDB.DeleteByIndex(qp.index.toInt()) + db.queuepagingDB.Resort() + val msg = "Canceled paging message $qp due to exception: ${e.message}" + db.logDB.Add("AAS", msg) + Logger.error { msg } } - } - - fun Read_Queue_Shalat() { - db.queuepagingDB.Get() - val list = db.queuepagingDB.List - .filter { it.Type.equals("SHALAT",true) } - list.forEach { qp -> - //println("Processing $qp") - if (qp.BroadcastZones.isNotBlank()){ - val zz = qp.BroadcastZones.split(";") - AllBroadcastZonesValid(zz).let { checkresult -> - if (checkresult.allvalid){ - val ann_id = Get_ANN_ID(qp.Message) - if (ann_id>0){ - val ips = BroadcastZones_to_SoundChannel_IP(zz) - - if (AllStreamerOutputIdle(ips)){ - val mblist = Get_MessageBank_by_id(ann_id, listOf(Language.INDONESIA.name)) - if (mblist.isNotEmpty()) { - mblist.forEach { mb -> - val result = Get_Soundbank_Files(mb, emptyMap()) - if (result.success){ - val listafi = mutableListOf() - result.files.forEach { filenya -> - val afi = contentCache.getAudioFile(filenya) - if (afi!=null && afi.isValid()){ - listafi.add(afi) - } else { - val afi = audioPlayer.LoadAudioFile(filenya) - if (afi.isValid()) { - listafi.add(afi) - contentCache.addAudioFile(filenya, afi) - } - } - } - val targetfile = SoundbankResult_directory.resolve( - Make_WAV_FileName( - "Shalat", - "" - ) - ).toString() - - val result =audioPlayer.WavWriter( - listafi, - targetfile, true, - ) - db.logDB.Add("AAS", result.message) - if (result.success) { - // file siap broadcast - val targetafi = audioPlayer.LoadAudioFile(targetfile) - if (targetafi.isValid()) { - // activate relays from broadcast zone - Activate_Relays(zz) - ips.forEach { ip -> - // send byte array to streamer output - StreamerOutputs[ip]?.SendData(targetafi.bytes, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) }, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) }) - } - - val logmsg = - "Broadcast started SHALAT message with generated file '$targetfile' to zones: ${qp.BroadcastZones}" - Logger.info { logmsg } - db.logDB.Add("AAS", logmsg) - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - } else { - db.logDB.Add( - "AAS", - "Failed to load generated Shalat WAV file $targetfile" - ) - } - } - } else{ - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - db.logDB.Add("AAS", result.message) - } - } - - - } else { - // tidak ada messagebank dengan ann_id ini, delete from queue paging - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - db.logDB.Add( - "AAS", - "Cancelled Shalat message $qp due to ANN_ID $ann_id not found in Messagebank" - ) - Logger.error { "Cancelled Shalat message $qp due to ANN_ID $ann_id not found in Messagebank" } - } - } // kalau enggak semua streamer idle, skip dulu - - } else { - // invalid ann_id, delete from queue paging - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - db.logDB.Add("AAS", "Cancelled shalat message $qp due to invalid ANN_ID") - Logger.error { "Cancelled shalat message $qp due to invalid ANN_ID" } - } - } else { - checkresult.invalidzones.forEach { iz -> - Logger.error { "Cancelled shalat message $qp due to invalid broadcast zone '${iz.zonename}': ${iz.reason}" } - } - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - } - } -// if (AllBroadcastZonesValid(zz)){ -// val ann_id = Get_ANN_ID(qp.Message) -// if (ann_id>0){ -// val ips = BroadcastZones_to_SoundChannel_IP(zz) -// if (AllStreamerOutputIdle(ips)){ -// val mblist = Get_MessageBank_by_id(ann_id, listOf(Language.INDONESIA.name)) -// if (mblist.isNotEmpty()) { -// mblist.forEach { mb -> -// val result = Get_Soundbank_Files(mb, emptyMap()) -// if (result.success){ -// val listafi = mutableListOf() -// result.files.forEach { filenya -> -// val afi = contentCache.getAudioFile(filenya) -// if (afi!=null && afi.isValid()){ -// listafi.add(afi) -// } else { -// val afi = audioPlayer.LoadAudioFile(filenya) -// if (afi.isValid()) { -// listafi.add(afi) -// contentCache.addAudioFile(filenya, afi) -// } -// } -// } -// val targetfile = SoundbankResult_directory.resolve( -// Make_WAV_FileName( -// "Shalat", -// "" -// ) -// ).toString() -// -// val result =audioPlayer.WavWriter( -// listafi, -// targetfile, true, -// ) -// db.logDB.Add("AAS", result.message) -// if (result.success) { -// // file siap broadcast -// val targetafi = audioPlayer.LoadAudioFile(targetfile) -// if (targetafi.isValid()) { -// // activate relays from broadcast zone -// Activate_Relays(zz) -// ips.forEach { ip -> -// // send byte array to streamer output -// StreamerOutputs[ip]?.SendData(targetafi.bytes, -// { -// Deactivate_Relays(zz) -// db.logDB.Add("AAS", it) }, -// { -// Deactivate_Relays(zz) -// db.logDB.Add("AAS", it) }) -// } -// -// val logmsg = -// "Broadcast started SHALAT message with generated file '$targetfile' to zones: ${qp.BroadcastZones}" -// Logger.info { logmsg } -// db.logDB.Add("AAS", logmsg) -// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) -// db.queuepagingDB.Resort() -// return true -// } else { -// db.logDB.Add( -// "AAS", -// "Failed to load generated Shalat WAV file $targetfile" -// ) -// } -// } -// } else{ -// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) -// db.queuepagingDB.Resort() -// db.logDB.Add("AAS", result.message) -// } -// } -// -// -// } else { -// // tidak ada messagebank dengan ann_id ini, delete from queue paging -// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) -// db.queuepagingDB.Resort() -// db.logDB.Add( -// "AAS", -// "Canceled Shalat message $qp due to ANN_ID $ann_id not found in Messagebank" -// ) -// Logger.error { "Canceled Shalat message $qp due to ANN_ID $ann_id not found in Messagebank" } -// } -// } // kalau enggak semua streamer idle, skip dulu -// -// } else { -// // invalid ann_id, delete from queue paging -// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) -// db.queuepagingDB.Resort() -// db.logDB.Add("AAS", "Cancelled shalat message $qp due to invalid ANN_ID") -// Logger.error { "Canceled shalat message $qp due to invalid ANN_ID" } -// } -// } else { -// // ada broadcast zone yang tidak valid, delete from queue paging -// db.queuepagingDB.DeleteByIndex(qp.index.toInt()) -// db.queuepagingDB.Resort() -// db.logDB.Add("AAS", "Cancelled shalat message $qp due to invalid broadcast zone") -// Logger.error { "Canceled shalat message $qp due to invalid broadcast zone" } -// } - } else { - // invalid broadcast zone, delete from queue paging - db.queuepagingDB.DeleteByIndex(qp.index.toInt()) - db.queuepagingDB.Resort() - db.logDB.Add("AAS", "Cancelled shalat message $qp due to empty broadcast zone") - Logger.error { "Cancelled shalat message $qp due to empty broadcast zone" } - } - } - } - - fun Read_Queue_Timer() { - db.queuetableDB.Get() - val list = db.queuetableDB.List.filter { it.Type.equals("TIMER",true) } - list.forEach { qa -> - //println("Processing $qa") - if (qa.BroadcastZones.isNotEmpty()){ - val zz = qa.BroadcastZones.split(";") - AllBroadcastZonesValid(zz).let { checkresult -> - if (checkresult.allvalid){ - val ips = BroadcastZones_to_SoundChannel_IP(zz) - val ann_id = Get_ANN_ID(qa.SB_TAGS) - if (ann_id>0){ - - if (AllStreamerOutputIdle(ips)){ - val mblist = Get_MessageBank_by_id(ann_id, qa.Language.split(";")) - if (mblist.isNotEmpty()) { - mblist.forEach { - mb -> - val result = Get_Soundbank_Files(mb, emptyMap()) - if (result.success){ - val listafi = mutableListOf() - result.files.forEach { filenya -> - val afi = contentCache.getAudioFile(filenya) - if (afi!=null && afi.isValid()){ - listafi.add(afi) - } else { - val afi = audioPlayer.LoadAudioFile(filenya) - if (afi.isValid()) { - listafi.add(afi) - contentCache.addAudioFile(filenya, afi) - } - } - - } - val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("Timer","")).toString() - val result = audioPlayer.WavWriter(listafi, targetfile, true) - db.logDB.Add("AAS", result.message) - if (result.success){ - // file siap broadcast - val targetafi = audioPlayer.LoadAudioFile(targetfile) - if (targetafi.isValid()) { - // activate relays from broadcast zone - Activate_Relays(zz) - ips.forEach { ip -> - // send byte array to streamer output - StreamerOutputs[ip]?.SendData(targetafi.bytes, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) }, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) }) - } - - val logmsg = - "Broadcast started TIMER message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" - Logger.info { logmsg } - db.logDB.Add("AAS", logmsg) - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - } - } else{ - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add("AAS", result.message) - } - } else { - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add("AAS", result.message) - } - } - - } else { - // tidak ada messagebank dengan ann_id ini, delete from queue table - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add( - "AAS", - "Cancelled TIMER $qa due to ANN_ID $ann_id not found in Messagebank" - ) - Logger.error { "Cancelled TIMER $qa due to ANN_ID $ann_id not found in Messagebank" } - } - } // kalau enggak semua streamer idle, skip dulu - - } else { - // invalid ann_id, delete from queue table - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add("AAS", "Cancelled TIMER message $qa due to invalid ANN_ID") - Logger.error { "Cancelled TIMER message $qa due to invalid ANN_ID" } - } - } else { - checkresult.invalidzones.forEach { iz -> - Logger.error { "Cancelled TIMER message $qa due to invalid broadcast zone '${iz.zonename}': ${iz.reason}" } - } - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - return@forEach - } - } -// if (AllBroadcastZonesValid(zz)){ -// val ips = BroadcastZones_to_SoundChannel_IP(zz) -// val ann_id = Get_ANN_ID(qa.SB_TAGS) -// if (ann_id>0){ -// if (AllStreamerOutputIdle(ips)){ -// val mblist = Get_MessageBank_by_id(ann_id, qa.Language.split(";")) -// if (mblist.isNotEmpty()) { -// mblist.forEach { -// mb -> -// val result = Get_Soundbank_Files(mb, emptyMap()) -// if (result.success){ -// val listafi = mutableListOf() -// result.files.forEach { filenya -> -// val afi = contentCache.getAudioFile(filenya) -// if (afi!=null && afi.isValid()){ -// listafi.add(afi) -// } else { -// val afi = audioPlayer.LoadAudioFile(filenya) -// if (afi.isValid()) { -// listafi.add(afi) -// contentCache.addAudioFile(filenya, afi) -// } -// } -// -// } -// val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("Timer","")).toString() -// val result = audioPlayer.WavWriter(listafi, targetfile, true) -// db.logDB.Add("AAS", result.message) -// if (result.success){ -// // file siap broadcast -// val targetafi = audioPlayer.LoadAudioFile(targetfile) -// if (targetafi.isValid()) { -// // activate relays from broadcast zone -// Activate_Relays(zz) -// ips.forEach { ip -> -// // send byte array to streamer output -// StreamerOutputs[ip]?.SendData(targetafi.bytes, -// { -// Deactivate_Relays(zz) -// db.logDB.Add("AAS", it) }, -// { -// Deactivate_Relays(zz) -// db.logDB.Add("AAS", it) }) -// } -// -// val logmsg = -// "Broadcast started TIMER message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" -// Logger.info { logmsg } -// db.logDB.Add("AAS", logmsg) -// db.queuetableDB.DeleteByIndex(qa.index.toInt()) -// db.queuetableDB.Resort() -// return true -// } -// } else{ -// db.queuetableDB.DeleteByIndex(qa.index.toInt()) -// db.queuetableDB.Resort() -// db.logDB.Add("AAS", result.message) -// } -// } else { -// db.queuetableDB.DeleteByIndex(qa.index.toInt()) -// db.queuetableDB.Resort() -// db.logDB.Add("AAS", result.message) -// } -// } -// -// } else { -// // tidak ada messagebank dengan ann_id ini, delete from queue table -// db.queuetableDB.DeleteByIndex(qa.index.toInt()) -// db.queuetableDB.Resort() -// db.logDB.Add( -// "AAS", -// "Cancelled TIMER $qa due to ANN_ID $ann_id not found in Messagebank" -// ) -// Logger.error { "Cancelled TIMER $qa due to ANN_ID $ann_id not found in Messagebank" } -// } -// } // kalau enggak semua streamer idle, skip dulu -// -// } else { -// // invalid ann_id, delete from queue table -// db.queuetableDB.DeleteByIndex(qa.index.toInt()) -// db.queuetableDB.Resort() -// db.logDB.Add("AAS", "Cancelled TIMER message $qa due to invalid ANN_ID") -// Logger.error { "Cancelled TIMER message $qa due to invalid ANN_ID" } -// } -// } else { -// // ada broadcast zone yang tidak valid, delete from queue table -// db.queuetableDB.DeleteByIndex(qa.index.toInt()) -// db.queuetableDB.Resort() -// db.logDB.Add("AAS", "Cancelled TIMER message $qa due to invalid broadcast zone") -// Logger.error {"Cancelled TIMER message $qa due to invalid broadcast zone"} -// } - } else { - // invalid broadcast zone, delete from queue table - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add("AAS", "Cancelled TIMER message $qa due to empty broadcast zone") - Logger.error { "Cancelled TIMER message $qa due to empty broadcast zone" } - } - } - } - - fun Activate_Relays(zz: List){ - zz.forEach { zonename -> - val bz = db.broadcastDB.List.find { it.description.equals(zonename,true) } - if (bz!=null){ - val bc = Get_Barix_Connection_by_ZoneName(zonename) - if (bc!=null){ - // ada barix connection - val relays = bz.bp.split(",") - .map { it.trim()} - .filter { it.isNotBlank() } - .filter { IsNumber(it) && it.toInt() in 1..8 } - .map{ it.toInt() } - if (relays.isNotEmpty()) bc.ActivateRelay(relays) - - } - } - - } - } - - fun Deactivate_Relays(zz: List){ - zz.forEach { zonename -> - val bz = db.broadcastDB.List.find { it.description.equals(zonename,true) } - if (bz!=null){ - val bc = Get_Barix_Connection_by_ZoneName(zonename) - bc?.DeactivateRelay() - } - - } + return false } /** - * Read and process Queue_Table table for SOUNDBANK type messages. + * Read and process Queue_Paging for SHALAT type + * @return true if a shalat job is processed and started streaming, false otherwise */ - fun Read_Queue_Soundbank() { - db.queuetableDB.Get() - val list = db.queuetableDB.List.filter { it.Type.equals("SOUNDBANK",true) } - list.forEach { qa -> - //println("Processing $qa") - if (qa.BroadcastZones.isNotEmpty()){ - val zz = qa.BroadcastZones.split(";") - AllBroadcastZonesValid(zz).let { checkresult -> - if (checkresult.allvalid){ - val ips = BroadcastZones_to_SoundChannel_IP(zz) + fun Read_Queue_Shalat(): Boolean { + db.queuepagingDB.Get() + val qp = db.queuepagingDB.List.firstOrNull { it.Type.equals("SHALAT", true) } + if (qp == null) return false + try { + val ann_id = Get_ANN_ID(qp.Message) + if (ann_id <= 0) throw Exception("Invalid ANN_ID") + val mblist = Get_MessageBank_by_id(ann_id, listOf(Language.INDONESIA.name)) + if (mblist.isEmpty()) throw Exception("ANN_ID $ann_id not found in Messagebank") + if (qp.BroadcastZones.isEmpty()) throw Exception("Empty broadcast zones") + val zz = qp.BroadcastZones.split(";", ",").map { it.trim() }.filter { ValidString(it) } + AllBroadcastZonesValid(zz).let { checkresult -> + if (!checkresult.allvalid) { + val reasons = + checkresult.invalidzones.joinToString("; ") { iz -> "Zone '${iz.zonename}': ${iz.reason}" } + throw Exception("Some broadcast zones are invalid: $reasons") + } + val ips = checkresult.validzones.map { it.ip } + if (!AllStreamerOutputIdle(ips)) return false // kalau enggak semua streamer idle, skip dulu - if (AllStreamerOutputIdle(ips)) { - val variables = Get_Soundbank_Data(qa.SB_TAGS) - val languages = qa.Language.split(";") - // cek apakah ANN_ID ada di SB_TAGS - if (variables!=null){ - Logger.info{"SB_TAGS variables: $variables"} - } else{ - Logger.info{"No SB_TAGS variables found"} - } - - var ann_id = variables?.get("ANN_ID")?.toIntOrNull() ?: 0 - - if (ann_id==0){ - // not available from variables, try to get from Message column - // ada ini, karena protokol FIS dulu tidak ada ANN_ID tapi pake Remark - val remark = variables?.get("REMARK").orEmpty() - db.logDB.Add("AAS", "Trying to get ANN_ID from REMARK field: $remark") - Logger.info{ "Trying to get ANN_ID from REMARK field: $remark" } - when(remark){ - "GOP" -> { - val remarkMsg = config.Get(configKeys.REMARK_GOP.key) - Logger.info{"Remark message for GOP: $remarkMsg"} - ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 - } - "GBD" ->{ - val remarkMsg = config.Get(configKeys.REMARK_GBD.key) - Logger.info{"Remark message for GBD: $remarkMsg"} - ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 - } - "GFC" ->{ - val remarkMsg = config.Get(configKeys.REMARK_GFC.key) - Logger.info{"Remark message for GFC: $remarkMsg"} - ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 - } - "FLD" ->{ - val remarkMsg = config.Get(configKeys.REMARK_FLD.key) - Logger.info{"Remark message for FLD: $remarkMsg"} - ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 - } - } - Logger.info{"Found ANN_ID from REMARK field: $ann_id" } - db.logDB.Add("AAS", "Found ANN_ID from REMARK field: $ann_id") - } else { - db.logDB.Add("AAS", "Found ANN_ID from SB_TAGS variables: $ann_id") - Logger.info{ "Found ANN_ID from SB_TAGS variables: $ann_id" } - } - - // recheck again - if (ann_id == 0) { - db.logDB.Add( - "AAS", - "Cancelled SOUNDBANK message $qa due to missing or invalid ANN_ID in SB_TAGS" - ) - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - return@forEach - } - // sampe sini punya ann_id valid - - val mblist = Get_MessageBank_by_id(ann_id, languages) - if (mblist.isNotEmpty()) { - val listafi = mutableListOf() - - mblist.forEach { mb -> - val result = Get_Soundbank_Files(mb, variables ?: emptyMap()) - if (result.success){ - result.files.forEach { filenya -> - val afi = contentCache.getAudioFile(filenya) - if (afi!=null && afi.isValid()){ - listafi.add(afi) - } else { - val afi = audioPlayer.LoadAudioFile(filenya) - if (afi.isValid()) { - listafi.add(afi) - contentCache.addAudioFile(filenya, afi) - } - } - - } - } else { - Logger.error{"Failed to get soundbank files for ANN_ID $ann_id: ${result.message}"} - db.logDB.Add("AAS", result.message) - - } - - } - - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - - if (listafi.isNotEmpty()){ - - // bikin nama file ada postifx nya dari SB_TAGS, tapi spasi diganti underscore - // dan titik dua diganti dash - val postfix = qa.SB_TAGS.replace(" ","_").replace(":","-") - val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("${qa.Source}_${qa.Type}",postfix)).toString() - //println("Writing to target WAV file: $targetfile") - val result = audioPlayer.WavWriter(listafi, targetfile, true) - if (result.success){ - // file siap broadcast - //println("Successfully wrote WAV file: $targetfile") - val targetafi = audioPlayer.LoadAudioFile(targetfile) - if (targetafi.isValid()) { - // activate relays from broadcast zone - Activate_Relays(zz) - ips.forEach { ip -> - // send byte array to streamer output - StreamerOutputs[ip]?.SendData(targetafi.bytes, - { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) - - }, { - Deactivate_Relays(zz) - db.logDB.Add("AAS", it) - }) - } - - val logmsg = - "Broadcast started SOUNDBANK message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" - Logger.info { logmsg } - db.logDB.Add("AAS", logmsg) - - } - } else { - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add("AAS", result.message) - } - - } else { - db.logDB.Add( - "AAS", - "Cancelled SOUNDBANK message $qa due to no valid soundbank files generated for ANN_ID $ann_id" - ) - Logger.error { "Cancelled SOUNDBANK message $qa due to no valid soundbank files generated for ANN_ID $ann_id" } - } - } else { - // tidak ada messagebank dengan ann_id ini, delete from queue table - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - db.logDB.Add( - "AAS", - "Cancelled SOUNDBANK message $qa due to ANN_ID $ann_id not found in Messagebank" - ) - Logger.error { "Cancelled SOUNDBANK message $qa due to ANN_ID $ann_id not found in Messagebank" } - return@forEach - } + val listafi = mutableListOf() + mblist.forEach { mb -> + val result = Get_Soundbank_Files(mb, emptyMap()) + if (!result.success) throw Exception(result.message) + result.files.forEach { filenya -> + val afi = contentCache.getAudioFile(filenya) + if (afi != null && afi.isValid()) { + listafi.add(afi) + } else { + val afi = audioPlayer.LoadAudioFile(filenya) + if (afi.isValid()) { + listafi.add(afi) + contentCache.addAudioFile(filenya, afi) + } else throw Exception("Failed to load soundbank file $filenya") } - } else { - checkresult.invalidzones.forEach { iz -> - Logger.error { "Cancelled SOUNDBANK message $qa due to invalid broadcast zone '${iz.zonename}': ${iz.reason}" } - } - db.queuetableDB.DeleteByIndex(qa.index.toInt()) - db.queuetableDB.Resort() - return@forEach + } + + } + if (listafi.isEmpty()) throw Exception("No audio found in Messagebank") + val targetfile = SoundbankResult_directory.resolve( + Make_WAV_FileName( + "Shalat", + "" + ) + ).toString() + + val result = audioPlayer.WavWriter( + listafi, + targetfile, true, + ) + db.logDB.Add("AAS", result.message) + if (!result.success) { + throw Exception(result.message) + } + + val targetafi = audioPlayer.LoadAudioFile(targetfile) + if (!targetafi.isValid()) throw Exception("Failed to load generated Shalat WAV file $targetfile") + // file siap broadcast + ips.forEach { ip -> + // send byte array to streamer output + val relay = Somecodes.StringToListInt(checkresult.validzones.find { it.ip == ip }?.contacts) + StreamerOutputs[ip]?.let { so -> + so.SetAudioFileInfo(targetafi) + so.AddUsedByBroadcastZone(zz) + so.ActivateRelay(relay) + so.SendData( + targetafi.bytes, + { + so.SetAudioFileInfo(null) + db.logDB.Add("AAS", it) + }, + { + so.SetAudioFileInfo(null) + db.logDB.Add("AAS", it) + }, cbPlaying = { isplaying -> + if (!isplaying) { + so.ClearUsedByBroadcastZones() + so.DeactivateRelay() + } + }) } } -// if (AllBroadcastZonesValid(zz)){ -// val ips = BroadcastZones_to_SoundChannel_IP(zz) -// if (AllStreamerOutputIdle(ips)) { -// val variables = Get_Soundbank_Data(qa.SB_TAGS) -// val languages = qa.Language.split(";") -// // cek apakah ANN_ID ada di SB_TAGS -// if (variables!=null){ -// Logger.info{"SB_TAGS variables: $variables"} -// } else{ -// Logger.info{"No SB_TAGS variables found"} -// } -// -// var ann_id = variables?.get("ANN_ID")?.toIntOrNull() ?: 0 -// -// if (ann_id==0){ -// // not available from variables, try to get from Message column -// // ada ini, karena protokol FIS dulu tidak ada ANN_ID tapi pake Remark -// val remark = variables?.get("REMARK").orEmpty() -// db.logDB.Add("AAS", "Trying to get ANN_ID from REMARK field: $remark") -// Logger.info{ "Trying to get ANN_ID from REMARK field: $remark" } -// when(remark){ -// "GOP" -> { -// val remarkMsg = config.Get(configKeys.REMARK_GOP.key) -// Logger.info{"Remark message for GOP: $remarkMsg"} -// ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 -// } -// "GBD" ->{ -// val remarkMsg = config.Get(configKeys.REMARK_GBD.key) -// Logger.info{"Remark message for GBD: $remarkMsg"} -// ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 -// } -// "GFC" ->{ -// val remarkMsg = config.Get(configKeys.REMARK_GFC.key) -// Logger.info{"Remark message for GFC: $remarkMsg"} -// ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 -// } -// "FLD" ->{ -// val remarkMsg = config.Get(configKeys.REMARK_FLD.key) -// Logger.info{"Remark message for FLD: $remarkMsg"} -// ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 -// } -// } -// Logger.info{"Found ANN_ID from REMARK field: $ann_id" } -// db.logDB.Add("AAS", "Found ANN_ID from REMARK field: $ann_id") -// } else { -// db.logDB.Add("AAS", "Found ANN_ID from SB_TAGS variables: $ann_id") -// Logger.info{ "Found ANN_ID from SB_TAGS variables: $ann_id" } -// } -// -// // recheck again -// if (ann_id == 0) { -// db.logDB.Add( -// "AAS", -// "Cancelled SOUNDBANK message $qa due to missing or invalid ANN_ID in SB_TAGS" -// ) -// db.queuetableDB.DeleteByIndex(qa.index.toInt()) -// db.queuetableDB.Resort() -// return@forEach -// } -// // sampe sini punya ann_id valid -// -// val mblist = Get_MessageBank_by_id(ann_id, languages) -// if (mblist.isNotEmpty()) { -// val listafi = mutableListOf() -// -// mblist.forEach { mb -> -// val result = Get_Soundbank_Files(mb, variables ?: emptyMap()) -// if (result.success){ -// result.files.forEach { filenya -> -// val afi = contentCache.getAudioFile(filenya) -// if (afi!=null && afi.isValid()){ -// listafi.add(afi) -// } else { -// val afi = audioPlayer.LoadAudioFile(filenya) -// if (afi.isValid()) { -// listafi.add(afi) -// contentCache.addAudioFile(filenya, afi) -// } -// } -// -// } -// } else { -// Logger.error{"Failed to get soundbank files for ANN_ID $ann_id: ${result.message}"} -// db.logDB.Add("AAS", result.message) -// -// } -// -// } -// -// db.queuetableDB.DeleteByIndex(qa.index.toInt()) -// db.queuetableDB.Resort() -// -// if (listafi.isNotEmpty()){ -// -// // bikin nama file ada postifx nya dari SB_TAGS, tapi spasi diganti underscore -// // dan titik dua diganti dash -// val postfix = qa.SB_TAGS.replace(" ","_").replace(":","-") -// val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("${qa.Source}_${qa.Type}",postfix)).toString() -// //println("Writing to target WAV file: $targetfile") -// val result = audioPlayer.WavWriter(listafi, targetfile, true) -// if (result.success){ -// // file siap broadcast -// //println("Successfully wrote WAV file: $targetfile") -// val targetafi = audioPlayer.LoadAudioFile(targetfile) -// if (targetafi.isValid()) { -// // activate relays from broadcast zone -// Activate_Relays(zz) -// ips.forEach { ip -> -// // send byte array to streamer output -// StreamerOutputs[ip]?.SendData(targetafi.bytes, -// { -// Deactivate_Relays(zz) -// db.logDB.Add("AAS", it) -// -// }, { -// Deactivate_Relays(zz) -// db.logDB.Add("AAS", it) -// }) -// } -// -// val logmsg = -// "Broadcast started SOUNDBANK message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" -// Logger.info { logmsg } -// db.logDB.Add("AAS", logmsg) -// -// return true -// } -// } else { -// db.queuetableDB.DeleteByIndex(qa.index.toInt()) -// db.queuetableDB.Resort() -// db.logDB.Add("AAS", result.message) -// } -// -// } else { -// db.logDB.Add( -// "AAS", -// "Cancelled SOUNDBANK message $qa due to no valid soundbank files generated for ANN_ID $ann_id" -// ) -// Logger.error { "Cancelled SOUNDBANK message $qa due to no valid soundbank files generated for ANN_ID $ann_id" } -// } -// } else { -// // tidak ada messagebank dengan ann_id ini, delete from queue table -// db.queuetableDB.DeleteByIndex(qa.index.toInt()) -// db.queuetableDB.Resort() -// db.logDB.Add( -// "AAS", -// "Cancelled SOUNDBANK message $qa due to ANN_ID $ann_id not found in Messagebank" -// ) -// Logger.error { "Cancelled SOUNDBANK message $qa due to ANN_ID $ann_id not found in Messagebank" } -// return@forEach -// } -// -// } -// -// } else { -// // ada broadcast zone yang tidak valid, delete from queue table -// db.queuetableDB.DeleteByIndex(qa.index.toInt()) -// db.queuetableDB.Resort() -// db.logDB.Add("AAS", "Cancelled SOUNDBANK message $qa due to invalid broadcast zone") -// Logger.error { "Cancelled SOUNDBANK message $qa due to invalid broadcast zone" } -// } - } else { - // invalid broadcast zone, delete from queue table + + val logmsg = + "Broadcast started SHALAT message with generated file '$targetfile' to zones: ${qp.BroadcastZones}" + Logger.info { logmsg } + db.logDB.Add("AAS", logmsg) + db.queuepagingDB.DeleteByIndex(qp.index.toInt()) + db.queuepagingDB.Resort() + return true + + } + } catch (e: Exception) { + db.queuepagingDB.DeleteByIndex(qp.index.toInt()) + db.queuepagingDB.Resort() + val msg = "Canceled shalat message $qp due to exception: ${e.message}" + db.logDB.Add("AAS", msg) + Logger.error { msg } + } + return false + } + + /** + * Read and process Queue_Table table for TIMER type messages. + * @return true if a timer job is processed and started streaming, false otherwise + */ + fun Read_Queue_Timer(): Boolean { + db.queuetableDB.Get() + val qa = db.queuetableDB.List.firstOrNull { it.Type.equals("TIMER", true) } + if (qa == null) return false + try { + val ann_id = Get_ANN_ID(qa.SB_TAGS) + if (ann_id <= 0) throw Exception("Invalid ANN_ID") + val mblist = Get_MessageBank_by_id(ann_id, qa.Language.split(";")) + if (mblist.isEmpty()) throw Exception("ANN_ID $ann_id not found in Messagebank") + if (qa.BroadcastZones.isEmpty()) throw Exception("Empty broadcast zones") + val zz = qa.BroadcastZones.split(";") + AllBroadcastZonesValid(zz).let { checkresult -> + if (!checkresult.allvalid) { + val reasons = + checkresult.invalidzones.joinToString("; ") { iz -> "Zone '${iz.zonename}': ${iz.reason}" } + throw Exception("Some broadcast zones are invalid: $reasons") + } + val ips = checkresult.validzones.map { it.ip } + if (!AllStreamerOutputIdle(ips)) return false // kalau enggak semua streamer idle, skip dulu + + val listafi = mutableListOf() + mblist.forEach { mb -> + val result = Get_Soundbank_Files(mb, emptyMap()) + if (!result.success) throw Exception(result.message) + + result.files.forEach { filenya -> + val afi = contentCache.getAudioFile(filenya) + if (afi != null && afi.isValid()) { + listafi.add(afi) + } else { + val afi = audioPlayer.LoadAudioFile(filenya) + if (afi.isValid()) { + listafi.add(afi) + contentCache.addAudioFile(filenya, afi) + } else throw Exception("Failed to load soundbank file $filenya") + } + } + + } + if (listafi.isEmpty()) throw Exception("No audio found in Messagebank") + + val targetfile = SoundbankResult_directory.resolve(Make_WAV_FileName("Timer", "")).toString() + val result = audioPlayer.WavWriter(listafi, targetfile, true) + if (!result.success) throw Exception(result.message) + val targetafi = audioPlayer.LoadAudioFile(targetfile) + if (!targetafi.isValid()) throw Exception("Failed to load generated Timer WAV file $targetfile") + // file siap broadcast + // activate relays from broadcast zone + ips.forEach { ip -> + // send byte array to streamer output + val relays = Somecodes.StringToListInt(checkresult.validzones.find { it.ip == ip }?.contacts) + StreamerOutputs[ip]?.let { so -> + so.ActivateRelay(relays) + so.SetAudioFileInfo(targetafi) + so.AddUsedByBroadcastZone(zz) + so.SendData( + targetafi.bytes, + { + so.SetAudioFileInfo(null) + so.ClearUsedByBroadcastZones() + so.DeactivateRelay() + db.logDB.Add("AAS", it) + }, + { + so.SetAudioFileInfo(null) + so.ClearUsedByBroadcastZones() + so.DeactivateRelay() + db.logDB.Add("AAS", it) + }, cbPlaying = { isplaying -> + if (!isplaying) { + so.ClearUsedByBroadcastZones() + so.DeactivateRelay() + } + }) + } + } + val logmsg = + "Broadcast started TIMER message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" + Logger.info { logmsg } + db.logDB.Add("AAS", logmsg) db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.Resort() - db.logDB.Add("AAS", "Cancelled SOUNDBANK message $qa due to empty broadcast zone") - Logger.error { "Cancelled SOUNDBANK message $qa due to empty broadcast zone" } + return true + } + } catch (e: Exception) { + db.queuetableDB.DeleteByIndex(qa.index.toInt()) + db.queuetableDB.Resort() + val msg = "Canceled TIMER message $qa due to exception: ${e.message}" + db.logDB.Add("AAS", msg) + Logger.error { msg } } + return false + } + + + /** + * Read and process Queue_Table table for SOUNDBANK type messages. + * @return true if a soundbank job is processed and started streaming, false otherwise + */ + fun Read_Queue_Soundbank(): Boolean { + db.queuetableDB.Get() + val qa = db.queuetableDB.List.firstOrNull { it.Type.equals("SOUNDBANK", true) } + if (qa == null) return false + //println("Processing $qa") + try { + if (qa.BroadcastZones.isEmpty()) throw Exception("Empty broadcast zones") + val zz = qa.BroadcastZones.split(";") + + AllBroadcastZonesValid(zz).let { checkresult -> + if (!checkresult.allvalid) { + val reasons = + checkresult.invalidzones.joinToString("; ") { iz -> "Zone '${iz.zonename}': ${iz.reason}" } + throw Exception("Some broadcast zones are invalid: $reasons") + } + + val ips = checkresult.validzones.map { it.ip } + if (!AllStreamerOutputIdle(ips)) return false // kalau enggak semua streamer idle, skip dulu + + + val variables = Get_Soundbank_Data(qa.SB_TAGS) + val languages = qa.Language.split(";") + // cek apakah ANN_ID ada di SB_TAGS + if (variables != null) { + Logger.info { "SB_TAGS variables: $variables" } + } else { + Logger.info { "No SB_TAGS variables found" } + } + + var ann_id = variables?.get("ANN_ID")?.toIntOrNull() ?: 0 + + if (ann_id == 0) { + // not available from variables, try to get from Message column + // ada ini, karena protokol FIS dulu tidak ada ANN_ID tapi pake Remark + val remark = variables?.get("REMARK").orEmpty() + db.logDB.Add("AAS", "Trying to get ANN_ID from REMARK field: $remark") + Logger.info { "Trying to get ANN_ID from REMARK field: $remark" } + when (remark) { + "GOP" -> { + val remarkMsg = config.Get(configKeys.REMARK_GOP.key) + Logger.info { "Remark message for GOP: $remarkMsg" } + ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 + } + + "GBD" -> { + val remarkMsg = config.Get(configKeys.REMARK_GBD.key) + Logger.info { "Remark message for GBD: $remarkMsg" } + ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 + } + + "GFC" -> { + val remarkMsg = config.Get(configKeys.REMARK_GFC.key) + Logger.info { "Remark message for GFC: $remarkMsg" } + ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 + } + + "FLD" -> { + val remarkMsg = config.Get(configKeys.REMARK_FLD.key) + Logger.info { "Remark message for FLD: $remarkMsg" } + ann_id = Regex("\\[(\\d+)]").find(remarkMsg)?.value?.toIntOrNull() ?: 0 + } + } + Logger.info { "Found ANN_ID from REMARK field: $ann_id" } + db.logDB.Add("AAS", "Found ANN_ID from REMARK field: $ann_id") + } else { + db.logDB.Add("AAS", "Found ANN_ID from SB_TAGS variables: $ann_id") + Logger.info { "Found ANN_ID from SB_TAGS variables: $ann_id" } + } + + // recheck again + if (ann_id == 0) throw Exception("No ANN_ID found") + val mblist = Get_MessageBank_by_id(ann_id, languages) + if (mblist.isEmpty()) throw Exception("ANN_ID $ann_id not found in Messagebank") + val listafi = mutableListOf() + mblist.forEach { mb -> + val result = Get_Soundbank_Files(mb, variables ?: emptyMap()) + if (!result.success) throw Exception(result.message) + result.files.forEach { filenya -> + val afi = contentCache.getAudioFile(filenya) + if (afi != null && afi.isValid()) { + listafi.add(afi) + } else { + val afi = audioPlayer.LoadAudioFile(filenya) + if (afi.isValid()) { + listafi.add(afi) + contentCache.addAudioFile(filenya, afi) + } else throw Exception("Failed to load soundbank file $filenya") + } + } + + } + if (listafi.isEmpty()) throw Exception("No audio found in Messagebank") + // bikin nama file ada postifx nya dari SB_TAGS, tapi spasi diganti underscore + // dan titik dua diganti dash + val postfix = qa.SB_TAGS.replace(" ", "_").replace(":", "-") + val targetfile = + SoundbankResult_directory.resolve(Make_WAV_FileName("${qa.Source}_${qa.Type}", postfix)).toString() + //println("Writing to target WAV file: $targetfile") + val result = audioPlayer.WavWriter(listafi, targetfile, true) + if (!result.success) throw Exception(result.message) + val targetafi = audioPlayer.LoadAudioFile(targetfile) + if (!targetafi.isValid()) throw Exception("Failed to load generated SOUNDBANK WAV file $targetfile") + + ips.forEach { ip -> + // send byte array to streamer output + val relay = Somecodes.StringToListInt(checkresult.validzones.find { it.ip == ip }?.contacts) + StreamerOutputs[ip]?.let { so -> + so.SetAudioFileInfo(targetafi) + so.AddUsedByBroadcastZone(zz) + so.ActivateRelay(relay) + so.SendData( + targetafi.bytes, + { + so.SetAudioFileInfo(null) + so.ClearUsedByBroadcastZones() + so.DeactivateRelay() + db.logDB.Add("AAS", it) + }, + { + so.SetAudioFileInfo(null) + so.ClearUsedByBroadcastZones() + so.DeactivateRelay() + db.logDB.Add("AAS", it) + }, cbPlaying = { isplaying -> + if (!isplaying) { + so.ClearUsedByBroadcastZones() + so.DeactivateRelay() + } + }) + } + } + + val logmsg = + "Broadcast started SOUNDBANK message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" + Logger.info { logmsg } + db.logDB.Add("AAS", logmsg) + db.queuetableDB.DeleteByIndex(qa.index.toInt()) + db.queuetableDB.Resort() + return true + + } + + } catch (e: Exception) { + val msg = "Canceled SOUNDBANK message $qa due to exception: ${e.message}" + db.logDB.Add("AAS", msg) + Logger.error { msg } + db.queuetableDB.DeleteByIndex(qa.index.toInt()) + db.queuetableDB.Resort() + } + return false } /** @@ -1517,13 +1194,13 @@ class MainExtension01 { * It checks for schedules that match the current time and day, * and adds them to the Queue_Table for processing. */ - fun Read_Schedule_Table(){ + fun Read_Schedule_Table() { val localtime = LocalTime.now() // detik harus 00 if (localtime.second != 0) return val timestring = timeformat2.format(localtime) val sch = db.scheduleDB.List.filter { - it.Time.equals(timestring,true) && it.Enable + it.Time.equals(timestring, true) && it.Enable } // tidak ada schedule dengan time sekarang dan enable=true if (sch.isEmpty()) return @@ -1532,7 +1209,7 @@ class MainExtension01 { val ddmmyyyy = dateformat1.format(localdate) // check special date dulu val specialdate = sch.find { - it.Day.equals(ddmmyyyy,true) + it.Day.equals(ddmmyyyy, true) } if (specialdate != null) { val qt = QueueTable( diff --git a/src/audio/AudioFileInfo.kt b/src/audio/AudioFileInfo.kt index c587329..643db5e 100644 --- a/src/audio/AudioFileInfo.kt +++ b/src/audio/AudioFileInfo.kt @@ -18,4 +18,39 @@ class AudioFileInfo { fun isValid() : Boolean { return fileName.isNotBlank() && fileSize > 0 && duration > 0.0 && bytes.isNotEmpty() } + + /** + * Convert the duration to a human-readable string format. + * @return Duration as a string in HH:MM:SS or MM:SS format or SS if less than a minute. + */ + fun DurationToString() : String { + val totalSeconds = duration.toInt() + val hours = totalSeconds / 3600 + val minutes = (totalSeconds % 3600) / 60 + val seconds = totalSeconds % 60 + + return when { + hours > 0 -> String.format("%02d h:%02d m:%02d s", hours, minutes, seconds) + minutes > 0 -> String.format("%02d m:%02d s", minutes, seconds) + else -> String.format("00:%02d s", seconds) + } + + } + + /** + * Convert the file size to a human-readable string format. + * @return File size as a string in Bytes, KB, MB, or GB. + */ + fun FileSizeToString() : String { + val kb = 1024 + val mb = kb * 1024 + val gb = mb * 1024 + + return when { + fileSize >= gb -> String.format("%.2f GB", fileSize.toDouble() / gb) + fileSize >= mb -> String.format("%.2f MB", fileSize.toDouble() / mb) + fileSize >= kb -> String.format("%.2f KB", fileSize.toDouble() / kb) + else -> "$fileSize Bytes" + } + } } \ No newline at end of file diff --git a/src/barix/BarixConnection.kt b/src/barix/BarixConnection.kt index d6a3a0f..30ec0d7 100644 --- a/src/barix/BarixConnection.kt +++ b/src/barix/BarixConnection.kt @@ -1,8 +1,7 @@ package barix +import audio.AudioFileInfo import audio.Mp3Encoder -import codes.Somecodes -import com.fasterxml.jackson.databind.JsonNode import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -16,7 +15,6 @@ import java.nio.ByteBuffer import java.util.function.Consumer import kotlin.experimental.or -@Suppress("unused") class BarixConnection(val index: UInt, var channel: String, val ipaddress: String, val port: Int = 5002) : AutoCloseable { private var _bR: Int = 0 private var _sd: Int = 0 @@ -29,6 +27,89 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin private val mp3Consumer = mutableMapOf>() private val udp = DatagramSocket() private var _barixmode: Boolean = false + private var _usedbybroadcastzone = mutableSetOf() + private var afi : AudioFileInfo? = null + private var starttick: Long? = null + + /** + * Set audio file information used for playback + * @param value The AudioFileInfo object containing audio file details + */ + fun SetAudioFileInfo(value: AudioFileInfo?){ + afi = value + } + + /** + * Get audio file information used for playback + * @return The AudioFileInfo object containing audio file details, or null if not set + */ + fun GetAudioFileInfo() : AudioFileInfo?{ + return afi + } + + /** + * Set the start tick for playback timing + * @param tick The start tick in milliseconds + */ + fun SetStartTick(tick: Long?){ + starttick = tick + } + + /** + * Get the elapsed time since the start tick + * @return Elapsed time as a formatted string or "N/A" if start tick is not set + */ + fun GetElapsed() : String { + if (starttick != null){ + val elapsedMs = System.currentTimeMillis() - starttick!! + val totalSeconds = elapsedMs / 1000 + val hours = totalSeconds / 3600 + val minutes = (totalSeconds % 3600) / 60 + val seconds = totalSeconds % 60 + + return when { + hours > 0 -> String.format("%02d h:%02d m:%02d s", hours, minutes, seconds) + minutes > 0 -> String.format("%02d m:%02d s", minutes, seconds) + else -> String.format("00:%02d s", seconds) + } + } else { + return "" + } + } + + /** + * Add a broadcast zone that uses this Barix device + * @param zoneName The name of the broadcast zone + */ + fun AddUsedByBroadcastZone(zoneName: String){ + _usedbybroadcastzone.add(zoneName) + println("Added used by broadcast zone: $zoneName to Barix device $ipaddress") + } + + /** + * Add multiple broadcast zones that use this Barix device + * @param zoneNames The list of broadcast zone names + */ + fun AddUsedByBroadcastZone(zoneNames: List){ + for (zoneName in zoneNames){ + AddUsedByBroadcastZone(zoneName) + } + } + + /** + * Clear all broadcast zones that use this Barix device + */ + fun ClearUsedByBroadcastZones(){ + _usedbybroadcastzone.clear() + } + + /** + * Get a comma-separated string of broadcast zones that use this Barix device + * @return Comma-separated string of broadcast zones + */ + fun GetUsedByBroadcastZones() : String{ + return _usedbybroadcastzone.joinToString(", ") + } /** * Barix mode flag @@ -53,6 +134,7 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin mp3Consumer.remove(key) } + @Suppress("unused") fun Exists_Mp3_Consumer(key: String) : Boolean{ return mp3Consumer.containsKey(key) } @@ -129,14 +211,59 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin return statusData == 1 } + /** + * Check if buffer has enough space (more than 10,000 bytes) + * @return true if buffer has enough space + */ + fun bufferEnough(): Boolean{ + return isOnline() && (bufferRemain > 10000) + } + /** * Send data to Barix device via UDP * @param data The data to send + * @param cbOK Callback function if sending is successful + * @param cbFail Callback function if sending fails + * @param cbPlaying Callback function to indicate if device is playing */ - fun SendData(data: ByteArray, cbOK: Consumer, cbFail: Consumer) { + fun SendData(data: ByteArray, cbOK: Consumer, cbFail: Consumer, cbPlaying: Consumer) { if (data.isNotEmpty()) { CoroutineScope(Dispatchers.IO).launch { val bb = ByteBuffer.wrap(data) + while(!bufferEnough()){ + delay(20) + Logger.info{"Waiting for StreamerOutput $ipaddress buffer to have enough space: $bufferRemain bytes available, need more than 10000 bytes"} + } + val bufmax = bufferRemain + val bufkosong = 0.2 * bufmax + val bufpenuh = 0.8 * bufmax + Logger.info{"Starting to send data to StreamerOutput $ipaddress on channel $channel, total data size: ${data.size} bytes, bufferRemain: $bufferRemain bytes, bufkosong: $bufkosong bytes, bufpenuh: $bufpenuh bytes"} + // Ide 07/02/2026, kasih buffer dummy 10x1000 byte pertama biar barix kebacanya stabil + for (i in 1..10){ + val chunk = ByteArray(1000){0} + udp.send(DatagramPacket(chunk, chunk.size, inet)) + delay(5) + println("Sending dummy buffer $i to $ipaddress") + } + + // delay interval awal = 5 ms untuk streamer output dan 10 ms untuk barix + // slow down interval = 5 ms untuk streamer output dan 10 ms untuk barix + // speed up interval = 2 ms untuk streamer output dan 5 ms untuk barix + val slowdowninterval = if (BarixMode) 8L else 5L + val speedupinterval = if (BarixMode) 5L else 2L + var delayinterval = if (BarixMode) 8L else 5L + + // buat hitung elapsed + CoroutineScope(Dispatchers.IO).launch { + SetStartTick(System.currentTimeMillis()) + cbPlaying.accept(true) + do{ + delay(1000) + } while (isPlaying()) + cbPlaying.accept(false) + SetStartTick(null) + } + while(bb.hasRemaining()){ try { @@ -144,51 +271,40 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin bb.get(chunk) while(bufferRemain { + if (delayinterval!=slowdowninterval){ + delayinterval = slowdowninterval + Logger.info{"Sending to $ipaddress on channel $channel, bufferRemain low: $bufferRemain bytes, slowing down to $delayinterval ms"} + } + } + bufferRemain >= bufpenuh -> { + if (delayinterval!=speedupinterval){ + delayinterval = speedupinterval + Logger.info{"Sending to $ipaddress on channel $channel, bufferRemain high: $bufferRemain bytes, speeding up to $delayinterval ms"} + } + } + } } udp.send(DatagramPacket(chunk, chunk.size, inet)) - mp3encoder.PushData(chunk) - if (_barixmode) delay(10) else delay(1) - - + delay(delayinterval) } catch (e: Exception) { + Logger.error{"SendData to $ipaddress failed, message: ${e.message}"} cbFail.accept("SendData to $ipaddress failed, message: ${e.message}") + cbPlaying.accept(false) return@launch } } - cbOK.accept("SendData to $channel ($ipaddress) succeeded, ${data.size} bytes sent") + Logger.info{"SendData to $channel ($ipaddress) ended, ${data.size} bytes sent"} + cbOK.accept("SendData to $channel ($ipaddress) ended, ${data.size} bytes sent") } } else cbFail.accept("SendData to $ipaddress failed, data is empty") } - /** - * Convert BarixConnection to JsonNode - * @return JsonNode representation of BarixConnection - */ - fun toJsonNode(): JsonNode { - // make json node from index, channel, ipaddress, port, bufferRemain, statusData, vu - return Somecodes.objectmapper.createObjectNode().apply { - put("index", index.toInt()) - put("channel", channel) - put("ipaddress", ipaddress) - put("port", port) - put("bufferRemain", bufferRemain) - put("statusData", statusData) - put("vu", vu) - put("isOnline", isOnline()) - } - } - - /** - * Convert BarixConnection to JSON string - * @return JSON string representation of BarixConnection - */ - fun toJsonString(): String { - return Somecodes.toJsonString(toJsonNode()) - } - fun ActivateRelay(relays: List){ if (relays.isNotEmpty()){ var value : Byte = 0 @@ -201,22 +317,6 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin } } - /** - * Activate relay on Barix device - * @param relays The relay numbers to activate (1-8) - */ - fun ActivateRelay(vararg relays: Int){ - if (relays.isNotEmpty()){ - var value : Byte = 0 - for (r in relays){ - if (r in 1..8){ - value = value or (1 shl (r - 1)).toByte() - } - } - SendSimpleCommand(byteArrayOf(0x1A, value, 0x61)) - } - } - /** * Deactivate relay on Barix device */ @@ -247,41 +347,7 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin } } - /** - * Send command to Barix device - * @param command The command to send - * @return true if successful - */ - fun SendCommand(command: String): Boolean { - try { - if (_tcp!=null){ - if (!_tcp!!.isClosed){ - val bb = command.toByteArray() - val size = bb.size + 4 - val b4 = byteArrayOf( - (size shr 24 and 0xFF).toByte(), - (size shr 16 and 0xFF).toByte(), - (size shr 8 and 0xFF).toByte(), - (size and 0xFF).toByte() - ) - val out = _tcp!!.getOutputStream() - out.write(b4) - out.write(bb) - out.flush() - Logger.info { "SendCommand to $ipaddress : $command" } - return true - }else { - Logger.error { "Socket to $ipaddress is not connected" } - } - } else { - Logger.error { "Socket to $ipaddress is null" } - } - } catch (e: Exception) { - Logger.error { "Failed to SendCommand to $ipaddress, Message : ${e.message}" } - } - return false - } override fun close() { try{ diff --git a/src/barix/TCP_Barix_Command_Server.kt b/src/barix/TCP_Barix_Command_Server.kt index 46c1fb5..fe16ea5 100644 --- a/src/barix/TCP_Barix_Command_Server.kt +++ b/src/barix/TCP_Barix_Command_Server.kt @@ -7,17 +7,15 @@ import org.tinylog.Logger import java.io.DataInputStream import java.net.ServerSocket import java.net.Socket -import java.nio.ByteBuffer import java.util.function.Consumer -@Suppress("unused") class TCP_Barix_Command_Server { lateinit var tcpserver: ServerSocket lateinit var job: Job private val socketMap = mutableMapOf() - private val regex = """STATUSBARIX;(\d+);(\d+)(;(\d+))?""" - private val pattern = Regex(regex) + //private val regex = """STATUSBARIX;(\d+);(\d+)(;(\d+))?""" + //private val pattern = Regex(regex) /** * Start TCP Command Server @@ -43,6 +41,7 @@ class TCP_Barix_Command_Server { try{ val din = DataInputStream(socket.getInputStream()) + var VuZeroCounter = 0L while (isActive) { val bb = ByteArray(128) @@ -60,25 +59,56 @@ class TCP_Barix_Command_Server { Logger.error { "Error reading length from Streamer Output with IP $key, Message : ${ex.message}" } continue } - val str = String(bb,4, stringlength).trim() + var str = String(bb,4, stringlength).trim() if (str.isBlank()) continue if (!str.startsWith("STATUSBARIX")) continue + if (str.endsWith("@")) str = str.removeSuffix("@") if (ValidString(str)) { - // Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$ - pattern.find(str)?.let { matchResult -> - val (vu, buffremain, statusdata) = matchResult.destructured - val status = BarixStatus( - socket.inetAddress.hostAddress, - vu.toInt(), - buffremain.toInt(), - statusdata.toIntOrNull() ?: 0, - statusdata.isNullOrEmpty() // barix tidak ada statusdata , Q-AG1 ada - ) - //Logger.info { "Received valid command from $key : $status" } - cb.accept(status) - } ?: run { - Logger.warn { "Invalid command format from $key : $str" } + // Valid command from StreamerOutput is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$ + // Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain"$ + val values = str.split(";") + if (values.size<3) continue + if ("STATUSBARIX" != values[0]) continue + val vu = values[1].toIntOrNull() ?: continue + + val buffremain = values[2].toIntOrNull() ?: continue + var status: BarixStatus + when(values.size){ + 3 ->{ + // mode barix + // kadang vu stuck tidak di 0 saat idle, + // jadi kalau vu <512 selama 10 kali berturut2 + // dan buffer lebih dari 16000, anggap idle + if ((vu < 512) && (buffremain>=16000)){ + VuZeroCounter++ + } else { + VuZeroCounter = 0 + } + // statusdata = isplaying = , if VuZeroCounter >=10 then idle (0) else playing (1) + val statusdata = if (VuZeroCounter>=10) 0 else 1 + status = BarixStatus( + socket.inetAddress.hostAddress, + vu, + buffremain, + statusdata, + true + ) + } + 4 ->{ + // mode Q-AG1 + val statusdata = values[3].toIntOrNull() ?: 0 + status = BarixStatus( + socket.inetAddress.hostAddress, + vu, + buffremain, + statusdata, + false + ) + } + else -> continue } + cb.accept(status) + } } diff --git a/src/codes/Somecodes.kt b/src/codes/Somecodes.kt index 40b9994..b68cb3e 100644 --- a/src/codes/Somecodes.kt +++ b/src/codes/Somecodes.kt @@ -109,6 +109,14 @@ class Somecodes { } else cb.accept(-1,-1) } + /** + * Convert a string of numbers separated by commas or semicolons into a list of integers. + */ + fun StringToListInt(value : String?) : List{ + if (value.isNullOrBlank()) return listOf() + return value.split(",",";").mapNotNull { it.trim().toIntOrNull() } + } + /** * Clear all files in PagingResult directory. * If PagingResult directory does not exist, callback with -1,-1. diff --git a/src/commandServer/TCP_Android_Command_Server.kt b/src/commandServer/TCP_Android_Command_Server.kt index 652279e..d18e0f1 100644 --- a/src/commandServer/TCP_Android_Command_Server.kt +++ b/src/commandServer/TCP_Android_Command_Server.kt @@ -72,14 +72,13 @@ class TCP_Android_Command_Server { //println("Received command from $key : $str") str.split("@").map { it.trim() }.filter { ValidString(it) } .forEach { - Logger.info{"Receive command from $key : $it"} process_command(key,it) { reply -> try { val cc = String_to_Byte_Android(reply) if (cc.isNotEmpty()){ dout.write(cc) dout.flush() - Logger.info{"Sent reply ${cc.size} bytes to $key : $reply"} + //Logger.info{"Sent reply ${cc.size} bytes to $key : $reply"} } else Logger.error { "Empty reply to send to $key" } } catch (e: Exception) { @@ -149,7 +148,7 @@ class TCP_Android_Command_Server { * @param cb Callback to send reply string */ private fun process_command(key: String, cmd: String, cb: Consumer) { - Logger.info { "Command from $key : $cmd" } + if ("PING" != cmd) Logger.info { "Command from $key : $cmd" } val parts = cmd.split(";").map { it.trim() }.filter { it.isNotBlank() } when (parts[0]) { "GETLOGIN" -> { diff --git a/src/database/BroadcastZones.kt b/src/database/BroadcastZones.kt index cf2529c..f98711e 100644 --- a/src/database/BroadcastZones.kt +++ b/src/database/BroadcastZones.kt @@ -25,4 +25,28 @@ data class BroadcastZones(var index: UInt, var description: String, var SoundCha override fun toString(): String { return "BroadcastZones(index=$index, description='$description', SoundChannel='$SoundChannel', id='$id', bp='$bp')" } + + companion object{ + + /** + * Get a list of relay numbers from the broadcast zone's bp field. + * Currently, supports relays 1 to 8. + * @param bz The BroadcastZones object + * @return List of relay numbers (Int) extracted from the bp field + */ + fun getRelaysFromBroadcastZone(bz : BroadcastZones) : List{ + val result = ArrayList() + // delimiters either comma or semicolon + val parts = bz.bp.split(",", ";") + for (part in parts){ + val relay = part.trim().toIntOrNull() + if (relay != null){ + if (relay in 1..8){ + result.add(relay) + } + } + } + return result + } + } } diff --git a/src/web/StreamerOutputData.kt b/src/web/StreamerOutputData.kt index 7294854..bd860b4 100644 --- a/src/web/StreamerOutputData.kt +++ b/src/web/StreamerOutputData.kt @@ -1,9 +1,9 @@ package web -class StreamerOutputData(val index: UInt, val channel: String, val ipaddress: String, val vu: Int, val bufferRemain: Int, var isPlaying: Boolean) { +data class StreamerOutputData(val index: UInt, val channel: String, val ipaddress: String, val vu: Int, val bufferRemain: Int, val isPlaying: Boolean, val filename: String, val duration: String, val elapsed: String, val broadcastzones: String) { companion object{ fun fromBarixConnection(bc: barix.BarixConnection): StreamerOutputData { - return StreamerOutputData(bc.index, bc.channel, bc.ipaddress, bc.vu, bc.bufferRemain, bc.isPlaying()) + return StreamerOutputData(bc.index, bc.channel, bc.ipaddress, bc.vu, bc.bufferRemain, bc.isPlaying(), bc.GetAudioFileInfo()?.fileName ?: "", bc.GetAudioFileInfo()?.DurationToString() ?: "", bc.GetElapsed(), bc.GetUsedByBroadcastZones()) } } } \ No newline at end of file