Solving Token Authentication Issues in Flutter Video Streaming with HLS & DASH

03 May 2025
  • Mobile

The Problem

When streaming secure HLS or DASH videos (e.g., .m3u8 playlists), simply passing a token on the initial playlist request is not enough. Each media segment (.ts) request also needs token authentication. If you're using ExoPlayer on Android or AVPlayer on iOS, and you're launching the video via a Flutter app, you might run into issues where:

  • The playlist loads fine âś…
  • But the segments fail to load ❌ due to missing headers

Android: ExoPlayer + Token in Segment Requests

Use a custom DataSource.Factory that injects your token into each segment request.

Kotlin
class ResolvingDataSource(
    private val context: Context,
    private val token: String
) : DataSource.Factory {

    override fun createDataSource(): DataSource {
        val defaultDataSourceFactory = DefaultHttpDataSource.Factory()
        defaultDataSourceFactory.setDefaultRequestProperties(
            mapOf("Authorization" to token)
        )
        return defaultDataSourceFactory.createDataSource()
    }
}

When initializing ExoPlayer:

Kotlin
// Usage:
val mediaItem = MediaItem.fromUri(videoUrl)
val dataSourceFactory = ResolvingDataSource(context, "Bearer eyJhbGciOiJI...")
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
exoPlayer.setMediaSource(mediaSource)
exoPlayer.prepare()
exoPlayer.playWhenReady = true

iOS: AVPlayer + Token Auth via Proxy

AVPlayer doesn’t allow direct header injection per segment. Workaround:

  • Create a local proxy server using GCDWebServer.
  • Download the original .m3u8 playlist, rewrite it to point to your local server.
  • Intercept segment requests, append the token in headers, and fetch them from the real server.
Kotlin
func serveVideoLocally(from url: URL, token: String, completion: @escaping (URL) -> Void) {
    let server = GCDWebServer()

    server.addHandler(forMethod: "GET", pathRegex: "/.*", request: GCDWebServerRequest.self) { request in
        let targetURL = resolveActualSegmentURL(request: request)
        var req = URLRequest(url: targetURL)
        req.setValue(token, forHTTPHeaderField: "Authorization")

        let response = try? Data(contentsOf: targetURL)
        return GCDWebServerDataResponse(data: response ?? Data(), contentType: "video/MP2T")
    }

    try? server.start(options: [GCDWebServerOption_Port: 8080])
    let rewrittenPlaylistURL = rewritePlaylist(originalURL: url)
    completion(rewrittenPlaylistURL)
}

func playHLS(from localURL: URL) {
    let player = AVPlayer(url: localURL)
    player.play()
}

Flutter Integration: Bridging the Gap

To connect your Flutter code with the custom native ExoPlayer (Android) and AVPlayer (iOS) logic, you’ll use MethodChannels. This allows Dart code to call native functions.

  • Set Up Platform Channel in Flutter
    Kotlin
    import 'package:flutter/services.dart';
    
    class NativeVideoPlayer {
      static const _channel = MethodChannel('com.rutu.native_video');
    
      /// Plays video with token-authenticated HLS or DASH stream
      static Future<void> playVideo({
        required String url,
        required String token,
      }) async {
        try {
          await _channel.invokeMethod('playVideo', {
            'url': url,
            'token': token,
          });
        } on PlatformException catch (e) {
          print("Error playing video: ${e.message}");
        }
      }
    }
  • How to Use in Flutter UI
    Kotlin
    ElevatedButton(
      onPressed: () {
        NativeVideoPlayer.playVideo(
          url: 'https://yourdomain.com/secure/video.m3u8',
          token: 'Bearer eyJhbGciOiJIUzI1NiIsInR...',
        );
      },
      child: const Text('Play Video'),
    ),
  • Android Native Code (MainActivity.kt or a dedicated platform handler)
    Kotlin
    private val CHANNEL = "com.rutu.native_video"
    
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
            call, result ->
            if (call.method == "playVideo") {
                val url = call.argument<String>("url")!!
                val token = call.argument<String>("token")!!
                // Call your custom ExoPlayer setup with ResolvingDataSource
                playVideoWithToken(url, token)
                result.success(null)
            } else {
                result.notImplemented()
            }
        }
    }
    
    fun playVideoWithToken(url: String, token: String) {
        val context = this
        val mediaItem = MediaItem.fromUri(url)
        val dataSourceFactory = ResolvingDataSource(context, token)
        val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
        val player = ExoPlayer.Builder(context).build()
        player.setMediaSource(mediaSource)
        player.prepare()
        player.playWhenReady = true
    }
  • iOS Native Code (AppDelegate.swift or a separate Swift file)
    Swift
    let controller = window.rootViewController as! FlutterViewController
    let channel = FlutterMethodChannel(name: "com.rutu.native_video", binaryMessenger: controller.binaryMessenger)
    channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
        if call.method == "playVideo",
           let args = call.arguments as? [String: Any],
           let urlStr = args["url"] as? String,
           let token = args["token"] as? String,
           let videoURL = URL(string: urlStr) {
    
            serveVideoLocally(from: videoURL, token: token) { localURL in
                playHLS(from: localURL)
                result(nil)
            }
        } else {
            result(FlutterMethodNotImplemented)
        }
    }
  • iOS Native Code (AppDelegate.swift or a separate Swift file)
    Swift
    let controller = window.rootViewController as! FlutterViewController
    let channel = FlutterMethodChannel(name: "com.rutu.native_video", binaryMessenger: controller.binaryMessenger)
    channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
        if call.method == "playVideo",
           let args = call.arguments as? [String: Any],
           let urlStr = args["url"] as? String,
           let token = args["token"] as? String,
           let videoURL = URL(string: urlStr) {
    
            serveVideoLocally(from: videoURL, token: token) { localURL in
                playHLS(from: localURL)
                result(nil)
            }
        } else {
            result(FlutterMethodNotImplemented)
        }
    }

Read More Blogs!

How to Build a Powerful AI Chatbot Using LangChain and Firebase: A Step-by-Step Guide

How to Build a Powerful AI Chatbot Using LangChain and Firebase: A Step-by-Step Guide

4 April 2025
  • Artificial Intelligence
The Future of JavaScript Runtime: Why Deno is Gaining Popularity

The Future of JavaScript Runtime: Why Deno is Gaining Popularity

29 March 2025
  • Backend