Skip to content

Commit 9dd02d1

Browse files
authored
Merge pull request #208 from kdroidFilter/fix/video-surface-recompose-recovery
fix: recover video playback after composition removal (#203)
2 parents 3807098 + b90d925 commit 9dd02d1

2 files changed

Lines changed: 90 additions & 22 deletions

File tree

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,15 @@ private fun VideoPlayerContent(
207207
},
208208
update = { playerView ->
209209
try {
210-
// Verify that the player is still valid before updating
211-
if (playerState is DefaultVideoPlayerState &&
212-
playerState.exoPlayer != null &&
213-
playerView.player != null
214-
) {
215-
// Update the resize mode when contentScale changes
210+
val state = playerState as? DefaultVideoPlayerState
211+
if (state?.exoPlayer != null) {
212+
// Re-attach after LazyList recycle: onReset nulls playerView.player
213+
// and calls onPause(). Without this, the surface stays blank until
214+
// the user navigates away and back.
215+
if (playerView.player == null) {
216+
state.attachPlayerView(playerView)
217+
playerView.onResume()
218+
}
216219
playerView.resizeMode = mapContentScaleToResizeMode(contentScale)
217220
}
218221
} catch (e: Exception) {
Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package io.github.kdroidfilter.composemediaplayer
22

33
import androidx.compose.runtime.Stable
4+
import androidx.compose.runtime.getValue
5+
import androidx.compose.runtime.mutableStateOf
6+
import androidx.compose.runtime.setValue
47

58
/**
69
* Represents metadata information of a video file.
710
*
8-
* This data class holds various attributes related to the video content,
9-
* including its title, artist, duration, dimensions, codec details, and audio properties.
10-
* This metadata is typically used to provide detailed information about a video
11-
* during playback or for insights in media management systems.
11+
* Properties are backed by [mutableStateOf] so mutations trigger Compose recomposition.
12+
* This matters when callers update fields in place (e.g. on `onVideoSizeChanged` or HLS
13+
* resolution changes) while the metadata instance is read from a composable.
1214
*
1315
* @property title The title of the video, if available.
1416
* @property duration The length of the video in milliseconds, if known.
@@ -21,21 +23,29 @@ import androidx.compose.runtime.Stable
2123
* @property audioSampleRate The sample rate of the audio track in the video, measured in Hz.
2224
*/
2325
@Stable
24-
data class VideoMetadata(
25-
var title: String? = null,
26-
var duration: Long? = null, // Duration in milliseconds
27-
var width: Int? = null,
28-
var height: Int? = null,
29-
var bitrate: Long? = null, // Bitrate in bits per second
30-
var frameRate: Float? = null,
31-
var mimeType: String? = null,
32-
var audioChannels: Int? = null,
33-
var audioSampleRate: Int? = null,
26+
class VideoMetadata(
27+
title: String? = null,
28+
duration: Long? = null,
29+
width: Int? = null,
30+
height: Int? = null,
31+
bitrate: Long? = null,
32+
frameRate: Float? = null,
33+
mimeType: String? = null,
34+
audioChannels: Int? = null,
35+
audioSampleRate: Int? = null,
3436
) {
37+
var title: String? by mutableStateOf(title)
38+
var duration: Long? by mutableStateOf(duration)
39+
var width: Int? by mutableStateOf(width)
40+
var height: Int? by mutableStateOf(height)
41+
var bitrate: Long? by mutableStateOf(bitrate)
42+
var frameRate: Float? by mutableStateOf(frameRate)
43+
var mimeType: String? by mutableStateOf(mimeType)
44+
var audioChannels: Int? by mutableStateOf(audioChannels)
45+
var audioSampleRate: Int? by mutableStateOf(audioSampleRate)
46+
3547
/**
3648
* Checks if all properties of this metadata object are null.
37-
*
38-
* @return true if all properties are null, false otherwise.
3949
*/
4050
fun isAllNull(): Boolean =
4151
title == null &&
@@ -47,4 +57,59 @@ data class VideoMetadata(
4757
mimeType == null &&
4858
audioChannels == null &&
4959
audioSampleRate == null
60+
61+
fun copy(
62+
title: String? = this.title,
63+
duration: Long? = this.duration,
64+
width: Int? = this.width,
65+
height: Int? = this.height,
66+
bitrate: Long? = this.bitrate,
67+
frameRate: Float? = this.frameRate,
68+
mimeType: String? = this.mimeType,
69+
audioChannels: Int? = this.audioChannels,
70+
audioSampleRate: Int? = this.audioSampleRate,
71+
): VideoMetadata =
72+
VideoMetadata(
73+
title = title,
74+
duration = duration,
75+
width = width,
76+
height = height,
77+
bitrate = bitrate,
78+
frameRate = frameRate,
79+
mimeType = mimeType,
80+
audioChannels = audioChannels,
81+
audioSampleRate = audioSampleRate,
82+
)
83+
84+
override fun equals(other: Any?): Boolean {
85+
if (this === other) return true
86+
if (other !is VideoMetadata) return false
87+
return title == other.title &&
88+
duration == other.duration &&
89+
width == other.width &&
90+
height == other.height &&
91+
bitrate == other.bitrate &&
92+
frameRate == other.frameRate &&
93+
mimeType == other.mimeType &&
94+
audioChannels == other.audioChannels &&
95+
audioSampleRate == other.audioSampleRate
96+
}
97+
98+
override fun hashCode(): Int {
99+
var result = title?.hashCode() ?: 0
100+
result = 31 * result + (duration?.hashCode() ?: 0)
101+
result = 31 * result + (width ?: 0)
102+
result = 31 * result + (height ?: 0)
103+
result = 31 * result + (bitrate?.hashCode() ?: 0)
104+
result = 31 * result + (frameRate?.hashCode() ?: 0)
105+
result = 31 * result + (mimeType?.hashCode() ?: 0)
106+
result = 31 * result + (audioChannels ?: 0)
107+
result = 31 * result + (audioSampleRate ?: 0)
108+
return result
109+
}
110+
111+
override fun toString(): String =
112+
"VideoMetadata(title=$title, duration=$duration, width=$width, height=$height, " +
113+
"bitrate=$bitrate, frameRate=$frameRate, mimeType=$mimeType, " +
114+
"audioChannels=$audioChannels, audioSampleRate=$audioSampleRate)"
50115
}

0 commit comments

Comments
 (0)