mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-24 13:13:17 -05:00
Merge pull request #490 from chme/jsonapi_outputs
JSON api cleanup and documentation
This commit is contained in:
commit
cc1785a843
527
README_JSON_API.md
Normal file
527
README_JSON_API.md
Normal file
@ -0,0 +1,527 @@
|
|||||||
|
# forked-daapd API Endpoint Reference
|
||||||
|
|
||||||
|
Available API endpoints:
|
||||||
|
|
||||||
|
* [Player](#player): control playback, volume, shuffle/repeat modes
|
||||||
|
* [Outputs / Speakers](#outputs--speakers): list available outputs and enable/disable outputs
|
||||||
|
* [Server info](#server-info): get server information
|
||||||
|
* [Push notifications](#push-notifications): receive push notifications
|
||||||
|
|
||||||
|
## Player
|
||||||
|
|
||||||
|
| 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/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 |
|
||||||
|
| PUT | [/api/player/repeat](#set-repeat-mode) | Set repeat mode |
|
||||||
|
| PUT | [/api/player/volume](#set-volume) | Set master volume or volume for a specific output |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Get player status
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/player
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| ----------------- | -------- | ----------------------------------------- |
|
||||||
|
| state | string | `play`, `pause` or `stop` |
|
||||||
|
| repeat | string | `off`, `all` or `single` |
|
||||||
|
| consume | boolean | `true` if consume mode is enabled |
|
||||||
|
| shuffle | boolean | `true` if shuffle mode is enabled |
|
||||||
|
| volume | integer | Master volume in percent (0 - 100) |
|
||||||
|
| item_id | integer | The current playing queue item `id` |
|
||||||
|
| item_length_ms | integer | Total length in milliseconds of the current queue item |
|
||||||
|
| item_progress_ms | integer | Progress into the current queue item in milliseconds |
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X GET "http://localhost:3689/api/player"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"state": "pause",
|
||||||
|
"repeat": "off",
|
||||||
|
"consume": false,
|
||||||
|
"shuffle": false,
|
||||||
|
"volume": 50,
|
||||||
|
"item_id": 269,
|
||||||
|
"item_length_ms": 278093,
|
||||||
|
"item_progress_ms": 3674
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Control playback
|
||||||
|
|
||||||
|
Start or resume, pause, stop playback.
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/player/play
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/player/pause
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/player/stop
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `204 No Content` success status response code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/player/play"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/player/pause"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/player/stop"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Skip tracks
|
||||||
|
|
||||||
|
Skip forward or backward
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/player/next
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/player/prev
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `204 No Content` success status response code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/player/next"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/player/prev"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Set shuffle mode
|
||||||
|
|
||||||
|
Enable or disable shuffle mode
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/player/shuffle
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| state | The new shuffle state, should be either `true` or `false` |
|
||||||
|
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `204 No Content` success status response code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/player/shuffle?state=true"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Set consume mode
|
||||||
|
|
||||||
|
Enable or disable consume mode
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/player/consume
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| state | The new consume state, should be either `true` or `false` |
|
||||||
|
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `204 No Content` success status response code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/player/consume?state=true"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Set repeat mode
|
||||||
|
|
||||||
|
Change repeat mode
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/player/repeat
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| state | The new repeat mode, should be either `off`, `all` or `single` |
|
||||||
|
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `204 No Content` success status response code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/player/repeat?state=all"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Set volume
|
||||||
|
|
||||||
|
Change master volume or volume of a specific output.
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/player/volume
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| volume | The new volume (0 - 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. |
|
||||||
|
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `204 No Content` success status response code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/player/volume?volume=50"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/player/volume?volume=50&output_id=0"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Outputs / Speakers
|
||||||
|
|
||||||
|
| Method | Endpoint | Description |
|
||||||
|
| --------- | ------------------------------------------------ | ------------------------------------ |
|
||||||
|
| GET | [/api/outputs](#get-a-list-of-available-outputs) | Get a list of available outputs |
|
||||||
|
| PUT | [/api/outputs/set](#set-enabled-outputs) | Set enabled outputs |
|
||||||
|
| GET | [/api/outputs/{id}](#get-an-output) | Get an output |
|
||||||
|
| PUT | [/api/outputs/{id}](#change-an-output) | Change an output (enable/disable or volume) |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Get a list of available outputs
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/outputs
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| outputs | array | Array of `output` objects |
|
||||||
|
|
||||||
|
**`output` object**
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| id | string | Output id |
|
||||||
|
| name | string | Output name |
|
||||||
|
| type | string | Type of the output: `AirPlay`, `Chromecast`, `ALSA`, `Pulseaudio`, `fifo` |
|
||||||
|
| selected | boolean | `true` if output is enabled |
|
||||||
|
| has_password | boolean | `true` if output is password protected |
|
||||||
|
| 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) |
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X GET "http://localhost:3689/api/outputs"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"id": "123456789012345",
|
||||||
|
"name": "kitchen",
|
||||||
|
"type": "AirPlay",
|
||||||
|
"selected": true,
|
||||||
|
"has_password": false,
|
||||||
|
"requires_auth": false,
|
||||||
|
"needs_auth_key": false,
|
||||||
|
"volume": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0",
|
||||||
|
"name": "Computer",
|
||||||
|
"type": "ALSA",
|
||||||
|
"selected": true,
|
||||||
|
"has_password": false,
|
||||||
|
"requires_auth": false,
|
||||||
|
"needs_auth_key": false,
|
||||||
|
"volume": 19
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "100",
|
||||||
|
"name": "daapd-fifo",
|
||||||
|
"type": "fifo",
|
||||||
|
"selected": false,
|
||||||
|
"has_password": false,
|
||||||
|
"requires_auth": false,
|
||||||
|
"needs_auth_key": false,
|
||||||
|
"volume": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Set enabled outputs
|
||||||
|
|
||||||
|
Set the enabled outputs by passing an array of output ids. forked-daapd enables all outputs
|
||||||
|
with the given ids and disables the remaining outputs.
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/outputs/set
|
||||||
|
```
|
||||||
|
|
||||||
|
**Body parameters**
|
||||||
|
|
||||||
|
| Parameter | Type | Value |
|
||||||
|
| --------------- | -------- | -------------------- |
|
||||||
|
| outputs | array | Array of output ids |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `204 No Content` success status response code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/outputs/set" --data "{\"outputs\":[\"198018693182577\",\"0\"]}"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Get an output
|
||||||
|
|
||||||
|
Get an output
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/outputs/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Path parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | -------------------- |
|
||||||
|
| id | Output id |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `200 OK` success status response code. With the response body holding the **`output` object**.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X GET "http://localhost:3689/api/outputs/0"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": "0",
|
||||||
|
"name": "Computer",
|
||||||
|
"type": "ALSA",
|
||||||
|
"selected": true,
|
||||||
|
"has_password": false,
|
||||||
|
"requires_auth": false,
|
||||||
|
"needs_auth_key": false,
|
||||||
|
"volume": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change an output
|
||||||
|
|
||||||
|
Enable or disable an output and change its volume.
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/outputs/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Path parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | -------------------- |
|
||||||
|
| id | Output id |
|
||||||
|
|
||||||
|
**Body parameters**
|
||||||
|
|
||||||
|
| Parameter | Type | Value |
|
||||||
|
| --------------- | --------- | -------------------- |
|
||||||
|
| selected | boolean | *(Optional)* `true` to enable and `false` to disable the output |
|
||||||
|
| volume | integer | *(Optional)* Volume in percent (0 - 100) |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `204 No Content` success status response code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/outputs/0" --data "{\"selected\":true, \"volume\": 50}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Server info
|
||||||
|
|
||||||
|
| Method | Endpoint | Description |
|
||||||
|
| --------- | ------------------------------------------------ | ------------------------------------ |
|
||||||
|
| GET | [/api/config](#config) | Get configuration information |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Config
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/config
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| version | string | forked-daapd server version |
|
||||||
|
| websocket_port | integer | Port number for the [websocket](#push-notifications) (or `0` if websocket is disabled) |
|
||||||
|
| buildoptions | array | Array of strings indicating which features are supported by the forked-daapd server |
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X GET "http://localhost:3689/api/config"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"websocket_port": 3688,
|
||||||
|
"version": "25.0",
|
||||||
|
"buildoptions": [
|
||||||
|
"ffmpeg",
|
||||||
|
"iTunes XML",
|
||||||
|
"Spotify",
|
||||||
|
"LastFM",
|
||||||
|
"MPD",
|
||||||
|
"Device verification",
|
||||||
|
"Websockets",
|
||||||
|
"ALSA"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Push notifications
|
||||||
|
|
||||||
|
If forked-daapd was built with websocket support, forked-daapd exposes a websocket at `localhost:3688` to inform clients of changes (e. g. player state or library updates).
|
||||||
|
The port depends on the forked-daapd configuration and can be read using the [`/api/config`](#config) endpoint.
|
||||||
|
|
||||||
|
After connecting to the websocket, the client should send a message containing the event types it is interested in. After that forked-daapd
|
||||||
|
will send a message each time one of the events occurred.
|
||||||
|
|
||||||
|
**Message**
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| notify | array | Array of event types |
|
||||||
|
|
||||||
|
**Event types**
|
||||||
|
|
||||||
|
| Type | Description |
|
||||||
|
| --------------- | ----------------------------------------- |
|
||||||
|
| update | Library update started or finished |
|
||||||
|
| outputs | An output was enabled or disabled |
|
||||||
|
| player | Player state changes |
|
||||||
|
| options | Playback option changes (shuffle, repeat, consume mode) |
|
||||||
|
| volume | Volume changes |
|
||||||
|
| queue | Queue changes |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl --include \
|
||||||
|
--no-buffer \
|
||||||
|
--header "Connection: Upgrade" \
|
||||||
|
--header "Upgrade: websocket" \
|
||||||
|
--header "Host: localhost:3688" \
|
||||||
|
--header "Origin: http://localhost:3688" \
|
||||||
|
--header "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \
|
||||||
|
--header "Sec-WebSocket-Version: 13" \
|
||||||
|
--header "Sec-WebSocket-Protocol: notify" \
|
||||||
|
http://localhost:3688/ \
|
||||||
|
--data "{ \"notify\": [ \"player\" ] }"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"notify": [
|
||||||
|
"player"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
@ -80,7 +80,7 @@ var app = new Vue({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.post('/api/select-outputs', { outputs: selected_outputs }).then(response => {
|
axios.put('/api/outputs/set', { outputs: selected_outputs }).then(response => {
|
||||||
if (!this.config.websocket_port) {
|
if (!this.config.websocket_port) {
|
||||||
this.loadOutputs();
|
this.loadOutputs();
|
||||||
}
|
}
|
||||||
|
10
src/httpd.c
10
src/httpd.c
@ -880,6 +880,7 @@ httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_par
|
|||||||
struct httpd_request *hreq;
|
struct httpd_request *hreq;
|
||||||
struct evhttp_connection *evcon;
|
struct evhttp_connection *evcon;
|
||||||
struct evkeyvalq *headers;
|
struct evkeyvalq *headers;
|
||||||
|
int req_method;
|
||||||
int i;
|
int i;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
@ -889,6 +890,7 @@ httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_par
|
|||||||
hreq->req = req;
|
hreq->req = req;
|
||||||
hreq->uri_parsed = uri_parsed;
|
hreq->uri_parsed = uri_parsed;
|
||||||
hreq->query = &(uri_parsed->ev_query);
|
hreq->query = &(uri_parsed->ev_query);
|
||||||
|
req_method = 0;
|
||||||
|
|
||||||
if (req)
|
if (req)
|
||||||
{
|
{
|
||||||
@ -900,6 +902,8 @@ httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_par
|
|||||||
evhttp_connection_get_peer(evcon, &hreq->peer_address, &hreq->peer_port);
|
evhttp_connection_get_peer(evcon, &hreq->peer_address, &hreq->peer_port);
|
||||||
else
|
else
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Connection to client lost or missing\n");
|
DPRINTF(E_LOG, L_HTTPD, "Connection to client lost or missing\n");
|
||||||
|
|
||||||
|
req_method = evhttp_request_get_command(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user_agent)
|
if (user_agent)
|
||||||
@ -908,6 +912,10 @@ httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_par
|
|||||||
// Find a handler for the path
|
// Find a handler for the path
|
||||||
for (i = 0; uri_map[i].handler; i++)
|
for (i = 0; uri_map[i].handler; i++)
|
||||||
{
|
{
|
||||||
|
// Check if handler supports the current http request method
|
||||||
|
if (uri_map[i].method && req_method && !(req_method & uri_map[i].method))
|
||||||
|
continue;
|
||||||
|
|
||||||
ret = regexec(&uri_map[i].preg, uri_parsed->path, 0, NULL, 0);
|
ret = regexec(&uri_map[i].preg, uri_parsed->path, 0, NULL, 0);
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
@ -1687,7 +1695,7 @@ httpd_init(const char *webroot)
|
|||||||
if (allow_origin)
|
if (allow_origin)
|
||||||
{
|
{
|
||||||
if (strlen(allow_origin) != 0)
|
if (strlen(allow_origin) != 0)
|
||||||
evhttp_set_allowed_methods(evhttpd, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_HEAD | EVHTTP_REQ_OPTIONS);
|
evhttp_set_allowed_methods(evhttpd, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_PUT | EVHTTP_REQ_DELETE | EVHTTP_REQ_HEAD | EVHTTP_REQ_OPTIONS);
|
||||||
else
|
else
|
||||||
allow_origin = NULL;
|
allow_origin = NULL;
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,7 @@ struct httpd_request {
|
|||||||
*/
|
*/
|
||||||
struct httpd_uri_map
|
struct httpd_uri_map
|
||||||
{
|
{
|
||||||
|
int method;
|
||||||
char *regexp;
|
char *regexp;
|
||||||
int (*handler)(struct httpd_request *hreq);
|
int (*handler)(struct httpd_request *hreq);
|
||||||
regex_t preg;
|
regex_t preg;
|
||||||
|
@ -64,18 +64,18 @@
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
pairing_kickoff(struct evhttp_request *req)
|
jsonapi_reply_pairing_kickoff(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
struct evbuffer *evbuf;
|
struct evbuffer *evbuf;
|
||||||
json_object* request;
|
json_object* request;
|
||||||
const char* message;
|
const char* message;
|
||||||
|
|
||||||
evbuf = evhttp_request_get_input_buffer(req);
|
evbuf = evhttp_request_get_input_buffer(hreq->req);
|
||||||
request = jparse_obj_from_evbuffer(evbuf);
|
request = jparse_obj_from_evbuffer(evbuf);
|
||||||
if (!request)
|
if (!request)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
||||||
return -1;
|
return HTTP_BADREQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_WEB, "Received pairing post request: %s\n", json_object_to_json_string(request));
|
DPRINTF(E_DBG, L_WEB, "Received pairing post request: %s\n", json_object_to_json_string(request));
|
||||||
@ -88,7 +88,7 @@ pairing_kickoff(struct evhttp_request *req)
|
|||||||
|
|
||||||
jparse_free(request);
|
jparse_free(request);
|
||||||
|
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -102,7 +102,7 @@ pairing_kickoff(struct evhttp_request *req)
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
pairing_get(struct evbuffer *evbuf)
|
jsonapi_reply_pairing_get(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
char *remote_name;
|
char *remote_name;
|
||||||
json_object *jreply;
|
json_object *jreply;
|
||||||
@ -121,12 +121,12 @@ pairing_get(struct evbuffer *evbuf)
|
|||||||
json_object_object_add(jreply, "active", json_object_new_boolean(false));
|
json_object_object_add(jreply, "active", json_object_new_boolean(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK_ERRNO(L_WEB, evbuffer_add_printf(evbuf, "%s", json_object_to_json_string(jreply)));
|
CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->reply, "%s", json_object_to_json_string(jreply)));
|
||||||
|
|
||||||
jparse_free(jreply);
|
jparse_free(jreply);
|
||||||
free(remote_name);
|
free(remote_name);
|
||||||
|
|
||||||
return 0;
|
return HTTP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ jsonapi_reply_config(struct httpd_request *hreq)
|
|||||||
|
|
||||||
jparse_free(jreply);
|
jparse_free(jreply);
|
||||||
|
|
||||||
return 0;
|
return HTTP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -212,7 +212,7 @@ jsonapi_reply_library(struct httpd_request *hreq)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "library: failed to get file count info\n");
|
DPRINTF(E_LOG, L_WEB, "library: failed to get file count info\n");
|
||||||
return -1;
|
return HTTP_INTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
artists = db_files_get_artist_count();
|
artists = db_files_get_artist_count();
|
||||||
@ -233,7 +233,7 @@ jsonapi_reply_library(struct httpd_request *hreq)
|
|||||||
|
|
||||||
jparse_free(jreply);
|
jparse_free(jreply);
|
||||||
|
|
||||||
return 0;
|
return HTTP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -243,7 +243,7 @@ static int
|
|||||||
jsonapi_reply_update(struct httpd_request *hreq)
|
jsonapi_reply_update(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
library_rescan();
|
library_rescan();
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -279,7 +279,7 @@ jsonapi_reply_spotify(struct httpd_request *hreq)
|
|||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Cannot display Spotify oauth interface (http_form_uriencode() failed)\n");
|
DPRINTF(E_LOG, L_WEB, "Cannot display Spotify oauth interface (http_form_uriencode() failed)\n");
|
||||||
jparse_free(jreply);
|
jparse_free(jreply);
|
||||||
return -1;
|
return HTTP_INTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
json_object_object_add(jreply, "oauth_uri", json_object_new_string(oauth_uri));
|
json_object_object_add(jreply, "oauth_uri", json_object_new_string(oauth_uri));
|
||||||
@ -300,7 +300,7 @@ jsonapi_reply_spotify(struct httpd_request *hreq)
|
|||||||
|
|
||||||
jparse_free(jreply);
|
jparse_free(jreply);
|
||||||
|
|
||||||
return 0;
|
return HTTP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -324,7 +324,7 @@ jsonapi_reply_spotify_login(struct httpd_request *hreq)
|
|||||||
if (!request)
|
if (!request)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
||||||
return -1;
|
return HTTP_BADREQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK_NULL(L_WEB, jreply = json_object_new_object());
|
CHECK_NULL(L_WEB, jreply = json_object_new_object());
|
||||||
@ -368,24 +368,7 @@ jsonapi_reply_spotify_login(struct httpd_request *hreq)
|
|||||||
DPRINTF(E_LOG, L_WEB, "Received spotify login request but was not compiled with enable-spotify\n");
|
DPRINTF(E_LOG, L_WEB, "Received spotify login request but was not compiled with enable-spotify\n");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return 0;
|
return HTTP_OK;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Endpoint to pair daap/dacp client
|
|
||||||
*
|
|
||||||
* If request is a GET request, returns information about active pairing remote.
|
|
||||||
* If request is a POST request, tries to pair the active remote with the given pin.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
jsonapi_reply_pairing(struct httpd_request *hreq)
|
|
||||||
{
|
|
||||||
if (evhttp_request_get_command(hreq->req) == EVHTTP_REQ_POST)
|
|
||||||
{
|
|
||||||
return pairing_kickoff(hreq->req);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pairing_get(hreq->reply);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -409,7 +392,7 @@ jsonapi_reply_lastfm(struct httpd_request *hreq)
|
|||||||
|
|
||||||
jparse_free(jreply);
|
jparse_free(jreply);
|
||||||
|
|
||||||
return 0;
|
return HTTP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -435,7 +418,7 @@ jsonapi_reply_lastfm_login(struct httpd_request *hreq)
|
|||||||
if (!request)
|
if (!request)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
||||||
return -1;
|
return HTTP_BADREQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK_NULL(L_WEB, jreply = json_object_new_object());
|
CHECK_NULL(L_WEB, jreply = json_object_new_object());
|
||||||
@ -482,7 +465,7 @@ jsonapi_reply_lastfm_login(struct httpd_request *hreq)
|
|||||||
DPRINTF(E_LOG, L_WEB, "Received LastFM login request but was not compiled with enable-lastfm\n");
|
DPRINTF(E_LOG, L_WEB, "Received LastFM login request but was not compiled with enable-lastfm\n");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return 0;
|
return HTTP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -491,17 +474,21 @@ jsonapi_reply_lastfm_logout(struct httpd_request *hreq)
|
|||||||
#ifdef LASTFM
|
#ifdef LASTFM
|
||||||
lastfm_logout();
|
lastfm_logout();
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
struct outputs_param
|
||||||
speaker_enum_cb(struct spk_info *spk, void *arg)
|
{
|
||||||
|
json_object *output;
|
||||||
|
uint64_t output_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
static json_object *
|
||||||
|
speaker_to_json(struct spk_info *spk)
|
||||||
{
|
{
|
||||||
json_object *outputs;
|
|
||||||
json_object *output;
|
json_object *output;
|
||||||
char output_id[21];
|
char output_id[21];
|
||||||
|
|
||||||
outputs = arg;
|
|
||||||
output = json_object_new_object();
|
output = json_object_new_object();
|
||||||
|
|
||||||
snprintf(output_id, sizeof(output_id), "%" PRIu64, spk->id);
|
snprintf(output_id, sizeof(output_id), "%" PRIu64, spk->id);
|
||||||
@ -514,14 +501,132 @@ speaker_enum_cb(struct spk_info *spk, void *arg)
|
|||||||
json_object_object_add(output, "needs_auth_key", json_object_new_boolean(spk->needs_auth_key));
|
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, "volume", json_object_new_int(spk->absvol));
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
speaker_enum_cb(struct spk_info *spk, void *arg)
|
||||||
|
{
|
||||||
|
json_object *outputs;
|
||||||
|
json_object *output;
|
||||||
|
|
||||||
|
outputs = arg;
|
||||||
|
|
||||||
|
output = speaker_to_json(spk);
|
||||||
json_object_array_add(outputs, output);
|
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;
|
||||||
|
uint64_t output_id;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = safe_atou64(hreq->uri_parsed->path_parts[2], &output_id);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->uri_parsed->path);
|
||||||
|
|
||||||
|
return HTTP_BADREQUEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs_param.output_id = output_id;
|
||||||
|
outputs_param.output = NULL;
|
||||||
|
|
||||||
|
player_speaker_enumerate(speaker_get_cb, &outputs_param);
|
||||||
|
|
||||||
|
if (!outputs_param.output)
|
||||||
|
{
|
||||||
|
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)));
|
||||||
|
|
||||||
|
jparse_free(outputs_param.output);
|
||||||
|
|
||||||
|
return HTTP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PUT /api/outputs/[output_id]
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
jsonapi_reply_outputs_put_byid(struct httpd_request *hreq)
|
||||||
|
{
|
||||||
|
uint64_t output_id;
|
||||||
|
struct evbuffer *in_evbuf;
|
||||||
|
json_object* request;
|
||||||
|
bool selected;
|
||||||
|
int volume;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = safe_atou64(hreq->uri_parsed->path_parts[2], &output_id);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_WEB, "No valid output id given to outputs endpoint '%s'\n", hreq->uri_parsed->path);
|
||||||
|
|
||||||
|
return HTTP_BADREQUEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
in_evbuf = evhttp_request_get_input_buffer(hreq->req);
|
||||||
|
request = jparse_obj_from_evbuffer(in_evbuf);
|
||||||
|
if (!request)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
||||||
|
|
||||||
|
return HTTP_BADREQUEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
if (jparse_contains_key(request, "selected", json_type_boolean))
|
||||||
|
{
|
||||||
|
selected = jparse_bool_from_obj(request, "selected");
|
||||||
|
if (selected)
|
||||||
|
ret = player_speaker_enable(output_id);
|
||||||
|
else
|
||||||
|
ret = player_speaker_disable(output_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == 0 && jparse_contains_key(request, "volume", json_type_int))
|
||||||
|
{
|
||||||
|
volume = jparse_int_from_obj(request, "volume");
|
||||||
|
ret = player_volume_setabs_speaker(output_id, volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
jparse_free(request);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
return HTTP_INTERNAL;
|
||||||
|
|
||||||
|
return HTTP_NOCONTENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Endpoint "/api/outputs"
|
||||||
|
*/
|
||||||
static int
|
static int
|
||||||
jsonapi_reply_outputs(struct httpd_request *hreq)
|
jsonapi_reply_outputs(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
json_object *jreply;
|
|
||||||
json_object *outputs;
|
json_object *outputs;
|
||||||
|
json_object *jreply;
|
||||||
|
|
||||||
outputs = json_object_new_array();
|
outputs = json_object_new_array();
|
||||||
|
|
||||||
@ -534,7 +639,7 @@ jsonapi_reply_outputs(struct httpd_request *hreq)
|
|||||||
|
|
||||||
jparse_free(jreply);
|
jparse_free(jreply);
|
||||||
|
|
||||||
return 0;
|
return HTTP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -544,18 +649,12 @@ jsonapi_reply_verification(struct httpd_request *hreq)
|
|||||||
json_object* request;
|
json_object* request;
|
||||||
const char* message;
|
const char* message;
|
||||||
|
|
||||||
if (evhttp_request_get_command(hreq->req) != EVHTTP_REQ_POST)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_WEB, "Verification: request is not a POST request\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
in_evbuf = evhttp_request_get_input_buffer(hreq->req);
|
in_evbuf = evhttp_request_get_input_buffer(hreq->req);
|
||||||
request = jparse_obj_from_evbuffer(in_evbuf);
|
request = jparse_obj_from_evbuffer(in_evbuf);
|
||||||
if (!request)
|
if (!request)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
||||||
return -1;
|
return HTTP_BADREQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_WEB, "Received verification post request: %s\n", json_object_to_json_string(request));
|
DPRINTF(E_DBG, L_WEB, "Received verification post request: %s\n", json_object_to_json_string(request));
|
||||||
@ -568,11 +667,11 @@ jsonapi_reply_verification(struct httpd_request *hreq)
|
|||||||
|
|
||||||
jparse_free(request);
|
jparse_free(request);
|
||||||
|
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
jsonapi_reply_select_outputs(struct httpd_request *hreq)
|
jsonapi_reply_outputs_set(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
struct evbuffer *in_evbuf;
|
struct evbuffer *in_evbuf;
|
||||||
json_object *request;
|
json_object *request;
|
||||||
@ -581,18 +680,12 @@ jsonapi_reply_select_outputs(struct httpd_request *hreq)
|
|||||||
int nspk, i, ret;
|
int nspk, i, ret;
|
||||||
uint64_t *ids;
|
uint64_t *ids;
|
||||||
|
|
||||||
if (evhttp_request_get_command(hreq->req) != EVHTTP_REQ_POST)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_WEB, "Select outputs: request is not a POST request\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
in_evbuf = evhttp_request_get_input_buffer(hreq->req);
|
in_evbuf = evhttp_request_get_input_buffer(hreq->req);
|
||||||
request = jparse_obj_from_evbuffer(in_evbuf);
|
request = jparse_obj_from_evbuffer(in_evbuf);
|
||||||
if (!request)
|
if (!request)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
|
||||||
return -1;
|
return HTTP_BADREQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_WEB, "Received select-outputs post request: %s\n", json_object_to_json_string(request));
|
DPRINTF(E_DBG, L_WEB, "Received select-outputs post request: %s\n", json_object_to_json_string(request));
|
||||||
@ -627,7 +720,7 @@ jsonapi_reply_select_outputs(struct httpd_request *hreq)
|
|||||||
|
|
||||||
jparse_free(request);
|
jparse_free(request);
|
||||||
|
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -639,10 +732,10 @@ jsonapi_reply_player_play(struct httpd_request *hreq)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Error starting playback.\n");
|
DPRINTF(E_LOG, L_WEB, "Error starting playback.\n");
|
||||||
return -1;
|
return HTTP_INTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -654,10 +747,10 @@ jsonapi_reply_player_pause(struct httpd_request *hreq)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Error pausing playback.\n");
|
DPRINTF(E_LOG, L_WEB, "Error pausing playback.\n");
|
||||||
return -1;
|
return HTTP_INTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -669,10 +762,10 @@ jsonapi_reply_player_stop(struct httpd_request *hreq)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Error stopping playback.\n");
|
DPRINTF(E_LOG, L_WEB, "Error stopping playback.\n");
|
||||||
return -1;
|
return HTTP_INTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -684,17 +777,17 @@ jsonapi_reply_player_next(struct httpd_request *hreq)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Error switching to next item.\n");
|
DPRINTF(E_LOG, L_WEB, "Error switching to next item.\n");
|
||||||
return -1;
|
return HTTP_INTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = player_playback_start();
|
ret = player_playback_start();
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Error starting playback after switching to next item.\n");
|
DPRINTF(E_LOG, L_WEB, "Error starting playback after switching to next item.\n");
|
||||||
return -1;
|
return HTTP_INTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -706,17 +799,17 @@ jsonapi_reply_player_previous(struct httpd_request *hreq)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Error switching to previous item.\n");
|
DPRINTF(E_LOG, L_WEB, "Error switching to previous item.\n");
|
||||||
return -1;
|
return HTTP_INTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = player_playback_start();
|
ret = player_playback_start();
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_WEB, "Error starting playback after switching to previous item.\n");
|
DPRINTF(E_LOG, L_WEB, "Error starting playback after switching to previous item.\n");
|
||||||
return -1;
|
return HTTP_INTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -771,7 +864,7 @@ jsonapi_reply_player(struct httpd_request *hreq)
|
|||||||
|
|
||||||
jparse_free(reply);
|
jparse_free(reply);
|
||||||
|
|
||||||
return 0;
|
return HTTP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static json_object *
|
static json_object *
|
||||||
@ -882,7 +975,10 @@ jsonapi_reply_queue(struct httpd_request *hreq)
|
|||||||
jparse_free(reply);
|
jparse_free(reply);
|
||||||
free(query_params.filter);
|
free(query_params.filter);
|
||||||
|
|
||||||
return ret;
|
if (ret < 0)
|
||||||
|
return HTTP_INTERNAL;
|
||||||
|
|
||||||
|
return HTTP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -892,7 +988,7 @@ jsonapi_reply_player_repeat(struct httpd_request *hreq)
|
|||||||
|
|
||||||
param = evhttp_find_header(hreq->query, "state");
|
param = evhttp_find_header(hreq->query, "state");
|
||||||
if (!param)
|
if (!param)
|
||||||
return -1;
|
return HTTP_BADREQUEST;
|
||||||
|
|
||||||
if (strcmp(param, "single") == 0)
|
if (strcmp(param, "single") == 0)
|
||||||
{
|
{
|
||||||
@ -907,7 +1003,7 @@ jsonapi_reply_player_repeat(struct httpd_request *hreq)
|
|||||||
player_repeat_set(REPEAT_OFF);
|
player_repeat_set(REPEAT_OFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -918,12 +1014,12 @@ jsonapi_reply_player_shuffle(struct httpd_request *hreq)
|
|||||||
|
|
||||||
param = evhttp_find_header(hreq->query, "state");
|
param = evhttp_find_header(hreq->query, "state");
|
||||||
if (!param)
|
if (!param)
|
||||||
return -1;
|
return HTTP_BADREQUEST;
|
||||||
|
|
||||||
shuffle = (strcmp(param, "true") == 0);
|
shuffle = (strcmp(param, "true") == 0);
|
||||||
player_shuffle_set(shuffle);
|
player_shuffle_set(shuffle);
|
||||||
|
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -934,12 +1030,12 @@ jsonapi_reply_player_consume(struct httpd_request *hreq)
|
|||||||
|
|
||||||
param = evhttp_find_header(hreq->query, "state");
|
param = evhttp_find_header(hreq->query, "state");
|
||||||
if (!param)
|
if (!param)
|
||||||
return -1;
|
return HTTP_BADREQUEST;
|
||||||
|
|
||||||
consume = (strcmp(param, "true") == 0);
|
consume = (strcmp(param, "true") == 0);
|
||||||
player_consume_set(consume);
|
player_consume_set(consume);
|
||||||
|
|
||||||
return 0;
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -952,21 +1048,21 @@ jsonapi_reply_player_volume(struct httpd_request *hreq)
|
|||||||
|
|
||||||
param = evhttp_find_header(hreq->query, "volume");
|
param = evhttp_find_header(hreq->query, "volume");
|
||||||
if (!param)
|
if (!param)
|
||||||
return -1;
|
return HTTP_BADREQUEST;
|
||||||
|
|
||||||
ret = safe_atoi32(param, &volume);
|
ret = safe_atoi32(param, &volume);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return -1;
|
return HTTP_BADREQUEST;
|
||||||
|
|
||||||
if (volume < 0 || volume > 100)
|
if (volume < 0 || volume > 100)
|
||||||
return -1;
|
return HTTP_BADREQUEST;
|
||||||
|
|
||||||
param = evhttp_find_header(hreq->query, "output_id");
|
param = evhttp_find_header(hreq->query, "output_id");
|
||||||
if (param)
|
if (param)
|
||||||
{
|
{
|
||||||
ret = safe_atou64(param, &output_id);
|
ret = safe_atou64(param, &output_id);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return -1;
|
return HTTP_BADREQUEST;
|
||||||
|
|
||||||
ret = player_volume_setabs_speaker(output_id, volume);
|
ret = player_volume_setabs_speaker(output_id, volume);
|
||||||
}
|
}
|
||||||
@ -975,35 +1071,46 @@ jsonapi_reply_player_volume(struct httpd_request *hreq)
|
|||||||
ret = player_volume_set(volume);
|
ret = player_volume_set(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
if (ret < 0)
|
||||||
|
return HTTP_INTERNAL;
|
||||||
|
|
||||||
|
return HTTP_NOCONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct httpd_uri_map adm_handlers[] =
|
static struct httpd_uri_map adm_handlers[] =
|
||||||
{
|
{
|
||||||
{ .regexp = "^/api/config", .handler = jsonapi_reply_config },
|
{ EVHTTP_REQ_GET, "^/api/config$", jsonapi_reply_config },
|
||||||
{ .regexp = "^/api/library", .handler = jsonapi_reply_library },
|
{ EVHTTP_REQ_GET, "^/api/library$", jsonapi_reply_library },
|
||||||
{ .regexp = "^/api/update", .handler = jsonapi_reply_update },
|
{ EVHTTP_REQ_GET, "^/api/update$", jsonapi_reply_update },
|
||||||
{ .regexp = "^/api/spotify-login", .handler = jsonapi_reply_spotify_login },
|
{ EVHTTP_REQ_POST, "^/api/spotify-login$", jsonapi_reply_spotify_login },
|
||||||
{ .regexp = "^/api/spotify", .handler = jsonapi_reply_spotify },
|
{ EVHTTP_REQ_GET, "^/api/spotify$", jsonapi_reply_spotify },
|
||||||
{ .regexp = "^/api/pairing", .handler = jsonapi_reply_pairing },
|
{ EVHTTP_REQ_GET, "^/api/pairing$", jsonapi_reply_pairing_get },
|
||||||
{ .regexp = "^/api/lastfm-login", .handler = jsonapi_reply_lastfm_login },
|
{ EVHTTP_REQ_POST, "^/api/pairing$", jsonapi_reply_pairing_kickoff },
|
||||||
{ .regexp = "^/api/lastfm-logout", .handler = jsonapi_reply_lastfm_logout },
|
{ EVHTTP_REQ_POST, "^/api/lastfm-login$", jsonapi_reply_lastfm_login },
|
||||||
{ .regexp = "^/api/lastfm", .handler = jsonapi_reply_lastfm },
|
{ EVHTTP_REQ_GET, "^/api/lastfm-logout$", jsonapi_reply_lastfm_logout },
|
||||||
{ .regexp = "^/api/outputs", .handler = jsonapi_reply_outputs },
|
{ EVHTTP_REQ_GET, "^/api/lastfm$", jsonapi_reply_lastfm },
|
||||||
{ .regexp = "^/api/select-outputs", .handler = jsonapi_reply_select_outputs },
|
{ EVHTTP_REQ_POST, "^/api/verification$", jsonapi_reply_verification },
|
||||||
{ .regexp = "^/api/verification", .handler = jsonapi_reply_verification },
|
|
||||||
{ .regexp = "^/api/player/play", .handler = jsonapi_reply_player_play },
|
{ EVHTTP_REQ_GET, "^/api/outputs$", jsonapi_reply_outputs },
|
||||||
{ .regexp = "^/api/player/pause", .handler = jsonapi_reply_player_pause },
|
{ EVHTTP_REQ_PUT, "^/api/outputs/set$", jsonapi_reply_outputs_set },
|
||||||
{ .regexp = "^/api/player/stop", .handler = jsonapi_reply_player_stop },
|
{ EVHTTP_REQ_POST, "^/api/select-outputs$", jsonapi_reply_outputs_set }, // deprecated: use "/api/outputs/set"
|
||||||
{ .regexp = "^/api/player/next", .handler = jsonapi_reply_player_next },
|
{ EVHTTP_REQ_GET, "^/api/outputs/[[:digit:]]+$", jsonapi_reply_outputs_get_byid },
|
||||||
{ .regexp = "^/api/player/previous", .handler = jsonapi_reply_player_previous },
|
{ EVHTTP_REQ_PUT, "^/api/outputs/[[:digit:]]+$", jsonapi_reply_outputs_put_byid },
|
||||||
{ .regexp = "^/api/player/shuffle", .handler = jsonapi_reply_player_shuffle },
|
|
||||||
{ .regexp = "^/api/player/repeat", .handler = jsonapi_reply_player_repeat },
|
{ EVHTTP_REQ_PUT, "^/api/player/play$", jsonapi_reply_player_play },
|
||||||
{ .regexp = "^/api/player/consume", .handler = jsonapi_reply_player_consume },
|
{ EVHTTP_REQ_PUT, "^/api/player/pause$", jsonapi_reply_player_pause },
|
||||||
{ .regexp = "^/api/player/volume", .handler = jsonapi_reply_player_volume },
|
{ EVHTTP_REQ_PUT, "^/api/player/stop$", jsonapi_reply_player_stop },
|
||||||
{ .regexp = "^/api/player", .handler = jsonapi_reply_player },
|
{ EVHTTP_REQ_PUT, "^/api/player/next$", jsonapi_reply_player_next },
|
||||||
{ .regexp = "^/api/queue", .handler = jsonapi_reply_queue },
|
{ EVHTTP_REQ_PUT, "^/api/player/previous$", jsonapi_reply_player_previous },
|
||||||
{ .regexp = NULL, .handler = NULL }
|
{ EVHTTP_REQ_PUT, "^/api/player/shuffle$", jsonapi_reply_player_shuffle },
|
||||||
|
{ EVHTTP_REQ_PUT, "^/api/player/repeat$", jsonapi_reply_player_repeat },
|
||||||
|
{ EVHTTP_REQ_PUT, "^/api/player/consume$", jsonapi_reply_player_consume },
|
||||||
|
{ EVHTTP_REQ_PUT, "^/api/player/volume$", jsonapi_reply_player_volume },
|
||||||
|
{ EVHTTP_REQ_GET, "^/api/player$", jsonapi_reply_player },
|
||||||
|
|
||||||
|
{ EVHTTP_REQ_GET, "^/api/queue$", jsonapi_reply_queue },
|
||||||
|
|
||||||
|
{ 0, NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -1014,7 +1121,7 @@ jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
|
|||||||
{
|
{
|
||||||
struct httpd_request *hreq;
|
struct httpd_request *hreq;
|
||||||
struct evkeyvalq *headers;
|
struct evkeyvalq *headers;
|
||||||
int ret;
|
int status_code;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_WEB, "JSON api request: '%s'\n", uri_parsed->uri);
|
DPRINTF(E_DBG, L_WEB, "JSON api request: '%s'\n", uri_parsed->uri);
|
||||||
|
|
||||||
@ -1030,23 +1137,33 @@ jsonapi_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
headers = evhttp_request_get_output_headers(req);
|
|
||||||
evhttp_add_header(headers, "DAAP-Server", "forked-daapd/" VERSION);
|
|
||||||
|
|
||||||
CHECK_NULL(L_WEB, hreq->reply = evbuffer_new());
|
CHECK_NULL(L_WEB, hreq->reply = evbuffer_new());
|
||||||
|
|
||||||
ret = hreq->handler(hreq);
|
status_code = hreq->handler(hreq);
|
||||||
if (ret < 0)
|
|
||||||
|
switch (status_code)
|
||||||
{
|
{
|
||||||
httpd_send_error(req, 500, "Internal Server Error");
|
case HTTP_OK: /* 200 OK */
|
||||||
goto error;
|
headers = evhttp_request_get_output_headers(req);
|
||||||
|
evhttp_add_header(headers, "Content-Type", "application/json");
|
||||||
|
httpd_send_reply(req, status_code, "OK", hreq->reply, 0);
|
||||||
|
break;
|
||||||
|
case HTTP_NOCONTENT: /* 204 No Content */
|
||||||
|
httpd_send_reply(req, status_code, "No Content", hreq->reply, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HTTP_BADREQUEST: /* 400 Bad Request */
|
||||||
|
httpd_send_error(req, status_code, "Bad Request");
|
||||||
|
break;
|
||||||
|
case HTTP_NOTFOUND: /* 404 Not Found */
|
||||||
|
httpd_send_error(req, status_code, "Not Found");
|
||||||
|
break;
|
||||||
|
case HTTP_INTERNAL: /* 500 Internal Server Error */
|
||||||
|
default:
|
||||||
|
httpd_send_error(req, HTTP_INTERNAL, "Internal Server Error");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
evhttp_add_header(headers, "Content-Type", "application/json");
|
|
||||||
|
|
||||||
httpd_send_reply(req, HTTP_OK, "OK", hreq->reply, 0);
|
|
||||||
|
|
||||||
error:
|
|
||||||
evbuffer_free(hreq->reply);
|
evbuffer_free(hreq->reply);
|
||||||
free(hreq);
|
free(hreq);
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,14 @@ jparse_free(json_object* haystack)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
jparse_contains_key(json_object *haystack, const char *key, json_type type)
|
||||||
|
{
|
||||||
|
json_object *needle;
|
||||||
|
|
||||||
|
return json_object_object_get_ex(haystack, key, &needle) && json_object_get_type(needle) == type;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
jparse_array_from_obj(json_object *haystack, const char *key, json_object **needle)
|
jparse_array_from_obj(json_object *haystack, const char *key, json_object **needle)
|
||||||
{
|
{
|
||||||
|
@ -36,6 +36,9 @@
|
|||||||
void
|
void
|
||||||
jparse_free(json_object *haystack);
|
jparse_free(json_object *haystack);
|
||||||
|
|
||||||
|
bool
|
||||||
|
jparse_contains_key(json_object *haystack, const char *key, json_type type);
|
||||||
|
|
||||||
int
|
int
|
||||||
jparse_array_from_obj(json_object *haystack, const char *key, json_object **needle);
|
jparse_array_from_obj(json_object *haystack, const char *key, json_object **needle);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user