From b84166e2a5a8b4373f091713b2596e7b80668dbf Mon Sep 17 00:00:00 2001 From: Damontecres Date: Mon, 10 Nov 2025 09:24:20 -0500 Subject: [PATCH] Beter end-of-file events --- .../damontecres/wholphin/util/mpv/MPVLib.kt | 49 ++++++++++++ .../wholphin/util/mpv/MpvPlayer.kt | 77 +++++++++++++++---- app/src/main/jni/event.cpp | 9 +++ app/src/main/jni/jni_utils.cpp | 1 + app/src/main/jni/jni_utils.h | 1 + 5 files changed, 121 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MPVLib.kt b/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MPVLib.kt index 7830a049..52af2644 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MPVLib.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MPVLib.kt @@ -170,6 +170,18 @@ object MPVLib { } } + @JvmStatic + fun eventEndFile( + reason: Int, + error: Int, + ) { + synchronized(observers) { + for (o in observers) { + o.eventEndFile(reason, error) + } + } + } + private val log_observers = mutableListOf() @JvmStatic @@ -223,6 +235,11 @@ object MPVLib { ) fun event(eventId: Int) + + fun eventEndFile( + reason: Int, + error: Int, + ) } interface LogObserver { @@ -282,4 +299,36 @@ object MPVLib { const val MPV_LOG_LEVEL_DEBUG: Int = 60 const val MPV_LOG_LEVEL_TRACE: Int = 70 } + + object MpvEndFileReason { + const val MPV_END_FILE_REASON_EOF: Int = 0 + const val MPV_END_FILE_REASON_STOP: Int = 2 + const val MPV_END_FILE_REASON_QUIT: Int = 3 + const val MPV_END_FILE_REASON_ERROR: Int = 4 + const val MPV_END_FILE_REASON_REDIRECT: Int = 5 + } + + object MpvError { + const val MPV_ERROR_SUCCESS = 0 + const val MPV_ERROR_EVENT_QUEUE_FULL = -1 + const val MPV_ERROR_NOMEM = -2 + const val MPV_ERROR_UNINITIALIZED = -3 + const val MPV_ERROR_INVALID_PARAMETER = -4 + const val MPV_ERROR_OPTION_NOT_FOUND = -5 + const val MPV_ERROR_OPTION_FORMAT = -6 + const val MPV_ERROR_OPTION_ERROR = -7 + const val MPV_ERROR_PROPERTY_NOT_FOUND = -8 + const val MPV_ERROR_PROPERTY_FORMAT = -9 + const val MPV_ERROR_PROPERTY_UNAVAILABLE = -10 + const val MPV_ERROR_PROPERTY_ERROR = -11 + const val MPV_ERROR_COMMAND = -12 + const val MPV_ERROR_LOADING_FAILED = -13 + const val MPV_ERROR_AO_INIT_FAILED = -14 + const val MPV_ERROR_VO_INIT_FAILED = -15 + const val MPV_ERROR_NOTHING_TO_PLAY = -16 + const val MPV_ERROR_UNKNOWN_FORMAT = -17 + const val MPV_ERROR_UNSUPPORTED = -18 + const val MPV_ERROR_NOT_IMPLEMENTED = -19 + const val MPV_ERROR_GENERIC = -20 + } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt b/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt index 6c9550f5..2969489b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt @@ -35,6 +35,9 @@ import androidx.media3.common.util.Util import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.exoplayer.trackselection.TrackSelector import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter +import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvEndFileReason.MPV_END_FILE_REASON_EOF +import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvEndFileReason.MPV_END_FILE_REASON_ERROR +import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvEndFileReason.MPV_END_FILE_REASON_STOP import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvEvent.MPV_EVENT_AUDIO_RECONFIG import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvEvent.MPV_EVENT_END_FILE import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvEvent.MPV_EVENT_FILE_LOADED @@ -83,6 +86,7 @@ class MpvPlayer( private var positionMs: Long = -1L private var playbackState: Int = STATE_READY + @Volatile var isReleased = false private set @@ -277,9 +281,15 @@ class MpvPlayer( } override fun stop() { - throwIfReleased() if (DEBUG) Timber.v("stop") + throwIfReleased() pause() + mediaItem = null + positionMs = -1L + durationMs = 0L + playbackState = STATE_IDLE + notifyListeners(EVENT_IS_PLAYING_CHANGED) { onIsPlayingChanged(false) } + notifyListeners(EVENT_PLAYBACK_STATE_CHANGED) { onPlaybackStateChanged(STATE_IDLE) } } override fun release() { @@ -294,6 +304,7 @@ class MpvPlayer( override fun getCurrentTracks(): Tracks { if (DEBUG) Timber.v("getCurrentTracks") + throwIfReleased() return getTracks() } @@ -307,6 +318,7 @@ class MpvPlayer( override fun setTrackSelectionParameters(parameters: TrackSelectionParameters) { Timber.v("TrackSelection: setTrackSelectionParameters %s", parameters) + throwIfReleased() val tracks = getTracks() if (C.TRACK_TYPE_TEXT in parameters.disabledTrackTypes) { // Subtitles disabled @@ -352,7 +364,7 @@ class MpvPlayer( override fun getMediaMetadata(): MediaMetadata { if (DEBUG) Timber.v("getMediaMetadata") - return mediaItem!!.mediaMetadata + return MediaMetadata.EMPTY } override fun getPlaylistMetadata(): MediaMetadata { @@ -408,6 +420,7 @@ class MpvPlayer( override fun getTotalBufferedDuration(): Long { if (DEBUG) Timber.v("getTotalBufferedDuration") + if (isReleased) return 0 return MPVLib.getPropertyDouble("demuxer-cache-duration")?.seconds?.inWholeMilliseconds ?: 0 } @@ -426,8 +439,7 @@ class MpvPlayer( override fun getAudioAttributes(): AudioAttributes = throw UnsupportedOperationException() - override fun setVolume(volume: Float) { - } + override fun setVolume(volume: Float): Unit = throw UnsupportedOperationException() override fun getVolume(): Float = 1f @@ -622,16 +634,7 @@ class MpvPlayer( MPV_EVENT_END_FILE -> { Timber.d("event: MPV_EVENT_END_FILE") - notifyListeners(EVENT_IS_PLAYING_CHANGED) { onIsPlayingChanged(false) } - - val curPos = MPVLib.getPropertyDouble("time-pos/full") - Timber.v("MPV_EVENT_END_FILE: positionMs=$positionMs, durationMs=$durationMs, curPos=$curPos") - if (positionMs >= (durationMs - 1000) && curPos == null) { - playbackState = STATE_ENDED - notifyListeners(EVENT_PLAYBACK_STATE_CHANGED) { - onPlaybackStateChanged(STATE_ENDED) - } - } + // Handled by eventEndFile } else -> { @@ -640,6 +643,42 @@ class MpvPlayer( } } + override fun eventEndFile( + reason: Int, + error: Int, + ) { + Timber.d("MPV_EVENT_END_FILE: %s %s", reason, error) + notifyListeners(EVENT_IS_PLAYING_CHANGED) { onIsPlayingChanged(false) } + when (reason) { + MPV_END_FILE_REASON_EOF -> { + notifyListeners(EVENT_PLAYBACK_STATE_CHANGED) { + onPlaybackStateChanged(STATE_ENDED) + } + } + + MPV_END_FILE_REASON_STOP -> { + // User initiated (eg stop, play next, etc) + } + + MPV_END_FILE_REASON_ERROR -> { + Timber.e("libmpv error, error=%s", error) + notifyListeners(EVENT_PLAYER_ERROR) { + onPlayerError( + PlaybackException( + "libmpv error", + null, + error, + ), + ) + } + } + + else -> { + // no-op + } + } + } + private fun loadFile() { val url = mediaItem!!.localConfiguration?.uri.toString() if (startPositionMs > 0) { @@ -743,8 +782,14 @@ class MpvPlayer( } var subtitleDelay: Double - get() = MPVLib.getPropertyDouble("sub-delay") ?: 0.0 - set(value) = MPVLib.setPropertyDouble("sub-delay", value) + get() { + throwIfReleased() + return MPVLib.getPropertyDouble("sub-delay") ?: 0.0 + } + set(value) { + throwIfReleased() + MPVLib.setPropertyDouble("sub-delay", value) + } } fun MPVLib.setPropertyColor( diff --git a/app/src/main/jni/event.cpp b/app/src/main/jni/event.cpp index 4265e925..b092b661 100644 --- a/app/src/main/jni/event.cpp +++ b/app/src/main/jni/event.cpp @@ -45,6 +45,11 @@ static void sendEventToJava(JNIEnv *env, int event) env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_event, event); } +static void sendEndFileEventToJava(JNIEnv *env, mpv_event_end_file *event) +{ + env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_end_file_event, event->reason, event->error); +} + static void sendLogMessageToJava(JNIEnv *env, mpv_event_log_message *msg) { // filter the most obvious cases of invalid utf-8, since Java would choke on it @@ -79,6 +84,7 @@ void *event_thread(void *arg) mpv_event *mp_event; mpv_event_property *mp_property = NULL; mpv_event_log_message *msg = NULL; + mpv_event_end_file *mp_end_file = NULL; mp_event = mpv_wait_event(g_mpv, -1.0); @@ -98,6 +104,9 @@ void *event_thread(void *arg) mp_property = (mpv_event_property*)mp_event->data; sendPropertyUpdateToJava(env, mp_property); break; + case MPV_EVENT_END_FILE: + mp_end_file = (mpv_event_end_file*)mp_event->data; + sendEndFileEventToJava(env, mp_end_file); default: ALOGV("event: %s\n", mpv_event_name(mp_event->event_id)); sendEventToJava(env, mp_event->event_id); diff --git a/app/src/main/jni/jni_utils.cpp b/app/src/main/jni/jni_utils.cpp index 752c03c5..f878fdff 100644 --- a/app/src/main/jni/jni_utils.cpp +++ b/app/src/main/jni/jni_utils.cpp @@ -44,6 +44,7 @@ void init_methods_cache(JNIEnv *env) mpv_MPVLib_eventProperty_Sd = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;D)V"); // eventProperty(String, double) mpv_MPVLib_eventProperty_SS = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;Ljava/lang/String;)V"); // eventProperty(String, String) mpv_MPVLib_event = env->GetStaticMethodID(mpv_MPVLib, "event", "(I)V"); // event(int) + mpv_MPVLib_end_file_event = env->GetStaticMethodID(mpv_MPVLib, "eventEndFile", "(II)V"); // eventEndFile(int, int) mpv_MPVLib_logMessage_SiS = env->GetStaticMethodID(mpv_MPVLib, "logMessage", "(Ljava/lang/String;ILjava/lang/String;)V"); // logMessage(String, int, String) #undef FIND_CLASS diff --git a/app/src/main/jni/jni_utils.h b/app/src/main/jni/jni_utils.h index e8759032..ed9d025d 100644 --- a/app/src/main/jni/jni_utils.h +++ b/app/src/main/jni/jni_utils.h @@ -26,4 +26,5 @@ UTIL_EXTERN jmethodID mpv_MPVLib_eventProperty_S, mpv_MPVLib_eventProperty_Sd, mpv_MPVLib_eventProperty_SS, mpv_MPVLib_event, + mpv_MPVLib_end_file_event, mpv_MPVLib_logMessage_SiS;