* 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
struct dbindex {
int res; // last result
+ int debug;
+
MDB_env *env;
MDB_dbi meta;
// 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++) {
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 {
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);
}
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");
} else
break;
}
+ mdb_cursor_close(cursor);
regfree(®);
mdb_txn_abort(tx);
}
}
+ mdb_cursor_close(cursor);
regfree(®);
mdb_txn_abort(tx);
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;
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);
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)) {
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) {
}
obstack_printf(io, ",\"size\":\"%d\"", list->size);
obstack_1grow(io, '}');
+ dblist_free(list);
}
obstack_1grow(io, ']');
obstack_1grow(io, '}');
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));
{ .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 },
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);
/* ********************************************************************** */
+// 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;
} 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));
// 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;
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;
// 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
#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;
}
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);
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) {
- 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)
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
}
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;
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;
}
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);