pilpil-server/static/script.js

433 lines
16 KiB
JavaScript

// Timeline drag and drop elements default attributes
const tl_cont_attr = {id:"tl_cont", ondrop: "drop(event, this)", ondragover:"allow_drop(event)"};
const tl_drag_attr = {id:"tl_drag", draggable:"true", ondragstart:"drag(event, this)"};
// Config
var DEBUG = 0;
var cmd_port = "8888";
var timeline_color_cursor = "#FF8839";
var timeline_color_bg = "#2EB8E600";
var scan_interval = 3000;
var status_all = "Searching network for live hosts...";
// Global vars
var src_elem = "";
var medias_status = {};
var fileButtons = [];
var freeze_timeline_update = 0;
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function update_playlist(host){
var media_count = document.getElementById("timeline_" + host).children.length;
// Reversed loop
for (i=media_count,l=0;i>l;i--) {
//~ for (i=0,l=media_count;i<l;i++) {
// Find current's timeline element children's 'media_id' value
toShift = document.getElementById("timeline_" + host).children[i-1].children[0].getAttribute("media_id");
console.log(toShift + " : " + document.getElementById("timeline_" + host).children[i-1].children[0].innerText);
// Move 'toShift' after element with id '1'
// In VLC's playlist XML representation, playlist gets id '1', so moving to that id
// really means moving to the the very start of the playlist.
send_ajax_cmd("/" + host + "/move/" + toShift + "/1");
await sleep(80);
}
// Un-freeze timeline update flag
freeze_timeline_update = 0;
}
function find_target_index(element, index, array, thisArg){
if (element == this) {
return index + 1;
}
return 0;
}
function shift_elements(target_elem) {
// Shift elements in the timeline UI
//
// Get a list of current element siblings
var elem_list = Array.from(src_elem.parentElement.children);
// Initiate slice
var elem_list_slice = elem_list;
// Find indexes of source and target elements in the timeline
var source_idx = elem_list.findIndex(find_target_index, src_elem);
var target_idx = elem_list.findIndex(find_target_index, target_elem);
var idx;
// idx is the index where the elements should be inserted back
// Target element is on the left of the source element
if (source_idx < target_idx){
elem_list_slice = elem_list.slice(source_idx + 1, target_idx + 1);
idx = source_idx;
} else {
// Target element is on the right of the source element
elem_list_slice = elem_list.slice(target_idx, source_idx );
idx = target_idx + 1;
}
// Shift elements according to idx
for (i=0, l=elem_list_slice.length; i<l;i++){
elem_list[idx + i].appendChild(elem_list_slice[i].children[0]);
}
return target_elem;
}
function allow_drop(ev) {
ev.preventDefault();
drop_id = ev.dataTransfer.getData("text");
}
function drag(ev, source) {
// Freeze timeline update flag
freeze_timeline_update = 1;
src_elem = ev.target.parentElement;
if (DEBUG) {
console.log(src_elem.children[0].getAttribute("media_id") + " : " + src_elem.children[0].innerText);
}
ev.dataTransfer.setData("text", ev.target.id);
}
function drop(ev, target_elem) {
// If the currently dragged element is dropped on another element, shift HTML elements in the timeline to the left or right from the target element.
//
ev.preventDefault();
// Get dragged element id
var data = ev.dataTransfer.getData("text");
// Only shift if not dropping on self
if (src_elem.id != target_elem.id) {
dropTarget = shift_elements(target_elem);
if (dropTarget) {
//
dropTarget.appendChild(document.getElementById(data));
// Update VLC playlist according to UI timeline
// TODO : This currently sends the move cmd to all instances. Fix it to specify which host to send the cmd to.
update_playlist("10.42.0.10");
}
}
send_ajax_cmd("/all/list");
//~ setTimeout(function(){ freeze_timeline_update = 0; console.log("shwing!"); }, 2000);
}
function adjust_timeline() {
// Adapt timeline's UI elements to fit the width of their parent container.
//
//~ var child = document.getElementById('timeline').children;
var child = document.querySelector('[id^="timeline_"]').children;
var divWidth = 100 / child.length;
for (i=0, l=child.length;i<l;i++) {
child[i].style.width= divWidth + "%";
}
}
function add_HTML_attr(id, attr, val , child=-1) {
// Add attribute 'attr' with value 'val' to the HTML element with id 'id'.
// If child is other than -1, the attribute is applied to the HTML element's child at position 'child'
var elem = document.getElementById(id);
if (child>-1){
elem = elem.children[child];
}
var att = document.createAttribute(attr);
att.value = val;
elem.setAttributeNode(att);
}
function add_HTML_element(type, attr, meta=0, j=0){
// Add HTML element with type 'type' and attributes 'attr'.
// 'attr' should be a javascript object
// 'meta' should be an array
// 'j' is used to pass an increment value when used in a loop
//
var elem = document.createElement(type);
var keys_array = Object.keys(attr);
for (i=0, l=keys_array.length;i<l;i++) {
var att = document.createAttribute(keys_array[i]);
// First iteration
if(!i){
att.value = Object.values(attr)[i]+j;
} else {
att.value = Object.values(attr)[i];
}
elem.setAttributeNode(att);
}
// Set media_id attribute if metadata is provided
if (meta) {
attribute = document.createAttribute("media_id");
attribute.value = meta[0];
elem.setAttributeNode(attribute);
}
// Get filename
elem.innerText = meta[1];
return elem;
}
// Bouttons de commande
addEventListener("DOMContentLoaded", function() {
//~ send_ajax_cmd("/scan");
//~ send_ajax_cmd("/browse_local");
//~ setTimeout(send_ajax_cmd, 3000, "/all/browse");
//~ setTimeout(send_ajax_cmd, 4000, "/all/rssi");
//~ setTimeout(send_ajax_cmd, 1000, "/all/list");
adjust_timeline();
// Get all elements with class 'command'
var commandButtons = document.querySelectorAll(".command");
for (var i=0, l=commandButtons.length; i<l; i++) {
var button = commandButtons[i];
// Listen for clicks
button.addEventListener("click", function(event) {
// Discard signal
event.preventDefault();
// Get value=""
var clickedButton = event.currentTarget;
var command = clickedButton.value;
// Proceed accordingly
if ( command.indexOf("/reboot" ) > -1 || command.indexOf("/poweroff") > -1 ) {
if ( !confirm("Êtes vous certain de vouloir effectuer cette action ?") ) {
return 0;
}
} else if ( command == "/scan" ) {
document.getElementById("status_all").innerHTML = status_all;
} else if ( command.indexOf("/sort") > -1 ){
if (command.indexOf('/1/') > -1 ) {
clickedButton.value = clickedButton.value.replace('/1/','/0/');
} else {
clickedButton.value = clickedButton.value.replace('/0/','/1/');
}
}
// AJAX request
var request = new XMLHttpRequest();
if ( command == "/scan") {
request.onload = send_ajax_cmd(command);
} else if ( command.indexOf("/clear") > -1 || command.indexOf("/sort") > -1) {
request.onload = send_ajax_cmd(command);
} else if ( command == "/sync/all" ) {
status_all = "Syncing files...";
request.onload = send_ajax_cmd("/sync/status");
}
// build request
request.open("GET", command, true);
// send request
request.send();
});
}
}, true);
// Metadata display
function parse_result(command, infos_array) {
if (command == "/all/status") {
// Requests current status of every instances, especially current media file's name, position and length.
// Also retrieves loop and repeat status.
//
// Iterate over array
for (var i = 0, l=infos_array.length; i<l; i++) {
// Get filename, time/length
if (infos_array[i].status) {
document.getElementById("status_"+infos_array[i].host).innerHTML = infos_array[i].file + " <br/> " + infos_array[i].time + " / " + infos_array[i].leng;
medias_status[infos_array[i].id] = infos_array[i].pos;
// Highlight currently playing element
timeline_medias_array = Array.from(document.querySelectorAll('[media_id]'));
for (var z=0, x=timeline_medias_array.length; z<x;z++){
if ( timeline_medias_array[z].getAttribute("media_id") == infos_array[i].id ) {
var pos = infos_array[i].pos * 100;
var pos1 = pos-1 + "%";
pos = pos + "%";
var media_url_str = "url(https://" + infos_array[i].host + ":" + cmd_port + "/thumb/" + infos_array[i].file + ")"
var media_cssgrad_rule = "linear-gradient(90deg," + timeline_color_bg + " " + pos1 + ", " + timeline_color_cursor + " " + pos + ", " + timeline_color_bg + " " + pos + ")," + media_url_str;
timeline_medias_array[z].style.backgroundImage = media_cssgrad_rule;
timeline_medias_array[z].style.borderBottom = "4px solid " + timeline_color_cursor;
} else {
var media_url_str = "url(https://" + infos_array[i].host + ":" + cmd_port + "/thumb/" + timeline_medias_array[z].innerText + ")"
timeline_medias_array[z].style.backgroundImage = media_url_str;
timeline_medias_array[z].style.borderBottom = "None";
}
}
} else {
document.getElementById("status_"+infos_array[i].host).innerHTML = "<br><br>";
}
// Toggle loop indicator
if (infos_array[i].loop == "true") {
document.getElementById("loop_ind_" + infos_array[i].host).style.backgroundColor = "#78E738";
} else {
document.getElementById("loop_ind_" + infos_array[i].host).style.backgroundColor = "#A42000";
}
// Toggle repeat indicator
if (infos_array[i].repeat == "true") {
document.getElementById("repeat_ind_" + infos_array[i].host).style.backgroundColor = "#78E738";
} else {
document.getElementById("repeat_ind_" + infos_array[i].host).style.backgroundColor = "#A42000";
}
}
} else if ( command.indexOf("/list") > -1 ) {
// Requests playlist of every instances
//
if (!freeze_timeline_update){
console.log("Timeline freeze : " + freeze_timeline_update);
console.log("Infos array : " + infos_array);
for (var i = 0, l=infos_array.length; i<l; i++) {
// Playlist infos are displayed in a div ; number of items in list and total duration.
//~ document.getElementById("playlist_"+infos_array[i].host).innerHTML = infos_array[i].leng + " item(s) in playlist - " + infos_array[i].duration;
// Populate timeline according to the content of the playlist
// Get returned items as an array
var items_array = Array.from(infos_array[i].items);
//console.log(items_array.length);
// If playlist is empty, remove all media divs in the timeline UI.
if (items_array.length == 0){
var child_list = Array.from(document.getElementById("timeline_" + infos_array[i].host).children);
for(x=0,y=child_list.length;x<y;x++){
document.getElementById("timeline_" + infos_array[i].host).removeChild(child_list[x]);
}
break;
}
for (var j = 0, k=items_array.length; j<k; j++) {
// Timeline
item_meta = items_array[j].split(';');
var child_node = add_HTML_element("div", tl_drag_attr, item_meta, j);
var len = document.getElementById("timeline_" + infos_array[i].host).children.length;
add_HTML_attr("timeline_" + infos_array[i].host, "length", len);
if ( len < items_array.length ) {
document.getElementById("timeline_" + infos_array[i].host).appendChild( add_HTML_element("div", tl_cont_attr, 0, len) );
}
document.getElementById(tl_cont_attr.id + j).replaceChildren(child_node);
var media_name = document.getElementById(tl_cont_attr.id + j).children[0].innerText;
var media_url_str = "url(https://" + infos_array[i].host + ":" + cmd_port + "/thumb/" + media_name + ")"
document.getElementById(tl_cont_attr.id + j).children[0].style.backgroundImage = media_url_str;
// Adjust elements width
adjust_timeline();
}
}
}
} else if ( command == "/scan") {
var host_up = infos_array[0];
var host_down = infos_array[1];
for ( var i=0, l=host_up.length; i<l; i++){
document.getElementById(host_up[i]).style.display = 'block';
}
for ( var i=0, l=host_down.length; i<l; i++){
document.getElementById(host_down[i]).style.display = 'none';
}
if (host_up.length) {
scan_interval = 10000;
//~ document.getElementById("status_all").innerHTML = "Scan interval set to " + scan_interval;
status_all = "Scan interval set to " + scan_interval;
}
//~ document.getElementById("status_all").innerHTML = host_up.length + " client(s).";
status_all = host_up.length + " client(s).";
} else if ( command == "/browse_local") {
// Display local media files in a table
var html_table = "<table>" +
"<tr>" +
"<th>Filename</th>" +
"<th>Duration</th>" +
"</tr>";
for (var j = 0, k=infos_array.length; j<k; j++) {
html_table += "<tr>" +
"<td>" + infos_array[j] + "</td>" +
"<td>" + "00:00" + "</td>" +
"</tr>" ;
}
html_table += "</table>";
document.getElementById("filelist").innerHTML = html_table;
} else if ( command == "/all/rssi") {
// RSSI strength indicator
var signal_color = 40;
var best_rssi = 30;
var worst_rssi = 70;
for (var j = 0, k=infos_array.length; j<k; j++) {
var rssi_norm = Math.ceil( (worst_rssi - parseInt(infos_array[j].rssi) ) / ( worst_rssi - best_rssi ) * 4 );
signal_color = (rssi_norm-1) * signal_color;
// Reset to grey
for (i=0, l=4; i<l;i++) {
document.getElementById("wl_"+i).style.backgroundColor = "hsl(0, 0%, 65%)";
}
// Color it
for (i=0, l=rssi_norm>4?4:rssi_norm; i<l;i++) {
document.getElementById("wl_"+i).style.backgroundColor = "hsl(" + signal_color + ", 100%, 50%)";
}
}
} else if ( command == "/all/browse") {
// Display remote media files in a table
for (var i=0, l=infos_array.length; i<l; i++) {
hosts = Object.keys(infos_array[i]);
//~ console.log(keys);
for ( var j=0, k=hosts.length;j<k;j++ ) {
keys_ = Object.keys(infos_array[i][hosts[j]]);
//~ console.log(infos_array[i][hosts[j]]);
for ( var m=0, n=keys_.length;m<n;m++ ) {
item_meta = infos_array[i][hosts[j]][keys_[m]];
//~ console.log(item_meta);
tr = document.createElement("tr");
td = document.createElement("td");
tr.appendChild(td);
tr.setAttribute("class", "file_selection");
tr.host = hosts[j];
td.innerText = item_meta.name;
// Add an event to the created element to send enqueue command when a file is clicked in the list
tr.addEventListener("click", enqueue_file, false);
document.getElementById("file_sel_"+hosts).appendChild(tr);
}
}
}
} else if ( command == "/sync/status") {
status_all = infos_array;
} else {
setTimeout(send_ajax_cmd, 80, "/all/list");
setTimeout(send_ajax_cmd, 120, "/all/status");
}
}
function send_ajax_cmd(command) {
var request = new XMLHttpRequest();
request.onload = function() {
if (request.readyState === request.DONE) {
if (request.status === 200) {
// responseText is a string, use parse to get an array.
if (!freeze_timeline_update) {
var infos_array = JSON.parse(request.responseText);
//console.log(infos_array.length);
parse_result(command, infos_array);
//return infos_array;
}
}
}
};
// On construit la commande
request.open("GET", command, true);
// et on l'envoie
request.send();
}
function enqueue_file(evt) {
//console.log(evt.currentTarget.innerText + evt.currentTarget.host);
send_ajax_cmd("/" + evt.currentTarget.host + "/enqueue/" + evt.currentTarget.innerText);
//~ send_ajax_cmd("/all/list");
setTimeout(send_ajax_cmd, 40, "/" + evt.currentTarget.host + "/list");
//~ send_ajax_cmd("/" + evt.currentTarget.host + "/list");
}
function update_statusall_content() {
document.getElementById("status_all").innerHTML = status_all;
}
var scan_hosts = function() {
send_ajax_cmd("/scan");
update_statusall_content();
setTimeout(scan_hosts, scan_interval);
}
send_ajax_cmd("/scan");
send_ajax_cmd("/browse_local");
setInterval(send_ajax_cmd, 500, "/all/status");
setTimeout(send_ajax_cmd, 1000, "/all/list");
setTimeout(send_ajax_cmd, 3000, "/all/browse");
setInterval(send_ajax_cmd, 20000, "/all/rssi");
//~ setInterval(update_statusall_content, 1000);
setTimeout(scan_hosts, scan_interval);