优化录制页性能
This commit is contained in:
+120
-32
@@ -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(
|
||||
|
||||
+73
-20
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user