Added radio metadata improvements, changed HTTP headers
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
DataHoarder 2022-03-07 16:19:38 +01:00
parent a2856ad82a
commit 5eda50e5b7
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
6 changed files with 82 additions and 14 deletions

View file

@ -31,6 +31,7 @@ func main() {
Addr: fmt.Sprintf(":%d", config.Radio.Port),
Handler: http.HandlerFunc(queue.HandleRadioRequest),
}
server.SetKeepAlivesEnabled(false)
//setup a timeout to prevent slow clients blocking. See https://github.com/golang/go/issues/16100
timeoutListener, err := newListener("tcp", server.Addr, time.Second*15, time.Second*30)

View file

@ -17,8 +17,12 @@ type Config struct {
ReplayGain bool `toml:"replaygain"`
} `toml:"queue"`
Radio struct {
Port int `toml:"port"`
Name string `toml:"name"`
Port int `toml:"port"`
Name string `toml:"name"`
Description string `toml:"description"`
URL string `toml:"url"`
Logo string `toml:"logo"`
Private bool `toml:"private"`
} `toml:"radio"`
Streams []struct {
MountPath string `toml:"mount"`

View file

@ -51,6 +51,14 @@ replaygain=false
port=8001
# Name of the stream.
name="my radio"
# Description of the stream.
description=""
# URL of the stream.
url=""
# Logo URL of the radio
logo=""
# Whether to make the radio "private" via headers
private=false
#
# A list of streams to make available at [radio.port]/*(mount) follows. The

17
http.go
View file

@ -13,12 +13,13 @@ import (
const rangeReaderBufferSize = 4096
type RangeReadSeekCloser struct {
uri *url.URL
size int64
i int64
body io.ReadCloser
buf []byte
ib int64
uri *url.URL
size int64
i int64
body io.ReadCloser
buf []byte
ib int64
closed bool
}
func NewRangeReadSeekCloser(uri string) (*RangeReadSeekCloser, error) {
@ -43,6 +44,9 @@ func (r *RangeReadSeekCloser) GetURI() string {
}
func (r *RangeReadSeekCloser) retryConnect() error {
if r.closed {
return errors.New("already closed")
}
headers := make(http.Header)
startOffset := r.i
@ -163,6 +167,7 @@ func (r *RangeReadSeekCloser) Seek(offset int64, whence int) (int64, error) {
func (r *RangeReadSeekCloser) Close() error {
if r.body != nil {
r.closed = true
defer func() {
r.body = nil
}()

View file

@ -35,6 +35,7 @@ type StreamMount struct {
Mount string
MimeType string
Packetizer packetizer.Packetizer
Options map[string]interface{}
SampleRate int
MetadataQueue *goconcurrentqueue.FIFO
listeners []*StreamListener
@ -152,6 +153,7 @@ func NewStreamMount(source audio.Source, mount string, codec string, container s
MimeType: mimeType,
Packetizer: packetizerEntry,
SampleRate: sampleRate,
Options: options,
MetadataQueue: goconcurrentqueue.NewFIFO(),
}
}

View file

@ -383,10 +383,58 @@ func (q *Queue) HandleRadioRequest(writer http.ResponseWriter, request *http.Req
writer.Header().Set("Server", "MeteorLight/radio")
writer.Header().Set("Content-Type", mount.MimeType)
writer.Header().Set("Accept-Ranges", "none")
writer.Header().Set("Connection", "keep-alive")
writer.Header().Set("X-Audiocast-Name", q.config.Radio.Name)
writer.Header().Set("Cache-Control", "no-store, max-age=0, no-transform")
writer.Header().Set("Connection", "close")
writer.Header().Set("Cache-Control", "no-store, max-age=604800")
writer.Header().Set("X-Content-Type-Options", "nosniff")
writer.Header().Set("Access-Control-Allow-Origin", "*")
writer.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Icy-Metadata")
writer.Header().Set("Access-Control-Expose-Headers", "Accept-Ranges, Server, Content-Type, Icy-MetaInt")
writer.Header().Set("Vary", "*")
rangeHeader := request.Header.Get("range")
if rangeHeader != "" && rangeHeader != "bytes=0-" {
//TODO: maybe should fail in case bytes are requested
}
bitrate := 0
if value, ok := mount.Options["bitrate"]; ok {
if intValue, ok := value.(int); ok {
bitrate = intValue
} else if int64Value, ok := value.(int64); ok {
bitrate = int(int64Value)
}
}
//set some audiocast/icy radio headers
writer.Header().Set("x-audiocast-name", q.config.Radio.Name)
writer.Header().Set("x-audiocast-bitrate", fmt.Sprintf("%d", bitrate))
writer.Header().Set("icy-name", q.config.Radio.Name)
writer.Header().Set("icy-version", "2")
writer.Header().Set("icy-index-metadata", "1")
if q.config.Radio.Description != "" {
writer.Header().Set("x-audiocast-description", q.config.Radio.Description)
writer.Header().Set("icy-description", q.config.Radio.Description)
}
if q.config.Radio.URL != "" {
writer.Header().Set("x-audiocast-url", q.config.Radio.URL)
writer.Header().Set("icy-url", q.config.Radio.URL)
}
if q.config.Radio.Logo != "" {
writer.Header().Set("icy-logo", q.config.Radio.Logo)
}
writer.Header().Set("icy-br", fmt.Sprintf("%d", bitrate))
writer.Header().Set("icy-sr", fmt.Sprintf("%d", mount.SampleRate))
writer.Header().Set("icy-audio-info", fmt.Sprintf("ice-channels=%d;ice-samplerate=%d;ice-bitrate=%d", 2, mount.SampleRate, bitrate))
if q.config.Radio.Private {
writer.Header().Set("icy-pub", "0")
writer.Header().Set("icy-do-not-index", "1")
writer.Header().Set("x-audiocast-public", "0")
writer.Header().Set("x-robots-tag", "noindex, nofollow")
} else {
writer.Header().Set("icy-pub", "1")
writer.Header().Set("icy-do-not-index", "0")
writer.Header().Set("x-audiocast-public", "1")
}
var packetWriteCallback func(packet packetizer.Packet) error
@ -398,12 +446,12 @@ func (q *Queue) HandleRadioRequest(writer http.ResponseWriter, request *http.Req
if numberValue, err := strconv.Atoi(request.Header.Get("icy-metadata")); err == nil && numberValue >= 1 {
metadataToSend := make(map[string]string)
const icyInterval = 8192
const icyInterval = 8192 //weird clients might not support other numbers than this
icyCounter := 0
writer.Header().Set("Icy-MetaInt", fmt.Sprintf("%d", icyInterval))
writer.Header().Set("icy-metaint", fmt.Sprintf("%d", icyInterval))
writeIcy := func() []byte {
packetContent := make([]byte, 1, 4096)
packetContent := make([]byte, 1, 16*255+1)
for k, v := range metadataToSend {
packetContent = append(packetContent, []byte(fmt.Sprintf("%s='%s';", k, v))...)
delete(metadataToSend, k)