Make this thing work again
This commit is contained in:
		
							parent
							
								
									08beb029a7
								
							
						
					
					
						commit
						8a087dea4a
					
				
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| Copyright (c) 2016 - 2018, Přemysl Eric Janouch <p@janouch.name> | Copyright (c) 2016 - 2024, Přemysl Eric Janouch <p@janouch.name> | ||||||
| 
 | 
 | ||||||
| Permission to use, copy, modify, and/or distribute this software for any | Permission to use, copy, modify, and/or distribute this software for any | ||||||
| purpose with or without fee is hereby granted. | purpose with or without fee is hereby granted. | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								README.adoc
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.adoc
									
									
									
									
									
								
							| @ -19,14 +19,19 @@ Build dependencies: go | |||||||
|  $ cd bbc-on-ice |  $ cd bbc-on-ice | ||||||
|  $ go build |  $ go build | ||||||
| 
 | 
 | ||||||
|  | or, if you know what you're doing: | ||||||
|  | 
 | ||||||
|  |  $ go install janouch.name/bbc-on-ice@master | ||||||
|  | 
 | ||||||
| To run the local server: | To run the local server: | ||||||
| 
 | 
 | ||||||
|  $ ./bbc-on-ice :8000 |  $ ./bbc-on-ice :8000 | ||||||
| 
 | 
 | ||||||
| Streams have URLs in the following form: | Streams have URLs in the following form, derived from | ||||||
|  | https://gist.github.com/bpsib/67089b959e4fa898af69fea59ad74bc3[this list]: | ||||||
| 
 | 
 | ||||||
|  $ mpv http://localhost:8000/nonuk/sbr_low/bbc_radio_one |  $ mpv http://localhost:8000/ww/96000/bbc_radio_one | ||||||
|  $ mpv http://localhost:8000/uk/sbr_high/bbc_1xtra |  $ mpv http://localhost:8000/uk/320000/bbc_1xtra | ||||||
| 
 | 
 | ||||||
| Socket activation | Socket activation | ||||||
| ----------------- | ----------------- | ||||||
|  | |||||||
							
								
								
									
										81
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								main.go
									
									
									
									
									
								
							| @ -21,9 +21,9 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	targetURI = "http://a.files.bbci.co.uk/media/live/manifesto/" + | 	targetURI = "http://as-hls-%s-live.akamaized.net/pool_904/live/%s/" + | ||||||
| 		"audio/simulcast/hls/%s/%s/ak/%s.m3u8" | 		"%s/%s.isml/%s-audio%%3d%s.norewind.m3u8" | ||||||
| 	metaBaseURI = "http://polling.bbc.co.uk/radio/nhppolling/" | 	metaURI = "https://rms.api.bbc.co.uk/v2/services/%s/segments/latest" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type meta struct { | type meta struct { | ||||||
| @ -33,7 +33,7 @@ type meta struct { | |||||||
| 
 | 
 | ||||||
| // 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(metaBaseURI + name) | 	resp, err := http.Get(fmt.Sprintf(metaURI, name)) | ||||||
| 	if resp != nil { | 	if resp != nil { | ||||||
| 		defer resp.Body.Close() | 		defer resp.Body.Close() | ||||||
| 	} | 	} | ||||||
| @ -41,46 +41,41 @@ 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 len(b) < 2 { |  | ||||||
| 		// There needs to be an enclosing () pair |  | ||||||
| 		return nil, errors.New("invalid metadata response") |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	type broadcast struct { | 	// TODO: update more completely for the new OpenAPI | ||||||
| 		Title      string // title of the broadcast | 	//  - `broadcasts/poll/bbc_radio_one` looks almost useful | ||||||
| 		Percentage int    // how far we're in | 	//  - https://rms.api.bbc.co.uk/v2/experience/inline/play/${name} | ||||||
| 	} | 	//    seems to be what we want, even provides timer/polling values | ||||||
| 	var v struct { | 	var v struct { | ||||||
| 		Packages struct { | 		Data []struct { | ||||||
| 			OnAir struct { | 			Titles struct { | ||||||
| 				Broadcasts        []broadcast | 				Primary   string  `json:"primary"` | ||||||
| 				BroadcastNowIndex uint | 				Secondary *string `json:"secondary"` | ||||||
| 			} `json:"on-air"` | 				Tertiary  *string `json:"tertiary"` | ||||||
| 			Richtracks []struct { | 			} `json:"titles"` | ||||||
| 				Artist       string | 			Offset struct { | ||||||
| 				Title        string | 				NowPlaying bool `json:"now_playing"` | ||||||
| 				IsNowPlaying bool `json:"is_now_playing"` | 			} `json:"offset"` | ||||||
| 			} | 		} `json:"data"` | ||||||
| 		} |  | ||||||
| 		Timeouts struct { |  | ||||||
| 			PollingTimeout uint `json:"polling_timeout"` |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	err = json.Unmarshal(b[1:len(b)-1], &v) | 	err = json.Unmarshal(b, &v) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, errors.New("invalid metadata response") | 		return nil, errors.New("invalid metadata response") | ||||||
| 	} | 	} | ||||||
| 	onAir := v.Packages.OnAir | 	if len(v.Data) == 0 || !v.Data[0].Offset.NowPlaying { | ||||||
| 	if onAir.BroadcastNowIndex >= uint(len(onAir.Broadcasts)) { | 		return nil, errors.New("no song is playing") | ||||||
| 		return nil, errors.New("no active broadcast") |  | ||||||
| 	} | 	} | ||||||
| 	title := onAir.Broadcasts[onAir.BroadcastNowIndex].Title | 
 | ||||||
| 	for _, rt := range v.Packages.Richtracks { | 	titles := v.Data[0].Titles | ||||||
| 		if rt.IsNowPlaying { | 	parts := []string{} | ||||||
| 			title = rt.Artist + " - " + rt.Title + " / " + title | 	if titles.Tertiary != nil { | ||||||
| 		} | 		parts = append(parts, *titles.Tertiary) | ||||||
| 	} | 	} | ||||||
| 	return &meta{timeout: v.Timeouts.PollingTimeout, title: title}, nil | 	if titles.Secondary != nil { | ||||||
|  | 		parts = append(parts, *titles.Secondary) | ||||||
|  | 	} | ||||||
|  | 	parts = append(parts, titles.Primary) | ||||||
|  | 	return &meta{timeout: 5000, title: strings.Join(parts, " - ")}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // resolveM3U8 resolves an M3U8 playlist to the first link that seems to | // resolveM3U8 resolves an M3U8 playlist to the first link that seems to | ||||||
| @ -239,15 +234,15 @@ func proxy(w http.ResponseWriter, req *http.Request) { | |||||||
| 		panic("cannot hijack connection") | 		panic("cannot hijack connection") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// E.g. `nonuk`, `sbr_low` `bbc_radio_one`, or `uk`, `sbr_high`, `bbc_1xtra` | 	// [ww]/[uk], [48000/96000]/[128000/320000], bbc_radio_one/bbc_1xtra/... | ||||||
| 	region, quality, name := m[1], m[2], m[3] | 	region, quality, name := m[1], m[2], m[3] | ||||||
| 
 | 
 | ||||||
| 	// TODO: We probably shouldn't poll the top level playlist. | 	mediaPlaylistURL := | ||||||
| 	mainPlaylistURL := fmt.Sprintf(targetURI, region, quality, name) | 		fmt.Sprintf(targetURI, region, region, name, name, name, quality) | ||||||
| 
 | 
 | ||||||
| 	// This validates the parameters as a side-effect. | 	// This validates the parameters as a side-effect. | ||||||
| 	target, err := resolveM3U8(mainPlaylistURL) | 	media, err := resolveM3U8(mediaPlaylistURL) | ||||||
| 	if err == nil && len(target) == 0 { | 	if err == nil && len(media) == 0 { | ||||||
| 		err = errors.New("cannot resolve playlist") | 		err = errors.New("cannot resolve playlist") | ||||||
| 	} | 	} | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -256,7 +251,7 @@ func proxy(w http.ResponseWriter, req *http.Request) { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	wantMeta := req.Header.Get("Icy-MetaData") == "1" | 	wantMeta := req.Header.Get("Icy-MetaData") == "1" | ||||||
| 	resp, err := http.Head(target[0]) | 	resp, err := http.Head(media[0]) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		http.Error(w, err.Error(), http.StatusInternalServerError) | 		http.Error(w, err.Error(), http.StatusInternalServerError) | ||||||
| 		return | 		return | ||||||
| @ -286,7 +281,7 @@ func proxy(w http.ResponseWriter, req *http.Request) { | |||||||
| 	go metaProc(req.Context(), name, metaChan) | 	go metaProc(req.Context(), name, metaChan) | ||||||
| 
 | 
 | ||||||
| 	chunkChan := make(chan []byte) | 	chunkChan := make(chan []byte) | ||||||
| 	go dataProc(req.Context(), mainPlaylistURL, metaint, chunkChan) | 	go dataProc(req.Context(), mediaPlaylistURL, metaint, chunkChan) | ||||||
| 
 | 
 | ||||||
| 	// dataProc may return less data near the end of a subfile, so we give it | 	// dataProc may return less data near the end of a subfile, so we give it | ||||||
| 	// a maximum count of bytes to return at once and do our own buffering. | 	// a maximum count of bytes to return at once and do our own buffering. | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user