From 5eda50e5b718ccb479365965f1290397070dd183 Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Mon, 7 Mar 2022 16:19:38 +0100 Subject: [PATCH] Added radio metadata improvements, changed HTTP headers --- MeteorLight.go | 1 + config.go | 8 ++++-- example_config.toml | 8 ++++++ http.go | 17 ++++++++----- mount.go | 2 ++ queue.go | 60 ++++++++++++++++++++++++++++++++++++++++----- 6 files changed, 82 insertions(+), 14 deletions(-) diff --git a/MeteorLight.go b/MeteorLight.go index 647bb70..edf91ec 100644 --- a/MeteorLight.go +++ b/MeteorLight.go @@ -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) diff --git a/config.go b/config.go index 57cdd74..294b28a 100644 --- a/config.go +++ b/config.go @@ -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"` diff --git a/example_config.toml b/example_config.toml index 79ae1b0..220f648 100644 --- a/example_config.toml +++ b/example_config.toml @@ -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 diff --git a/http.go b/http.go index 7e5a6e1..5a7b86a 100644 --- a/http.go +++ b/http.go @@ -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 }() diff --git a/mount.go b/mount.go index 68d7ccf..4343926 100644 --- a/mount.go +++ b/mount.go @@ -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(), } } diff --git a/queue.go b/queue.go index e876515..8fddecd 100644 --- a/queue.go +++ b/queue.go @@ -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)