DataHoarder
eec79cb3ab
All checks were successful
continuous-integration/drone/push Build is passing
238 lines
No EOL
6.5 KiB
Markdown
238 lines
No EOL
6.5 KiB
Markdown
# MeteorLight
|
|
|
|
Radio streamer ([kawa](https://github.com/Luminarys/kawa) drop-in compatible).
|
|
|
|
# Improvements / differences from Kawa
|
|
* Does not use libav ([see supported formats/codecs on Kirika](https://git.gammaspectra.live/S.O.N.G/Kirika#codecs-supported))
|
|
* Supports HTTP clients that have more than 16 HTTP request headers or longer than 64 bytes per header.
|
|
* Does not restart stream per-track, instead being a continuous stream without parameter changes.
|
|
* Normalized channels / sample rates for all mounts.
|
|
* Implements ICY metadata (artist, title, url).
|
|
* Uses sample/timed packet buffers, instead of kawa byte buffers, which caused wild differences between endpoints. Mounts usually align within 0.2s of each other, depending on client.
|
|
* Use `queue.buffer_size` to specify number of seconds to buffer (by default 0, automatic per client).
|
|
* Implements `queue.nr` and `/random` (to be deprecated/changed).
|
|
* Supports max queue length via `queue.length` config.
|
|
* Supports extra encoder bitrate control settings (CBR, VBR, auto, etc.)
|
|
* Can set custom sample rate / channel count / bitdepth / compression level per stream mount.
|
|
* Can read and apply ReplayGain tags, or normalize audio loudness.
|
|
* Can have audio sources over HTTP(s) URLs on `path` property, and supports seeking.
|
|
* [Precise metadata and timing information packet stream](PACKET_STREAM.md), trigger via `x-audio-packet-stream: 1` HTTP header.
|
|
* Workaround to allow FLAC streaming under Safari.
|
|
* API additions to allow working with direct queue items or listeners.
|
|
|
|
## Dependencies
|
|
### Go >= 1.19
|
|
|
|
### [Kirika](https://git.gammaspectra.live/S.O.N.G/Kirika) dependencies
|
|
Kirika is a collection of audio utilities for decoding/encoding files and streams.
|
|
|
|
Check its native dependencies that must be installed before usage.
|
|
|
|
## Usage
|
|
Start by copying [example_config.toml](example_config.toml) to the location of your choice and reading through it. Of importance are `queue.fallback`, and `queue.random_song_api`.
|
|
|
|
MeteorLight will search for `config.toml` in its working directory. Alternatively you can pass `-config "/example/path/config.toml"` to specify a different location.
|
|
|
|
Batteries are not included - MeteorLight needs to be paired with your own software to find songs to stream.
|
|
You will have to provide an external API that MeteorLight can query for songs to play and notify as new songs being played.
|
|
|
|
Before continuing, you will need to install the dependencies listed above.
|
|
|
|
### From Git repository
|
|
```shell
|
|
$ git clone https://git.gammaspectra.live/S.O.N.G/MeteorLight.git && cd MeteorLight
|
|
# create/edit config.toml
|
|
$ go run .
|
|
```
|
|
|
|
### From Go run
|
|
```shell
|
|
$ go run git.gammaspectra.live/S.O.N.G/MeteorLight@<commit_hash>
|
|
```
|
|
|
|
### From Docker/Podman
|
|
|
|
Image is using `golang:1.19-alpine`, dependencies are built from scratch. See [Dockerfile](Dockerfile).
|
|
|
|
```shell
|
|
$ docker build -t meteorlight .
|
|
$ docker run --rm -it -v "$(pwd)/config.toml:/config.toml:ro" -v "$(pwd)/fallback.flac:/fallback.flac:ro" -p 8001:8001 -p 127.0.0.1:4040:4040 meteorlight
|
|
```
|
|
|
|
## API
|
|
See [kawa API](https://github.com/Luminarys/kawa#api) for a general overview. Additional endpoints or changed ones are listed below.
|
|
|
|
Track blobs returned have a `queue_id` parameter, regardless of source.
|
|
|
|
Queue entry metadata (`title`, `artist`, `album`, and ReplayGain tags `track_peak`, `track_gain`, `album_peak`, `album_gain`) will be used if provided, then fallback to on-file tags where possible. These will be made available over `/np` and similar endpoints, and be exposed via ICY metadata and timing/metadata stream.
|
|
|
|
### `NEW` DELETE /queue/<queue_id>
|
|
Unqueues the track with `queue_id` specified as a parameter.
|
|
|
|
#### Response
|
|
```json
|
|
{
|
|
"success": true,
|
|
"reason": null
|
|
}
|
|
```
|
|
|
|
### `CHANGED` POST /queue/head
|
|
Same as kawa's, but `queue_id` is added to response directly.
|
|
|
|
#### Response
|
|
```json
|
|
{
|
|
"success": true,
|
|
"reason": null,
|
|
"queue_id": 3
|
|
}
|
|
```
|
|
|
|
### `CHANGED` POST /queue/tail
|
|
Same as kawa's, but `queue_id` is added to response directly.
|
|
|
|
#### Response
|
|
```json
|
|
{
|
|
"success": true,
|
|
"reason": null,
|
|
"queue_id": 5
|
|
}
|
|
```
|
|
|
|
### `NEW` DELETE /listeners/<listener_id>
|
|
Drops the listener connection with `listener_id` specified as a parameter.
|
|
|
|
#### Response
|
|
```json
|
|
{
|
|
"success": true,
|
|
"reason": null
|
|
}
|
|
```
|
|
|
|
### `CHANGED` GET /listeners
|
|
Same as kawa's, but `identifier` and `start` is added to each listener entry.
|
|
|
|
The listener `identifier` is generated based on user connection address, port, user-agent and mount.
|
|
|
|
The `start` field denotes the unix time this listener connected.
|
|
|
|
Additionally, a `x-listener-identifier` header is exposed to mount response.
|
|
|
|
#### Response
|
|
```json
|
|
[
|
|
{
|
|
"identifier": "641df131cb52f8f6381d9946cccb822e",
|
|
"mount": "stream.flac",
|
|
"path": "/stream.flac",
|
|
"start": 1661283903,
|
|
"headers": [
|
|
{
|
|
"name": "User-Agent",
|
|
"value": "libmpv"
|
|
},
|
|
{
|
|
"name": "Accept",
|
|
"value": "*/*"
|
|
},
|
|
{
|
|
"name": "Range",
|
|
"value": "bytes=0-"
|
|
},
|
|
{
|
|
"name": "Connection",
|
|
"value": "close"
|
|
},
|
|
{
|
|
"name": "Icy-Metadata",
|
|
"value": "1"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
```
|
|
|
|
## Mount API
|
|
### `NEW` GET /mounts
|
|
A simple listing of the working mounts + settings are made available.
|
|
|
|
#### Response
|
|
```json
|
|
[
|
|
{
|
|
"mount": "/stream128.mp3",
|
|
"mime": "audio/mpeg;codecs=mp3",
|
|
"sampleRate": 44100,
|
|
"channels": 2,
|
|
"listeners": 0,
|
|
"options": {
|
|
"bitrate": 128
|
|
}
|
|
},
|
|
{
|
|
"mount": "/stream192.mp3",
|
|
"mime": "audio/mpeg;codecs=mp3",
|
|
"sampleRate": 44100,
|
|
"channels": 2,
|
|
"listeners": 0,
|
|
"options": {
|
|
"bitrate": 192
|
|
}
|
|
},
|
|
{
|
|
"mount": "/stream128.aac",
|
|
"mime": "audio/aac",
|
|
"sampleRate": 44100,
|
|
"channels": 2,
|
|
"listeners": 0,
|
|
"options": {
|
|
"bitrate": 128
|
|
}
|
|
},
|
|
{
|
|
"mount": "/stream128.opus",
|
|
"mime": "audio/ogg;codecs=opus",
|
|
"sampleRate": 48000,
|
|
"channels": 2,
|
|
"listeners": 0,
|
|
"options": {
|
|
"bitrate": 128
|
|
}
|
|
},
|
|
{
|
|
"mount": "/stream192.opus",
|
|
"mime": "audio/ogg;codecs=opus",
|
|
"sampleRate": 48000,
|
|
"channels": 2,
|
|
"listeners": 0,
|
|
"options": {
|
|
"bitrate": 192
|
|
}
|
|
},
|
|
{
|
|
"mount": "/stream256.opus",
|
|
"mime": "audio/ogg;codecs=opus",
|
|
"sampleRate": 48000,
|
|
"channels": 2,
|
|
"listeners": 0,
|
|
"options": {
|
|
"bitrate": 256
|
|
}
|
|
},
|
|
{
|
|
"mount": "/stream.flac",
|
|
"mime": "audio/flac",
|
|
"sampleRate": 44100,
|
|
"channels": 2,
|
|
"listeners": 0,
|
|
"options": {
|
|
"bitdepth": 16,
|
|
"block_size": 0,
|
|
"compression_level": 5
|
|
}
|
|
}
|
|
]
|
|
``` |