diff --git a/src/components/ImageMethodDialog.vue b/src/components/ImageMethodDialog.vue
index 7564e913..b6cdd9bd 100644
--- a/src/components/ImageMethodDialog.vue
+++ b/src/components/ImageMethodDialog.vue
@@ -20,7 +20,7 @@
Camera preview not started.
@@ -328,6 +328,9 @@ export default class ImageMethodDialog extends Vue {
/** Whether to show camera preview */
showCameraPreview = false;
+ /** Whether currently retrying camera preview */
+ isRetrying = false;
+
/** Camera stream reference */
private cameraStream: MediaStream | null = null;
@@ -589,6 +592,7 @@ export default class ImageMethodDialog extends Vue {
}
this.blob = undefined;
this.showCameraPreview = false;
+ this.isRetrying = false;
}
async startCameraPreview() {
@@ -600,6 +604,13 @@ export default class ImageMethodDialog extends Vue {
"getUserMedia available:",
!!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),
);
+ logger.debug("Current facing mode:", this.currentFacingMode);
+
+ // If camera is already active, don't restart it
+ if (this.cameraState === "active" && this.cameraStream) {
+ logger.debug("Camera is already active, skipping restart");
+ return;
+ }
try {
this.cameraState = "initializing";
@@ -611,10 +622,17 @@ export default class ImageMethodDialog extends Vue {
throw new Error("Camera API not available in this browser");
}
+ logger.debug(
+ "Requesting camera stream with facingMode:",
+ this.currentFacingMode,
+ );
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: this.currentFacingMode },
});
- logger.debug("Camera access granted");
+ logger.debug(
+ "Camera access granted, stream tracks:",
+ stream.getTracks().map((t) => ({ kind: t.kind, label: t.label })),
+ );
this.cameraStream = stream;
this.cameraState = "active";
this.cameraStateMessage = "Camera is active";
@@ -623,20 +641,26 @@ export default class ImageMethodDialog extends Vue {
const videoElement = this.$refs.videoElement as HTMLVideoElement;
if (videoElement) {
+ logger.debug("Setting video element srcObject");
videoElement.srcObject = stream;
- await new Promise((resolve) => {
+ await new Promise((resolve, reject) => {
videoElement.onloadedmetadata = () => {
+ logger.debug("Video metadata loaded, starting playback");
videoElement
.play()
.then(() => {
- logger.debug("Video element started playing");
+ logger.debug("Video element started playing successfully");
resolve(true);
})
.catch((error) => {
logger.error("Error playing video:", error);
- throw error;
+ reject(error);
});
};
+ videoElement.onerror = (error) => {
+ logger.error("Video element error:", error);
+ reject(new Error("Video element error"));
+ };
});
} else {
logger.error("Video element not found");
@@ -672,14 +696,31 @@ export default class ImageMethodDialog extends Vue {
}
stopCameraPreview() {
+ logger.debug("stopCameraPreview called");
+
if (this.cameraStream) {
- this.cameraStream.getTracks().forEach((track) => track.stop());
+ logger.debug("Stopping camera stream tracks");
+ this.cameraStream.getTracks().forEach((track) => {
+ track.stop();
+ logger.debug(`Stopped track: ${track.kind} - ${track.label}`);
+ });
this.cameraStream = null;
}
+
+ // Clear video element srcObject to ensure cleanup
+ const videoElement = this.$refs.videoElement as HTMLVideoElement;
+ if (videoElement) {
+ videoElement.srcObject = null;
+ logger.debug("Cleared video element srcObject");
+ }
+
this.showCameraPreview = false;
this.cameraState = "off";
this.cameraStateMessage = "Camera stopped";
this.error = null;
+ // Don't reset isRetrying here - let the calling method handle it
+
+ logger.debug("Camera preview stopped and cleaned up");
}
async capturePhoto() {
@@ -746,10 +787,42 @@ export default class ImageMethodDialog extends Vue {
}
async retryImage() {
+ // Set retry flag FIRST to prevent error message flash
+ this.isRetrying = true;
+
this.blob = undefined;
- if (!this.platformCapabilities.isNativeApp) {
- await this.startCameraPreview();
+ this.showRetry = true;
+
+ // Stop camera stream but keep showCameraPreview true to prevent error flash
+ if (this.cameraStream) {
+ logger.debug("Stopping camera stream tracks");
+ this.cameraStream.getTracks().forEach((track) => {
+ track.stop();
+ logger.debug(`Stopped track: ${track.kind} - ${track.label}`);
+ });
+ this.cameraStream = null;
}
+
+ // Clear video element srcObject to ensure cleanup
+ const videoElement = this.$refs.videoElement as HTMLVideoElement;
+ if (videoElement) {
+ videoElement.srcObject = null;
+ logger.debug("Cleared video element srcObject");
+ }
+
+ this.cameraState = "off";
+ this.cameraStateMessage = "Camera stopped";
+ this.error = null;
+
+ // Add a small delay to ensure cleanup is complete
+ await new Promise((resolve) => setTimeout(resolve, 100));
+
+ // For native apps (iOS/Android), we need to restart the camera preview
+ // For web browsers, we also restart the camera preview
+ await this.startCameraPreview();
+
+ // Reset retry flag after camera is started
+ this.isRetrying = false;
}
async uploadImage() {