[player/jsonapi/db] Add interface to get and set an output format

This commit is contained in:
ejurgensen 2024-01-07 00:00:18 +01:00
parent c079df5da7
commit 9f719ca155
8 changed files with 134 additions and 13 deletions

View File

@ -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**

View File

@ -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 */

View File

@ -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 \

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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, &param);
return commands_exec_sync(cmdbase, speaker_authorize, speaker_generic_bh, &param);
}
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, &param);
}
int

View File

@ -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);