Implementing Audio Streaming in Mobile Applications
Radio, podcasts, music streaming — audio in background with buffering. Main complexity not in playback but in stability: user goes to another app, returns — player should be alive, not recreated, synchronized with what's playing.
Architecture: Player in Service
Android. MediaBrowserServiceCompat (deprecated) or media3 MediaSessionService — player lives in separate service, Activity only displays state. MediaController binds UI to service via Binder/IPC. Activity destruction — player continues.
// In MediaSessionService
val player = ExoPlayer.Builder(this).build()
val mediaSession = MediaSession.Builder(this, player).build()
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession
iOS. AVAudioSession.sharedInstance().setCategory(.playback) + UIBackgroundModes: audio in Info.plist. Player created in AppDelegate or separate singleton, survives ViewController recreation.
Buffering and Chunk Caching
ExoPlayer buffers forward automatically. Control via DefaultLoadControl:
val loadControl = DefaultLoadControl.Builder()
.setBufferDurationsMs(
15_000, // minBufferMs
50_000, // maxBufferMs
2_500, // bufferForPlaybackMs
5_000 // bufferForPlaybackAfterRebufferMs
)
.build()
minBufferMs = 15000 — player starts playback after 2.5 s buffer accumulation, holds in memory up to 50 s. On network loss — continues from buffer 50 s, then pause with loading indicator.
For disk caching (so not re-downloading on track return):
val cache = SimpleCache(cacheDir, LeastRecentlyUsedCacheEvictor(100 * 1024 * 1024))
val cacheDataSourceFactory = CacheDataSource.Factory()
.setCache(cache)
.setUpstreamDataSourceFactory(DefaultHttpDataSource.Factory())
iOS. AVURLAsset doesn't cache to disk natively. For caching — AVAssetResourceLoader with custom AVAssetResourceLoadingDelegate, write data to file on load. Or URLCache for HTTP segments with HLS.
Streaming Formats
| Protocol | Latency | Use |
|---|---|---|
| HTTP progressive | none | podcasts, single file |
| HLS | 3–30 s | music streaming |
| Icecast/Shoutcast (MP3/AAC stream) | < 1 s | internet radio |
| OPUS over WebRTC | < 0.2 s | voice chats |
Icecast streams (Content-Type: audio/mpeg with infinite body) — ExoPlayer handles as ProgressiveMediaSource. On iOS — AVPlayer handles natively via http:// stream URL.
IceCast Metadata
Radio stations transmit metadata (track title) directly in stream via ICY headers. ExoPlayer IcyDecoder reads automatically, get via Player.Listener.onMediaMetadataChanged. iOS not supported natively — need custom AVAssetResourceLoadingDelegate with ICY parsing.
Network Loss Handling
Streaming — unstable environment. On connection loss, player should auto-reconnect, not just stop.
ExoPlayer: LoadControl.getBackBufferDurationUs() keeps already-played data in memory. On reconnect — buffer not lost, playback continues from where it stopped. For radio stream (live), reconnect means getting actual fragment, not what was before break.
On iOS: AVPlayer.automaticallyWaitsToMinimizeStalling = true — player decides when to buffer. On HLS stream break, subscribe to AVPlayerItem.status KVO, on .failed with NSURLErrorNetworkConnectionLost — replaceCurrentItem(with:) with new AVPlayerItem from same URL after 3–5 seconds.
Timeline
Basic audio streaming with background playback and media controls — 2 days. Disk chunk caching, Icecast metadata handling and offline mode — 3–4 days.







