Skip to content

FragmentedMp4Extractor marks fragmented MP4 as unseekable when mfra box is present but no sidx box #3088

@brAzzi64

Description

@brAzzi64

Version

Media3 1.8.0

More version details

No response

Devices that reproduce the issue

Pixel 8 Pro (emulator, API 36), physical Pixel 8 Pro (Android 16.0)

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Yes

Reproduction steps

  1. Produce a fragmented MP4 file using FFmpeg with standard flags:
    ffmpeg -i input.wav -c:a aac -b:a 128k -movflags empty_moov+default_base_moof+separate_moof -frag_duration 1000000 output.mp4
  2. Play the file using ProgressiveMediaSource with default extractors (or via exoPlayer.setMediaItem())
  3. Attempt to seek

Expected result

Seeking works. The file contains an mfra (Movie Fragment Random Access) box at the end, which is defined in ISO 14496-12 §8.8.9 specifically to enable random access in fragmented MP4 files. The mfra contains tfra entries mapping presentation times to moof byte offsets, and an mfro trailer for locating the mfra from the end of the file.

The file structure is:

ftyp  size=28       offset=0
moov  size=701      offset=28       (empty moov, as expected for fMP4)
moof  size=276      offset=729      <-- ExoPlayer sees this, outputs Unseekable
mdat  size=16333    offset=1005
... (30 moof+mdat fragment pairs) ...
mfra  size=618      offset=493039   <-- contains full tfra index, ignored by ExoPlayer

Other players handle this correctly:

  • iOS AVPlayer: seeks within fMP4 files
  • Web browsers via MSE: seek within buffered fMP4 ranges
  • FFmpeg/mpv: seek using the mfra index

Actual result

FragmentedMp4Extractor outputs SeekMap.Unseekable when it encounters the first moof box without a preceding sidx box. The mfra box at the end of the file is never read.

player.isCurrentMediaItemSeekable returns false, and seeking is not possible.

For inputs where ExtractorInput.getLength() returns a known value (HTTP with Content-Length, local files), the extractor could seek to the end of the file to read mfro/mfra and build a ChunkIndex from the tfra entries — similar to how Mp4Extractor handles moov at the end of non-fragmented files.

Workaround

We implemented a custom Extractor wrapper that:

  1. Pre-fetches the last ~4KB of the file via an HTTP Range request to read the mfra box
  2. Parses the tfra entries into a custom SeekMap implementation
  3. Wraps ExtractorOutput to intercept the seekMap(Unseekable) call and replaces it with the tfra-based seekable map

This confirms that FragmentedMp4Extractor works correctly for actual extraction and seeking once positioned — the gap is only in the seek map construction.

Media

Will email the sample fragmented MP4 file to android-media-github@google.com.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions