Skip to content

cstsortan/hls2mp4

Repository files navigation

hls2mp4

A C library that converts HLS streams (.m3u8) to MP4 files without FFmpeg or any GPL/LGPL code. Includes a Flutter FFI plugin for iOS, Android, and macOS.

Features

  • Parses both master and media M3U8 playlists
  • Automatically selects highest-bandwidth variant from master playlists
  • Supports AES-128-CBC encrypted segments (with key caching)
  • Demuxes MPEG-TS to extract H.264 video and AAC audio
  • Muxes into a standard MP4 container
  • Progress and logging callbacks
  • Concurrent segment prefetching for faster downloads
  • Per-segment retry with configurable timeout
  • No global state; fully reentrant
  • C99, no compiler extensions

Limitations

  • MPEG-TS segments only — The library supports HLS streams with MPEG-TS (.ts) segments. Streams using fMP4 (fragmented MP4 / .m4s) segments are not currently supported. You can identify fMP4 playlists by the #EXT-X-MAP tag in the media playlist.
  • VOD only — Live streams (playlists without #EXT-X-ENDLIST) are not supported. The library downloads all segments listed in the playlist; live playlists with a sliding window will fail or produce incomplete output.
  • H.264 + AAC only — The demuxer extracts H.264 video and AAC audio. Other codecs (H.265/HEVC, AC-3, EAC-3) are not demuxed.

Dependencies

All dependencies are permissively licensed:

Library License Purpose
libcurl MIT HTTP fetching
mbedTLS Apache 2.0 AES-128-CBC decryption
minimp4 Public Domain MP4 muxing (vendored)

Installing Dependencies

Ubuntu / Debian

sudo apt update
sudo apt install libcurl4-openssl-dev libmbedtls-dev cmake build-essential

macOS (Homebrew)

brew install curl mbedtls@3 cmake

Note: mbedTLS 4.x removed the legacy AES API. Use mbedtls@3 (3.6.x) which provides mbedtls/aes.h. Pass -DCMAKE_PREFIX_PATH="/opt/homebrew/opt/curl;/opt/homebrew/opt/mbedtls@3" to CMake if the keg-only packages aren't found automatically.

Building

Using the build script:

./build.sh build       # Build all targets
./build.sh test        # Build and run unit tests
./build.sh run         # Build and run the video benchmark
./build.sh clean       # Remove build directory

Or manually with CMake:

mkdir build && cd build
cmake .. -DCMAKE_PREFIX_PATH="/opt/homebrew/opt/curl;/opt/homebrew/opt/mbedtls@3"
cmake --build .
ctest --output-on-failure   # run tests

To disable tests: cmake -DBUILD_TESTS=OFF ..

C API

The library exposes two functions:

#include <hls2mp4.h>

int         hls2mp4_convert(const hls2mp4_options *opts);
const char *hls2mp4_error_string(int code);

Options

typedef struct {
    const char           *input_url;    // URL to .m3u8 playlist
    const char           *output_path;  // Local path for output .mp4
    hls2mp4_progress_cb   on_progress;  // Optional, may be NULL
    hls2mp4_log_cb        on_log;       // Optional, may be NULL
    void                 *userdata;     // Passed to callbacks
    int                   timeout_ms;   // HTTP timeout, 0 = default (10s)
    int                   max_retries;  // Per-segment retries, 0 = default (3)
} hls2mp4_options;

Error Codes

Code Constant Meaning
0 HLS2MP4_OK Success
-1 HLS2MP4_ERR_NETWORK HTTP request failed
-2 HLS2MP4_ERR_PARSE Playlist parse error
-3 HLS2MP4_ERR_DEMUX TS demux error
-4 HLS2MP4_ERR_DECRYPT AES decryption failed
-5 HLS2MP4_ERR_MUXER MP4 muxer error
-6 HLS2MP4_ERR_IO File I/O error

Example

#include <hls2mp4.h>
#include <stdio.h>

static void on_progress(int done, int total, void *ud) {
    (void)ud;
    printf("\r[%d/%d] segments", done, total);
    fflush(stdout);
}

int main(void) {
    hls2mp4_options opts = {
        .input_url   = "https://example.com/stream/playlist.m3u8",
        .output_path = "output.mp4",
        .on_progress = on_progress,
        .timeout_ms  = 15000,
        .max_retries = 3,
    };

    int ret = hls2mp4_convert(&opts);
    if (ret != HLS2MP4_OK) {
        fprintf(stderr, "Error: %s\n", hls2mp4_error_string(ret));
        return 1;
    }

    printf("\nDone!\n");
    return 0;
}

Linking

With CMake (after installing or using add_subdirectory):

find_package(hls2mp4 REQUIRED)
target_link_libraries(myapp PRIVATE hls2mp4::hls2mp4)

Or link directly:

gcc -o myapp myapp.c -lhls2mp4 -lcurl -lmbedtls -lmbedcrypto

Flutter Plugin

The hls2mp4_flutter/ directory contains a Flutter FFI plugin that wraps the C library for mobile and desktop apps.

Platforms

Platform Status
iOS Supported
Android Supported
macOS Supported

Dart API

import 'package:hls2mp4_flutter/hls2mp4_flutter.dart';

final result = await Hls2Mp4Converter.convert(
  inputUrl: 'https://example.com/stream/playlist.m3u8',
  outputPath: '/path/to/output.mp4',
  timeoutMs: 30000,
  maxRetries: 3,
  onProgress: (progress) {
    print('${(progress.fraction * 100).toStringAsFixed(0)}%');
  },
);

if (result.success) {
  print('Conversion complete!');
} else {
  print('Error: ${result.errorMessage}');
}

The conversion runs on a background isolate and never blocks the UI thread. Progress callbacks are delivered in real time via SendPort.

Building the Flutter Plugin

cd hls2mp4_flutter

# Generate FFI bindings (only needed after changing hls2mp4.h)
dart run ffigen --config ffigen.yaml

# Build Android prebuilt dependencies (first time only)
cd android && ./build_android_deps.sh && cd ..

# Run the example app
cd example
flutter run -d macos   # or -d ios, -d android

Android

Android requires cross-compiled curl and mbedTLS static libraries. Run the build script once before your first Android build:

cd hls2mp4_flutter/android
./build_android_deps.sh

This downloads curl 8.12.1 and mbedTLS 3.6.3, cross-compiles them for arm64-v8a, armeabi-v7a, and x86_64, and places the results in android/prebuilt/.

Example App

The example app at hls2mp4_flutter/example/ demonstrates converting 11 public HLS streams to MP4 with a progress UI and file opening via the open_file package.

Test Streams

The benchmark and example app use these public HLS streams:

Stream Source Protocol
Tears of Steel Unified Streaming HTTPS
fMP4 BIPBOP Apple HTTPS
Tears of Steel (MP4) Unified Streaming HTTPS
Live Test 1 Akamai HTTPS
Live Test 2 Akamai HTTPS
Dolby Stereo CloudFront HTTP
Dolby Multichannel CloudFront HTTP
Dolby Multilanguage CloudFront HTTP
Azure Promo 1 Azure Media Services HTTP
Azure Promo 2 Azure Media Services HTTP
Azure 4K Azure Media Services HTTP

Project Structure

include/hls2mp4.h       Public API header
src/
  hls2mp4.c             Main conversion logic + segment prefetching
  http.c                HTTP client (libcurl wrapper)
  m3u8.c                M3U8 playlist parser
  ts_demux.c            MPEG-TS demultiplexer
  aes.c                 AES-128-CBC decryption (mbedTLS wrapper)
  muxer.c               MP4 muxer (minimp4 wrapper)
third_party/
  minimp4.h             Vendored MP4 muxing library
tests/
  run_videos.c          Benchmark with 11 HLS streams
  compare_ffmpeg.sh     Compares output against ffmpeg reference
  fix_loop.sh           Iterative fix loop using Claude Code
  test_m3u8.c           M3U8 parser unit tests
  test_ts_demux.c       TS demuxer unit tests
  test_aes.c            AES decryption unit tests
hls2mp4_flutter/        Flutter FFI plugin (iOS, Android, macOS)
  lib/                  Dart API wrapper
  src/                  Symlinks to ../src, ../include, ../third_party
  android/              Android build config + prebuilt dependency script
  example/              Flutter example app

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors