Handle failed Camera2 sessions without waiting for an absent callback

This commit is contained in:
evgeny committed 2026-10-29 13:34:24 +01:00
1 parent 53340db0e3
commit 4d559dc08b
2 files changed
+17 -4

No files matched your search

@@ -591,6 +591,7 @@ class CallVideoEngine(private val context: Context, private val id: Long) {
}
}
sessions[serial] = device to ::complete
var submitted = false
try {
val c = manager.getCameraCharacteristics(device.id)
var previewFormat: StreamPreviewFormat? = null
@@ -605,6 +606,7 @@ class CallVideoEngine(private val context: Context, private val id: Long) {
preview.update(previewFormat)
} else if (withPreview) preview.update(null)
val targets = listOfNotNull(source.surface, output?.surface)
submitted = true
device.createCaptureSession(targets, object : CameraCaptureSession.StateCallback() {
override fun onConfigured(s: CameraCaptureSession) {
if (completed) { try { s.close() } catch (e: Exception) { LogManager.addLog("WARN", "call", "closed camera session callback id=$id: $e") }; return }
@@ -624,7 +626,8 @@ class CallVideoEngine(private val context: Context, private val id: Long) {
} catch (e: Exception) { closingSessions.add(s); queueCleanup(0); rejected("Camera request: ${e.message}") }
}
override fun onConfigureFailed(s: CameraCaptureSession) {
ownedSessions[s] = device; closingSessions.add(s); queueCleanup(0)
// SDK уже закрыл сессию; onClosed после этого callback не гарантируется.
ownedSessions.remove(s); closingSessions.remove(s); complete()
rejected("Camera session configuration failed")
}
override fun onClosed(s: CameraCaptureSession) {
@@ -633,7 +636,15 @@ class CallVideoEngine(private val context: Context, private val id: Long) {
complete(); queueCleanup(0)
}
}, handler)
} catch (e: Exception) { complete(); rejected("Camera session: ${e.message}") }
} catch (e: Exception) {
if (!submitted) { complete(); rejected("Camera session: ${e.message}") }
else {
// SDK может одновременно бросить CameraAccessException и назначить callback сессии.
// До camera.onClosed сохраняем все выходы даже при отсутствии callback сессии.
LogManager.addLog("ERROR", "call", "camera session submission failed id=$id session=$serial; awaiting device closure: $e")
fail("Camera session: ${e.message}")
}
}
}
private fun drain(finish: Boolean = false) {
val codec = encoder ?: return; val f = format ?: return
@@ -89,9 +89,10 @@ class CameraDevice(val id: String, private val owner: CameraManager, private val
val session = CameraCaptureSession(this, callback, delivery).also { it.failures = owner.nextSessionFailures }
owner.sessions.add(session)
if (owner.autoConfigured) delivery.post {
if ("configure" in session.failures) callback.onConfigureFailed(session) else callback.onConfigured(session)
if ("configure" in session.failures || "session after" in failures) session.failConfiguration()
else callback.onConfigured(session)
}
check("session after" !in failures) { "session creation failed after effect" }
if ("session after" in failures) throw CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED)
}
fun close() {
closeCalls++; check("close" !in failures) { "camera close failed" }
@@ -118,6 +119,7 @@ class CameraCaptureSession(val device: CameraDevice, val callback: StateCallback
check("request" !in failures) { "repeating request failed" }; repeating = true
}
fun close() { check("close" !in failures) { "session close failed" }; forceClose(); check("close after" !in failures) }
internal fun failConfiguration() { closed = true; callback.onConfigureFailed(this) }
internal fun forceClose() {
if (closed) return
closed = true; repeating = false; handler.post { callback.onClosed(this) }