Added radio metadata improvements, changed HTTP headers
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
a2856ad82a
commit
5eda50e5b7
|
@ -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)
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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
17
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
|
||||
}()
|
||||
|
|
2
mount.go
2
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(),
|
||||
}
|
||||
}
|
||||
|
|
60
queue.go
60
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)
|
||||
|
|
Loading…
Reference in a new issue