feat: auto-reconnect VOD playback after network loss

This commit is contained in:
edde746
2026-03-10 18:47:31 +01:00
parent af2ac2c993
commit 23a100afdc
3 changed files with 40 additions and 1 deletions
@@ -37,6 +37,7 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
private var configuredTunnelingEnabled: Boolean = true
private var debugLoggingEnabled: Boolean = false
private val pendingMpvProperties = mutableListOf<Pair<String, String>>()
// FlutterPlugin
@@ -133,6 +134,7 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
"setSubtitleStyle" -> handleSetSubtitleStyle(call, result)
"observeProperty" -> handleObserveProperty(call, result)
"setMpvProperty" -> handleSetMpvProperty(call, result)
"setLogLevel" -> {
val level = call.argument<String>("level") ?: "warn"
debugLoggingEnabled = (level == "v" || level == "debug" || level == "trace")
@@ -209,6 +211,9 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
return
}
// New media = fresh slate for pending MPV properties
pendingMpvProperties.clear()
activity?.runOnUiThread {
if (usingMpvFallback) {
// MPV: Build loadfile command with options
@@ -472,6 +477,24 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
result.success(null)
}
private fun handleSetMpvProperty(call: MethodCall, result: MethodChannel.Result) {
val name = call.argument<String>("name")
val value = call.argument<String>("value")
if (name == null || value == null) {
result.error("INVALID_ARGS", "Missing 'name' or 'value'", null)
return
}
if (usingMpvFallback) {
mpvCore?.setProperty(name, value)
} else {
// Store for later application if ExoPlayer falls back to MPV
pendingMpvProperties.add(Pair(name, value))
}
result.success(null)
}
private fun handleGetStats(result: MethodChannel.Result) {
activity?.runOnUiThread {
val stats = if (usingMpvFallback) {
@@ -644,6 +667,12 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
}
// Apply any pending MPV properties from Dart
for ((propName, propValue) in pendingMpvProperties) {
mpvCore?.setProperty(propName, propValue)
}
pendingMpvProperties.clear()
// Setup property observers
mpvCore?.observeProperty("time-pos", "double")
mpvCore?.observeProperty("duration", "double")
+3 -1
View File
@@ -220,7 +220,9 @@ class PlayerAndroid extends PlayerBase {
}
}
break;
// Other properties are no-ops for ExoPlayer
default:
// Forward unknown properties to Kotlin for MPV fallback
await invoke('setMpvProperty', {'name': name, 'value': value});
}
}
+8
View File
@@ -1053,6 +1053,14 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
? Duration(milliseconds: widget.metadata.viewOffset!)
: null;
// Enable FFmpeg auto-reconnect for VOD streams (covers network drops up to 10 min)
if (!widget.isOffline && !widget.isLive) {
await player!.setProperty('stream-lavf-o-append', 'reconnect=1');
await player!.setProperty('stream-lavf-o-append', 'reconnect_on_network_error=1');
await player!.setProperty('stream-lavf-o-append', 'reconnect_streamed=1');
await player!.setProperty('stream-lavf-o-append', 'reconnect_delay_max=600');
}
// If we have external subtitles, open paused to add them before playback starts.
// This prevents a race condition on Android where adding subtitle tracks
// during active playback can freeze the video decoder (issue #226).