Completely re-did player state to use per-playlist state.
authorNot Zed <notzed@gmail.com>
Tue, 9 Jan 2024 10:37:09 +0000 (21:07 +1030)
committerNot Zed <notzed@gmail.com>
Tue, 9 Jan 2024 10:37:09 +0000 (21:07 +1030)
README
dbindex.c
dbindex.h
http-monitor.c
index-util.c
music-player.c
player.html

diff --git a/README b/README
index f5de658..7f7ad0a 100644 (file)
--- a/README
+++ b/README
@@ -72,6 +72,14 @@ System playlists
 * user list
   A user playlist
 
+Playing State
+
+The play state is stored for each list.  There should be only one (if
+any) with the 'state' value non-zero.
+
+ISSUE: How to resolve a user-selected playlist if that is active since
+       everything else is pre-known.
+
 Operations on Search
 
 * Play Now
index c282f6d..a390127 100644 (file)
--- a/dbindex.c
+++ b/dbindex.c
@@ -77,6 +77,8 @@ struct dblistfile {
 struct dbindex {
        int res; // last result
 
+       int debug;
+
        MDB_env *env;
 
        MDB_dbi meta;
@@ -211,8 +213,8 @@ dbindex *dbindex_open(const char *ipath) {
 
        // setup system playlists
        {
-               static const char *names[] = { "all", "all#shuffle", "jukebox", "jukebox#shuffle", "playnow", "playnow#shuffle", "shit" };
-               static const char *descs[] = { "All files ordered by path", "All files shuffled", "All enqueued songs in order", "All endqueued songs shuffled", "All play-now files in order", "All play-now files shuffled", "The shitlist" };
+               static const char *names[] = { "all", "all#shuffle", "jukebox", "jukebox#shuffle", "playnow", "playnow#shuffle", "history", "shit" };
+               static const char *descs[] = { "All files ordered by path", "All files shuffled", "All enqueued songs in order", "All endqueued songs shuffled", "All play-now files in order", "All play-now files shuffled", "History", "The shitlist" };
                dblist list = { 0 };
 
                for (int i=0;i<sizeof(names)/sizeof(names[0]);i++) {
@@ -289,16 +291,30 @@ void dbindex_abort(MDB_txn *tx) {
        mdb_txn_abort(tx);
 }
 
-int dbstate_get(MDB_txn *tx, dbindex *db, dbstate *s) {
-       MDB_val key = { .mv_data = "state", .mv_size = strlen("state") };
+int dbstate_get(MDB_txn *tx, dbindex *db, dbid_t listid, dbstate *s) {
+       MDB_val key;
        MDB_val data;
+       char id[32];
+
+       sprintf(id, "state#%016x", listid);
+       key.mv_data = id;
+       key.mv_size = strlen(id);
 
        db->res = mdb_get(tx, db->meta, &key, &data);
 
        if (db->res == 0) {
                dbstate *p = (dbstate *)data.mv_data;
-               if (p->size != sizeof(dbstate))
+               if (p->version != ZPL_STATE_VERSION)
                        return MDB_NOTFOUND;
+
+               if (db->debug > 1) {
+                       printf("dbstate get\n");
+                       printf(" state: %08x\n", p->state);
+                       printf(" listd: %d\n", p->curr.listid);
+                       printf(" seq: %d\n", p->curr.seq);
+                       printf(" filed: %d\n", p->curr.fileid);
+               }
+
                *s = *p;
                return 0;
        } else {
@@ -308,11 +324,27 @@ int dbstate_get(MDB_txn *tx, dbindex *db, dbstate *s) {
        return db->res;
 }
 
-int dbstate_put(MDB_txn *tx, dbindex *db, dbstate *s) {
-       MDB_val key = { .mv_data = "state", .mv_size = strlen("state") };
+int dbstate_del_id(MDB_txn *tx, dbindex *db, dbid_t listid) {
+       MDB_val key;
+       char id[32];
+
+       sprintf(id, "state#%016x", listid);
+       key.mv_data = id;
+       key.mv_size = strlen(id);
+
+       return mdb_del(tx, db->meta, &key, NULL);
+}
+
+int dbstate_put(MDB_txn *tx, dbindex *db, dbid_t listid, dbstate *s) {
+       MDB_val key;
        MDB_val data = { .mv_data = s, .mv_size = sizeof(*s) };
+       char id[32];
+
+       sprintf(id, "state#%016x", listid);
+       key.mv_data = id;
+       key.mv_size = strlen(id);
 
-       s->size = sizeof(dbstate);
+       s->version = ZPL_STATE_VERSION;
 
        return mdb_put(tx, db->meta, &key, &data, 0);
 }
@@ -1335,6 +1367,9 @@ int dblist_del(dbtxn *txn, dbindex *db, int listid) {
        if ((res = mdb_del(tx, db->file_by_list, &key, NULL)))
                goto fail;
 
+       if ((res = dbstate_del_id(tx, db, listid)))
+               goto fail;
+
        res = mdb_cursor_open(tx, db->list_by_file, &cursor);
 
        printf("delete reverse list\n");
@@ -1749,6 +1784,7 @@ int dbfile_searchx(dbindex *db, const char *pattern, dbfile **results, int maxle
                } else
                        break;
        }
+       mdb_cursor_close(cursor);
        regfree(&reg);
        mdb_txn_abort(tx);
 
@@ -1808,6 +1844,7 @@ int dbfile_search(dbindex *db, const char *pattern, dbfile **results, int maxlen
                }
 
        }
+       mdb_cursor_close(cursor);
        regfree(&reg);
        mdb_txn_abort(tx);
 
index fc74aba..8ea61de 100644 (file)
--- a/dbindex.h
+++ b/dbindex.h
@@ -72,36 +72,32 @@ struct dblistcursor {
        dbid_t fileid;
 };
 
-/* player state, this is passed around as a struct */
+/* player state, this isn't blobbed */
 typedef struct dbstate dbstate;
 
 // current list mode
-// or just list mode to try on mode finish (doesn't need to be stored?)
 enum list_mode_t {
-       LIST_DEFAULT,
-       LIST_JUKEBOX,
-       LIST_USER,
-       LIST_PLAYNOW
+       ZPL_DEFAULT,
+       ZPL_USER,
+       ZPL_JUKEBOX,
+       ZPL_PLAYNOW,
+       ZPL_SIZEOF
 };
 
 struct dbstate {
-       uint32_t size;          // don't need to initialise
-       uint32_t state;         // some info on playing
-
-       uint32_t list_mode;     // which list are we playing
-
-       dblistcursor current;   // current file for restore
-
-       dblistcursor list_playnow;// the playnow list (records history)
-       dblistcursor list_user; // user requested list/position
-       dblistcursor list_jukebox; // jukebox list position
-       dblistcursor list_default; // default list position
+       uint32_t version;       // version written, matches source
 
+       uint32_t state;         // some info on playing
+       time_t stamp;           // last update time
        uint64_t pos;           // last approximate position pts
        double poss;            // last approximate position in seconds
-       time_t stamp;           // last update time
+
+       dblistcursor curr;      // current track/list info
 };
 
+// current version/magic
+#define ZPL_STATE_VERSION 0x5a5e0001
+
 typedef struct dbindex dbindex;
 typedef struct MDB_txn dbtxn;
 typedef struct dbscan dbscan;
@@ -124,8 +120,9 @@ dbtxn *dbindex_begin(dbindex *db, dbtxn *txn, int readonly);
 int dbindex_commit(dbtxn *tx);
 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(dbtxn *tx, dbindex *db, dbid_t listid, dbstate *s);
+int dbstate_put(dbtxn *tx, dbindex *db, dbid_t listid, dbstate *s);
+int dbstate_del_id(MDB_txn *tx, dbindex *db, dbid_t listid);
 
 //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);
index e8460ec..6fc480c 100644 (file)
@@ -87,11 +87,13 @@ static int blobio_load(struct ez_blobio *io, const char *path, struct obstack *o
        return res;
 }
 
+#if 0
 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);
 }
+#endif
 
 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)) {
@@ -188,42 +190,152 @@ static void write_file_json(struct obstack *os, dbfile *file) {
        obstack_printf(os, ",\"lengthms\":\"%zd\"", file->duration / 1000);
 }
 
+// TODO: This needs to be common with the player
+const char * const list_names[4] = {
+       "all#shuffle",
+       NULL,
+       "jukebox",
+       "playnow"
+};
+
 static void write_state_json(dbindex *db, struct obstack *os) {
        dbtxn *tx = dbindex_begin(db, NULL, 1);
-       dbstate state;
-       int res = dbstate_get(tx, db, &state);
+       int res;
        dbfile *file = NULL;
 
-       printf("state.fileid = %d\n", state.current.fileid);
-       printf("state.pos = %zd\n", state.pos);
-       printf("state.listid = %d\n", state.current.listid);
-       printf("state.seq = %d\n", state.current.seq);
-
-       if (res == 0
-           && (state.state & 1)
-           && (file = dbfile_get(tx, db, state.current.fileid))) {
-               obstack_1grow(os, '{');
-               write_file_json(os, file);
-
-               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_printf(os, ",\"seq\":\"%u\"", state.current.seq);
-               obstack_printf(os, ",\"fileid\":\"%u\"", state.current.fileid);
-
-               obstack_sgrow(os, ",\"position\":");
-               duration_value(os, (uint64_t)(state.poss * 1000));
-               obstack_printf(os, ",\"positionms\":\"%.0f\"", (state.poss * 1000));
-               obstack_1grow(os, '}');
-       } else {
-               obstack_sgrow(os, "{ 'status': 'idle' }");
+       for (int i=ZPL_SIZEOF-1;i>=0;i--) {
+               dbstate state;
+               dbid_t listid;
+
+               if (!list_names[i])
+                       continue;
+
+               listid = dblistid_get_name(tx, db, list_names[i]);
+               res = dbstate_get(tx, db, listid, &state);
+
+               if (res == 0
+                       && (state.state & 1)
+                       && (file = dbfile_get(tx, db, state.curr.fileid))) {
+
+                       printf("state.fileid = %d\n", state.curr.fileid);
+                       printf("state.pos = %zd\n", state.pos);
+                       printf("state.listid = %d\n", state.curr.listid);
+                       printf("state.seq = %d\n", state.curr.seq);
+
+                       obstack_1grow(os, '{');
+                       write_file_json(os, file);
+
+                       obstack_sgrow(os, ",\"path\":");
+                       //write_value_json(os, file->path);
+                       write_value_json(os, dbfile_full_path(tx, db, file));
+
+                       obstack_printf(os, ",\"status\":\"%s\"", (state.state & 2) ? "paused" : "playing");
+
+                       obstack_printf(os, ",\"listid\":\"%u\"", state.curr.listid);
+                       obstack_printf(os, ",\"seq\":\"%u\"", state.curr.seq);
+                       obstack_printf(os, ",\"fileid\":\"%u\"", state.curr.fileid);
+
+                       obstack_sgrow(os, ",\"position\":");
+                       duration_value(os, (uint64_t)(state.poss * 1000));
+                       obstack_printf(os, ",\"positionms\":\"%.0f\"", (state.poss * 1000));
+                       obstack_1grow(os, '}');
+
+                       dbfile_free(file);
+                       goto done;
+               }
        }
 
+       obstack_sgrow(os, "{ \"status\": \"idle\" }");
+done:
        dbindex_abort(tx);
+}
 
-       dbfile_free(file);
+/* upcoming queue, maybe also go back a couple for history? */
+/* TODO: make this reusable for this, player, and index-util */
+static void write_queue_json(dbindex *db, struct obstack *io) {
+       dbtxn *tx = dbindex_begin(db, NULL, 1);
+       int limit = 10;
+       int count = 0;
+       int res;
+       dbstate states[ZPL_SIZEOF] = { 0 };
+       int playing = -1;
+
+       obstack_1grow(io, '{');
+
+       obstack_sgrow(io, "\"name\":");
+       write_value_json(io, "queue");
+       obstack_sgrow(io, ",\"items\": [");
+
+       for (int i=ZPL_SIZEOF-1;i>=0 && count < limit;i--) {
+               if (!list_names[i])
+                       continue;
+
+               states[i].curr.listid = dblistid_get_name(tx, db, list_names[i]);
+               res = dbstate_get(tx, db, states[i].curr.listid, &states[i]);
+
+               // Grab the playing item as the first entry
+               if (res == 0 && (states[i].state & 1) == 1 && count == 0) {
+                       dbscan scan;
+                       dblistcursor cursor = states[i].curr;
+                       dbfile *file = dbscan_list_entry(tx, &scan, db, &cursor);
+
+                       if (file && dbfile_full_path(tx, db, file)) {
+                               obstack_sgrow(io, count > 0 ? ",{" : "{");
+                               write_file_json(io, file);
+
+                               obstack_printf(io, ",\"listid\":\"%u\"", cursor.listid);
+                               obstack_printf(io, ",\"seq\":\"%u\"", cursor.seq);
+
+                               obstack_1grow(io, '}');
+
+                               playing = i;
+                               count += 1;
+                       }
+                       dbfile_free(file);
+                       dbscan_free(&scan);
+               }
+       }
+
+       for (int i=ZPL_SIZEOF-1;i>=0 && count < limit;i--) {
+               dbfile *file;
+               dbscan scan;
+               dblistcursor cursor = states[i].curr;
+               int scancount = 0;
+
+       loop:
+               file = dbscan_list_entry(tx, &scan, db, &cursor);
+               while (count < limit && file) {
+                       if (dbfile_full_path(tx, db, file)
+                               && !(count == 1 && playing == i && scancount == 0)) {
+                               obstack_sgrow(io, count > 0 ? ",{" : "{");
+                               write_file_json(io, file);
+
+                               obstack_printf(io, ",\"listid\":\"%u\"", cursor.listid);
+                               obstack_printf(io, ",\"seq\":\"%u\"", cursor.seq);
+
+                               obstack_1grow(io, '}');
+                               count++;
+                       }
+
+                       dbfile_free(file);
+                       file = dbscan_list_entry_next(&scan, &cursor);
+                       scancount++;
+               }
+               dbfile_free(file);
+               res = dbscan_result(&scan);
+               dbscan_free(&scan);
+
+               // loop on default playlist always
+               if (res == MDB_NOTFOUND) {
+                       if (i == 0 && cursor.seq > 0) {
+                               cursor.seq = 0;
+                               cursor.fileid = 0;
+                               goto loop;
+                       }
+               }
+       }
+       obstack_sgrow(io, "]}");
+       dbindex_abort(tx);
 }
 
 static int write_lists_json(dbindex *db, struct obstack *io) {
@@ -249,6 +361,7 @@ static int write_lists_json(dbindex *db, struct obstack *io) {
                }
                obstack_printf(io, ",\"size\":\"%d\"", list->size);
                obstack_1grow(io, '}');
+               dblist_free(list);
        }
        obstack_1grow(io, ']');
        obstack_1grow(io, '}');
@@ -463,6 +576,28 @@ static int handle_lists(struct ez_httprequest *req, struct ez_httpresponse *rep)
        return 0;
 }
 
+static int handle_queue(struct ez_httprequest *req, struct ez_httpresponse *rep) {
+       struct obstack *os = &rep->http.conn->os;
+
+       if (strcmp(req->method, "GET") == 0) {
+               write_queue_json(db, os);
+               set_data_json(rep);
+       } else if (strcmp(req->method, "POST") == 0) {
+               // TODO: enqueue
+               int fid = param_int(req, "f", 0);
+
+               if (fid) {
+                       httpresponse_set_response(rep, 501, "Not implemented");
+               } else {
+                       httpresponse_set_response(rep, 400, "Missing name");
+               }
+       } else {
+               httpresponse_set_response(rep, 400, "Invalid method");
+       }
+
+       return 0;
+}
+
 static void http_addheader(struct ez_http *http, const char *name, const char *value) {
        struct obstack *os = &http->conn->os;
        ez_pair *location = obstack_alloc(os, sizeof(*location));
@@ -591,6 +726,7 @@ static struct ez_httphandler handler_list[] = {
        { .path = "/x/goto", .fn = handle_goto },
 
        { .path = "/x/status", .fn = handle_status },
+       { .path = "/x/queue", .fn = handle_queue },
        { .path = "/x/list", .fn = handle_lists },
        { .path = "/x/list/", .fn = handle_list, .mode=EZ_PATH_PREFIX },
 
@@ -607,6 +743,8 @@ int main(int argc, char **argv) {
        player = notify_writer_new(NOTIFY_PLAYER);
 
        httpserver_init(&serv, 8000);
+       // TODO: param
+       serv.debug = 1;
        httpserver_addhandlers(&serv, handler_list, sizeof(handler_list)/sizeof(handler_list[0]));
        httpserver_run(&serv);
        httpserver_free(&serv);
index 778795c..bb815bb 100644 (file)
@@ -168,6 +168,14 @@ void search_suffix(const char *path) {
 
 /* ********************************************************************** */
 
+// TODO: This needs to be common with the player
+const char * const list_names[4] = {
+       "all#shuffle",
+       NULL,
+       "jukebox",
+       "playnow"
+};
+
 int main(int argc, char **argv) {
        const char *dbdir = MAIN_INDEX;
        dbid_t diskid = 0;
@@ -327,6 +335,119 @@ int main(int argc, char **argv) {
                        } else {
                                printf("search failed\n");
                        }
+               } else if (strcmp(cmd, "play-state") == 0) {
+                       dbtxn *tx = dbindex_begin(db, NULL, 1);
+
+                       for (int i=ZPL_SIZEOF-1;i>=0;i--) {
+                               dbstate state;
+
+                               if (!list_names[i])
+                                       continue;
+
+                               state.curr.listid = dblistid_get_name(tx, db, list_names[i]);
+                               res = dbstate_get(tx, db, state.curr.listid, &state);
+                               if (res != 0)
+                                       continue;
+                               printf("%4d: ver=%08x state=%08x stamp=%016lx pos=%9.3fs lid=%4d seq=%4d fid=%5d\n",
+                                       i, state.version, state.state, state.stamp, state.poss,
+                                       state.curr.listid, state.curr.seq, state.curr.fileid);
+                       }
+                       dbindex_commit(tx);
+               } else if (strcmp(cmd, "play-queue") == 0) {
+                       dbtxn *tx = dbindex_begin(db, NULL, 1);
+                       int res;
+                       int count = 0;
+                       int limit = 100;
+
+                       for (int i=ZPL_SIZEOF-1;i>=0 && count < limit;i--) {
+                               dbstate state;
+                               dbscan scan;
+                               dbfile *file;
+
+                               if (!list_names[i])
+                                       continue;
+
+                               state.curr.listid = dblistid_get_name(tx, db, list_names[i]);
+                               res = dbstate_get(tx, db, state.curr.listid, &state);
+                               if (res != 0)
+                                       continue;
+                       loop:
+                               file = dbscan_list_entry(tx, &scan, db, &state.curr);
+                               while (count < limit && file) {
+                                       if (dbfile_full_path(tx, db, file)) {
+                                               printf("%4d: fid=%4d lid=%4d seq=%4d title: %-60s %s\n", count++, file->id, state.curr.listid, state.curr.seq, file->title, file->path);
+                                       }
+                                       dbfile_free(file);
+                                       file = dbscan_list_entry_next(&scan, &state.curr);
+                               }
+                               dbfile_free(file);
+                               res = dbscan_result(&scan);
+                               dbscan_free(&scan);
+
+                               // loop on default playlist always
+                               if (res == MDB_NOTFOUND) {
+                                       if (i == 0 && state.curr.seq > 0) {
+                                               state.curr.seq = 0;
+                                               state.curr.fileid = 0;
+                                               goto loop;
+                                       }
+                               }
+
+                       }
+
+#if 0
+                       res = dbstate_get(tx, db, &state);
+                       if (res >= 0) {
+                               int next = 1;
+
+                               info = state.curr;
+                               file = dbfile_get(tx, db, info.fileid);
+                               printf("%4d: fid=%4d lid=%4d seq=%4d title: %-60s %s\n", count++, file->id, info.listid, info.seq, file->title, file->path);
+                               if (state.list_curr != ZPL_USER
+                                       && state.list[state.list_curr].listid != info.listid) {
+                                       info = state.list[state.list_curr];
+                                       next = 0;
+                               }
+
+                               while (count < limit) {
+                                       file = dbscan_list_entry(tx, &scan, db, &info);
+                                       if (file) {
+                                               if (next) {
+                                                       dbfile_free(file);
+                                                       file = dbscan_list_entry_next(&scan, &info);
+                                               }
+                                               while (file && count < limit) {
+                                                       printf("%4d: fid=%4d lid=%4d seq=%4d title: %-60s %s\n", count++, file->id, info.listid, info.seq, file->title, file->path);
+                                                       dbfile_free(file);
+                                                       if (count < limit) {
+                                                               file = dbscan_list_entry_next(&scan, &info);
+                                                       } else {
+                                                               file = NULL;
+                                                       }
+                                               }
+                                       }
+                                       res = dbscan_result(&scan);
+                                       dbscan_free(&scan);
+                                       if (res == MDB_NOTFOUND) {
+                                               if (state.list_curr > 0) {
+                                                       state.list_curr -= 1;
+                                                       info = state.list[state.list_curr];
+                                                       next = 0;
+                                               } else if (info.listid == state.list[ZPL_DEFAULT].listid && info.seq == 0 && info.fileid == 0) {
+                                                       break;
+                                               } else {
+                                                       info.listid = state.list[ZPL_DEFAULT].listid;
+                                                       info.seq = 0;
+                                                       info.fileid = 0;
+                                                       next = 0;
+                                               }
+                                       }
+                               }
+                       } else {
+                               printf("state query failed\n");
+                       }
+#endif
+                       dbindex_commit(tx);
                }
                if (res)
                        printf("error (%d): %s\n", res, mdb_strerror(res));
index e1e678b..40bc775 100644 (file)
@@ -113,10 +113,9 @@ struct audio_player {
        // playlist management
        dbindex *index;
        dbfile *playing;
-       dbstate playing_state;
-
-       // current track/playlist management
-       dblistcursor *list_active; // update in play now?
+       dbstate state[ZPL_SIZEOF];
+       int state_curr;
+       dbid_t list_id[ZPL_SIZEOF];
 
        int quit;
        int paused;
@@ -130,12 +129,6 @@ struct audio_player {
        int file_seq;           // currently active file, hmm, do i care?
        //  delete >>
 
-       // per-file state
-       // << delete
-       pthread_t reader_id;
-       volatile int state;     // read by thread to communicate shutdown
-       //  delete >>
-
        AVFormatContext *fc;
        AVCodecContext *cc;
        AVStream *audio;
@@ -149,7 +142,7 @@ struct audio_player {
 
        // pre-frame state
        //AVFrame *frame;// unused?
-       int64_t pos;            // current pos in audio stream units
+       int64_t pos;            // current pos in audio stream units, copied over to playing_state.pos
 
        char *data[1];          // data pointers for current output, planar or not, may point to 'buffer'
        int nsamples;           // nsamples remaining for output
@@ -171,6 +164,14 @@ static struct audio_player *audio_player_new(const char *device) {
 #endif
        audio_init_mixer(ap);
 
+       // Load list constants
+       dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
+       ap->list_id[ZPL_DEFAULT] = dblistid_get_name(tx, ap->index, "all#shuffle");
+       ap->list_id[ZPL_USER] = 0; // not implemented yet
+       ap->list_id[ZPL_JUKEBOX] = dblistid_get_name(tx, ap->index, "jukebox");
+       ap->list_id[ZPL_PLAYNOW] = dblistid_get_name(tx, ap->index, "playnow");
+       dbindex_abort(tx);
+
        return ap;
 }
 
@@ -555,6 +556,7 @@ static int audio_init_media(struct audio_player *ap, const char *path) {
        printf("audio_init_media '%s'\n", path);
 
        audio_close_media(ap);
+       ap->pos = 0;
 
        ap->fc = avformat_alloc_context();
        res = avformat_open_input(&ap->fc, path, NULL, NULL);
@@ -606,45 +608,28 @@ static int audio_init_media(struct audio_player *ap, const char *path) {
 static int audio_checkpoint_state(struct audio_player *ap) {
        dbtxn *tx = dbindex_begin(ap->index, NULL, 0);
        int res;
+       dbstate *state = &ap->state[ap->state_curr];
 
-       if (tx) {
-               // TODO: meaningful state value
-               /*
-               if (ap->playing) {
-                       ap->playing_state.state = 1;
-                       ap->playing_state.fileid = ap->playing->id;
-               } else {
-                       // ?
-                       ap->playing_state.state = 0;
-                       ap->playing_state.fileid = 0;
-                       }*/
-               if (ap->playing) {
-                       ap->playing_state.state = 1 | (ap->paused ? 2 : 0);
-                       // TODO: just update pos directly?
-                       ap->playing_state.pos = ap->pos;
-                       AVRational sb = ap->audio->time_base;
-                       ap->playing_state.poss = av_q2d(sb) * ap->pos;
-               } else {
-                       ap->playing_state.state = 0;
-                       ap->playing_state.pos = 0;
-                       ap->playing_state.poss = 0;
-               }
-               ap->playing_state.stamp = time(NULL);
-
-               //printf("checkpoint state=%d file=%d pos=%zd poss=%f '%s'\n", ap->playing_state.state, ap->playing_state.current.fileid, ap->playing_state.pos, ap->playing_state.poss, ap->playing ? ap->playing->full_path : "");
-               //printf("Checkpoint state, = %d\n", ap->playing_state.state);
+       if (ap->audio) {
+               state->state = (ap->playing ? 1 : 0) | (ap->paused ? 2 : 0);
+               state->pos = ap->pos;
+               state->poss = av_q2d(ap->audio->time_base) * ap->pos;
+       } else {
+               state->state = 0;
+               state->pos = 0;
+               state->poss = 0;
+       }
+       state->stamp = time(NULL);
 
-               if ((res = dbstate_put(tx, ap->index, &ap->playing_state)) == 0) {
-                       dbindex_commit(tx);
-               } else {
-                       printf("checkpoint failed: %s\n", mdb_strerror(res));
-                       dbindex_abort(tx);
-               }
+       res = dbstate_put(tx, ap->index, state->curr.listid, state);
+       if (res == 0){
+               dbindex_commit(tx);
        } else {
-               printf("checkpoint failed to get tx\n");
+               printf("checkpoint failed: %s\n", mdb_strerror(res));
+               dbindex_abort(tx);
        }
 
-       return 0;
+       return res;
 }
 
 static void dump_cursor(const char *what, dblistcursor *info) {
@@ -671,250 +656,175 @@ static void dump_cursor(const char *what, dblistcursor *info) {
    - 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;
-       int empty = ap->playing == NULL;
-       dbscan scan;
-       dbfile *file;
-       dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
-       int retry = 0;
+
+// Find the current file and start it playing
+static int audio_play_curr(struct audio_player *ap) {
+       dbtxn *tx = dbindex_begin(ap->index, NULL, 0);
+       int res = -1;
 
        dbfile_free(ap->playing);
        ap->playing = NULL;
 
-       // Playnow only runs once
-       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;
-
-               switch (ap->playing_state.list_mode) {
-               case LIST_DEFAULT:
-               default:
-                       printf("trying default playlist\n");
-                       active = &ap->playing_state.list_default;
-                       break;
-               case LIST_JUKEBOX:
-                       printf("trying jukebox playlist\n");
-                       active = &ap->playing_state.list_jukebox;
-                       break;
-               case LIST_USER:
-                       printf("trying user playlist\n");
-                       active = &ap->playing_state.list_user;
-                       break;
-               }
-
-               dump_cursor("active?", active);
+       for (int i=ZPL_SIZEOF-1;i>=0;i--) {
+               dbstate *state =  &ap->state[i];
+               dbscan scan;
+               dbfile *file;
 
-               printf("loop: %d\n", retry);
-               file = dbscan_list_entry(tx, &scan, ap->index, active);
-               if (file) {
-                       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));
-               }
+               printf(" ? lid=%4d seq=%4d fid=%4d\n", state->curr.listid, state->curr.seq, state->curr.fileid);
+       retry:
+               file = dbscan_list_entry(tx, &scan, ap->index, &state->curr);
                while (file) {
-                       dump_cursor("playing?", active);
-
-                       res = audio_init_media(ap, dbfile_full_path(tx, ap->index, file));
-
-                       if (res == (-30798) && !empty) // && >loop?
-                               res = 1;
-
-                               if (res == 0) {
-                               ap->playing_state.current = *active;
-                               ap->playing = file;
-                               audio_checkpoint_state(ap);
+                       if (dbfile_full_path(tx, ap->index, file)) {
+                               res = audio_init_media(ap, file->full_path);
+                               if (res == 0) {
+                                       // seek ?
+                                       ap->playing = file;
+                                       ap->state_curr = i;
+
+                                       state->state = 1;
+                                       state->pos = 0;
+                                       state->poss = 0;
+                                       dbstate_put(tx, ap->index, state->curr.listid, state);
+
+                                       printf("ok lid=%4d seq=%4d fid=%4d\n", state->curr.listid, state->curr.seq, state->curr.fileid);
+
+                                       dbscan_free(&scan);
+
+                                       // Ensure other states are not playing
+                                       for (int j=i-1;j>=0;j--) {
+                                               dbstate *s = &ap->state[j];
+                                               if (s->curr.listid) {
+                                                       s->state = 0;
+                                                       dbstate_put(tx, ap->index, s->curr.listid, s);
+                                               }
+                                       }
 
-                               // store advanced state as well
-                               file = dbscan_list_entry_next(&scan, active);
-                               if (file) {
-                                       dbfile_free(file);
-                               } else {
-                                       active->seq += 1;
-                                       active->fileid = 0;
+                                       dbindex_commit(tx);
+                                       return res;
                                }
-                               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);
-                       file = advance(&scan, active);
+                       file = dbscan_list_entry_next(&scan, &state->curr);
                }
+               state->state = 0;
+               dbstate_put(tx, ap->index, state->curr.listid, state);
+               res = dbscan_result(&scan);
+               dbscan_free(&scan);
 
-               // repeat at end of list?
-               // not sure on the trynext logic here
-               if (!file && dbscan_result(&scan) == -30798) {
-                       switch (ap->playing_state.list_mode) {
-                       case LIST_USER:
-                               // if loop ... just loop
-                               ap->playing_state.list_mode = LIST_JUKEBOX;
-                               break;
-                       case LIST_JUKEBOX:
-                               ap->playing_state.list_mode = LIST_DEFAULT;
-                               break;
-                       case LIST_DEFAULT:
-                               // loop the default list always
-                               ap->playing_state.list_default.seq = 0;
-                               ap->playing_state.list_default.fileid = 0;
-                               break;
+               if (res == MDB_NOTFOUND) {
+                       // loop on default playlist always
+                       if (i == 0) {
+                               if (state->curr.seq > 0) {
+                                       state->curr.seq = 0;
+                                       state->curr.fileid = 0;
+                                       goto retry;
+                               }
                        }
-               } else {
-                       printf("worked?\n");
                }
-               dbscan_free(&scan);
-       } while (!file && retry++ < 5);
-
-       dbindex_abort(tx);
-       return res;
-}
+       }
 
-static int audio_next_file(struct audio_player *ap) {
-       return audio_advance_file(ap, dbscan_list_entry_next);
+       dbindex_commit(tx);
+       return -1;
 }
 
-static int audio_prev_file(struct audio_player *ap) {
-       return audio_advance_file(ap, dbscan_list_entry_prev);
-}
 
 // jump to a file (in a playlist)
 // TODO: if the playlist is the same should it jump to the next occurance of the file in the list?
 static int audio_goto_file(struct audio_player *ap, dblistcursor *info) {
-       int res = -1; // MDB_NOTFOUND
        dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
-       dbfile *file = NULL;
        dbscan scan;
+       dbfile *file;
 
-       //if (info->listid != ap->current->listid) {
-               // now what?  or do we just jump to that (user?) list instead?
-               // yes I think so!
-       //}
-
-       printf("curret listid=%4d seq=%4d fileid=%4d\n", ap->playing_state.current.listid, ap->playing_state.current.seq, ap->playing_state.current.fileid);
-       printf("goto   listid=%4d seq=%4d fileid=%4d\n", info->listid, info->seq, info->fileid);
+       if (ap->list_id[ap->state_curr] == info->listid) {
+               file = dbscan_list_entry(tx, &scan, ap->index, info);
+               if (file && file->id == info->fileid) {
+                       ap->state[ap->state_curr].curr = *info;
+                       audio_play_curr(ap);
+               }
+       } else {
+               // TODO: this should be ok if it's within the upcoming queue?
+               //       but would have to advance all the other lists?
+               printf("Trying to jump out of the current playlist");
+       }
 
-       // FIXME: file leaks if playing not set.
-       file = dbscan_list_entry(tx, &scan, ap->index, info);
+       dbindex_abort(tx);
 
-       if (!file || file->id != info->fileid) {
-               res = MDB_NOTFOUND;
-               goto fail;
-       }
+       return 0;
+}
 
-       if ((res = audio_init_media(ap, dbfile_full_path(tx, ap->index, file))))
-               goto fail;
+static int audio_append(struct audio_player *ap, int start, dbid_t listid, dbid_t fileid) {
+       dbtxn *tx = dbindex_begin(ap->index, NULL, 0);
+       dbfile *file = dbfile_get(tx, ap->index, fileid);
+       dblistcursor info;
+       int res;
 
-       ap->playing = file;
-       file = NULL;
-       ap->playing_state.current = *info;
-
-       // Work out the playlist mode now
-       if (info->listid == ap->playing_state.list_jukebox.listid) {
-               printf("jumping into jukebox list\n");
-               ap->playing_state.list_jukebox = *info;
-               ap->playing_state.list_mode = LIST_JUKEBOX;
-       } else if (info->listid == ap->playing_state.list_default.listid) {
-               printf("jumping into default list\n");
-               ap->playing_state.list_default = *info;
-               ap->playing_state.list_mode = LIST_DEFAULT;
+       if (file) {
+               info.listid = listid;
+               info.seq = 0;
+               info.fileid = fileid;
+               res = dblist_add_file(tx, ap->index, &info);
        } else {
-               printf("jumping to user list\n");
-               ap->playing_state.list_user = *info;
-               ap->playing_state.list_mode = LIST_USER;
+               res = dbindex_result(ap->index);
        }
 
-       audio_checkpoint_state(ap);
-       printf("-> now listid=%4d seq=%4d fileid=%4d\n", info->listid, info->seq, info->fileid);
-fail:
-       dbscan_free(&scan);
-       dbindex_abort(tx);
+       if (res == 0)
+               dbindex_commit(tx);
+       else
+               dbindex_abort(tx);
+
+       if (res == 0 && start)
+               audio_play_curr(ap);
 
        return res;
 }
 
 // play a specific file right now out of band
 static int audio_play_now(struct audio_player *ap, int fileid) {
-       int res = -1; // MDB_NOTFOUND
        dbtxn *tx = dbindex_begin(ap->index, NULL, 0);
        dbfile *file = dbfile_get(tx, ap->index, fileid);
-
-       printf("player goto: %d  diskid=%d\n", fileid, file->diskid);
+       dblistcursor info;
+       int res;
 
        if (file) {
-               // add to end of the playnow playlist
-               ap->playing_state.list_playnow.seq = 0;
-               ap->playing_state.list_playnow.fileid = fileid;
-               dblist_add_file(tx, ap->index, &ap->playing_state.list_playnow); // ignore result?
-
-               res = audio_init_media(ap, dbfile_full_path(tx, ap->index, file));
-               if (res == 0) {
-                       ap->playing = file;
+               info.listid = ap->list_id[ZPL_PLAYNOW];
+               info.seq = 0;
+               info.fileid = fileid;
+               res = dblist_add_file(tx, ap->index, &info);
 
-                       ap->playing_state.list_mode = LIST_PLAYNOW;
-                       ap->playing_state.current = ap->playing_state.list_playnow;
-                       printf("playing %s\n", file->path);
-               } else {
-                       dbfile_free(file);
-               }
+               // Force jumping to end of playnow list
+               ap->state[ZPL_PLAYNOW].curr.seq = info.seq;
+               res = dbstate_put(tx, ap->index, info.listid, &ap->state[ZPL_PLAYNOW]);
+       } else {
+               res = dbindex_result(ap->index);
        }
 
-       if (res == 0) {
+       if (res == 0)
                dbindex_commit(tx);
-               // separate write transaction
-               // FIXME: put in same transaction?
-               audio_checkpoint_state(ap);
-       } else
+       else
                dbindex_abort(tx);
 
+       if (res == 0)
+               audio_play_curr(ap);
+
        return res;
 }
 
 // jukebox
 // this adds the song to the jukebox playlist but doesn't jump immediately
-// however ... "next song" will jump over it, so ... sigh
+// TODO: this doesn't need to be in player
 static int audio_play_enqueue(struct audio_player *ap, int fileid) {
-       int res = -1; // MDB_NOTFOUND
        dbtxn *tx = dbindex_begin(ap->index, NULL, 0);
        dbfile *file = dbfile_get(tx, ap->index, fileid);
-
-       printf("player enqueue: %d  diskid=%d\n", fileid, file->diskid);
+       dblistcursor info;
+       int res;
 
        if (file) {
-               dblistcursor info;
-
-               // add to end of the jukebox playlist
-               info.listid = ap->playing_state.list_jukebox.listid;
+               info.listid = ap->list_id[ZPL_JUKEBOX];
                info.seq = 0;
                info.fileid = fileid;
-               if ((res = dblist_add_file(tx, ap->index, &info)) == 0) {
-                       printf("enqueued listid=%4d seq=%4d fileid=%4d\n",
-                               info.listid,
-                               info.seq,
-                               info.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 (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 (ap->playing_state.list_mode != LIST_JUKEBOX) {
-                               printf(" set play state to jukebox\n");
-                               ap->playing_state.list_jukebox.seq = info.seq -1;
-                               ap->playing_state.list_jukebox.fileid = 0;
-                               ap->playing_state.list_mode = LIST_JUKEBOX;
-                       } else {
-                               printf(" play state already jukebox\n");
-                       }
-               }
+               res = dblist_add_file(tx, ap->index, &info);
+       } else {
+               res = dbindex_result(ap->index);
        }
 
        if (res == 0)
@@ -925,69 +835,106 @@ static int audio_play_enqueue(struct audio_player *ap, int fileid) {
        return res;
 }
 
-// first file on restarting server, see if we were playing something last time and start back there
-// TODO: do something with the position
-static int audio_restore_state(struct audio_player *ap) {
-       dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
-       int res = -1;
+static void audio_play_done(struct audio_player *ap) {
+       dbstate *state = &ap->state[ap->state_curr];
 
-       // FIXME: playlist stuff
-       // FIXME: need to check current, etc
-       // Or just advance to next and let advance handle it?
+       dbfile_free(ap->playing);
+       ap->playing = NULL;
 
-       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);
+       state->curr.seq += 1;
+       audio_checkpoint_state(ap);
+}
 
-               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;
+static int audio_play_next(struct audio_player *ap) {
+       audio_play_done(ap);
+       return audio_play_curr(ap);
+}
+
+// FIXME: this should use history, otherwise it gets stuck on list
+static int audio_play_prev(struct audio_player *ap) {
+       dbtxn *tx = dbindex_begin(ap->index, NULL, 1);
+       dbstate *state = &ap->state[ap->state_curr];
+       dbscan scan;
+       dbfile *file;
+       int res;
 
+       dbfile_free(ap->playing);
+       ap->playing = NULL;
+
+       file = dbscan_list_entry(tx, &scan, ap->index, &state->curr);
+       if (file) {
+               dbfile_free(file);
+               file = dbscan_list_entry_prev(&scan, &state->curr);
+               while (file) {
+                       if (dbfile_full_path(tx, ap->index, file)) {
                                res = audio_init_media(ap, file->full_path);
-                               // seek ?
-                       } else {
-                               dbfile_free(file);
+                               if (res == 0) {
+                                       ap->playing = file;
+
+                                       state->state = 1;
+
+                                       goto found;
+                               }
                        }
-       }
-       } else {
-               // FIXME: check playing_state.state == 1 separate to error test above
-               res = -1;
-               printf("unable to restore state\n");
+                       dbfile_free(file);
+                       file = dbscan_list_entry_prev(&scan, &state->curr);
+               }
        }
 
-       // always these
-       ap->playing_state.list_default.listid = dblistid_get_name(tx, ap->index, "all");
-       ap->playing_state.list_playnow.listid = dblistid_get_name(tx, ap->index, "playnow");
-       ap->playing_state.list_jukebox.listid = dblistid_get_name(tx, ap->index, "jukebox");
+       res = -1;
+       state->state = 0;
+found:
+       state->pos = 0;
+       state->poss = 0;
+       state->stamp = time(NULL);
 
-       {
-               dblist *list = dblist_get(tx, ap->index, ap->playing_state.current.listid);
+       dbstate_put(tx, ap->index, state->curr.listid, state);
 
-               printf("restore state=%d file=%d list='%s' '%s'\n", ap->playing_state.state, ap->playing_state.current.fileid,
-                       ap->playing ? ap->playing->full_path : NULL,
-                       list ? list->name : NULL);
+       dbscan_free(&scan);
+       dbindex_commit(tx);
+
+       return res;
+}
+
+// first file on restarting server, see if we were playing something last time and start back there
+// TODO: do something with the position
+static int audio_restore_state(struct audio_player *ap) {
+       dbtxn *tx = dbindex_begin(ap->index, NULL, 0);
+       int res = -1;
 
-               dblist_free(list);
+       printf("restore state\n");
+       for (int i=0;i<ZPL_SIZEOF;i++) {
+               dbstate *state = &ap->state[i];
+               if (ap->list_id[i] == 0)
+                       continue;
+               res = dbstate_get(tx, ap->index, ap->list_id[i], state);
+               if (res != 0) {
+                       state->curr.listid = ap->list_id[i];
+                       state->curr.fileid = 0;
+                       state->curr.seq = 0;
+                       // Initialise sequence to end of list for playnow and jukebox
+                       if (i == ZPL_PLAYNOW || i == ZPL_JUKEBOX) {
+                               dblist *list = dblist_get(tx, ap->index, state->curr.listid);
+
+                               if (list) {
+                                       state->curr.seq = list->size + 1;
+                                       dblist_free(list);
+                               }
+                       }
+                       dbstate_put(tx, ap->index, state->curr.listid, state);
+               }
+               printf(" state: lid=%4d seq=%4d fid=%4d\n", state->curr.listid, state->curr.seq, state->curr.fileid);
        }
 
        dbindex_commit(tx);
 
-       if (res == 0)
-               return res;
-
-       return audio_next_file(ap);
+       return audio_play_curr(ap);
 }
 
-
-
 static void audio_player_control(struct audio_player *ap) {
        int ready = notify_msg_ready(ap->player);
 
-       if (time(NULL) != ap->playing_state.stamp)
+       if (ap->playing && time(NULL) != ap->state[ap->state_curr].stamp)
                audio_checkpoint_state(ap);
 
 #ifdef VOICE_MT
@@ -1096,10 +1043,10 @@ static void audio_player_control(struct audio_player *ap) {
                        }
                        break;
                case NOTIFY_PLAY_NEXT:
-                       audio_next_file(ap);
+                       audio_play_next(ap);
                        break;
                case NOTIFY_PLAY_PREV:
-                       audio_prev_file(ap);
+                       audio_play_prev(ap);
                        break;
                case NOTIFY_PLAY_NOW: {
                        struct notify_goto *g = msg;
@@ -1276,7 +1223,8 @@ static void audio_player_loop(struct audio_player *ap) {
                        res = av_read_frame(ap->fc, &pkt);
                        if (res < 0) {
                                audio_close_media(ap);
-                               audio_next_file(ap);
+                               audio_play_done(ap);
+                               audio_play_curr(ap);
                                continue;
                        }
 
index 9bb7f4e..62e1107 100644 (file)
 
       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");
+       requestGET("/x/queue", 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);