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) .setEnabled(false)
.build() .build()
// 是否处于 PiP // 是否处于 PiP
// 这台设备上进入 PiP 时,动画事件比 onPictureInPictureModeChanged() 更稳定, // MIUI 上进入 PiP 时,动画事件比 onPictureInPictureModeChanged() 更稳定,
// 所以进入 PiP 直接由动画事件置为 true // 所以进入 PiP 直接由动画事件置为 true
private val _pipModeState = MutableStateFlow(false) private val _pipModeState = MutableStateFlow(false)
val pipModeState: StateFlow<Boolean> = _pipModeState val pipModeState: StateFlow<Boolean> = _pipModeState

View File

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