Document camera finalization ownership and isolate late SDK diagnostics

This commit is contained in:
evgeny committed 2026-10-29 13:34:26 +01:00
1 parent 8dd8dc96d7
commit 1529dfd04b
2 files changed
+32 -10

No files matched your search

@@ -283,13 +283,18 @@ fun CameraRecordScreen(initialMode: CaptureMode, onSend: (String, CaptureMode) -
val ready = provider; val selected = lens
if (ready != null && selected != null) {
if (mode == CaptureMode.PHOTO) {
resolutions = CameraLensManager.photoResolutions(ready, selected.cameraId)
resolution = photoSettings.resolveResolution(resolutions)
if (photoSettings.width > 0 && (resolution?.width != photoSettings.width || resolution?.height != photoSettings.height))
LogManager.addLog("WARN", "Capture", "Saved photo resolution unavailable camera=${selected.cameraId} " +
"saved=${photoSettings.width}x${photoSettings.height} selected=${resolution?.label}")
LogManager.addLog("INFO", "Capture", "Photo settings restored camera=${selected.cameraId} " +
"resolution=${resolution?.label} jpegQuality=$jpegQuality")
try {
resolutions = CameraLensManager.photoResolutions(ready, selected.cameraId)
resolution = photoSettings.resolveResolution(resolutions)
if (photoSettings.width > 0 && (resolution?.width != photoSettings.width || resolution?.height != photoSettings.height))
LogManager.addLog("WARN", "Capture", "Saved photo resolution unavailable camera=${selected.cameraId} " +
"saved=${photoSettings.width}x${photoSettings.height} selected=${resolution?.label}")
LogManager.addLog("INFO", "Capture", "Photo settings restored camera=${selected.cameraId} " +
"resolution=${resolution?.label} jpegQuality=$jpegQuality")
} catch (e: Exception) {
resolutions = emptyList(); resolution = null; error = true
LogManager.addLog("ERROR", "Capture", "Photo capabilities failed camera=${selected.cameraId}: $e")
}
return@LaunchedEffect
}
try {
@@ -404,7 +409,11 @@ fun CameraRecordScreen(initialMode: CaptureMode, onSend: (String, CaptureMode) -
val pending = videoCapture.output.prepareRecording(context, FileOutputOptions.Builder(file).build()).withAudioEnabled()
attemptedStart = true
recording = pending.start(main) { event ->
if (event is VideoRecordEvent.Finalize) resources.completed(file)
if (event is VideoRecordEvent.Finalize) {
if (event.hasError()) LogManager.addLog(if (closed || output != file) "DEBUG" else "ERROR", "Capture",
"Video finalize code=${event.error} discarded=${closed || output != file}: ${event.cause}")
resources.completed(file)
}
if (closed || output != file) {
if (event is VideoRecordEvent.Finalize) resources.discard(file)
} else when (event) {
@@ -426,7 +435,6 @@ fun CameraRecordScreen(initialMode: CaptureMode, onSend: (String, CaptureMode) -
recording = null
if (event.hasError()) {
resources.discard(file); output = null; pendingSend = false; phase = CapturePhase.IDLE; error = true
LogManager.addLog("ERROR", "Capture", "Video finalize failed code=${event.error}: ${event.cause?.message}")
} else {
phase = CapturePhase.FINALIZING
scope.launch {
@@ -486,7 +494,10 @@ fun CameraRecordScreen(initialMode: CaptureMode, onSend: (String, CaptureMode) -
val request = selected?.cameraControl?.enableTorch(next)
request?.addListener({
try { request.get(); if (!closed && camera === selected) torch = next }
catch (e: Exception) { error = true; LogManager.addLog("ERROR", "Capture", "Torch failed: $e") }
catch (e: Exception) {
if (!closed && camera === selected) error = true
LogManager.addLog("ERROR", "Capture", "Torch failed: $e")
}
}, main)
} catch (e: Exception) { error = true; LogManager.addLog("ERROR", "Capture", "Torch request failed: $e") }
}) { Icon(if (torch) Icons.Default.FlashOn else Icons.Default.FlashOff, stringResource(R.string.camera_light)) }
+11
View File
@@ -266,6 +266,17 @@ feed/pull workers и маршрутом Android Telecom; DSP и сетевой
Вызовы lifecycle DSP выполняются после остановки его внешних feed/pull callers.
Контракты PCM/PTS: `src/call/call_audio.h`, `src/radio/radio_audio.h`, `jni_bridge/android_jni_bridge.h`.
CameraRecordScreen сохраняет CameraCaptureResources до захвата и снимает только свои
use cases. Cache фото живёт до OnImageSaved/onError, видео — до Finalize; discard/выход
UI сначала запрашивает закрытие записи и ждёт writer перед удалением файла.
Ошибки Recording.close, unbind, отключения ориентации и удаления независимы и сохраняются
в process registry с retry. Поздний callback не восстанавливает UI и не удаляет переданный sender файл.
Pause/resume подтверждаются событиями CameraX; разрешения перечитываются при ON_RESUME.
VideoFileTracks проверяет MIME/геометрию готового MP4; MediaExtractor остаётся до release ACK,
ошибка чтения сохраняется при дополнительной ошибке cleanup. Контракты CameraX:
[Recording.close](https://developer.android.com/reference/androidx/camera/video/Recording#close()),
[PendingRecording.start](https://developer.android.com/reference/androidx/camera/video/PendingRecording#start(java.util.concurrent.Executor,androidx.core.util.Consumer)).
Локальный `VideoPlayerScreen` сохраняет пассивный VideoFilePlayer до start. UI отражает
результаты MediaPlayer start/pause/seek, ошибка команды завершает playback и оставляет
доступным закрытие экрана. Пользовательская пауза сохраняется при пересоздании Surface.