/** * @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 | null} title - The jQuery result should be

element. * @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} vu - The jQuery result should be element. */ function getCardByIndex(index) { 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')}`), // 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')}`), // 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')}`), // 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')}`), // 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`), } return obj; } /** * Updates the streamer card with the provided values. * @param {StreamerOutputData[]} values */ function UpdateStreamerCard(values) { if (!Array.isArray(values) || values.length === 0) return; 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 } for (let i = 1; i <= 64; i++) { let vv = values.find(v => v.index === i); let card = getCardByIndex(i); 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); } } 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); } } } } /** * @type {PagingQueue[]} */ window.PagingQueue = []; /** * @type {JQuery | 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 | 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(` ${item.index} ${item.date_Time} ${item.source} ${item.type} ${item.message} ${item.broadcastZones} `); 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(` ${item.index} ${item.date_Time} ${item.source} ${item.type} ${item.message} ${item.broadcastZones} `); 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 */ let streamws = null; let 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 (streamws) { streamws.close(); streamws = null; } if (mediasource) { mediasource.endOfStream(); 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); streamws = new WebSocket(`ws://${window.location.host}/api/LiveAudio/ws`); streamws.binaryType = 'arraybuffer'; mediasource = new MediaSource(); let audio = document.getElementById('listenaudio'); audio.src = URL.createObjectURL(mediasource); mediasource.addEventListener('sourceopen', () => { const sourceBuffer = mediasource.addSourceBuffer('audio/mpeg'); 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', (event) => { 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; }); });