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.
This commit is contained in:
edde746
2026-08-23 01:42:36 +02:00
parent 75c095a5b2
commit c201bc2f03
32 changed files with 111 additions and 221 deletions
@@ -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()
}
@@ -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<String>("bufferTier") ?: "auto"
val demuxerMode = call.argument<String>("demuxerMode") ?: "auto"
val demuxerMode = call.argument<String>("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.
@@ -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
}
@@ -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? {
@@ -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))
}
}
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -321,9 +321,8 @@
"dvConversionHevcStripDescription": "Премахва слоевете Dolby Vision RPU/EL и подава обикновен HEVC поток",
"demuxer": "",
"demuxerDescription": "",
"demuxerAuto": "",
"demuxerFfmpegFirst": "",
"demuxerMedia3Only": "",
"demuxerFfmpeg": "",
"demuxerMedia3": "",
"requireProfileSelectionOnOpen": "Питай за профил при отваряне на приложението",
"requireProfileSelectionOnOpenDescription": "Показвай избор на профил всеки път при отваряне на приложението",
"forceTvMode": "Принуди TV режим",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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",
+3 -4
View File
@@ -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",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -321,9 +321,8 @@
"dvConversionHevcStripDescription": "Dolby Vision の RPU/EL レイヤーを削除し、通常の HEVC として扱います",
"demuxer": "",
"demuxerDescription": "",
"demuxerAuto": "",
"demuxerFfmpegFirst": "",
"demuxerMedia3Only": "",
"demuxerFfmpeg": "",
"demuxerMedia3": "",
"requireProfileSelectionOnOpen": "アプリ起動時にプロフィールを確認",
"requireProfileSelectionOnOpenDescription": "アプリを開くたびにプロフィール選択を表示",
"forceTvMode": "TVモードを強制",
+2 -3
View File
@@ -321,9 +321,8 @@
"dvConversionHevcStripDescription": "Dolby Vision қабаттарын алып тастап, HEVC ретінде көрсету",
"demuxer": "",
"demuxerDescription": "",
"demuxerAuto": "",
"demuxerFfmpegFirst": "",
"demuxerMedia3Only": "",
"demuxerFfmpeg": "",
"demuxerMedia3": "",
"requireProfileSelectionOnOpen": "Ашқанда профильді сұрау",
"requireProfileSelectionOnOpenDescription": "Қолданба ашылған сайын профильді таңдауды көрсету",
"forceTvMode": "TV режимін мәжбүрлеу",
+2 -3
View File
@@ -321,9 +321,8 @@
"dvConversionHevcStripDescription": "Dolby Vision RPU/EL 레이어를 제거하고 일반 HEVC로 제공합니다",
"demuxer": "",
"demuxerDescription": "",
"demuxerAuto": "",
"demuxerFfmpegFirst": "",
"demuxerMedia3Only": "",
"demuxerFfmpeg": "",
"demuxerMedia3": "",
"requireProfileSelectionOnOpen": "앱 실행 시 프로필 선택",
"requireProfileSelectionOnOpenDescription": "앱을 열 때마다 프로필 선택 화면을 표시합니다",
"forceTvMode": "TV 모드 강제 사용",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -321,9 +321,8 @@
"dvConversionHevcStripDescription": "Удалять слои Dolby Vision RPU/EL и передавать обычный HEVC",
"demuxer": "",
"demuxerDescription": "",
"demuxerAuto": "",
"demuxerFfmpegFirst": "",
"demuxerMedia3Only": "",
"demuxerFfmpeg": "",
"demuxerMedia3": "",
"requireProfileSelectionOnOpen": "Запрашивать профиль при запуске",
"requireProfileSelectionOnOpenDescription": "Показывать выбор профиля при каждом открытии приложения",
"forceTvMode": "Принудительный режим ТВ",
+1 -1
View File
@@ -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
+12 -16
View File
@@ -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',
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -321,9 +321,8 @@
"dvConversionHevcStripDescription": "移除 Dolby Vision RPU/EL 層,並以一般 HEVC 呈現",
"demuxer": "",
"demuxerDescription": "",
"demuxerAuto": "",
"demuxerFfmpegFirst": "",
"demuxerMedia3Only": "",
"demuxerFfmpeg": "",
"demuxerMedia3": "",
"requireProfileSelectionOnOpen": "開啟應用程式時要求選擇使用者",
"requireProfileSelectionOnOpenDescription": "每次開啟應用程式時顯示使用者設定檔選擇畫面",
"forceTvMode": "強制 TV 模式",
+2 -3
View File
@@ -321,9 +321,8 @@
"dvConversionHevcStripDescription": "移除 Dolby Vision RPU/EL 层并呈现普通 HEVC",
"demuxer": "",
"demuxerDescription": "",
"demuxerAuto": "",
"demuxerFfmpegFirst": "",
"demuxerMedia3Only": "",
"demuxerFfmpeg": "",
"demuxerMedia3": "",
"requireProfileSelectionOnOpen": "打开应用时选择用户资料",
"requireProfileSelectionOnOpenDescription": "每次打开应用时都显示用户资料选择界面",
"forceTvMode": "强制 TV 模式",
+1 -1
View File
@@ -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;
@@ -474,9 +474,8 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
);
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() {
+6 -7
View File
@@ -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<DemuxerPreference>(
'demuxer_mode',
values: DemuxerPreference.values,
defaultValue: DemuxerPreference.auto,
defaultValue: DemuxerPreference.ffmpeg,
);
static const defaultQualityPreset = EnumPref<TranscodeQualityPreset>(
'default_quality_preset',