Fix crash on stopping

This commit is contained in:
Damontecres 2025-11-05 14:48:08 -05:00
parent 85a32d18d3
commit 5255a89126
No known key found for this signature in database
3 changed files with 110 additions and 22 deletions

View file

@ -138,7 +138,8 @@ class PlaybackViewModel
// .setEnableDecoderFallback(true) // .setEnableDecoderFallback(true)
// .setExtensionRendererMode(rendererMode), // .setExtensionRendererMode(rendererMode),
// ).build() // ).build()
MpvPlayer(context) // TODO add settings for enabling HW decoding
MpvPlayer(context, false)
.apply { .apply {
playWhenReady = true playWhenReady = true
} }

View file

@ -8,6 +8,7 @@ import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvFormat.MPV_FORMAT_STRI
object MPVProperty { object MPVProperty {
const val POSITION = "time-pos" const val POSITION = "time-pos"
const val POSITION_FULL = "time-pos/full"
const val DURATION = "duration/full" const val DURATION = "duration/full"
const val PAUSED = "pause" const val PAUSED = "pause"
const val PAUSED_FOR_CACHE = "paused-for-cache" const val PAUSED_FOR_CACHE = "paused-for-cache"
@ -24,6 +25,7 @@ object MPVProperty {
val observedProperties = val observedProperties =
mapOf( mapOf(
POSITION to MPV_FORMAT_INT64, POSITION to MPV_FORMAT_INT64,
// POSITION_FULL to MPV_FORMAT_DOUBLE,
DURATION to MPV_FORMAT_DOUBLE, DURATION to MPV_FORMAT_DOUBLE,
PAUSED to MPV_FORMAT_FLAG, PAUSED to MPV_FORMAT_FLAG,
PAUSED_FOR_CACHE to MPV_FORMAT_FLAG, PAUSED_FOR_CACHE to MPV_FORMAT_FLAG,

View file

@ -27,14 +27,19 @@ import androidx.media3.common.util.UnstableApi
import androidx.media3.common.util.Util import androidx.media3.common.util.Util
import timber.log.Timber import timber.log.Timber
import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds
@kotlin.OptIn(ExperimentalAtomicApi::class) @kotlin.OptIn(ExperimentalAtomicApi::class)
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
class MpvPlayer( class MpvPlayer(
private val context: Context, private val context: Context,
enableHardwareDecoding: Boolean,
) : BasePlayer(), ) : BasePlayer(),
MPVLib.EventObserver { MPVLib.EventObserver {
companion object {
private const val DEBUG = false
}
private var surface: Surface? = null private var surface: Surface? = null
private val listeners = mutableListOf<Player.Listener>() private val listeners = mutableListOf<Player.Listener>()
private val looper = Util.getCurrentOrMainLooper() private val looper = Util.getCurrentOrMainLooper()
@ -43,15 +48,22 @@ class MpvPlayer(
private var mediaItem: MediaItem? = null private var mediaItem: MediaItem? = null
private var durationMs: Long = 0L
private var positionMs: Long = 0L
var isReleased = false
private set
init { init {
MPVLib.create(context) MPVLib.create(context)
MPVLib.init() MPVLib.init()
// TODO add option for enabling HW decoding if (enableHardwareDecoding) {
// MPVLib.setOptionString("hwdec", "mediacodec,mediacodec-copy") MPVLib.setOptionString("hwdec", "mediacodec,mediacodec-copy")
// MPVLib.setOptionString("vo", "gpu") MPVLib.setOptionString("vo", "gpu")
} else {
MPVLib.setOptionString("hwdec", "no")
}
MPVLib.setOptionString("gpu-context", "android") MPVLib.setOptionString("gpu-context", "android")
// TODO disable HW decoding for now
MPVLib.setOptionString("hwdec", "no")
MPVLib.setOptionString("opengl-es", "yes") MPVLib.setOptionString("opengl-es", "yes")
MPVLib.setOptionString("hwdec-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1") MPVLib.setOptionString("hwdec-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1")
@ -95,10 +107,12 @@ class MpvPlayer(
override fun getApplicationLooper(): Looper = looper override fun getApplicationLooper(): Looper = looper
override fun addListener(listener: Player.Listener) { override fun addListener(listener: Player.Listener) {
if (DEBUG) Timber.v("addListener")
listeners.add(listener) listeners.add(listener)
} }
override fun removeListener(listener: Player.Listener) { override fun removeListener(listener: Player.Listener) {
if (DEBUG) Timber.v("removeListener")
listeners.remove(listener) listeners.remove(listener)
} }
@ -106,6 +120,9 @@ class MpvPlayer(
mediaItems: List<MediaItem>, mediaItems: List<MediaItem>,
resetPosition: Boolean, resetPosition: Boolean,
) { ) {
throwIfReleased()
if (DEBUG) Timber.v("setMediaItems")
mediaItems.firstOrNull()?.let { mediaItems.firstOrNull()?.let {
mediaItem = it mediaItem = it
} }
@ -116,6 +133,7 @@ class MpvPlayer(
startIndex: Int, startIndex: Int,
startPositionMs: Long, startPositionMs: Long,
) { ) {
if (DEBUG) Timber.v("setMediaItems")
setMediaItems(mediaItems.subList(startIndex, mediaItems.size), false) setMediaItems(mediaItems.subList(startIndex, mediaItems.size), false)
seekTo(startPositionMs) seekTo(startPositionMs)
} }
@ -145,9 +163,11 @@ class MpvPlayer(
override fun getAvailableCommands(): Player.Commands = availableCommands override fun getAvailableCommands(): Player.Commands = availableCommands
override fun prepare() { override fun prepare() {
if (DEBUG) Timber.v("prepare")
} }
override fun getPlaybackState(): Int { override fun getPlaybackState(): Int {
if (DEBUG) Timber.v("getPlaybackState")
val state = STATE_READY val state = STATE_READY
return state return state
} }
@ -159,6 +179,8 @@ class MpvPlayer(
} }
override fun setPlayWhenReady(playWhenReady: Boolean) { override fun setPlayWhenReady(playWhenReady: Boolean) {
throwIfReleased()
if (DEBUG) Timber.v("setPlayWhenReady")
if (playWhenReady) { if (playWhenReady) {
} else { } else {
MPVLib.setPropertyBoolean("pause", true) MPVLib.setPropertyBoolean("pause", true)
@ -166,16 +188,20 @@ class MpvPlayer(
} }
override fun getPlayWhenReady(): Boolean { override fun getPlayWhenReady(): Boolean {
throwIfReleased()
if (DEBUG) Timber.v("getPlayWhenReady")
val isPaused = MPVLib.getPropertyBoolean("pause")!! val isPaused = MPVLib.getPropertyBoolean("pause")!!
return !isPaused return !isPaused
} }
override fun setRepeatMode(repeatMode: Int) { override fun setRepeatMode(repeatMode: Int) {
if (DEBUG) Timber.v("setRepeatMode")
} }
override fun getRepeatMode(): Int = Player.REPEAT_MODE_OFF override fun getRepeatMode(): Int = Player.REPEAT_MODE_OFF
override fun setShuffleModeEnabled(shuffleModeEnabled: Boolean) { override fun setShuffleModeEnabled(shuffleModeEnabled: Boolean) {
if (DEBUG) Timber.v("setShuffleModeEnabled")
} }
override fun getShuffleModeEnabled(): Boolean = false override fun getShuffleModeEnabled(): Boolean = false
@ -189,73 +215,113 @@ class MpvPlayer(
override fun getMaxSeekToPreviousPosition(): Long = 10_000 override fun getMaxSeekToPreviousPosition(): Long = 10_000
override fun setPlaybackParameters(playbackParameters: PlaybackParameters) { override fun setPlaybackParameters(playbackParameters: PlaybackParameters) {
if (DEBUG) Timber.v("setPlaybackParameters")
// TODO // TODO
} }
override fun getPlaybackParameters(): PlaybackParameters { override fun getPlaybackParameters(): PlaybackParameters {
if (DEBUG) Timber.v("getPlaybackParameters")
// TODO // TODO
return PlaybackParameters.DEFAULT return PlaybackParameters.DEFAULT
} }
override fun stop() { override fun stop() {
// TODO
throwIfReleased()
if (DEBUG) Timber.v("stop")
pause() pause()
// MPVLib.setPropertyString("vo", "gpu")
} }
override fun release() { override fun release() {
MPVLib.destroy() Timber.i("release")
if (!isReleased) {
clearVideoSurfaceView(null)
MPVLib.destroy()
}
isReleased = true
} }
override fun getCurrentTracks(): Tracks { override fun getCurrentTracks(): Tracks {
if (DEBUG) Timber.v("getCurrentTracks")
// TODO // TODO
return Tracks.EMPTY return Tracks.EMPTY
} }
override fun getTrackSelectionParameters(): TrackSelectionParameters { override fun getTrackSelectionParameters(): TrackSelectionParameters {
if (DEBUG) Timber.v("getTrackSelectionParameters")
// TODO // TODO
return TrackSelectionParameters.Builder().build() return TrackSelectionParameters.Builder().build()
} }
override fun setTrackSelectionParameters(parameters: TrackSelectionParameters) { override fun setTrackSelectionParameters(parameters: TrackSelectionParameters) {
if (DEBUG) Timber.v("setTrackSelectionParameters")
// TODO // TODO
} }
override fun getMediaMetadata(): MediaMetadata = mediaItem!!.mediaMetadata override fun getMediaMetadata(): MediaMetadata {
if (DEBUG) Timber.v("getMediaMetadata")
return mediaItem!!.mediaMetadata
}
override fun getPlaylistMetadata(): MediaMetadata = MediaMetadata.EMPTY override fun getPlaylistMetadata(): MediaMetadata {
if (DEBUG) Timber.v("getPlaylistMetadata")
return MediaMetadata.EMPTY
}
override fun setPlaylistMetadata(mediaMetadata: MediaMetadata): Unit = throw UnsupportedOperationException() override fun setPlaylistMetadata(mediaMetadata: MediaMetadata): Unit = throw UnsupportedOperationException()
override fun getCurrentTimeline(): Timeline { override fun getCurrentTimeline(): Timeline {
if (DEBUG) Timber.v("getCurrentTimeline")
// TODO // TODO
return Timeline.EMPTY return Timeline.EMPTY
} }
override fun getCurrentPeriodIndex(): Int { override fun getCurrentPeriodIndex(): Int {
if (DEBUG) Timber.v("getCurrentPeriodIndex")
// TODO // TODO
return 0 return 0
} }
override fun getCurrentMediaItemIndex(): Int = 0 override fun getCurrentMediaItemIndex(): Int {
if (DEBUG) Timber.v("getCurrentMediaItemIndex")
return 0
}
override fun getDuration(): Long { override fun getDuration(): Long {
val duration = MPVLib.getPropertyDouble("duration/full")!!.milliseconds.inWholeMilliseconds throwIfReleased()
if (DEBUG) Timber.v("getDuration")
val duration =
MPVLib.getPropertyDouble("duration/full")?.seconds?.inWholeMilliseconds
?: durationMs
return duration return duration
} }
override fun getCurrentPosition(): Long { override fun getCurrentPosition(): Long {
val position = MPVLib.getPropertyDouble("time-pos/full")?.milliseconds?.inWholeMilliseconds ?: 0 if (DEBUG) Timber.v("getCurrentPosition")
if (isReleased) {
return positionMs
}
val position =
MPVLib.getPropertyDouble("time-pos/full")?.seconds?.inWholeMilliseconds
?: positionMs
return position return position
} }
override fun getBufferedPosition(): Long { override fun getBufferedPosition(): Long {
if (DEBUG) Timber.v("getBufferedPosition")
// TODO // TODO
return currentPosition return currentPosition
} }
override fun getTotalBufferedDuration(): Long = bufferedPosition override fun getTotalBufferedDuration(): Long {
if (DEBUG) Timber.v("getTotalBufferedDuration")
return bufferedPosition
}
override fun isPlayingAd(): Boolean = false override fun isPlayingAd(): Boolean {
if (DEBUG) Timber.v("isPlayingAd")
return false
}
override fun getCurrentAdGroupIndex(): Int = C.INDEX_UNSET override fun getCurrentAdGroupIndex(): Int = C.INDEX_UNSET
@ -285,6 +351,8 @@ class MpvPlayer(
override fun clearVideoSurfaceHolder(surfaceHolder: SurfaceHolder?): Unit = throw UnsupportedOperationException() override fun clearVideoSurfaceHolder(surfaceHolder: SurfaceHolder?): Unit = throw UnsupportedOperationException()
override fun setVideoSurfaceView(surfaceView: SurfaceView?) { override fun setVideoSurfaceView(surfaceView: SurfaceView?) {
throwIfReleased()
if (DEBUG) Timber.v("setVideoSurfaceView")
val surface = surfaceView?.holder?.surface val surface = surfaceView?.holder?.surface
val newSurfaceSize = if (surface == null) 0 else C.LENGTH_UNSET val newSurfaceSize = if (surface == null) 0 else C.LENGTH_UNSET
this.surfaceSize = if (surface == null) Size(0, 0) else Size.UNKNOWN this.surfaceSize = if (surface == null) Size(0, 0) else Size.UNKNOWN
@ -305,7 +373,7 @@ class MpvPlayer(
} }
override fun clearVideoSurfaceView(surfaceView: SurfaceView?) { override fun clearVideoSurfaceView(surfaceView: SurfaceView?) {
Timber.d("Detaching surface") Timber.d("clearVideoSurfaceView")
MPVLib.detachSurface() MPVLib.detachSurface()
MPVLib.setPropertyString("vo", "null") MPVLib.setPropertyString("vo", "null")
MPVLib.setPropertyString("force-window", "no") MPVLib.setPropertyString("force-window", "no")
@ -317,6 +385,8 @@ class MpvPlayer(
override fun clearVideoTextureView(textureView: TextureView?): Unit = throw UnsupportedOperationException() override fun clearVideoTextureView(textureView: TextureView?): Unit = throw UnsupportedOperationException()
override fun getVideoSize(): VideoSize { override fun getVideoSize(): VideoSize {
throwIfReleased()
if (DEBUG) Timber.v("getVideoSize")
val width = MPVLib.getPropertyInt("width") val width = MPVLib.getPropertyInt("width")
val height = MPVLib.getPropertyInt("height") val height = MPVLib.getPropertyInt("height")
return if (width != null && height != null) { return if (width != null && height != null) {
@ -333,6 +403,7 @@ class MpvPlayer(
override fun getCurrentCues(): CueGroup = CueGroup.EMPTY_TIME_ZERO override fun getCurrentCues(): CueGroup = CueGroup.EMPTY_TIME_ZERO
override fun getDeviceInfo(): DeviceInfo { override fun getDeviceInfo(): DeviceInfo {
if (DEBUG) Timber.v("getDeviceInfo")
// TODO // TODO
return DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE).build() return DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE).build()
} }
@ -400,10 +471,12 @@ class MpvPlayer(
seekCommand: Int, seekCommand: Int,
isRepeatingCurrentItem: Boolean, isRepeatingCurrentItem: Boolean,
) { ) {
throwIfReleased()
if (DEBUG) Timber.v("seekTo")
if (mediaItemIndex == C.INDEX_UNSET) { if (mediaItemIndex == C.INDEX_UNSET) {
return return
} }
MPVLib.setPropertyDouble("time-pos", positionMs.toDouble()) MPVLib.setPropertyDouble("time-pos", positionMs / 1000.0)
} }
override fun eventProperty(property: String) { override fun eventProperty(property: String) {
@ -414,31 +487,43 @@ class MpvPlayer(
property: String, property: String,
value: Long, value: Long,
) { ) {
Timber.v("eventProperty: $property=$value") Timber.v("eventPropertyLong: $property=$value")
when (property) {
MPVProperty.POSITION -> positionMs = value.seconds.inWholeMilliseconds
}
} }
override fun eventProperty( override fun eventProperty(
property: String, property: String,
value: Boolean, value: Boolean,
) { ) {
Timber.v("eventProperty: $property=$value") Timber.v("eventPropertyBoolean: $property=$value")
} }
override fun eventProperty( override fun eventProperty(
property: String, property: String,
value: String, value: String,
) { ) {
Timber.v("eventProperty: $property=$value") Timber.v("eventPropertyString: $property=$value")
} }
override fun eventProperty( override fun eventProperty(
property: String, property: String,
value: Double, value: Double,
) { ) {
Timber.v("eventProperty: $property=$value") Timber.v("eventPropertyDouble: $property=$value")
when (property) {
MPVProperty.DURATION -> durationMs = value.seconds.inWholeMilliseconds
}
} }
override fun event(eventId: Int) { override fun event(eventId: Int) {
Timber.v("event: $eventId") Timber.v("event: $eventId")
} }
private fun throwIfReleased() {
if (isReleased) {
throw IllegalStateException("Cannot access MpvPlayer after it is released")
}
}
} }