mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-25 22:55:56 -05:00
Merge pull request #499 from chme/jsonapi
New endpoints for the JSON api (library, queue)
This commit is contained in:
commit
ea4ea072cd
@ -4,9 +4,19 @@ Available API endpoints:
|
|||||||
|
|
||||||
* [Player](#player): control playback, volume, shuffle/repeat modes
|
* [Player](#player): control playback, volume, shuffle/repeat modes
|
||||||
* [Outputs / Speakers](#outputs--speakers): list available outputs and enable/disable outputs
|
* [Outputs / Speakers](#outputs--speakers): list available outputs and enable/disable outputs
|
||||||
|
* [Queue](#queue): list, add or modify the current queue
|
||||||
|
* [Library](#library): list playlists, artists, albums and tracks from your library
|
||||||
* [Server info](#server-info): get server information
|
* [Server info](#server-info): get server information
|
||||||
* [Push notifications](#push-notifications): receive push notifications
|
* [Push notifications](#push-notifications): receive push notifications
|
||||||
|
|
||||||
|
JSON-Object model:
|
||||||
|
|
||||||
|
* [Queue item](#queue-item-object)
|
||||||
|
* [Playlist](#playlist-object)
|
||||||
|
* [Artist](#artist-object)
|
||||||
|
* [Album](#album-object)
|
||||||
|
* [Track](#track-object)
|
||||||
|
|
||||||
## Player
|
## Player
|
||||||
|
|
||||||
| Method | Endpoint | Description |
|
| Method | Endpoint | Description |
|
||||||
@ -429,6 +439,574 @@ On success returns the HTTP `204 No Content` success status response code.
|
|||||||
curl -X PUT "http://localhost:3689/api/outputs/0" --data "{\"selected\":true, \"volume\": 50}"
|
curl -X PUT "http://localhost:3689/api/outputs/0" --data "{\"selected\":true, \"volume\": 50}"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Queue
|
||||||
|
|
||||||
|
| Method | Endpoint | Description |
|
||||||
|
| --------- | ----------------------------------------------------------- | ------------------------------------ |
|
||||||
|
| GET | [/api/queue](#list-queue-items) | Get a list of queue items |
|
||||||
|
| PUT | [/api/queue/clear](#clearing-the-queue) | Remove all items from the queue |
|
||||||
|
| POST | [/api/queue/items/add](#adding-items-to-the-queue) | Add items to the queue |
|
||||||
|
| PUT | [/api/queue/items/{id}](#moving-a-queue-item) | Move a queue item in the queue |
|
||||||
|
| DELETE | [/api/queue/items/{id}](#removing-a-queue-item) | Remove a queue item form the queue |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### List queue items
|
||||||
|
|
||||||
|
Lists the items in the current queue
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/queue
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| id | *(Optional)* If a queue item id is given, only the item with the id will be returend. |
|
||||||
|
| start | *(Optional)* If a `start`and an `end` position is given, only the items from `start` (included) to `end` (excluded) will be returned. If only a `start` position is given, only the item at this position will be returned. |
|
||||||
|
| end | *(Optional)* See `start` parameter |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| version | integer | Version number of the current queue |
|
||||||
|
| count | integer | Number of items in the current queue |
|
||||||
|
| items | array | Array of [`queue item`](#queue-item-object) objects |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X GET "http://localhost:3689/api/queue"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"version": 833,
|
||||||
|
"count": 20,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": 12122,
|
||||||
|
"position": 0,
|
||||||
|
"track_id": 10749,
|
||||||
|
"title": "Angels",
|
||||||
|
"artist": "The xx",
|
||||||
|
"artist_sort": "xx, The",
|
||||||
|
"album": "Coexist",
|
||||||
|
"album_sort": "Coexist",
|
||||||
|
"albumartist": "The xx",
|
||||||
|
"albumartist_sort": "xx, The",
|
||||||
|
"genre": "Indie Rock",
|
||||||
|
"year": 2012,
|
||||||
|
"track_number": 1,
|
||||||
|
"disc_number": 1,
|
||||||
|
"length_ms": 171735,
|
||||||
|
"media_kind": "music",
|
||||||
|
"data_kind": "file",
|
||||||
|
"path": "/home/asiate/MusicTest/The xx/Coexist/01 Angels.mp3",
|
||||||
|
"uri": "library:track:10749"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Clearing the queue
|
||||||
|
|
||||||
|
Remove all items form the current queue
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/queue/clear
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `204 No Content` success status response code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/queue/clear"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Adding items to the queue
|
||||||
|
|
||||||
|
Add tracks, playlists artists or albums to the current queue
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/queue/items/add
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| uris | Comma seperated list of resource identifiers (`track`, `playlist`, `artist` or `album` object `uri`) |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `204 No Content` success status response code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X POST "http://localhost:3689/api/queue/items/add?uris=library:playlist:68,library:artist:2932599850102967727"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Moving a queue item
|
||||||
|
|
||||||
|
Move a queue item in the current queue
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /api/queue/items/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Path parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | -------------------- |
|
||||||
|
| id | Queue item id |
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| new_position | The new position for the queue item in the current queue. |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `204 No Content` success status response code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/queue/items/3?new_position=0"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Removing a queue item
|
||||||
|
|
||||||
|
Remove a queue item from the current queue
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /api/queue/items/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Path parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | -------------------- |
|
||||||
|
| id | Queue item id |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
On success returns the HTTP `204 No Content` success status response code.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X PUT "http://localhost:3689/api/queue/items/2"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Library
|
||||||
|
|
||||||
|
| Method | Endpoint | Description |
|
||||||
|
| --------- | ----------------------------------------------------------- | ------------------------------------ |
|
||||||
|
| GET | [/api/library/playlists](#list-playlists) | Get a list of playlists |
|
||||||
|
| GET | [/api/library/playlists/{id}/tracks](#list-playlist-tracks) | Get list of tracks for a playlist |
|
||||||
|
| GET | [/api/library/artists](#list-artists) | Get a list of artists |
|
||||||
|
| GET | [/api/library/artists/{id}/albums](#list-artist-albums) | Get list of albums for an artist |
|
||||||
|
| GET | [/api/library/albums](#list-albums) | Get a list of albums |
|
||||||
|
| GET | [/api/library/albums/{id}/tracks](#list-album-tracks) | Get list of tracks for an album |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### List playlists
|
||||||
|
|
||||||
|
Lists the playlists in your library
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/library/playlists
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| offset | *(Optional)* Offset of the first playlist to return |
|
||||||
|
| limit | *(Optional)* Maximum number of playlists to return |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| items | array | Array of [`playlist`](#playlist-object) objects |
|
||||||
|
| total | integer | Total number of playlists in the library |
|
||||||
|
| offset | integer | Requested offset of the first playlist |
|
||||||
|
| limit | integer | Requested maximum number of playlists |
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X GET "http://localhost:3689/api/library/playlists"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "radio",
|
||||||
|
"path": "/music/srv/radio.m3u",
|
||||||
|
"smart_playlist": false,
|
||||||
|
"uri": "library:playlist:1"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"total": 20,
|
||||||
|
"offset": 0,
|
||||||
|
"limit": -1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### List playlist tracks
|
||||||
|
|
||||||
|
Lists the tracks in a playlists
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/library/playlists/{id}/tracks
|
||||||
|
```
|
||||||
|
|
||||||
|
**Path parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | -------------------- |
|
||||||
|
| id | Playlist id |
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| offset | *(Optional)* Offset of the first track to return |
|
||||||
|
| limit | *(Optional)* Maximum number of tracks to return |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| items | array | Array of [`track`](#track-object) objects |
|
||||||
|
| total | integer | Total number of tracks in the playlist |
|
||||||
|
| offset | integer | Requested offset of the first track |
|
||||||
|
| limit | integer | Requested maximum number of tracks |
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X GET "http://localhost:3689/api/library/playlists/1/tracks"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": 10766,
|
||||||
|
"title": "Solange wir tanzen",
|
||||||
|
"artist": "Heinrich",
|
||||||
|
"artist_sort": "Heinrich",
|
||||||
|
"album": "Solange wir tanzen",
|
||||||
|
"album_sort": "Solange wir tanzen",
|
||||||
|
"albumartist": "Heinrich",
|
||||||
|
"albumartist_sort": "Heinrich",
|
||||||
|
"genre": "Electronica",
|
||||||
|
"year": 2014,
|
||||||
|
"track_number": 1,
|
||||||
|
"disc_number": 1,
|
||||||
|
"length_ms": 223085,
|
||||||
|
"play_count": 2,
|
||||||
|
"last_time_played": "2018-02-23T10:31:20Z",
|
||||||
|
"media_kind": "music",
|
||||||
|
"data_kind": "file",
|
||||||
|
"path": "/music/srv/Heinrich/Solange wir tanzen/01 Solange wir tanzen.mp3",
|
||||||
|
"uri": "library:track:10766"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"total": 20,
|
||||||
|
"offset": 0,
|
||||||
|
"limit": -1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### List artists
|
||||||
|
|
||||||
|
Lists the artists in your library
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/library/artists
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| offset | *(Optional)* Offset of the first artist to return |
|
||||||
|
| limit | *(Optional)* Maximum number of artists to return |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ------------------------------------------- |
|
||||||
|
| items | array | Array of [`artist`](#artist-object) objects |
|
||||||
|
| total | integer | Total number of artists in the library |
|
||||||
|
| offset | integer | Requested offset of the first artist |
|
||||||
|
| limit | integer | Requested maximum number of artists |
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X GET "http://localhost:3689/api/library/artists"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "3815427709949443149",
|
||||||
|
"name": "ABAY",
|
||||||
|
"name_sort": "ABAY",
|
||||||
|
"album_count": 1,
|
||||||
|
"track_count": 10,
|
||||||
|
"length_ms": 2951554,
|
||||||
|
"uri": "library:artist:3815427709949443149"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"total": 20,
|
||||||
|
"offset": 0,
|
||||||
|
"limit": -1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### List artist albums
|
||||||
|
|
||||||
|
Lists the albums of an artist
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/library/artists/{id}/albums
|
||||||
|
```
|
||||||
|
|
||||||
|
**Path parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | -------------------- |
|
||||||
|
| id | Artist id |
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| offset | *(Optional)* Offset of the first album to return |
|
||||||
|
| limit | *(Optional)* Maximum number of albums to return |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| items | array | Array of [`album`](#album-object) objects |
|
||||||
|
| total | integer | Total number of albums of this artist |
|
||||||
|
| offset | integer | Requested offset of the first album |
|
||||||
|
| limit | integer | Requested maximum number of albums |
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X GET "http://localhost:3689/api/library/artists/32561671101664759/albums"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "8009851123233197743",
|
||||||
|
"name": "Add Violence",
|
||||||
|
"name_sort": "Add Violence",
|
||||||
|
"artist": "Nine Inch Nails",
|
||||||
|
"artist_id": "32561671101664759",
|
||||||
|
"track_count": 5,
|
||||||
|
"length_ms": 1634961,
|
||||||
|
"uri": "library:album:8009851123233197743"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"total": 20,
|
||||||
|
"offset": 0,
|
||||||
|
"limit": -1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### List albums
|
||||||
|
|
||||||
|
Lists the albums in your library
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/library/albums
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| offset | *(Optional)* Offset of the first album to return |
|
||||||
|
| limit | *(Optional)* Maximum number of albums to return |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| items | array | Array of [`album`](#album-object) objects |
|
||||||
|
| total | integer | Total number of albums in the library |
|
||||||
|
| offset | integer | Requested offset of the first albums |
|
||||||
|
| limit | integer | Requested maximum number of albums |
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X GET "http://localhost:3689/api/library/albums"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "8009851123233197743",
|
||||||
|
"name": "Add Violence",
|
||||||
|
"name_sort": "Add Violence",
|
||||||
|
"artist": "Nine Inch Nails",
|
||||||
|
"artist_id": "32561671101664759",
|
||||||
|
"track_count": 5,
|
||||||
|
"length_ms": 1634961,
|
||||||
|
"uri": "library:album:8009851123233197743"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"total": 20,
|
||||||
|
"offset": 0,
|
||||||
|
"limit": -1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### List album tracks
|
||||||
|
|
||||||
|
Lists the tracks in an album
|
||||||
|
|
||||||
|
**Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/library/albums/{id}/tracks
|
||||||
|
```
|
||||||
|
|
||||||
|
**Path parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | -------------------- |
|
||||||
|
| id | Album id |
|
||||||
|
|
||||||
|
**Query parameters**
|
||||||
|
|
||||||
|
| Parameter | Value |
|
||||||
|
| --------------- | ----------------------------------------------------------- |
|
||||||
|
| offset | *(Optional)* Offset of the first track to return |
|
||||||
|
| limit | *(Optional)* Maximum number of tracks to return |
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| items | array | Array of [`track`](#track-object) objects |
|
||||||
|
| total | integer | Total number of tracks |
|
||||||
|
| offset | integer | Requested offset of the first track |
|
||||||
|
| limit | integer | Requested maximum number of tracks |
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X GET "http://localhost:3689/api/library/albums/1/tracks"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": 10766,
|
||||||
|
"title": "Solange wir tanzen",
|
||||||
|
"artist": "Heinrich",
|
||||||
|
"artist_sort": "Heinrich",
|
||||||
|
"album": "Solange wir tanzen",
|
||||||
|
"album_sort": "Solange wir tanzen",
|
||||||
|
"albumartist": "Heinrich",
|
||||||
|
"albumartist_sort": "Heinrich",
|
||||||
|
"genre": "Electronica",
|
||||||
|
"year": 2014,
|
||||||
|
"track_number": 1,
|
||||||
|
"disc_number": 1,
|
||||||
|
"length_ms": 223085,
|
||||||
|
"play_count": 2,
|
||||||
|
"last_time_played": "2018-02-23T10:31:20Z",
|
||||||
|
"media_kind": "music",
|
||||||
|
"data_kind": "file",
|
||||||
|
"path": "/music/srv/Heinrich/Solange wir tanzen/01 Solange wir tanzen.mp3",
|
||||||
|
"uri": "library:track:10766"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"total": 20,
|
||||||
|
"offset": 0,
|
||||||
|
"limit": -1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Server info
|
## Server info
|
||||||
|
|
||||||
| Method | Endpoint | Description |
|
| Method | Endpoint | Description |
|
||||||
@ -525,3 +1103,94 @@ curl --include \
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Object model
|
||||||
|
|
||||||
|
|
||||||
|
### `queue item` object
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| ------------------ | -------- | ----------------------------------------- |
|
||||||
|
| id | string | Item id |
|
||||||
|
| position | integer | Position in the queue (starting with zero) |
|
||||||
|
| track_id | string | Track id |
|
||||||
|
| title | string | Title |
|
||||||
|
| artist | string | Track artist name |
|
||||||
|
| artist_sort | string | Track artist sort name |
|
||||||
|
| album | string | Album name |
|
||||||
|
| album_sort | string | Album sort name |
|
||||||
|
| album_artist | string | Album artist name |
|
||||||
|
| album_artist_sort | string | Album artist sort name |
|
||||||
|
| genre | string | Genre |
|
||||||
|
| year | integer | Release year |
|
||||||
|
| track_number | integer | Track number |
|
||||||
|
| disc_number | integer | Disc number |
|
||||||
|
| length_ms | integer | Track length in milliseconds |
|
||||||
|
| media_kind | string | Media type of this track: `music`, `movie`, `podcast`, `audiobook`, `musicvideo`, `tvshow` |
|
||||||
|
| data_kind | string | Data type of this track: `file`, `url`, `spotify`, `pipe` |
|
||||||
|
| path | string | Path |
|
||||||
|
| uri | string | Resource identifier |
|
||||||
|
|
||||||
|
|
||||||
|
### `playlist` object
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| id | string | Playlist id |
|
||||||
|
| name | string | Playlist name |
|
||||||
|
| path | string | Path |
|
||||||
|
| smart_playlist | boolean | `true` if playlist is a smart playlist |
|
||||||
|
| uri | string | Resource identifier |
|
||||||
|
|
||||||
|
|
||||||
|
### `artist` object
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| id | string | Artist id |
|
||||||
|
| name | string | Artist name |
|
||||||
|
| name_sort | string | Artist sort name |
|
||||||
|
| album_count | integer | Number of albums |
|
||||||
|
| track_count | integer | Number of tracks |
|
||||||
|
| length_ms | integer | Total length of tracks in milliseconds |
|
||||||
|
| uri | string | Resource identifier |
|
||||||
|
|
||||||
|
|
||||||
|
### `album` object
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| --------------- | -------- | ----------------------------------------- |
|
||||||
|
| id | string | Album id |
|
||||||
|
| name | string | Album name |
|
||||||
|
| name_sort | string | Album sort name |
|
||||||
|
| artist_id | string | Album artist id |
|
||||||
|
| artist | string | Album artist name |
|
||||||
|
| track_count | integer | Number of tracks |
|
||||||
|
| length_ms | integer | Total length of tracks in milliseconds |
|
||||||
|
| uri | string | Resource identifier |
|
||||||
|
|
||||||
|
|
||||||
|
### `track` object
|
||||||
|
|
||||||
|
| Key | Type | Value |
|
||||||
|
| ------------------ | -------- | ----------------------------------------- |
|
||||||
|
| id | string | Track id |
|
||||||
|
| title | string | Title |
|
||||||
|
| artist | string | Track artist name |
|
||||||
|
| artist_sort | string | Track artist sort name |
|
||||||
|
| album | string | Album name |
|
||||||
|
| album_sort | string | Album sort name |
|
||||||
|
| album_artist | string | Album artist name |
|
||||||
|
| album_artist_sort | string | Album artist sort name |
|
||||||
|
| genre | string | Genre |
|
||||||
|
| year | integer | Release year |
|
||||||
|
| track_number | integer | Track number |
|
||||||
|
| disc_number | integer | Disc number |
|
||||||
|
| length_ms | integer | Track length in milliseconds |
|
||||||
|
| play_count | integer | How many times the track was played |
|
||||||
|
| time_played | string | The timestamp in `ISO 8601` format |
|
||||||
|
| media_kind | string | Media type of this track: `music`, `movie`, `podcast`, `audiobook`, `musicvideo`, `tvshow` |
|
||||||
|
| data_kind | string | Data type of this track: `file`, `stream`, `spotify`, `pipe` |
|
||||||
|
| path | string | Path |
|
||||||
|
| uri | string | Resource identifier |
|
||||||
|
62
src/db.c
62
src/db.c
@ -333,6 +333,68 @@ static const struct browse_clause browse_clause[] =
|
|||||||
{ "f.path, f.path", "f.path", "f.path" },
|
{ "f.path, f.path", "f.path", "f.path" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct media_kind_label {
|
||||||
|
enum media_kind type;
|
||||||
|
const char *label;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Keep in sync with enum media_kind */
|
||||||
|
static const struct media_kind_label media_kind_labels[] =
|
||||||
|
{
|
||||||
|
{ MEDIA_KIND_MUSIC, "music" },
|
||||||
|
{ MEDIA_KIND_MOVIE, "movie" },
|
||||||
|
{ MEDIA_KIND_PODCAST, "podcast" },
|
||||||
|
{ MEDIA_KIND_AUDIOBOOK, "audiobook" },
|
||||||
|
{ MEDIA_KIND_MUSICVIDEO, "musicvideo" },
|
||||||
|
{ MEDIA_KIND_TVSHOW, "tvshow" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *
|
||||||
|
db_media_kind_label(enum media_kind media_kind)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(media_kind_labels); i++)
|
||||||
|
{
|
||||||
|
if (media_kind == media_kind_labels[i].type)
|
||||||
|
return media_kind_labels[i].label;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum media_kind
|
||||||
|
db_media_kind_enum(const char *label)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!label)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(media_kind_labels); i++)
|
||||||
|
{
|
||||||
|
if (strcmp(label, media_kind_labels[i].label) == 0)
|
||||||
|
return media_kind_labels[i].type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep in sync with enum data_kind */
|
||||||
|
static char *data_kind_label[] = { "file", "url", "spotify", "pipe" };
|
||||||
|
|
||||||
|
const char *
|
||||||
|
db_data_kind_label(enum data_kind data_kind)
|
||||||
|
{
|
||||||
|
if (data_kind < ARRAY_SIZE(data_kind_label))
|
||||||
|
{
|
||||||
|
return data_kind_label[data_kind];
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* Shuffle RNG state */
|
/* Shuffle RNG state */
|
||||||
struct rng_ctx shuffle_rng;
|
struct rng_ctx shuffle_rng;
|
||||||
|
|
||||||
|
11
src/db.h
11
src/db.h
@ -105,6 +105,7 @@ struct pairing_info {
|
|||||||
char *guid;
|
char *guid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Keep in sync with media_kind_labels[] */
|
||||||
enum media_kind {
|
enum media_kind {
|
||||||
MEDIA_KIND_MUSIC = 1,
|
MEDIA_KIND_MUSIC = 1,
|
||||||
MEDIA_KIND_MOVIE = 2,
|
MEDIA_KIND_MOVIE = 2,
|
||||||
@ -114,6 +115,13 @@ enum media_kind {
|
|||||||
MEDIA_KIND_TVSHOW = 64,
|
MEDIA_KIND_TVSHOW = 64,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const char *
|
||||||
|
db_media_kind_label(enum media_kind media_kind);
|
||||||
|
|
||||||
|
enum media_kind
|
||||||
|
db_media_kind_enum(const char *label);
|
||||||
|
|
||||||
|
/* Keep in sync with data_kind_label[] */
|
||||||
enum data_kind {
|
enum data_kind {
|
||||||
DATA_KIND_FILE = 0, /* normal file */
|
DATA_KIND_FILE = 0, /* normal file */
|
||||||
DATA_KIND_HTTP = 1, /* network stream (radio) */
|
DATA_KIND_HTTP = 1, /* network stream (radio) */
|
||||||
@ -121,6 +129,9 @@ enum data_kind {
|
|||||||
DATA_KIND_PIPE = 3, /* iTunes has no pipe data kind, but we use 3 */
|
DATA_KIND_PIPE = 3, /* iTunes has no pipe data kind, but we use 3 */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const char *
|
||||||
|
db_data_kind_label(enum data_kind data_kind);
|
||||||
|
|
||||||
/* Note that fields marked as integers in the metadata map in filescanner_ffmpeg must be uint32_t here */
|
/* Note that fields marked as integers in the metadata map in filescanner_ffmpeg must be uint32_t here */
|
||||||
struct media_file_info {
|
struct media_file_info {
|
||||||
char *path;
|
char *path;
|
||||||
|
108
src/httpd.c
108
src/httpd.c
@ -255,12 +255,25 @@ httpd_fixup_uri(struct evhttp_request *req)
|
|||||||
|
|
||||||
/* --------------------------- REQUEST HELPERS ------------------------------ */
|
/* --------------------------- REQUEST HELPERS ------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
httpd_redirect_to_admin(struct evhttp_request *req)
|
||||||
|
{
|
||||||
|
struct evkeyvalq *headers;
|
||||||
|
|
||||||
|
headers = evhttp_request_get_output_headers(req);
|
||||||
|
evhttp_add_header(headers, "Location", "/admin.html");
|
||||||
|
|
||||||
|
httpd_send_reply(req, HTTP_MOVETEMP, "Moved", NULL, HTTPD_SEND_NO_GZIP);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
serve_file(struct evhttp_request *req, const char *uri)
|
serve_file(struct evhttp_request *req, const char *uri)
|
||||||
{
|
{
|
||||||
char *ext;
|
char *ext;
|
||||||
char path[PATH_MAX];
|
char path[PATH_MAX];
|
||||||
char *deref;
|
char deref[PATH_MAX];
|
||||||
char *ctype;
|
char *ctype;
|
||||||
struct evbuffer *evbuf;
|
struct evbuffer *evbuf;
|
||||||
struct evkeyvalq *input_headers;
|
struct evkeyvalq *input_headers;
|
||||||
@ -272,6 +285,7 @@ serve_file(struct evhttp_request *req, const char *uri)
|
|||||||
const char *modified_since;
|
const char *modified_since;
|
||||||
char last_modified[1000];
|
char last_modified[1000];
|
||||||
struct tm *tm_modified;
|
struct tm *tm_modified;
|
||||||
|
bool slashed;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* Check authentication */
|
/* Check authentication */
|
||||||
@ -298,16 +312,9 @@ serve_file(struct evhttp_request *req, const char *uri)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (S_ISDIR(sb.st_mode))
|
if (S_ISLNK(sb.st_mode))
|
||||||
{
|
{
|
||||||
httpd_redirect_to_index(req, uri);
|
if (!realpath(path, deref))
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (S_ISLNK(sb.st_mode))
|
|
||||||
{
|
|
||||||
deref = realpath(path, NULL);
|
|
||||||
if (!deref)
|
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Could not dereference %s: %s\n", path, strerror(errno));
|
DPRINTF(E_LOG, L_HTTPD, "Could not dereference %s: %s\n", path, strerror(errno));
|
||||||
|
|
||||||
@ -322,12 +329,10 @@ serve_file(struct evhttp_request *req, const char *uri)
|
|||||||
|
|
||||||
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
|
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
|
||||||
|
|
||||||
free(deref);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
strcpy(path, deref);
|
strcpy(path, deref);
|
||||||
free(deref);
|
|
||||||
|
|
||||||
ret = stat(path, &sb);
|
ret = stat(path, &sb);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
@ -338,13 +343,38 @@ serve_file(struct evhttp_request *req, const char *uri)
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (S_ISDIR(sb.st_mode))
|
if (S_ISDIR(sb.st_mode))
|
||||||
{
|
{
|
||||||
httpd_redirect_to_index(req, uri);
|
slashed = (path[strlen(path) - 1] == '/');
|
||||||
|
|
||||||
|
ret = snprintf(deref, sizeof(deref), "%s%sindex.html", path, (slashed) ? "" : "/");
|
||||||
|
if ((ret < 0) || (ret >= sizeof(deref)))
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_HTTPD, "Redirection URL exceeds buffer length\n");
|
||||||
|
|
||||||
|
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strcpy(path, deref);
|
||||||
|
|
||||||
|
ret = stat(path, &sb);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
if (strcmp(uri, "/") == 0)
|
||||||
|
{
|
||||||
|
httpd_redirect_to_admin(req);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", path, strerror(errno));
|
||||||
|
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path_is_legal(path) != 0)
|
if (path_is_legal(path) != 0)
|
||||||
@ -713,9 +743,7 @@ httpd_gen_cb(struct evhttp_request *req, void *arg)
|
|||||||
output_headers = evhttp_request_get_output_headers(req);
|
output_headers = evhttp_request_get_output_headers(req);
|
||||||
|
|
||||||
evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin);
|
evhttp_add_header(output_headers, "Access-Control-Allow-Origin", allow_origin);
|
||||||
|
evhttp_add_header(output_headers, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||||
// Allow only GET method and authorization header in cross origin requests
|
|
||||||
evhttp_add_header(output_headers, "Access-Control-Allow-Method", "GET");
|
|
||||||
evhttp_add_header(output_headers, "Access-Control-Allow-Headers", "authorization");
|
evhttp_add_header(output_headers, "Access-Control-Allow-Headers", "authorization");
|
||||||
|
|
||||||
// In this case there is no reason to go through httpd_send_reply
|
// In this case there is no reason to go through httpd_send_reply
|
||||||
@ -732,12 +760,17 @@ httpd_gen_cb(struct evhttp_request *req, void *arg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
parsed = httpd_uri_parse(uri);
|
parsed = httpd_uri_parse(uri);
|
||||||
if (!parsed || !parsed->path || (strcmp(parsed->path, "/") == 0))
|
if (!parsed || !parsed->path)
|
||||||
{
|
{
|
||||||
httpd_redirect_to_admin(req);
|
httpd_redirect_to_admin(req);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strcmp(parsed->path, "/") == 0)
|
||||||
|
{
|
||||||
|
goto serve_file;
|
||||||
|
}
|
||||||
|
|
||||||
/* Dispatch protocol-specific handlers */
|
/* Dispatch protocol-specific handlers */
|
||||||
if (dacp_is_request(parsed->path))
|
if (dacp_is_request(parsed->path))
|
||||||
{
|
{
|
||||||
@ -773,6 +806,7 @@ httpd_gen_cb(struct evhttp_request *req, void *arg)
|
|||||||
DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", parsed->uri);
|
DPRINTF(E_DBG, L_HTTPD, "HTTP request: '%s'\n", parsed->uri);
|
||||||
|
|
||||||
/* Serve web interface files */
|
/* Serve web interface files */
|
||||||
|
serve_file:
|
||||||
serve_file(req, parsed->path);
|
serve_file(req, parsed->path);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@ -1379,42 +1413,6 @@ httpd_send_error(struct evhttp_request* req, int error, const char* reason)
|
|||||||
evbuffer_free(evbuf);
|
evbuffer_free(evbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
httpd_redirect_to_admin(struct evhttp_request *req)
|
|
||||||
{
|
|
||||||
struct evkeyvalq *headers;
|
|
||||||
|
|
||||||
headers = evhttp_request_get_output_headers(req);
|
|
||||||
evhttp_add_header(headers, "Location", "/admin.html");
|
|
||||||
|
|
||||||
httpd_send_reply(req, HTTP_MOVETEMP, "Moved", NULL, HTTPD_SEND_NO_GZIP);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
httpd_redirect_to_index(struct evhttp_request *req, const char *uri)
|
|
||||||
{
|
|
||||||
struct evkeyvalq *headers;
|
|
||||||
char buf[256];
|
|
||||||
int slashed;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
slashed = (uri[strlen(uri) - 1] == '/');
|
|
||||||
|
|
||||||
ret = snprintf(buf, sizeof(buf), "%s%sindex.html", uri, (slashed) ? "" : "/");
|
|
||||||
if ((ret < 0) || (ret >= sizeof(buf)))
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Redirection URL exceeds buffer length\n");
|
|
||||||
|
|
||||||
httpd_send_error(req, HTTP_NOTFOUND, "Not Found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = evhttp_request_get_output_headers(req);
|
|
||||||
evhttp_add_header(headers, "Location", buf);
|
|
||||||
|
|
||||||
httpd_send_reply(req, HTTP_MOVETEMP, "Moved", NULL, HTTPD_SEND_NO_GZIP);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
httpd_admin_check_auth(struct evhttp_request *req)
|
httpd_admin_check_auth(struct evhttp_request *req)
|
||||||
{
|
{
|
||||||
|
@ -145,11 +145,6 @@ httpd_send_error(struct evhttp_request *req, int error, const char *reason);
|
|||||||
void
|
void
|
||||||
httpd_redirect_to_admin(struct evhttp_request *req);
|
httpd_redirect_to_admin(struct evhttp_request *req);
|
||||||
|
|
||||||
/*
|
|
||||||
* Redirects to [uri]/index.html
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
httpd_redirect_to_index(struct evhttp_request *req, const char *uri);
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
httpd_admin_check_auth(struct evhttp_request *req);
|
httpd_admin_check_auth(struct evhttp_request *req);
|
||||||
|
1379
src/httpd_jsonapi.c
1379
src/httpd_jsonapi.c
File diff suppressed because it is too large
Load Diff
@ -15,6 +15,7 @@
|
|||||||
#define STOB(s) ((s) * 4)
|
#define STOB(s) ((s) * 4)
|
||||||
#define BTOS(b) ((b) / 4)
|
#define BTOS(b) ((b) / 4)
|
||||||
|
|
||||||
|
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||||||
|
|
||||||
struct onekeyval {
|
struct onekeyval {
|
||||||
char *name;
|
char *name;
|
||||||
@ -57,7 +58,6 @@ safe_strdup(const char *str);
|
|||||||
char *
|
char *
|
||||||
safe_asprintf(const char *fmt, ...);
|
safe_asprintf(const char *fmt, ...);
|
||||||
|
|
||||||
|
|
||||||
/* Key/value functions */
|
/* Key/value functions */
|
||||||
struct keyval *
|
struct keyval *
|
||||||
keyval_alloc(void);
|
keyval_alloc(void);
|
||||||
|
Loading…
Reference in New Issue
Block a user