From 9f719ca155692c4fc43986c7e66a0dd7c28aae76 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 7 Jan 2024 00:00:18 +0100 Subject: [PATCH] [player/jsonapi/db] Add interface to get and set an output format --- docs/json-api.md | 12 +++++++++--- src/db.c | 8 +++++--- src/db_init.c | 5 +++-- src/db_init.h | 2 +- src/db_upgrade.c | 25 +++++++++++++++++++++++++ src/httpd_jsonapi.c | 43 +++++++++++++++++++++++++++++++++++++++++++ src/player.c | 40 +++++++++++++++++++++++++++++++++++++--- src/player.h | 12 +++++++++++- 8 files changed, 134 insertions(+), 13 deletions(-) diff --git a/docs/json-api.md b/docs/json-api.md index 7107a29c..a988df25 100644 --- a/docs/json-api.md +++ b/docs/json-api.md @@ -341,6 +341,7 @@ GET /api/outputs | requires_auth | boolean | `true` if output requires authentication | | needs_auth_key | boolean | `true` if output requires an authorization key (device verification) | | volume | integer | Volume in percent (0 - 100) | +| format | string | Stream format | **Example** @@ -359,7 +360,8 @@ curl -X GET "http://localhost:3689/api/outputs" "has_password": false, "requires_auth": false, "needs_auth_key": false, - "volume": 0 + "volume": 0, + "format": "alac" }, { "id": "0", @@ -369,7 +371,8 @@ curl -X GET "http://localhost:3689/api/outputs" "has_password": false, "requires_auth": false, "needs_auth_key": false, - "volume": 19 + "volume": 19, + "format": "pcm" }, { "id": "100", @@ -379,7 +382,8 @@ curl -X GET "http://localhost:3689/api/outputs" "has_password": false, "requires_auth": false, "needs_auth_key": false, - "volume": 0 + "volume": 0, + "format": "pcm" } ] } @@ -448,6 +452,7 @@ curl -X GET "http://localhost:3689/api/outputs/0" "requires_auth": false, "needs_auth_key": false, "volume": 3 + "format": "pcm", } ``` @@ -474,6 +479,7 @@ PUT /api/outputs/{id} | selected | boolean | *(Optional)* `true` to enable and `false` to disable the output | | volume | integer | *(Optional)* Volume in percent (0 - 100) | | pin | string | *(Optional)* PIN for device verification | +| format | string | *(Optional)* Stream format | **Response** diff --git a/src/db.c b/src/db.c index 12770c2e..8a78c631 100644 --- a/src/db.c +++ b/src/db.c @@ -4787,10 +4787,10 @@ db_admin_delete(const char *key) int db_speaker_save(struct output_device *device) { -#define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name, auth_key) VALUES (%" PRIi64 ", %d, %d, %Q, %Q);" +#define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name, auth_key, format) VALUES (%" PRIi64 ", %d, %d, %Q, %Q, %d);" char *query; - query = sqlite3_mprintf(Q_TMPL, device->id, device->selected, device->volume, device->name, device->auth_key); + query = sqlite3_mprintf(Q_TMPL, device->id, device->selected, device->volume, device->name, device->auth_key, device->format); return db_query_run(query, 1, 0); #undef Q_TMPL @@ -4799,7 +4799,7 @@ db_speaker_save(struct output_device *device) int db_speaker_get(struct output_device *device, uint64_t id) { -#define Q_TMPL "SELECT s.selected, s.volume, s.name, s.auth_key FROM speakers s WHERE s.id = %" PRIi64 ";" +#define Q_TMPL "SELECT s.selected, s.volume, s.name, s.auth_key, s.format FROM speakers s WHERE s.id = %" PRIi64 ";" sqlite3_stmt *stmt; char *query; int ret; @@ -4841,6 +4841,8 @@ db_speaker_get(struct output_device *device, uint64_t id) free(device->auth_key); device->auth_key = safe_strdup((char *)sqlite3_column_text(stmt, 3)); + device->format = sqlite3_column_int(stmt, 4); + #ifdef DB_PROFILE while (db_blocking_step(stmt) == SQLITE_ROW) ; /* EMPTY */ diff --git a/src/db_init.c b/src/db_init.c index 1a406e1d..db1492a2 100644 --- a/src/db_init.c +++ b/src/db_init.c @@ -151,8 +151,9 @@ " id INTEGER PRIMARY KEY NOT NULL," \ " selected INTEGER NOT NULL," \ " volume INTEGER NOT NULL," \ - " name VARCHAR(255) DEFAULT NULL," \ - " auth_key VARCHAR(2048) DEFAULT NULL" \ + " name VARCHAR(255) DEFAULT NULL," \ + " auth_key VARCHAR(2048) DEFAULT NULL," \ + " format INTEGER DEFAULT 0" \ ");" #define T_INOTIFY \ diff --git a/src/db_init.h b/src/db_init.h index aaf6ba1b..20052f1d 100644 --- a/src/db_init.h +++ b/src/db_init.h @@ -26,7 +26,7 @@ * is a major upgrade. In other words minor version upgrades permit downgrading * the server after the database was upgraded. */ #define SCHEMA_VERSION_MAJOR 22 -#define SCHEMA_VERSION_MINOR 1 +#define SCHEMA_VERSION_MINOR 2 int db_init_indices(sqlite3 *hdl); diff --git a/src/db_upgrade.c b/src/db_upgrade.c index 90fff3b8..efc1c74a 100644 --- a/src/db_upgrade.c +++ b/src/db_upgrade.c @@ -1246,6 +1246,24 @@ static const struct db_upgrade_query db_upgrade_v2201_queries[] = }; +/* ---------------------------- 22.01 -> 22.02 ------------------------------ */ + +#define U_v2202_ALTER_SPEAKERS_ADD_FORMAT \ + "ALTER TABLE speakers ADD COLUMN format INTEGER DEFAULT 0;" + +#define U_v2202_SCVER_MAJOR \ + "UPDATE admin SET value = '22' WHERE key = 'schema_version_major';" +#define U_v2202_SCVER_MINOR \ + "UPDATE admin SET value = '02' WHERE key = 'schema_version_minor';" + +static const struct db_upgrade_query db_upgrade_v2202_queries[] = + { + { U_v2202_ALTER_SPEAKERS_ADD_FORMAT, "alter table speakers add column format" }, + + { U_v2202_SCVER_MAJOR, "set schema_version_major to 22" }, + { U_v2202_SCVER_MINOR, "set schema_version_minor to 02" }, + }; + /* -------------------------- Main upgrade handler -------------------------- */ @@ -1464,6 +1482,13 @@ db_upgrade(sqlite3 *hdl, int db_ver) if (ret < 0) return -1; + /* FALLTHROUGH */ + + case 2201: + ret = db_generic_upgrade(hdl, db_upgrade_v2202_queries, ARRAY_SIZE(db_upgrade_v2202_queries)); + if (ret < 0) + return -1; + /* Last case statement is the only one that ends with a break statement! */ break; diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 24d3e7da..5e9de857 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -1522,6 +1522,40 @@ struct outputs_param int output_volume; }; +static enum player_format +plformat_from_string(const char *format) +{ + if (strcmp(format, "pcm") == 0) + return PLAYER_FORMAT_PCM; + if (strcmp(format, "wav") == 0) + return PLAYER_FORMAT_WAV; + if (strcmp(format, "mp3") == 0) + return PLAYER_FORMAT_MP3; + if (strcmp(format, "alac") == 0) + return PLAYER_FORMAT_ALAC; + if (strcmp(format, "opus") == 0) + return PLAYER_FORMAT_OPUS; + + return PLAYER_FORMAT_UNKNOWN; +} + +static const char * +plformat_to_string(enum player_format format) +{ + if (format == PLAYER_FORMAT_PCM) + return "pcm"; + if (format == PLAYER_FORMAT_WAV) + return "wav"; + if (format == PLAYER_FORMAT_MP3) + return "mp3"; + if (format == PLAYER_FORMAT_ALAC) + return "alac"; + if (format == PLAYER_FORMAT_OPUS) + return "opus"; + + return "unknown"; +} + static json_object * speaker_to_json(struct player_speaker_info *spk) { @@ -1539,6 +1573,7 @@ speaker_to_json(struct player_speaker_info *spk) json_object_object_add(output, "requires_auth", json_object_new_boolean(spk->requires_auth)); json_object_object_add(output, "needs_auth_key", json_object_new_boolean(spk->needs_auth_key)); json_object_object_add(output, "volume", json_object_new_int(spk->absvol)); + json_object_object_add(output, "format", json_object_new_string(plformat_to_string(spk->format))); return output; } @@ -1602,6 +1637,7 @@ jsonapi_reply_outputs_put_byid(struct httpd_request *hreq) bool selected; int volume; const char *pin; + const char *format; int ret; ret = safe_atou64(hreq->path_parts[2], &output_id); @@ -1644,6 +1680,13 @@ jsonapi_reply_outputs_put_byid(struct httpd_request *hreq) ret = player_speaker_authorize(output_id, pin); } + if (ret == 0 && jparse_contains_key(request, "format", json_type_string)) + { + format = jparse_str_from_obj(request, "format"); + if (format) + ret = player_speaker_format_set(output_id, plformat_from_string(format)); + } + jparse_free(request); if (ret < 0) diff --git a/src/player.c b/src/player.c index 7147a0e9..96975dca 100644 --- a/src/player.c +++ b/src/player.c @@ -2526,6 +2526,7 @@ device_to_speaker_info(struct player_speaker_info *spk, struct output_device *de spk->output_type[sizeof(spk->output_type) - 1] = '\0'; spk->relvol = device->relvol; spk->absvol = device->volume; + spk->format = device->format; spk->selected = OUTPUTS_DEVICE_DISPLAY_SELECTED(device); @@ -2730,6 +2731,8 @@ speaker_prevent_playback_set(void *arg, int *retval) struct speaker_attr_param *param = arg; struct output_device *device; + *retval = -1; + device = outputs_device_get(param->spk_id); if (!device) return COMMAND_END; @@ -2778,6 +2781,8 @@ speaker_busy_set(void *arg, int *retval) struct speaker_attr_param *param = arg; struct output_device *device; + *retval = -1; + device = outputs_device_get(param->spk_id); if (!device) return COMMAND_END; @@ -2803,6 +2808,27 @@ speaker_busy_set(void *arg, int *retval) return COMMAND_END; } +static enum command_state +speaker_format_set(void *arg, int *retval) +{ + struct speaker_attr_param *param = arg; + struct output_device *device; + + *retval = -1; + + if (param->format == PLAYER_FORMAT_UNKNOWN) + return COMMAND_END; + + device = outputs_device_get(param->spk_id); + if (!device) + return COMMAND_END; + + device->format = param->format; + + *retval = 0; + return COMMAND_END; +} + // Attempts to reactivate a speaker that has failed. That includes restarting // playback if it was stopped. static enum command_state @@ -3466,14 +3492,22 @@ int player_speaker_authorize(uint64_t id, const char *pin) { struct speaker_attr_param param; - int ret; param.spk_id = id; param.pin = pin; - ret = commands_exec_sync(cmdbase, speaker_authorize, speaker_generic_bh, ¶m); + return commands_exec_sync(cmdbase, speaker_authorize, speaker_generic_bh, ¶m); +} - return ret; +int +player_speaker_format_set(uint64_t id, enum player_format format) +{ + struct speaker_attr_param param; + + param.spk_id = id; + param.format = format; + + return commands_exec_sync(cmdbase, speaker_format_set, NULL, ¶m); } int diff --git a/src/player.h b/src/player.h index 602a13e0..8aed0aae 100644 --- a/src/player.h +++ b/src/player.h @@ -29,7 +29,12 @@ enum player_seek_mode { }; enum player_format { - PLAYER_FORMAT_MP3, + PLAYER_FORMAT_UNKNOWN = -1, + PLAYER_FORMAT_PCM = 0, + PLAYER_FORMAT_WAV = 1, + PLAYER_FORMAT_MP3 = 2, + PLAYER_FORMAT_ALAC = 3, + PLAYER_FORMAT_OPUS = 4, }; struct player_speaker_info { @@ -40,6 +45,8 @@ struct player_speaker_info { int relvol; int absvol; + enum player_format format; + bool selected; bool has_password; bool requires_auth; @@ -122,6 +129,9 @@ player_speaker_resurrect(void *arg); int player_speaker_authorize(uint64_t id, const char *pin); +int +player_speaker_format_set(uint64_t id, enum player_format format); + int player_streaming_register(int *audio_fd, int *metadata_fd, enum player_format format, struct media_quality quality);