From 0100b2a2ea14852cb8dabfaec58638659ffa734e Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sun, 15 Mar 2026 11:32:25 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20preserve=20start=20position=20and=20subt?= =?UTF-8?q?itles=20in=20ExoPlayer=E2=86=92MPV=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../edde746/plezy/exoplayer/ExoPlayerCore.kt | 8 ++++--- .../plezy/exoplayer/ExoPlayerPlugin.kt | 7 ++++-- lib/mpv/player/player.dart | 10 ++++++++ lib/mpv/player/player_base.dart | 23 +++++++++++++++++++ lib/mpv/player/player_native.dart | 22 ------------------ lib/screens/video_player_screen.dart | 1 + 6 files changed, 44 insertions(+), 27 deletions(-) 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 10ef7539..973bbaf8 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 @@ -143,6 +143,8 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener { // Track state for event emission private var lastPosition: Long = 0 + /** Position to use for fallback: max of current position and pending start position. */ + private val effectivePosition: Long get() = maxOf(lastPosition, pendingStartPositionMs) private var lastDuration: Long = 0 private var lastBufferedPosition: Long = 0 private var positionUpdateRunnable: Runnable? = null @@ -645,7 +647,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener { delegate?.onFormatUnsupported( uri = currentMediaUri!!, headers = currentHeaders, - positionMs = lastPosition, + positionMs = effectivePosition, errorMessage = "Video track present but no decoder available" ) return @@ -671,7 +673,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener { val handled = delegate?.onFormatUnsupported( uri = currentMediaUri!!, headers = currentHeaders, - positionMs = lastPosition, + positionMs = effectivePosition, errorMessage = error.message ?: "Unknown error" ) ?: false @@ -1069,7 +1071,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener { delegate?.onFormatUnsupported( uri = uri, headers = currentHeaders, - positionMs = lastPosition, + positionMs = effectivePosition, errorMessage = "Decoder hang: $decoderName accepted input but produced no output" ) } diff --git a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerPlugin.kt b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerPlugin.kt index 3bd6af4b..8d2c3e5e 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerPlugin.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerPlugin.kt @@ -211,8 +211,11 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, return } - // New media = fresh slate for pending MPV properties - pendingMpvProperties.clear() + // Only clear pending MPV properties when MPV is the active backend. + // When ExoPlayer is active, keep them for potential ExoPlayer→MPV fallback. + if (usingMpvFallback) { + pendingMpvProperties.clear() + } activity?.runOnUiThread { if (usingMpvFallback) { diff --git a/lib/mpv/player/player.dart b/lib/mpv/player/player.dart index 3e4b1228..3fc33759 100644 --- a/lib/mpv/player/player.dart +++ b/lib/mpv/player/player.dart @@ -162,6 +162,16 @@ abstract class Player { /// [args] - Command and arguments as a list of strings. Future command(List args); + // ============================================ + // Subtitle Fonts + // ============================================ + + /// Configure subtitle fonts for libass rendering. + /// + /// Extracts a comprehensive Unicode font (Go Noto) to the cache directory + /// and sets `sub-fonts-dir` and `sub-font` properties. + Future configureSubtitleFonts(); + // ============================================ // Passthrough Mode (Audio) // ============================================ diff --git a/lib/mpv/player/player_base.dart b/lib/mpv/player/player_base.dart index defea1c2..89e4498a 100644 --- a/lib/mpv/player/player_base.dart +++ b/lib/mpv/player/player_base.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart' show protected; import 'package:flutter/services.dart'; import '../../utils/app_logger.dart'; +import '../font_loader.dart'; import '../models.dart'; import 'player.dart'; import 'player_state.dart'; @@ -550,6 +551,28 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player { // ignore: no-empty-block - base no-op, overridden by platform subclasses Future setLogLevel(String level) async {} + // ============================================ + // Subtitle Fonts + // ============================================ + + @override + Future configureSubtitleFonts() async { + try { + final fontDir = await SubtitleFontLoader.loadSubtitleFont(); + if (fontDir != null) { + await setProperty('sub-fonts-dir', fontDir); + await setProperty('sub-font', SubtitleFontLoader.fontName); + } + } catch (e) { + // Font configuration is not critical - continue without it + logController.add(PlayerLog( + prefix: 'fonts', + level: PlayerLogLevel.warn, + text: 'Failed to configure subtitle fonts: $e', + )); + } + } + // ============================================ // Lifecycle // ============================================ diff --git a/lib/mpv/player/player_native.dart b/lib/mpv/player/player_native.dart index aba2ad74..11a52823 100644 --- a/lib/mpv/player/player_native.dart +++ b/lib/mpv/player/player_native.dart @@ -2,7 +2,6 @@ import 'dart:io' show Platform; import 'package:flutter/services.dart'; -import '../font_loader.dart'; import '../models.dart'; import '../../utils/app_logger.dart'; import 'player_base.dart'; @@ -55,9 +54,6 @@ class PlayerNative extends PlayerBase { throw Exception('Failed to initialize player'); } - // Configure subtitle fonts for libass support - await _configureSubtitleFonts(); - // Subscribe to MPV properties await observeProperty('time-pos', 'double'); await observeProperty('duration', 'double'); @@ -80,24 +76,6 @@ class PlayerNative extends PlayerBase { } } - /// Configures subtitle fonts for libass support. - /// Provides a comprehensive Unicode font (Go Noto) with CJK coverage to ensure - /// proper rendering of non-Latin characters in subtitles. - Future _configureSubtitleFonts() async { - try { - final fontDir = await SubtitleFontLoader.loadSubtitleFont(); - if (fontDir != null) { - // Configure MPV to use the extracted font for libass - await setProperty('config', 'yes'); - await setProperty('sub-fonts-dir', fontDir); - await setProperty('sub-font', SubtitleFontLoader.fontName); - } - } catch (e) { - // Font configuration is not critical - continue without it - errorController.add('Failed to configure subtitle fonts: $e'); - } - } - // ============================================ // Playback Control // ============================================ diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index 8ba3e2de..3783cab5 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -413,6 +413,7 @@ class VideoPlayerScreenState extends State with WidgetsBindin // Create player (on Android, uses ExoPlayer by default, MPV as fallback) player = Player(useExoPlayer: useExoPlayer); + await player!.configureSubtitleFonts(); await player!.setProperty('sub-ass', 'yes'); // Enable libass if (Platform.isAndroid && useExoPlayer) { final tunneledPlayback = settingsService.getTunneledPlayback();