parent
1cd87209be
commit
035776c504
59
main.go
59
main.go
|
@ -23,7 +23,8 @@ import (
|
||||||
const (
|
const (
|
||||||
targetURI = "http://as-hls-%s-live.akamaized.net/pool_904/live/%s/" +
|
targetURI = "http://as-hls-%s-live.akamaized.net/pool_904/live/%s/" +
|
||||||
"%s/%s.isml/%s-audio%%3d%s.norewind.m3u8"
|
"%s/%s.isml/%s-audio%%3d%s.norewind.m3u8"
|
||||||
metaURI = "https://rms.api.bbc.co.uk/v2/services/%s/segments/latest"
|
networksURI = "https://rms.api.bbc.co.uk/radio/networks.json"
|
||||||
|
metaURI = "https://rms.api.bbc.co.uk/v2/services/%s/segments/latest"
|
||||||
)
|
)
|
||||||
|
|
||||||
type meta struct {
|
type meta struct {
|
||||||
|
@ -31,6 +32,42 @@ type meta struct {
|
||||||
timeout uint // timeout for the next poll in ms
|
timeout uint // timeout for the next poll in ms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getServiceTitle returns a human-friendly identifier for a BBC service ID.
|
||||||
|
func getServiceTitle(name string) (string, error) {
|
||||||
|
resp, err := http.Get(networksURI)
|
||||||
|
if resp != nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return name, err
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
var v struct {
|
||||||
|
Results []struct {
|
||||||
|
Services []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
} `json:"services"`
|
||||||
|
} `json:"results"`
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, &v)
|
||||||
|
if err != nil {
|
||||||
|
return name, errors.New("invalid metadata response")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, network := range v.Results {
|
||||||
|
for _, service := range network.Services {
|
||||||
|
if service.ID == name {
|
||||||
|
return service.Title, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name, errors.New("unknown service")
|
||||||
|
}
|
||||||
|
|
||||||
|
var errNoSong = errors.New("no song is playing")
|
||||||
|
|
||||||
// getMeta retrieves and decodes metadata info from an independent webservice.
|
// getMeta retrieves and decodes metadata info from an independent webservice.
|
||||||
func getMeta(name string) (*meta, error) {
|
func getMeta(name string) (*meta, error) {
|
||||||
resp, err := http.Get(fmt.Sprintf(metaURI, name))
|
resp, err := http.Get(fmt.Sprintf(metaURI, name))
|
||||||
|
@ -41,6 +78,9 @@ func getMeta(name string) (*meta, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if os.Getenv("DEBUG") != "" {
|
||||||
|
log.Println(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: update more completely for the new OpenAPI
|
// TODO: update more completely for the new OpenAPI
|
||||||
// - `broadcasts/poll/bbc_radio_one` looks almost useful
|
// - `broadcasts/poll/bbc_radio_one` looks almost useful
|
||||||
|
@ -63,7 +103,7 @@ func getMeta(name string) (*meta, error) {
|
||||||
return nil, errors.New("invalid metadata response")
|
return nil, errors.New("invalid metadata response")
|
||||||
}
|
}
|
||||||
if len(v.Data) == 0 || !v.Data[0].Offset.NowPlaying {
|
if len(v.Data) == 0 || !v.Data[0].Offset.NowPlaying {
|
||||||
return nil, errors.New("no song is playing")
|
return nil, errNoSong
|
||||||
}
|
}
|
||||||
|
|
||||||
titles := v.Data[0].Titles
|
titles := v.Data[0].Titles
|
||||||
|
@ -124,9 +164,10 @@ func metaProc(ctx context.Context, name string, out chan<- string) {
|
||||||
var interval time.Duration
|
var interval time.Duration
|
||||||
for {
|
for {
|
||||||
meta, err := getMeta(name)
|
meta, err := getMeta(name)
|
||||||
if err != nil {
|
if err == errNoSong {
|
||||||
current = name + " - " + err.Error()
|
interval, current = maxInterval, ""
|
||||||
interval = maxInterval
|
} else if err != nil {
|
||||||
|
interval, current = maxInterval, err.Error()
|
||||||
} else {
|
} else {
|
||||||
current = meta.title
|
current = meta.title
|
||||||
interval = time.Duration(meta.timeout) * time.Millisecond
|
interval = time.Duration(meta.timeout) * time.Millisecond
|
||||||
|
@ -263,11 +304,10 @@ func proxy(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
// TODO: Retrieve some general information from somewhere?
|
serviceTitle, _ := getServiceTitle(name)
|
||||||
// There's nothing interesting in the playlist files.
|
|
||||||
|
|
||||||
fmt.Fprintf(bufrw, "ICY 200 OK\r\n")
|
fmt.Fprintf(bufrw, "ICY 200 OK\r\n")
|
||||||
fmt.Fprintf(bufrw, "icy-name:%s\r\n", name)
|
fmt.Fprintf(bufrw, "icy-name:%s\r\n", serviceTitle)
|
||||||
// BBC marks this as a video type, maybe just force audio/mpeg.
|
// BBC marks this as a video type, maybe just force audio/mpeg.
|
||||||
fmt.Fprintf(bufrw, "content-type:%s\r\n", resp.Header["Content-Type"][0])
|
fmt.Fprintf(bufrw, "content-type:%s\r\n", resp.Header["Content-Type"][0])
|
||||||
fmt.Fprintf(bufrw, "icy-pub:%d\r\n", 0)
|
fmt.Fprintf(bufrw, "icy-pub:%d\r\n", 0)
|
||||||
|
@ -300,6 +340,9 @@ func proxy(w http.ResponseWriter, req *http.Request) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case title := <-metaChan:
|
case title := <-metaChan:
|
||||||
|
if title == "" {
|
||||||
|
title = serviceTitle
|
||||||
|
}
|
||||||
queuedMetaUpdate = []byte(fmt.Sprintf("StreamTitle='%s'",
|
queuedMetaUpdate = []byte(fmt.Sprintf("StreamTitle='%s'",
|
||||||
strings.Replace(title, "'", "’", -1)))
|
strings.Replace(title, "'", "’", -1)))
|
||||||
case chunk, ok := <-chunkChan:
|
case chunk, ok := <-chunkChan:
|
||||||
|
|
Loading…
Reference in New Issue