From 617f200cb5be204a6d430ff3dba623e7d6caf2e5 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Mon, 11 May 2026 11:13:41 +0200 Subject: [PATCH] fix(android): avoid pixel eac3 decoder fallback close #1007 --- .../kotlin/com/edde746/plezy/MainActivity.kt | 12 ++------- .../edde746/plezy/exoplayer/ExoPlayerCore.kt | 27 ++++++++++++++----- .../com/edde746/plezy/shared/DeviceQuirks.kt | 23 ++++++++++++++++ 3 files changed, 45 insertions(+), 17 deletions(-) create mode 100644 android/app/src/main/kotlin/com/edde746/plezy/shared/DeviceQuirks.kt diff --git a/android/app/src/main/kotlin/com/edde746/plezy/MainActivity.kt b/android/app/src/main/kotlin/com/edde746/plezy/MainActivity.kt index dbc46723..50b59d5a 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/MainActivity.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/MainActivity.kt @@ -19,6 +19,7 @@ import android.widget.FrameLayout import androidx.core.content.FileProvider import com.edde746.plezy.exoplayer.ExoPlayerPlugin import com.edde746.plezy.mpv.MpvPlayerPlugin +import com.edde746.plezy.shared.DeviceQuirks import com.edde746.plezy.shared.ThemeHelper import com.edde746.plezy.watchnext.WatchNextPlugin import io.flutter.embedding.android.FlutterActivity @@ -169,16 +170,7 @@ class MainActivity : FlutterActivity() { private fun shouldDisableImpeller(): Boolean { // Android TV devices — weaker GPUs, less Impeller testing if (isAndroidTvDevice()) return true - // Google Tensor SoC (Mali GPU) — Pixel 6+ - // SOC_MODEL may return marketing name ("Tensor G2") or internal ID ("GS201") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val soc = Build.SOC_MODEL - if (soc.startsWith("Tensor", ignoreCase = true) || - soc.startsWith("GS", ignoreCase = true) - ) { - return true - } - } + if (DeviceQuirks.isEWaste) return true // NVIDIA Tegra (Shield TV) if (Build.MANUFACTURER.equals("NVIDIA", ignoreCase = true)) return true // Huawei/HONOR Kirin SoCs use Mali GPUs 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 95a2d03a..5743e6ff 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 @@ -50,6 +50,7 @@ import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.CaptionStyleCompat import androidx.media3.ui.SubtitleView import com.edde746.plezy.shared.AudioFocusManager +import com.edde746.plezy.shared.DeviceQuirks import com.edde746.plezy.shared.FlutterOverlayHelper import com.edde746.plezy.shared.FrameRateManager import io.github.peerless2012.ass.media.AssHandler @@ -136,6 +137,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener { private var decoderHangRunnable: Runnable? = null private var decoderInitName: String? = null private var audioDecoderInitName: String? = null + private var loggedEwasteEac3Workaround: Boolean = false private var firstFrameRendered: Boolean = false var delegate: ExoPlayerDelegate? = null var debugLoggingEnabled: Boolean = false @@ -388,14 +390,12 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener { .setUsage(C.USAGE_MEDIA) .build() - // Use DefaultRenderersFactory with FFmpeg fallback for unsupported audio codecs + // Use DefaultRenderersFactory with FFmpeg fallback for unsupported or blocked audio codecs. val renderersFactory = PlezyRenderersFactory(activity).apply { setEnableDecoderFallback(true) setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) - // Force FFmpeg for FLAC — hardware FLAC decoders (e.g. Samsung c2.sec.flac.decoder) - // have buggy 32KB input buffer limits causing InsufficientCapacityException. setMediaCodecSelector { mimeType, requiresSecureDecoder, requiresTunnelingDecoder -> - if (mimeType == MimeTypes.AUDIO_FLAC) { + if (shouldForceAppAudioDecoder(mimeType)) { emptyList() } else { MediaCodecSelector.DEFAULT.getDecoderInfos( @@ -1084,10 +1084,23 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener { // Tunneling control — disabled when audio codec has no hardware decoder (requires FFmpeg) + private fun shouldForceAppAudioDecoder(mimeType: String): Boolean { + if (mimeType == MimeTypes.AUDIO_FLAC) return true + if (DeviceQuirks.isEWaste && isEac3MimeType(mimeType)) { + if (!loggedEwasteEac3Workaround) { + loggedEwasteEac3Workaround = true + emitLog("info", "decoder", "Using app decoder for E-AC3 on this device") + } + return true + } + return false + } + + private fun isEac3MimeType(mimeType: String): Boolean = mimeType == MimeTypes.AUDIO_E_AC3 || mimeType == MimeTypes.AUDIO_E_AC3_JOC + private fun hasHardwareAudioDecoder(mimeType: String): Boolean { - // FLAC hardware decoders are excluded via MediaCodecSelector (Samsung c2.sec.flac.decoder - // has buggy 32KB input buffer limits), so report no hardware decoder for tunneling purposes. - if (mimeType == MimeTypes.AUDIO_FLAC) return false + // Keep tunneling decisions aligned with the MediaCodecSelector exclusions. + if (shouldForceAppAudioDecoder(mimeType)) return false hwAudioDecoderCache[mimeType]?.let { return it } val result = try { val codecList = android.media.MediaCodecList(android.media.MediaCodecList.REGULAR_CODECS) diff --git a/android/app/src/main/kotlin/com/edde746/plezy/shared/DeviceQuirks.kt b/android/app/src/main/kotlin/com/edde746/plezy/shared/DeviceQuirks.kt new file mode 100644 index 00000000..c808518e --- /dev/null +++ b/android/app/src/main/kotlin/com/edde746/plezy/shared/DeviceQuirks.kt @@ -0,0 +1,23 @@ +package com.edde746.plezy.shared + +import android.os.Build + +object DeviceQuirks { + val isEWaste: Boolean + get() = isGooglePixelDevice || isGoogleTensorDevice + + private val isGooglePixelDevice: Boolean + get() = ( + Build.MANUFACTURER.equals("Google", ignoreCase = true) || + Build.BRAND.equals("google", ignoreCase = true) + ) && + Build.MODEL.contains("Pixel", ignoreCase = true) + + private val isGoogleTensorDevice: Boolean + get() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return false + val soc = Build.SOC_MODEL + return soc.startsWith("Tensor", ignoreCase = true) || + soc.startsWith("GS", ignoreCase = true) + } +}