Skip to content

Commit 4edba26

Browse files
authored
Merge pull request #892 from DimensionDev/bugfix/video_playback
fix video playback stop when navigate back
2 parents de7ef05 + 1d4c178 commit 4edba26

File tree

6 files changed

+149
-82
lines changed

6 files changed

+149
-82
lines changed

app/src/main/java/dev/dimension/flare/ui/component/NavigationSuiteScaffold2.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ fun NavigationSuiteScaffold2(
232232
} else {
233233
0.dp
234234
},
235-
LocalBottomBarShowing provides (layoutType == NavigationSuiteType.NavigationBar && !shouldHideBottomBar),
235+
LocalBottomBarShowing provides (layoutType == NavigationSuiteType.NavigationBar),
236236
) {
237237
content.invoke()
238238
}

app/src/main/java/dev/dimension/flare/ui/screen/media/MediaScreen.kt

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
package dev.dimension.flare.ui.screen.media
22

33
import android.Manifest
4-
import android.app.Activity
54
import android.content.ContentValues
65
import android.content.Context
7-
import android.content.ContextWrapper
86
import android.graphics.BitmapFactory
97
import android.os.Build
108
import android.os.Environment
119
import android.provider.MediaStore
12-
import android.view.View
13-
import android.view.Window
14-
import android.view.WindowManager
15-
import android.widget.FrameLayout
1610
import android.widget.Toast
1711
import androidx.compose.animation.AnimatedVisibility
1812
import androidx.compose.animation.slideInVertically
@@ -33,7 +27,6 @@ import androidx.compose.material3.FilledTonalIconButton
3327
import androidx.compose.material3.MaterialTheme
3428
import androidx.compose.runtime.Composable
3529
import androidx.compose.runtime.LaunchedEffect
36-
import androidx.compose.runtime.SideEffect
3730
import androidx.compose.runtime.getValue
3831
import androidx.compose.runtime.mutableStateOf
3932
import androidx.compose.runtime.remember
@@ -90,41 +83,11 @@ import java.io.IOException
9083
object FullScreenDialogStyle : DestinationStyle.Dialog() {
9184
override val properties =
9285
DialogProperties(
93-
usePlatformDefaultWidth = true,
9486
decorFitsSystemWindows = false,
87+
usePlatformDefaultWidth = false,
9588
)
9689
}
9790

98-
@Composable
99-
fun getActivityWindow(): Window? = LocalView.current.context.getActivityWindow()
100-
101-
private tailrec fun Context.getActivityWindow(): Window? =
102-
when (this) {
103-
is Activity -> window
104-
is ContextWrapper -> baseContext.getActivityWindow()
105-
else -> null
106-
}
107-
108-
@Composable
109-
fun SetDialogDestinationToEdgeToEdge() {
110-
val activityWindow = getActivityWindow()
111-
val dialogWindow = (LocalView.current.parent as? DialogWindowProvider)?.window
112-
val parentView = LocalView.current.parent as View
113-
SideEffect {
114-
if (activityWindow != null && dialogWindow != null) {
115-
val attributes = WindowManager.LayoutParams()
116-
attributes.copyFrom(activityWindow.attributes)
117-
attributes.type = dialogWindow.attributes.type
118-
dialogWindow.attributes = attributes
119-
parentView.layoutParams =
120-
FrameLayout.LayoutParams(
121-
activityWindow.decorView.width,
122-
activityWindow.decorView.height,
123-
)
124-
}
125-
}
126-
}
127-
12891
@Composable
12992
@Destination<RootGraph>(
13093
style = FullScreenDialogStyle::class,
@@ -142,7 +105,10 @@ fun MediaRoute(
142105
navigator: DestinationsNavigator,
143106
previewUrl: String? = null,
144107
) {
145-
SetDialogDestinationToEdgeToEdge()
108+
val view = LocalView.current
109+
LaunchedEffect(view) {
110+
(view.parent as DialogWindowProvider).window.setDimAmount(0f)
111+
}
146112
MediaScreen(
147113
uri = uri,
148114
onDismiss = navigator::navigateUp,

app/src/main/java/dev/dimension/flare/ui/screen/media/StatusMediaScreen.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ import androidx.compose.ui.graphics.Brush
5959
import androidx.compose.ui.graphics.Color
6060
import androidx.compose.ui.layout.ContentScale
6161
import androidx.compose.ui.platform.LocalContext
62+
import androidx.compose.ui.platform.LocalView
6263
import androidx.compose.ui.res.stringResource
6364
import androidx.compose.ui.unit.dp
65+
import androidx.compose.ui.window.DialogWindowProvider
6466
import androidx.media3.common.util.UnstableApi
6567
import androidx.media3.exoplayer.ExoPlayer
6668
import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
@@ -170,6 +172,10 @@ internal fun StatusMediaDeeplinkRoute(
170172
navigator: DestinationsNavigator,
171173
navigationState: NavigationState,
172174
) {
175+
val view = LocalView.current
176+
LaunchedEffect(view) {
177+
(view.parent as DialogWindowProvider).window.setDimAmount(0f)
178+
}
173179
val accountType = accountKey?.let { AccountType.Specific(it) } ?: AccountType.Guest
174180
StatusMediaRoute(
175181
statusKey = statusKey,
@@ -200,7 +206,10 @@ internal fun StatusMediaRoute(
200206
accountType: AccountType,
201207
navigationState: NavigationState,
202208
) {
203-
SetDialogDestinationToEdgeToEdge()
209+
val view = LocalView.current
210+
LaunchedEffect(view) {
211+
(view.parent as DialogWindowProvider).window.setDimAmount(0f)
212+
}
204213
// AnimatedVisibility(true) {
205214
// SharedTransitionScope {
206215
// DisposableEffect(Unit) {

app/src/main/java/dev/dimension/flare/ui/screen/settings/LocalFilterEditDialog.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import dev.dimension.flare.ui.model.onSuccess
3939
import dev.dimension.flare.ui.presenter.invoke
4040
import dev.dimension.flare.ui.presenter.settings.LocalFilterPresenter
4141
import dev.dimension.flare.ui.screen.media.FullScreenDialogStyle
42-
import dev.dimension.flare.ui.screen.media.SetDialogDestinationToEdgeToEdge
4342
import moe.tlaster.precompose.molecule.producePresenter
4443

4544
@Destination<RootGraph>(
@@ -66,7 +65,6 @@ private fun LocalFilterEditDialog(
6665
val state by producePresenter {
6766
presenter(keyword = keyword)
6867
}
69-
SetDialogDestinationToEdgeToEdge()
7068
FlareScaffold(
7169
topBar = {
7270
FlareTopAppBar(

shared/ui/component/src/androidMain/kotlin/dev/dimension/flare/ui/component/VideoPlayer.kt

Lines changed: 108 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import kotlinx.coroutines.delay
4141
import kotlinx.coroutines.launch
4242
import kotlinx.coroutines.withContext
4343
import org.koin.compose.koinInject
44+
import java.util.UUID
4445
import kotlin.concurrent.timer
4546
import kotlin.time.Duration.Companion.minutes
4647

@@ -141,34 +142,40 @@ public fun VideoPlayer(
141142
playerPool.release(uri)
142143
}
143144
}
144-
PlayerSurface(
145-
player = player,
146-
modifier =
147-
Modifier
148-
.clipToBounds()
149-
.resizeWithContentScale(
150-
contentScale = contentScale,
151-
sourceSizeDp = state.videoSizeDp,
152-
).let {
153-
if (aspectRatio != null) {
154-
it.aspectRatio(
155-
aspectRatio,
156-
)
157-
} else {
158-
it
159-
}
160-
}.let {
161-
if (onClick != null) {
162-
it.combinedClickable(
163-
onClick = onClick,
164-
onLongClick = onLongClick,
165-
)
166-
} else {
167-
it
168-
}
169-
}.matchParentSize(),
170-
surfaceType = SURFACE_TYPE_TEXTURE_VIEW,
171-
)
145+
val isActive = rememberSurfaceBinding(uri)
146+
val playerModifier =
147+
Modifier
148+
.clipToBounds()
149+
.resizeWithContentScale(
150+
contentScale = contentScale,
151+
sourceSizeDp = state.videoSizeDp,
152+
).let {
153+
if (aspectRatio != null) {
154+
it.aspectRatio(
155+
aspectRatio,
156+
)
157+
} else {
158+
it
159+
}
160+
}.let {
161+
if (onClick != null) {
162+
it.combinedClickable(
163+
onClick = onClick,
164+
onLongClick = onLongClick,
165+
)
166+
} else {
167+
it
168+
}
169+
}.matchParentSize()
170+
if (isActive) {
171+
PlayerSurface(
172+
player = player,
173+
modifier = playerModifier,
174+
surfaceType = SURFACE_TYPE_TEXTURE_VIEW,
175+
)
176+
} else {
177+
loadingPlaceholder.invoke(this)
178+
}
172179
if (!isLoaded) {
173180
loadingPlaceholder()
174181
} else {
@@ -248,3 +255,76 @@ public class VideoPlayerPool(
248255
return count == 0L
249256
}
250257
}
258+
259+
@Composable
260+
private fun rememberSurfaceBinding(
261+
uri: String,
262+
autoRequest: Boolean = true,
263+
): Boolean {
264+
val surfaceKey = remember { UUID.randomUUID().toString() }
265+
var isActive by remember { mutableStateOf(false) }
266+
267+
LaunchedEffect(uri) {
268+
SurfaceBindingManager.register(uri, surfaceKey) { active ->
269+
isActive = active
270+
}
271+
272+
if (autoRequest) {
273+
SurfaceBindingManager.requestBind(uri, surfaceKey)
274+
}
275+
}
276+
277+
DisposableEffect(uri) {
278+
onDispose {
279+
SurfaceBindingManager.unregister(uri, surfaceKey)
280+
}
281+
}
282+
283+
return isActive
284+
}
285+
286+
private object SurfaceBindingManager {
287+
private val bindings = mutableMapOf<String, String>() // uri -> ownerKey
288+
private val listeners = mutableMapOf<String, MutableMap<String, (Boolean) -> Unit>>() // uri -> (ownerKey -> callback)
289+
290+
fun register(
291+
uri: String,
292+
key: String,
293+
onActiveChanged: (Boolean) -> Unit,
294+
) {
295+
val uriListeners = listeners.getOrPut(uri) { mutableMapOf() }
296+
uriListeners[key] = onActiveChanged
297+
298+
val current = bindings[uri]
299+
if (current == null) {
300+
bindings[uri] = key
301+
onActiveChanged(true)
302+
} else {
303+
onActiveChanged(current == key)
304+
}
305+
}
306+
307+
fun unregister(
308+
uri: String,
309+
key: String,
310+
) {
311+
listeners[uri]?.remove(key)
312+
if (bindings[uri] == key) {
313+
bindings.remove(uri)
314+
listeners[uri]?.entries?.firstOrNull()?.let { (nextKey, callback) ->
315+
bindings[uri] = nextKey
316+
callback(true)
317+
}
318+
}
319+
}
320+
321+
fun requestBind(
322+
uri: String,
323+
key: String,
324+
) {
325+
if (bindings[uri] == key) return
326+
listeners[uri]?.get(bindings[uri])?.invoke(false)
327+
bindings[uri] = key
328+
listeners[uri]?.get(key)?.invoke(true)
329+
}
330+
}

shared/ui/component/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/StatusMediaComponent.kt

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize
99
import androidx.compose.foundation.layout.padding
1010
import androidx.compose.foundation.layout.size
1111
import androidx.compose.runtime.Composable
12+
import androidx.compose.runtime.CompositionLocalProvider
1213
import androidx.compose.runtime.getValue
1314
import androidx.compose.runtime.mutableStateOf
1415
import androidx.compose.runtime.remember
@@ -76,11 +77,23 @@ internal fun StatusMediaComponent(
7677
content = {
7778
data.forEach { media ->
7879
Box {
79-
MediaItem(
80-
media = media,
81-
modifier =
82-
Modifier
83-
.clipToBounds()
80+
CompositionLocalProvider(
81+
LocalComponentAppearance provides
82+
appearanceSettings
83+
.copy(
84+
videoAutoplay =
85+
if (hideSensitive) {
86+
ComponentAppearance.VideoAutoplay.NEVER
87+
} else {
88+
appearanceSettings.videoAutoplay
89+
},
90+
),
91+
) {
92+
MediaItem(
93+
media = media,
94+
modifier =
95+
Modifier
96+
.clipToBounds()
8497
// .sharedElement(
8598
// rememberSharedContentState(
8699
// when (media) {
@@ -92,12 +105,13 @@ internal fun StatusMediaComponent(
92105
// ),
93106
// animatedVisibilityScope = this@AnimatedVisibilityScope,
94107
// )
95-
.pointerHoverIcon(PointerIcon.Hand)
96-
.clickable {
97-
onMediaClick(media)
98-
},
99-
keepAspectRatio = data.size == 1 && appearanceSettings.expandMediaSize,
100-
)
108+
.pointerHoverIcon(PointerIcon.Hand)
109+
.clickable {
110+
onMediaClick(media)
111+
},
112+
keepAspectRatio = data.size == 1 && appearanceSettings.expandMediaSize,
113+
)
114+
}
101115
if (!media.description.isNullOrEmpty()) {
102116
PlatformText(
103117
text = "ALT",

0 commit comments

Comments
 (0)