return res;
}
+static void dump_params(struct ez_httprequest *r) {
+ printf("request: '%s'\n", r->url);
+ for (struct ez_pair *w = ez_list_head(&r->params), *n = ez_node_succ(w);n;w=n,n = ez_node_succ(w))
+ printf(" %s=%s\n", w->name, w->value);
+}
+
static struct ez_pair *find_param(struct ez_httprequest *r, const char *name) {
for (struct ez_pair *w = ez_list_head(&r->params), *n = ez_node_succ(w);n;w=n,n = ez_node_succ(w)) {
if (strcasecmp(w->name, name) == 0)
obstack_printf(os, "\"%02d:%02d:%02d\"", (int)h, (int)m, (int)s);
}
+// Might be good enough assuming 'nice' data.
+static void write_value_json(struct obstack *os, const char *s) {
+ char c;
+
+ obstack_1grow(os, '"');
+ while ((c = *s++)) {
+ switch (c) {
+ case '\b':
+ obstack_grow(os, "\\b", 2);
+ break;
+ case '\f':
+ obstack_grow(os, "\\f", 2);
+ break;
+ case '\n':
+ obstack_grow(os, "\\n", 2);
+ break;
+ case '\r':
+ obstack_grow(os, "\\r", 2);
+ break;
+ case '\t':
+ obstack_grow(os, "\\t", 2);
+ break;
+ case '\\':
+ case '"':
+ obstack_1grow(os, '\\');
+ default:
+ obstack_1grow(os, c);
+ break;
+ }
+ }
+ obstack_1grow(os, '"');
+}
+
static void write_file_json(struct obstack *os, dbfile *file) {
obstack_printf(os, "\"id\":\"%d\"", file->id);
- if (file->title)
- obstack_printf(os, ",\"title\":\"%s\"", file->title);
- if (file->artist)
- obstack_printf(os, ",\"artist\":\"%s\"", file->artist);
+ if (file->title) {
+ obstack_sgrow(os, ",\"title\":");
+ write_value_json(os, file->title);
+ }
+ if (file->artist) {
+ obstack_sgrow(os, ",\"artist\":");
+ write_value_json(os, file->artist);
+ }
obstack_sgrow(os, ",\"length\":");
duration_value(os, file->duration / 1000);
obstack_1grow(os, '{');
write_file_json(os, file);
- obstack_printf(os, ",\"path\":\"%s\"", file->path);
+ obstack_sgrow(os, ",\"path\":");
+ write_value_json(os, file->path);
obstack_printf(os, ",\"status\":\"%s\"", (state.state & 2) ? "paused" : "playing");
obstack_printf(os, ",\"listid\":\"%u\"", state.current.listid);
obstack_1grow(io, '{');
obstack_printf(io, "\"id\":\"%d\"", list->id);
- obstack_printf(io, ",\"name\":\"%s\"", list->name);
+ obstack_sgrow(io, ",\"name\":");
+ write_value_json(io, list->name);
+ if (list->desc) {
+ obstack_sgrow(io, ",\"desc\":");
+ write_value_json(io, list->desc);
+ }
obstack_printf(io, ",\"size\":\"%d\"", list->size);
obstack_1grow(io, '}');
}
dbscan scan;
dbfile *file;
dbtxn *tx = dbindex_begin(db, NULL, 1);
- dblistcursor list = { .listid = listid, .seq = seq, .fileid = fileid };
+ dblistcursor cursor = { .listid = listid, .seq = seq, .fileid = fileid };
+ dblist *list = dblist_get(tx, db, listid);
- // TODO: get list properly and seq
+ // FIXME: get list properly and seq
+ if (!list)
+ goto fail;
obstack_1grow(io, '{');
- obstack_sgrow(io, "\"list_name\":\"shuffle\"");
+
+ obstack_sgrow(io, "\"name\":");
+ write_value_json(io, list->name);
+ if (list->desc) {
+ obstack_sgrow(io, ",\"desc\":");
+ write_value_json(io, list->desc);
+ }
obstack_sgrow(io, ",\"items\": [");
- file = dbscan_list_entry(tx, &scan, db, &list);
+ file = dbscan_list_entry(tx, &scan, db, &cursor);
for (int i=0;file && i<10;i++) {
obstack_sgrow(io, i > 0 ? ",{" : "{");
write_file_json(io, file);
- obstack_printf(io, ",\"listid\":\"%u\"", list.listid);
- obstack_printf(io, ",\"seq\":\"%u\"", list.seq);
+ obstack_printf(io, ",\"listid\":\"%u\"", cursor.listid);
+ obstack_printf(io, ",\"seq\":\"%u\"", cursor.seq);
obstack_1grow(io, '}');
dbfile_free(file);
- file = dbscan_list_entry_next(&scan, &list);
+ file = dbscan_list_entry_next(&scan, &cursor);
}
dbfile_free(file);
obstack_sgrow(io, "]}");
dbscan_free(&scan);
+fail:
dbindex_abort(tx);
}
int seq = param_int(req, "s", 0); // TODO: should be in /x/list/{listid}/{seq} ?
printf("%s %s, listid=%d seq=%d fid=%d\n", req->method, req->url, lid, seq, fid);
-
if (strcmp(req->method, "GET") == 0) {
write_list_json(db, os, lid, seq, fid);
return set_data_json(rep);
} else if (strcmp(req->method, "POST") == 0) {
struct dblistcursor info = { .listid = lid, .fileid = fid };
- // /x/list/{listid} post fid=? ignore seq
+ // /x/list/{listid} post f=? ignore seq
if (fid == 0)
goto fail0;
dbtxn *tx = dbindex_begin(db, NULL, 0);
if (!tx)
goto fail0;
- if (dblist_add_file(tx, db, &info))
- goto fail1;
dblist *list = dblist_get(tx, db, lid);
if (!list)
goto fail1;
+ if (dblist_add_file(tx, db, &info))
+ goto fail1;
if (dbindex_commit(tx))
goto fail2;
return set_data_json(rep);
}
-// goto specific file /goto?f={file.id}
+// goto specific file /goto?f={file.id}[&l={list.id}]
+// fid lid action
+// 0 !0 start playing list (FIXME)
+// !0 0 'play now' (play next?)
+// !0 !0 play file in list
+//
// TODO: playlist
static int handle_goto(struct ez_httprequest *req, struct ez_httpresponse *rep) {
int fid = param_int(req, "f", 0);
+ int lid = param_int(req, "l", 0);
- // TODO: look it up in the db?
+ if (fid == 0 && lid == 0) {
+ httpresponse_set_response(rep, 404, "Not Found");
+ return 0;
+ } else {
+ struct notify_goto gogo = { .info.fileid = fid, .info.listid = lid };
- if (fid != 0) {
- struct notify_goto gogo = { .info.fileid = fid };
+ notify_msg_send(player, (fid != 0 && lid == 0) ? NOTIFY_PLAY_NOW : NOTIFY_PLAY_GOTO, 0, &gogo);
- notify_msg_send(player, NOTIFY_PLAY_GOTO, 0, &gogo);
httpresponse_set_response(rep, 202, "Requested");
return 0;
- } else {
- httpresponse_set_response(rep, 404, "Not Found");
- return 0;
}
}
<http://www.gnu.org/licenses/>.
*/
+#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavfilter/avfilter.h>
AVCodecContext *avctx = ap->cc;
AVRational time_base = ap->audio->time_base;
+ char layout[32];
- sprintf(tmp, "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64,
+ res = av_channel_layout_describe(&avctx->ch_layout, layout, sizeof(layout));
+ sprintf(tmp, "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%s",
time_base.num, time_base.den, avctx->sample_rate,
av_get_sample_fmt_name(avctx->sample_fmt),
- avctx->channel_layout);
+ layout);
fprintf(stderr, "asource: %s\n", tmp);
res = avfilter_graph_create_filter(&ap->asource_ctx, asource, "src", tmp, NULL, ap->fg);
if (res < 0)
static int audio_init_media(struct audio_player *ap, const char *path) {
int res;
- AVCodec *codec;
+ const AVCodec *codec;
int audioid;
+ char layout[32];
printf("audio_init_media '%s'\n", path);
if (res < 0)
goto fail;
+ res = av_channel_layout_describe(&ap->cc->ch_layout, layout, sizeof(layout));
+
printf("codec rate: %d\n", ap->cc->sample_rate);
- printf("codec channels: %d\n", ap->cc->channels);
- printf("codec layout: %lu\n", ap->cc->channel_layout);
+ printf("codec channels: %d\n", ap->cc->ch_layout.nb_channels);
+ printf("codec layout: %s\n", layout);
printf("codec format: %s\n", av_get_sample_fmt_name(ap->cc->sample_fmt));
res = audio_init_pcm(ap);
- DEFAULT
Once a file finishes the search begins at USER always?
+
+ This is shit.
+
+ Playlist needs to be done differently.
+ - one list-cursor for each list
+ - current is just what is playing now
+ - when it finishes the list state is copied to current and advanced
*/
static int audio_advance_file(struct audio_player *ap, dbfile *(*advance)(dbscan *scan, dblistcursor *info)) {
int res;
dbfile *file;
dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
int retry = 0;
- int trynext = 1;
dbfile_free(ap->playing);
ap->playing = NULL;
if (ap->playing_state.list_mode == LIST_PLAYNOW)
ap->playing_state.list_mode = LIST_USER;
+ // TODO: what what
+ ap->playing_state.list_mode = LIST_JUKEBOX;
+
do {
dblistcursor *active;
printf("loop: %d\n", retry);
file = dbscan_list_entry(tx, &scan, ap->index, active);
if (file) {
- //printf("entry: %s\n", file->path);
- if (trynext) {
- dbfile_free(file);
- file = advance(&scan, active);
- }
- //printf("next: %s\n", file ? file->path : "<nil>");
+ printf("entry: %s\n", file->path);
} else {
// repeat? reset?
printf("no entry found. scan.res =%d db.res=%d\n", dbscan_result(&scan), dbindex_result(ap->index));
ap->playing_state.current = *active;
ap->playing = file;
audio_checkpoint_state(ap);
- break;
+
+ // store advanced state as well
+ file = dbscan_list_entry_next(&scan, active);
+ if (file) {
+ dbfile_free(file);
+ } else {
+ active->seq += 1;
+ active->fileid = 0;
+ }
+ printf("playing ok, next is lid=%d fid=%d seq=%d\n", active->listid, active->fileid, active->seq);
+ dbscan_free(&scan);
+ dbindex_abort(tx);
+ return res;
}
dbfile_free(file);
case LIST_USER:
// if loop ... just loop
ap->playing_state.list_mode = LIST_JUKEBOX;
- trynext = 1;
break;
case LIST_JUKEBOX:
ap->playing_state.list_mode = LIST_DEFAULT;
- trynext = 0;
break;
case LIST_DEFAULT:
// loop the default list always
ap->playing_state.list_default.seq = 0;
ap->playing_state.list_default.fileid = 0;
- trynext = 0;
break;
}
} else {
// If we're not already in jukebox mode, set the jukebox state to point
// to the previous file so that 'next file' will find the right one
// FIXME: what about 'prev' file?
- // FIXME: find the actual seq in the db
+ // FIXME: find the actual seq in the db (last entry before this is added)
// FIXME: this will drop the last song on restore, perhaps it should have a next_list_mode instead
// this would also fix the same problem for play-now
if ((res = dbstate_get(tx, ap->index, &ap->playing_state)) == 0 && ap->playing_state.state == 1) {
dbfile *file = dbfile_get(tx, ap->index, ap->playing_state.current.fileid);
- printf("restoring file %s\n", file->path);
+ printf("restoring lid=%d fid=%d seq=%d file %s\n",
+ ap->playing_state.current.listid,
+ ap->playing_state.current.fileid,
+ ap->playing_state.current.seq,
+ file->path);
if (file) {
if (dbfile_full_path(tx, ap->index, file)) {
ap->playing = file;
} else {
dbfile_free(file);
}
- }
+ }
} else {
// FIXME: check playing_state.state == 1 separate to error test above
res = -1;
.media-button path {
fill: white;
}
+ /* radio/tabs */
+ .options {
+ display: flex;
+ flex-wrap: wrap;
+ }
+ .options input {
+ display: none;
+ }
+ .options input + label {
+ display: block;
+ padding: 4px 8px;
+ margin: 10px;
+ border: 1px solid black;
+ border-radius: 4px;
+ margin: 0px 2px;
+ background-color: lightgrey;
+ box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.5);
+ min-width: 10em;
+ }
+ .options input:checked + label {
+ background-color: slategrey;
+ }
/* scrolling lists */
.track-list tbody {
display: block;
*/
- var playlistID = 1; // current list, from state?
- var listStartID = 0; // "coming up" first entry
- var allPlayLists = [];
-
- // failed?
- function requestGET(url, done) {
- var r = new XMLHttpRequest();
- r.onreadystatechange = function() {
- //console.log(r);
- if (r.readyState === XMLHttpRequest.DONE) {
- done(r);
- }
- }
- r.open("GET", url);
- r.send();
- }
-
- function playerCommand(name) {
- var r = new XMLHttpRequest();
-
- console.log("command " + name);
- r.onreadystatechange = function() {
- console.log(r);
- if (r.readyState === XMLHttpRequest.DONE) {
- if (r.status === 0 || (r.status >= 200 && r.status < 400)) {
- console.log("got it");
- }
- }
- }
- r.open("GET", "/x/" + name);
- r.send();
- }
- // goto button, b.id = "goto-fileid"
- function playerGotoClick(e) {
- playerCommand("goto?f=" + e.currentTarget.id.substring(5));
- }
- function playerGotoKey(e) {
- if (e.key == "Enter" || e.key == " ") {
- playerCommand("goto?f=" + e.currentTarget.id.substring(5));
- }
- }
-
- function playerCommandClick(e) {
- playerCommand(e.currentTarget.id.substring(7));
- }
- function playerCommandKey(e) {
- if (e.key == "Enter" || e.key == " ") {
- playerCommand(e.currentTarget.id.substring(7));
- }
- }
-
- function playerCommandClick2(e) {
- playerCommand(e.currentTarget.getAttribute("query"));
- }
- function playerCommandKey2(e) {
- if (e.key == "Enter" || e.key == " ") {
- playerCommand(e.currentTarget.getAttribute("query"));
- }
- }
-
- function playerSetPlaylists(items, list) {
- let selected = items.value;
-
- while (items.firstChild)
- items.firstChild.remove();
-
- for (var i=0; i<list.length; i++) {
- let v = list[i];
- let o = document.createElement("option");
-
- o.value = v.id; // v.name?
- o.textContent = v.name + " (" + v.size + ")";
-
- items.appendChild(o);
- }
-
- if (selected) {
- items.value = selected;
- }
- }
-
- function playerUpdateLists() {
- console.log("polling lists");
- requestGET("/x/list", function(r) {
- if (r.status === 200) {
- let list = JSON.parse(r.response);
-
- allPlayLists = list.items;
-
- // TODO: put this somewhere
- playerSetPlaylists(document.getElementById("lists"), allPlayLists);
- //playerSetPlaylists(document.getElementById("next-lists"), list);
- }
- });
- }
- function createIcon(href) {
- let b = document.createElementNS("http://www.w3.org/2000/svg", "svg");
-
- b.classList.add('player-button');
- b.tabIndex = 1;
- b.setAttribute("viewBox", "-2 -2 28 28");
- b.setAttribute("width", "3em");
-
- let u = document.createElementNS("http://www.w3.org/2000/svg", "use");
- u.setAttribute("href", href);
- b.appendChild(u);
- return b;
- }
- function playerAppendTracks(items, list, search) {
- for (var i=0; i<list.length; i++) {
- let v = list[i];
- let row = document.createElement("tr");
- let c = row.insertCell();
-
- let d = document.createElement("div");
- let t = document.createElement("div");
- let a = document.createElement("div");
-
- c.classList.add("track");
-
- t.textContent = v.title;
- t.classList.add("title");
- a.textContent = v.artist;
- a.classList.add("artist");
-
- d.appendChild(t);
- d.appendChild(a);
- c.appendChild(d);
-
- c = row.insertCell();
- c.textContent = v.length;
- c.classList.add("duration");
-
- c = row.insertCell();
- /*
- let b = document.createElement("span");
- b.id = "goto-" + v.id;
- b.textContent = "\u23f5";// "\u25B6";
- b.tabIndex = 50;
- b.setAttribute("query", "goto?f=" + v.id);
- b.addEventListener("click", playerCommandClick2);
- b.addEventListener("keypress", playerCommandKey2);
- */
- /*
- let b = document.createElementNS("http://www.w3.org/2000/svg", "svg");
-
- b.tabIndex = 1;
- b.setAttribute("query", "goto?f=" + v.id);
- b.setAttribute("viewBox", "-2 -2 28 28");
- b.setAttribute("width", "3em");
+ var listStartID = 0; // "coming up" first entry
+ var allPlayLists = [];
+ var playerStatus = null; // last status update from player
+ var playerStatusShown = null; // last time player status was updated?
+
+ // failed?
+ function requestGET(url, done) {
+ var r = new XMLHttpRequest();
+ r.onreadystatechange = function() {
+ //console.log(r);
+ if (r.readyState === XMLHttpRequest.DONE) {
+ done(r);
+ }
+ }
+ r.open("GET", url);
+ r.send();
+ }
+
+ function playerCommand(method, name) {
+ var r = new XMLHttpRequest();
+
+ console.log("command " + method + " /x/" + name);
+ r.onreadystatechange = function() {
+ console.log(r);
+ if (r.readyState === XMLHttpRequest.DONE) {
+ if (r.status === 0 || (r.status >= 200 && r.status < 400)) {
+ console.log("command " + name + " finished rc=" + r.status);
+ }
+ }
+ }
+ r.open(method, "/x/" + name);
+ r.send();
+ }
+
+ // goto button, b.id = "goto-fileid"
+ function playerGotoClick(e) {
+ playerCommand("GET", "goto?f=" + e.currentTarget.id.substring(5));
+ }
+ function playerGotoKey(e) {
+ if (e.key == "Enter" || e.key == " ") {
+ playerCommand("GET", "goto?f=" + e.currentTarget.id.substring(5));
+ }
+ }
+
+ function playerCommandClick(e) {
+ playerCommand("GET", e.currentTarget.id.substring(7));
+ }
+ function playerCommandKey(e) {
+ if (e.key == "Enter" || e.key == " ") {
+ playerCommand("GET", e.currentTarget.id.substring(7));
+ }
+ }
+
+ function playerCommandClick2(e) {
+ playerCommand("GET", e.currentTarget.getAttribute("query"));
+ }
+ function playerCommandKey2(e) {
+ if (e.key == "Enter" || e.key == " ") {
+ playerCommand("GET", e.currentTarget.getAttribute("query"));
+ }
+ }
+ function playerPostClick(e) {
+ playerCommand("POST", e.currentTarget.getAttribute("query"));
+ }
+ function playerPostKey(e) {
+ if (e.key == "Enter" || e.key == " ") {
+ playerCommand("POST", e.currentTarget.getAttribute("query"));
+ }
+ }
+
+ function clear(items) {
+ while (items.firstChild)
+ items.firstChild.remove();
+ }
+ // TBD
+ function playerSetPlaylists(items, list) {
+ let selected = items.value;
+
+ clear(items);
+
+ for (var i=0; i<list.length; i++) {
+ let v = list[i];
+ let o = document.createElement("option");
+
+ o.value = v.id; // v.name?
+ o.textContent = v.name + " (" + v.size + ")";
+
+ items.appendChild(o);
+ }
+
+ if (selected) {
+ items.value = selected;
+ }
+ }
+ // the tabby version
+ function playerSetPlaylistsX(items, list) {
+ clear(items);
+ console.log('status listid = ' + playerStatus.listid);
+ for (var i=0; i<list.length; i++) {
+ let v = list[i];
+ let input = document.createElement("input");
+ let label = document.createElement("label");
+
+ input.type = 'radio';
+ input.name = 'list-options';
+ input.id = 'list_' + v.id;
+ input.value = v.id;
+ input.checked = (v.id == playerStatus.listid);
+ items.appendChild(input);
+
+ label.htmlFor = 'list_' + v.id;
+ label.textContent = v.desc;
+
+ items.appendChild(label);
+ console.log(' item.id = ' + v.id);
+ }
+ }
+
+
+ function playerUpdateLists() {
+ console.log("polling lists");
+ requestGET("/x/list", function(r) {
+ if (r.status === 200) {
+ let list = JSON.parse(r.response);
+
+ allPlayLists = list.items;
+
+ // TODO: put this somewhere
+ // TBD
+ //playerSetPlaylists(document.getElementById("lists"), allPlayLists);
+ //playerSetPlaylists(document.getElementById("next-lists"), list);
+
+ playerSetPlaylistsX(document.getElementById("list-options"), allPlayLists);
+ }
+ });
+ }
+ function createIcon(href) {
+ let b = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+
+ b.classList.add('player-button');
+ b.tabIndex = 1;
+ b.setAttribute("viewBox", "-2 -2 28 28");
+ b.setAttribute("width", "3em");
+
+ let u = document.createElementNS("http://www.w3.org/2000/svg", "use");
+ u.setAttribute("href", href);
+ b.appendChild(u);
+ return b;
+ }
+ function playerAppendTracks(items, list, issearch) {
+ for (var i=0; i<list.length; i++) {
+ let v = list[i];
+ let row = document.createElement("tr");
+ let c = row.insertCell();
+
+ let d = document.createElement("div");
+ let t = document.createElement("div");
+ let a = document.createElement("div");
+
+ c.classList.add("track");
+
+ t.textContent = v.title;
+ t.classList.add("title");
+ a.textContent = v.artist;
+ a.classList.add("artist");
+
+ d.appendChild(t);
+ d.appendChild(a);
+ c.appendChild(d);
+
+ c = row.insertCell();
+ c.textContent = v.length;
+ c.classList.add("duration");
+
+ c = row.insertCell();
+
+ //if (!issearch) {
+ // let b = createIcon("#icon-nope");
+ // c.appendChild(b);
+ //}
+
+ if (issearch) {
+ let b = createIcon("#icon-playlist-add");
+ // FIXME: hardcoded jukebox list
+ b.setAttribute("query", "list/3?f=" + v.id);
+ c.appendChild(b);
+ b.addEventListener("click", playerPostClick);
+ b.addEventListener("keypress", playerPostKey);
+ c.appendChild(b);
+ } else {
+ let b = createIcon("#icon-play");
+ b.setAttribute("query", "goto?f=" + v.id + "&l=" + v.listid);
b.addEventListener("click", playerCommandClick2);
b.addEventListener("keypress", playerCommandKey2);
-
- let u = document.createElementNS("http://www.w3.org/2000/svg", "use");
- u.setAttribute("href", "#icon-play");
- b.appendChild(u);
- */
-
- let b = createIcon("#icon-play");
- b.setAttribute("query", "goto?f=" + v.id);
- b.addEventListener("click", playerCommandClick2);
- b.addEventListener("keypress", playerCommandKey2);
-
- c.appendChild(b);
-
- if (search) {
- b = createIcon("#icon-playlist-add");
- //b.setAttribute("query", "list/" + v.goto?f=" + v.id);
- //b.addEventListener("click", playerCommandClick2);
- //b.addEventListener("keypress", playerCommandKey2);
- c.appendChild(b);
- } else {
- b = createIcon("#icon-nope");
- c.appendChild(b);
- }
-
- c.classList.add("action");
-
- row.classList.add((i & 1) ? "odd" : "even");
-
- items.appendChild(row);
- }
- }
-
- function playerUpdateList(fileid, seq) {
- console.log("polling playlist");
- requestGET("/x/list/" + playlistID + "?f=" + fileid + "?s=" + seq, function(r) {
- if (r.status === 200) {
- let table = document.getElementById("coming-up");
- let items = document.getElementById("coming-up-list");
- let list = JSON.parse(r.response);
-
- while (items.firstChild)
- items.firstChild.remove();
-
- if (list.items.length > 0) {
- table.caption = document.createElement("caption");
- table.caption.textContent = "Coming up in playlist '" + list.list_name + "' …";
- }
-
- playerAppendTracks(items, list.items, 0);
- /*
- for (var i=0; i<list.list.length; i++) {
- let v = list.list[i];
- let row = document.createElement("tr");
- let c = row.insertCell();
-
- c.textContent = v.title;
- c.classList.add("title");
- c = row.insertCell();
- c.textContent = v.artist;
- c.classList.add("artist");
- c = row.insertCell();
- c.textContent = v.length;
- c.classList.add("duration");
-
- c = row.insertCell();
- let b = document.createElement("span");
- b.id = "goto-" + v.id;
- b.textContent = "GOTO";
- b.tabIndex = 50;
- b.addEventListener("click", playerGotoClick);
- b.addEventListener("keypress", playerGotoKey);
- c.appendChild(b);
-
- items.appendChild(row);
- } */
- }
- listStartID = fileid;
- });
- }
-
- function playerSearch(query) {
- console.log("search: " + query);
- requestGET("/x/search?q=" + encodeURIComponent(query), function(r) {
- if (r.status === 200) {
- let table = document.getElementById("search-results");
- let items = document.getElementById("search-results-list");
- let list = JSON.parse(r.response);
-
- while (items.firstChild)
- items.firstChild.remove();
-
- //table.caption = document.createElement("caption");
- //if (list.items.length > 0) {
- // table.caption.textContent = "Matches";
- //} else {
- // table.caption.textContent = "No matches";
- // }
-
- playerAppendTracks(items, list.items, 1);
- /*
- for (var i=0; i<list.items.length; i++) {
- let v = list.items[i];
- let row = document.createElement("tr");
- let c = row.insertCell();
-
- c.textContent = v.title;
- c = row.insertCell();
- c.textContent = v.artist;
- c = row.insertCell();
- c.textContent = v.length;
-
- c = row.insertCell();
- let b = document.createElement("span");
- b.id = "goto-" + v.id;
- b.textContent = "GOTO";
- b.tabIndex = 50;
- b.addEventListener("click", playerGotoClick);
- b.addEventListener("keypress", playerGotoKey);
- c.appendChild(b);
-
- // add to playlist button, or whatever
-
- items.appendChild(row);
- }*/
- }
- });
- }
-
- function playerPoll() {
- var r = new XMLHttpRequest();
-
- //console.log("polling");
- r.onreadystatechange = function() {
- //console.log(r);
- if (r.readyState === XMLHttpRequest.DONE) {
- if (r.status == 200) {
- var s = JSON.parse(r.response);
-
- document.getElementById("track-title").textContent = s.title;
- document.getElementById("track-artist").textContent = s.artist;
- document.getElementById("track-path").textContent = s.path;
- document.getElementById("track-length").textContent = s.length;
-
- /*
+ c.appendChild(b);
+ }
+
+ c.classList.add("action");
+
+ row.classList.add((i & 1) ? "odd" : "even");
+
+ items.appendChild(row);
+ }
+ }
+
+ function playerUpdateList(status) {
+ console.log("polling playlist");
+ requestGET("/x/list/" + status.listid + "?f=" + status.id + "?s=" + status.seq, function(r) {
+ if (r.status === 200) {
+ let table = document.getElementById("coming-up");
+ let items = document.getElementById("coming-up-list");
+ let list = JSON.parse(r.response);
+
+ while (items.firstChild)
+ items.firstChild.remove();
+
+ if (list.items.length > 0) {
+ table.caption = document.createElement("caption");
+ table.caption.textContent = "Coming up ...";
+ }
+
+ playerAppendTracks(items, list.items, 0);
+ }
+ // TODO: update based on this
+ playerStatusShown = status;
+ // TBD?
+ listStartID = status.id;
+ });
+ }
+
+ function playerUpdateSearch(query) {
+ console.log("search: " + query);
+ requestGET("/x/search?q=" + encodeURIComponent(query), function(r) {
+ if (r.status === 200) {
+ let table = document.getElementById("coming-up");
+ let items = document.getElementById("coming-up-list");
+ //let table = document.getElementById("search-results");
+ //let items = document.getElementById("search-results-list");
+ let list = JSON.parse(r.response);
+
+ while (items.firstChild)
+ items.firstChild.remove();
+
+ table.caption = document.createElement("caption");
+ if (list.items.length > 0) {
+ table.caption.textContent = "Matches (" + list.items.length + ")";
+ } else {
+ table.caption.textContent = "No matches";
+ }
+
+ playerAppendTracks(items, list.items, 1);
+ }
+ });
+ }
+ function playerUpdateListX() {
+ console.log("update search or playlist");
+ let query = document.getElementById("search-query").value;
+
+ if (query) {
+ playerUpdateSearch(query);
+ } else {
+ playerUpdateList(playerStatus);
+ }
+ }
+ function playerPoll() {
+ var r = new XMLHttpRequest();
+
+ //console.log("polling");
+ r.onreadystatechange = function() {
+ //console.log(r);
+ if (r.readyState === XMLHttpRequest.DONE) {
+ if (r.status == 200) {
+ var s = JSON.parse(r.response);
+
+ playerStatus = s;
+
+ document.getElementById("track-title").textContent = s.title;
+ document.getElementById("track-artist").textContent = s.artist;
+ document.getElementById("track-path").textContent = s.path;
+ document.getElementById("track-length").textContent = s.length;
+
+ /*
let p = document.getElementById("track-position");
if (s.status == "playing") {
p.textContent = "(stopped)";
}*/
- document.getElementById("track-progress").style.width = (s.positionms * 100 / s.lengthms) + "%";
- document.getElementById("track-position").textContent = s.position;
- document.getElementById("track-length").textContent = s.length;
-
- if (s.id != listStartID) {
- playerUpdateList(s.id, s.seq);
- playerUpdateLists();
- }
- } else {
- document.getElementById("track-title").textContent = "?";
- document.getElementById("track-artist").textContent = "?";
- document.getElementById("track-length").textContent = "?";
- document.getElementById("track-position").textContent = "?";
- }
-
- window.setTimeout(playerPoll, 5000);
- }
- }
- r.open("GET", "/x/status");
- r.send();
- }
-
- function playerCreateList(name) {
- var r = new XMLHttpRequest();
-
- console.log("create list: " + name);
-
- r.onreadystatechange = function() {
- console.log(r);
- if (r.readyState === XMLHttpRequest.DONE) {
- if (r.status === 0 || (r.status >= 200 && r.status < 400)) {
- console.log("got it");
- playerUpdateLists();
- }
- }
- }
- r.open("POST", "/x/list?name=" + encodeURIComponent(name));
- r.send();
- }
-
- document.addEventListener("DOMContentLoaded", function() {
- // query in attribute
- Array.prototype.forEach.call(document.getElementsByClassName("player-button"), function(b) {
- b.addEventListener("click", playerCommandClick2);
- b.addEventListener("keypress", playerCommandKey2);
- });
-
- playerPoll();
- });
+ document.getElementById("track-progress").style.width = (s.positionms * 100 / s.lengthms) + "%";
+ document.getElementById("track-position").textContent = s.position;
+ document.getElementById("track-length").textContent = s.length;
+
+ // TODO: use playerSttatusShown, or just pass it to playerUpdateListX()
+ if (s.id != listStartID) {
+ playerUpdateListX();
+ playerUpdateLists();
+ }
+ } else {
+ playerStatus = null;
+ document.getElementById("track-title").textContent = "?";
+ document.getElementById("track-artist").textContent = "?";
+ document.getElementById("track-length").textContent = "?";
+ document.getElementById("track-position").textContent = "?";
+ }
+
+ window.setTimeout(playerPoll, 1000);
+ }
+ }
+ r.open("GET", "/x/status");
+ r.send();
+ }
+
+ function playerCreateList(name) {
+ var r = new XMLHttpRequest();
+
+ console.log("create list: " + name);
+
+ r.onreadystatechange = function() {
+ console.log(r);
+ if (r.readyState === XMLHttpRequest.DONE) {
+ if (r.status === 0 || (r.status >= 200 && r.status < 400)) {
+ console.log("got it");
+ playerUpdateLists();
+ }
+ }
+ }
+ r.open("POST", "/x/list?name=" + encodeURIComponent(name));
+ r.send();
+ }
+
+ document.addEventListener("DOMContentLoaded", function() {
+ // query in attribute
+ Array.prototype.forEach.call(document.getElementsByClassName("player-button"), function(b) {
+ b.addEventListener("click", playerCommandClick2);
+ b.addEventListener("keypress", playerCommandKey2);
+ });
+
+ document.getElementById("track-bar").addEventListener("click", function(e) {
+ if (playerStatus) {
+ var seekms = Math.round(playerStatus.lengthms * e.offsetX / e.target.clientWidth);
+
+ playerCommand("GET", "seek?seek=" + (seekms/1000));
+ }
+ }, true);
+
+ playerPoll();
+ });
</script>
</head>
<body>
<rect class='button-border' width='24' height='24'/>
<path d="M3 6c-.55 0-1 .45-1 1v13c0 1.1.9 2 2 2h13c.55 0 1-.45 1-1s-.45-1-1-1H5c-.55 0-1-.45-1-1V7c0-.55-.45-1-1-1zm17-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 9h-3v3c0 .55-.45 1-1 1s-1-.45-1-1v-3h-3c-.55 0-1-.45-1-1s.45-1 1-1h3V6c0-.55.45-1 1-1s1 .45 1 1v3h3c.55 0 1 .45 1 1s-.45 1-1 1z"/>
</svg>
- <svg id=icon-add-to-queue' class='media-button' xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24">
+ <svg id='icon-add-to-queue' class='media-button' xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24">
<rect class='button-border' width='24' height='24'/>
<path d="M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v1c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-1h5c1.1 0 2-.9 2-2V5c0-1.11-.9-2-2-2zm-1 14H4c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h16c.55 0 1 .45 1 1v10c0 .55-.45 1-1 1zm-4-6c0 .55-.45 1-1 1h-2v2c0 .55-.45 1-1 1s-1-.45-1-1v-2H9c-.55 0-1-.45-1-1s.45-1 1-1h2V8c0-.55.45-1 1-1s1 .45 1 1v2h2c.55 0 1 .45 1 1z"/>
</svg>
</div>
<div class='progress-bar' style='width:100%;display:grid;grid-template-columns:8em 1fr 8em;'>
<span id='track-position' style='text-align:center;'></span>
- <div id='track-progress' class='progress-indicator'></div>
+ <div id='track-bar'>
+ <div id='track-progress' class='progress-indicator'></div>
+ </div>
<span id='track-length' style='text-align:centre;'></span>
</div>
</div>
</div>
-->
</div>
+ <!-- all playlists -->
+ <div id='list-options' class='options' style='padding:1em;'></div>
+
<div style='padding:1em;'>
<input id='new-list-name' type='text'> <input type='button' value='Create Playlist' onClick='playerCreateList(document.getElementById("new-list-name").value)'>
- <select id='lists'></select>
+ Search <input type='text' id='search-query' oninput='playerUpdateListX()'>
</div>
<div class='section'>
<table id='coming-up' class='track-list'>
</tbody>
</table>
</div>
+ <!--
<div id='search' class='section'>
- <!-- <input type='text' onchange='playerSearch(this.value)'> -->
+ <!-- <input type='text' onchange='playerSearch(this.value)'> --
<table id='search-results' class='track-list'>
- <caption>Search <input type='text' oninput='playerSearch(this.value)'></caption>
- <!--
- <thead>
- <tr><th class='track'>Track<th class='duration'>Length<th class='action'>Action <select id='search-lists'><option>list a</option><option>list b</option></select></tr>
- </thead> -->
+ <caption>Search <input id='search-query' type='text' oninput='playerUpdateListX()'></caption>
<tbody id='search-results-list'>
</tbody>
</table>
- </div>
+ </div> -->
</body>
</html>