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:
@@ -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? {
|
||||
|
||||
+11
-57
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -321,9 +321,8 @@
|
||||
"dvConversionHevcStripDescription": "Премахва слоевете Dolby Vision RPU/EL и подава обикновен HEVC поток",
|
||||
"demuxer": "",
|
||||
"demuxerDescription": "",
|
||||
"demuxerAuto": "",
|
||||
"demuxerFfmpegFirst": "",
|
||||
"demuxerMedia3Only": "",
|
||||
"demuxerFfmpeg": "",
|
||||
"demuxerMedia3": "",
|
||||
"requireProfileSelectionOnOpen": "Питай за профил при отваряне на приложението",
|
||||
"requireProfileSelectionOnOpenDescription": "Показвай избор на профил всеки път при отваряне на приложението",
|
||||
"forceTvMode": "Принуди TV режим",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -321,9 +321,8 @@
|
||||
"dvConversionHevcStripDescription": "Dolby Vision の RPU/EL レイヤーを削除し、通常の HEVC として扱います",
|
||||
"demuxer": "",
|
||||
"demuxerDescription": "",
|
||||
"demuxerAuto": "",
|
||||
"demuxerFfmpegFirst": "",
|
||||
"demuxerMedia3Only": "",
|
||||
"demuxerFfmpeg": "",
|
||||
"demuxerMedia3": "",
|
||||
"requireProfileSelectionOnOpen": "アプリ起動時にプロフィールを確認",
|
||||
"requireProfileSelectionOnOpenDescription": "アプリを開くたびにプロフィール選択を表示",
|
||||
"forceTvMode": "TVモードを強制",
|
||||
|
||||
@@ -321,9 +321,8 @@
|
||||
"dvConversionHevcStripDescription": "Dolby Vision қабаттарын алып тастап, HEVC ретінде көрсету",
|
||||
"demuxer": "",
|
||||
"demuxerDescription": "",
|
||||
"demuxerAuto": "",
|
||||
"demuxerFfmpegFirst": "",
|
||||
"demuxerMedia3Only": "",
|
||||
"demuxerFfmpeg": "",
|
||||
"demuxerMedia3": "",
|
||||
"requireProfileSelectionOnOpen": "Ашқанда профильді сұрау",
|
||||
"requireProfileSelectionOnOpenDescription": "Қолданба ашылған сайын профильді таңдауды көрсету",
|
||||
"forceTvMode": "TV режимін мәжбүрлеу",
|
||||
|
||||
@@ -321,9 +321,8 @@
|
||||
"dvConversionHevcStripDescription": "Dolby Vision RPU/EL 레이어를 제거하고 일반 HEVC로 제공합니다",
|
||||
"demuxer": "",
|
||||
"demuxerDescription": "",
|
||||
"demuxerAuto": "",
|
||||
"demuxerFfmpegFirst": "",
|
||||
"demuxerMedia3Only": "",
|
||||
"demuxerFfmpeg": "",
|
||||
"demuxerMedia3": "",
|
||||
"requireProfileSelectionOnOpen": "앱 실행 시 프로필 선택",
|
||||
"requireProfileSelectionOnOpenDescription": "앱을 열 때마다 프로필 선택 화면을 표시합니다",
|
||||
"forceTvMode": "TV 모드 강제 사용",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -321,9 +321,8 @@
|
||||
"dvConversionHevcStripDescription": "Удалять слои Dolby Vision RPU/EL и передавать обычный HEVC",
|
||||
"demuxer": "",
|
||||
"demuxerDescription": "",
|
||||
"demuxerAuto": "",
|
||||
"demuxerFfmpegFirst": "",
|
||||
"demuxerMedia3Only": "",
|
||||
"demuxerFfmpeg": "",
|
||||
"demuxerMedia3": "",
|
||||
"requireProfileSelectionOnOpen": "Запрашивать профиль при запуске",
|
||||
"requireProfileSelectionOnOpenDescription": "Показывать выбор профиля при каждом открытии приложения",
|
||||
"forceTvMode": "Принудительный режим ТВ",
|
||||
|
||||
@@ -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
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -321,9 +321,8 @@
|
||||
"dvConversionHevcStripDescription": "移除 Dolby Vision RPU/EL 層,並以一般 HEVC 呈現",
|
||||
"demuxer": "",
|
||||
"demuxerDescription": "",
|
||||
"demuxerAuto": "",
|
||||
"demuxerFfmpegFirst": "",
|
||||
"demuxerMedia3Only": "",
|
||||
"demuxerFfmpeg": "",
|
||||
"demuxerMedia3": "",
|
||||
"requireProfileSelectionOnOpen": "開啟應用程式時要求選擇使用者",
|
||||
"requireProfileSelectionOnOpenDescription": "每次開啟應用程式時顯示使用者設定檔選擇畫面",
|
||||
"forceTvMode": "強制 TV 模式",
|
||||
|
||||
@@ -321,9 +321,8 @@
|
||||
"dvConversionHevcStripDescription": "移除 Dolby Vision RPU/EL 层并呈现普通 HEVC",
|
||||
"demuxer": "",
|
||||
"demuxerDescription": "",
|
||||
"demuxerAuto": "",
|
||||
"demuxerFfmpegFirst": "",
|
||||
"demuxerMedia3Only": "",
|
||||
"demuxerFfmpeg": "",
|
||||
"demuxerMedia3": "",
|
||||
"requireProfileSelectionOnOpen": "打开应用时选择用户资料",
|
||||
"requireProfileSelectionOnOpenDescription": "每次打开应用时都显示用户资料选择界面",
|
||||
"forceTvMode": "强制 TV 模式",
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user