优化录制页性能

This commit is contained in:
2026-07-28 13:47:25 +08:00
parent a56a42c7c8
commit 36253aacbe
7 changed files with 450 additions and 167 deletions
@@ -2,6 +2,8 @@ package video.api.flutter.livestream
import android.Manifest
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Size
import android.view.Surface
import io.flutter.view.TextureRegistry
@@ -17,7 +19,15 @@ import io.github.thibaultbee.streampack.utils.frontCameraList
import io.github.thibaultbee.streampack.utils.isBackCamera
import io.github.thibaultbee.streampack.utils.isExternalCamera
import io.github.thibaultbee.streampack.utils.isFrontCamera
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
class FlutterLiveStreamView(
private val context: Context,
@@ -39,9 +49,21 @@ class FlutterLiveStreamView(
initialOnConnectionListener = this,
initialOnErrorListener = this
)
private val mainHandler = Handler(Looper.getMainLooper())
private val operationScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val operationMutex = Mutex()
private val disposeLock = Any()
private val disposeCallbacks =
mutableListOf<Pair<() -> Unit, (Exception) -> Unit>>()
@Volatile
private var _isPreviewing = false
@Volatile
private var _isStreaming = false
@Volatile
private var _isDisposing = false
@Volatile
private var _isDisposed = false
val isStreaming: Boolean
get() = _isStreaming
@@ -63,7 +85,7 @@ class FlutterLiveStreamView(
val wasPreviewing = _isPreviewing
if (wasPreviewing) {
stopPreview()
stopPreviewInternal()
}
streamer.configure(videoConfig)
_videoConfig = videoConfig
@@ -174,23 +196,58 @@ class FlutterLiveStreamView(
setCamera(cameraList.first(), onSuccess, onError)
}
fun dispose() {
try {
stopStream()
} catch (e: Exception) {
android.util.Log.w("ApiVideoLiveStream", "stopStream during dispose failed", e)
_isStreaming = false
fun dispose(onSuccess: () -> Unit, onError: (Exception) -> Unit) {
var shouldStartDispose = false
synchronized(disposeLock) {
if (_isDisposed) {
mainHandler.post(onSuccess)
return
}
disposeCallbacks.add(onSuccess to onError)
if (!_isDisposing) {
_isDisposing = true
shouldStartDispose = true
}
}
try {
streamer.stopPreview()
} catch (e: Exception) {
android.util.Log.w("ApiVideoLiveStream", "stopPreview during dispose failed", e)
if (!shouldStartDispose) return
operationScope.launch {
var failure: Exception? = null
operationMutex.withLock {
try {
stopStreamInternal()
} catch (e: Exception) {
android.util.Log.w("ApiVideoLiveStream", "stopStream during dispose failed", e)
failure = e
}
try {
stopPreviewInternal()
} catch (e: Exception) {
android.util.Log.w("ApiVideoLiveStream", "stopPreview during dispose failed", e)
failure = failure ?: e
}
}
withContext(Dispatchers.Main.immediate) {
try {
flutterTexture.release()
} catch (e: Exception) {
failure = failure ?: e
}
val callbacks = synchronized(disposeLock) {
_isDisposed = true
_isDisposing = false
disposeCallbacks.toList().also { disposeCallbacks.clear() }
}
callbacks.forEach { (success, error) ->
failure?.let(error) ?: success()
}
}
operationScope.cancel()
}
_isPreviewing = false
flutterTexture.release()
}
fun startStream(url: String) {
check(!_isDisposing && !_isDisposed) { "Live stream has been disposed" }
runBlocking {
streamer.connect(url)
try {
@@ -204,29 +261,31 @@ class FlutterLiveStreamView(
}
}
fun stopStream() {
if (!_isStreaming && !streamer.isConnected) {
return
fun stopStream(onSuccess: () -> Unit, onError: (Exception) -> Unit) {
runBackgroundOperation(onSuccess, onError) {
stopStreamInternal()
}
}
private suspend fun stopStreamInternal() {
if (!_isStreaming && !streamer.isConnected) return
val isConnected = streamer.isConnected
var failure: Exception? = null
try {
runBlocking {
streamer.stopStream()
streamer.disconnect()
if (isConnected) {
onDisconnected()
}
_isStreaming = false
}
streamer.stopStream()
} catch (e: Exception) {
android.util.Log.w("ApiVideoLiveStream", "stopStream failed", e)
_isStreaming = false
try {
streamer.disconnect()
} catch (_: Exception) {
// ignore secondary disconnect failures
}
failure = e
}
try {
streamer.disconnect()
} catch (e: Exception) {
failure = failure ?: e
}
if (isConnected) {
onDisconnected()
}
_isStreaming = false
failure?.let { throw it }
}
fun startPreview(onSuccess: () -> Unit, onError: (Exception) -> Unit) {
@@ -262,11 +321,40 @@ class FlutterLiveStreamView(
})
}
fun stopPreview() {
fun stopPreview(onSuccess: () -> Unit, onError: (Exception) -> Unit) {
runBackgroundOperation(onSuccess, onError) {
stopPreviewInternal()
}
}
private fun stopPreviewInternal() {
if (!_isPreviewing) return
streamer.stopPreview()
_isPreviewing = false
}
private fun runBackgroundOperation(
onSuccess: () -> Unit,
onError: (Exception) -> Unit,
operation: suspend () -> Unit,
) {
if (_isDisposing || _isDisposed) {
mainHandler.post(onSuccess)
return
}
operationScope.launch {
val failure = try {
operationMutex.withLock { operation() }
null
} catch (e: Exception) {
e
}
withContext(Dispatchers.Main.immediate) {
failure?.let(onError) ?: onSuccess()
}
}
}
private fun getSurface(resolution: Size): Surface {
val surfaceTexture = flutterTexture.surfaceTexture().apply {
setDefaultBufferSize(
@@ -50,27 +50,44 @@ class MethodCallHandlerImpl(
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"create" -> {
try {
flutterView?.dispose()
flutterView = FlutterLiveStreamView(
context,
textureRegistry,
permissionsManager,
{ sendConnected() },
{ sendDisconnected() },
{ sendConnectionFailed(it) },
{ sendError(it) },
{ sendVideoSizeChanged(it) }
val previousView = flutterView
if (previousView == null) {
createFlutterView(result)
} else {
previousView.dispose(
onSuccess = {
if (flutterView === previousView) {
flutterView = null
}
createFlutterView(result)
},
onError = {
result.error("failed_to_replace_live_stream", it.message, null)
},
)
result.success(mapOf("textureId" to flutterView!!.textureId))
} catch (e: Exception) {
result.error("failed_to_create_live_stream", e.message, null)
}
}
"dispose" -> {
flutterView?.dispose()
flutterView = null
val view = flutterView
if (view == null) {
result.success(null)
} else {
view.dispose(
onSuccess = {
if (flutterView === view) {
flutterView = null
}
result.success(null)
},
onError = {
if (flutterView === view) {
flutterView = null
}
result.error("failed_to_dispose_live_stream", it.message, null)
},
)
}
}
"setVideoConfig" -> {
@@ -128,8 +145,17 @@ class MethodCallHandlerImpl(
}
"stopPreview" -> {
flutterView?.stopPreview()
result.success(null)
val view = flutterView
if (view == null) {
result.success(null)
} else {
view.stopPreview(
onSuccess = { result.success(null) },
onError = {
result.error("failed_to_stop_preview", it.message, null)
},
)
}
}
"startStreaming" -> {
@@ -163,8 +189,17 @@ class MethodCallHandlerImpl(
}
"stopStreaming" -> {
flutterView?.stopStream()
result.success(null)
val view = flutterView
if (view == null) {
result.success(null)
} else {
view.stopStream(
onSuccess = { result.success(null) },
onError = {
result.error("failed_to_stop_stream", it.message, null)
},
)
}
}
"getIsStreaming" -> result.success(mapOf("isStreaming" to flutterView!!.isStreaming))
@@ -275,6 +310,24 @@ class MethodCallHandlerImpl(
}
}
private fun createFlutterView(result: MethodChannel.Result) {
try {
flutterView = FlutterLiveStreamView(
context,
textureRegistry,
permissionsManager,
{ sendConnected() },
{ sendDisconnected() },
{ sendConnectionFailed(it) },
{ sendError(it) },
{ sendVideoSizeChanged(it) },
)
result.success(mapOf("textureId" to flutterView!!.textureId))
} catch (e: Exception) {
result.error("failed_to_create_live_stream", e.message, null)
}
}
private fun sendConnected() {
sendEvent("connected")
}