fix(android): avoid pixel eac3 decoder fallback

close #1007
This commit is contained in:
edde746
2026-05-11 11:14:32 +02:00
parent b96d734e8c
commit 617f200cb5
3 changed files with 45 additions and 17 deletions
@@ -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
@@ -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)
@@ -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)
}
}