From c165c55b5b404d91f9873055b99244813b341a9a Mon Sep 17 00:00:00 2001 From: chme Date: Sun, 20 Jan 2019 12:03:50 +0100 Subject: [PATCH 1/5] [jsonapi] Add player/toggle endpoint Toggles playback state depending on the current player state: - playing --> pause - paused, stopped --> play --- src/httpd_jsonapi.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 1c9ba23b..64ecf827 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -1440,6 +1440,33 @@ jsonapi_reply_player_stop(struct httpd_request *hreq) return HTTP_NOCONTENT; } +static int +jsonapi_reply_player_toggle(struct httpd_request *hreq) +{ + struct player_status status; + int ret; + + player_get_status(&status); + DPRINTF(E_DBG, L_WEB, "Toggle playback request with current state %d.\n", status.status); + + if (status.status == PLAY_PLAYING) + { + ret = player_playback_pause(); + } + else + { + ret = player_playback_start(); + } + + if (ret < 0) + { + DPRINTF(E_LOG, L_WEB, "Error toggling playback state.\n"); + return HTTP_INTERNAL; + } + + return HTTP_NOCONTENT; +} + static int jsonapi_reply_player_next(struct httpd_request *hreq) { @@ -3240,6 +3267,7 @@ static struct httpd_uri_map adm_handlers[] = { EVHTTP_REQ_PUT, "^/api/player/play$", jsonapi_reply_player_play }, { EVHTTP_REQ_PUT, "^/api/player/pause$", jsonapi_reply_player_pause }, { EVHTTP_REQ_PUT, "^/api/player/stop$", jsonapi_reply_player_stop }, + { EVHTTP_REQ_PUT, "^/api/player/toggle$", jsonapi_reply_player_toggle }, { EVHTTP_REQ_PUT, "^/api/player/next$", jsonapi_reply_player_next }, { EVHTTP_REQ_PUT, "^/api/player/previous$", jsonapi_reply_player_previous }, { EVHTTP_REQ_PUT, "^/api/player/shuffle$", jsonapi_reply_player_shuffle }, From db7b9c689bf988f5bc64f23ac67e4651f76326c8 Mon Sep 17 00:00:00 2001 From: chme Date: Mon, 21 Jan 2019 09:34:37 +0100 Subject: [PATCH 2/5] [player] Add command to get a single speaker info by its id --- src/player.c | 72 +++++++++++++++++++++++++++++++++++++++++++--------- src/player.h | 7 +++-- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/player.c b/src/player.c index 74316f29..b6029ea8 100644 --- a/src/player.c +++ b/src/player.c @@ -146,6 +146,12 @@ struct speaker_set_param int intval; }; +struct speaker_get_param +{ + uint64_t spk_id; + struct spk_info *spk_info; +}; + struct metadata_param { struct input_metadata *input; @@ -2407,6 +2413,25 @@ player_speaker_status_trigger(void) listener_notify(LISTENER_SPEAKER); } +static void +device_to_speaker_info(struct spk_info *spk, struct output_device *device) +{ + memset(spk, 0, sizeof(struct spk_info)); + spk->id = device->id; + strncpy(spk->name, device->name, sizeof(spk->name)); + spk->name[sizeof(spk->name) - 1] = '\0'; + strncpy(spk->output_type, device->type_name, sizeof(spk->output_type)); + spk->output_type[sizeof(spk->output_type) - 1] = '\0'; + spk->relvol = device->relvol; + spk->absvol = device->volume; + + spk->selected = device->selected; + spk->has_password = device->has_password; + spk->has_video = device->has_video; + spk->requires_auth = device->requires_auth; + spk->needs_auth_key = (device->requires_auth && device->auth_key == NULL); +} + static enum command_state speaker_enumerate(void *arg, int *retval) { @@ -2418,18 +2443,7 @@ speaker_enumerate(void *arg, int *retval) { if (device->advertised || device->selected) { - spk.id = device->id; - spk.name = device->name; - spk.output_type = device->type_name; - spk.relvol = device->relvol; - spk.absvol = device->volume; - - spk.selected = device->selected; - spk.has_password = device->has_password; - spk.has_video = device->has_video; - spk.requires_auth = device->requires_auth; - spk.needs_auth_key = (device->requires_auth && device->auth_key == NULL); - + device_to_speaker_info(&spk, device); spk_enum->cb(&spk, spk_enum->arg); } } @@ -2438,6 +2452,28 @@ speaker_enumerate(void *arg, int *retval) return COMMAND_END; } +static enum command_state +speaker_get_byid(void *arg, int *retval) +{ + struct speaker_get_param *spk_param = arg; + struct output_device *device; + + for (device = dev_list; device; device = device->next) + { + if ((device->advertised || device->selected) + && device->id == spk_param->spk_id) + { + device_to_speaker_info(spk_param->spk_info, device); + *retval = 0; + return COMMAND_END; + } + } + + // No output device found with matching id + *retval = -1; + return COMMAND_END; +} + static int speaker_activate(struct output_device *device) { @@ -3123,6 +3159,18 @@ player_speaker_set(uint64_t *ids) return ret; } +int +player_speaker_get_byid(uint64_t id, struct spk_info *spk) +{ + struct speaker_get_param param; + int ret; + + param.spk_id = id; + + ret = commands_exec_sync(cmdbase, speaker_get_byid, NULL, ¶m); + return ret; +} + int player_speaker_enable(uint64_t id) { diff --git a/src/player.h b/src/player.h index 4771e41f..082ac1f0 100644 --- a/src/player.h +++ b/src/player.h @@ -31,8 +31,8 @@ enum repeat_mode { struct spk_info { uint64_t id; - const char *name; - const char *output_type; + char name[255]; + char output_type[50]; int relvol; int absvol; @@ -95,6 +95,9 @@ player_speaker_enumerate(spk_enum_cb cb, void *arg); int player_speaker_set(uint64_t *ids); +int +player_speaker_get_byid(uint64_t id, struct spk_info *spk); + int player_speaker_enable(uint64_t id); From 343c58322967f70047e7ff2caa1eade04a35212d Mon Sep 17 00:00:00 2001 From: chme Date: Sun, 20 Jan 2019 13:13:01 +0100 Subject: [PATCH 3/5] [jsonapi] Support changing volume by the given step (increase/decrease volume) --- src/httpd_jsonapi.c | 129 +++++++++++++++++++++++++++++++++----------- 1 file changed, 98 insertions(+), 31 deletions(-) diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 64ecf827..893b73a9 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -30,6 +30,7 @@ # include #endif +#include #include #include #include @@ -1066,6 +1067,7 @@ struct outputs_param { json_object *output; uint64_t output_id; + int output_volume; }; static json_object * @@ -1101,25 +1103,15 @@ speaker_enum_cb(struct spk_info *spk, void *arg) json_object_array_add(outputs, output); } -static void -speaker_get_cb(struct spk_info *spk, void *arg) -{ - struct outputs_param *outputs_param = arg; - - if (outputs_param->output_id == spk->id) - { - outputs_param->output = speaker_to_json(spk); - } -} - /* * GET /api/outputs/[output_id] */ static int jsonapi_reply_outputs_get_byid(struct httpd_request *hreq) { - struct outputs_param outputs_param; + struct spk_info speaker_info; uint64_t output_id; + json_object *jreply; int ret; ret = safe_atou64(hreq->uri_parsed->path_parts[2], &output_id); @@ -1130,21 +1122,19 @@ jsonapi_reply_outputs_get_byid(struct httpd_request *hreq) return HTTP_BADREQUEST; } - outputs_param.output_id = output_id; - outputs_param.output = NULL; + ret = player_speaker_get_byid(output_id, &speaker_info); - player_speaker_enumerate(speaker_get_cb, &outputs_param); - - if (!outputs_param.output) + if (ret < 0) { DPRINTF(E_LOG, L_WEB, "No output found for '%s'\n", hreq->uri_parsed->path); return HTTP_BADREQUEST; } - CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(outputs_param.output))); + jreply = speaker_to_json(&speaker_info); + CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply))); - jparse_free(outputs_param.output); + jparse_free(jreply); return HTTP_OK; } @@ -2202,37 +2192,114 @@ jsonapi_reply_player_consume(struct httpd_request *hreq) return HTTP_NOCONTENT; } +static int +volume_set(int volume, int step) +{ + int new_volume; + struct player_status status; + int ret; + + new_volume = volume; + + if (step != 0) + { + // Calculate new volume from given step value + player_get_status(&status); + new_volume = status.volume + step; + } + + // Make sure we are setting a correct value + new_volume = new_volume > 100 ? 100 : new_volume; + new_volume = new_volume < 0 ? 0 : new_volume; + + ret = player_volume_set(new_volume); + return ret; +} + +static int +output_volume_set(int volume, int step, uint64_t output_id) +{ + int new_volume; + struct spk_info speaker_info; + int ret; + + new_volume = volume; + + if (step != 0) + { + // Calculate new output volume from the given step value + ret = player_speaker_get_byid(output_id, &speaker_info); + if (ret < 0) + { + DPRINTF(E_LOG, L_WEB, "No output found for the given output id .\n"); + return -1; + } + + new_volume = speaker_info.absvol + step; + } + + // Make sure we are setting a correct value + new_volume = new_volume > 100 ? 100 : new_volume; + new_volume = new_volume < 0 ? 0 : new_volume; + + ret = player_volume_setabs_speaker(output_id, new_volume); + return ret; +} + static int jsonapi_reply_player_volume(struct httpd_request *hreq) { + const char *param_volume; + const char *param_step; const char *param; uint64_t output_id; int volume; + int step; int ret; - param = evhttp_find_header(hreq->query, "volume"); - if (!param) - return HTTP_BADREQUEST; + volume = 0; + step = 0; - ret = safe_atoi32(param, &volume); - if (ret < 0) - return HTTP_BADREQUEST; + // Parse and validate parameters + param_volume = evhttp_find_header(hreq->query, "volume"); + if (param_volume) + { + ret = safe_atoi32(param_volume, &volume); + if (ret < 0) + return HTTP_BADREQUEST; + } - if (volume < 0 || volume > 100) - return HTTP_BADREQUEST; + param_step = evhttp_find_header(hreq->query, "step"); + if (param_step) + { + ret = safe_atoi32(param_step, &step); + if (ret < 0) + return HTTP_BADREQUEST; + } + + if ((!param_volume && !param_step) + || (param_volume && param_step)) + { + DPRINTF(E_LOG, L_WEB, "Invalid parameters for player/volume request. Either 'volume' or 'step' parameter required.\n"); + return HTTP_BADREQUEST; + } param = evhttp_find_header(hreq->query, "output_id"); if (param) { + // Update volume for individual output ret = safe_atou64(param, &output_id); if (ret < 0) - return HTTP_BADREQUEST; - - ret = player_volume_setabs_speaker(output_id, volume); + { + DPRINTF(E_LOG, L_WEB, "Invalid value for parameter 'output_id'. Output id must be an integer (output_id='%s').\n", param); + return HTTP_BADREQUEST; + } + ret = output_volume_set(volume, step, output_id); } else { - ret = player_volume_set(volume); + // Update master volume + ret = volume_set(volume, step); } if (ret < 0) From f91df003fe85803954a88cff42b0a3646bd512f6 Mon Sep 17 00:00:00 2001 From: chme Date: Tue, 22 Jan 2019 09:24:14 +0100 Subject: [PATCH 4/5] [README] Update JSON API documentation - api/player/toggle - api/player/volume --- README_JSON_API.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README_JSON_API.md b/README_JSON_API.md index 412e0d71..df9f7497 100644 --- a/README_JSON_API.md +++ b/README_JSON_API.md @@ -23,7 +23,7 @@ JSON-Object model: | Method | Endpoint | Description | | --------- | ------------------------------------------------ | ------------------------------------ | | GET | [/api/player](#get-player-status) | Get player status | -| PUT | [/api/player/play, /api/player/pause, /api/player/stop](#control-playback) | Start, pause or stop playback | +| PUT | [/api/player/play, /api/player/pause, /api/player/stop, /api/player/toggle](#control-playback) | Start, pause or stop playback | | PUT | [/api/player/next, /api/player/prev](#skip-tracks) | Skip forward or backward | | PUT | [/api/player/shuffle](#set-shuffle-mode) | Set shuffle mode | | PUT | [/api/player/consume](#set-consume-mode) | Set consume mode | @@ -93,6 +93,10 @@ PUT /api/player/pause PUT /api/player/stop ``` +```http +PUT /api/player/toggle +``` + **Response** On success returns the HTTP `204 No Content` success status response code. @@ -111,6 +115,10 @@ curl -X PUT "http://localhost:3689/api/player/pause" curl -X PUT "http://localhost:3689/api/player/stop" ``` +```shell +curl -X PUT "http://localhost:3689/api/player/toggle" +``` + ### Skip tracks @@ -240,8 +248,10 @@ PUT /api/player/volume | Parameter | Value | | --------------- | ----------------------------------------------------------- | | volume | The new volume (0 - 100) | +| step | The increase or decrease volume by the given amount (-100 - 100) | | output_id | *(Optional)* If an output id is given, only the volume of this output will be changed. If parameter is omited, the master volume will be changed. | +Either `volume` or `step` must be present as query parameter **Response** @@ -253,6 +263,10 @@ On success returns the HTTP `204 No Content` success status response code. curl -X PUT "http://localhost:3689/api/player/volume?volume=50" ``` +```shell +curl -X PUT "http://localhost:3689/api/player/volume?step=-5" +``` + ```shell curl -X PUT "http://localhost:3689/api/player/volume?volume=50&output_id=0" ``` From 116c315a84960408efb4019de936d9e0c132f5db Mon Sep 17 00:00:00 2001 From: chme Date: Tue, 22 Jan 2019 17:47:15 +0100 Subject: [PATCH 5/5] [player] Rename struct spk_info to player_speaker_info --- src/httpd_dacp.c | 2 +- src/httpd_jsonapi.c | 8 ++++---- src/mpd.c | 4 ++-- src/player.c | 10 +++++----- src/player.h | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/httpd_dacp.c b/src/httpd_dacp.c index e38dfed8..d8dde6b6 100644 --- a/src/httpd_dacp.c +++ b/src/httpd_dacp.c @@ -512,7 +512,7 @@ playqueuecontents_add_queue_item(struct evbuffer *songlist, struct db_queue_item } static void -speaker_enum_cb(struct spk_info *spk, void *arg) +speaker_enum_cb(struct player_speaker_info *spk, void *arg) { struct evbuffer *evbuf; int len; diff --git a/src/httpd_jsonapi.c b/src/httpd_jsonapi.c index 893b73a9..c7a1042c 100644 --- a/src/httpd_jsonapi.c +++ b/src/httpd_jsonapi.c @@ -1071,7 +1071,7 @@ struct outputs_param }; static json_object * -speaker_to_json(struct spk_info *spk) +speaker_to_json(struct player_speaker_info *spk) { json_object *output; char output_id[21]; @@ -1092,7 +1092,7 @@ speaker_to_json(struct spk_info *spk) } static void -speaker_enum_cb(struct spk_info *spk, void *arg) +speaker_enum_cb(struct player_speaker_info *spk, void *arg) { json_object *outputs; json_object *output; @@ -1109,7 +1109,7 @@ speaker_enum_cb(struct spk_info *spk, void *arg) static int jsonapi_reply_outputs_get_byid(struct httpd_request *hreq) { - struct spk_info speaker_info; + struct player_speaker_info speaker_info; uint64_t output_id; json_object *jreply; int ret; @@ -2220,7 +2220,7 @@ static int output_volume_set(int volume, int step, uint64_t output_id) { int new_volume; - struct spk_info speaker_info; + struct player_speaker_info speaker_info; int ret; new_volume = volume; diff --git a/src/mpd.c b/src/mpd.c index dca63bb2..25ae2cfe 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -3548,7 +3548,7 @@ mpd_command_password(struct evbuffer *evbuf, int argc, char **argv, char **errms * the shortid of output_get_param matches the given speaker/output spk. */ static void -output_get_cb(struct spk_info *spk, void *arg) +output_get_cb(struct player_speaker_info *spk, void *arg) { struct output_get_param *param = arg; @@ -3696,7 +3696,7 @@ mpd_command_toggleoutput(struct evbuffer *evbuf, int argc, char **argv, char **e * outputvolume: 50 */ static void -speaker_enum_cb(struct spk_info *spk, void *arg) +speaker_enum_cb(struct player_speaker_info *spk, void *arg) { struct evbuffer *evbuf; diff --git a/src/player.c b/src/player.c index b6029ea8..c732740e 100644 --- a/src/player.c +++ b/src/player.c @@ -149,7 +149,7 @@ struct speaker_set_param struct speaker_get_param { uint64_t spk_id; - struct spk_info *spk_info; + struct player_speaker_info *spk_info; }; struct metadata_param @@ -2414,9 +2414,9 @@ player_speaker_status_trigger(void) } static void -device_to_speaker_info(struct spk_info *spk, struct output_device *device) +device_to_speaker_info(struct player_speaker_info *spk, struct output_device *device) { - memset(spk, 0, sizeof(struct spk_info)); + memset(spk, 0, sizeof(struct player_speaker_info)); spk->id = device->id; strncpy(spk->name, device->name, sizeof(spk->name)); spk->name[sizeof(spk->name) - 1] = '\0'; @@ -2437,7 +2437,7 @@ speaker_enumerate(void *arg, int *retval) { struct spk_enum *spk_enum = arg; struct output_device *device; - struct spk_info spk; + struct player_speaker_info spk; for (device = dev_list; device; device = device->next) { @@ -3160,7 +3160,7 @@ player_speaker_set(uint64_t *ids) } int -player_speaker_get_byid(uint64_t id, struct spk_info *spk) +player_speaker_get_byid(uint64_t id, struct player_speaker_info *spk) { struct speaker_get_param param; int ret; diff --git a/src/player.h b/src/player.h index 082ac1f0..3cae9c2d 100644 --- a/src/player.h +++ b/src/player.h @@ -29,7 +29,7 @@ enum repeat_mode { REPEAT_ALL = 2, }; -struct spk_info { +struct player_speaker_info { uint64_t id; char name[255]; char output_type[50]; @@ -64,7 +64,7 @@ struct player_status { uint32_t len_ms; }; -typedef void (*spk_enum_cb)(struct spk_info *spk, void *arg); +typedef void (*spk_enum_cb)(struct player_speaker_info *spk, void *arg); struct player_history { @@ -96,7 +96,7 @@ int player_speaker_set(uint64_t *ids); int -player_speaker_get_byid(uint64_t id, struct spk_info *spk); +player_speaker_get_byid(uint64_t id, struct player_speaker_info *spk); int player_speaker_enable(uint64_t id);