diff --git a/mediaplayer/src/androidMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerSurface.android.kt b/mediaplayer/src/androidMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerSurface.android.kt index c14ab80b..00171348 100644 --- a/mediaplayer/src/androidMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerSurface.android.kt +++ b/mediaplayer/src/androidMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoPlayerSurface.android.kt @@ -207,12 +207,15 @@ private fun VideoPlayerContent( }, update = { playerView -> try { - // Verify that the player is still valid before updating - if (playerState is DefaultVideoPlayerState && - playerState.exoPlayer != null && - playerView.player != null - ) { - // Update the resize mode when contentScale changes + val state = playerState as? DefaultVideoPlayerState + if (state?.exoPlayer != null) { + // Re-attach after LazyList recycle: onReset nulls playerView.player + // and calls onPause(). Without this, the surface stays blank until + // the user navigates away and back. + if (playerView.player == null) { + state.attachPlayerView(playerView) + playerView.onResume() + } playerView.resizeMode = mapContentScaleToResizeMode(contentScale) } } catch (e: Exception) { diff --git a/mediaplayer/src/commonMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoMetadata.kt b/mediaplayer/src/commonMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoMetadata.kt index eb5764f9..bd33715b 100644 --- a/mediaplayer/src/commonMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoMetadata.kt +++ b/mediaplayer/src/commonMain/kotlin/io/github/kdroidfilter/composemediaplayer/VideoMetadata.kt @@ -1,14 +1,16 @@ package io.github.kdroidfilter.composemediaplayer import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue /** * Represents metadata information of a video file. * - * This data class holds various attributes related to the video content, - * including its title, artist, duration, dimensions, codec details, and audio properties. - * This metadata is typically used to provide detailed information about a video - * during playback or for insights in media management systems. + * Properties are backed by [mutableStateOf] so mutations trigger Compose recomposition. + * This matters when callers update fields in place (e.g. on `onVideoSizeChanged` or HLS + * resolution changes) while the metadata instance is read from a composable. * * @property title The title of the video, if available. * @property duration The length of the video in milliseconds, if known. @@ -21,21 +23,29 @@ import androidx.compose.runtime.Stable * @property audioSampleRate The sample rate of the audio track in the video, measured in Hz. */ @Stable -data class VideoMetadata( - var title: String? = null, - var duration: Long? = null, // Duration in milliseconds - var width: Int? = null, - var height: Int? = null, - var bitrate: Long? = null, // Bitrate in bits per second - var frameRate: Float? = null, - var mimeType: String? = null, - var audioChannels: Int? = null, - var audioSampleRate: Int? = null, +class VideoMetadata( + title: String? = null, + duration: Long? = null, + width: Int? = null, + height: Int? = null, + bitrate: Long? = null, + frameRate: Float? = null, + mimeType: String? = null, + audioChannels: Int? = null, + audioSampleRate: Int? = null, ) { + var title: String? by mutableStateOf(title) + var duration: Long? by mutableStateOf(duration) + var width: Int? by mutableStateOf(width) + var height: Int? by mutableStateOf(height) + var bitrate: Long? by mutableStateOf(bitrate) + var frameRate: Float? by mutableStateOf(frameRate) + var mimeType: String? by mutableStateOf(mimeType) + var audioChannels: Int? by mutableStateOf(audioChannels) + var audioSampleRate: Int? by mutableStateOf(audioSampleRate) + /** * Checks if all properties of this metadata object are null. - * - * @return true if all properties are null, false otherwise. */ fun isAllNull(): Boolean = title == null && @@ -47,4 +57,59 @@ data class VideoMetadata( mimeType == null && audioChannels == null && audioSampleRate == null + + fun copy( + title: String? = this.title, + duration: Long? = this.duration, + width: Int? = this.width, + height: Int? = this.height, + bitrate: Long? = this.bitrate, + frameRate: Float? = this.frameRate, + mimeType: String? = this.mimeType, + audioChannels: Int? = this.audioChannels, + audioSampleRate: Int? = this.audioSampleRate, + ): VideoMetadata = + VideoMetadata( + title = title, + duration = duration, + width = width, + height = height, + bitrate = bitrate, + frameRate = frameRate, + mimeType = mimeType, + audioChannels = audioChannels, + audioSampleRate = audioSampleRate, + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is VideoMetadata) return false + return title == other.title && + duration == other.duration && + width == other.width && + height == other.height && + bitrate == other.bitrate && + frameRate == other.frameRate && + mimeType == other.mimeType && + audioChannels == other.audioChannels && + audioSampleRate == other.audioSampleRate + } + + override fun hashCode(): Int { + var result = title?.hashCode() ?: 0 + result = 31 * result + (duration?.hashCode() ?: 0) + result = 31 * result + (width ?: 0) + result = 31 * result + (height ?: 0) + result = 31 * result + (bitrate?.hashCode() ?: 0) + result = 31 * result + (frameRate?.hashCode() ?: 0) + result = 31 * result + (mimeType?.hashCode() ?: 0) + result = 31 * result + (audioChannels ?: 0) + result = 31 * result + (audioSampleRate ?: 0) + return result + } + + override fun toString(): String = + "VideoMetadata(title=$title, duration=$duration, width=$width, height=$height, " + + "bitrate=$bitrate, frameRate=$frameRate, mimeType=$mimeType, " + + "audioChannels=$audioChannels, audioSampleRate=$audioSampleRate)" }