Download Events

Pre-requisite: The user has to be signed in to perform the following operations.

M3U8ToMP4Converter turns an event’s HLS playlist into one or more MP4 files. It downloads the playlist, rewrites it against the segment host, and runs FFmpeg over the result.


Downloading an event

The short overload covers the common case:

M3U8ToMP4Converter.convert(
  context = context,
  m3u8Url = url,
  onSuccess = { files -> },
  onError = { message -> }
)

onSuccess receives a List<File> of MP4s written to the app’s cache directory; onError receives a String describing the failure. Both fire on a background thread — post to the main thread before touching UI.

This overload is not the full one with defaults filled in. It is a separate four-argument function that hard-codes no interpolation, 2_000_000L, 1920 and 1080, and passes no hardware config. To change any of those, call the full overload below.


The URL it needs

Pass the same stamped playlist URL you would give the player — see Event Playback for how to build one. The converter imposes two hard requirements on it:

  • it must contain Authorization=, which is where the converter reads the token from, and
  • it must end in cam.m3u8, which is where the converter splits off the base URL for the segments.

A URL missing either is not merely unauthorized — the converter cannot parse it, so check the URL before calling rather than relying on onError. A playlist that fails to download reports Error downloading m3u8 file from <url>; a conversion that fails reports Error converting m3u8 to mp4: <returnCode> with the FFmpeg return code.


Scaling options

The full overload takes four more parameters:

  • shouldInterpolate (default false): Upscale the output. It takes effect only when minMp is at least as large as the source’s pixel count — on footage already bigger than minMp it does nothing.
  • minMp (default 2_000_000L): The pixel count at or below which the output is scaled up.
  • minWidth (default 1920) and minHeight (default 1080): The target dimensions used when scaling.
  • hardwareConfig (default null): See below.
M3U8ToMP4Converter.convert(
  context = context,
  m3u8Url = url,
  onSuccess = { files -> },
  onError = { message -> },
  shouldInterpolate = true,
  minMp = 2_000_000L,
  minWidth = 1920,
  minHeight = 1080,
  hardwareConfig = device.getHardwareConfig()
)

Multi-sensor cameras

A camera with several lenses records them stitched into one frame. Passing device.getHardwareConfig() splits them apart: the converter crops one MP4 per entry in the config’s stitchedLensSensors, and onSuccess receives that list. Leave hardwareConfig as null to get the single stitched file instead.

Sensors are converted one after another, not in parallel, and the first failure stops the run and reports Error converting sensor <index>: <returnCode> — a different message from the single-file path, and any sensors already converted are discarded.


Threading and output

Without interpolation the converter avoids re-encoding where it can: HEVC footage on Android 11 and above is stream-copied into the MP4 container, which is fast and lossless. Everything else is re-encoded to H.264, and interpolation always re-encodes.

convert launches its own coroutine on Dispatchers.IO and returns immediately. There is no job handle and nothing to cancel, so a conversion runs to completion even if the screen that started it has gone away — take that into account before starting one per item in a list.

The MP4s are temporary files in context.cacheDir. Move them somewhere durable in onSuccess if the user is meant to keep them; anything left in the cache directory can be reclaimed by the system.


This site uses Just the Docs, a documentation theme for Jekyll.