[READ-ONLY] Mirror of https://github.com/aaronateataco/SonicBridge. proxy for web radios sonicradio.vercel.app
0

Configure Feed

Select the types of activity you want to include in your feed.

Update stream handling and improve audio component behavior; change image URLs for stations

aaronateataco (Jul 15, 2026, 7:03 PM +0100) 7f98ab87 87765fa5

+39 -13
+24 -10
api/[station_id].py
··· 33 33 return 34 34 35 35 config = STATIONS[token] 36 + slug = config['slug'] 37 + pool = config['pool'] 36 38 37 - # Build the BBC stream URL 38 - # This is the actual Akamai stream endpoint that BBC uses 39 - stream_url = f"https://a.files.bbci.co.uk/mediaconnection/live/akamai/{config['pool']}/output/index.m3u" 39 + # Direct HLS endpoint - returns m3u8 playlist with segment URLs 40 + # Browser will fetch segments directly from BBC's CDN, avoiding timeouts 41 + hls_url = f"http://as-hls-ww-live.akamaized.net/{pool}/live/ww/{slug}/{slug}.isml/{slug}-audio=96000.norewind.m3u8" 40 42 41 43 try: 42 - # Fetch the actual stream URL 43 - with urllib.request.urlopen(stream_url, timeout=10) as response: 44 - stream_data = response.read() 44 + # Fetch the HLS playlist 45 + req = urllib.request.Request(hls_url, headers={ 46 + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36' 47 + }) 48 + with urllib.request.urlopen(req, timeout=10) as response: 49 + playlist_data = response.read() 45 50 46 - # Return the stream data 51 + # Return the HLS playlist with proper headers 47 52 self.send_response(200) 48 53 self.send_header('Content-type', 'application/vnd.apple.mpegurl') 49 54 self.send_header('Access-Control-Allow-Origin', '*') 50 - self.send_header('Content-Length', len(stream_data)) 55 + self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS') 56 + self.send_header('Access-Control-Allow-Headers', 'Content-Type') 57 + self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate') 58 + self.send_header('Pragma', 'no-cache') 59 + self.send_header('Expires', '0') 60 + self.send_header('Content-Length', len(playlist_data)) 51 61 self.end_headers() 52 - self.wfile.write(stream_data) 62 + self.wfile.write(playlist_data) 53 63 except Exception as e: 54 - self.send_error(502, f"Failed to fetch stream: {str(e)}") 64 + self.send_response(502) 65 + self.send_header('Content-type', 'text/plain') 66 + self.send_header('Access-Control-Allow-Origin', '*') 67 + self.end_headers() 68 + self.wfile.write(f"Failed to fetch stream: {str(e)}".encode()) 55 69 56 70 def do_OPTIONS(self): 57 71 self.send_response(200)
+1 -1
api/stations.py
··· 29 29 "id": token, 30 30 "name": config["name"], 31 31 "description": f"Live {config['name']} stream", 32 - "image": f"https://images.unsplash.com/photo-1516280440614-37939bbacd81?auto=format&fit=crop&w=600&q=80" 32 + "image": f"https://sounds.files.bbci.co.uk/3.9.4/networks/{config['slug']}/colour_default.svg" 33 33 }) 34 34 35 35 import json
+1 -1
app.py
··· 37 37 "id": token, 38 38 "name": config["name"], 39 39 "description": f"Live {config['name']} stream", 40 - "image": f"https://images.unsplash.com/photo-1516280440614-37939bbacd81?auto=format&fit=crop&w=600&q=80" 40 + "image": f"https://sounds.files.bbci.co.uk/3.9.4/networks/{config['slug']}/colour_default.svg" 41 41 }) 42 42 return stations 43 43
+13 -1
src/App.jsx
··· 79 79 setCurrent(station); 80 80 setIsBuffering(true); 81 81 audio.src = streamUrl(station); 82 + audio.crossOrigin = "anonymous"; 82 83 audio.play().catch(() => setIsBuffering(false)); 83 84 } 84 85 ··· 151 152 <footer className={`nowplaying${current ? " nowplaying-active" : ""}`}> 152 153 <audio 153 154 ref={audioRef} 155 + onCanPlay={() => { 156 + setIsBuffering(false); 157 + }} 154 158 onPlaying={() => { 155 159 setIsPlaying(true); 156 160 setIsBuffering(false); 157 161 }} 158 162 onPause={() => setIsPlaying(false)} 159 163 onWaiting={() => setIsBuffering(true)} 160 - onError={() => setIsBuffering(false)} 164 + onStalled={() => setIsBuffering(true)} 165 + onError={(e) => { 166 + setIsBuffering(false); 167 + console.error("Audio error:", e.currentTarget.error); 168 + }} 169 + onEnded={() => { 170 + setIsPlaying(false); 171 + setIsBuffering(false); 172 + }} 161 173 /> 162 174 {current ? ( 163 175 <>