From 4c8b6c5e9e6e65f6f27c7b127dde8bef2d28c3e9 Mon Sep 17 00:00:00 2001
From: Niklas Gruhn
Date: Tue, 18 Jul 2023 18:58:02 +0200
Subject: [PATCH] feat(QrcodeStream): throw timeout error when camera won't
load
On iOS devices in PWA mode, QrcodeStream works initially, but after
killing and restarting the PWA, all video elements fail to display
camera streams. Looks like this is related to this WebKit issue:
https://bugs.webkit.org/show_bug.cgi?id=252465
No workarounds at the time of writing. But as suggested in the
thread we can put a timeout on the video elements to start loading.
That way we can at least detect when the error occurs and throw an
error event.
Also this commit:
1. Makes the demo page PWA installable to reproduce this problem.
2. Renames the "DecodeAll" demo to "HandleErrors". The demo already
is focused on error handling because it renders the error message
instead of just printing to the console, which is particularly
useful on mobile devices. Renaming the demo emphasizes that and
hints user to use this demo for debugging.
See #298
---
.github/ISSUE_TEMPLATE/bug_report.md | 2 +-
.gitignore | 1 +
CONTRIBUTING.md | 4 +-
README.md | 10 +-
.../demos/{DecodeAll.vue => HandleErrors.vue} | 19 +-
.../components/demos/SwitchCamera.vue | 2 +-
docs/.vitepress/components/demos/Torch.vue | 2 +-
docs/.vitepress/config.ts | 45 +-
docs/demos/{DecodeAll.md => HandleErrors.md} | 8 +-
docs/public/pwa-192x192.png | Bin 0 -> 26062 bytes
docs/public/pwa-512x512.png | Bin 0 -> 39910 bytes
package.json | 4 +-
pnpm-lock.yaml | 2408 ++++++++++++++++-
src/components/QrcodeStream.vue | 153 +-
src/misc/callforth.ts | 10 +-
src/misc/camera.ts | 16 +-
src/misc/errors.ts | 8 +
vite.config.ts | 3 +-
18 files changed, 2568 insertions(+), 127 deletions(-)
rename docs/.vitepress/components/demos/{DecodeAll.vue => HandleErrors.vue} (63%)
rename docs/demos/{DecodeAll.md => HandleErrors.md} (58%)
create mode 100644 docs/public/pwa-192x192.png
create mode 100644 docs/public/pwa-512x512.png
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index e595d60a..822c55b1 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -8,7 +8,7 @@ assignees: ''
---
**Describe the bug**
-A clear and concise description of what the bug is. Can you reproduce this issue with [one of the demos](https://vue-qrcode-reader.netlify.app/demos/DecodeAll.html)?
+A clear and concise description of what the bug is. Can you reproduce this issue with [one of the demos](https://vue-qrcode-reader.netlify.app/demos/HandleErrors.html)?
**To Reproduce**
Please provide a link to a minimal reproduction of the bug. For example on jsFiddle, CodePen, etc. Please don't attach a ZIP file with your entire code base. I know this is additional effort but if it takes too much time to reproduce your issue you'll likely won't get help at all.
diff --git a/.gitignore b/.gitignore
index 824f247a..740aeb0d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -142,6 +142,7 @@ out
# Nuxt.js build / generate output
.nuxt
dist
+dev-dist
# Gatsby files
.cache/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fbcd6fdc..18342082 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,11 +9,11 @@ Clone the repository and run
npm install
```
-We use a locally served version of the [demo page](https://vue-qrcode-reader.netlify.app/demos/DecodeAll.html) during development.
+We use a locally served version of the [demo page](https://vue-qrcode-reader.netlify.app/) during development.
To get that started run
```
-npm run dev
+npm run docs:dev
```
### Commit Messages
diff --git a/README.md b/README.md
index 1a30d129..440fa331 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@
- live demos |
+ live demos |
api reference
@@ -147,9 +147,11 @@ Use kebab-case to reference them in your templates:
#### I don't see the camera when using `QrcodeStream`.
-- Check if it works on the demo page. Especially the [Decode All](https://vue-qrcode-reader.netlify.app/demos/DecodeAll.html) demo, since it renders error messages. If you see errors, consult the docs to understand their meaning.
- - The demo works but it doesn't work in my project: Listen for the `init` event to investigate errors.
- - The demo doesn't work: Carefully review the Browser Support section above. Maybe your device is just not supported.
+- Check if it works on the demo page. Especially the [Handle Errors](https://vue-qrcode-reader.netlify.app/demos/HandleErrors.html) demo,
+ since it renders error messages.
+ - The demo works but it doesn't work in my project: Listen for the `error` event to investigate errors.
+ - The demo doesn't work: Carefully review the Browser Support section above.
+ Maybe your device is just not supported.
#### I'm running a dev server on localhost. How to test on my mobile device without HTTPS?
diff --git a/docs/.vitepress/components/demos/DecodeAll.vue b/docs/.vitepress/components/demos/HandleErrors.vue
similarity index 63%
rename from docs/.vitepress/components/demos/DecodeAll.vue
rename to docs/.vitepress/components/demos/HandleErrors.vue
index 53270189..23991612 100644
--- a/docs/.vitepress/components/demos/DecodeAll.vue
+++ b/docs/.vitepress/components/demos/HandleErrors.vue
@@ -28,23 +28,24 @@ const onDetect = detectedCodes => {
}
const onError = err => {
+ error.value = `[${err.name}]: `
+
if (err.name === 'NotAllowedError') {
- error.value = 'ERROR: you need to grant camera access permission'
+ error.value += 'you need to grant camera access permission'
} else if (err.name === 'NotFoundError') {
- error.value = 'ERROR: no camera on this device'
+ error.value += 'no camera on this device'
} else if (err.name === 'NotSupportedError') {
- error.value = 'ERROR: secure context required (HTTPS, localhost)'
+ error.value += 'secure context required (HTTPS, localhost)'
} else if (err.name === 'NotReadableError') {
- error.value = 'ERROR: is the camera already in use?'
+ error.value += 'is the camera already in use?'
} else if (err.name === 'OverconstrainedError') {
- error.value = 'ERROR: installed cameras are not suitable'
+ error.value += 'installed cameras are not suitable'
} else if (err.name === 'StreamApiNotSupportedError') {
- error.value = 'ERROR: Stream API is not supported in this browser'
+ error.value += 'Stream API is not supported in this browser'
} else if (err.name === 'InsecureContextError') {
- error.value =
- 'ERROR: Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.'
+ error.value += 'Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.'
} else {
- error.value = `ERROR: Camera error (${err.name})`
+ error.value += err.message
}
}
diff --git a/docs/.vitepress/components/demos/SwitchCamera.vue b/docs/.vitepress/components/demos/SwitchCamera.vue
index a6618ce7..58ae0099 100644
--- a/docs/.vitepress/components/demos/SwitchCamera.vue
+++ b/docs/.vitepress/components/demos/SwitchCamera.vue
@@ -69,7 +69,7 @@ button {
top: 10px;
}
button img {
- with: 50px;
+ width: 50px;
height: 50px;
}
.error {
diff --git a/docs/.vitepress/components/demos/Torch.vue b/docs/.vitepress/components/demos/Torch.vue
index 480be052..250ab153 100644
--- a/docs/.vitepress/components/demos/Torch.vue
+++ b/docs/.vitepress/components/demos/Torch.vue
@@ -52,7 +52,7 @@ button {
top: 10px;
}
button img {
- with: 50px;
+ width: 50px;
height: 50px;
}
.error {
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 7bbae3d3..c0fc1fa9 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -1,7 +1,7 @@
import { defineConfig } from 'vitepress'
+import { withPwa } from '@vite-pwa/vitepress'
-export default defineConfig({
- // base: '/vue-qrcode-reader/',
+export default withPwa(defineConfig({
description: 'A set of Vue.js components for detecting and decoding QR codes.',
lang: 'en-US',
lastUpdated: true,
@@ -27,8 +27,8 @@ export default defineConfig({
link: '/demos/Simple'
},
{
- text: 'Decode Continuously',
- link: '/demos/DecodeAll'
+ text: 'Handle Errors',
+ link: '/demos/HandleErrors'
},
{
text: 'Visual Tracking',
@@ -105,5 +105,38 @@ export default defineConfig({
'@': __dirname
}
}
- }
-})
+ },
+ pwa: {
+ mode: 'development',
+ base: '/',
+ scope: '/',
+ registerType: 'autoUpdate',
+ // injectRegister: 'inline',
+ includeAssets: ['favicon.svg'],
+ manifest: {
+ name: 'Vue Qrcode Reader',
+ short_name: 'Vue QR',
+ theme_color: '#10b981',
+ icons: [
+ {
+ src: 'pwa-192x192.png',
+ sizes: '192x192',
+ type: 'image/png',
+ },
+ {
+ src: 'pwa-512x512.png',
+ sizes: '512x512',
+ type: 'image/png',
+ }
+ ],
+ },
+ workbox: {
+ globPatterns: ['**/*.{css,js,html,svg,png,ico,txt,woff2}'],
+ },
+ devOptions: {
+ enabled: true,
+ suppressWarnings: true,
+ navigateFallback: '/',
+ },
+ },
+}))
diff --git a/docs/demos/DecodeAll.md b/docs/demos/HandleErrors.md
similarity index 58%
rename from docs/demos/DecodeAll.md
rename to docs/demos/HandleErrors.md
index 18a8cd82..c3d9ee32 100644
--- a/docs/demos/DecodeAll.md
+++ b/docs/demos/HandleErrors.md
@@ -1,17 +1,17 @@
-# Decode Continuously
+# Handle Errors
Hold a QR code in the camera and see what happens. Note, you can't scan the same
QR code multiple time in a row.
-
+
### Source
-<<< @/.vitepress/components/demos/DecodeAll.vue
+<<< @/.vitepress/components/demos/HandleErrors.vue
diff --git a/docs/public/pwa-192x192.png b/docs/public/pwa-192x192.png
new file mode 100644
index 0000000000000000000000000000000000000000..cfda4297b6904e9f033fadc394a64d5b12094466
GIT binary patch
literal 26062
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