From: Not Zed Date: Sun, 7 Jan 2024 23:35:59 +0000 (+1030) Subject: Checkpoint ongoing work. X-Git-Url: https://code.zedzone.au/cvs?a=commitdiff_plain;h=0cd869b900c3b921e9384ccca6fe36e679b29d26;p=playerz Checkpoint ongoing work. Updates to player gui. Added internals documentation. --- diff --git a/Makefile b/Makefile index fdb8bab..57b111f 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ # FIXME: dependencies n shit -FFMPEG=/opt/ffmpeg/4.0 -LMDB=/home/notzed/src/openldap/libraries/liblmdb +#FFMPEG=/opt/ffmpeg/4.0 +#LMDB=/home/notzed/src/openldap/libraries/liblmdb EZE=../libeze -WARN=-Wno-deprecated-declarations -Wno-parentheses -Wno-unused-but-set-variable -Wno-pointer-sign +WARN=-Wno-unused-but-set-variable -Wno-pointer-sign -Wmissing-prototypes pkgs=ffmpeg lmdb blkid asound espeak @@ -56,8 +56,8 @@ dbmarshal.c: blobs.o $(EZE)/ez-blob-compiler $(EZE)/ez-blob-compiler $< DBDISK_DESC DBFILE_DESC DBLIST_DESC > $@~ mv $@~ $@ -dbmarshal.o: dbmarshal.c - $(CC) $(CFLAGS) -Wno-unused -c -o $@ $< +dbmarshal.o: dbmarshal.c dbmarshal.h + $(CC) $(CFLAGS) -Wno-unused -Wno-missing-prototypes -c -o $@ $< clean: rm -f $(PROGS) @@ -117,6 +117,12 @@ http-monitor: http-monitor.o dbindex.o notify.o dbmarshal.o blobs.o ../libeze/li dbindex.o: dbmarshal.h +proto: proto.o dbmarshal.o blobs.o +proto.o: proto.c dbindex.c + +test-index: test-index.o dbmarshal.o blobs.o +test-index.o: test-index.c dbindex.c + %.d: %.c @rm -rf $@ @cc -MM -MT "$*.o" $< -o $@~ $(CFLAGS) 2>/dev/null diff --git a/README b/README index b7572c6..f5de658 100644 --- a/README +++ b/README @@ -90,3 +90,59 @@ Operations on Coming Up * Remove (user playlist, queue) Remove from playlist + + +internals +--------- + +Database implemented in lmdb using ez-blob for serialisation. + +pseudo-SQL equivalent. + +All primary keys are 'auto increment start=1'. + +create table disk ( + id int rimary key, + uuid text unique, + label text, + type text + mount text +) + +create table file ( + id int primary key, + diskid int not null references disk(id), + size bigint, + mtime bigint, + duration bigint, + + path text unique, + dir text, + title text, + artist text, +) + +create index file_by_title on file(dir); +create index file_by_title on file(title); +create index file_by_title on file(artist); + +create table list ( + id int primary key, + int size, + name text unique, + desc text +) + +create table file_by_list ( + listid int not null references list(id), + seq int not null, + fileid int not null, + unique (listid, seq) +) + +create table list_by_file ( + fileid int not null references file(id), + listid int not null references list(id), + seq int not null, + unique (listid, seq) +) diff --git a/TODO b/TODO index 98db9f7..a62d457 100644 --- a/TODO +++ b/TODO @@ -1,31 +1,4 @@ -o bugs - - something wrong with playlist and direct-play state/reverse lookup? - - -o Multiple playlists - - leverage the shuffle code - - add a playlist secondary index? - - create table shuffle { - seq int, - index int, - playlist int, - primary key (seq), - foreign key seq references file(id), - index on (index) - } - -table playlist { - id int, - shuffleid int, - - text name, - - foreign key id references shuffle(index), - foreign key shuffleid references shuffle(index) -} - o check end of file processing - seems to truncate the last frame? @@ -38,3 +11,106 @@ o internals - add items as scanned? - when you add an item to a playlist, randomly swap it's order with an existing item X problem is all-playlist is ordered by path so will typically require very large update anyway + +o enhancements + - some sort of play stats + - plays, seeks, skips + - date? + - by playlist? + - or just log each play? + +o playlist + - coming up + - list in order that would be played + - play now + - user list + - juke box + - default + - should coming up have history? + - how to visit 'play now' history? + +o display only a single list + - tab-like buttons to switch between which list is being shown? + - search as one option, or implied if search query not empty? + - how to switch lists? how to enqueue? + +[all] [jukebox] [...] +[search | query] + + [ entries ] + [ entries ] + [ entries ] + [ entries ] + + - entry buttons + - add to playlist (what context?) + - goto this list next? + + * naah this is a bit pants + +[coming up] [search | query | clear] + + - how to switch to a playlist? + + +o list views + @ coming up list + - includes the playing now, jukebox, default logic + - only allows 'jump to' for entries + - search? + @ search + - add to list (which?) + - play now + @ playlist + - playlist editing + - delete, ordering? + - pagination? + - search? + - search + add? + +maybe + + [coming up] [all?] [list 1] [list 2] [list n] + [search: ] + + - search applies to current list + - entries displayed in play order? or? + X still no context for list add + +simplify! blah, still want playlists though + + playlist + jukebox + default + + +o should playlists remember their location? + - switch to playlist just continues + - jukebox how?? + + +ARGH - just need to fucking work it out. + + +!BASICS! +-------- + +Core function, just have a default playlist and a jukebox. + + jukebox + default + +Search + - all songs + - only option is to queue + +The List + - is either coming up or search results + - coming up shows jukebox or default list + + Coming up + - play button will jump to that position in list + + Search Results + - play button adds to jukebox playlist + diff --git a/dbindex.h b/dbindex.h index 4e2b4db..6b53ef5 100644 --- a/dbindex.h +++ b/dbindex.h @@ -123,6 +123,9 @@ void dbindex_abort(dbtxn *tx); int dbstate_get(dbtxn *tx, dbindex *db, dbstate *s); int dbstate_put(dbtxn *tx, dbindex *db, dbstate *s); +//int dbstate_get_list(dbtxn *tx, dbindex *db, dbid_t listid, dblistcursor *c); +//int dbstate_put_list(dbtxn *tx, dbindex *db, dbid_t listid, dblistcursor *c); + dbdisk *dbdisk_get(dbtxn *tx, dbindex *db, int diskid); dbdisk *dbdisk_get_uuid(dbtxn *tx, dbindex *db, const char *uuid); void dbdisk_free(dbdisk *d); diff --git a/http-monitor.c b/http-monitor.c index 2a1fdbb..e8460ec 100644 --- a/http-monitor.c +++ b/http-monitor.c @@ -87,6 +87,12 @@ static int blobio_load(struct ez_blobio *io, const char *path, struct obstack *o 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) @@ -132,13 +138,50 @@ static void duration_value(struct obstack *os, uint64_t ms) { 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); @@ -162,7 +205,8 @@ static void write_state_json(dbindex *db, struct obstack *os) { 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); @@ -197,7 +241,12 @@ static int write_lists_json(dbindex *db, struct obstack *io) { 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, '}'); } @@ -217,31 +266,41 @@ static void write_list_json(dbindex *db, struct obstack *io, int listid, int seq 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); } @@ -421,24 +480,23 @@ static int handle_list(struct ez_httprequest *req, struct ez_httpresponse *rep) 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; @@ -475,22 +533,27 @@ static int handle_search(struct ez_httprequest *req, struct ez_httpresponse *rep 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; } } diff --git a/music-player.c b/music-player.c index 299c1ec..ce09607 100644 --- a/music-player.c +++ b/music-player.c @@ -17,6 +17,7 @@ . */ +#include #include #include #include @@ -414,11 +415,13 @@ static int audio_init_filter(struct audio_player *ap) { 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) @@ -546,8 +549,9 @@ int audio_close_media(struct audio_player *ap) { 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); @@ -580,9 +584,11 @@ static int audio_init_media(struct audio_player *ap, const char *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); @@ -658,6 +664,13 @@ static void dump_cursor(const char *what, dblistcursor *info) { - 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; @@ -666,7 +679,6 @@ static int audio_advance_file(struct audio_player *ap, dbfile *(*advance)(dbscan dbfile *file; dbtxn *tx = dbindex_begin(ap->index, NULL, 1); int retry = 0; - int trynext = 1; dbfile_free(ap->playing); ap->playing = NULL; @@ -675,6 +687,9 @@ static int audio_advance_file(struct audio_player *ap, dbfile *(*advance)(dbscan 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; @@ -699,12 +714,7 @@ static int audio_advance_file(struct audio_player *ap, dbfile *(*advance)(dbscan 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 : ""); + 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)); @@ -721,7 +731,19 @@ static int audio_advance_file(struct audio_player *ap, dbfile *(*advance)(dbscan 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); @@ -735,17 +757,14 @@ static int audio_advance_file(struct audio_player *ap, dbfile *(*advance)(dbscan 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 { @@ -884,7 +903,7 @@ static int audio_play_enqueue(struct audio_player *ap, int fileid) { // 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 @@ -920,7 +939,11 @@ static int audio_restore_state(struct audio_player *ap) { 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; @@ -930,7 +953,7 @@ static int audio_restore_state(struct audio_player *ap) { } else { dbfile_free(file); } - } + } } else { // FIXME: check playing_state.state == 1 separate to error test above res = -1; diff --git a/player.html b/player.html index d84788a..9bb7f4e 100644 --- a/player.html +++ b/player.html @@ -41,6 +41,28 @@ .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; @@ -105,302 +127,288 @@ */ - 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= 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 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 0) { - // table.caption.textContent = "Matches"; - //} else { - // table.caption.textContent = "No matches"; - // } - - playerAppendTracks(items, list.items, 1); - /* - for (var i=0; i 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") { @@ -412,55 +420,65 @@ 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(); + }); @@ -506,7 +524,7 @@ - + @@ -531,7 +549,9 @@
-
+
+
+
@@ -569,9 +589,12 @@ --> + +
+
- + Search 
@@ -583,17 +606,14 @@
+ + + Search - + -->