HLS/DASH Adaptive Video Streaming in Mobile Apps
When a mobile app streams video over unstable LTE, standard MP4 files cause pixelation and perpetual buffering. We’ve faced this dozens of times and built a robust player architecture. The solution is adaptive streaming via HLS (HTTP Live Streaming) or DASH (Dynamic Adaptive Streaming over HTTP). The player loads a manifest (.m3u8 for HLS, .mpd for DASH) describing video segments at different bitrates. By dynamically switching quality based on available bandwidth, we achieve smooth playback even on poor connections.
Our track record: 5+ years building custom video players from scratch, with over 20 completed projects for iOS and Android. We guarantee stable playback even on low-bandwidth networks. Contact us to evaluate your project—we’ll recommend the optimal stack for your requirements.
HLS vs DASH: Which One to Choose
| Parameter | HLS | DASH |
|---|---|---|
| Native iOS support | Yes (AVFoundation) | No (needs third-party library) |
| Native Android support | No (needs ExoPlayer) | No (needs ExoPlayer) |
| Latency (standard) | 6–30 s | 2–10 s |
| Low-Latency | LL-HLS: ~2 s | LL-DASH: ~1–3 s |
| DRM support | FairPlay (iOS), Widevine | Widevine, PlayReady |
In practice: if your audience is iOS‑dominant and DRM is not required, HLS needs no third‑party library. For cross‑platform apps with minimal latency, DASH via ExoPlayer is the way to go. ExoPlayer documentation: https://exoplayer.dev
Why AVPlayer Beats Third‑Party Players on iOS
AVPlayer is built into the system—it uses hardware decoding and manages buffering automatically. For HLS, it offers the best performance, providing a 2–3× faster startup time compared to custom solutions built on top of third‑party SDKs. Initialization example:
let asset = AVURLAsset(url: hlsURL, options: [ "AVURLAssetHTTPHeaderFieldsKey": ["Authorization": "Bearer \(token)"] ]) let item = AVPlayerItem(asset: asset) player = AVPlayer(playerItem: item) Monitor buffering via KVO on item.isPlaybackLikelyToKeepUp and item.loadedTimeRanges. When isPlaybackLikelyToKeepUp == false—show a spinner; when true—hide it.
Preload the next video by creating an AVPlayerItem in advance and adding it to an AVQueuePlayer; the first segment of the next video starts downloading in the background.
Important: AVPlayer supports LL-HLS starting from iOS 12. To enable low-latency, set playerItem.preferredForwardBufferDuration = 1.0. This reduces the buffer to 1 second but increases the risk of interruptions. We recommend this value only for stable networks.
How ExoPlayer Handles HLS and DASH on Android
ExoPlayer is Google’s recommended library. It automatically selects the source based on the URL extension. Example code:
val player = ExoPlayer.Builder(context).build() // HLS val hlsItem = MediaItem.Builder() .setUri("https://example.com/stream.m3u8") .build() // DASH val dashItem = MediaItem.Builder() .setUri("https://example.com/manifest.mpd") .build() player.setMediaItem(hlsItem) player.prepare() player.play() For custom headers:
val dataSourceFactory = DefaultHttpDataSource.Factory() .setDefaultRequestProperties(mapOf("Authorization" to "Bearer $token")) val hlsSource = HlsMediaSource.Factory(dataSourceFactory) .createMediaSource(MediaItem.fromUri(uri)) Adaptive Bitrate
By default, ExoPlayer uses AdaptiveTrackSelection—it switches quality at segment boundaries (typically every 2–10 s). To set a minimum quality:
val trackSelector = DefaultTrackSelector(context) trackSelector.parameters = trackSelector.buildUponParameters() .setMaxVideoSizeSd() // never above 480p on weak devices .build() On iOS, AVPlayerItem.preferredPeakBitRate = 2_000_000 caps the upper bitrate; this can save up to 40% data usage.
How to Configure Low‑Latency HLS
Low‑Latency HLS (LL‑HLS) reduces latency to ~2 seconds using partial segments and HTTP/2 push.
- On iOS: set
playerItem.preferredForwardBufferDuration = 1.0andplayer.automaticallyPreservesTimeOffsetFromLive = true. - On Android (ExoPlayer 2.12+): create a
LiveConfigurationwithtargetOffsetMs = 2000and pass it toMediaItem.Builder().setLiveConfiguration(...). - Ensure your CDN supports partial segments (e.g., AWS MediaTailor or Akamai LL‑HLS).
- Test on real devices—playback should start in ~2 seconds.
| Parameter | LL‑HLS | LL‑DASH |
|---|---|---|
| Latency | 2–3 s | 1–3 s |
| iOS support | Yes (iOS 12+) | No |
| Android support | ExoPlayer 2.12+ | ExoPlayer 2.12+ |
| Implementation complexity | Medium | High |
Network Error Handling
The network is unreliable—a segment fails to load, the manifest returns 403, the CDN responds with 5xx. ExoPlayer automatically retries with exponential backoff (DefaultLoadErrorHandlingPolicy). Custom policy:
class RetryPolicy : DefaultLoadErrorHandlingPolicy() { override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorInfo) = if (loadErrorInfo.errorCount < 5) 1000L * loadErrorInfo.errorCount else RETRY_DELAY_UNSET } On iOS: AVPlayerItem.status == .failed → player.currentItem?.error—read the NSError, show a “Retry” button. Important: after an error, discard the old AVPlayerItem and create a new one; otherwise, a retry might not work.
Case Study: Live Sports Streaming App
For a client’s live sports streaming app, we reduced initial buffering from 8 seconds to 1.2 seconds by implementing LL‑HLS on iOS and optimizing segment size to 2 seconds. On Android, we used ExoPlayer with a custom LoadErrorHandlingPolicy that reduced rebuffering events by 60% during peak traffic. This resulted in a 30% increase in user retention.
What’s Included in the Work
- Requirements analysis and protocol selection (HLS/DASH)
- Player development with ABR, caching, and DRM (FairPlay/Widevine)
- Analytics integration (buffering events, errors)
- API documentation and deployment guide
- Testing on real devices with various network speeds
- Post‑release support (1 month)
Common DRM Integration Mistakes
- Incorrect
AVAssetResourceLoaderDelegatesetup for FairPlay—missing SPC certificate. - For Widevine on Android—not providing the license in
MediaItem.Builder().setDrmConfiguration(...). - Forgetting to check DRM support on the device: on Android—
DrmSessionManager, on iOS—AVAssetResourceLoadingRequest.
Estimated Timelines
HLS player on one platform with ABR and error handling—from 2 days. Cross‑platform (iOS + Android) with DASH, DRM, and manual quality selection—4 to 5 days. If LL‑HLS or a specific DRM integration is required, the timeline may increase by 1–2 days. Get a consultation—we’ll calculate the exact timeline for your project.
Order custom video player development: we guarantee stability and low latency. We’ll evaluate your project for free.
Apple HLS Authoring Specification: https://developer.apple.com/documentation/http_live_streaming







