Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ interface VideoPlayerState {
var isFullscreen: Boolean
val aspectRatio: Float

/**
* Whether a frame of the current media has been handed to the surface. False until the first frame after
* [openUri] / [openFile], so a caller can keep a placeholder or loader up until there is a picture to show.
*
* Reported by the desktop (JVM) players; false on the other platforms for now.
*/
val hasRenderedFirstFrame: Boolean get() = false

val isPipSupported: Boolean get() = false
var isPipActive: Boolean get() = false
set(value) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ open class DefaultVideoPlayerState : VideoPlayerState {

override val metadata: VideoMetadata get() = delegate.metadata
override val aspectRatio: Float get() = delegate.aspectRatio
override val hasRenderedFirstFrame: Boolean get() = delegate.hasRenderedFirstFrame

override var subtitlesEnabled = delegate.subtitlesEnabled
override var currentSubtitleTrack: SubtitleTrack? = delegate.currentSubtitleTrack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class LinuxVideoPlayerState : VideoPlayerState {
private val _currentFrameState = MutableStateFlow<ImageBitmap?>(null)
internal val currentFrameState: State<ImageBitmap?> = mutableStateOf(null)

// The last frame stays on the surface across an open, so the first frame of a media is tracked on its own
private var _hasRenderedFirstFrame by mutableStateOf(false)
override val hasRenderedFirstFrame: Boolean get() = _hasRenderedFirstFrame

// Double-buffered Skia bitmaps
private var skiaBitmapWidth: Int = 0
private var skiaBitmapHeight: Int = 0
Expand Down Expand Up @@ -174,6 +178,7 @@ class LinuxVideoPlayerState : VideoPlayerState {
ensureActive()
withContext(Dispatchers.Main) {
(currentFrameState as MutableState).value = newFrame
if (newFrame != null) _hasRenderedFirstFrame = true
}
}
}
Expand Down Expand Up @@ -235,6 +240,7 @@ class LinuxVideoPlayerState : VideoPlayerState {
isLoading = true
error = null
playbackSpeed = 1.0f
_hasRenderedFirstFrame = false
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ class MacVideoPlayerState : VideoPlayerState {
private val bitmapLock = ReentrantReadWriteLock()
internal val currentFrameState = mutableStateOf<ImageBitmap?>(null)

// currentFrameState is cleared on every open (releaseAllResources) and set by the first consumed frame
override val hasRenderedFirstFrame: Boolean get() = currentFrameState.value != null

// Aspect ratio — driven by the live frame (see syncAspectRatioToFrame).
private var _aspectRatio by mutableStateOf(16f / 9f)
override val aspectRatio: Float get() = _aspectRatio
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ class WindowsVideoPlayerState : VideoPlayerState {
private val bitmapLock = ReentrantReadWriteLock()
internal val currentFrameState = mutableStateOf<ImageBitmap?>(null)

// currentFrameState is cleared on every open (releaseAllResources) and set by the first consumed frame
override val hasRenderedFirstFrame: Boolean get() = currentFrameState.value != null

// Aspect ratio property
override val aspectRatio: Float
get() =
Expand Down