fix: memory-aware buffer limits

This commit is contained in:
edde746
2026-02-24 00:56:09 +01:00
parent a1a030a130
commit 2276750704
3 changed files with 61 additions and 7 deletions
@@ -1,7 +1,10 @@
package com.edde746.plezy.exoplayer
import android.app.Activity
import android.app.ActivityManager
import android.content.ComponentCallbacks2
import android.content.Context
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.PixelFormat
import android.hardware.display.DisplayManager
@@ -102,6 +105,9 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
private var hasAudioFocus: Boolean = false
private var wasPlayingBeforeFocusLoss: Boolean = false
// Memory pressure detection
private var memoryCallback: ComponentCallbacks2? = null
// Track state for event emission
private var lastPosition: Long = 0
private var lastDuration: Long = 0
@@ -339,15 +345,29 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
val wrappedRenderersFactory = AssRenderersFactory(handler, renderersFactory)
val loadControl = DefaultLoadControl.Builder().apply {
if (bufferSizeBytes != null && bufferSizeBytes > 0) {
setTargetBufferBytes(bufferSizeBytes)
setPrioritizeTimeOverSizeThresholds(false)
Log.d(TAG, "Buffer byte limit set to ${bufferSizeBytes / 1024 / 1024}MB")
} else {
Log.d(TAG, "Buffer in auto mode (time-based thresholds)")
// Compute memory-aware buffer limits to prevent CCodec OOM crashes
val activityManager = activity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val memoryInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memoryInfo)
val availableMB = memoryInfo.availMem / (1024 * 1024)
val targetBufferBytes = if (bufferSizeBytes != null && bufferSizeBytes > 0) {
bufferSizeBytes
} else {
// Scale buffer to available memory to reduce hardware decoder pressure
when {
availableMB < 512 -> 50 * 1024 * 1024
availableMB < 1024 -> 75 * 1024 * 1024
else -> 150 * 1024 * 1024
}
}
val loadControl = DefaultLoadControl.Builder().apply {
setTargetBufferBytes(targetBufferBytes)
setPrioritizeTimeOverSizeThresholds(false)
setBufferDurationsMs(15_000, 30_000, 2_500, 5_000)
}.build()
Log.d(TAG, "Buffer: ${targetBufferBytes / 1024 / 1024}MB limit, available=${availableMB}MB")
exoPlayer = ExoPlayer.Builder(activity)
.setTrackSelector(trackSelector!!)
@@ -381,6 +401,22 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
}
}
// Register memory pressure listener to detect impending OOM
memoryCallback = object : ComponentCallbacks2 {
override fun onTrimMemory(level: Int) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
Log.w(TAG, "TRIM_MEMORY level $level - critical memory pressure")
delegate?.onEvent("memory-pressure", mapOf("level" to "critical"))
}
}
override fun onConfigurationChanged(newConfig: Configuration) {}
override fun onLowMemory() {
Log.w(TAG, "onLowMemory - system-wide memory pressure")
delegate?.onEvent("memory-pressure", mapOf("level" to "critical"))
}
}
activity.registerComponentCallbacks(memoryCallback)
// Start position update loop
startPositionUpdates()
@@ -1266,6 +1302,9 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
abandonAudioFocus()
audioManager = null
memoryCallback?.let { activity.unregisterComponentCallbacks(it) }
memoryCallback = null
exoPlayer?.clearVideoSurface()
exoPlayer?.removeListener(this)
exoPlayer?.release()
@@ -32,6 +32,7 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
private var activity: Activity? = null
private var activityBinding: ActivityPluginBinding? = null
private val nameToId = mutableMapOf<String, Int>()
private var configuredBufferSizeBytes: Int? = null
// FlutterPlugin
@@ -145,6 +146,7 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
val bufferSizeBytes = call.argument<Int>("bufferSizeBytes")
configuredBufferSizeBytes = bufferSizeBytes
currentActivity.runOnUiThread {
try {
@@ -598,6 +600,13 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
mpvCore?.setProperty("vo", "gpu")
mpvCore?.setProperty("ao", "audiotrack")
// Forward user's buffer config to MPV fallback
configuredBufferSizeBytes?.let { bytes ->
if (bytes > 0) {
mpvCore?.setProperty("demuxer-max-bytes", bytes.toString())
}
}
// Setup property observers
mpvCore?.observeProperty("time-pos", "double")
mpvCore?.observeProperty("duration", "double")
+6
View File
@@ -36,6 +36,12 @@ class PlayerAndroid extends PlayerBase {
return;
}
if (name == 'memory-pressure') {
// System memory is critically low — playback may be at risk of OOM crash
errorController.add('Low memory — playback may be unstable');
return;
}
// Delegate to base class for common events
super.handlePlayerEvent(name, data);
}