Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ kotlin.daemon.jvmargs=-Xmx4G
kotlin.mpp.enableCInteropCommonization=true
kotlin.native.ignoreDisabledTargets=true
kotlin.native.enableKlibsCrossCompilation=false
## Incremental compilation breaks Wasm tests, see https://youtrack.jetbrains.com/projects/KT/issues/KT-84610/Wasm-Failed-to-compile-klibs-in-IC-mode
kotlin.incremental.js=false
kotlin.incremental.js.klib=false

#Android
android.useAndroidX=true
android.nonTransitiveRClass=true

org.jetbrains.compose.experimental.macos.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ actual fun VideoPlayerSurface(
// State for CORS mode changes
var lastPosition by remember { mutableStateOf(0.0) }
var wasPlaying by remember { mutableStateOf(false) }
var lastPlaybackSpeed by remember { mutableStateOf(1.0f) }

// Shared effects
VideoPlayerEffects(
Expand All @@ -36,10 +35,8 @@ actual fun VideoPlayerSurface(
useCors = useCors,
onLastPositionChange = { lastPosition = it },
onWasPlayingChange = { wasPlaying = it },
onLastPlaybackSpeedChange = { lastPlaybackSpeed = it },
lastPosition = lastPosition,
wasPlaying = wasPlaying,
lastPlaybackSpeed = lastPlaybackSpeed,
)

VideoVolumeAndSpeedEffects(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ actual fun VideoPlayerSurface(
// State for CORS mode changes
var lastPosition by remember { mutableStateOf(0.0) }
var wasPlaying by remember { mutableStateOf(false) }
var lastPlaybackSpeed by remember { mutableStateOf(1.0f) }

// Shared effects
VideoPlayerEffects(
Expand All @@ -36,10 +35,8 @@ actual fun VideoPlayerSurface(
useCors = useCors,
onLastPositionChange = { lastPosition = it },
onWasPlayingChange = { wasPlaying = it },
onLastPlaybackSpeedChange = { lastPlaybackSpeed = it },
lastPosition = lastPosition,
wasPlaying = wasPlaying,
lastPlaybackSpeed = lastPlaybackSpeed,
)

VideoVolumeAndSpeedEffects(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class VideoPlayerStateTest {
assertFalse(playerState.isPlaying)
assertEquals(0f, playerState.sliderPos)
assertEquals(1f, playerState.volume)
assertEquals(1f, playerState.playbackSpeed)
assertFalse(playerState.loop)
assertEquals("00:00", playerState.positionText)
assertEquals("00:00", playerState.durationText)
Expand Down Expand Up @@ -57,6 +58,28 @@ class VideoPlayerStateTest {
playerState.dispose()
}

@Test
fun `testPlaybackSpeedControl`() {
val playerState = createVideoPlayerState()

// Test initial speed
assertEquals(1f, playerState.playbackSpeed)

// Test setting speed
playerState.playbackSpeed = 0.5f
assertEquals(0.5f, playerState.playbackSpeed)

// Test speed bounds
playerState.playbackSpeed = -1f
assertEquals(0.25f, playerState.playbackSpeed, "Playback speed should be clamped to 0.25")

playerState.playbackSpeed = 10f
assertEquals(2f, playerState.playbackSpeed, "Playback speed should be clamped to 2")

// Clean up
playerState.dispose()
}

/**
* Test loop setting
*/
Expand Down Expand Up @@ -185,4 +208,14 @@ class VideoPlayerStateTest {
// Clean up
playerState.dispose()
}

@Test
fun testLoadingVideoDoNotResetPlaybackSpeed() {
val playerState = createVideoPlayerState()
playerState.playbackSpeed = 2f

playerState.openUri("file:///path/to/file")

assertEquals(2f, playerState.playbackSpeed)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ open class DefaultVideoPlayerState : VideoPlayerState {
_isLoading = true // Set initial loading state
_error = null
_isPlaying = false
_playbackSpeed = 1.0f

// Don't set isLoading to false here - let the video events handle it
playerScope.launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,10 +518,8 @@ internal fun VideoPlayerEffects(
useCors: Boolean,
onLastPositionChange: (Double) -> Unit,
onWasPlayingChange: (Boolean) -> Unit,
onLastPlaybackSpeedChange: (Float) -> Unit,
lastPosition: Double,
wasPlaying: Boolean,
lastPlaybackSpeed: Float,
) {
// Handle fullscreen
LaunchedEffect(playerState.isFullscreen) {
Expand Down Expand Up @@ -593,7 +591,6 @@ internal fun VideoPlayerEffects(
videoElement?.let {
onLastPositionChange(it.currentTime)
onWasPlayingChange(playerState.isPlaying)
onLastPlaybackSpeedChange(playerState.playbackSpeed)
}
}

Expand All @@ -605,11 +602,6 @@ internal fun VideoPlayerEffects(
onLastPositionChange(0.0)
}

if (lastPlaybackSpeed != 1.0f) {
video.safeSetPlaybackRate(lastPlaybackSpeed)
onLastPlaybackSpeedChange(1.0f)
}

if (wasPlaying) {
video.safePlay()
onWasPlayingChange(false)
Expand Down Expand Up @@ -666,61 +658,46 @@ internal fun VideoVolumeAndSpeedEffects(
playerState: VideoPlayerState,
videoElement: HTMLVideoElement?,
) {
var pendingVolumeChange by remember { mutableStateOf<Double?>(null) }
var pendingPlaybackSpeedChange by remember { mutableStateOf<Float?>(null) }

if (playerState is DefaultVideoPlayerState) {
DisposableEffect(videoElement) {
val video = videoElement ?: return@DisposableEffect onDispose {}
if (playerState !is DefaultVideoPlayerState) {
webVideoLogger.e {
"""
Expected ${DefaultVideoPlayerState::class} but found ${playerState::class}.
Volume and speed settings won't work.
""".trimIndent()
}
return
}

playerState.applyVolumeCallback = { value ->
if (playerState._isLoading) {
pendingVolumeChange = value.toDouble()
} else {
video.volume = value.toDouble()
pendingVolumeChange = null
}
}
DisposableEffect(videoElement) {
val video = videoElement ?: return@DisposableEffect onDispose {}

if (!playerState._isLoading) {
video.volume = playerState.volume.toDouble()
} else {
pendingVolumeChange = playerState.volume.toDouble()
}
// Init video element state when video is loaded
// Volume could be changed any time but playbackRate require video to be loaded
val videoLoadedListener: (Event) -> Unit = {
video.volume = playerState.volume.toDouble()
video.safeSetPlaybackRate(playerState.playbackSpeed)
}

playerState.applyPlaybackSpeedCallback = { value ->
if (playerState._isLoading) {
pendingPlaybackSpeedChange = value
} else {
video.safeSetPlaybackRate(value)
pendingPlaybackSpeedChange = null
}
}
video.addEventListener("canplay", videoLoadedListener)

if (!playerState._isLoading) {
video.safeSetPlaybackRate(playerState.playbackSpeed)
} else {
pendingPlaybackSpeedChange = playerState.playbackSpeed
// They should change only `playerState` while the video is loading
// The video element will be updated in `videoLoadedListener`
playerState.applyVolumeCallback = { value ->
if (!playerState.isLoading) {
video.volume = value.toDouble()
}
}

val seekedListener: (Event) -> Unit = {
pendingVolumeChange?.let { volume ->
video.volume = volume
pendingVolumeChange = null
}
pendingPlaybackSpeedChange?.let { speed ->
video.safeSetPlaybackRate(speed)
pendingPlaybackSpeedChange = null
}
playerState.applyPlaybackSpeedCallback = { value ->
if (!playerState.isLoading) {
video.safeSetPlaybackRate(value)
}
}

video.addEventListener("seeked", seekedListener)

onDispose {
video.removeEventListener("seeked", seekedListener)
playerState.applyVolumeCallback = null
playerState.applyPlaybackSpeedCallback = null
}
onDispose {
video.removeEventListener("canplay", videoLoadedListener)
playerState.applyVolumeCallback = null
playerState.applyPlaybackSpeedCallback = null
}
}
}