refactor: extract shared mixins and helpers, drop dead abstractions

Introduces shared seams for paginated views, D-pad reorder, media control
routing, async singletons and the device method channel, then points the
open-coded copies at them.

Also removes unused models and duplicated provider/server plumbing, folds
the twice-implemented artifact store in the server, and factors the
repeated Flutter toolchain prologue in CI into a composite action.
This commit is contained in:
edde746
2026-07-26 06:09:48 +02:00
parent 61344f7862
commit 352b88109b
217 changed files with 6813 additions and 8773 deletions
@@ -77,6 +77,7 @@ import com.edde746.plezy.shared.FlutterOverlayHelper
import com.edde746.plezy.shared.FrameRateManager
import com.edde746.plezy.shared.MediaCodecQuery
import com.edde746.plezy.shared.PlayerSurfaceHost
import com.edde746.plezy.shared.SurfacePlayerCore
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicLong
import org.chromium.net.CronetEngine
@@ -102,7 +103,7 @@ interface ExoPlayerDelegate : com.edde746.plezy.shared.PlayerDelegate {
internal fun playbackMimeType(isLive: Boolean): String? = if (isLive) MimeTypes.APPLICATION_M3U8 else null
@OptIn(UnstableApi::class)
class ExoPlayerCore(private val activity: Activity) : Player.Listener {
class ExoPlayerCore(private val activity: Activity) : Player.Listener, SurfacePlayerCore {
companion object {
private const val TAG = "ExoPlayerCore"
@@ -3400,7 +3401,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
else -> null
}
fun setVisible(visible: Boolean) {
override fun setVisible(visible: Boolean) {
if (disposing) return
currentVisible = visible
activity.runOnUiThread {
@@ -3493,7 +3494,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
}
}
fun onPipModeChanged(isInPipMode: Boolean) {
override fun onPipModeChanged(isInPipMode: Boolean) {
if (disposing) return
activity.runOnUiThread {
if (disposing) return@runOnUiThread
@@ -3509,7 +3510,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
}
}
fun updateFrame() {
override fun updateFrame() {
if (disposing) return
activity.runOnUiThread {
if (disposing) return@runOnUiThread
@@ -3524,15 +3525,15 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
// Audio Focus
fun requestAudioFocus(): Boolean = audioFocusManager?.requestAudioFocus() ?: false
override fun requestAudioFocus(): Boolean = audioFocusManager?.requestAudioFocus() ?: false
fun abandonAudioFocus() {
override fun abandonAudioFocus() {
audioFocusManager?.abandonAudioFocus()
}
// Frame Rate Matching
fun setVideoFrameRate(
override fun setVideoFrameRate(
fps: Float,
videoDurationMs: Long,
extraDelayMs: Long,
@@ -3548,7 +3549,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
mgr.setVideoFrameRate(fps, videoDurationMs, extraDelayMs, videoWidth, videoHeight, onComplete)
}
fun clearVideoFrameRate() {
override fun clearVideoFrameRate() {
frameRateManager?.clearVideoFrameRate()
}
@@ -12,6 +12,7 @@ import com.edde746.plezy.shared.MpvContentUriResolver
import com.edde746.plezy.shared.PlayerChannelBinding
import com.edde746.plezy.shared.PlayerDelegate
import com.edde746.plezy.shared.ResolvedMpvUri
import com.edde746.plezy.shared.SurfacePlayerCore
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
@@ -45,6 +46,10 @@ class ExoPlayerPlugin :
private var activity: Activity? = null
private var activityBinding: ActivityPluginBinding? = null
/** Whichever core currently owns the surface; both expose [SurfacePlayerCore] identically. */
private val activeSurfaceCore: SurfacePlayerCore?
get() = if (usingMpvFallback) mpvCore else playerCore
// Every Dart observeProperty registration, kept so an ExoPlayer→MPV
// fallback can re-observe exactly what Dart asked for instead of
// maintaining a parallel hard-coded list.
@@ -949,20 +954,12 @@ class ExoPlayerPlugin :
return
}
if (usingMpvFallback) {
mpvCore?.setVisible(visible)
} else {
playerCore?.setVisible(visible)
}
activeSurfaceCore?.setVisible(visible)
result.success(null)
}
private fun handleUpdateFrame(result: MethodChannel.Result) {
if (usingMpvFallback) {
mpvCore?.updateFrame()
} else {
playerCore?.updateFrame()
}
activeSurfaceCore?.updateFrame()
result.success(null)
}
@@ -974,51 +971,31 @@ class ExoPlayerPlugin :
val videoHeight = call.argument<Number>("videoHeight")?.toInt() ?: 0
Log.d(TAG, "setVideoFrameRate: fps=$fps, duration=$duration, extraDelayMs=$extraDelayMs, video=${videoWidth}x$videoHeight")
val onComplete: (Boolean) -> Unit = { switched -> result.success(switched) }
if (usingMpvFallback) {
val core = mpvCore
if (core == null) {
result.success(false)
} else {
core.setVideoFrameRate(fps, duration, extraDelayMs, videoWidth, videoHeight, onComplete)
}
} else {
val core = playerCore
if (core == null) {
result.success(false)
} else {
core.setVideoFrameRate(fps, duration, extraDelayMs, videoWidth, videoHeight, onComplete)
}
val core = activeSurfaceCore
if (core == null) {
result.success(false)
return
}
core.setVideoFrameRate(fps, duration, extraDelayMs, videoWidth, videoHeight) { switched ->
result.success(switched)
}
}
private fun handleClearVideoFrameRate(result: MethodChannel.Result) {
Log.d(TAG, "clearVideoFrameRate")
if (usingMpvFallback) {
mpvCore?.clearVideoFrameRate()
} else {
playerCore?.clearVideoFrameRate()
}
activeSurfaceCore?.clearVideoFrameRate()
result.success(null)
}
private fun handleRequestAudioFocus(result: MethodChannel.Result) {
Log.d(TAG, "requestAudioFocus")
val granted = if (usingMpvFallback) {
mpvCore?.requestAudioFocus() ?: false
} else {
playerCore?.requestAudioFocus() ?: false
}
val granted = activeSurfaceCore?.requestAudioFocus() ?: false
result.success(granted)
}
private fun handleAbandonAudioFocus(result: MethodChannel.Result) {
Log.d(TAG, "abandonAudioFocus")
if (usingMpvFallback) {
mpvCore?.abandonAudioFocus()
} else {
playerCore?.abandonAudioFocus()
}
activeSurfaceCore?.abandonAudioFocus()
result.success(null)
}
@@ -1228,11 +1205,7 @@ class ExoPlayerPlugin :
fun onPipModeChanged(isInPipMode: Boolean) {
activity?.runOnUiThread {
if (usingMpvFallback) {
mpvCore?.onPipModeChanged(isInPipMode)
} else {
playerCore?.onPipModeChanged(isInPipMode)
}
activeSurfaceCore?.onPipModeChanged(isInPipMode)
}
}
@@ -19,6 +19,7 @@ import com.edde746.plezy.shared.AudioFocusManager
import com.edde746.plezy.shared.FrameRateManager
import com.edde746.plezy.shared.PlayerDelegate
import com.edde746.plezy.shared.PlayerSurfaceHost
import com.edde746.plezy.shared.SurfacePlayerCore
import dev.jdtech.mpv.*
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
@@ -40,7 +41,7 @@ class MpvPlayerCore private constructor(
private val audioOnly: Boolean,
private val propertyWriterOverride: (suspend (String, String) -> Unit)?,
initializedForTesting: Boolean
) : SurfaceHolder.Callback {
) : SurfaceHolder.Callback, SurfacePlayerCore {
constructor(context: Context, audioOnly: Boolean = false) : this(context, audioOnly, null, false)
internal constructor(
@@ -405,7 +406,7 @@ class MpvPlayerCore private constructor(
// Audio Focus
fun requestAudioFocus(): Boolean {
override fun requestAudioFocus(): Boolean {
val granted = audioFocusManager?.requestAudioFocus() ?: false
if (granted && pausedForAudioFocusLoss) {
resumeAfterAudioFocusGain("audio focus request granted")
@@ -413,7 +414,7 @@ class MpvPlayerCore private constructor(
return granted
}
fun abandonAudioFocus() {
override fun abandonAudioFocus() {
audioFocusManager?.abandonAudioFocus()
}
@@ -1042,7 +1043,7 @@ class MpvPlayerCore private constructor(
}
}
fun setVisible(visible: Boolean) {
override fun setVisible(visible: Boolean) {
// Audio-only: no render layer to show or hide — tolerated no-op.
if (audioOnly || disposing) return
runOnMain {
@@ -1067,11 +1068,11 @@ class MpvPlayerCore private constructor(
}
}
fun onPipModeChanged(isInPipMode: Boolean) {
override fun onPipModeChanged(isInPipMode: Boolean) {
// MPV handles aspect ratio internally via its own surface management
}
fun updateFrame() {
override fun updateFrame() {
// Audio-only: no surface to refresh — tolerated no-op.
if (audioOnly || disposing) return
runOnMain {
@@ -1106,7 +1107,7 @@ class MpvPlayerCore private constructor(
// Frame Rate Matching
fun setVideoFrameRate(
override fun setVideoFrameRate(
fps: Float,
videoDurationMs: Long,
extraDelayMs: Long,
@@ -1128,7 +1129,7 @@ class MpvPlayerCore private constructor(
}
}
fun clearVideoFrameRate() {
override fun clearVideoFrameRate() {
frameRateManager?.clearVideoFrameRate()
}
@@ -0,0 +1,27 @@
package com.edde746.plezy.shared
/**
* Surface and display concerns that the ExoPlayer and mpv cores implement
* identically, so a plugin holding either one dispatches without branching
* on which backend is active.
*
* Only backend-independent members belong here: playback control
* (play/seek/track selection) stays off this interface because mpv drives it
* through properties and commands where ExoPlayer uses direct method calls.
*/
interface SurfacePlayerCore {
fun setVisible(visible: Boolean)
fun updateFrame()
fun onPipModeChanged(isInPipMode: Boolean)
fun requestAudioFocus(): Boolean
fun abandonAudioFocus()
fun clearVideoFrameRate()
fun setVideoFrameRate(
fps: Float,
videoDurationMs: Long,
extraDelayMs: Long,
videoWidth: Int,
videoHeight: Int,
onComplete: (switched: Boolean) -> Unit
)
}