File tree Expand file tree Collapse file tree
androidMain/kotlin/io/github/kdroidfilter/composemediaplayer
commonMain/kotlin/io/github/kdroidfilter/composemediaplayer
iosMain/kotlin/io/github/kdroidfilter/composemediaplayer
jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer
webMain/kotlin/io/github/kdroidfilter/composemediaplayer
sample/composeApp/src/commonMain/kotlin/sample/app/player Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -225,6 +225,8 @@ open class DefaultVideoPlayerState(
225225 // User interaction states
226226 override var userDragging by mutableStateOf(false )
227227
228+ override var onPlaybackEnded: (() -> Unit )? = null
229+
228230 // Loop control
229231 private var _loop by mutableStateOf(false )
230232 override var loop: Boolean
@@ -454,6 +456,7 @@ open class DefaultVideoPlayerState(
454456 _isLoading = false
455457 stopPositionUpdates()
456458 _isPlaying = false
459+ onPlaybackEnded?.invoke()
457460 }
458461
459462 Player .STATE_IDLE -> {
Original file line number Diff line number Diff line change @@ -37,7 +37,7 @@ interface VideoPlayerState {
3737 var volume: Float
3838
3939 /* *
40- * Represents the current playback position as a normalized value between 0.0 and 1 .0.
40+ * Represents the current playback position as a value between 0.0 and 1000 .0.
4141 */
4242 var sliderPos: Float
4343
@@ -52,6 +52,12 @@ interface VideoPlayerState {
5252 var loop: Boolean
5353 var playbackSpeed: Float
5454
55+ /* *
56+ * Callback invoked when playback reaches the end of the media.
57+ * Only called when [loop] is false. May be invoked from a background thread.
58+ */
59+ var onPlaybackEnded: (() -> Unit )?
60+
5561 companion object {
5662 const val MIN_PLAYBACK_SPEED = 0.25f
5763 const val MAX_PLAYBACK_SPEED = 2.0f
@@ -96,7 +102,7 @@ interface VideoPlayerState {
96102 fun stop ()
97103
98104 /* *
99- * Seeks to a specific playback position based on the provided normalized value .
105+ * Seeks to a specific playback position. The [value] should be between 0.0 and 1000.0 .
100106 */
101107 fun seekTo (value : Float )
102108
@@ -196,6 +202,7 @@ data class PreviewableVideoPlayerState(
196202 override val isPipSupported : Boolean = false ,
197203 override var isPipActive : Boolean = false ,
198204 override var isPipEnabled : Boolean = false ,
205+ override var onPlaybackEnded : (() -> Unit )? = null ,
199206) : VideoPlayerState {
200207 override fun play () {}
201208
Original file line number Diff line number Diff line change @@ -72,6 +72,7 @@ open class DefaultVideoPlayerState(
7272 }
7373 }
7474
75+ override var onPlaybackEnded: (() -> Unit )? = null
7576 override var sliderPos: Float by mutableStateOf(0f ) // value between 0 and 1000
7677 override var userDragging: Boolean = false
7778 private var _loop by mutableStateOf(false )
@@ -344,6 +345,7 @@ open class DefaultVideoPlayerState(
344345 } else {
345346 player.pause()
346347 _isPlaying = false
348+ onPlaybackEnded?.invoke()
347349 }
348350 }
349351
Original file line number Diff line number Diff line change @@ -126,5 +126,11 @@ open class DefaultVideoPlayerState : VideoPlayerState {
126126
127127 override fun dispose () = delegate.dispose()
128128
129+ override var onPlaybackEnded: (() -> Unit )?
130+ get() = delegate.onPlaybackEnded
131+ set(value) {
132+ delegate.onPlaybackEnded = value
133+ }
134+
129135 override fun clearError () = delegate.clearError()
130136}
Original file line number Diff line number Diff line change @@ -82,6 +82,7 @@ class LinuxVideoPlayerState : VideoPlayerState {
8282 override var userDragging: Boolean by mutableStateOf(false )
8383 override var loop: Boolean by mutableStateOf(false )
8484 override var isLoading: Boolean by mutableStateOf(false )
85+ override var onPlaybackEnded: (() -> Unit )? = null
8586 override var error: VideoPlayerError ? by mutableStateOf(null )
8687 override var subtitlesEnabled: Boolean by mutableStateOf(false )
8788 override var currentSubtitleTrack: SubtitleTrack ? by mutableStateOf(null )
@@ -504,7 +505,7 @@ class LinuxVideoPlayerState : VideoPlayerState {
504505
505506 // Native-to-native copy: frame buffer -> Skia bitmap pixels
506507 srcBuf.rewind()
507- val destRowBytes = pixmap.rowBytes.toInt()
508+ val destRowBytes = pixmap.rowBytes
508509 val destSizeBytes = destRowBytes.toLong() * height.toLong()
509510 val destBuf =
510511 LinuxNativeBridge .nWrapPointer(pixelsAddr, destSizeBytes)
@@ -572,6 +573,7 @@ class LinuxVideoPlayerState : VideoPlayerState {
572573 } else {
573574 withContext(Dispatchers .Main ) { isPlaying = false }
574575 pauseInBackground()
576+ onPlaybackEnded?.invoke()
575577 }
576578 }
577579
Original file line number Diff line number Diff line change @@ -79,6 +79,7 @@ class MacVideoPlayerState : VideoPlayerState {
7979 override var userDragging: Boolean by mutableStateOf(false )
8080 override var loop: Boolean by mutableStateOf(false )
8181 override var isLoading: Boolean by mutableStateOf(false )
82+ override var onPlaybackEnded: (() -> Unit )? = null
8283 override var error: VideoPlayerError ? by mutableStateOf(null )
8384 override var subtitlesEnabled: Boolean by mutableStateOf(false )
8485 override var currentSubtitleTrack: SubtitleTrack ? by mutableStateOf(null )
@@ -604,7 +605,7 @@ class MacVideoPlayerState : VideoPlayerState {
604605
605606 // Single copy: CVPixelBuffer → Skia bitmap pixels (no intermediate buffer)
606607 srcBuf.rewind()
607- val dstRowBytes = pixmap.rowBytes.toInt()
608+ val dstRowBytes = pixmap.rowBytes
608609 val dstSizeBytes = dstRowBytes.toLong() * height.toLong()
609610 val destBuf =
610611 MacNativeBridge .nWrapPointer(pixelsAddr, dstSizeBytes)
@@ -699,6 +700,7 @@ class MacVideoPlayerState : VideoPlayerState {
699700 isPlaying = false
700701 }
701702 pauseInBackground()
703+ onPlaybackEnded?.invoke()
702704 }
703705 }
704706
Original file line number Diff line number Diff line change @@ -3,4 +3,4 @@ package io.github.kdroidfilter.composemediaplayer.util
33import io.github.vinceglb.filekit.PlatformFile
44import io.github.vinceglb.filekit.path
55
6- actual fun PlatformFile.getUri (): String = this .path.toString()
6+ actual fun PlatformFile.getUri (): String = this .path
Original file line number Diff line number Diff line change @@ -35,7 +35,6 @@ import kotlinx.coroutines.flow.filter
3535import kotlinx.coroutines.flow.first
3636import kotlinx.coroutines.isActive
3737import kotlinx.coroutines.launch
38- import kotlinx.coroutines.runBlocking
3938import kotlinx.coroutines.sync.Mutex
4039import kotlinx.coroutines.sync.withLock
4140import kotlinx.coroutines.withContext
@@ -161,6 +160,8 @@ class WindowsVideoPlayerState : VideoPlayerState {
161160 _loop = value
162161 }
163162
163+ override var onPlaybackEnded: (() -> Unit )? = null
164+
164165 private var _playbackSpeed by mutableStateOf(1.0f )
165166 override var playbackSpeed: Float
166167 get() = _playbackSpeed
@@ -675,6 +676,7 @@ class WindowsVideoPlayerState : VideoPlayerState {
675676 _progress = 1f
676677 }
677678 pause()
679+ onPlaybackEnded?.invoke()
678680 break
679681 }
680682 }
Original file line number Diff line number Diff line change @@ -62,6 +62,8 @@ open class DefaultVideoPlayerState : VideoPlayerState {
6262 override val isLoading: Boolean get() = _isLoading
6363
6464 // Error handling
65+ override var onPlaybackEnded: (() -> Unit )? = null
66+
6567 private var _error by mutableStateOf<VideoPlayerError ?>(null )
6668 override val error: VideoPlayerError ? get() = _error
6769
Original file line number Diff line number Diff line change @@ -378,7 +378,12 @@ internal fun setupVideoElement(
378378 events =
379379 mapOf (
380380 " timeupdate" to { event -> playerState.onTimeUpdateEvent(event) },
381- " ended" to { scope.launch { playerState.pause() } },
381+ " ended" to {
382+ scope.launch {
383+ playerState.pause()
384+ playerState.onPlaybackEnded?.invoke()
385+ }
386+ },
382387 ),
383388 loadingEvents =
384389 mapOf (
You can’t perform that action at this time.
0 commit comments