From c201bc2f03c023cd90ff53783cdb00630371881e Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sun, 23 Aug 2026 01:42:36 +0200 Subject: [PATCH] feat(player): default every container to the ffmpeg demuxer The Auto demuxer mode routed only media3's weak container families (AVI, ASF/WMV, MPEG-PS, Matroska/WebM) through FFmpeg and kept MP4/TS on media3's extractors, with a second any-container FFmpeg instance behind media3's list. With the goal of fully switching to the ffmpeg demuxer, the split served no purpose and the catch-all could never add coverage once the primary accepts everything. Collapse the preference to two modes: FFmpeg (default), which demuxes every progressive container ahead of media3's list, and media3 only as the user-facing escape hatch in case a file misbehaves. media3's extractors stay behind FFmpeg, so anything FFmpeg cannot sniff still reaches them. The catch-all role and the Auto container list are removed; unknown persisted wire values (including the retired "auto") resolve to FFmpeg. Verified on a Pixel 7: MP4, TS, and MKV all demux through FFmpeg under the default ("sniff accepted mov,mp4 / mpegts / matroska,webm") with forward and backward seeks landing; JVM suites and analyzer pass. --- .../edde746/plezy/exoplayer/ExoPlayerCore.kt | 29 +++----- .../plezy/exoplayer/ExoPlayerPlugin.kt | 2 +- .../plezy/exoplayer/FfmpegDemuxerPolicy.kt | 49 +++++-------- .../plezy/exoplayer/FfmpegExtractor.kt | 22 ++---- .../exoplayer/FfmpegDemuxerPolicyTest.kt | 68 +++---------------- lib/i18n/az.i18n.json | 5 +- lib/i18n/bg.i18n.json | 5 +- lib/i18n/da.i18n.json | 5 +- lib/i18n/de.i18n.json | 5 +- lib/i18n/en.i18n.json | 7 +- lib/i18n/es.i18n.json | 5 +- lib/i18n/fr.i18n.json | 5 +- lib/i18n/hu.i18n.json | 5 +- lib/i18n/it.i18n.json | 5 +- lib/i18n/ja.i18n.json | 5 +- lib/i18n/kk.i18n.json | 5 +- lib/i18n/ko.i18n.json | 5 +- lib/i18n/nb.i18n.json | 5 +- lib/i18n/nl.i18n.json | 5 +- lib/i18n/pl.i18n.json | 5 +- lib/i18n/pt.i18n.json | 5 +- lib/i18n/ru.i18n.json | 5 +- lib/i18n/strings.g.dart | 2 +- lib/i18n/strings_en.g.dart | 28 ++++---- lib/i18n/sv.i18n.json | 5 +- lib/i18n/tr.i18n.json | 5 +- lib/i18n/uz.i18n.json | 5 +- lib/i18n/zh-Hant.i18n.json | 5 +- lib/i18n/zh.i18n.json | 5 +- lib/mpv/player/platform/player_android.dart | 2 +- .../settings/playback_settings_screen.dart | 5 +- lib/services/settings_service.dart | 13 ++-- 32 files changed, 111 insertions(+), 221 deletions(-) diff --git a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerCore.kt b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerCore.kt index a53285e48..5d2d443a4 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerCore.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerCore.kt @@ -564,7 +564,7 @@ class ExoPlayerCore(private val activity: Activity) : @Volatile private var activeDoviMp4Wrapper: DoviExtractorWrapper? = null // Container-demuxer placement; read per media item when extractors are built. - @Volatile private var demuxerPreference: FfmpegDemuxerPolicy.Preference = FfmpegDemuxerPolicy.Preference.AUTO + @Volatile private var demuxerPreference: FfmpegDemuxerPolicy.Preference = FfmpegDemuxerPolicy.Preference.FFMPEG fun setDemuxerMode(mode: String) { demuxerPreference = FfmpegDemuxerPolicy.fromWire(mode) @@ -589,9 +589,9 @@ class ExoPlayerCore(private val activity: Activity) : // is internal and this function is not; unrecognised names resolve to Auto, which is also // the default (#1816). bufferTier: String = "auto", - // Which containers the FFmpeg demuxer takes ahead of media3's extractors. - // Wire values from [FfmpegDemuxerPolicy.Preference]; unrecognised resolves to auto. - demuxerMode: String = "auto" + // Whether FFmpeg demuxes ahead of media3's extractors. Wire values from + // [FfmpegDemuxerPolicy.Preference]; unrecognised resolves to FFmpeg. + demuxerMode: String = "ffmpeg" ): Boolean { if (isInitialized) { Log.d(TAG, "Already initialized") @@ -775,23 +775,12 @@ class ExoPlayerCore(private val activity: Activity) : val currentDemuxerPreference = this.demuxerPreference Log.i(TAG, "[init] extractors: demuxer=${currentDemuxerPreference.wireName}") // media3 invokes the factory once per player session and reuses the - // extractor instances for every item, so both roles are always built - // and each sniff reads the live preference instead of a captured one. + // extractor instances for every item, so the sniff reads the live + // preference instead of a captured one. val liveDemuxerPreference = { this@ExoPlayerCore.demuxerPreference } - val ffmpegPrimary = + val ffmpegFirst = listOfNotNull( FfmpegExtractor.create( - FfmpegExtractor.Role.PRIMARY, - liveDemuxerPreference, - currentDvMode, - subtitleParserFactory, - handler - ) - ) - val ffmpegCatchAll = - listOfNotNull( - FfmpegExtractor.create( - FfmpegExtractor.Role.CATCH_ALL, liveDemuxerPreference, currentDvMode, subtitleParserFactory, @@ -799,7 +788,7 @@ class ExoPlayerCore(private val activity: Activity) : ) ) ( - ffmpegPrimary + extractorsFactory.createExtractors().map { extractor -> + ffmpegFirst + extractorsFactory.createExtractors().map { extractor -> when { extractor is MatroskaExtractor -> { val withTextPipeline = OutputWrappingExtractor(extractor) { out -> @@ -824,7 +813,7 @@ class ExoPlayerCore(private val activity: Activity) : } else -> extractor } - } + ffmpegCatchAll + } ).toTypedArray() } diff --git a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerPlugin.kt b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerPlugin.kt index 2356b154c..5d2b677a3 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerPlugin.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerPlugin.kt @@ -305,7 +305,7 @@ class ExoPlayerPlugin : // ExoPlayer-only: mpv's read-ahead is owned by the mpv.conf editor, so there is no // fallback replay for this one. Resolved in the core; unrecognised means Auto (#1816). val bufferTier = call.argument("bufferTier") ?: "auto" - val demuxerMode = call.argument("demuxerMode") ?: "auto" + val demuxerMode = call.argument("demuxerMode") ?: "ffmpeg" configuredBufferSizeBytes = bufferSizeBytes // Seed the request here rather than waiting for Dart's separate setAudioPassthrough // call, so a fallback raised before that arrives still derives audio-spdif correctly. diff --git a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/FfmpegDemuxerPolicy.kt b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/FfmpegDemuxerPolicy.kt index 7829ae8a3..f155b1d21 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/FfmpegDemuxerPolicy.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/FfmpegDemuxerPolicy.kt @@ -1,45 +1,28 @@ package com.edde746.plezy.exoplayer /** - * Decides where the FFmpeg demuxer sits relative to media3's own extractors. + * Decides whether the FFmpeg demuxer sits ahead of media3's own extractors. * - * media3's bundled extractors are strong for MP4/TS and weak exactly where - * this app kept accumulating container patches: AVI (XviD packed-bitstream - * timestamps, missing VOL csd — issue #2052), ASF/WMV (no extractor at all), - * MPEG-PS/VOB (no extractor), and Matroska (zlib-compressed subtitles, - * LOAS/LATM audio as A_MS/ACM, cueless seeking, font attachments — all of - * which libavformat handles natively). "Auto" therefore puts FFmpeg first for - * those families and leaves everything else on media3, with an any-container - * FFmpeg fallback behind media3's list so exotic containers still play - * instead of failing outright. + * FFmpeg demuxes every progressive container by default. media3's bundled + * extractors were weak exactly where this app kept accumulating container + * patches — AVI (XviD packed-bitstream timestamps, missing VOL csd — issue + * #2052), ASF/WMV (no extractor at all), MPEG-PS/VOB (no extractor), and + * Matroska (zlib-compressed subtitles, LOAS/LATM audio as A_MS/ACM, cueless + * seeking, font attachments) — and libavformat handles all of it natively. + * media3's extractors stay behind FFmpeg in the list, so a container FFmpeg + * fails to sniff still reaches media3's parsers. [Preference.MEDIA3_ONLY] is + * the user-facing escape hatch in case a file misbehaves on the FFmpeg path. */ internal object FfmpegDemuxerPolicy { enum class Preference(val wireName: String) { - AUTO("auto"), - FFMPEG_FIRST("ffmpeg"), + FFMPEG("ffmpeg"), MEDIA3_ONLY("media3") } - /** - * Containers FFmpeg demuxes ahead of media3 under Auto. Probed short names - * from libavformat: "avi" covers AVI/DIVX, "asf" covers WMV, "mpeg" covers - * MPEG-PS and VOB, "matroska,webm" covers MKV and WebM. - */ - val FFMPEG_FIRST_CONTAINERS = setOf("avi", "asf", "mpeg", "matroska,webm") + /** Unknown wire values (including the retired "auto") resolve to FFmpeg. */ + fun fromWire(value: String?): Preference = + Preference.entries.firstOrNull { it.wireName == value } ?: Preference.FFMPEG - fun fromWire(value: String?): Preference = Preference.entries.firstOrNull { it.wireName == value } ?: Preference.AUTO - - /** - * Whether the FFmpeg extractor placed before media3's list accepts a probed - * container name. Under [Preference.FFMPEG_FIRST] it accepts everything, so - * the trailing catch-all becomes redundant but harmless. - */ - fun primaryAccepts(preference: Preference, probedContainer: String): Boolean = when (preference) { - Preference.MEDIA3_ONLY -> false - Preference.FFMPEG_FIRST -> true - Preference.AUTO -> probedContainer.lowercase() in FFMPEG_FIRST_CONTAINERS - } - - /** Whether the any-container FFmpeg fallback behind media3's list exists. */ - fun catchAllEnabled(preference: Preference): Boolean = preference != Preference.MEDIA3_ONLY + /** Whether the FFmpeg extractor placed before media3's list demuxes at all. */ + fun enabled(preference: Preference): Boolean = preference == Preference.FFMPEG } diff --git a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/FfmpegExtractor.kt b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/FfmpegExtractor.kt index 0f959332d..c12ee955c 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/FfmpegExtractor.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/FfmpegExtractor.kt @@ -41,21 +41,18 @@ import java.io.IOException * returns RESULT_SEEK or resumes in place when the loader already positioned * the input at the requested byte. * - * [role] places the instance: [Role.PRIMARY] sits before media3's extractors - * and sniffs only what [FfmpegDemuxerPolicy] hands to FFmpeg; [Role.CATCH_ALL] - * sits behind them and accepts any container media3 could not sniff. + * The instance sits before media3's extractors and sniffs everything FFmpeg + * can probe while [FfmpegDemuxerPolicy] enables it; media3's list behind it + * is the fallback for anything FFmpeg cannot open. */ @OptIn(UnstableApi::class) internal class FfmpegExtractor private constructor( - private val role: Role, private val preferenceSupplier: () -> FfmpegDemuxerPolicy.Preference, private val dvMode: DvConversionMode, private val subtitleParserFactory: SubtitleParser.Factory, private val assHandler: AssHandler ) : Extractor { - enum class Role { PRIMARY, CATCH_ALL } - companion object { private const val TAG = "FfmpegExtractor" private const val SNIFF_BYTES = 64 * 1024 @@ -69,13 +66,12 @@ internal class FfmpegExtractor private constructor( /** Null when the native library is unavailable. */ fun create( - role: Role, preferenceSupplier: () -> FfmpegDemuxerPolicy.Preference, dvMode: DvConversionMode, subtitleParserFactory: SubtitleParser.Factory, assHandler: AssHandler ): FfmpegExtractor? = if (FfmpegDemuxerJni.available) { - FfmpegExtractor(role, preferenceSupplier, dvMode, subtitleParserFactory, assHandler) + FfmpegExtractor(preferenceSupplier, dvMode, subtitleParserFactory, assHandler) } else { null } @@ -135,14 +131,10 @@ internal class FfmpegExtractor private constructor( // media3 materializes the extractor array once per player session, so the // policy must be read live at sniff time — a preference captured at // construction would freeze for the session. - val preference = preferenceSupplier() + if (!FfmpegDemuxerPolicy.enabled(preferenceSupplier())) return false val probed = probe(input) ?: return false - val accepted = when (role) { - Role.PRIMARY -> FfmpegDemuxerPolicy.primaryAccepts(preference, probed) - Role.CATCH_ALL -> FfmpegDemuxerPolicy.catchAllEnabled(preference) - } - if (accepted) Log.i(TAG, "sniff accepted $probed (role=$role)") - return accepted + Log.i(TAG, "sniff accepted $probed") + return true } private fun probe(input: ExtractorInput): String? { diff --git a/android/app/src/test/kotlin/com/edde746/plezy/exoplayer/FfmpegDemuxerPolicyTest.kt b/android/app/src/test/kotlin/com/edde746/plezy/exoplayer/FfmpegDemuxerPolicyTest.kt index 100678cb8..fccd981d3 100644 --- a/android/app/src/test/kotlin/com/edde746/plezy/exoplayer/FfmpegDemuxerPolicyTest.kt +++ b/android/app/src/test/kotlin/com/edde746/plezy/exoplayer/FfmpegDemuxerPolicyTest.kt @@ -6,70 +6,24 @@ import org.junit.Assert.assertTrue import org.junit.Test class FfmpegDemuxerPolicyTest { - @Test fun wireValuesRoundTrip() { - assertEquals(FfmpegDemuxerPolicy.Preference.AUTO, FfmpegDemuxerPolicy.fromWire("auto")) - assertEquals( - FfmpegDemuxerPolicy.Preference.FFMPEG_FIRST, - FfmpegDemuxerPolicy.fromWire("ffmpeg") - ) - assertEquals( - FfmpegDemuxerPolicy.Preference.MEDIA3_ONLY, - FfmpegDemuxerPolicy.fromWire("media3") - ) + assertEquals(FfmpegDemuxerPolicy.Preference.FFMPEG, FfmpegDemuxerPolicy.fromWire("ffmpeg")) + assertEquals(FfmpegDemuxerPolicy.Preference.MEDIA3_ONLY, FfmpegDemuxerPolicy.fromWire("media3")) } @Test - fun unknownWireValuesResolveToAuto() { - assertEquals(FfmpegDemuxerPolicy.Preference.AUTO, FfmpegDemuxerPolicy.fromWire(null)) - assertEquals(FfmpegDemuxerPolicy.Preference.AUTO, FfmpegDemuxerPolicy.fromWire("")) - assertEquals(FfmpegDemuxerPolicy.Preference.AUTO, FfmpegDemuxerPolicy.fromWire("bogus")) + fun unknownWireValuesResolveToFfmpeg() { + assertEquals(FfmpegDemuxerPolicy.Preference.FFMPEG, FfmpegDemuxerPolicy.fromWire(null)) + assertEquals(FfmpegDemuxerPolicy.Preference.FFMPEG, FfmpegDemuxerPolicy.fromWire("")) + assertEquals(FfmpegDemuxerPolicy.Preference.FFMPEG, FfmpegDemuxerPolicy.fromWire("bogus")) + // Retired wire value from the container-list era maps to the default. + assertEquals(FfmpegDemuxerPolicy.Preference.FFMPEG, FfmpegDemuxerPolicy.fromWire("auto")) } @Test - fun autoHandsOnlyWeakContainersToFfmpeg() { - // Containers media3 has no working extractor for — AVI (#2052), ASF/WMV, - // MPEG-PS/VOB — plus Matroska, whose media3 extractor needed app-side - // patches (zlib subtitles, LATM audio, cueless seeking, font attachments) - // that libavformat covers natively. - assertTrue(FfmpegDemuxerPolicy.primaryAccepts(FfmpegDemuxerPolicy.Preference.AUTO, "avi")) - assertTrue(FfmpegDemuxerPolicy.primaryAccepts(FfmpegDemuxerPolicy.Preference.AUTO, "asf")) - assertTrue(FfmpegDemuxerPolicy.primaryAccepts(FfmpegDemuxerPolicy.Preference.AUTO, "mpeg")) - assertTrue(FfmpegDemuxerPolicy.primaryAccepts(FfmpegDemuxerPolicy.Preference.AUTO, "matroska,webm")) - // Case-insensitive: probed names are lowercased by the extractor. - assertTrue(FfmpegDemuxerPolicy.primaryAccepts(FfmpegDemuxerPolicy.Preference.AUTO, "AVI")) - - // Containers media3 handles well stay on media3's extractors. - assertFalse(FfmpegDemuxerPolicy.primaryAccepts(FfmpegDemuxerPolicy.Preference.AUTO, "mp4")) - assertFalse(FfmpegDemuxerPolicy.primaryAccepts(FfmpegDemuxerPolicy.Preference.AUTO, "mpegts")) - assertFalse(FfmpegDemuxerPolicy.primaryAccepts(FfmpegDemuxerPolicy.Preference.AUTO, "ogg")) - } - - @Test - fun ffmpegFirstTakesEverythingFromMedia3() { - for (container in listOf("avi", "asf", "mpeg", "matroska,webm", "mp4", "mpegts", "flv")) { - assertTrue( - "expected ffmpeg-first to accept $container", - FfmpegDemuxerPolicy.primaryAccepts(FfmpegDemuxerPolicy.Preference.FFMPEG_FIRST, container) - ) - } - } - - @Test - fun media3OnlyNeverUsesTheFfmpegDemuxer() { - for (container in listOf("avi", "asf", "mpeg", "matroska,webm", "mp4")) { - assertFalse( - FfmpegDemuxerPolicy.primaryAccepts(FfmpegDemuxerPolicy.Preference.MEDIA3_ONLY, container) - ) - } - assertFalse(FfmpegDemuxerPolicy.catchAllEnabled(FfmpegDemuxerPolicy.Preference.MEDIA3_ONLY)) - } - - @Test - fun catchAllFollowsPreference() { - assertTrue(FfmpegDemuxerPolicy.catchAllEnabled(FfmpegDemuxerPolicy.Preference.AUTO)) - assertTrue(FfmpegDemuxerPolicy.catchAllEnabled(FfmpegDemuxerPolicy.Preference.FFMPEG_FIRST)) - assertFalse(FfmpegDemuxerPolicy.catchAllEnabled(FfmpegDemuxerPolicy.Preference.MEDIA3_ONLY)) + fun ffmpegDemuxesByDefaultAndMedia3OnlyDisables() { + assertTrue(FfmpegDemuxerPolicy.enabled(FfmpegDemuxerPolicy.Preference.FFMPEG)) + assertFalse(FfmpegDemuxerPolicy.enabled(FfmpegDemuxerPolicy.Preference.MEDIA3_ONLY)) } } diff --git a/lib/i18n/az.i18n.json b/lib/i18n/az.i18n.json index e6c593a4c..9aad5180f 100644 --- a/lib/i18n/az.i18n.json +++ b/lib/i18n/az.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Dolby Vision təbəqələrini sil və sadə HEVC kimi təqdim et", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Açılışda profil soruş", "requireProfileSelectionOnOpenDescription": "Tətbiq hər dəfə açıldıqda profil seçimini göstər", "forceTvMode": "TV rejimini məcburi et", diff --git a/lib/i18n/bg.i18n.json b/lib/i18n/bg.i18n.json index 509d52cb1..3782749c5 100644 --- a/lib/i18n/bg.i18n.json +++ b/lib/i18n/bg.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Премахва слоевете Dolby Vision RPU/EL и подава обикновен HEVC поток", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Питай за профил при отваряне на приложението", "requireProfileSelectionOnOpenDescription": "Показвай избор на профил всеки път при отваряне на приложението", "forceTvMode": "Принуди TV режим", diff --git a/lib/i18n/da.i18n.json b/lib/i18n/da.i18n.json index 110a0f92e..f65104925 100644 --- a/lib/i18n/da.i18n.json +++ b/lib/i18n/da.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Fjern Dolby Vision RPU/EL-lag og brug almindelig HEVC", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Spørg om profil ved åbning", "requireProfileSelectionOnOpenDescription": "Vis profilvalg hver gang appen åbnes", "forceTvMode": "Gennemtving TV-tilstand", diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index 26b0ac402..e0db681c5 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Dolby-Vision-RPU/EL-Schichten entfernen und reines HEVC ausgeben", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Profil beim Öffnen abfragen", "requireProfileSelectionOnOpenDescription": "Profilauswahl bei jedem Öffnen der App anzeigen", "forceTvMode": "TV-Modus erzwingen", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 540904ba7..cb3e79f7c 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -320,10 +320,9 @@ "dvConversionDv81Description": "Force inline RPU conversion to Dolby Vision profile 8.1", "dvConversionHevcStripDescription": "Strip Dolby Vision RPU/EL layers and present plain HEVC", "demuxer": "Container Demuxer", - "demuxerDescription": "Which demuxer parses direct-played files. FFmpeg covers more containers; media3 is the previous behavior.", - "demuxerAuto": "Auto (Recommended)", - "demuxerFfmpegFirst": "FFmpeg first", - "demuxerMedia3Only": "media3 only", + "demuxerDescription": "Which demuxer parses direct-played files. Switch to media3 if a file misbehaves.", + "demuxerFfmpeg": "FFmpeg (Recommended)", + "demuxerMedia3": "media3", "requireProfileSelectionOnOpen": "Ask for profile on app open", "requireProfileSelectionOnOpenDescription": "Show profile selection every time the app is opened", "forceTvMode": "Force TV mode", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index 7f9407a9a..c89fb1ce4 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Eliminar las capas RPU/EL de Dolby Vision y presentar HEVC convencional", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Pedir perfil al abrir la aplicación", "requireProfileSelectionOnOpenDescription": "Mostrar selección de perfil cada vez que se abre la aplicación", "forceTvMode": "Forzar modo TV", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index f2ab0b3bc..ab2cee526 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Supprimer les couches RPU/EL Dolby Vision et présenter du HEVC simple", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Demander le profil à l'ouverture", "requireProfileSelectionOnOpenDescription": "Afficher la sélection de profil à chaque ouverture de l'application", "forceTvMode": "Forcer le mode TV", diff --git a/lib/i18n/hu.i18n.json b/lib/i18n/hu.i18n.json index a4aaa0f68..12547ca4d 100644 --- a/lib/i18n/hu.i18n.json +++ b/lib/i18n/hu.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "A Dolby Vision RPU/EL-rétegek eltávolítása és egyszerű HEVC-ként való megjelenítés", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Profil kérése az alkalmazás megnyitásakor", "requireProfileSelectionOnOpenDescription": "Profilválasztó megjelenítése minden alkalommal, amikor az alkalmazást megnyitod", "forceTvMode": "TV-mód kényszerítése", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 345c978a2..63f668951 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Rimuove i livelli RPU/EL di Dolby Vision e riproduce il video come semplice HEVC", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Chiedi di scegliere il profilo all'apertura", "requireProfileSelectionOnOpenDescription": "Mostra la selezione del profilo ogni volta che l'app viene aperta", "forceTvMode": "Forza modalità TV", diff --git a/lib/i18n/ja.i18n.json b/lib/i18n/ja.i18n.json index 45b05f010..f5fb5d0c5 100644 --- a/lib/i18n/ja.i18n.json +++ b/lib/i18n/ja.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Dolby Vision の RPU/EL レイヤーを削除し、通常の HEVC として扱います", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "アプリ起動時にプロフィールを確認", "requireProfileSelectionOnOpenDescription": "アプリを開くたびにプロフィール選択を表示", "forceTvMode": "TVモードを強制", diff --git a/lib/i18n/kk.i18n.json b/lib/i18n/kk.i18n.json index 054b598eb..0124504d4 100644 --- a/lib/i18n/kk.i18n.json +++ b/lib/i18n/kk.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Dolby Vision қабаттарын алып тастап, HEVC ретінде көрсету", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Ашқанда профильді сұрау", "requireProfileSelectionOnOpenDescription": "Қолданба ашылған сайын профильді таңдауды көрсету", "forceTvMode": "TV режимін мәжбүрлеу", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index f42075220..54f373cdf 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Dolby Vision RPU/EL 레이어를 제거하고 일반 HEVC로 제공합니다", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "앱 실행 시 프로필 선택", "requireProfileSelectionOnOpenDescription": "앱을 열 때마다 프로필 선택 화면을 표시합니다", "forceTvMode": "TV 모드 강제 사용", diff --git a/lib/i18n/nb.i18n.json b/lib/i18n/nb.i18n.json index 8cde931f8..0eef0cf4a 100644 --- a/lib/i18n/nb.i18n.json +++ b/lib/i18n/nb.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Fjern Dolby Vision RPU/EL-lag og lever som vanlig HEVC", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Spør om profil ved appåpning", "requireProfileSelectionOnOpenDescription": "Vis profilvalg hver gang appen åpnes", "forceTvMode": "Tving TV-modus", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 48eaceb9b..7fe83c2eb 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Verwijder Dolby Vision RPU/EL-lagen en bied gewone HEVC aan", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Vraag om profiel bij openen", "requireProfileSelectionOnOpenDescription": "Toon profielselectie telkens wanneer de app wordt geopend", "forceTvMode": "Tv-modus afdwingen", diff --git a/lib/i18n/pl.i18n.json b/lib/i18n/pl.i18n.json index b0006c2c5..88731ec8a 100644 --- a/lib/i18n/pl.i18n.json +++ b/lib/i18n/pl.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Usuń warstwy Dolby Vision RPU/EL i przedstaw zwykłe HEVC", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Pytaj o profil przy otwarciu aplikacji", "requireProfileSelectionOnOpenDescription": "Pokaż wybór profilu za każdym razem, gdy aplikacja jest otwierana", "forceTvMode": "Wymuś tryb TV", diff --git a/lib/i18n/pt.i18n.json b/lib/i18n/pt.i18n.json index f11494a94..c00cdf28e 100644 --- a/lib/i18n/pt.i18n.json +++ b/lib/i18n/pt.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Remover as camadas RPU/EL do Dolby Vision e apresentar HEVC sem Dolby Vision", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Pedir perfil ao abrir o app", "requireProfileSelectionOnOpenDescription": "Mostrar a seleção de perfil sempre que o app for aberto", "forceTvMode": "Forçar modo TV", diff --git a/lib/i18n/ru.i18n.json b/lib/i18n/ru.i18n.json index 98ef71d2a..b5d7b4b35 100644 --- a/lib/i18n/ru.i18n.json +++ b/lib/i18n/ru.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Удалять слои Dolby Vision RPU/EL и передавать обычный HEVC", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Запрашивать профиль при запуске", "requireProfileSelectionOnOpenDescription": "Показывать выбор профиля при каждом открытии приложения", "forceTvMode": "Принудительный режим ТВ", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 83f6b2759..df316afec 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,7 +4,7 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 22 -/// Strings: 40970 (1862 per locale) +/// Strings: 40969 (1862 per locale) // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index de472c0ab..6d068304d 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -1038,17 +1038,14 @@ class Translations$settings$en { /// en: 'Container Demuxer' String get demuxer => 'Container Demuxer'; - /// en: 'Which demuxer parses direct-played files. FFmpeg covers more containers; media3 is the previous behavior.' - String get demuxerDescription => 'Which demuxer parses direct-played files. FFmpeg covers more containers; media3 is the previous behavior.'; + /// en: 'Which demuxer parses direct-played files. Switch to media3 if a file misbehaves.' + String get demuxerDescription => 'Which demuxer parses direct-played files. Switch to media3 if a file misbehaves.'; - /// en: 'Auto (Recommended)' - String get demuxerAuto => 'Auto (Recommended)'; + /// en: 'FFmpeg (Recommended)' + String get demuxerFfmpeg => 'FFmpeg (Recommended)'; - /// en: 'FFmpeg first' - String get demuxerFfmpegFirst => 'FFmpeg first'; - - /// en: 'media3 only' - String get demuxerMedia3Only => 'media3 only'; + /// en: 'media3' + String get demuxerMedia3 => 'media3'; /// en: 'Ask for profile on app open' String get requireProfileSelectionOnOpen => 'Ask for profile on app open'; @@ -6753,10 +6750,9 @@ extension on Translations { 'settings.dvConversionDv81Description' => 'Force inline RPU conversion to Dolby Vision profile 8.1', 'settings.dvConversionHevcStripDescription' => 'Strip Dolby Vision RPU/EL layers and present plain HEVC', 'settings.demuxer' => 'Container Demuxer', - 'settings.demuxerDescription' => 'Which demuxer parses direct-played files. FFmpeg covers more containers; media3 is the previous behavior.', - 'settings.demuxerAuto' => 'Auto (Recommended)', - 'settings.demuxerFfmpegFirst' => 'FFmpeg first', - 'settings.demuxerMedia3Only' => 'media3 only', + 'settings.demuxerDescription' => 'Which demuxer parses direct-played files. Switch to media3 if a file misbehaves.', + 'settings.demuxerFfmpeg' => 'FFmpeg (Recommended)', + 'settings.demuxerMedia3' => 'media3', 'settings.requireProfileSelectionOnOpen' => 'Ask for profile on app open', 'settings.requireProfileSelectionOnOpenDescription' => 'Show profile selection every time the app is opened', 'settings.forceTvMode' => 'Force TV mode', @@ -6957,9 +6953,9 @@ extension on Translations { 'mediaMenu.deleteShowFromServer' => 'Delete show from server', 'mediaMenu.deleteMovieFromServer' => 'Delete movie from server', 'mediaMenu.deleteEpisodeTitle' => 'Delete this episode?', + 'mediaMenu.deleteSeasonTitle' => 'Delete this season?', _ => null, } ?? switch (path) { - 'mediaMenu.deleteSeasonTitle' => 'Delete this season?', 'mediaMenu.deleteShowTitle' => 'Delete this show?', 'mediaMenu.deleteMovieTitle' => 'Delete this movie?', 'mediaMenu.deleteEpisodeConfirm' => 'Delete episode', @@ -7471,9 +7467,9 @@ extension on Translations { 'explore.status.ended' => 'Ended', 'explore.status.canceled' => 'Canceled', 'explore.status.upcoming' => 'Upcoming', + 'explore.episodeCount' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n, one: '${n} episode', other: '${n} episodes', ), _ => null, } ?? switch (path) { - 'explore.episodeCount' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n, one: '${n} episode', other: '${n} episodes', ), 'explore.cast' => 'Cast', 'explore.characters' => 'Characters', 'explore.addToWatchlist' => 'Add to Watchlist', @@ -7985,9 +7981,9 @@ extension on Translations { 'companionRemote.remote.seekForward' => 'Seek Fwd', 'companionRemote.remote.volume' => 'Volume', 'companionRemote.remote.volumeDown' => 'Down', + 'companionRemote.remote.volumeUp' => 'Up', _ => null, } ?? switch (path) { - 'companionRemote.remote.volumeUp' => 'Up', 'companionRemote.remote.fullscreen' => 'Fullscreen', 'companionRemote.remote.subtitles' => 'Subtitles', 'companionRemote.remote.audio' => 'Audio', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index 4d760482b..84540d774 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Ta bort Dolby Visions RPU-/EL-lager och använd vanlig HEVC", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Fråga efter profil vid appstart", "requireProfileSelectionOnOpenDescription": "Visa profilval varje gång appen öppnas", "forceTvMode": "Tvinga TV-läge", diff --git a/lib/i18n/tr.i18n.json b/lib/i18n/tr.i18n.json index c75f12a7b..fe02c5ee8 100644 --- a/lib/i18n/tr.i18n.json +++ b/lib/i18n/tr.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Dolby Vision RPU/EL katmanlarını soy ve düz HEVC olarak sun", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Uygulama açılışında profil sor", "requireProfileSelectionOnOpenDescription": "Uygulama her açıldığında profil seçimini göster", "forceTvMode": "TV modunu zorla", diff --git a/lib/i18n/uz.i18n.json b/lib/i18n/uz.i18n.json index 4a96152ba..50d274c6d 100644 --- a/lib/i18n/uz.i18n.json +++ b/lib/i18n/uz.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "Dolby Vision qatlamlarini olib tashlash va HEVC sifatida koʻrsatish", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "Ochilganda profilni soʻrash", "requireProfileSelectionOnOpenDescription": "Ilova ochilgan har safar profilni tanlashni koʻrsatish", "forceTvMode": "TV rejimini majburlash", diff --git a/lib/i18n/zh-Hant.i18n.json b/lib/i18n/zh-Hant.i18n.json index 169834dcb..f79a04558 100644 --- a/lib/i18n/zh-Hant.i18n.json +++ b/lib/i18n/zh-Hant.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "移除 Dolby Vision RPU/EL 層,並以一般 HEVC 呈現", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "開啟應用程式時要求選擇使用者", "requireProfileSelectionOnOpenDescription": "每次開啟應用程式時顯示使用者設定檔選擇畫面", "forceTvMode": "強制 TV 模式", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 36957cea4..382c02ea9 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -321,9 +321,8 @@ "dvConversionHevcStripDescription": "移除 Dolby Vision RPU/EL 层并呈现普通 HEVC", "demuxer": "", "demuxerDescription": "", - "demuxerAuto": "", - "demuxerFfmpegFirst": "", - "demuxerMedia3Only": "", + "demuxerFfmpeg": "", + "demuxerMedia3": "", "requireProfileSelectionOnOpen": "打开应用时选择用户资料", "requireProfileSelectionOnOpenDescription": "每次打开应用时都显示用户资料选择界面", "forceTvMode": "强制 TV 模式", diff --git a/lib/mpv/player/platform/player_android.dart b/lib/mpv/player/platform/player_android.dart index 51ffb1397..7347cb188 100644 --- a/lib/mpv/player/platform/player_android.dart +++ b/lib/mpv/player/platform/player_android.dart @@ -17,7 +17,7 @@ class PlayerAndroid extends PlayerBase { String _bufferTier = 'auto'; bool _tunnelingEnabled = false; String _dvConversionMode = 'auto'; - String _demuxerMode = 'auto'; + String _demuxerMode = 'ffmpeg'; bool _audioNormalizationEnabled = false; bool _audioPassthroughEnabled = false; bool _downmixEnabled = false; diff --git a/lib/screens/settings/playback_settings_screen.dart b/lib/screens/settings/playback_settings_screen.dart index 066f8abe8..8bd8d4768 100644 --- a/lib/screens/settings/playback_settings_screen.dart +++ b/lib/screens/settings/playback_settings_screen.dart @@ -474,9 +474,8 @@ class _PlaybackSettingsScreenState extends State { ); String _demuxerModeLabel(DemuxerPreference mode) => switch (mode) { - DemuxerPreference.auto => t.settings.demuxerAuto, - DemuxerPreference.ffmpegFirst => t.settings.demuxerFfmpegFirst, - DemuxerPreference.media3Only => t.settings.demuxerMedia3Only, + DemuxerPreference.ffmpeg => t.settings.demuxerFfmpeg, + DemuxerPreference.media3 => t.settings.demuxerMedia3, }; Widget _bufferSizeTile() { diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index 587506943..b42d0504f 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -113,15 +113,14 @@ extension DvConversionModePreferenceNativeValue on DvConversionModePreference { }; } -/// Which containers the Android FFmpeg demuxer takes ahead of media3's own -/// extractors. Wire values are read natively by `FfmpegDemuxerPolicy`. -enum DemuxerPreference { auto, ffmpegFirst, media3Only } +/// Which demuxer parses direct-played files on Android. Wire values are read +/// natively by `FfmpegDemuxerPolicy`; unknown values resolve to FFmpeg there. +enum DemuxerPreference { ffmpeg, media3 } extension DemuxerPreferenceNativeValue on DemuxerPreference { String get nativeValue => switch (this) { - DemuxerPreference.auto => 'auto', - DemuxerPreference.ffmpegFirst => 'ffmpeg', - DemuxerPreference.media3Only => 'media3', + DemuxerPreference.ffmpeg => 'ffmpeg', + DemuxerPreference.media3 => 'media3', }; } @@ -478,7 +477,7 @@ class SettingsService extends BaseSharedPreferencesService { static const demuxerMode = EnumPref( 'demuxer_mode', values: DemuxerPreference.values, - defaultValue: DemuxerPreference.auto, + defaultValue: DemuxerPreference.ffmpeg, ); static const defaultQualityPreset = EnumPref( 'default_quality_preset',