// Config const DEBUG = 0; const CMD_PORT = "8888"; const TIMELINE_COLOR_CURSOR = "#FF8839"; const TIMELINE_COLOR_BG = "#2EB8E600"; // 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)"}; // Global object window.currentUser = { scan_interval : 3000, status_all : "Searching network for live hosts...", medias_status : {}, freeze_timeline_update : 0, }; function sleep(ms) { let delay = new Promise(function(resolve) { setTimeout(resolve, ms); }); return delay ; // arrow notation equivalent // return new Promise(resolve => setTimeout(resolve, ms)); } async function update_VLC_playlist(host) { // Update host's VLC playlist according to web UI timeline let media_count = document.getElementById(`timeline_${host}`).children.length; // Reversed loop for (let i=media_count, l=0; i>l; i--) { // Find current's timeline element children's 'media_id' value let to_shift = document.getElementById(`timeline_${host}`).children[i-1].children[0].getAttribute("media_id"); console.log(to_shift + " : " + document.getElementById(`timeline_${host}`).children[i-1].children[0].innerText); // Move 'to_shift' after element with id '1' : // In VLC's playlist XML representation, the playlist node always gets id '1', so moving to that id // really means moving to the the very start of the playlist. send_ajax_cmd("/" + host + "/move/" + to_shift + "/1"); await sleep(90); } // Un-freeze timeline update flag currentUser.freeze_timeline_update = 0; } function find_target_index(element, index) { if (element == this) { return index + 1; } return 0; } function shift_elements(source_element, target_element) { // Shift elements in the timeline UI // // Get a list of current element siblings let siblings_list = Array.from(source_element.parentElement.children); let siblings_list_slice; // Find indexes of source and target elements in the timeline let source_index = siblings_list.findIndex(find_target_index, source_element); let target_index = siblings_list.findIndex(find_target_index, target_element); let insert_at = 0; // Target element is on the left of the source element if (source_index < target_index){ siblings_list_slice = siblings_list.slice(source_index + 1, target_index + 1); insert_at = source_index; } else { // Target element is on the right of the source element siblings_list_slice = siblings_list.slice(target_index, source_index ); insert_at = target_index + 1; } // Shift elements according to insert_at for (let i=0, l=siblings_list_slice.length; i -1){ HTML_element = HTML_element.children[child]; } let HTML_attr = document.createAttribute(attribute); HTML_attr.value = val; HTML_element.setAttributeNode(HTML_attr); } function add_HTML_element(type, attribute, meta=0, uid=0){ // Add HTML element with type 'type' and attributes 'attribute'. // 'attribute' should be a javascript object containing HTML attributes keys/values // 'meta' should be an array // 'uid' is used to make the HTML id attribute unique // let HTML_element = document.createElement(type); let HTML_attributes = Object.keys(attribute); for (let i=0, l=HTML_attributes.length; i " + infos_array_element.time + " / " + infos_array_element.leng; currentUser.medias_status[infos_array_element.id] = infos_array_element.pos; // Highlight currently playing element let timeline_medias_array = Array.from(document.querySelectorAll('[media_id]')); timeline_medias_array.forEach(function(media_element){ if ( media_element.getAttribute("media_id") == infos_array_element.id ) { let first_CSS_gradient_stop = infos_array_element.pos * 100; let second_CSS_gradient_stop = first_CSS_gradient_stop - 1 + "%"; first_CSS_gradient_stop = first_CSS_gradient_stop + "%"; let media_url_str = "url(https://" + infos_array_element.host + ":" + CMD_PORT + "/thumb/" + infos_array_element.file + ")"; let media_cssgrad_rule = "linear-gradient(90deg," + TIMELINE_COLOR_BG + " " + second_CSS_gradient_stop + ", " + TIMELINE_COLOR_CURSOR + " " + first_CSS_gradient_stop + ", " + TIMELINE_COLOR_BG + " " + first_CSS_gradient_stop + ")," + media_url_str; media_element.style.backgroundImage = media_cssgrad_rule; media_element.style.borderBottom = "4px solid " + TIMELINE_COLOR_CURSOR; } else { let media_url_str = "url(https://" + infos_array_element.host + ":" + CMD_PORT + "/thumb/" + media_element.innerText + ")" media_element.style.backgroundImage = media_url_str; media_element.style.borderBottom = "None"; } }); } else { document.getElementById("status_" + infos_array_element.host).innerHTML = "

"; } toggle_indicator(infos_array_element, "loop"); toggle_indicator(infos_array_element, "repeat"); return infos_array_element.status; } function update_list(infos_array_element){ // 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 let items_array = Array.from(infos_array_element.items); let host = infos_array_element.host; // If playlist is empty, remove all media divs in the timeline UI. if (items_array.length == 0){ let child_list = Array.from(document.getElementById("timeline_" + host).children); child_list.forEach(function(child){ document.getElementById("timeline_" + host).removeChild(child); }); } else { items_array.forEach(function(item, j){ item_meta = item.split(';'); let child_node = add_HTML_element("div", tl_drag_attr, item_meta, j); let len = document.getElementById("timeline_" + host).children.length; add_HTML_attr("timeline_" + host, "length", len); if ( len < items_array.length ) { document.getElementById("timeline_" + host).appendChild( add_HTML_element("div", tl_cont_attr, 0, len) ); } document.getElementById(tl_cont_attr.id + j).replaceChildren(child_node); let media_name = document.getElementById(tl_cont_attr.id + j).children[0].innerText; let media_url_str = "url(https://" + 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(); }); } return items_array.length; } function populate_HTML_table(inner_text, host="all", CSS_class="file_selection") { tr = document.createElement("tr"); td = document.createElement("td"); tr.appendChild(td); tr.setAttribute("class", CSS_class); tr.host = host; td.innerText = inner_text; // 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_" + host).appendChild(tr); } function update_remote_filelist(infos_array_element) { hosts = Object.keys(infos_array_element); for (let j=0, m=hosts.length; j 4 ? 4 : rssi_norm; i" + "Filename" + "Duration" + ""; infos_array.forEach(function(element){ html_table += "" + "" + element + "" + "" + "00:00" + "" + "" ; }); html_table += ""; return html_table; } function update_host_list(infos_array){ let host_up = infos_array[0]; let host_down = infos_array[1]; if (host_up.length) { host_up.forEach(function(host){ document.getElementById(host).style.display = 'block'; send_ajax_cmd("/" + host + "/list"); send_ajax_cmd("/" + host + "/rssi"); }); currentUser.scan_interval = 10000; } host_down.forEach(function(element){ document.getElementById(element).style.display = 'none'; }); currentUser.status_all = host_up.length + " client(s)."; return host_up.length; } // 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. infos_array.forEach(update_status); } else if ( command.indexOf("/list") > -1 ) { // Requests playlist of every instances if (!currentUser.freeze_timeline_update){ infos_array.forEach(update_list); } } else if ( command == "/scan") { // Scan for live hosts update_host_list(infos_array); } else if ( command == "/browse_local") { // Display local media files in a table document.getElementById("filelist").innerHTML = update_local_filelist(infos_array); } else if ( command == "/all/rssi") { // RSSI strength indicator infos_array.forEach(update_rssi_indicator); } else if ( command == "/all/browse") { // Display remote media files in a table infos_array.forEach(update_remote_filelist); } else if ( command == "/sync/status") { // TODO : File sync UI status currentUser.status_all = infos_array; } else { setTimeout(send_ajax_cmd, 80, "/all/list"); setTimeout(send_ajax_cmd, 120, "/all/status"); } } function send_ajax_cmd(command) { let 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 (!currentUser.freeze_timeline_update) { let infos_array = JSON.parse(request.responseText); parse_result(command, infos_array); } } } }; request.open("GET", command, true); request.send(); } function enqueue_file(evt) { send_ajax_cmd("/" + evt.currentTarget.host + "/enqueue/" + evt.currentTarget.innerText); setTimeout(send_ajax_cmd, 40, "/" + evt.currentTarget.host + "/list"); } function update_statusall_content() { document.getElementById("status_all").innerHTML = currentUser.status_all; } function scan_hosts() { send_ajax_cmd("/scan"); update_statusall_content(); setTimeout(scan_hosts, currentUser.scan_interval); }; // UI addEventListener("DOMContentLoaded", function() { adjust_timeline(); let commandButtons = document.querySelectorAll(".command"); for (let i=0, l=commandButtons.length; i -1 || command.indexOf("/poweroff") > -1 ) { // TODO : l10n if ( !confirm("Êtes vous certain de vouloir effectuer cette action ?") ) { return 0; } } else if ( command == "/scan" ) { document.getElementById("status_all").innerHTML = currentUser.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 let 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" ) { currentUser.status_all = "Syncing files..."; request.onload = send_ajax_cmd("/sync/status"); } request.open("GET", command, true); request.send(); }); } }, true); 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"); setTimeout(scan_hosts, currentUser.scan_interval);