544 lines
21 KiB
JavaScript
544 lines
21 KiB
JavaScript
/**
|
|
* @typedef {Object} StreamerOutputData
|
|
* @property {number} index - The index of the Barix connection.
|
|
* @property {string} channel - The channel name of the Barix connection.
|
|
* @property {string} ipaddress - The IP address of the Barix connection.
|
|
* @property {number} bufferRemain - The remaining buffer size of the Barix connection.
|
|
* @property {boolean} isPlaying - true = playback started, false = playback stopped
|
|
* @property {number} vu - The VU level of the Barix connection, 0 to 100.
|
|
*/
|
|
|
|
|
|
/**
|
|
* @typedef {Object} PagingQueue
|
|
* @property {number} index - The index of the paging queue item.
|
|
* @property {string} Date_Time - The date and time of the paging queue item.
|
|
* @property {string} Source - The source of the paging queue item.
|
|
* @property {string} Type - The type of the paging queue item.
|
|
* @property {string} Message - The message of the paging queue item.
|
|
* @property {string} BroadcastZones - The broadcast zones of the paging queue item.
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} StreamerCard
|
|
* @property {JQuery<HTMLElement> | null} title - The jQuery result should be <h4> element.
|
|
* @property {JQuery<HTMLElement> | null} ip - The jQuery result should be <h6> element.
|
|
* @property {JQuery<HTMLElement> | null} buffer - The jQuery result should be <h6> element.
|
|
* @property {JQuery<HTMLElement> | null} status - The jQuery result should be <p> element.
|
|
* @property {JQuery<HTMLElement> | null} filename - The jQuery result should be <h6> element.
|
|
* @property {JQuery<HTMLElement> | null} duration - The jQuery result should be <h6> element.
|
|
* @property {JQuery<HTMLElement> | null} elapsed - The jQuery result should be <h6> element.
|
|
* @property {JQuery<HTMLElement> | null} broadcastzones - The jQuery result should be <h6> element.
|
|
* @property {JQuery<HTMLElement> | null} vu - The jQuery result should be <progress-bar> element.
|
|
*/
|
|
|
|
function getCardByIndex(index) {
|
|
let cardname = "ch" + index.toString().padStart(2, '0');
|
|
let obj = {
|
|
// title is <h4> element wiht id `streamertitle${index}`, with index as two digit number, e.g. 01, 02, 03
|
|
//title: $(`#streamertitle${index.toString().padStart(2, '0')}`),
|
|
// ip is <h6> element with id `streamerip${index}`, with index as two digit number, e.g. 01, 02, 03
|
|
//ip: $(`#streamerip${index.toString().padStart(2, '0')}`),
|
|
// buffer is <h6> element with id `streamerbuffer${index}`, with index as two digit number, e.g. 01, 02, 03
|
|
//buffer: $(`#streamerbuffer${index.toString().padStart(2, '0')}`),
|
|
// status is <p> element with id `streamerstatus${index}`, with index as two digit number, e.g. 01, 02, 03
|
|
//status: $(`#streamerstatus${index.toString().padStart(2, '0')}`),
|
|
// vu is <progress-bar> 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`),
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Updates the streamer card with the provided values.
|
|
* @param {StreamerOutputData[]} values
|
|
*/
|
|
function UpdateStreamerCard(values) {
|
|
|
|
function setProgress(index, $bar, value, max = 100) {
|
|
const v = Number(value ?? 0);
|
|
const pct = Math.max(0, Math.min(100, Math.round((v / max) * 100)));
|
|
//if (index!==1) return; // only update index 1 for testing
|
|
$bar
|
|
.attr('aria-valuenow', v) // semantic value
|
|
.css('width', pct + '%') // visual width
|
|
.text(pct); // optional label
|
|
}
|
|
if (!Array.isArray(values) || values.length === 0) {
|
|
// create dummy data to disable all cards
|
|
values = [];
|
|
}
|
|
|
|
let visiblilitychanged = false;
|
|
|
|
for (let i = 1; i <= 64; i++) {
|
|
const vv = values.find(v => v.index === i);
|
|
const cardname = "ch" + i.toString().padStart(2, '0');
|
|
const $card = $(`#${cardname}`);
|
|
|
|
|
|
if (vv) {
|
|
// 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 {
|
|
// 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');
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @type {PagingQueue[]}
|
|
*/
|
|
window.PagingQueue = [];
|
|
/**
|
|
* @type {JQuery<HTMLElement> | null}
|
|
*/
|
|
window.selectedpagingrow = null;
|
|
|
|
/**
|
|
* @typedef {Object} QueueTable
|
|
* @property {number} index - The index of the automatic queue item.
|
|
* @property {string} Date_Time - The date and time of the automatic queue item.
|
|
* @property {string} Source - The source of the automatic queue item.
|
|
* @property {string} Type - The type of the automatic queue item.
|
|
* @property {string} Message - The message of the automatic queue item.
|
|
* @property {string} SB_TAGS - The SB_TAGS of the automatic queue item.
|
|
* @property {string} BroadcastZones - The broadcast zones of the automatic queue item.
|
|
* @property {number} Repeat - The repeat count of the automatic queue item.
|
|
* @property {string} Language - The language of the automatic queue item.
|
|
*/
|
|
|
|
/**
|
|
* @type {QueueTable[]}
|
|
*/
|
|
window.QueueTable = [];
|
|
/**
|
|
* @type {JQuery<HTMLElement> | null}
|
|
*/
|
|
window.selectedautomaticrow = null;
|
|
|
|
/**
|
|
* Fills the paging queue table body with the provided data.
|
|
* @param {PagingQueue[]} vv array of PagingQueue objects
|
|
* @returns
|
|
*/
|
|
function fill_pagingqueuetablebody(vv) {
|
|
$('#pagingqueuetable').empty();
|
|
if (!Array.isArray(vv) || vv.length === 0) return;
|
|
vv.forEach(item => {
|
|
// fill index and description columns using item properties
|
|
$('#pagingqueuetable').append(`<tr>
|
|
<td>${item.index}</td>
|
|
<td>${item.date_Time}</td>
|
|
<td>${item.source}</td>
|
|
<td>${item.type}</td>
|
|
<td>${item.message}</td>
|
|
<td>${item.broadcastZones}</td>
|
|
</tr>`);
|
|
let $addedrow = $('#pagingqueuetable tr:last');
|
|
$addedrow.off('click').on('click', function () {
|
|
if (window.selectedpagingrow) {
|
|
window.selectedpagingrow.find('td').css('background-color', '');
|
|
if (window.selectedpagingrow.is($(this))) {
|
|
window.selectedpagingrow = null;
|
|
$('#removepagingqueue').prop('disabled', true);
|
|
return;
|
|
}
|
|
}
|
|
window.selectedpagingrow = $(this);
|
|
window.selectedpagingrow.find('td').css('background-color', 'lightblue');
|
|
$('#removepagingqueue').prop('disabled', false);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fills the automatic queue table body with the provided data.
|
|
* @param {QueueTable[]} vv array of QueueTable objects
|
|
* @returns
|
|
*/
|
|
function fill_automaticqueuetablebody(vv) {
|
|
$('#automaticqueuetable').empty();
|
|
if (!Array.isArray(vv) || vv.length === 0) return;
|
|
vv.forEach(item => {
|
|
// fill index and description columns using item properties
|
|
$('#automaticqueuetable').append(`<tr>
|
|
<td>${item.index}</td>
|
|
<td>${item.date_Time}</td>
|
|
<td>${item.source}</td>
|
|
<td>${item.type}</td>
|
|
<td>${item.message}</td>
|
|
<td>${item.broadcastZones}</td>
|
|
</tr>`);
|
|
let $addedrow = $('#automaticqueuetable tr:last');
|
|
$addedrow.off('click').on('click', function () {
|
|
if (window.selectedautomaticrow) {
|
|
window.selectedautomaticrow.find('td').css('background-color', '');
|
|
if (window.selectedautomaticrow.is($(this))) {
|
|
window.selectedautomaticrow = null;
|
|
$('#removeautomatictable').prop('disabled', true);
|
|
return;
|
|
}
|
|
}
|
|
window.selectedautomaticrow = $(this);
|
|
window.selectedautomaticrow.find('td').css('background-color', 'lightblue');
|
|
$('#removeautomatictable').prop('disabled', false);
|
|
});
|
|
});
|
|
}
|
|
|
|
function reloadPagingQueue(APIURL = "QueuePaging/") {
|
|
window.PagingQueue = [];
|
|
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
|
|
if (Array.isArray(okdata) && okdata.length > 0) {
|
|
window.PagingQueue.push(...okdata);
|
|
fill_pagingqueuetablebody(window.PagingQueue);
|
|
} else {
|
|
console.log("reloadPagingQueue: okdata is not array");
|
|
}
|
|
}, (errdata) => {
|
|
console.log("reloadPagingQueue: errdata", errdata);
|
|
});
|
|
}
|
|
|
|
function reloadAutomaticQueue(APIURL = "QueueTable/") {
|
|
window.QueueTable = [];
|
|
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
|
|
if (Array.isArray(okdata) && okdata.length > 0) {
|
|
window.QueueTable.push(...okdata);
|
|
fill_automaticqueuetablebody(window.QueueTable);
|
|
} else {
|
|
console.log("reloadAutomaticQueue: okdata is not array");
|
|
}
|
|
}, (errdata) => {
|
|
console.log("reloadAutomaticQueue: errdata", errdata);
|
|
});
|
|
}
|
|
|
|
function RemovePagingQueueByIndex(index, APIURL = "QueuePaging/") {
|
|
fetchAPI(APIURL + "DeleteByIndex/" + index, "DELETE", {}, null, (okdata) => {
|
|
reloadPagingQueue(APIURL);
|
|
}, (errdata) => {
|
|
console.log("RemovePagingQueueByIndex: errdata", errdata);
|
|
});
|
|
}
|
|
|
|
function RemoveAutomaticQueueByIndex(index, APIURL = "QueueTable/") {
|
|
fetchAPI(APIURL + "DeleteByIndex/" + index, "DELETE", {}, null, (okdata) => {
|
|
reloadAutomaticQueue(APIURL);
|
|
}, (errdata) => {
|
|
console.log("RemoveAutomaticQueueByIndex: errdata", errdata);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetches the list of listening zones and populates the dropdown.
|
|
*/
|
|
function GetListeningZones() {
|
|
$("#listenzone").empty();
|
|
fetchAPI("BroadcastZones/List", "GET", {}, null, (okdata) => {
|
|
if (Array.isArray(okdata) && okdata.length > 0) {
|
|
okdata.forEach(zone => {
|
|
$("#listenzone").append(new Option(zone.description, zone.zone));
|
|
});
|
|
} else {
|
|
console.log("GetListeningZones: okdata is not array");
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Open or Close Live Audio for the selected Broadcast Zone.
|
|
* @param {string} command either 'Open' or 'Close'
|
|
* @param {string} bz Broadcast Zone
|
|
* @param {Function} cbOK callback function on success
|
|
* @param {Function} cbFail callback function on failure
|
|
*/
|
|
function LiveAudioCommand(command, bz, cbOK = null, cbFail = null) {
|
|
function raise_cbOK(value = null) {
|
|
if (cbOK) cbOK(value);
|
|
}
|
|
function raise_cbFail(value) {
|
|
if (cbFail) cbFail(value);
|
|
}
|
|
|
|
if (command && command.length > 0) {
|
|
if (bz && bz.length > 0) {
|
|
if (command === 'Open' || command === 'Close') {
|
|
let url = `/api/LiveAudio`;
|
|
let payload = {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
command: command,
|
|
broadcastzone: bz
|
|
})
|
|
};
|
|
fetch(url, payload)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
raise_cbOK(data);
|
|
})
|
|
.catch(error => {
|
|
raise_cbFail(error);
|
|
});
|
|
|
|
} else raise_cbFail("LiveAudioCommand: Unknown command " + command);
|
|
} else raise_cbFail("LiveAudioCommand: Broadcast Zone is empty");
|
|
} else raise_cbFail("LiveAudioCommand: command is empty");
|
|
}
|
|
|
|
/**
|
|
* Websocket for streaming
|
|
*/
|
|
window.streamws = null;
|
|
window.mediasource = null;
|
|
|
|
|
|
|
|
$(document).ready(function () {
|
|
|
|
|
|
GetListeningZones();
|
|
$("#startstoplisten").off('click').on('click', function () {
|
|
let bz = $("#listenzone").val();
|
|
let $icon = $(this).find('svg');
|
|
if ($icon.hasClass('fa-stop')) {
|
|
LiveAudioCommand('Close', bz, (okdata) => {
|
|
if (okdata.message && okdata.message.length > 0) {
|
|
console.log("Live Audio Session Closed:", okdata.message);
|
|
}
|
|
$icon.toggleClass('fa-stop fa-play');
|
|
$("#listenzone").prop('disabled', false);
|
|
if (window.streamws) {
|
|
window.streamws.close();
|
|
window.streamws = null;
|
|
}
|
|
if (window.mediasource) {
|
|
window.mediasource.endOfStream();
|
|
window.mediasource = null;
|
|
}
|
|
let audio = document.getElementById('listenaudio');
|
|
audio.src = "";
|
|
|
|
}, (errdata) => {
|
|
alert("Error stopping Live Audio: " + errdata);
|
|
});
|
|
} else {
|
|
LiveAudioCommand('Open', bz, (okdata) => {
|
|
if (okdata.message && okdata.message.length > 0) {
|
|
let uuid = okdata.message;
|
|
console.log("Live Audio Session UUID:", uuid);
|
|
}
|
|
$icon.toggleClass('fa-stop fa-play');
|
|
$("#listenzone").prop('disabled', true);
|
|
window.streamws = new WebSocket(`ws://${window.location.host}/api/LiveAudio/ws`);
|
|
window.streamws.binaryType = 'arraybuffer';
|
|
window.mediasource = new MediaSource();
|
|
let audio = document.getElementById('listenaudio');
|
|
audio.src = URL.createObjectURL(window.mediasource);
|
|
window.mediasource.addEventListener('sourceopen', () => {
|
|
const sourceBuffer = window.mediasource.addSourceBuffer('audio/mpeg');
|
|
window.streamws.onmessage = (event) => {
|
|
if (event.data instanceof ArrayBuffer) {
|
|
const chunk = new Uint8Array(event.data);
|
|
sourceBuffer.appendBuffer(chunk);
|
|
}
|
|
};
|
|
});
|
|
|
|
}, (errdata) => {
|
|
alert("Error starting Live Audio: " + errdata);
|
|
});
|
|
}
|
|
|
|
});
|
|
$('#clearpagingqueue').off('click').on('click', function () {
|
|
DoClear("QueuePaging/", "Paging Queue", (okdata) => {
|
|
reloadPagingQueue();
|
|
alert("Success clear Paging Queue: " + okdata.message);
|
|
}, (errdata) => {
|
|
alert("Error clear Paging Queue: " + errdata.message);
|
|
});
|
|
});
|
|
$('#removepagingqueue').off('click').on('click', function () {
|
|
if (window.selectedpagingrow) {
|
|
let cells = window.selectedpagingrow.find('td');
|
|
let index = Number(cells.eq(0).text());
|
|
let description = cells.eq(1).text();
|
|
if (!isNaN(index) && description && description.length > 0) {
|
|
if (confirm(`Are you sure to remove Paging Queue Index: ${index} Description: ${description} ?`)) {
|
|
RemovePagingQueueByIndex(index);
|
|
window.selectedpagingrow = null;
|
|
$('#removepagingqueue').prop('disabled', true);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
|
|
$('#clearautomatictable').off('click').on('click', function () {
|
|
DoClear("QueueTable/", "Automatic Queue", (okdata) => {
|
|
reloadAutomaticQueue();
|
|
alert("Success clear Automatic Queue: " + okdata.message);
|
|
}, (errdata) => {
|
|
alert("Error clear Automatic Queue: " + errdata.message);
|
|
});
|
|
});
|
|
$('#removeautomatictable').off('click').on('click', function () {
|
|
if (window.selectedautomaticrow) {
|
|
let cells = window.selectedautomaticrow.find('td');
|
|
let index = Number(cells.eq(0).text());
|
|
let description = cells.eq(1).text();
|
|
if (!isNaN(index) && description && description.length > 0) {
|
|
if (confirm(`Are you sure to remove Automatic Queue Index: ${index} Description: ${description} ?`)) {
|
|
RemoveAutomaticQueueByIndex(index);
|
|
window.selectedautomaticrow = null;
|
|
$('#removeautomatictable').prop('disabled', true);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let intervaljob1 = null;
|
|
let intervaljob2 = null;
|
|
function runIntervalJob() {
|
|
if (intervaljob1) clearInterval(intervaljob1);
|
|
intervaljob1 = setInterval(() => {
|
|
sendCommand("getStreamerOutputs", "");
|
|
}, 100);
|
|
if (intervaljob2) clearInterval(intervaljob2);
|
|
intervaljob2 = setInterval(() => {
|
|
sendCommand("getPagingQueue", "");
|
|
sendCommand("getAASQueue", "");
|
|
}, 2000);
|
|
console.log("overview.js interval job started");
|
|
}
|
|
|
|
runIntervalJob();
|
|
|
|
|
|
|
|
|
|
window.addEventListener('ws_connected', () => {
|
|
console.log("overview.js ws_connected event triggered");
|
|
runIntervalJob();
|
|
});
|
|
window.addEventListener('ws_disconnected', () => {
|
|
console.log("overview.js ws_disconnected event triggered");
|
|
if (intervaljob1) clearInterval(intervaljob1);
|
|
if (intervaljob2) clearInterval(intervaljob2);
|
|
intervaljob1 = null;
|
|
intervaljob2 = null;
|
|
});
|
|
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;
|
|
}
|
|
}
|
|
});
|
|
|
|
$(window).on('beforeunload', function () {
|
|
console.log("overview.js beforeunload event triggered");
|
|
clearInterval(intervaljob1);
|
|
clearInterval(intervaljob2);
|
|
intervaljob1 = null;
|
|
intervaljob2 = null;
|
|
});
|
|
});
|