@@ -238,6 +238,9 @@ class MpvPlayerCore(private val activity: Activity) : SurfaceHolder.Callback {
|
||||
setOption("opengl-es", "yes")
|
||||
setOption("vd-lavc-film-grain", "cpu")
|
||||
setOption("ao", "audiotrack,opensles")
|
||||
// Pause on the last frame at EOF instead of unloading the file, so a
|
||||
// seek after the video ends still works (matches Linux/Windows).
|
||||
setOption("keep-open", "yes")
|
||||
if (displayFpsOverride != null) {
|
||||
setOption("display-fps-override", displayFpsOverride)
|
||||
}
|
||||
|
||||
@@ -99,12 +99,25 @@ extension _VideoPlayerPlaybackPromptMethods on VideoPlayerScreenState {
|
||||
_autoPlayTimer?.cancel();
|
||||
_unfocusPlayNextPrompt();
|
||||
_progressTracker?.resumeAfterStoppedReport();
|
||||
_completionTriggered = false; // Reset so it can trigger again if user seeks near end
|
||||
// Keep _completionTriggered set: playback is still parked inside the
|
||||
// end-of-video window, so clearing it here would let the position listener
|
||||
// re-fire this prompt on the next tick. It is re-armed once playback seeks
|
||||
// back clear of the end region (see the position listener) or new media loads.
|
||||
_setPlayerState(() {
|
||||
_showPlayNextDialog = false;
|
||||
});
|
||||
}
|
||||
|
||||
/// Re-arm the end-of-video latch so Play Next can fire again — but only when no
|
||||
/// prompt is visible and no auto-play countdown is running, so we never clobber
|
||||
/// an active dialog. Callers decide *when* it is safe to re-arm (media reloaded,
|
||||
/// or playback moved back out of the end region).
|
||||
void _rearmCompletionLatch() {
|
||||
if (_completionTriggered && !_showPlayNextDialog && _autoPlayTimer?.isActive != true) {
|
||||
_completionTriggered = false;
|
||||
}
|
||||
}
|
||||
|
||||
void _showStillWatchingDialog() {
|
||||
// Don't show if auto-play dialog is already visible
|
||||
if (_showPlayNextDialog) return;
|
||||
|
||||
@@ -323,9 +323,7 @@ extension _VideoPlayerPlaybackServiceMethods on VideoPlayerScreenState {
|
||||
appLogger.i('Network restored while buffering, forcing stream reconnect at ${pos.inSeconds}s');
|
||||
// Clear any stale completion latch caused by a spurious EOF during the drop,
|
||||
// so the real end-of-file can trigger Play Next after we recover.
|
||||
if (_completionTriggered && !_showPlayNextDialog && _autoPlayTimer?.isActive != true) {
|
||||
_completionTriggered = false;
|
||||
}
|
||||
_rearmCompletionLatch();
|
||||
unawaited(_seekPlayback(pos));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,6 +352,13 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
int _autoPlayCountdown = 5;
|
||||
bool _completionTriggered = false;
|
||||
|
||||
// End-of-video Play Next thresholds. Fire the prompt within _kPlayNextTriggerMs
|
||||
// of the end; re-arm (allow it to fire again) only once playback is more than
|
||||
// _kPlayNextRearmMs from the end. The gap is hysteresis so a position parked at
|
||||
// the boundary can't oscillate between firing and re-arming.
|
||||
static const int _kPlayNextTriggerMs = 1000;
|
||||
static const int _kPlayNextRearmMs = 2000;
|
||||
|
||||
late final FocusNode _playNextCancelFocusNode;
|
||||
late final FocusNode _playNextConfirmFocusNode;
|
||||
|
||||
@@ -881,13 +888,18 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
|
||||
_playingSubscription = currentPlayer.streams.playing.listen(_onPlayingStateChanged);
|
||||
|
||||
// Listen to completion. When mpv emits completed=false (file-loaded after a
|
||||
// reconnect-seek or fresh open), clear a stale _completionTriggered so the
|
||||
// real end-of-file can still show Play Next. Guarded against clobbering an
|
||||
// active dialog or running auto-play countdown.
|
||||
_completedSubscription = currentPlayer.streams.completed.listen((done) {
|
||||
if (!done && _completionTriggered && !_showPlayNextDialog && _autoPlayTimer?.isActive != true) {
|
||||
_completionTriggered = false;
|
||||
// completed=false means a file (re)loaded after a reconnect-seek or fresh
|
||||
// open — re-arm the end-of-video latch so the real EOF can still show Play
|
||||
// Next. But only when playback is clear of the end region: a stray
|
||||
// completed=false while parked at EOF must NOT re-arm, or the position
|
||||
// listener would immediately re-fire the Play Next prompt.
|
||||
if (!done) {
|
||||
final durMs = currentPlayer.state.duration.inMilliseconds;
|
||||
final posMs = currentPlayer.state.position.inMilliseconds;
|
||||
if (durMs <= 0 || posMs < durMs - _kPlayNextRearmMs) {
|
||||
_rearmCompletionLatch();
|
||||
}
|
||||
}
|
||||
_onVideoCompleted(done);
|
||||
});
|
||||
@@ -973,11 +985,16 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
}
|
||||
|
||||
final duration = activePlayer.state.duration;
|
||||
if (duration.inMilliseconds > 0 &&
|
||||
position.inMilliseconds >= duration.inMilliseconds - 1000 &&
|
||||
!_showPlayNextDialog &&
|
||||
!_completionTriggered) {
|
||||
_onVideoCompleted(true);
|
||||
if (duration.inMilliseconds > 0) {
|
||||
if (position.inMilliseconds >= duration.inMilliseconds - _kPlayNextTriggerMs &&
|
||||
!_showPlayNextDialog &&
|
||||
!_completionTriggered) {
|
||||
_onVideoCompleted(true);
|
||||
} else if (position.inMilliseconds < duration.inMilliseconds - _kPlayNextRearmMs) {
|
||||
// Seeked back out of the end region after dismissing Play Next — re-arm
|
||||
// so the prompt can fire again if the user returns to the end.
|
||||
_rearmCompletionLatch();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -708,6 +708,9 @@ class MpvPlayerCoreBase: NSObject {
|
||||
checkError(mpv_set_option_string(mpv, "hwdec-codecs", "all"))
|
||||
checkError(mpv_set_option_string(mpv, "hwdec-software-fallback", "yes"))
|
||||
checkError(mpv_set_option_string(mpv, "target-colorspace-hint", "auto"))
|
||||
// Pause on the last frame at EOF instead of unloading the file, so seeking
|
||||
// back after the video ends still works (matches Linux/Windows).
|
||||
checkError(mpv_set_option_string(mpv, "keep-open", "yes"))
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
Reference in New Issue
Block a user