Media enqueuing UI

This commit is contained in:
ABelliqueux 2022-11-12 18:17:25 +01:00
parent 9bcb04890b
commit 82a67286ea
8 changed files with 253 additions and 129 deletions

103
app.py
View File

@ -7,15 +7,18 @@ from flask import Flask, render_template, request, make_response, jsonify
import gettext import gettext
import http.client import http.client
import os import os
import socket # ~ import requests
# ~ import socket
import ssl import ssl
import subprocess import subprocess
import requests
from shutil import which from shutil import which
import sys import sys
# ~ import threading
import toml import toml
from urllib.parse import quote, unquote
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from waitress import serve from waitress import serve
# l10n # l10n
LOCALE = os.getenv('LANG', 'en') LOCALE = os.getenv('LANG', 'en')
@ -30,8 +33,6 @@ for location in config_locations:
if app.config.from_file(os.path.expanduser( location + "pilpil-server.toml"), load=toml.load, silent=True): if app.config.from_file(os.path.expanduser( location + "pilpil-server.toml"), load=toml.load, silent=True):
print( _("Found configuration file in {}").format( os.path.expanduser(location) ) ) print( _("Found configuration file in {}").format( os.path.expanduser(location) ) )
###
hosts_available, hosts_unavailable = [],[] hosts_available, hosts_unavailable = [],[]
queue_msgs = [ queue_msgs = [
_("No items"), _("No items"),
@ -57,7 +58,6 @@ cmd_player = {
"move" : "pl_move", "move" : "pl_move",
"sort" : "pl_sort", "sort" : "pl_sort",
"seek" : "seek", "seek" : "seek",
"sync" : "sync",
"status" : "status.xml", "status" : "status.xml",
"list" : "playlist.xml", "list" : "playlist.xml",
#"volume" : "volume", #"volume" : "volume",
@ -65,19 +65,20 @@ cmd_player = {
#"dir" : "?dir=<uri>", #"dir" : "?dir=<uri>",
#"command" : "?command=<cmd>", #"command" : "?command=<cmd>",
#"key" : "key=", #"key" : "key=",
#"browse" : "browse.xml?uri=file://~" "browse" : "browse.xml?uri=file://~"
} }
cmd_server = [ cmd_server = [
# Map pilpil-client http url parameters to pilpil-server urls # Map pilpil-client http url parameters to pilpil-server urls
"blink", "blink",
"reboot", "reboot",
"poweroff", "poweroff",
"rssi" "rssi",
"sync"
] ]
# Configuration # Configuration
debug = app.config['DEFAULT']['debug'] debug = app.config['DEFAULT']['debug']
media_folder_remote = app.config['DEFAULT']['media_folder_remote'] media_folder_remote = os.path.expanduser(app.config['DEFAULT']['media_folder_remote'])
media_folder_local = os.path.expanduser(app.config['DEFAULT']['media_folder_local']) media_folder_local = os.path.expanduser(app.config['DEFAULT']['media_folder_local'])
media_exts = app.config['DEFAULT']['media_exts'] media_exts = app.config['DEFAULT']['media_exts']
auth = str(base64.b64encode(str(":" + app.config['DEFAULT']['vlc_auth']).encode('utf-8')), 'utf-8') auth = str(base64.b64encode(str(":" + app.config['DEFAULT']['vlc_auth']).encode('utf-8')), 'utf-8')
@ -98,6 +99,7 @@ else:
sslcontext.check_hostname = False sslcontext.check_hostname = False
sslcontext.verify_mode = ssl.CERT_NONE sslcontext.verify_mode = ssl.CERT_NONE
current_upload = 0
class PilpilClient: class PilpilClient:
def __init__(self): def __init__(self):
@ -190,6 +192,8 @@ def sync_media_folder(media_folder_local, media_folder_remote, host_local, port,
''' '''
Sync media_folder_local with media_folder_remote using sync_facility Sync media_folder_local with media_folder_remote using sync_facility
''' '''
total_size = 0
transfer_ok = 0
trailing_slash = 1 trailing_slash = 1
# Check for trailing / and add it if missing # Check for trailing / and add it if missing
if media_folder_local[-1:] != "/": if media_folder_local[-1:] != "/":
@ -200,10 +204,18 @@ def sync_media_folder(media_folder_local, media_folder_remote, host_local, port,
#Using http_upload #Using http_upload
if sync_facility == "http": if sync_facility == "http":
media_list = list_media_files(media_folder_local) media_list = list_media_files(media_folder_local)
transfer_ok = 0
for media in media_list: for media in media_list:
transfer_ok += HTTP_upload(media, host_local, port, trailing_slash) current_upload = media.strip("/")
return _("{} files uploaded.").format(str(transfer_ok)) if debug:
print("Uploading " + str(media))
if HTTP_upload(media, host_local, port, trailing_slash):
if debug:
print("File size : " + str(os.stat(media_folder_local + media).st_size))
total_size += os.stat(media_folder_local + media).st_size
transfer_ok += 1
# ~ return _("{} files uploaded.").format(str(transfer_ok))
# ~ return _("{} files uploaded.").format(str(transfer_ok))
# Using system cmd
elif which(sync_facility): elif which(sync_facility):
# Build subprocess arg list accroding to sync_facility # Build subprocess arg list accroding to sync_facility
# Using Rsync # Using Rsync
@ -224,9 +236,9 @@ def sync_media_folder(media_folder_local, media_folder_remote, host_local, port,
if len(sync_proc.stdout): if len(sync_proc.stdout):
scrape_index = str(sync_proc.stdout).index(scrape_str)+len(scrape_str) scrape_index = str(sync_proc.stdout).index(scrape_str)+len(scrape_str)
total_size = str(sync_proc.stdout)[ scrape_index: ].split(" ")[0][:-1] total_size = str(sync_proc.stdout)[ scrape_index: ].split(" ")[0][:-1]
else: if debug:
total_size = "N/A"; print("Transferred size : " + str(total_size))
return total_size return total_size
def get_meta_data(host, xml_data, request_="status", m3u_=0): def get_meta_data(host, xml_data, request_="status", m3u_=0):
''' '''
@ -239,7 +251,7 @@ def get_meta_data(host, xml_data, request_="status", m3u_=0):
} }
if request_ == "list": if request_ == "list":
# Return current instance's playlist # Return current instance's playlist
return get_playlist(host, xml_data, m3u_) return get_playlist(host, xml_data, m3u_)
elif request_ == "status": elif request_ == "status":
# Return current instance's status ( currently playing, state, time length, etc. ) # Return current instance's status ( currently playing, state, time length, etc. )
if xml_data.findall("./information/category/"): if xml_data.findall("./information/category/"):
@ -267,22 +279,37 @@ def get_meta_data(host, xml_data, request_="status", m3u_=0):
'id': cur_id, 'id': cur_id,
}) })
elif request_ == "rssi": elif request_ == "rssi":
# # Return current instance's wifi signal quality # Return current instance's wifi signal quality
print(xml_data) if debug:
print(xml_data)
if xml_data.findall("rssi"): if xml_data.findall("rssi"):
media_infos.update({ media_infos.update({
'status': 1, 'status': 1,
'rssi' : xml_data.find('rssi').text 'rssi' : xml_data.find('rssi').text
}) })
elif request_ == "browse":
host_medias = {}
for media in xml_data:
media_name = media.attrib["name"]
if len(media_name.split('.')) > 1:
if media_name.split('.')[1] in media_exts:
host_medias[media_name] = {
"name": media_name,
"uri": media.attrib["uri"],
"size": round(int(media.attrib["size"])/1000000, 2)}
media_infos = {host : host_medias}
if debug:
print(media_infos)
return media_infos return media_infos
def get_playlist(host, xml_data, m3u=0): def get_playlist(host, xml_data, m3u=0):
playlist = [] playlist = []
item_list = [] item_list = []
playlist_duration = 0 playlist_duration = 0
if xml_data.find("./node") and xml_data.find("./node").get('name') == "Playlist": if xml_data.find("./node") and xml_data.find("./node").get('name') == _("Playlist"):
print("HAHAHAH")
playlist = xml_data.findall("./node/leaf") playlist = xml_data.findall("./node/leaf")
content_format = "{};{};{};" content_format = "{};{};{};"
@ -329,14 +356,22 @@ def send_pilpil_command(host, arg0, arg1, arg2):
# #
# Default request # Default request
HTTP_request = "/requests/status.xml" HTTP_request = "/requests/status.xml"
if arg0 == "list" : if arg0 == "list":
# Get playlist # Get playlist
HTTP_request = "/requests/playlist.xml" HTTP_request = "/requests/playlist.xml"
elif arg0 == "browse":
if debug:
print("Brosing {}".format(media_folder_remote))
# Browse remote media folder
media_folder_remote_URL = quote(media_folder_remote)
HTTP_request = "/requests/browse.xml?uri=file://{}".format(media_folder_remote_URL)
if debug:
print("requesting url {} to {}:{}".format(HTTP_request, host, port_))
elif arg0 in cmd_server: elif arg0 in cmd_server:
# Switching to cmd server # Switching to cmd server
HTTP_request = "/" + str(arg0) HTTP_request = "/" + str(arg0)
port_ = cmd_port port_ = cmd_port
elif arg0 != "status" : elif arg0 != "status":
# Build request for VLC command # Build request for VLC command
HTTP_request = HTTP_request + "?command=" + cmd_player[arg0] HTTP_request = HTTP_request + "?command=" + cmd_player[arg0]
if arg1 != "null" : if arg1 != "null" :
@ -352,7 +387,7 @@ def send_pilpil_command(host, arg0, arg1, arg2):
HTTP_request = HTTP_request + "&val=" + arg1 HTTP_request = HTTP_request + "&val=" + arg1
elif (arg0 == "enqueue") or (arg0 == "add") : elif (arg0 == "enqueue") or (arg0 == "add") :
# Add 'input' url parameter # Add 'input' url parameter
HTTP_request = HTTP_request + "&input=file://" + media_folder_remote + "/" + arg1 HTTP_request = HTTP_request + "&input=file://" + quote(media_folder_remote) + "/" + arg1
# Send request and get data response # Send request and get data response
data = send_HTTP_request(host, port_, time_out=3, request_=HTTP_request) data = send_HTTP_request(host, port_, time_out=3, request_=HTTP_request)
@ -371,7 +406,7 @@ def send_pilpil_command(host, arg0, arg1, arg2):
return metadata return metadata
# Utilities # Utilities
def list_media_files(folder): def list_local_media_files(folder):
''' '''
List files in folder which extension is allowed (exists in media_exts). List files in folder which extension is allowed (exists in media_exts).
''' '''
@ -426,20 +461,24 @@ def scan():
hosts_available, hosts_unavailable = check_hosts(hosts) hosts_available, hosts_unavailable = check_hosts(hosts)
return [hosts_available, hosts_unavailable] return [hosts_available, hosts_unavailable]
@app.route("/browse") @app.route("/browse_local")
def browse(): def browse():
return list_media_files(media_folder_local) return list_local_media_files(media_folder_local)
@app.route("/sync/<host>") @app.route("/sync/<host>")
def sync(host): def sync(host):
# TODO : Add feedback for transfer in GUI # TODO : Add feedback for transfer in GUI
size = 0 size = 0
if host == "all": if host == "status":
return str(current_upload)
elif host == "all":
for hostl in hosts_available: for hostl in hosts_available:
size += sync_media_folder(media_folder_local, media_folder_remote, hostl, cmd_port) size += sync_media_folder(media_folder_local, media_folder_remote, hostl, cmd_port)
# ~ th = threading.Thread(target=blink_pi, args=(16,))
# ~ th.start()
else: else:
size = sync_media_folder(media_folder_local, media_folder_remote, host, cmd_port) size = sync_media_folder(media_folder_local, media_folder_remote, host, cmd_port)
return size; return str(size)
@app.route("/<host>/<arg0>/", defaults = { "arg1": "null", "arg2": "null" }) @app.route("/<host>/<arg0>/", defaults = { "arg1": "null", "arg2": "null" })
@app.route("/<host>/<arg0>/<arg1>/", defaults = { "arg2": "null" }) @app.route("/<host>/<arg0>/<arg1>/", defaults = { "arg2": "null" })
@ -453,10 +492,10 @@ def action(host, arg0, arg1, arg2):
if host == "all": if host == "all":
# Send request to all available hosts # Send request to all available hosts
resp = [] responses = []
for hostl in hosts_available: for hostl in hosts_available:
resp.append(send_pilpil_command(hostl, arg0, arg1, arg2)) responses.append(send_pilpil_command(hostl, arg0, arg1, arg2))
status_message = resp status_message = responses
elif host not in hosts_available: elif host not in hosts_available:
status_message = "<p>{}</p>".format("Host is not reachable") status_message = "<p>{}</p>".format("Host is not reachable")
else: else:

View File

@ -32,7 +32,7 @@ sha256 : 0fe3fe76d0e56e445124fa20646fa8b3d8c59568786b3ebc8a96d83d92f203e3
md5 : dee7af70135994169cab4f073ee51905 md5 : dee7af70135994169cab4f073ee51905
sha256 : ec3e17fc9b41f8c5181484e9866be2d1d92cab8403210e3d22f4f689edd4cfde sha256 : ec3e17fc9b41f8c5181484e9866be2d1d92cab8403210e3d22f4f689edd4cfde
* Switch to rpi os Bullseye * Switch to rpi os Bullseye arm64
* Switch to user 'pil', pw 'pilpoil' * Switch to user 'pil', pw 'pilpoil'
* client config file parsing ( look for 'pilpil-client.toml' in ./, ~/., ~/.config/) * client config file parsing ( look for 'pilpil-client.toml' in ./, ~/., ~/.config/)
* Add media folder sync (scp, rsync, http upload) * Add media folder sync (scp, rsync, http upload)
@ -70,6 +70,7 @@ sha256 :
* setup : Generate/install http auth secret at setup * setup : Generate/install http auth secret at setup
* setup : Add dry, help flags * setup : Add dry, help flags
* FR localisation for server, client and install script * FR localisation for server, client and install script
* pep8ification
# OTHER: # OTHER:
@ -77,7 +78,7 @@ sha256 :
# TODO : # TODO :
* ? python sanitization * ~ python sanitization
* ~ Test with several rpis * ~ Test with several rpis
* ? Scripts hotspot linux/win/mac * ? Scripts hotspot linux/win/mac
* ? Config sync * ? Config sync

Binary file not shown.

View File

@ -54,6 +54,10 @@ msgstr "Erreur pendant la connexion à {}:{}"
msgid "{} reachable on {}" msgid "{} reachable on {}"
msgstr "{} accessible sur {}" msgstr "{} accessible sur {}"
#: app.py:311
msgid "Playlist"
msgstr "Liste de lecture"
#: app.py:331 #: app.py:331
msgid "Idle" msgid "Idle"
msgstr "Innocupé" msgstr "Innocupé"

View File

@ -1,16 +1,17 @@
[DEFAULT] [DEFAULT]
debug = 1 debug = 1
useSSL = true useSSL = false
CAfile = "selfCA.crt" CAfile = "selfCA.crt"
# scp, rsync, http # scp, rsync, http
sync_facility = "http" sync_facility = "http"
media_folder_local = "../medias" media_folder_local = "../medias"
media_folder_remote = "/home/pil/Videos" media_folder_remote = "~/Vidéos"
media_exts = ["mp4", "avi", "mkv"] media_exts = ["mp4", "avi", "mkv"]
vlc_auth = "secret" vlc_auth = "secret"
# OnNlY3JldA== # OnNlY3JldA==
cmd_auth = "secret" cmd_auth = "secret"
hosts = ["10.42.0.10", "10.42.0.11"] #hosts = ["10.42.0.10", "10.42.0.11"]
hosts = ["127.0.0.1"]
# VLC http LUA port # VLC http LUA port
vlc_port = 8887 vlc_port = 8887
# Clients cmd port # Clients cmd port

View File

@ -5,15 +5,17 @@ const tl_drag_attr = {id:"tl_drag", draggable:"true", ondragstart:"drag(event, t
// Config // Config
var timeline_color_cursor = "#FF8839"; var timeline_color_cursor = "#FF8839";
var timeline_color_bg = "#2EB8E6"; var timeline_color_bg = "#2EB8E6";
var scanInterval = 3000;
var status_all = "Searching network for live hosts..."
// Global vars
var src_id = ""; var src_id = "";
var medias_status = {}; var medias_status = {};
var fileButtons = [];
function updatePlaylist(){ function updatePlaylist(){
var new_list = []; var new_list = [];
var media_count = document.getElementById("timeline").children.length; var media_count = document.getElementById("timeline").children.length;
for (i=media_count,l=0;i>l;i--) { for (i=media_count,l=0;i>l;i--) {
//~ new_list.push(document.getElementById("timeline").children[i].children[0].getAttribute("media_id"));
toMove = document.getElementById("timeline").children[i-1].children[0].getAttribute("media_id"); toMove = document.getElementById("timeline").children[i-1].children[0].getAttribute("media_id");
console.log(toMove); console.log(toMove);
sendCmd("/all/move/" + toMove + "/1"); sendCmd("/all/move/" + toMove + "/1");
@ -21,10 +23,10 @@ function updatePlaylist(){
} }
function findTargetIndex(element, index, array, thisArg){ function findTargetIndex(element, index, array, thisArg){
if (element == this) { if (element == this) {
return index+1; return index+1;
} }
return 0; return 0;
}; };
function moveElements(target) { function moveElements(target) {
@ -47,26 +49,25 @@ function moveElements(target) {
}; };
function allowDrop(ev) { function allowDrop(ev) {
ev.preventDefault(); ev.preventDefault();
drop_id = ev.dataTransfer.getData("text"); drop_id = ev.dataTransfer.getData("text");
}; };
function drag(ev, source) { function drag(ev, source) {
src_id = ev.target.parentElement; src_id = ev.target.parentElement;
ev.dataTransfer.setData("text", ev.target.id); ev.dataTransfer.setData("text", ev.target.id);
}; };
function drop(ev, target) { function drop(ev, target) {
ev.preventDefault(); ev.preventDefault();
var data = ev.dataTransfer.getData("text"); var data = ev.dataTransfer.getData("text");
if (src_id.id != target.id) { if (src_id.id != target.id) {
dropTarget = moveElements(target); dropTarget = moveElements(target);
if (dropTarget) { if (dropTarget) {
dropTarget.appendChild(document.getElementById(data)); dropTarget.appendChild(document.getElementById(data));
updatePlaylist(); updatePlaylist();
} }
} }
}; };
function adjustTl() { function adjustTl() {
@ -88,36 +89,45 @@ function addAttr(id, attr, val , child=-1) {
}; };
function addElement(type, attr, meta = 0, j = 0){ function addElement(type, attr, meta = 0, j = 0){
var elem = document.createElement(type); var elem = document.createElement(type);
var keys_array = Object.keys(attr); var keys_array = Object.keys(attr);
for (i=0, l=keys_array.length;i<l;i++) { for (i=0, l=keys_array.length;i<l;i++) {
var att = document.createAttribute(keys_array[i]); var att = document.createAttribute(keys_array[i]);
if(!i){ if(!i){
att.value = Object.values(attr)[i]+j; att.value = Object.values(attr)[i]+j;
} else { } else {
att.value = Object.values(attr)[i]; att.value = Object.values(attr)[i];
}
elem.setAttributeNode(att);
}
// Set playlist id attribute
if (meta) {
att = document.createAttribute("media_id");
att.value = meta[0];
elem.setAttributeNode(att);
} }
elem.setAttributeNode(att); // Get filename
} elem.innerText = meta[1];
// Set playlist id attribute return elem;
if (meta) {
att = document.createAttribute("media_id");
att.value = meta[0];
elem.setAttributeNode(att);
}
// Get filename
elem.innerText = meta[1];
return elem;
}; };
var scanInterval = 3000;
// Bouttons de commande // Bouttons de commande
addEventListener("DOMContentLoaded", function() { addEventListener("DOMContentLoaded", function() {
sendCmd("/scan"); sendCmd("/scan");
sendCmd("/browse"); sendCmd("/browse_local");
sendCmd("/all/rssi"); sendCmd("/all/rssi");
sendCmd("/all/list"); sendCmd("/all/list");
sendCmd("/all/browse");
adjustTl(); adjustTl();
// Get filename when selected in table
//~ for (var i=0, l=fileButtons.length; i<l; i++) {
//~ var selected_file = fileButtons[i];
//~ // Sur un click
//~ selected_file.addEventListener("click", function(event) {
//~ // On intercepte le signal
//~ event.preventDefault();
//~ }
//~ }
// Tous les elements avec la classe ".command" // Tous les elements avec la classe ".command"
var commandButtons = document.querySelectorAll(".command"); var commandButtons = document.querySelectorAll(".command");
for (var i=0, l=commandButtons.length; i<l; i++) { for (var i=0, l=commandButtons.length; i<l; i++) {
@ -133,14 +143,17 @@ addEventListener("DOMContentLoaded", function() {
if ( !confirm("Êtes vous certain de vouloir effectuer cette action ?") ) { if ( !confirm("Êtes vous certain de vouloir effectuer cette action ?") ) {
return 0; return 0;
} }
} else if ( command == "/scan" ) { } else if ( command == "/scan" ) {
document.getElementById("status_all").innerHTML = "Searching network for live hosts..."; document.getElementById("status_all").innerHTML = status_all;
} else if ( command.indexOf("/sort") > -1 ){ } else if ( command.indexOf("/sort") > -1 ){
if (command.indexOf('/1/') > -1 ) { if (command.indexOf('/1/') > -1 ) {
clickedButton.value = clickedButton.value.replace('/1/','/0/') clickedButton.value = clickedButton.value.replace('/1/','/0/')
} else { } else {
clickedButton.value = clickedButton.value.replace('/0/','/1/') clickedButton.value = clickedButton.value.replace('/0/','/1/')
} }
} else if ( command.indexOf("/move") > -1 ) { } else if ( command.indexOf("/move") > -1 ) {
const test_array = [21,19,20]; const test_array = [21,19,20];
for (i=test_array.length, l=0;i>l;i--){ for (i=test_array.length, l=0;i>l;i--){
@ -155,7 +168,11 @@ addEventListener("DOMContentLoaded", function() {
var request = new XMLHttpRequest(); var request = new XMLHttpRequest();
if ( command == "/scan" ) { if ( command == "/scan" ) {
request.onload = sendCmd(command); request.onload = sendCmd(command);
} } else if ( command == "/sync/all" ) {
status_all = "Syncing files..."
request.onload = sendCmd("/sync/status");
};
// On construit la commande // On construit la commande
request.open("GET", command, true); request.open("GET", command, true);
// et on l'envoie // et on l'envoie
@ -175,15 +192,6 @@ function parseResult(command, infos_array) {
document.getElementById("status_"+infos_array[i].host).innerHTML = infos_array[i].file + " <br/> " + infos_array[i].time + " / " + infos_array[i].leng; 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; medias_status[infos_array[i].id] = infos_array[i].pos;
} }
// Find currently playing element
//~ var pl_length = document.getElementById("timeline").getAttribute("length");
//~ for (j=0,k=pl_length;j<k;j++){
//~ if (document.getElementById("timeline").children[j].children[0].getAttribute('media_id') == infos_array[i].id ) {
//~ addAttr(document.getElementById("timeline").children[j].children[0].id, "pos", infos_array[i].pos);
//~ }
//~ };
//~ document.getElementById("timeline").children[0].children[0].hasAttribute('media_id')
// Toggle loop indicator
if (infos_array[i].loop == "true") { if (infos_array[i].loop == "true") {
document.getElementById("loop_ind_" + infos_array[i].host).style.backgroundColor = "#78E738" document.getElementById("loop_ind_" + infos_array[i].host).style.backgroundColor = "#78E738"
} else { } else {
@ -201,7 +209,7 @@ function parseResult(command, infos_array) {
for (var i = 0, l=infos_array.length; i<l; i++) { for (var i = 0, l=infos_array.length; i<l; i++) {
// Fill Playlist infos // Fill Playlist infos
document.getElementById("playlist_"+infos_array[i].host).innerHTML = infos_array[i].leng + " item(s) in playlist - " + infos_array[i].duration; //~ document.getElementById("playlist_"+infos_array[i].host).innerHTML = infos_array[i].leng + " item(s) in playlist - " + infos_array[i].duration;
// Build html table and timeline // Build html table and timeline
var items_array = Array.from(infos_array[i].items); var items_array = Array.from(infos_array[i].items);
//console.log(items_array.length); //console.log(items_array.length);
@ -212,20 +220,20 @@ function parseResult(command, infos_array) {
}; };
break; break;
} }
var html_table = "<table>" + //~ var html_table = "<table>" +
"<tr>" + //~ "<tr>" +
"<th>Id</th>" + //~ "<th>Id</th>" +
"<th>Filename</th>" + //~ "<th>Filename</th>" +
"<th>Duration</th>" + //~ "<th>Duration</th>" +
"</tr>"; //~ "</tr>";
for (var j = 0, k=items_array.length; j<k; j++) { for (var j = 0, k=items_array.length; j<k; j++) {
// Table // Table
item_meta = items_array[j].split(';'); item_meta = items_array[j].split(';');
html_table += "<tr>" + //~ html_table += "<tr>" +
"<td>" + item_meta[0] + "</td>" + //~ "<td>" + item_meta[0] + "</td>" +
"<td>" + item_meta[1] + "</td>" + //~ "<td>" + item_meta[1] + "</td>" +
"<td>" + item_meta[2] + "</td>" + //~ "<td>" + item_meta[2] + "</td>" +
"</tr>" ; //~ "</tr>" ;
// Timeline // Timeline
var child_node = addElement("div", tl_drag_attr, item_meta, j); var child_node = addElement("div", tl_drag_attr, item_meta, j);
var len = document.getElementById("timeline").children.length; var len = document.getElementById("timeline").children.length;
@ -248,8 +256,8 @@ function parseResult(command, infos_array) {
document.getElementById(tl_cont_attr.id + j).children[0].style.background = "linear-gradient(90deg," + timeline_color_bg + " " + pos1 + ", " + timeline_color_cursor + " " + pos + ", " + timeline_color_bg + " " + pos + ")"; document.getElementById(tl_cont_attr.id + j).children[0].style.background = "linear-gradient(90deg," + timeline_color_bg + " " + pos1 + ", " + timeline_color_cursor + " " + pos + ", " + timeline_color_bg + " " + pos + ")";
} }
} }
html_table += "</table>"; //~ html_table += "</table>";
document.getElementById("playlist_"+infos_array[i].host).innerHTML += html_table; //~ document.getElementById("playlist_"+infos_array[i].host).innerHTML += html_table;
}; };
break; break;
case "/scan": case "/scan":
@ -263,11 +271,13 @@ function parseResult(command, infos_array) {
} }
if (host_up.length) { if (host_up.length) {
scanInterval = 10000; scanInterval = 10000;
document.getElementById("status_all").innerHTML = "Scan intarvel set to " + scanInterval; //~ document.getElementById("status_all").innerHTML = "Scan interval set to " + scanInterval;
status_all = "Scan interval set to " + scanInterval;
} }
document.getElementById("status_all").innerHTML = host_up.length + " client(s)."; //~ document.getElementById("status_all").innerHTML = host_up.length + " client(s).";
status_all = host_up.length + " client(s).";
break; break;
case "/browse": case "/browse_local":
var html_table = "<table>" + var html_table = "<table>" +
"<tr>" + "<tr>" +
"<th>Filename</th>" + "<th>Filename</th>" +
@ -299,6 +309,47 @@ function parseResult(command, infos_array) {
}; };
}; };
break; break;
case "/all/browse":
for (var i=0, l=infos_array.length; i<l; i++) {
hosts = Object.keys(infos_array[i]);
//~ console.log(keys);
//~ var html_table = "<table id='file_sel_" + hosts + "'>" +
//~ "<tr>" +
//~ "<th>Filename</th>" +
//"<th>Size</th>" +
//~ "</tr>";
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);
//~ html_table += "<tr>" +
//~ "<td class='.file_selection'>" + item_meta["name"] + "</td>" +
//"<td>" + item_meta["size"] + "</td>" +
//~ "</tr>" ;
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"];
tr.addEventListener("click", enqueueFile, false)
document.getElementById("file_sel_"+hosts).appendChild(tr);
};
}
//~ html_table += "</table>";
//~ document.getElementById("playlist_"+hosts).innerHTML += html_table;
//~ fileButtons = document.querySelectorAll('.file_selection');
//~ console.log(fileButtons);
//~ for (var i=fileButtons.length; i>0; i--) {
//~ fileButtons[i].addEventListener('click', selectFile, false);
//~ }
}
break;
case "/sync/status":
status_all = infos_array;
break;
}; // End switch case }; // End switch case
}; };
@ -321,8 +372,20 @@ function sendCmd(command) {
request.send(); request.send();
}; };
setInterval( sendCmd, 500, "/all/status"); function enqueueFile(evt) {
setInterval( sendCmd, 1000, "/all/list"); //console.log(evt.currentTarget.innerText + evt.currentTarget.host);
setInterval( sendCmd, scanInterval, "/scan"); sendCmd("/" + evt.currentTarget.host + "/enqueue/" + evt.currentTarget.innerText);
setInterval( sendCmd, 20000, "/all/rssi"); };
function updateStatusAll() {
document.getElementById("status_all").innerHTML = status_all;
};
setInterval(sendCmd, 500, "/all/status");
setInterval(sendCmd, 1000, "/all/list");
//~ setInterval(sendCmd, 3000, "/all/browse");
setInterval(sendCmd, scanInterval, "/scan");
setInterval(sendCmd, 20000, "/all/rssi");
setInterval(updateStatusAll, 1000);

View File

@ -1,7 +1,7 @@
body {color:#fff;background-color:#666;margin:0;} body {color:#fff;background-color:#666;margin:0;}
div {box-sizing: border-box;} div {box-sizing: border-box;}
h2 {margin:0;} h2 {margin:0;}
table {background-color: #555;margin:.5em;max-width:100%;font-size:.9em} table {background-color: #555;margin-left:.5em;max-width:100%;font-size:.9em}
td {padding: 0;} td {padding: 0;}
th {background-color: #aaa;} th {background-color: #aaa;}
tr:nth-child(2n+1) {background-color: #888;} tr:nth-child(2n+1) {background-color: #888;}
@ -87,9 +87,9 @@ tr:nth-child(2n+1) {background-color: #888;}
} }
.btn_txt {display: block;font-size: small;} .btn_txt {display: block;font-size: small;}
/*Right column*/ /*Right column*/
.right_col {width: 79.9%;display: inline-block;} .right_col {width: 71%;display: inline-block;}
/*Left column*/ /*Left column*/
.left_col {width: 20%;display: inline-block;float: left;clear: left;} .left_col {width: 28%;display: inline-block;float: left;clear: left;}
.left_col button { .left_col button {
width: 2em; width: 2em;
height: 2em; height: 2em;
@ -109,30 +109,38 @@ tr:nth-child(2n+1) {background-color: #888;}
} }
.left_col button:hover .btn_txt {display:block;} .left_col button:hover .btn_txt {display:block;}
.col_1 { .col_1 {
width: 40%; width: 40%;
float: left; float: left;
padding: 3% 0 0 5%; padding: 3% 0 0 5%;
}
} }
.col_2 { .col_2 {
width: 60%; width: 59%;
display: inline-block; display: inline-block;
clear: right; clear: right;
height: 170px;
}
.col_2 div {
overflow-y: scroll;
height: 170px;
} }
.indicator { .indicator {
display:inline-block; display:inline-block;
background-color: #C32600; background-color: #C32600;
margin: 0 0 0 5%; margin: 0 0 0 5%;
padding: 0.3em; padding: 0.3em;
} }
.file_sel {
width: 100%;
margin: 0;
}
.wl_indicator { .wl_indicator {
display: inline-block; display: inline-block;
background-color: #bbb; background-color: #bbb;
vertical-align: bottom; vertical-align: bottom;
margin: 0 1px; margin: 0 1px;
padding: 0; padding: 0;
height: .5em; height: .5em;
width: 5%; width: 5%;
} }
#wl_0 {height:.5em;} #wl_0 {height:.5em;}
#wl_1 {height:.65em;} #wl_1 {height:.65em;}

View File

@ -56,7 +56,15 @@
<p id="repeat_ind_{{ host }}" class="indicator">Repeat</p> <p id="repeat_ind_{{ host }}" class="indicator">Repeat</p>
</div> </div>
<div class="col_2"> <div class="col_2">
<p id="playlist_{{ host }}">{{queue_msgs[1]}}</p> <div>{{queue_msgs[1]}}
<div id="playlist_{{ host }}" class="table_cont">
<table id="file_sel_{{ host }}">
<tr>
<th>Filename</th>
</tr>
</table>
</div>
</div>
</div> </div>
</div> </div>
<div class="right_col"> <div class="right_col">