diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt index 90e95399..78280266 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt @@ -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 diff --git a/tools/chatgui-android/video-tests/src/main/kotlin/android/hardware/camera2/HostCamera2.kt b/tools/chatgui-android/video-tests/src/main/kotlin/android/hardware/camera2/HostCamera2.kt index fad7a4d5..4a7755cd 100644 --- a/tools/chatgui-android/video-tests/src/main/kotlin/android/hardware/camera2/HostCamera2.kt +++ b/tools/chatgui-android/video-tests/src/main/kotlin/android/hardware/camera2/HostCamera2.kt @@ -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) }