fix(android): derive the mpv fallback passthrough list from the audio route

Audio passthrough defaults on for Android TV, scoped to ExoPlayer because
mpv force-passes through every codec named in audio-spdif and has no
decode fallback. That scoping did not survive the ExoPlayer to mpv
handoff: PlayerAndroid queued the raw ac3,eac3,dts,dts-hd,truehd list as
a pending mpv property and prepareMpvFallback replayed it verbatim, so a
sink that bitstreams only Dolby formats was told to force TrueHD and
DTS-HD anyway. mpv selected spdif_truehd, the audio output never
initialised, and playback froze at its start position while still showing
a first frame — the stop timeline reported the position it opened with.

Treat passthrough as a request and resolve the codec list against the
route when mpv actually starts, so an HDMI or AVR change between
ExoPlayer startup and the handoff cannot replay codecs from the old sink.
Gate each codec on the exact advertised encoding rather than media3's
passthrough probe: that probe answers DTS-HD by downgrading to the DTS
core, and mpv reads "dts,dts-hd" as "dts-hd" alone, so accepting the
downgrade would name DTS-HD MA to a core-only receiver and lose DTS too.
This commit is contained in:
edde746
2026-07-28 19:44:16 +02:00
parent ae331b217c
commit a183c17c3b
6 changed files with 198 additions and 9 deletions
+34
View File
@@ -192,6 +192,40 @@ void main() {
);
});
test('ExoPlayer leaves the mpv passthrough codec list to the native fallback', () async {
final calls = <MethodCall>[];
await withMockPlayerChannels(
methodChannelName: 'com.plezy/exo_player',
eventChannelName: 'com.plezy/exo_player/events',
methodHandler: (call) async {
calls.add(call);
if (call.method == 'initialize') return true;
if (call.method == 'requestAudioFocus') return true;
return null;
},
testBody: () async {
final player = PlayerAndroid();
try {
expect(await player.requestAudioFocus(), isTrue);
await player.setAudioPassthrough(true);
final passthrough = calls.singleWhere((call) => call.method == 'setAudioPassthrough');
expect((passthrough.arguments as Map)['enabled'], isTrue);
// mpv force-passthroughs audio-spdif with no decode fallback, so the
// plugin derives it from the audio route instead (#1703).
expect(
calls.where(
(call) => call.method == 'setMpvProperty' && (call.arguments as Map)['name'] == 'audio-spdif',
),
isEmpty,
);
} finally {
await player.dispose();
}
},
);
});
test('ExoPlayer retries initialization after a recoverable native failure', () async {
var initializeAttempts = 0;
await withMockPlayerChannels(