This revises a closed ticket's boundary, so that part first.#84 classified probeWithExtractor and probeForConcat as device-bound and explicitly not a gap:
These are exercised by RemuxTest, ConcatEngineTest and RealMediaBenchmark in androidTest … Do not read their 0% as untested, and do not try to fix it by mocking FFprobe.
Right about FFprobe. Right about the measurement boundary. Not right that these are only orchestration. The track walk is a branch matrix, and androidTest reaches it only through whatever the committed fixtures happen to contain — so none of its rules is chosen by any test there. A fixture with two video tracks, a track that omits its duration, or an audio-before-video ordering is not something a device test produces on purpose.
Two ways in: drive ShadowMediaExtractor (verified reachable — it shadows the exact setDataSource(Context, Uri, Map) overload), or cut the loop into a pure function. Taking the second, which is the pattern CLAUDE.md names and work/FailureOutcome.kt documents.
extractedFrom(List<MediaFormat>) and concatInputFrom(List<MediaFormat>) hold the rules. What is left needing a device — setDataSource, getTrackFormat, release — is one three-line extension function, which is the thin edge androidTestshould be covering.
The two are deliberately not merged despite the overlap. One reads duration and not frame rate; the other reads frame rate and not duration. A merged version would compute both for every caller, and ConcatPlanner treats an unknown frame rate as "cannot prove a match" — so a field the join flow does not need must not start arriving as a number.
Eleven tests, over cases no fixture provides
Two video tracks · two audio tracks · audio outlasting video · a track with no KEY_DURATION · audio declared before video · a subtitle track · no tracks at all.
Mutations — six run, six red
mutation
reddens
last video track wins
first-video test
last audio track wins
first-audio test
duration = last rather than maxOf
longest-track test
drop the containsKey guard
six tests — getLong throws on a missing key
guess a frame rate of 30
no-frame-rate test
join takes the last video track
join frame-rate test
Behaviour preservation
This is the only one of these PRs that changes production code, so the check that matters is the e2e legs — RemuxTest.probeIdentifiesTheSourceContainerOfEachFixture, probeDistinguishesAudioFromImagesFromRubbish and ConcatEngineTest.probeReadsThePropertiesTheStrategyDependsOn all drive these paths over real files on a device. The JVM suite passing is necessary, not sufficient; please read the e2e results here as the real gate.
Coverage
MediaProbe's missed branches 91 → 70. What remains is the FFprobe half and the two catch arms — native and device-bound, exactly as #84 said.
Local gate green: ktlintCheck, detekt, lintDebug, testDebugUnitTest, compileDebugAndroidTestKotlin.
Closes #141.
**This revises a closed ticket's boundary, so that part first.** #84 classified `probeWithExtractor` and `probeForConcat` as device-bound and explicitly not a gap:
> These are exercised by `RemuxTest`, `ConcatEngineTest` and `RealMediaBenchmark` in `androidTest` … **Do not read their 0% as untested**, and do not try to fix it by mocking FFprobe.
Right about FFprobe. Right about the measurement boundary. **Not right that these are only orchestration.** The track walk is a branch matrix, and `androidTest` reaches it only through whatever the committed fixtures happen to contain — so none of its rules is *chosen* by any test there. A fixture with two video tracks, a track that omits its duration, or an audio-before-video ordering is not something a device test produces on purpose.
I've left a note on #84 pointing here.
### The decision #133 asked for
Two ways in: drive `ShadowMediaExtractor` (verified reachable — it shadows the exact `setDataSource(Context, Uri, Map)` overload), or cut the loop into a pure function. **Taking the second**, which is the pattern `CLAUDE.md` names and `work/FailureOutcome.kt` documents.
`extractedFrom(List<MediaFormat>)` and `concatInputFrom(List<MediaFormat>)` hold the rules. What is left needing a device — `setDataSource`, `getTrackFormat`, `release` — is one three-line extension function, which is the thin edge `androidTest` *should* be covering.
**The two are deliberately not merged despite the overlap.** One reads duration and not frame rate; the other reads frame rate and not duration. A merged version would compute both for every caller, and `ConcatPlanner` treats an unknown frame rate as "cannot prove a match" — so a field the join flow does not need must not start arriving as a number.
### Eleven tests, over cases no fixture provides
Two video tracks · two audio tracks · audio outlasting video · a track with no `KEY_DURATION` · audio declared before video · a subtitle track · no tracks at all.
### Mutations — six run, six red
| mutation | reddens |
|---|---|
| last video track wins | first-video test |
| last audio track wins | first-audio test |
| duration = last rather than `maxOf` | longest-track test |
| drop the `containsKey` guard | six tests — `getLong` throws on a missing key |
| guess a frame rate of 30 | no-frame-rate test |
| join takes the last video track | join frame-rate test |
### Behaviour preservation
This is the only one of these PRs that changes production code, so the check that matters is the **e2e legs** — `RemuxTest.probeIdentifiesTheSourceContainerOfEachFixture`, `probeDistinguishesAudioFromImagesFromRubbish` and `ConcatEngineTest.probeReadsThePropertiesTheStrategyDependsOn` all drive these paths over real files on a device. The JVM suite passing is necessary, not sufficient; please read the e2e results here as the real gate.
### Coverage
`MediaProbe`'s missed branches 91 → **70**. What remains is the FFprobe half and the two `catch` arms — native and device-bound, exactly as #84 said.
Local gate green: `ktlintCheck`, `detekt`, `lintDebug`, `testDebugUnitTest`, `compileDebugAndroidTestKotlin`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Behaviour preservation, confirmed on device. This is the only PR in the batch that touches production code, and the PR body asked reviewers to read the e2e legs rather than the JVM suite as the real gate. They are in:
Zero failures matching RemuxTest, ConcatEngineTest or MediaProbe on any leg — those are the tests that drive probeWithExtractor and probeForConcat over real files, and they are what would break if extracting the walk had changed its answer.
API 37 is red on SafPickerRoundTripTest.pickingAFileThroughTheSystemPickerFillsInTheFileCard — "the system picker would not close: after 4 back presses … com.google.android.documentsui is in front" — which is an emulator/system-UI flake unrelated to this diff. Same failure appeared today on #146, a PR that adds only JVM model tests. Tallied on #102. Re-running that leg.
**Behaviour preservation, confirmed on device.** This is the only PR in the batch that touches production code, and the PR body asked reviewers to read the e2e legs rather than the JVM suite as the real gate. They are in:
| leg | shape |
|---|---|
| API 33 | `expected: 60, received: 60, failed: 0, completed cleanly: yes` |
| API 34 | same |
| API 35 | same |
| API 36 | same |
Zero failures matching `RemuxTest`, `ConcatEngineTest` or `MediaProbe` on any leg — those are the tests that drive `probeWithExtractor` and `probeForConcat` over real files, and they are what would break if extracting the walk had changed its answer.
API 37 is red on **`SafPickerRoundTripTest.pickingAFileThroughTheSystemPickerFillsInTheFileCard`** — "the system picker would not close: after 4 back presses … `com.google.android.documentsui` is in front" — which is an emulator/system-UI flake unrelated to this diff. Same failure appeared today on #146, a PR that adds only JVM model tests. Tallied on #102. Re-running that leg.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #141.
This revises a closed ticket's boundary, so that part first. #84 classified
probeWithExtractorandprobeForConcatas device-bound and explicitly not a gap:Right about FFprobe. Right about the measurement boundary. Not right that these are only orchestration. The track walk is a branch matrix, and
androidTestreaches it only through whatever the committed fixtures happen to contain — so none of its rules is chosen by any test there. A fixture with two video tracks, a track that omits its duration, or an audio-before-video ordering is not something a device test produces on purpose.I've left a note on #84 pointing here.
The decision #133 asked for
Two ways in: drive
ShadowMediaExtractor(verified reachable — it shadows the exactsetDataSource(Context, Uri, Map)overload), or cut the loop into a pure function. Taking the second, which is the patternCLAUDE.mdnames andwork/FailureOutcome.ktdocuments.extractedFrom(List<MediaFormat>)andconcatInputFrom(List<MediaFormat>)hold the rules. What is left needing a device —setDataSource,getTrackFormat,release— is one three-line extension function, which is the thin edgeandroidTestshould be covering.The two are deliberately not merged despite the overlap. One reads duration and not frame rate; the other reads frame rate and not duration. A merged version would compute both for every caller, and
ConcatPlannertreats an unknown frame rate as "cannot prove a match" — so a field the join flow does not need must not start arriving as a number.Eleven tests, over cases no fixture provides
Two video tracks · two audio tracks · audio outlasting video · a track with no
KEY_DURATION· audio declared before video · a subtitle track · no tracks at all.Mutations — six run, six red
maxOfcontainsKeyguardgetLongthrows on a missing keyBehaviour preservation
This is the only one of these PRs that changes production code, so the check that matters is the e2e legs —
RemuxTest.probeIdentifiesTheSourceContainerOfEachFixture,probeDistinguishesAudioFromImagesFromRubbishandConcatEngineTest.probeReadsThePropertiesTheStrategyDependsOnall drive these paths over real files on a device. The JVM suite passing is necessary, not sufficient; please read the e2e results here as the real gate.Coverage
MediaProbe's missed branches 91 → 70. What remains is the FFprobe half and the twocatcharms — native and device-bound, exactly as #84 said.Local gate green:
ktlintCheck,detekt,lintDebug,testDebugUnitTest,compileDebugAndroidTestKotlin.🤖 Generated with Claude Code
Behaviour preservation, confirmed on device. This is the only PR in the batch that touches production code, and the PR body asked reviewers to read the e2e legs rather than the JVM suite as the real gate. They are in:
expected: 60, received: 60, failed: 0, completed cleanly: yesZero failures matching
RemuxTest,ConcatEngineTestorMediaProbeon any leg — those are the tests that driveprobeWithExtractorandprobeForConcatover real files, and they are what would break if extracting the walk had changed its answer.API 37 is red on
SafPickerRoundTripTest.pickingAFileThroughTheSystemPickerFillsInTheFileCard— "the system picker would not close: after 4 back presses …com.google.android.documentsuiis in front" — which is an emulator/system-UI flake unrelated to this diff. Same failure appeared today on #146, a PR that adds only JVM model tests. Tallied on #102. Re-running that leg.