HTTP Live Streaming (HLS), characterized by the .m3u8 playlist format, has become the de facto standard for video delivery on the web. However, when transitioning from web browsers to native mobile applications, developers face vastly different playback architectures. iOS and Android handle M3U8 parsing, adaptive bitrate (ABR) switching, and DRM entirely differently at the OS level.
In this expert guide, we will explore how to reliably play M3U8 streams on iOS and Android native players. We will cover Apple's AVFoundation, Google's ExoPlayer (now part of AndroidX Media3), background playback configurations, and how to debug mobile-specific playback failures.
iOS Implementation: AVPlayer and AVFoundation
Because Apple invented the HLS protocol, iOS provides first-class, native support for M3U8 streams out of the box. You do not need third-party libraries to achieve highly optimized adaptive bitrate streaming on iOS.
The core framework for media playback is AVFoundation, and the primary class used to control playback is AVPlayer. For standard user interfaces, developers wrap this in an AVPlayerViewController.
Basic Swift Implementation
Loading an M3U8 URL in Swift is essentially identical to loading a standard MP4 file. The OS automatically detects the playlist, parses the segments, and manages network buffers.
import UIKit
import AVKit
import AVFoundation
class VideoPlayerViewController: UIViewController {
var playerViewController = AVPlayerViewController()
var playerView = AVPlayer()
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "https://example.com/master.m3u8") else { return }
// Initialize the player
playerView = AVPlayer(url: url)
playerViewController.player = playerView
// Present the player
self.present(playerViewController, animated: true) {
self.playerViewController.player?.play()
}
}
}
AirPlay and PIP
Because AVPlayerViewController is a native component, you inherit AirPlay streaming and Picture-in-Picture (PiP) capabilities automatically, provided you have configured your app's audio session properly.
FairPlay DRM
If your M3U8 stream is protected by Apple FairPlay, you must implement the AVAssetResourceLoaderDelegate to intercept the key request, fetch the SPC (Server Playback Context), and return the CKC (Content Key Context) to the player.
Android Implementation: ExoPlayer / Media3
Historically, Android's native MediaPlayer had notoriously buggy and inconsistent support for HLS. It struggled with adaptive bitrate switching, specific audio codecs, and live stream boundaries.
Today, the absolute industry standard for Android video playback is ExoPlayer, which is now officially part of the Jetpack Media3 suite. ExoPlayer is an application-level media player that provides highly customizable HLS parsers and renderers.
Basic Kotlin Implementation (Media3)
To play an M3U8 stream using Media3, you must construct a MediaItem and pass it to the ExoPlayer instance.
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
class PlayerActivity : AppCompatActivity() {
private var player: ExoPlayer? = null
private lateinit var playerView: PlayerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
playerView = findViewById(R.id.player_view)
// Initialize ExoPlayer
player = ExoPlayer.Builder(this).build()
playerView.player = player
// Build the MediaItem pointing to the M3U8
val hlsUrl = "https://example.com/master.m3u8"
val mediaItem = MediaItem.fromUri(hlsUrl)
// Prepare and play
player?.setMediaItem(mediaItem)
player?.prepare()
player?.playWhenReady = true
}
override fun onDestroy() {
super.onDestroy()
player?.release()
}
}
Handling Advanced HLS Features on Android
- Adaptive Bitrate Control: ExoPlayer uses a
DefaultTrackSelectorwhich monitors bandwidth. You can customize this to limit the maximum bitrate if the user is on a cellular network. - Widevine DRM: Unlike iOS, Android uses Widevine. You can attach DRM configurations directly to the
MediaItem.Builderby specifying the license server URL.
Cross-Platform Frameworks (React Native & Flutter)
If you are building an app using a cross-platform framework, you do not need to write Swift and Kotlin natively. The community has built excellent bridges that utilize AVPlayer on iOS and ExoPlayer on Android under the hood.
React Native
Use react-native-video. Pass the M3U8 URL to the source={{ uri: '...' }} prop. It handles HLS natively and provides extensive callback events for buffering states.
Flutter
Use the official video_player package. Initialize a VideoPlayerController.networkUrl(). For advanced DRM and HLS track selection in Flutter, the better_player package is highly recommended.
Troubleshooting Mobile Playback Issues
When an M3U8 plays fine on the web but fails in a native mobile app, developers often look in the wrong places. Here are the crucial engineering differences to investigate:
1. CORS Does Not Apply to Native Players
On the web, Cross-Origin Resource Sharing (CORS) blocks playback if headers are missing. Native mobile apps do not enforce CORS. If a stream works natively but fails in a browser (or a mobile WebView), CORS is your culprit. Conversely, if it fails natively but works on the web, look at DRM or codecs, not CORS.
2. ATS and Cleartext Traffic (HTTP vs HTTPS)
Both iOS and Android strictly enforce secure connections by default.
- iOS: App Transport Security (ATS) will block any M3U8 URL or segment URL starting with
http://. You must addNSAppTransportSecurityexceptions in yourInfo.plistto permit insecure loads. - Android: Android 9+ blocks Cleartext traffic. You must add
android:usesCleartextTraffic="true"to yourAndroidManifest.xmlor configure a Network Security Config to allow specific HTTP domains.
Important: A common failure occurs when the Master Playlist is HTTPS, but the internal segment paths return absolute HTTP URLs. The OS will parse the manifest successfully, but silently fail to download the segments.
3. Codec Compatibility
Mobile hardware decoders are opinionated. While modern devices support H.264, H.265 (HEVC), and AAC, older Android devices may struggle with certain H.264 profiles (like High Profile). Always ensure your M3U8 master playlist accurately defines the CODECS attribute so the native player can quickly determine hardware compatibility.
Summary
Building a robust mobile streaming experience requires leaning into OS-specific tools: AVPlayer on iOS and ExoPlayer on Android. By understanding how these engines parse M3U8 manifests and respecting platform-specific security policies regarding HTTP traffic and DRM, engineers can deliver flawless HLS video playback across the mobile ecosystem.