forked from trent_larson/crowd-funder-for-time-pwa
Fix: image retry in mobile app displays error
This commit is contained in:
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
<!-- FEEDBACK: Show if camera preview is not visible after mounting -->
|
<!-- FEEDBACK: Show if camera preview is not visible after mounting -->
|
||||||
<div
|
<div
|
||||||
v-if="!showCameraPreview && !blob && isRegistered"
|
v-if="!showCameraPreview && !blob && isRegistered && !isRetrying"
|
||||||
class="bg-red-100 text-red-700 border border-red-400 rounded px-4 py-3 my-4 text-sm"
|
class="bg-red-100 text-red-700 border border-red-400 rounded px-4 py-3 my-4 text-sm"
|
||||||
>
|
>
|
||||||
<strong>Camera preview not started.</strong>
|
<strong>Camera preview not started.</strong>
|
||||||
@@ -328,6 +328,9 @@ export default class ImageMethodDialog extends Vue {
|
|||||||
/** Whether to show camera preview */
|
/** Whether to show camera preview */
|
||||||
showCameraPreview = false;
|
showCameraPreview = false;
|
||||||
|
|
||||||
|
/** Whether currently retrying camera preview */
|
||||||
|
isRetrying = false;
|
||||||
|
|
||||||
/** Camera stream reference */
|
/** Camera stream reference */
|
||||||
private cameraStream: MediaStream | null = null;
|
private cameraStream: MediaStream | null = null;
|
||||||
|
|
||||||
@@ -589,6 +592,7 @@ export default class ImageMethodDialog extends Vue {
|
|||||||
}
|
}
|
||||||
this.blob = undefined;
|
this.blob = undefined;
|
||||||
this.showCameraPreview = false;
|
this.showCameraPreview = false;
|
||||||
|
this.isRetrying = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async startCameraPreview() {
|
async startCameraPreview() {
|
||||||
@@ -600,6 +604,13 @@ export default class ImageMethodDialog extends Vue {
|
|||||||
"getUserMedia available:",
|
"getUserMedia available:",
|
||||||
!!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),
|
!!(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 {
|
try {
|
||||||
this.cameraState = "initializing";
|
this.cameraState = "initializing";
|
||||||
@@ -611,10 +622,17 @@ export default class ImageMethodDialog extends Vue {
|
|||||||
throw new Error("Camera API not available in this browser");
|
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({
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
video: { facingMode: this.currentFacingMode },
|
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.cameraStream = stream;
|
||||||
this.cameraState = "active";
|
this.cameraState = "active";
|
||||||
this.cameraStateMessage = "Camera is active";
|
this.cameraStateMessage = "Camera is active";
|
||||||
@@ -623,20 +641,26 @@ export default class ImageMethodDialog extends Vue {
|
|||||||
|
|
||||||
const videoElement = this.$refs.videoElement as HTMLVideoElement;
|
const videoElement = this.$refs.videoElement as HTMLVideoElement;
|
||||||
if (videoElement) {
|
if (videoElement) {
|
||||||
|
logger.debug("Setting video element srcObject");
|
||||||
videoElement.srcObject = stream;
|
videoElement.srcObject = stream;
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve, reject) => {
|
||||||
videoElement.onloadedmetadata = () => {
|
videoElement.onloadedmetadata = () => {
|
||||||
|
logger.debug("Video metadata loaded, starting playback");
|
||||||
videoElement
|
videoElement
|
||||||
.play()
|
.play()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.debug("Video element started playing");
|
logger.debug("Video element started playing successfully");
|
||||||
resolve(true);
|
resolve(true);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error("Error playing video:", 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 {
|
} else {
|
||||||
logger.error("Video element not found");
|
logger.error("Video element not found");
|
||||||
@@ -672,14 +696,31 @@ export default class ImageMethodDialog extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stopCameraPreview() {
|
stopCameraPreview() {
|
||||||
|
logger.debug("stopCameraPreview called");
|
||||||
|
|
||||||
if (this.cameraStream) {
|
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;
|
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.showCameraPreview = false;
|
||||||
this.cameraState = "off";
|
this.cameraState = "off";
|
||||||
this.cameraStateMessage = "Camera stopped";
|
this.cameraStateMessage = "Camera stopped";
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
// Don't reset isRetrying here - let the calling method handle it
|
||||||
|
|
||||||
|
logger.debug("Camera preview stopped and cleaned up");
|
||||||
}
|
}
|
||||||
|
|
||||||
async capturePhoto() {
|
async capturePhoto() {
|
||||||
@@ -746,10 +787,42 @@ export default class ImageMethodDialog extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async retryImage() {
|
async retryImage() {
|
||||||
|
// Set retry flag FIRST to prevent error message flash
|
||||||
|
this.isRetrying = true;
|
||||||
|
|
||||||
this.blob = undefined;
|
this.blob = undefined;
|
||||||
if (!this.platformCapabilities.isNativeApp) {
|
this.showRetry = true;
|
||||||
await this.startCameraPreview();
|
|
||||||
|
// 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() {
|
async uploadImage() {
|
||||||
|
|||||||
Reference in New Issue
Block a user