Skip to content

Commit 64c5c8d

Browse files
authored
Merge pull request #222 from vitalir2/master
fix(web): set correctly initial volume & playback speed
2 parents 90b1240 + 61d8dba commit 64c5c8d

6 files changed

Lines changed: 69 additions & 63 deletions

File tree

gradle.properties

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ kotlin.daemon.jvmargs=-Xmx4G
1212
kotlin.mpp.enableCInteropCommonization=true
1313
kotlin.native.ignoreDisabledTargets=true
1414
kotlin.native.enableKlibsCrossCompilation=false
15+
## Incremental compilation breaks Wasm tests, see https://youtrack.jetbrains.com/projects/KT/issues/KT-84610/Wasm-Failed-to-compile-klibs-in-IC-mode
16+
kotlin.incremental.js=false
17+
kotlin.incremental.js.klib=false
1518

1619
#Android
1720
android.useAndroidX=true
1821
android.nonTransitiveRClass=true
1922

20-
org.jetbrains.compose.experimental.macos.enabled=true
23+
org.jetbrains.compose.experimental.macos.enabled=true

mediaplayer/src/jsMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerSurface.js.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ actual fun VideoPlayerSurface(
2626
// State for CORS mode changes
2727
var lastPosition by remember { mutableStateOf(0.0) }
2828
var wasPlaying by remember { mutableStateOf(false) }
29-
var lastPlaybackSpeed by remember { mutableStateOf(1.0f) }
3029

3130
// Shared effects
3231
VideoPlayerEffects(
@@ -36,10 +35,8 @@ actual fun VideoPlayerSurface(
3635
useCors = useCors,
3736
onLastPositionChange = { lastPosition = it },
3837
onWasPlayingChange = { wasPlaying = it },
39-
onLastPlaybackSpeedChange = { lastPlaybackSpeed = it },
4038
lastPosition = lastPosition,
4139
wasPlaying = wasPlaying,
42-
lastPlaybackSpeed = lastPlaybackSpeed,
4340
)
4441

4542
VideoVolumeAndSpeedEffects(

mediaplayer/src/wasmJsMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerSurface.wasm.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ actual fun VideoPlayerSurface(
2626
// State for CORS mode changes
2727
var lastPosition by remember { mutableStateOf(0.0) }
2828
var wasPlaying by remember { mutableStateOf(false) }
29-
var lastPlaybackSpeed by remember { mutableStateOf(1.0f) }
3029

3130
// Shared effects
3231
VideoPlayerEffects(
@@ -36,10 +35,8 @@ actual fun VideoPlayerSurface(
3635
useCors = useCors,
3736
onLastPositionChange = { lastPosition = it },
3837
onWasPlayingChange = { wasPlaying = it },
39-
onLastPlaybackSpeedChange = { lastPlaybackSpeed = it },
4038
lastPosition = lastPosition,
4139
wasPlaying = wasPlaying,
42-
lastPlaybackSpeed = lastPlaybackSpeed,
4340
)
4441

4542
VideoVolumeAndSpeedEffects(

mediaplayer/src/wasmJsTest/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerStateTest.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class VideoPlayerStateTest {
2323
assertFalse(playerState.isPlaying)
2424
assertEquals(0f, playerState.sliderPos)
2525
assertEquals(1f, playerState.volume)
26+
assertEquals(1f, playerState.playbackSpeed)
2627
assertFalse(playerState.loop)
2728
assertEquals("00:00", playerState.positionText)
2829
assertEquals("00:00", playerState.durationText)
@@ -57,6 +58,28 @@ class VideoPlayerStateTest {
5758
playerState.dispose()
5859
}
5960

61+
@Test
62+
fun `testPlaybackSpeedControl`() {
63+
val playerState = createVideoPlayerState()
64+
65+
// Test initial speed
66+
assertEquals(1f, playerState.playbackSpeed)
67+
68+
// Test setting speed
69+
playerState.playbackSpeed = 0.5f
70+
assertEquals(0.5f, playerState.playbackSpeed)
71+
72+
// Test speed bounds
73+
playerState.playbackSpeed = -1f
74+
assertEquals(0.25f, playerState.playbackSpeed, "Playback speed should be clamped to 0.25")
75+
76+
playerState.playbackSpeed = 10f
77+
assertEquals(2f, playerState.playbackSpeed, "Playback speed should be clamped to 2")
78+
79+
// Clean up
80+
playerState.dispose()
81+
}
82+
6083
/**
6184
* Test loop setting
6285
*/
@@ -185,4 +208,14 @@ class VideoPlayerStateTest {
185208
// Clean up
186209
playerState.dispose()
187210
}
211+
212+
@Test
213+
fun testLoadingVideoDoNotResetPlaybackSpeed() {
214+
val playerState = createVideoPlayerState()
215+
playerState.playbackSpeed = 2f
216+
217+
playerState.openUri("file:///path/to/file")
218+
219+
assertEquals(2f, playerState.playbackSpeed)
220+
}
188221
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,6 @@ open class DefaultVideoPlayerState : VideoPlayerState {
246246
_isLoading = true // Set initial loading state
247247
_error = null
248248
_isPlaying = false
249-
_playbackSpeed = 1.0f
250249

251250
// Don't set isLoading to false here - let the video events handle it
252251
playerScope.launch {

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

Lines changed: 32 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -518,10 +518,8 @@ internal fun VideoPlayerEffects(
518518
useCors: Boolean,
519519
onLastPositionChange: (Double) -> Unit,
520520
onWasPlayingChange: (Boolean) -> Unit,
521-
onLastPlaybackSpeedChange: (Float) -> Unit,
522521
lastPosition: Double,
523522
wasPlaying: Boolean,
524-
lastPlaybackSpeed: Float,
525523
) {
526524
// Handle fullscreen
527525
LaunchedEffect(playerState.isFullscreen) {
@@ -593,7 +591,6 @@ internal fun VideoPlayerEffects(
593591
videoElement?.let {
594592
onLastPositionChange(it.currentTime)
595593
onWasPlayingChange(playerState.isPlaying)
596-
onLastPlaybackSpeedChange(playerState.playbackSpeed)
597594
}
598595
}
599596

@@ -605,11 +602,6 @@ internal fun VideoPlayerEffects(
605602
onLastPositionChange(0.0)
606603
}
607604

608-
if (lastPlaybackSpeed != 1.0f) {
609-
video.safeSetPlaybackRate(lastPlaybackSpeed)
610-
onLastPlaybackSpeedChange(1.0f)
611-
}
612-
613605
if (wasPlaying) {
614606
video.safePlay()
615607
onWasPlayingChange(false)
@@ -666,61 +658,46 @@ internal fun VideoVolumeAndSpeedEffects(
666658
playerState: VideoPlayerState,
667659
videoElement: HTMLVideoElement?,
668660
) {
669-
var pendingVolumeChange by remember { mutableStateOf<Double?>(null) }
670-
var pendingPlaybackSpeedChange by remember { mutableStateOf<Float?>(null) }
671-
672-
if (playerState is DefaultVideoPlayerState) {
673-
DisposableEffect(videoElement) {
674-
val video = videoElement ?: return@DisposableEffect onDispose {}
661+
if (playerState !is DefaultVideoPlayerState) {
662+
webVideoLogger.e {
663+
"""
664+
Expected ${DefaultVideoPlayerState::class} but found ${playerState::class}.
665+
Volume and speed settings won't work.
666+
""".trimIndent()
667+
}
668+
return
669+
}
675670

676-
playerState.applyVolumeCallback = { value ->
677-
if (playerState._isLoading) {
678-
pendingVolumeChange = value.toDouble()
679-
} else {
680-
video.volume = value.toDouble()
681-
pendingVolumeChange = null
682-
}
683-
}
671+
DisposableEffect(videoElement) {
672+
val video = videoElement ?: return@DisposableEffect onDispose {}
684673

685-
if (!playerState._isLoading) {
686-
video.volume = playerState.volume.toDouble()
687-
} else {
688-
pendingVolumeChange = playerState.volume.toDouble()
689-
}
674+
// Init video element state when video is loaded
675+
// Volume could be changed any time but playbackRate require video to be loaded
676+
val videoLoadedListener: (Event) -> Unit = {
677+
video.volume = playerState.volume.toDouble()
678+
video.safeSetPlaybackRate(playerState.playbackSpeed)
679+
}
690680

691-
playerState.applyPlaybackSpeedCallback = { value ->
692-
if (playerState._isLoading) {
693-
pendingPlaybackSpeedChange = value
694-
} else {
695-
video.safeSetPlaybackRate(value)
696-
pendingPlaybackSpeedChange = null
697-
}
698-
}
681+
video.addEventListener("canplay", videoLoadedListener)
699682

700-
if (!playerState._isLoading) {
701-
video.safeSetPlaybackRate(playerState.playbackSpeed)
702-
} else {
703-
pendingPlaybackSpeedChange = playerState.playbackSpeed
683+
// They should change only `playerState` while the video is loading
684+
// The video element will be updated in `videoLoadedListener`
685+
playerState.applyVolumeCallback = { value ->
686+
if (!playerState.isLoading) {
687+
video.volume = value.toDouble()
704688
}
689+
}
705690

706-
val seekedListener: (Event) -> Unit = {
707-
pendingVolumeChange?.let { volume ->
708-
video.volume = volume
709-
pendingVolumeChange = null
710-
}
711-
pendingPlaybackSpeedChange?.let { speed ->
712-
video.safeSetPlaybackRate(speed)
713-
pendingPlaybackSpeedChange = null
714-
}
691+
playerState.applyPlaybackSpeedCallback = { value ->
692+
if (!playerState.isLoading) {
693+
video.safeSetPlaybackRate(value)
715694
}
695+
}
716696

717-
video.addEventListener("seeked", seekedListener)
718-
719-
onDispose {
720-
video.removeEventListener("seeked", seekedListener)
721-
playerState.applyVolumeCallback = null
722-
playerState.applyPlaybackSpeedCallback = null
723-
}
697+
onDispose {
698+
video.removeEventListener("canplay", videoLoadedListener)
699+
playerState.applyVolumeCallback = null
700+
playerState.applyPlaybackSpeedCallback = null
724701
}
725702
}
726703
}

0 commit comments

Comments
 (0)