Skip to content

Commit 47044e6

Browse files
authored
Merge pull request #191 from kdroidFilter/feat/on-playback-ended-callback
feat: add onPlaybackEnded callback to VideoPlayerState
2 parents a1ae997 + 58d0608 commit 47044e6

11 files changed

Lines changed: 43 additions & 7 deletions

File tree

mediaplayer/src/androidMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerState.android.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff 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 -> {

mediaplayer/src/commonMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerState.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff 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

mediaplayer/src/iosMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerState.ios.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff 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

mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerState.jvm.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff 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
}

mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/linux/LinuxVideoPlayerState.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff 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

mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/mac/MacVideoPlayerState.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff 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

mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/util/Uri.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ package io.github.kdroidfilter.composemediaplayer.util
33
import io.github.vinceglb.filekit.PlatformFile
44
import io.github.vinceglb.filekit.path
55

6-
actual fun PlatformFile.getUri(): String = this.path.toString()
6+
actual fun PlatformFile.getUri(): String = this.path

mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import kotlinx.coroutines.flow.filter
3535
import kotlinx.coroutines.flow.first
3636
import kotlinx.coroutines.isActive
3737
import kotlinx.coroutines.launch
38-
import kotlinx.coroutines.runBlocking
3938
import kotlinx.coroutines.sync.Mutex
4039
import kotlinx.coroutines.sync.withLock
4140
import 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
}

mediaplayer/src/webMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerState.web.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff 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

mediaplayer/src/webMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerSurfaceImpl.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff 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(

0 commit comments

Comments
 (0)