Repository navigation
Description
Version
Media3 1.11.0
More version details
Regression introduced with the tracks-after-clusters support from d386bbf (#3250), first released in 1.11.0. Still present on main.
Devices that reproduce the issue
Deterministic extractor logic, not device-specific. Confirmed on Pixel 7 (Android 16) and Samsung SM-S936B (Android 16) with a stock ExoPlayer.Builder + MatroskaExtractor, and host-side by the repository's own extractor dump (see below).
Devices that do not reproduce the issue
None known.
Reproducible in the demo app?
Yes. Demo app (demos/main, noDecoderExtensions flavor) built from the 1.11.0 tag, Pixel 7 / Android 16:
- Tracks-after-clusters file: plays fine, but the time bar is disabled (
exo_progressreportsenabled=falsein the accessibility tree) and taps on it produce noEventLoggerevents — seeking is impossible, becauseCOMMAND_SEEK_IN_CURRENT_MEDIA_ITEMis unavailable for the unseekable window. - Control file (identical content, Tracks in front): time bar enabled; tapping it seeks normally (
positionDiscontinuity … reason=SEEK, PositionInfo:old […, pos=60031], PositionInfo:new […, pos=42199]).
The demo-visible symptom is "seeking disabled" rather than "snap to start" because the demo UI gates seeks on command availability; a direct player.seekTo() (as in the instrumentation test above) restarts from 0. Both stem from the same isSeekable() == false.
Repro file recipe (any length, per the #3250 report — an oversized track name forces mkvtoolnix to relocate the Tracks element to the end of the file):
ffmpeg -f lavfi -i "testsrc2=size=1280x720:rate=30:duration=60" \
-f lavfi -i "sine=frequency=440:duration=60" \
-c:v libx264 -pix_fmt yuv420p -c:a aac repro.mkv
mkvpropedit repro.mkv --edit track:1 --set name="$(printf 'e%.0s' $(seq 1 1400))"
Resulting layout: SeekHead, Info, Void, Tags, Cluster…, Cues, Tracks, with the SeekHead referencing both Cues and Tracks after the clusters.
Reproduction steps
For a Matroska file whose SeekHead references both a Tracks and a Cues element located after the Cluster elements (FFmpeg produces this layout), MatroskaExtractor emits a cues-backed MatroskaSeekMap that permanently reports isSeekable() == false and resolves every getSeekPoints(timeUs) to SeekPoint.START.
Cause is an ordering defect in read():
if (continueReading
&& (maybeSeekForTracks(seekPosition, input.getPosition())
|| maybeSeekForCues(seekPosition, input.getPosition()))) {
return Extractor.RESULT_SEEK;
}- At the first Cluster,
startMasterElement(ID_CLUSTER)sets bothseekForTracksandseekForCues. maybeSeekForTrackswins the short-circuit||, soseekForCuesstays pending and the extractor jumps to the Tracks position.- On the very next
reader.read()— before a single TrackEntry has parsed — the pendingmaybeSeekForCuesdiverts to the Cues position. endMasterElement(ID_CUES)constructsMatroskaSeekMapwhileprimarySeekTrackNumberis stillC.INDEX_UNSET(it is only assigned inendMasterElement(ID_TRACKS), which has not run yet).MatroskaSeekMap.isSeekable()delegates toisSeekable(primarySeekTrackNumber)→perTrackCues.get(-1)→false, andbuildChunkIndexreturnsnull, sogetSeekPoints(timeUs)→new SeekPoints(SeekPoint.START).
The ordering is deterministic, not racy: it happens for every tracks-after-clusters file that also has Cues.
The repository already demonstrates it: libraries/test_data/src/test/assets/media/mkv/sample_last_cluster.mkv has a valid Cues element (CueTime=67, CueTrack=1, CueClusterPosition=5510) and a known duration, yet the checked-in dump records the bug:
// extractordumps/mkv/sample_last_cluster.mkv.0.dump
seekMap:
isSeekable = false
duration = 1072000
getPosition(0) = [[timeUs=0, position=0]]
The same dump contains ThumbnailMetadata: presentationTimeUs=67000, which is derived from perTrackCues — proving the Cues were parsed and the MatroskaSeekMap branch was taken, not SeekMap.Unseekable.
Note the seek path of this feature has no test coverage: ExtractorAsserts computes numberSeekTests = seekMap.isSeekable() && durationUs != C.TIME_UNSET ? 4 : 1, so the committed sample_last_cluster.mkv.1/.2/.3.dump files are unreachable (and are byte-identical placeholders).
Possible fixes:
- Don't honor
seekForCueswhiletracksContentPosition != C.INDEX_UNSET && !readTracks(finish the Tracks detour first), or - Construct/emit the seek map only once both Cues and Tracks have been read, or resolve
primarySeekTrackNumberlazily inMatroskaSeekMap.
The TrackAwareSeekMap per-track overloads already work (perTrackCues is held by reference and populated by seek time), which is how we work around this downstream.
Expected result
A tracks-after-clusters file with valid Cues is seekable, like the same file with Tracks in front.
Actual result
Timeline.Window.isSeekable is false, and every seek restarts playback from the beginning: ProgressiveMediaPeriod.seekToUs() coerces all seeks into non-seekable media to 0 (positionUs = seekMap.isSeekable() ? positionUs : 0;), and selectTracks() routes post-first track reselections through the same path via seekRequired, so track changes rewind too.
On-device confirmation (Pixel 7, Android 16, stock ExoPlayer.Builder + MatroskaExtractor(DefaultSubtitleParserFactory()), playing a tracks-after-clusters MKV, paused): isCurrentMediaItemSeekable == false; seekTo(500) settles at position 0.
Reported downstream against a real FFmpeg-muxed file (Lavf62.3.100) in a Plex client: edde746/plezy#1969.
Media
libraries/test_data/src/test/assets/media/mkv/sample_last_cluster.mkv (already in the repository).
Bug Report
The repro is deterministic from the checked-in asset and dump, so no adb bugreport is attached; happy to provide one on request.
Activity
chore(player): link upstream issue androidx/media#3377 in seek workar…
Matroska: Fix seekable timeline with Tracks after Clusters
rohitjoins commentedon Aug 19, 2026
Thank you for reporting the issue and providing a detailed explanation. We have added a fix for this issue.
Matroska: Fix seekable timeline with Tracks after Clusters
chore(player): link upstream issue androidx/media#3377 in seek workar…