commit 06/11/2025
This commit is contained in:
@@ -344,6 +344,16 @@ th, td {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-disable {
|
||||||
|
color: white;
|
||||||
|
background-color: var(--bs-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-enable {
|
||||||
|
color: white;
|
||||||
|
background-color: #36d636;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-logout {
|
.btn-logout {
|
||||||
background-color: #4280ab;
|
background-color: #4280ab;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ let $input_gatenumber = null;
|
|||||||
let $input_flightnumber = null;
|
let $input_flightnumber = null;
|
||||||
let $input_licenseplate = null;
|
let $input_licenseplate = null;
|
||||||
let $input_conveyorbelt = null;
|
let $input_conveyorbelt = null;
|
||||||
let $input_hours = null;
|
let $input_etad = null;
|
||||||
let $input_minutes = null;
|
|
||||||
|
|
||||||
let $row_airplane = null;
|
let $row_airplane = null;
|
||||||
let $row_city = null;
|
let $row_city = null;
|
||||||
@@ -42,6 +41,11 @@ let $col_conveyorbelt = null;
|
|||||||
let $col_procedure = null;
|
let $col_procedure = null;
|
||||||
let $col_licenseplate = null;
|
let $col_licenseplate = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {message[]}
|
||||||
|
*/
|
||||||
|
let selected_messages = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} message
|
* @typedef {Object} message
|
||||||
* @property {number} id - The ID of the message
|
* @property {number} id - The ID of the message
|
||||||
@@ -438,8 +442,8 @@ function reload_database() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function empty_preview(){
|
function empty_preview() {
|
||||||
$preview_arabic.empty();
|
$preview_arabic.empty();
|
||||||
$preview_chinese.empty();
|
$preview_chinese.empty();
|
||||||
$preview_english.empty();
|
$preview_english.empty();
|
||||||
$preview_indonesia.empty();
|
$preview_indonesia.empty();
|
||||||
@@ -455,7 +459,7 @@ function empty_preview(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function enable_disable_fields(){
|
function enable_disable_fields() {
|
||||||
$row_airplane.hide();
|
$row_airplane.hide();
|
||||||
$row_city.hide();
|
$row_city.hide();
|
||||||
$row_gatenumber.hide();
|
$row_gatenumber.hide();
|
||||||
@@ -469,10 +473,10 @@ function enable_disable_fields(){
|
|||||||
|
|
||||||
// show airplane row if English preview contains the placeholder
|
// show airplane row if English preview contains the placeholder
|
||||||
const text = $preview_english.text() || "";
|
const text = $preview_english.text() || "";
|
||||||
if (text.indexOf('[CITY]') !== -1) $row_city.show();
|
if (text.indexOf('[CITY]') !== -1) $row_city.show();
|
||||||
if (text.indexOf('[AIRPLANE_NAME]') !== -1) $row_airplane.show();
|
if (text.indexOf('[AIRPLANE_NAME]') !== -1) $row_airplane.show();
|
||||||
if (text.indexOf('[FLIGHT_NUMBER]') !== -1) $row_airplane.show();
|
if (text.indexOf('[FLIGHT_NUMBER]') !== -1) $row_airplane.show();
|
||||||
if (text.indexOf('[GATENUMBER]') !== -1) $row_gatenumber.show();
|
if (text.indexOf('[GATENUMBER]') !== -1) $row_gatenumber.show();
|
||||||
if (text.indexOf('[ETAD]') !== -1) $row_time.show();
|
if (text.indexOf('[ETAD]') !== -1) $row_time.show();
|
||||||
if (text.indexOf('[REASON]') !== -1) $col_reason.show();
|
if (text.indexOf('[REASON]') !== -1) $col_reason.show();
|
||||||
if (text.indexOf('[PLACES]') !== -1) $col_places.show();
|
if (text.indexOf('[PLACES]') !== -1) $col_places.show();
|
||||||
@@ -482,76 +486,140 @@ function enable_disable_fields(){
|
|||||||
if (text.indexOf('[PLATNOMOR]') !== -1) $col_licenseplate.show();
|
if (text.indexOf('[PLATNOMOR]') !== -1) $col_licenseplate.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_preview(language){
|
function ValidString(str) {
|
||||||
|
return (str != null && typeof str === 'string' && str.trim().length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_preview(language) {
|
||||||
let text = "";
|
let text = "";
|
||||||
let $preview = null;
|
let $preview = null;
|
||||||
if (language === "indonesia") {
|
if (language === "indonesia") {
|
||||||
$preview = $preview_indonesia;
|
$preview = $preview_indonesia;
|
||||||
|
text = selected_messages.find(m => (m.language || "").toLowerCase() === "indonesia")?.message_details || "";
|
||||||
} else if (language === "english") {
|
} else if (language === "english") {
|
||||||
$preview = $preview_english;
|
$preview = $preview_english;
|
||||||
|
text = selected_messages.find(m => (m.language || "").toLowerCase() === "english")?.message_details || "";
|
||||||
} else if (language === "chinese") {
|
} else if (language === "chinese") {
|
||||||
$preview = $preview_chinese;
|
$preview = $preview_chinese;
|
||||||
|
text = selected_messages.find(m => (m.language || "").toLowerCase() === "chinese")?.message_details || "";
|
||||||
} else if (language === "japanese") {
|
} else if (language === "japanese") {
|
||||||
$preview = $preview_japanese;
|
$preview = $preview_japanese;
|
||||||
|
text = selected_messages.find(m => (m.language || "").toLowerCase() === "japanese")?.message_details || "";
|
||||||
} else if (language === "arabic") {
|
} else if (language === "arabic") {
|
||||||
$preview = $preview_arabic;
|
$preview = $preview_arabic;
|
||||||
|
text = selected_messages.find(m => (m.language || "").toLowerCase() === "arabic")?.message_details || "";
|
||||||
} else if (language === "local") {
|
} else if (language === "local") {
|
||||||
$preview = $preview_local;
|
$preview = $preview_local;
|
||||||
|
text = selected_messages.find(m => (m.language || "").toLowerCase() === "local")?.message_details || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($preview) {
|
|
||||||
text = $preview.text();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text.indexOf('[CITY]') !== -1) {
|
if (text.indexOf('[CITY]') !== -1) {
|
||||||
let cities = $select_city.val();
|
let cities = $select_city.val();
|
||||||
if (Array.isArray(cities)) {
|
if (Array.isArray(cities) && cities.length > 0) {
|
||||||
text = text.replace(/\[CITY\]/g, cities.join(", "));
|
let citiesNames = [];
|
||||||
} else {
|
for (let cityTag of cities) {
|
||||||
text = text.replace(/\[CITY\]/g, cities || "");
|
let ct = window.semiautodata.cities.find(c => c.tag === cityTag);
|
||||||
|
if (ct) {
|
||||||
|
citiesNames.push(ct.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (citiesNames.length > 0) {
|
||||||
|
text = text.replace(/\[CITY\]/g, citiesNames.join(", "));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (text.indexOf('[AIRPLANE_NAME]') !== -1) {
|
if (text.indexOf('[AIRPLANE_NAME]') !== -1) {
|
||||||
let airlineTag = $select_airline.val() || "";
|
let airlineTag = $select_airline.val();
|
||||||
let airlineObj = window.semiautodata.airlines.find(a => a.tag === airlineTag);
|
if (ValidString(airlineTag)) {
|
||||||
let airlineName = airlineObj ? airlineObj.value : "";
|
let airlineObj = window.semiautodata.airlines.find(a => a.tag === airlineTag);
|
||||||
text = text.replace(/\[AIRPLANE_NAME\]/g, airlineName);
|
let airlineName = airlineObj ? airlineObj.value : "";
|
||||||
|
if (airlineName.length > 0) {
|
||||||
|
text = text.replace(/\[AIRPLANE_NAME\]/g, airlineName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (text.indexOf('[FLIGHT_NUMBER]') !== -1) {
|
if (text.indexOf('[FLIGHT_NUMBER]') !== -1) {
|
||||||
let flightNumber = $input_flightnumber.val() || "";
|
let airlineTag = $select_airline.val();
|
||||||
text = text.replace(/\[FLIGHT_NUMBER\]/g, flightNumber);
|
let flightNumber = $input_flightnumber.val();
|
||||||
|
if (ValidString(airlineTag) && ValidString(flightNumber)) {
|
||||||
|
text = text.replace(/\[FLIGHT_NUMBER\]/g, `${airlineTag} ${flightNumber}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (text.indexOf('[GATENUMBER]') !== -1) {
|
if (text.indexOf('[GATENUMBER]') !== -1) {
|
||||||
let gateNumber = $input_gatenumber.val() || "";
|
let gateNumber = $input_gatenumber.val();
|
||||||
text = text.replace(/\[GATENUMBER\]/g, gateNumber);
|
if (ValidString(gateNumber)) {
|
||||||
|
text = text.replace(/\[GATENUMBER\]/g, gateNumber);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (text.indexOf('[ETAD]') !== -1) {
|
if (text.indexOf('[ETAD]') !== -1) {
|
||||||
let etad = $input_etad.val() || "";
|
let etad = $input_etad.val();
|
||||||
text = text.replace(/\[ETAD\]/g, etad);
|
if (ValidString(etad)) {
|
||||||
|
text = text.replace(/\[ETAD\]/g, etad);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (text.indexOf('[REASON]') !== -1) {
|
if (text.indexOf('[REASON]') !== -1) {
|
||||||
let reason = $select_reason.val() || "";
|
let reason = $select_reason.val();
|
||||||
text = text.replace(/\[REASON\]/g, reason);
|
if (ValidString(reason)) {
|
||||||
|
let reasonobj = window.semiautodata.reasons.find(r => r.tag === reason);
|
||||||
|
let reasontext = reasonobj ? reasonobj.value : "";
|
||||||
|
if (ValidString(reasontext)) {
|
||||||
|
text = text.replace(/\[REASON\]/g, reasontext);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (text.indexOf('[PLACES]') !== -1) {
|
if (text.indexOf('[PLACES]') !== -1) {
|
||||||
let placeTag = $select_places.val() || "";
|
let placeTag = $select_places.val();
|
||||||
text = text.replace(/\[PLACES\]/g, placeTag);
|
if (ValidString(placeTag)) {
|
||||||
|
let placeobj = window.semiautodata.places.find(p => p.tag === placeTag);
|
||||||
|
let placetext = placeobj ? placeobj.value : "";
|
||||||
|
if (ValidString(placetext)) {
|
||||||
|
text = text.replace(/\[PLACES\]/g, placetext);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (text.indexOf('[SHALAT]') !== -1) {
|
if (text.indexOf('[SHALAT]') !== -1) {
|
||||||
let shalatTag = $select_shalat.val() || "";
|
let shalatTag = $select_shalat.val();
|
||||||
text = text.replace(/\[SHALAT\]/g, shalatTag);
|
if (ValidString(shalatTag)) {
|
||||||
|
let shalatobj = window.semiautodata.shalat.find(s => s.tag === shalatTag);
|
||||||
|
let shalattext = shalatobj ? shalatobj.value : "";
|
||||||
|
if (ValidString(shalattext)) {
|
||||||
|
text = text.replace(/\[SHALAT\]/g, shalattext);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (text.indexOf('[BCB]') !== -1) {
|
if (text.indexOf('[BCB]') !== -1) {
|
||||||
let bcbTag = $select_bcb.val() || "";
|
let bcbTag = $select_bcb.val();
|
||||||
text = text.replace(/\[BCB\]/g, bcbTag);
|
if (ValidString(bcbTag)) {
|
||||||
|
text = text.replace(/\[BCB\]/g, bcbTag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (text.indexOf('[PROCEDURE]') !== -1) {
|
if (text.indexOf('[PROCEDURE]') !== -1) {
|
||||||
let procedureTag = $select_procedure.val() || "";
|
let procedureTag = $select_procedure.val();
|
||||||
text = text.replace(/\[PROCEDURE\]/g, procedureTag);
|
if (ValidString(procedureTag)) {
|
||||||
|
let procedureobj = window.semiautodata.procedures.find(p => p.tag === procedureTag);
|
||||||
|
let proceduretext = procedureobj ? procedureobj.value : "";
|
||||||
|
if (ValidString(proceduretext)) {
|
||||||
|
text = text.replace(/\[PROCEDURE\]/g, proceduretext);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (text.indexOf('[PLATNOMOR]') !== -1) {
|
if (text.indexOf('[PLATNOMOR]') !== -1) {
|
||||||
let platNomorTag = $select_licenseplate.val() || "";
|
let platNomorTag = $select_licenseplate.val();
|
||||||
text = text.replace(/\[PLATNOMOR\]/g, platNomorTag);
|
if (ValidString(platNomorTag)) {
|
||||||
|
text = text.replace(/\[PLATNOMOR\]/g, platNomorTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (text.indexOf('[SEQUENCE]') !== -1) {
|
||||||
|
let sequenceTag = $select_sequence.val();
|
||||||
|
if (ValidString(sequenceTag)) {
|
||||||
|
let sequenceobj = window.semiautodata.sequences.find(s => s.tag === sequenceTag);
|
||||||
|
let sequencetext = sequenceobj ? sequenceobj.value : "";
|
||||||
|
if (ValidString(sequencetext)) {
|
||||||
|
text = text.replace(/\[SEQUENCE\]/g, sequencetext);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($preview) {
|
if ($preview) {
|
||||||
@@ -559,13 +627,45 @@ function update_preview(language){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_all_previews(){
|
function isComplete(chkbox, preview) {
|
||||||
|
if (chkbox.is(":checked")) {
|
||||||
|
const text = preview.text();
|
||||||
|
if (text.indexOf('[') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (text.indexOf(']') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_complete_message() {
|
||||||
|
let complete = true;
|
||||||
|
if (!isComplete($enable_indonesia, $preview_indonesia)) complete = false;
|
||||||
|
if (!isComplete($enable_english, $preview_english)) complete = false;
|
||||||
|
if (!isComplete($enable_chinese, $preview_chinese)) complete = false;
|
||||||
|
if (!isComplete($enable_japanese, $preview_japanese)) complete = false;
|
||||||
|
if (!isComplete($enable_arabic, $preview_arabic)) complete = false;
|
||||||
|
if (!isComplete($enable_local, $preview_local)) complete = false;
|
||||||
|
|
||||||
|
if (complete) {
|
||||||
|
$("#send_broadcast").removeClass("btn-disable").addClass("btn-enable");
|
||||||
|
} else {
|
||||||
|
$("#send_broadcast").removeClass("btn-enable").addClass("btn-disable");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_all_previews() {
|
||||||
update_preview("indonesia");
|
update_preview("indonesia");
|
||||||
update_preview("english");
|
update_preview("english");
|
||||||
update_preview("chinese");
|
update_preview("chinese");
|
||||||
update_preview("japanese");
|
update_preview("japanese");
|
||||||
update_preview("arabic");
|
update_preview("arabic");
|
||||||
update_preview("local");
|
update_preview("local");
|
||||||
|
|
||||||
|
check_complete_message();
|
||||||
}
|
}
|
||||||
|
|
||||||
function fill_items() {
|
function fill_items() {
|
||||||
@@ -584,8 +684,7 @@ function fill_items() {
|
|||||||
$input_flightnumber.empty();
|
$input_flightnumber.empty();
|
||||||
$input_licenseplate.empty();
|
$input_licenseplate.empty();
|
||||||
$input_conveyorbelt.empty();
|
$input_conveyorbelt.empty();
|
||||||
$input_hours.empty();
|
$input_etad.empty();
|
||||||
$input_minutes.empty();
|
|
||||||
|
|
||||||
empty_preview();
|
empty_preview();
|
||||||
|
|
||||||
@@ -623,17 +722,17 @@ function fill_items() {
|
|||||||
$enable_indonesia.prop("checked", !!checked);
|
$enable_indonesia.prop("checked", !!checked);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (l.startsWith("english") ) {
|
if (l.startsWith("english")) {
|
||||||
if (checked) $preview_english.text(value); else $preview_english.empty();
|
if (checked) $preview_english.text(value); else $preview_english.empty();
|
||||||
$enable_english.prop("checked", !!checked);
|
$enable_english.prop("checked", !!checked);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (l.startsWith("chinese") ) {
|
if (l.startsWith("chinese")) {
|
||||||
if (checked) $preview_chinese.text(value); else $preview_chinese.empty();
|
if (checked) $preview_chinese.text(value); else $preview_chinese.empty();
|
||||||
$enable_chinese.prop("checked", !!checked);
|
$enable_chinese.prop("checked", !!checked);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (l.startsWith("japanese") ) {
|
if (l.startsWith("japanese")) {
|
||||||
if (checked) $preview_japanese.text(value); else $preview_japanese.empty();
|
if (checked) $preview_japanese.text(value); else $preview_japanese.empty();
|
||||||
$enable_japanese.prop("checked", !!checked);
|
$enable_japanese.prop("checked", !!checked);
|
||||||
return true;
|
return true;
|
||||||
@@ -660,6 +759,7 @@ function fill_items() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selected_messages = [];
|
||||||
if (willSelect) {
|
if (willSelect) {
|
||||||
// unselect other selected rows and clear their previews
|
// unselect other selected rows and clear their previews
|
||||||
$tbody_message.find("tr.table-active").each(function () {
|
$tbody_message.find("tr.table-active").each(function () {
|
||||||
@@ -676,8 +776,10 @@ function fill_items() {
|
|||||||
const sameMsgs = window.semiautodata.messages.filter(m => Number(m.id) === Number(groupId));
|
const sameMsgs = window.semiautodata.messages.filter(m => Number(m.id) === Number(groupId));
|
||||||
for (let m of sameMsgs) {
|
for (let m of sameMsgs) {
|
||||||
applyLang(m.language, m.message_details, true);
|
applyLang(m.language, m.message_details, true);
|
||||||
|
selected_messages.push(m);
|
||||||
}
|
}
|
||||||
enable_disable_fields();
|
enable_disable_fields();
|
||||||
|
update_all_previews();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// deselect this row and clear its previews
|
// deselect this row and clear its previews
|
||||||
@@ -742,7 +844,7 @@ function fill_items() {
|
|||||||
placeholder: "Select a place",
|
placeholder: "Select a place",
|
||||||
width: '100%',
|
width: '100%',
|
||||||
multiple: false,
|
multiple: false,
|
||||||
data: window.semiautodata.places.map(p => ({ id: p.tag, text: p.value }))
|
data: window.semiautodata.places.map(p => ({ id: p.tag, text: p.value + " [" + p.tag + "]" }))
|
||||||
});
|
});
|
||||||
|
|
||||||
$select_places.val(null).trigger("change");
|
$select_places.val(null).trigger("change");
|
||||||
@@ -756,7 +858,7 @@ function fill_items() {
|
|||||||
placeholder: "Select shalat time",
|
placeholder: "Select shalat time",
|
||||||
width: '100%',
|
width: '100%',
|
||||||
multiple: false,
|
multiple: false,
|
||||||
data: window.semiautodata.shalat.map(s => ({ id: s.tag, text: s.value }))
|
data: window.semiautodata.shalat.map(s => ({ id: s.tag, text: s.value + " [" + s.tag + "]" }))
|
||||||
});
|
});
|
||||||
$select_shalat.val(null).trigger("change");
|
$select_shalat.val(null).trigger("change");
|
||||||
}
|
}
|
||||||
@@ -765,7 +867,7 @@ function fill_items() {
|
|||||||
placeholder: "Select a reason",
|
placeholder: "Select a reason",
|
||||||
width: '100%',
|
width: '100%',
|
||||||
multiple: false,
|
multiple: false,
|
||||||
data: window.semiautodata.reasons.map(r => ({ id: r.tag, text: r.value }))
|
data: window.semiautodata.reasons.map(r => ({ id: r.tag, text: r.value + " [" + r.tag + "]" }))
|
||||||
});
|
});
|
||||||
$select_reason.val(null).trigger("change");
|
$select_reason.val(null).trigger("change");
|
||||||
$select_reason.on('change', function () {
|
$select_reason.on('change', function () {
|
||||||
@@ -778,7 +880,7 @@ function fill_items() {
|
|||||||
placeholder: "Select compensation",
|
placeholder: "Select compensation",
|
||||||
width: '100%',
|
width: '100%',
|
||||||
multiple: false,
|
multiple: false,
|
||||||
data: window.semiautodata.compensation.map(c => ({ id: c.tag, text: c.value }))
|
data: window.semiautodata.compensation.map(c => ({ id: c.tag, text: c.value + " [" + c.tag + "]" }))
|
||||||
});
|
});
|
||||||
$select_compensation.val(null).trigger("change");
|
$select_compensation.val(null).trigger("change");
|
||||||
$select_compensation.on('change', function () {
|
$select_compensation.on('change', function () {
|
||||||
@@ -790,7 +892,7 @@ function fill_items() {
|
|||||||
placeholder: "Select procedure",
|
placeholder: "Select procedure",
|
||||||
width: '100%',
|
width: '100%',
|
||||||
multiple: false,
|
multiple: false,
|
||||||
data: window.semiautodata.procedures.map(p => ({ id: p.tag, text: p.value }))
|
data: window.semiautodata.procedures.map(p => ({ id: p.tag, text: p.value + " [" + p.tag + "]" }))
|
||||||
});
|
});
|
||||||
$select_procedure.val(null).trigger("change");
|
$select_procedure.val(null).trigger("change");
|
||||||
$select_procedure.on('change', function () {
|
$select_procedure.on('change', function () {
|
||||||
@@ -814,10 +916,111 @@ function fill_items() {
|
|||||||
update_all_previews();
|
update_all_previews();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$input_etad.on('input', function () {
|
||||||
|
update_all_previews();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function send_broadcast_message() {
|
||||||
|
console.log("send_broadcast_message");
|
||||||
|
if ($("#send_broadcast").hasClass("btn-disable")) {
|
||||||
|
alert("Cannot send broadcast message. Please complete all required fields.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// get all checked broadcast zones from tbody_broadcastzones
|
||||||
|
let selected_zones = [];
|
||||||
|
$tbody_broadcastzones.find("input[type='checkbox']").each(function () {
|
||||||
|
const $checkbox = $(this);
|
||||||
|
if ($checkbox.is(":checked")) {
|
||||||
|
selected_zones.push($checkbox.val());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selected_zones.length === 0) {
|
||||||
|
alert("Please select at least one broadcast zone.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let languages = [];
|
||||||
|
if ($enable_indonesia.is(":checked")) languages.push("INDONESIA");
|
||||||
|
if ($enable_english.is(":checked")) languages.push("ENGLISH");
|
||||||
|
if ($enable_chinese.is(":checked")) languages.push("CHINESE");
|
||||||
|
if ($enable_japanese.is(":checked")) languages.push("JAPANESE");
|
||||||
|
if ($enable_arabic.is(":checked")) languages.push("ARABIC");
|
||||||
|
if ($enable_local.is(":checked")) languages.push("LOCAL");
|
||||||
|
|
||||||
|
if (languages.length === 0) {
|
||||||
|
alert("Please select at least one language to send.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let description = selected_messages.length > 0 ? selected_messages[0].description : "";
|
||||||
|
if (!ValidString(description)) {
|
||||||
|
alert("Message not selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tags = [];
|
||||||
|
let msg = selected_messages[0];
|
||||||
|
tags.push("ANN_ID:"+msg.id);
|
||||||
|
if (msg.message_details.indexOf('[CITY]') !== -1) {
|
||||||
|
let cities = $select_city.val();
|
||||||
|
if (Array.isArray(cities) && cities.length > 0) {
|
||||||
|
tags.push("CITY:" + cities.join(";"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg.message_details.indexOf('[AIRPLANE_NAME]') !== -1) {
|
||||||
|
tags.push("AL:" + $select_airline.val());
|
||||||
|
}
|
||||||
|
if (msg.message_details.indexOf('[FLIGHT_NUMBER]') !== -1) {
|
||||||
|
tags.push("FLNUM:" + $input_flightnumber.val());
|
||||||
|
}
|
||||||
|
if (msg.message_details.indexOf('[PLATNOMOR]') !== -1) {
|
||||||
|
tags.push("PLATNOMOR:" + $input_licenseplate.val());
|
||||||
|
}
|
||||||
|
if (msg.message_details.indexOf('[PLACES]') !== -1) {
|
||||||
|
tags.push("PLACES:" + $select_places.val());
|
||||||
|
}
|
||||||
|
if (msg.message_details.indexOf('[ETAD]') !== -1) {
|
||||||
|
tags.push("ETAD:" + $input_etad.val());
|
||||||
|
}
|
||||||
|
if (msg.message_details.indexOf('[SHALAT]') !== -1) {
|
||||||
|
tags.push("SHALAT:" + $select_shalat.val());
|
||||||
|
}
|
||||||
|
if (msg.message_details.indexOf('[BCB]') !== -1) {
|
||||||
|
tags.push("BCB:" + $input_conveyorbelt.val());
|
||||||
|
}
|
||||||
|
if (msg.message_details.indexOf('[GATENUMBER]') !== -1) {
|
||||||
|
tags.push("GATECODE:" + $input_gatenumber.val());
|
||||||
|
}
|
||||||
|
if (msg.message_details.indexOf('[REASON]') !== -1) {
|
||||||
|
tags.push("REASON:" + $select_reason.val());
|
||||||
|
}
|
||||||
|
if (msg.message_details.indexOf('[PROCEDURE]') !== -1) {
|
||||||
|
tags.push("PROCEDURE:" + $select_procedure.val());
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload = {
|
||||||
|
description: description,
|
||||||
|
languages: languages.join(";"),
|
||||||
|
tags: tags.join(" "),
|
||||||
|
broadcastzones: selected_zones.join(";")
|
||||||
|
}
|
||||||
|
console.log("Payload:", payload);
|
||||||
|
fetchAPI("SemiAuto", "POST", {}, payload, (data) => {
|
||||||
|
alert("Broadcast message sent successfully.");
|
||||||
|
}, (error) => {
|
||||||
|
console.error("Error:", error);
|
||||||
|
alert("Error sending broadcast message: " + error);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// App start here
|
// App start here
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
console.log("javascript loaded");
|
console.log("javascript loaded");
|
||||||
@@ -846,8 +1049,7 @@ $(document).ready(function () {
|
|||||||
$input_flightnumber = $("#input_flightnumber");
|
$input_flightnumber = $("#input_flightnumber");
|
||||||
$input_licenseplate = $("#input_licenseplate");
|
$input_licenseplate = $("#input_licenseplate");
|
||||||
$input_conveyorbelt = $("#input_conveyorbelt");
|
$input_conveyorbelt = $("#input_conveyorbelt");
|
||||||
$input_hours = $("#input_hours");
|
$input_etad = $("#input_etad");
|
||||||
$input_minutes = $("#input_minutes");
|
|
||||||
$row_airplane = $("#row_airplane");
|
$row_airplane = $("#row_airplane");
|
||||||
$row_city = $("#row_city");
|
$row_city = $("#row_city");
|
||||||
$row_gatenumber = $("#row_gatenumber");
|
$row_gatenumber = $("#row_gatenumber");
|
||||||
@@ -864,5 +1066,9 @@ $(document).ready(function () {
|
|||||||
reload_database();
|
reload_database();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#send_broadcast").off("click").on("click", function () {
|
||||||
|
send_broadcast_message();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
<div class="col-5 pad-row-input">
|
<div class="col-5 pad-row-input">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-5"><label class="col-form-label">Flight Number</label></div>
|
<div class="col-5"><label class="col-form-label">Flight Number</label></div>
|
||||||
<div class="col-7 pad-select"><input type="text" id="input_flightnumber" class="form-control pad-input"></div>
|
<div class="col-7 pad-select"><input type="text" id="input_flightnumber" class="form-control pad-input" placeholder="flight number" minlength="1" maxlength="4" inputmode="numeric"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,18 +123,8 @@
|
|||||||
<div class="col-8 col-sm-8 col-md-9 col-lg-9 col-xl-10 pad-input-left pad-row-input"><input type="text" id="input_gatenumber" class="pad-input form-control" placeholder="gate number"></div>
|
<div class="col-8 col-sm-8 col-md-9 col-lg-9 col-xl-10 pad-input-left pad-row-input"><input type="text" id="input_gatenumber" class="pad-input form-control" placeholder="gate number"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" id="row_time">
|
<div class="row" id="row_time">
|
||||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6 pad-row-input">
|
<div class="col-4 col-sm-4 col-md-3 col-lg-3 col-xl-2 pad-row-input"><label class="col-form-label">ETAD</label></div>
|
||||||
<div class="row">
|
<div class="col"><input id="input_etad" type="time"></div>
|
||||||
<div class="col-4 col-sm-4 col-md-6 col-lg-6 col-xl-4"><label class="col-form-label">Hours</label></div>
|
|
||||||
<div class="col-8 col-sm-8 col-md-6 col-lg-6 col-xl-8 pad-select"><input type="number" id="input_hours" class="form-control pad-input" value="0" min="0" max="23" step="1" placeholder="hour"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6 pad-row-input">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4 col-sm-4 col-md-6 col-lg-6 col-xl-4"><label class="col-form-label">Minutes</label></div>
|
|
||||||
<div class="col-8 col-sm-8 col-md-6 col-lg-6 col-xl-8 pad-select"><input type="number" id="input_minutes" class="form-control pad-input" value="0" min="0" max="59" step="1" placeholder="minute"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6 pad-row-input" id="col_reason">
|
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6 pad-row-input" id="col_reason">
|
||||||
@@ -236,7 +226,7 @@
|
|||||||
<path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747"></path>
|
<path d="M19.933 13.041a8 8 0 1 1 -9.925 -8.788c3.899 -1 7.935 1.007 9.425 4.747"></path>
|
||||||
<path d="M20 4v5h-5"></path>
|
<path d="M20 4v5h-5"></path>
|
||||||
</svg> Reload Database</button></div>
|
</svg> Reload Database</button></div>
|
||||||
<div class="col-sm-6 col-md-6 col-lg-6 col-xl-4 col-xxl-4 py-2"><button class="btn btn-primary w-100 pad-input btn-broadcast" id="send_broadcast" type="button" style="font-family: Raleway, sans-serif;box-shadow: 0px 0px 0px 0px var(--bs-blue);border-color: rgba(255,255,255,0.5);"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icon-tabler-send pad-icon">
|
<div class="col-sm-6 col-md-6 col-lg-6 col-xl-4 col-xxl-4 py-2"><button class="btn w-100 pad-input btn-disable" id="send_broadcast" type="button" style="font-family: Raleway, sans-serif;box-shadow: 0px 0px 0px 0px var(--bs-blue);border-color: rgba(255,255,255,0.5);"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icon-tabler-send pad-icon">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
<path d="M10 14l11 -11"></path>
|
<path d="M10 14l11 -11"></path>
|
||||||
<path d="M21 3l-6.5 18a.55 .55 0 0 1 -1 0l-3.5 -7l-7 -3.5a.55 .55 0 0 1 0 -1l18 -6.5"></path>
|
<path d="M21 3l-6.5 18a.55 .55 0 0 1 -1 0l-3.5 -7l-7 -3.5a.55 .55 0 0 1 0 -1l18 -6.5"></path>
|
||||||
|
|||||||
@@ -1337,7 +1337,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="accordion-item invisible pad-accordion">
|
<div class="accordion-item pad-accordion">
|
||||||
<h2 class="accordion-header" role="tab"><button class="accordion-button bg-heading3" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-4" aria-expanded="true" aria-controls="accordion-1 .item-4">Remote Listening</button></h2>
|
<h2 class="accordion-header" role="tab"><button class="accordion-button bg-heading3" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-4" aria-expanded="true" aria-controls="accordion-1 .item-4">Remote Listening</button></h2>
|
||||||
<div class="accordion-collapse collapse show item-4" role="tabpanel" data-bs-parent="#accordion-1">
|
<div class="accordion-collapse collapse show item-4" role="tabpanel" data-bs-parent="#accordion-1">
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
|
|||||||
BIN
libs/linux-aarch64/libbassenc_mp3.so
Normal file
BIN
libs/linux-aarch64/libbassenc_mp3.so
Normal file
Binary file not shown.
BIN
libs/linux-aarch64/libbassenc_ogg.so
Normal file
BIN
libs/linux-aarch64/libbassenc_ogg.so
Normal file
Binary file not shown.
BIN
libs/linux-aarch64/libbassenc_opus.so
Normal file
BIN
libs/linux-aarch64/libbassenc_opus.so
Normal file
Binary file not shown.
BIN
libs/linux-armhf/libbassenc_mp3.so
Normal file
BIN
libs/linux-armhf/libbassenc_mp3.so
Normal file
Binary file not shown.
BIN
libs/linux-armhf/libbassenc_ogg.so
Normal file
BIN
libs/linux-armhf/libbassenc_ogg.so
Normal file
Binary file not shown.
BIN
libs/linux-armhf/libbassenc_opus.so
Normal file
BIN
libs/linux-armhf/libbassenc_opus.so
Normal file
Binary file not shown.
BIN
libs/linux-x86-64/libbassenc_mp3.so
Normal file
BIN
libs/linux-x86-64/libbassenc_mp3.so
Normal file
Binary file not shown.
BIN
libs/linux-x86-64/libbassenc_ogg.so
Normal file
BIN
libs/linux-x86-64/libbassenc_ogg.so
Normal file
Binary file not shown.
BIN
libs/linux-x86-64/libbassenc_opus.so
Normal file
BIN
libs/linux-x86-64/libbassenc_opus.so
Normal file
Binary file not shown.
BIN
libs/linux-x86/libbassenc_mp3.so
Normal file
BIN
libs/linux-x86/libbassenc_mp3.so
Normal file
Binary file not shown.
BIN
libs/linux-x86/libbassenc_ogg.so
Normal file
BIN
libs/linux-x86/libbassenc_ogg.so
Normal file
Binary file not shown.
BIN
libs/linux-x86/libbassenc_opus.so
Normal file
BIN
libs/linux-x86/libbassenc_opus.so
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
libs/win32-x86-64/bassenc_mp3.dll
Normal file
BIN
libs/win32-x86-64/bassenc_mp3.dll
Normal file
Binary file not shown.
BIN
libs/win32-x86-64/bassenc_ogg.dll
Normal file
BIN
libs/win32-x86-64/bassenc_ogg.dll
Normal file
Binary file not shown.
BIN
libs/win32-x86-64/bassenc_opus.dll
Normal file
BIN
libs/win32-x86-64/bassenc_opus.dll
Normal file
Binary file not shown.
BIN
libs/win32-x86/bassenc_mp3.dll
Normal file
BIN
libs/win32-x86/bassenc_mp3.dll
Normal file
Binary file not shown.
BIN
libs/win32-x86/bassenc_ogg.dll
Normal file
BIN
libs/win32-x86/bassenc_ogg.dll
Normal file
Binary file not shown.
BIN
libs/win32-x86/bassenc_opus.dll
Normal file
BIN
libs/win32-x86/bassenc_opus.dll
Normal file
Binary file not shown.
@@ -401,7 +401,7 @@ class MainExtension01 {
|
|||||||
|
|
||||||
"[ETAD]" -> {
|
"[ETAD]" -> {
|
||||||
val values = variables["ETAD"].orEmpty().split(":").map { it.trim() }.filter { IsNumber(it) }
|
val values = variables["ETAD"].orEmpty().split(":").map { it.trim() }.filter { IsNumber(it) }
|
||||||
println("ETAD values: $values")
|
//println("ETAD values: $values")
|
||||||
if (values.size == 2) {
|
if (values.size == 2) {
|
||||||
if (IsNumber(values[0]) && IsNumber(values[1])) {
|
if (IsNumber(values[0]) && IsNumber(values[1])) {
|
||||||
val _h = values[0].toInt()
|
val _h = values[0].toInt()
|
||||||
|
|||||||
@@ -16,9 +16,12 @@ import contentCache
|
|||||||
import org.tinylog.Logger
|
import org.tinylog.Logger
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class AudioPlayer (var samplingrate: Int) {
|
class AudioPlayer (var samplingrate: Int = 44100) {
|
||||||
val bass: Bass = Bass.Instance
|
val bass: Bass = Bass.Instance
|
||||||
val bassenc : BassEnc = BassEnc.Instance
|
val bassenc : BassEnc = BassEnc.Instance
|
||||||
|
val bassencmp3: BassEncMP3 = BassEncMP3.Instance
|
||||||
|
val bassencopus: BassEncOpus = BassEncOpus.Instance
|
||||||
|
val bassencogg : BassEncOGG = BassEncOGG.Instance
|
||||||
var initedDevice = -1
|
var initedDevice = -1
|
||||||
|
|
||||||
|
|
||||||
@@ -28,6 +31,9 @@ class AudioPlayer (var samplingrate: Int) {
|
|||||||
if (samplingrate<1) samplingrate = 44100 // Default sampling rate
|
if (samplingrate<1) samplingrate = 44100 // Default sampling rate
|
||||||
Logger.info {"Bass version ${Integer.toHexString(bass.BASS_GetVersion())}"}
|
Logger.info {"Bass version ${Integer.toHexString(bass.BASS_GetVersion())}"}
|
||||||
Logger.info { "BassEnc version ${Integer.toHexString(bassenc.BASS_Encode_GetVersion())}" }
|
Logger.info { "BassEnc version ${Integer.toHexString(bassenc.BASS_Encode_GetVersion())}" }
|
||||||
|
Logger.info { "BassEncMP3 version ${Integer.toHexString(bassencmp3.BASS_Encode_MP3_GetVersion())}" }
|
||||||
|
Logger.info { "BassEncOpus version ${Integer.toHexString(bassencopus.BASS_Encode_OPUS_GetVersion())}" }
|
||||||
|
Logger.info {" BassEncOGG version ${Integer.toHexString(bassencogg.BASS_Encode_OGG_GetVersion())}"}
|
||||||
InitAudio(0) // Audio 0 is No Sound, use for reading and writing wav silently
|
InitAudio(0) // Audio 0 is No Sound, use for reading and writing wav silently
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public interface BassEnc extends Library {
|
|||||||
* @param length number of bytes
|
* @param length number of bytes
|
||||||
* @param user the user pointer passed to BASS_Encode_Start
|
* @param user the user pointer passed to BASS_Encode_Start
|
||||||
*/
|
*/
|
||||||
void ENCODEPROC(int encoderhandle, int channelhandle, Memory encodedData, int length, Pointer user);
|
void ENCODEPROC(int encoderhandle, int channelhandle, Pointer encodedData, int length, Pointer user);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ENCODEPROCEX extends Callback {
|
interface ENCODEPROCEX extends Callback {
|
||||||
@@ -101,7 +101,7 @@ public interface BassEnc extends Library {
|
|||||||
* @param offset file offset of the data
|
* @param offset file offset of the data
|
||||||
* @param user the user pointer passed to BASS_Encode_Start
|
* @param user the user pointer passed to BASS_Encode_Start
|
||||||
*/
|
*/
|
||||||
void ENCODEPROCEX(int handle, int channel, Memory buffer, int length, long offset, Object user);
|
void ENCODEPROCEX(int handle, int channel, Pointer buffer, int length, long offset, Pointer user);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ENCODERPROC extends Callback {
|
interface ENCODERPROC extends Callback {
|
||||||
@@ -115,7 +115,7 @@ public interface BassEnc extends Library {
|
|||||||
* @param user the user pointer passed to BASS_Encode_Start
|
* @param user the user pointer passed to BASS_Encode_Start
|
||||||
* @return the amount of encoded data (-1 = stop)
|
* @return the amount of encoded data (-1 = stop)
|
||||||
*/
|
*/
|
||||||
int ENCODERPROC(int encoderHandle, int channelHandle, Memory encodedData, int length, int maxOut, Pointer user);
|
int ENCODERPROC(int encoderHandle, int channelHandle, Pointer encodedData, int length, int maxOut, Pointer user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
13
src/audio/BassEncMP3.java
Normal file
13
src/audio/BassEncMP3.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package audio;
|
||||||
|
|
||||||
|
import com.sun.jna.Library;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public interface BassEncMP3 extends Library {
|
||||||
|
|
||||||
|
BassEncMP3 Instance = (BassEncMP3) com.sun.jna.Native.load("bassenc_mp3", BassEncMP3.class);
|
||||||
|
int BASS_Encode_MP3_GetVersion();
|
||||||
|
int BASS_Encode_MP3_Start(int handle, String options, int flags, BassEnc.ENCODEPROCEX proc, Object user);
|
||||||
|
int BASS_Encode_MP3_StartFile(int handle, String options, int flags, String filename);
|
||||||
|
|
||||||
|
}
|
||||||
15
src/audio/BassEncOGG.java
Normal file
15
src/audio/BassEncOGG.java
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package audio;
|
||||||
|
|
||||||
|
import com.sun.jna.Library;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public interface BassEncOGG extends Library {
|
||||||
|
|
||||||
|
BassEncOGG Instance = (BassEncOGG) com.sun.jna.Native.load("bassenc_ogg", BassEncOGG.class);
|
||||||
|
int BASS_ENCODE_OGG_RESET = 0x1000000;
|
||||||
|
int BASS_Encode_OGG_GetVersion();
|
||||||
|
int BASS_Encode_OGG_Start(int handle, String options, int flags, BassEnc.ENCODEPROC proc, Object user);
|
||||||
|
int BASS_Encode_OGG_StartFile(int handle, String options, int flags, String filename);
|
||||||
|
boolean BASS_Encode_OGG_NewStream(int handle, String options, int flags);
|
||||||
|
|
||||||
|
}
|
||||||
18
src/audio/BassEncOpus.java
Normal file
18
src/audio/BassEncOpus.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package audio;
|
||||||
|
|
||||||
|
import com.sun.jna.Library;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public interface BassEncOpus extends Library {
|
||||||
|
BassEncOpus Instance = (BassEncOpus) com.sun.jna.Native.load("bassenc_opus", BassEncOpus.class);
|
||||||
|
int BASS_ENCODE_OPUS_RESET = 0x1000000;
|
||||||
|
int BASS_ENCODE_OPUS_CTLONLY = 0x2000000;
|
||||||
|
|
||||||
|
|
||||||
|
int BASS_Encode_OPUS_GetVersion();
|
||||||
|
int BASS_Encode_OPUS_Start(int handle, String options, int flags, BassEnc.ENCODEPROC proc, Object user);
|
||||||
|
int BASS_Encode_OPUS_StartFile(int handle, String options, int flags, String filename);
|
||||||
|
boolean BASS_Encode_OPUS_NewStream(int handle, String options, int flags);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
126
src/audio/Mp3Encoder.kt
Normal file
126
src/audio/Mp3Encoder.kt
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package audio
|
||||||
|
|
||||||
|
import audioPlayer
|
||||||
|
import com.sun.jna.Memory
|
||||||
|
import com.sun.jna.Pointer
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
import org.tinylog.Logger
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
class Mp3Encoder(val samplingrate: Int=44100, val channels: Int=1) {
|
||||||
|
var push_handle: Int = 0
|
||||||
|
var mp3_handle: Int = 0
|
||||||
|
private val bass = audioPlayer.bass
|
||||||
|
private val bassencmp3 = audioPlayer.bassencmp3
|
||||||
|
var callback : Consumer<ByteArray>? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the encoder is started
|
||||||
|
* @return true if started, false otherwise
|
||||||
|
*/
|
||||||
|
fun isStarted() : Boolean {
|
||||||
|
return push_handle!=0 && mp3_handle!=0
|
||||||
|
}
|
||||||
|
|
||||||
|
val proc = BassEnc.ENCODEPROCEX{
|
||||||
|
enchandle, sourcehandle, buffer, length, offset, user ->
|
||||||
|
if (enchandle == mp3_handle) {
|
||||||
|
if (sourcehandle == push_handle) {
|
||||||
|
val data = ByteArray(length)
|
||||||
|
buffer.read(0, data, 0, length)
|
||||||
|
callback?.accept(data)
|
||||||
|
} else Logger.error { "MP3 Encoder callback called with unknown source handle: $sourcehandle" }
|
||||||
|
} else Logger.error { "MP3 Encoder callback called with unknown encoder handle: $enchandle" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the MP3 encoder
|
||||||
|
* @param cb Function to receive encoded MP3 data
|
||||||
|
*/
|
||||||
|
fun Start(cb: Consumer<ByteArray>){
|
||||||
|
callback = null
|
||||||
|
push_handle = bass.BASS_StreamCreate(samplingrate, channels, Bass.BASS_STREAM_DECODE, Pointer(-1), null)
|
||||||
|
if (push_handle!=0){
|
||||||
|
Logger.info{"MP3 Encoder initialized with sampling rate $samplingrate Hz and $channels channel(s)" }
|
||||||
|
|
||||||
|
mp3_handle = bassencmp3.BASS_Encode_MP3_Start(push_handle, null, BassEnc.BASS_ENCODE_AUTOFREE,proc, null)
|
||||||
|
|
||||||
|
if (mp3_handle!=0){
|
||||||
|
callback = cb
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
val readsize = 4*1024 // 4 K buffer
|
||||||
|
Logger.info{"MP3 Encoder started successfully." }
|
||||||
|
while(isActive && push_handle!=0){
|
||||||
|
val p = Memory(readsize.toLong())
|
||||||
|
val read = bass.BASS_ChannelGetData(push_handle, p, readsize)
|
||||||
|
|
||||||
|
if (read==-1){
|
||||||
|
val err = bass.BASS_ErrorGetCode()
|
||||||
|
Logger.error{"Failed to read data from MP3 Encoder stream. BASS error code: $err" }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
delay(2)
|
||||||
|
}
|
||||||
|
Logger.info{"MP3 Encoder finished successfully." }
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
val err = bass.BASS_ErrorGetCode()
|
||||||
|
Logger.error{"Failed to start MP3 Encoder. BASS error code: $err"}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val err = bass.BASS_ErrorGetCode()
|
||||||
|
Logger.error{"Failed to initialize MP3 Encoder. BASS error code: $err" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push PCM data to be encoded
|
||||||
|
* @param data PCM data in ByteArray
|
||||||
|
* @return Number of bytes written, or 0 if failed
|
||||||
|
*/
|
||||||
|
fun PushData(data: ByteArray): Int {
|
||||||
|
if (push_handle==0){
|
||||||
|
Logger.error{"MP3 Encoder is not started." }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
val mem = Memory(data.size.toLong())
|
||||||
|
mem.write(0, data, 0, data.size)
|
||||||
|
val written = bass.BASS_StreamPutData(push_handle, mem, data.size)
|
||||||
|
if (written==-1){
|
||||||
|
val err = bass.BASS_ErrorGetCode()
|
||||||
|
Logger.error{"Failed to push data to MP3 Encoder. BASS error code: $err" }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
//println("MP3 Encoder: Pushed $written bytes of PCM data.")
|
||||||
|
return written
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the MP3 encoder and free resources
|
||||||
|
*/
|
||||||
|
fun Stop(){
|
||||||
|
|
||||||
|
if (push_handle!=0){
|
||||||
|
val res = bass.BASS_StreamFree(push_handle)
|
||||||
|
if (res){
|
||||||
|
Logger.info{"MP3 Encoder stream freed successfully." }
|
||||||
|
} else {
|
||||||
|
val err = bass.BASS_ErrorGetCode()
|
||||||
|
Logger.error{"Failed to free MP3 Encoder stream. BASS error code: $err"}
|
||||||
|
}
|
||||||
|
push_handle = 0
|
||||||
|
}
|
||||||
|
// auto close by BASS_ENCODE_AUTOFREE
|
||||||
|
mp3_handle = 0
|
||||||
|
callback = null
|
||||||
|
println("MP3 Encoder: Stopped.")
|
||||||
|
}
|
||||||
|
}
|
||||||
127
src/audio/OpusEncoder.kt
Normal file
127
src/audio/OpusEncoder.kt
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
package audio
|
||||||
|
|
||||||
|
import audioPlayer
|
||||||
|
import com.sun.jna.Memory
|
||||||
|
import com.sun.jna.Pointer
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
import org.tinylog.Logger
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
class OpusEncoder(val samplingrate: Int=44100, val channels: Int=1) {
|
||||||
|
var push_handle: Int = 0
|
||||||
|
var opus_handle: Int = 0
|
||||||
|
private val bass = audioPlayer.bass
|
||||||
|
private val bassencopus = audioPlayer.bassencopus
|
||||||
|
var callback : Consumer<ByteArray>? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the encoder is started
|
||||||
|
* @return true if started, false otherwise
|
||||||
|
*/
|
||||||
|
fun isStarted() : Boolean {
|
||||||
|
return push_handle!=0 && opus_handle!=0
|
||||||
|
}
|
||||||
|
|
||||||
|
val proc = BassEnc.ENCODEPROC{
|
||||||
|
enchandle, sourcehandle, buffer, length, user ->
|
||||||
|
if (enchandle == opus_handle) {
|
||||||
|
if (sourcehandle == push_handle) {
|
||||||
|
val data = ByteArray(length)
|
||||||
|
buffer.read(0, data, 0, length)
|
||||||
|
callback?.accept(data)
|
||||||
|
//println("MP3 Encoder callback: Sent $length bytes")
|
||||||
|
} else Logger.error { "Opus Encoder callback called with unknown source handle: $sourcehandle" }
|
||||||
|
} else Logger.error { "Opus Encoder callback called with unknown encoder handle: $enchandle" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the Opus encoder
|
||||||
|
* @param cb Function to receive encoded MP3 data
|
||||||
|
*/
|
||||||
|
fun Start(cb: Consumer<ByteArray>){
|
||||||
|
callback = null
|
||||||
|
push_handle = bass.BASS_StreamCreate(samplingrate, channels, Bass.BASS_STREAM_DECODE, Pointer(-1), null)
|
||||||
|
if (push_handle!=0){
|
||||||
|
Logger.info{"Opus Encoder initialized with sampling rate $samplingrate Hz and $channels channel(s)" }
|
||||||
|
|
||||||
|
opus_handle = bassencopus.BASS_Encode_OPUS_Start(push_handle, null, BassEnc.BASS_ENCODE_AUTOFREE, proc, null)
|
||||||
|
|
||||||
|
if (opus_handle!=0){
|
||||||
|
callback = cb
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
val readsize = 8 * 1024 * 1024 // 8 MB buffer
|
||||||
|
Logger.info{"Opus Encoder started successfully." }
|
||||||
|
while(isActive && push_handle!=0){
|
||||||
|
delay(2)
|
||||||
|
val p = Memory(readsize.toLong())
|
||||||
|
val read = bass.BASS_ChannelGetData(push_handle, p, readsize)
|
||||||
|
|
||||||
|
if (read==-1){
|
||||||
|
val err = bass.BASS_ErrorGetCode()
|
||||||
|
Logger.error{"Failed to read data from Opus Encoder stream. BASS error code: $err" }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logger.info{"Opus Encoder finished successfully." }
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
val err = bass.BASS_ErrorGetCode()
|
||||||
|
Logger.error{"Failed to start Opus Encoder. BASS error code: $err"}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val err = bass.BASS_ErrorGetCode()
|
||||||
|
Logger.error{"Failed to initialize Opus Encoder. BASS error code: $err" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push PCM data to be encoded
|
||||||
|
* @param data PCM data in ByteArray
|
||||||
|
* @return Number of bytes written, or 0 if failed
|
||||||
|
*/
|
||||||
|
fun PushData(data: ByteArray): Int {
|
||||||
|
if (push_handle==0){
|
||||||
|
Logger.error{"Opus Encoder is not started." }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
val mem = Memory(data.size.toLong())
|
||||||
|
mem.write(0, data, 0, data.size)
|
||||||
|
val written = bass.BASS_StreamPutData(push_handle, mem, data.size)
|
||||||
|
if (written==-1){
|
||||||
|
val err = bass.BASS_ErrorGetCode()
|
||||||
|
Logger.error{"Failed to push data to Opus Encoder. BASS error code: $err" }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
//println("Opus Encoder: Pushed $written bytes of PCM data.")
|
||||||
|
return written
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the Opus encoder and free resources
|
||||||
|
*/
|
||||||
|
fun Stop(){
|
||||||
|
|
||||||
|
if (push_handle!=0){
|
||||||
|
val res = bass.BASS_StreamFree(push_handle)
|
||||||
|
if (res){
|
||||||
|
Logger.info{"Opus Encoder stream freed successfully." }
|
||||||
|
} else {
|
||||||
|
val err = bass.BASS_ErrorGetCode()
|
||||||
|
Logger.error{"Failed to free Opus Encoder stream. BASS error code: $err"}
|
||||||
|
}
|
||||||
|
push_handle = 0
|
||||||
|
}
|
||||||
|
// auto close by BASS_ENCODE_AUTOFREE
|
||||||
|
opus_handle = 0
|
||||||
|
callback = null
|
||||||
|
println("Opus Encoder: Stopped.")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package barix
|
package barix
|
||||||
|
|
||||||
|
import audio.Mp3Encoder
|
||||||
|
import audio.OpusEncoder
|
||||||
import codes.Somecodes
|
import codes.Somecodes
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -24,37 +26,37 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
|
|||||||
private val inet = InetSocketAddress(ipaddress, port)
|
private val inet = InetSocketAddress(ipaddress, port)
|
||||||
private val maxUDPsize = 1000
|
private val maxUDPsize = 1000
|
||||||
private var _tcp: Socket? = null
|
private var _tcp: Socket? = null
|
||||||
private val PipeOuts = mutableMapOf<String,PipedOutputStream>()
|
private val pipeOuts = mutableMapOf<String,PipedOutputStream>()
|
||||||
|
private val mp3encoder = Mp3Encoder()
|
||||||
|
|
||||||
fun AddPipeOut(key: String, pipeOut: PipedOutputStream) {
|
fun AddPipeOut(key: String, pipeOut: PipedOutputStream) {
|
||||||
RemovePipeOut(key)
|
RemovePipeOut(key)
|
||||||
PipeOuts[key] = pipeOut
|
pipeOuts[key] = pipeOut
|
||||||
println("Added pipeOut $key to BarixConnection $channel ($ipaddress)")
|
println("Added pipeOut $key to BarixConnection $channel ($ipaddress)")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RemovePipeOut(key: String) {
|
fun RemovePipeOut(key: String) {
|
||||||
println("Removing pipeOut $key")
|
if (pipeOuts.contains(key)){
|
||||||
if (PipeOuts.contains(key)){
|
val pipe = pipeOuts[key]
|
||||||
val pipe = PipeOuts[key]
|
|
||||||
try {
|
try {
|
||||||
pipe?.close()
|
pipe?.close()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
println("Removed pipeOut $key from BarixConnection $channel ($ipaddress)")
|
println("Removed pipeOut $key from BarixConnection $channel ($ipaddress)")
|
||||||
PipeOuts.remove(key)
|
pipeOuts.remove(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ClearPipeOuts() {
|
fun ClearPipeOuts() {
|
||||||
PipeOuts.values.forEach { piped ->
|
pipeOuts.values.forEach { piped ->
|
||||||
try {
|
try {
|
||||||
piped.close()
|
piped.close()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PipeOuts.clear()
|
pipeOuts.clear()
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Buffer remain in bytes
|
* Buffer remain in bytes
|
||||||
@@ -137,34 +139,40 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
|
|||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
DatagramSocket().use{ udp ->
|
DatagramSocket().use{ udp ->
|
||||||
val bb = ByteBuffer.wrap(data)
|
val bb = ByteBuffer.wrap(data)
|
||||||
|
if (!mp3encoder.isStarted()) {
|
||||||
|
mp3encoder.Start { data ->
|
||||||
|
pipeOuts.keys.forEach { kk ->
|
||||||
|
val pp = pipeOuts[kk]
|
||||||
|
try {
|
||||||
|
pp?.write(data)
|
||||||
|
pp?.flush()
|
||||||
|
println("Written ${data.size} bytes to pipeOut $kk")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.error { "Failed to write to pipeOut $kk, message: ${e.message}" }
|
||||||
|
pp?.close()
|
||||||
|
pipeOuts.remove(kk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
while(bb.hasRemaining()){
|
while(bb.hasRemaining()){
|
||||||
try {
|
try {
|
||||||
val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining())
|
val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining())
|
||||||
bb.get(chunk)
|
bb.get(chunk)
|
||||||
//println("Buffer remain: $bufferRemain, sending chunk size: ${chunk.size}")
|
|
||||||
while(bufferRemain<chunk.size){
|
while(bufferRemain<chunk.size){
|
||||||
delay(10)
|
delay(10)
|
||||||
}
|
}
|
||||||
udp.send(DatagramPacket(chunk, chunk.size, inet))
|
udp.send(DatagramPacket(chunk, chunk.size, inet))
|
||||||
|
mp3encoder.PushData(chunk)
|
||||||
delay(2)
|
delay(2)
|
||||||
PipeOuts.keys.forEach { kk ->
|
|
||||||
val pp = PipeOuts[kk]
|
|
||||||
try {
|
|
||||||
pp?.write(chunk)
|
|
||||||
pp?.flush()
|
|
||||||
println("Written ${chunk.size} bytes to pipeOut $kk")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Logger.error { "Failed to write to pipeOut $kk, message: ${e.message}" }
|
|
||||||
pp?.close()
|
|
||||||
PipeOuts.remove(kk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
cbFail.accept("SendData to $ipaddress failed, message: ${e.message}")
|
cbFail.accept("SendData to $ipaddress failed, message: ${e.message}")
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mp3encoder.Stop()
|
||||||
cbOK.accept("SendData to $channel ($ipaddress) succeeded, ${data.size} bytes sent")
|
cbOK.accept("SendData to $channel ($ipaddress) succeeded, ${data.size} bytes sent")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,13 +49,14 @@ import java.io.File
|
|||||||
import java.io.PipedInputStream
|
import java.io.PipedInputStream
|
||||||
import java.io.PipedOutputStream
|
import java.io.PipedOutputStream
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val _config: configFile) {
|
class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val _config: configFile) {
|
||||||
|
|
||||||
lateinit var app: Javalin
|
lateinit var app: Javalin
|
||||||
lateinit var semiauto : Javalin
|
lateinit var semiauto: Javalin
|
||||||
val objectmapper = jacksonObjectMapper()
|
val objectmapper = jacksonObjectMapper()
|
||||||
|
|
||||||
private fun SendReply(context: WsMessageContext, command: String, value: String) {
|
private fun SendReply(context: WsMessageContext, command: String, value: String) {
|
||||||
@@ -73,7 +74,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Start_WebServer(){
|
private fun Start_WebServer() {
|
||||||
Logger.info { "Starting Web Application on port $listenPort" }
|
Logger.info { "Starting Web Application on port $listenPort" }
|
||||||
app = Javalin.create { config ->
|
app = Javalin.create { config ->
|
||||||
config.useVirtualThreads = true
|
config.useVirtualThreads = true
|
||||||
@@ -253,15 +254,26 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
if (param.isNotEmpty()) {
|
if (param.isNotEmpty()) {
|
||||||
val bc = Get_Barix_Connection_by_ZoneName(param)
|
val bc = Get_Barix_Connection_by_ZoneName(param)
|
||||||
if (bc != null) {
|
if (bc != null) {
|
||||||
val key = ctx.req().remoteAddr + ":" + ctx.req().remotePort
|
|
||||||
|
|
||||||
|
val key = ctx.cookie("client-id") ?: UUID.randomUUID().toString()
|
||||||
|
.also { ctx.cookie("client-id", it) }
|
||||||
val pipeIN = PipedInputStream(8192)
|
val pipeIN = PipedInputStream(8192)
|
||||||
val pipeOUT = PipedOutputStream(pipeIN)
|
val pipeOUT = PipedOutputStream(pipeIN)
|
||||||
// write WAV Header 44 bytes contains samplingrate 44100 Hz, 16 bit, mono
|
// write WAV Header 44 bytes contains samplingrate 44100 Hz, 16 bit, mono
|
||||||
pipeOUT.write(Generate_WAV_Header())
|
// pipeOUT.write(Generate_WAV_Header())
|
||||||
bc.AddPipeOut(key, pipeOUT)
|
bc.AddPipeOut(key, pipeOUT)
|
||||||
ctx.contentType("audio/wav")
|
ctx.contentType("audio/mpeg")
|
||||||
ctx.header("Cache-Control", "no-cache")
|
ctx.header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
ctx.header("Pragma", "no-cache")
|
||||||
|
ctx.header("Expires", "0")
|
||||||
|
|
||||||
|
// 🔥 This one is critical — tells Jetty to send data in HTTP chunks as it becomes available:
|
||||||
|
ctx.header("Transfer-Encoding", "chunked")
|
||||||
|
|
||||||
|
// Keeps the TCP socket open so the browser doesn’t close after initial data
|
||||||
ctx.header("Connection", "keep-alive")
|
ctx.header("Connection", "keep-alive")
|
||||||
|
|
||||||
ctx.result(pipeIN)
|
ctx.result(pipeIN)
|
||||||
println("LiveAudio Open for zone $param SUCCESS")
|
println("LiveAudio Open for zone $param SUCCESS")
|
||||||
} else ctx.status(400)
|
} else ctx.status(400)
|
||||||
@@ -273,17 +285,23 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
get("Close/{broadcastzone}") { ctx ->
|
get("Close/{broadcastzone}") { ctx ->
|
||||||
val param = ctx.pathParam("broadcastzone")
|
val param = ctx.pathParam("broadcastzone")
|
||||||
println("LiveAudio Close for zone $param")
|
println("LiveAudio Close for zone $param")
|
||||||
if (param.isNotEmpty()) {
|
val key = ctx.cookie("client-id")
|
||||||
val bc = Get_Barix_Connection_by_ZoneName(param)
|
if (key != null && key.isNotEmpty()) {
|
||||||
if (bc != null) {
|
if (param.isNotEmpty()) {
|
||||||
val key = ctx.req().remoteAddr + ":" + ctx.req().remotePort
|
val bc = Get_Barix_Connection_by_ZoneName(param)
|
||||||
bc.RemovePipeOut(key)
|
if (bc != null) {
|
||||||
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
|
|
||||||
println("LiveAudio Close for zone $param SUCCESS")
|
bc.RemovePipeOut(key)
|
||||||
|
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
|
||||||
|
println("LiveAudio Close for zone $param SUCCESS")
|
||||||
|
} else ctx.status(400)
|
||||||
|
.result(objectmapper.writeValueAsString(resultMessage("Broadcastzone not found")))
|
||||||
} else ctx.status(400)
|
} else ctx.status(400)
|
||||||
.result(objectmapper.writeValueAsString(resultMessage("Broadcastzone not found")))
|
.result(objectmapper.writeValueAsString(resultMessage("Invalid broadcastzone")))
|
||||||
} else ctx.status(400)
|
} else {
|
||||||
.result(objectmapper.writeValueAsString(resultMessage("Invalid broadcastzone")))
|
ctx.status(400)
|
||||||
|
.result(objectmapper.writeValueAsString(resultMessage("No client-id cookie found")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
path("VoiceType") {
|
path("VoiceType") {
|
||||||
@@ -2043,55 +2061,55 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
}.start(listenPort)
|
}.start(listenPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Start_SemiAutoServer(){
|
private fun Start_SemiAutoServer() {
|
||||||
Logger.info {"Starting SemiAuto web server at port ${listenPort+1}"}
|
Logger.info { "Starting SemiAuto web server at port ${listenPort + 1}" }
|
||||||
semiauto = Javalin.create{ config ->
|
semiauto = Javalin.create { config ->
|
||||||
config.useVirtualThreads = true
|
config.useVirtualThreads = true
|
||||||
config.staticFiles.add ("/semiauto")
|
config.staticFiles.add("/semiauto")
|
||||||
config.jsonMapper(JavalinJackson(jacksonObjectMapper()))
|
config.jsonMapper(JavalinJackson(jacksonObjectMapper()))
|
||||||
config.router.apiBuilder {
|
config.router.apiBuilder {
|
||||||
path("/"){
|
path("/") {
|
||||||
get {
|
get {
|
||||||
it.cookie("semiauto-user","")
|
it.cookie("semiauto-user", "")
|
||||||
it.redirect("login.html")
|
it.redirect("login.html")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
path("logout"){
|
path("logout") {
|
||||||
get {
|
get {
|
||||||
it.cookie("semiauto-user","")
|
it.cookie("semiauto-user", "")
|
||||||
it.redirect("login.html")
|
it.redirect("login.html")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
path("login.html"){
|
path("login.html") {
|
||||||
post {
|
post {
|
||||||
val formuser = it.formParam("username") ?: ""
|
val formuser = it.formParam("username") ?: ""
|
||||||
val formpass = it.formParam("password") ?: ""
|
val formpass = it.formParam("password") ?: ""
|
||||||
if (formuser.isNotEmpty() && formpass.isNotEmpty()){
|
if (formuser.isNotEmpty() && formpass.isNotEmpty()) {
|
||||||
val user = db.userDB.List.find { u -> u.username==formuser && u.password==formpass }
|
val user = db.userDB.List.find { u -> u.username == formuser && u.password == formpass }
|
||||||
if (user!=null){
|
if (user != null) {
|
||||||
it.cookie("semiauto-user",user.username)
|
it.cookie("semiauto-user", user.username)
|
||||||
it.redirect("index.html")
|
it.redirect("index.html")
|
||||||
} else ResultMessageString(it, 400, "Invalid username or password")
|
} else ResultMessageString(it, 400, "Invalid username or password")
|
||||||
} else ResultMessageString(it, 400, "Username or password cannot be empty")
|
} else ResultMessageString(it, 400, "Username or password cannot be empty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
path("index.html"){
|
path("index.html") {
|
||||||
before { CheckSemiAutoUsers(it) }
|
before { CheckSemiAutoUsers(it) }
|
||||||
}
|
}
|
||||||
path("log.html"){
|
path("log.html") {
|
||||||
before { CheckSemiAutoUsers(it) }
|
before { CheckSemiAutoUsers(it) }
|
||||||
}
|
}
|
||||||
path("api"){
|
path("api") {
|
||||||
path("Initialize"){
|
path("Initialize") {
|
||||||
get { ctx ->
|
get { ctx ->
|
||||||
val username = ctx.cookie("semiauto-user") ?: ""
|
val username = ctx.cookie("semiauto-user") ?: ""
|
||||||
if (username.isNotEmpty()){
|
if (username.isNotEmpty()) {
|
||||||
val user = db.userDB.List.find { u -> u.username==username }
|
val user = db.userDB.List.find { u -> u.username == username }
|
||||||
if (user!=null){
|
if (user != null) {
|
||||||
val result = SemiAutoInitData(username)
|
val result = SemiAutoInitData(username)
|
||||||
// messages
|
// messages
|
||||||
String_To_List(user.messagebank_ann_id).forEach { msg ->
|
String_To_List(user.messagebank_ann_id).forEach { msg ->
|
||||||
db.messageDB.List.filter{it.ANN_ID==msg.toUInt()}.forEach {xx ->
|
db.messageDB.List.filter { it.ANN_ID == msg.toUInt() }.forEach { xx ->
|
||||||
result.messages.add("${xx.ANN_ID};${xx.Description};${xx.Language};${xx.Message_Detail}")
|
result.messages.add("${xx.ANN_ID};${xx.Description};${xx.Language};${xx.Message_Detail}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2100,47 +2118,55 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
// cities
|
// cities
|
||||||
String_To_List(user.city_tags).forEach { ct ->
|
String_To_List(user.city_tags).forEach { ct ->
|
||||||
db.soundDB.List.firstOrNull { it.TAG == ct && it.Category == Category.City.name }
|
db.soundDB.List.firstOrNull { it.TAG == ct && it.Category == Category.City.name }
|
||||||
?.let{
|
?.let {
|
||||||
result.cities.add("${it.TAG};${it.Description}")
|
result.cities.add("${it.TAG};${it.Description}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// airplane names
|
// airplane names
|
||||||
String_To_List(user.airline_tags).forEach { at ->
|
String_To_List(user.airline_tags).forEach { at ->
|
||||||
db.soundDB.List.firstOrNull { it.TAG == at && it.Category == Category.Airplane_Name.name }
|
db.soundDB.List.firstOrNull { it.TAG == at && it.Category == Category.Airplane_Name.name }
|
||||||
?.let{
|
?.let {
|
||||||
result.airlines.add("${it.TAG};${it.Description}")
|
result.airlines.add("${it.TAG};${it.Description}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// places
|
// places
|
||||||
db.soundDB.List.filter{it.Category==Category.Places.name}.distinctBy { it.TAG }.forEach { xx ->
|
db.soundDB.List.filter { it.Category == Category.Places.name }.distinctBy { it.TAG }
|
||||||
result.places.add("${xx.TAG};${xx.Description}")
|
.forEach { xx ->
|
||||||
}
|
result.places.add("${xx.TAG};${xx.Description}")
|
||||||
|
}
|
||||||
// shalat
|
// shalat
|
||||||
db.soundDB.List.filter{it.Category==Category.Shalat.name}.distinctBy { it.TAG }.forEach { xx ->
|
db.soundDB.List.filter { it.Category == Category.Shalat.name }.distinctBy { it.TAG }
|
||||||
result.shalat.add("${xx.TAG};${xx.Description}")
|
.forEach { xx ->
|
||||||
}
|
result.shalat.add("${xx.TAG};${xx.Description}")
|
||||||
|
}
|
||||||
// sequences
|
// sequences
|
||||||
db.soundDB.List.filter{it.Category==Category.Sequence.name}.distinctBy { it.TAG }.forEach { xx ->
|
db.soundDB.List.filter { it.Category == Category.Sequence.name }
|
||||||
|
.distinctBy { it.TAG }.forEach { xx ->
|
||||||
result.sequences.add("${xx.TAG};${xx.Description}")
|
result.sequences.add("${xx.TAG};${xx.Description}")
|
||||||
}
|
}
|
||||||
// reasons
|
// reasons
|
||||||
db.soundDB.List.filter{it.Category==Category.Reason.name}.distinctBy { it.TAG }.forEach { xx ->
|
db.soundDB.List.filter { it.Category == Category.Reason.name }.distinctBy { it.TAG }
|
||||||
result.reasons.add("${xx.TAG};${xx.Description}")
|
.forEach { xx ->
|
||||||
}
|
result.reasons.add("${xx.TAG};${xx.Description}")
|
||||||
|
}
|
||||||
// procedures
|
// procedures
|
||||||
db.soundDB.List.filter{it.Category==Category.Procedure.name}.distinctBy { it.TAG }.forEach { xx ->
|
db.soundDB.List.filter { it.Category == Category.Procedure.name }
|
||||||
|
.distinctBy { it.TAG }.forEach { xx ->
|
||||||
result.procedures.add("${xx.TAG};${xx.Description}")
|
result.procedures.add("${xx.TAG};${xx.Description}")
|
||||||
}
|
}
|
||||||
// gates
|
// gates
|
||||||
db.soundDB.List.filter{it.Category==Category.Gate.name}.distinctBy { it.TAG }.forEach { xx ->
|
db.soundDB.List.filter { it.Category == Category.Gate.name }.distinctBy { it.TAG }
|
||||||
result.gates.add("${xx.TAG};${xx.Description}")
|
.forEach { xx ->
|
||||||
}
|
result.gates.add("${xx.TAG};${xx.Description}")
|
||||||
|
}
|
||||||
// compensation
|
// compensation
|
||||||
db.soundDB.List.filter{it.Category==Category.Compensation.name}.distinctBy { it.TAG }.forEach { xx ->
|
db.soundDB.List.filter { it.Category == Category.Compensation.name }
|
||||||
|
.distinctBy { it.TAG }.forEach { xx ->
|
||||||
result.compensation.add("${xx.TAG};${xx.Description}")
|
result.compensation.add("${xx.TAG};${xx.Description}")
|
||||||
}
|
}
|
||||||
// greetings
|
// greetings
|
||||||
db.soundDB.List.filter{it.Category==Category.Greeting.name}.distinctBy { it.TAG }.forEach { xx ->
|
db.soundDB.List.filter { it.Category == Category.Greeting.name }
|
||||||
|
.distinctBy { it.TAG }.forEach { xx ->
|
||||||
result.greetings.add("${xx.TAG};${xx.Description}")
|
result.greetings.add("${xx.TAG};${xx.Description}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2149,18 +2175,18 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
} else ResultMessageString(ctx, 400, "Username is empty")
|
} else ResultMessageString(ctx, 400, "Username is empty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
path("SemiAuto"){
|
path("SemiAuto") {
|
||||||
post { ctx ->
|
post { ctx ->
|
||||||
val json : JsonNode = objectmapper.readTree(ctx.body())
|
val json: JsonNode = objectmapper.readTree(ctx.body())
|
||||||
// butuh description, languages, tags, dan broadcastzones
|
// butuh description, languages, tags, dan broadcastzones
|
||||||
val description = json.get("description").asText("")
|
val description = json.get("description").asText("")
|
||||||
val languages = json.get("languages").asText("")
|
val languages = json.get("languages").asText("")
|
||||||
val tags = json.get("tags").asText("")
|
val tags = json.get("tags").asText("")
|
||||||
val broadcastzones = json.get("broadcastzones").asText("")
|
val broadcastzones = json.get("broadcastzones").asText("")
|
||||||
if (description.isNotEmpty()){
|
if (description.isNotEmpty()) {
|
||||||
if (languages.isNotEmpty()){
|
if (languages.isNotEmpty()) {
|
||||||
if (tags.isNotEmpty()){
|
if (tags.isNotEmpty()) {
|
||||||
if (broadcastzones.isNotEmpty()){
|
if (broadcastzones.isNotEmpty()) {
|
||||||
val qt = QueueTable(
|
val qt = QueueTable(
|
||||||
0u,
|
0u,
|
||||||
LocalDateTime.now().format(datetimeformat1),
|
LocalDateTime.now().format(datetimeformat1),
|
||||||
@@ -2172,10 +2198,10 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
1u,
|
1u,
|
||||||
languages
|
languages
|
||||||
)
|
)
|
||||||
if (db.queuetableDB.Add(qt)){
|
if (db.queuetableDB.Add(qt)) {
|
||||||
db.queuetableDB.Resort()
|
db.queuetableDB.Resort()
|
||||||
Logger.info{"SemiAutoWeb added to queue table: $qt" }
|
Logger.info { "SemiAutoWeb added to queue table: $qt" }
|
||||||
println("SemiAuto added to queue table: ${objectmapper.writeValueAsString(qt)}")
|
//println("SemiAuto added to queue table: ${objectmapper.writeValueAsString(qt)}")
|
||||||
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
|
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
|
||||||
} else ResultMessageString(ctx, 500, "Failed to add to queue table")
|
} else ResultMessageString(ctx, 500, "Failed to add to queue table")
|
||||||
} else ResultMessageString(ctx, 400, "Broadcast zones cannot be empty")
|
} else ResultMessageString(ctx, 400, "Broadcast zones cannot be empty")
|
||||||
@@ -2185,13 +2211,12 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
path("Log"){
|
path("Log") {
|
||||||
get("/{datelog}"){ ctx ->
|
get("/{datelog}") { ctx ->
|
||||||
val datelog = ctx.pathParam("datelog")
|
val datelog = ctx.pathParam("datelog")
|
||||||
if (ValidDate(datelog)){
|
if (ValidDate(datelog)) {
|
||||||
println("SemiAuto Get Log for date $datelog")
|
println("SemiAuto Get Log for date $datelog")
|
||||||
db.GetLogForHtml(datelog){
|
db.GetLogForHtml(datelog) { loghtml ->
|
||||||
loghtml ->
|
|
||||||
val resultstring = objectmapper.writeValueAsString(loghtml)
|
val resultstring = objectmapper.writeValueAsString(loghtml)
|
||||||
println("Log HTML for date $datelog: $resultstring")
|
println("Log HTML for date $datelog: $resultstring")
|
||||||
ctx.result(resultstring)
|
ctx.result(resultstring)
|
||||||
@@ -2202,18 +2227,19 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start(listenPort+1)
|
}.start(listenPort + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ResultMessageString(ctx: Context, code: Int, message: String) {
|
fun ResultMessageString(ctx: Context, code: Int, message: String) {
|
||||||
ctx.status(code).result(objectmapper.writeValueAsString(resultMessage(message)))
|
ctx.status(code).result(objectmapper.writeValueAsString(resultMessage(message)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CheckSemiAutoUsers(ctx: Context){
|
fun CheckSemiAutoUsers(ctx: Context) {
|
||||||
val user = ctx.cookie("semiauto-user")
|
val user = ctx.cookie("semiauto-user")
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
ctx.redirect("login.html")
|
ctx.redirect("login.html")
|
||||||
}
|
}
|
||||||
val foundUser = db.userDB.List.find { u -> u.username==user }
|
val foundUser = db.userDB.List.find { u -> u.username == user }
|
||||||
if (foundUser == null) {
|
if (foundUser == null) {
|
||||||
ctx.redirect("login.html")
|
ctx.redirect("login.html")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user