fix: SurfaceView rather than TextureView

This commit is contained in:
Miuzarte
2026-04-11 15:33:15 +08:00
parent dbf83b33c2
commit 65d5f12344
2 changed files with 38 additions and 28 deletions

View File

@@ -29,9 +29,9 @@ class StreamActivity : ComponentActivity() {
.setEnabled(false)
.build()
// 是否处于 PiP
// 这台设备上进入 PiP 时,动画事件比 onPictureInPictureModeChanged() 更稳定,
// 所以进入 PiP 直接由动画事件置为 true
// 是否处于 PiP
// MIUI 上进入 PiP 时,动画事件比 onPictureInPictureModeChanged() 更稳定,
// 所以进入 PiP 直接由动画事件置为 true
private val _pipModeState = MutableStateFlow(false)
val pipModeState: StateFlow<Boolean> = _pipModeState

View File

@@ -1,9 +1,9 @@
package io.github.miuzarte.scrcpyforandroid.widgets
import android.annotation.SuppressLint
import android.graphics.SurfaceTexture
import android.view.Surface
import android.view.TextureView
import android.view.SurfaceHolder
import android.view.SurfaceView
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
@@ -742,16 +742,16 @@ private fun PairingDialog(
* ScrcpyVideoSurface
*
* Purpose:
* - Hosts a `TextureView` and bridges its `Surface` to `nativeCore` for video rendering.
* - Hosts a `SurfaceView` and bridges its `Surface` to `nativeCore` for video rendering.
* - Ensures only a single `Surface` instance is registered at any time under the
* stable `surfaceTag` ("video-main"). This reduces surface recreation bugs seen
* when preview/fullscreen used separate tags.
*
* Concurrency / lifecycle:
* - `currentSurface` is only mutated on the UI thread via TextureView callbacks.
* - `currentSurface` is only mutated on the UI thread via SurfaceHolder callbacks.
* - Registration to `nativeCore` is triggered from a [LaunchedEffect] when both
* `session` and `currentSurface` are available. Unregistration happens in
* `onSurfaceTextureDestroyed` and `DisposableEffect.onDispose` to guarantee
* `surfaceDestroyed` and `DisposableEffect.onDispose` to guarantee
* cleanup even if the composable leaves composition.
*
* Reliability notes:
@@ -765,10 +765,21 @@ fun ScrcpyVideoSurface(
target: VideoOutputTarget,
) {
var currentSurface by remember { mutableStateOf<Surface?>(null) }
var currentSurfaceView by remember { mutableStateOf<SurfaceView?>(null) }
val scope = rememberCoroutineScope()
val taskScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.IO) }
val lifecycleOwner = LocalLifecycleOwner.current
val currentTarget by VideoOutputTargetState.current.collectAsState()
val latestSession by rememberUpdatedState(session)
val latestCurrentTarget by rememberUpdatedState(currentTarget)
LaunchedEffect(session?.width, session?.height, currentSurfaceView) {
val surfaceView = currentSurfaceView ?: return@LaunchedEffect
val currentSession = session ?: return@LaunchedEffect
if (currentSession.width > 0 && currentSession.height > 0) {
surfaceView.holder.setFixedSize(currentSession.width, currentSession.height)
}
}
LaunchedEffect(session, currentSurface, currentTarget, target) {
val surface = currentSurface
@@ -811,44 +822,43 @@ fun ScrcpyVideoSurface(
AndroidView(
modifier = modifier,
factory = { context ->
TextureView(context).apply {
surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(
surfaceTexture: SurfaceTexture,
width: Int,
height: Int
) {
@SuppressLint("Recycle")
val newSurface = Surface(surfaceTexture)
SurfaceView(context).apply {
currentSurfaceView = this
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
val newSurface = holder.surface
if (!newSurface.isValid) return
currentSurface = newSurface
// Register immediately when surface becomes available
if (currentTarget == target && session != null) {
if (latestCurrentTarget == target && latestSession != null) {
scope.launch {
NativeCoreFacade.attachVideoSurface(newSurface)
}
}
}
override fun onSurfaceTextureSizeChanged(
surfaceTexture: SurfaceTexture,
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) = Unit
height: Int,
) {
if (width <= 0 || height <= 0) return
val surface = holder.surface
if (!surface.isValid) return
currentSurface = surface
}
override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean {
override fun surfaceDestroyed(holder: SurfaceHolder) {
val surface = currentSurface
if (surface != null) {
taskScope.launch {
NativeCoreFacade.detachVideoSurface(surface, releaseDecoder = false)
}
surface.release()
currentSurface = null
}
return true
}
override fun onSurfaceTextureUpdated(surfaceTexture: SurfaceTexture) = Unit
}
})
}
},
update = {},