2
2
import { binaryToBlock , createDecoder } from ' ~~/utils/lt-code'
3
3
import { readFileHeaderMetaFromBuffer } from ' ~~/utils/lt-code/binary-meta'
4
4
import { toUint8Array } from ' js-base64'
5
- import { scan } from ' qr-scanner-wechat '
5
+ import QrScanner from ' qr-scanner'
6
6
import { useBytesRate } from ' ~/composables/timeseries'
7
7
8
8
const props = withDefaults (defineProps <{
9
- speed? : number
10
- width? : number
11
- height? : number
9
+ maxScansPerSecond? : number
12
10
}>(), {
13
- speed: 34 ,
14
- width: 1080 ,
15
- height: 1080 ,
11
+ maxScansPerSecond: 30 ,
16
12
})
17
13
18
14
enum CameraSignalStatus {
@@ -65,8 +61,7 @@ watch(cameras, () => {
65
61
66
62
// const results = defineModel<Set<string>>('results', { default: new Set() })
67
63
68
- let stream: MediaStream | undefined
69
-
64
+ let qrScanner: QrScanner | undefined
70
65
let timestamp = 0
71
66
const fps = ref (0 )
72
67
function setFps() {
@@ -78,52 +73,75 @@ function setFps() {
78
73
const error = ref <any >()
79
74
const shutterCount = ref (0 )
80
75
const video = shallowRef <HTMLVideoElement >()
76
+ const videoWidth = ref (0 )
77
+ const videoHeight = ref (0 )
81
78
82
79
onMounted (async () => {
83
- watch ([() => props .width , () => props .height , selectedCamera ], () => {
84
- disconnectCamera ()
85
- connectCamera ()
86
- }, { immediate: true })
87
-
88
- useIntervalFn (
89
- async () => {
80
+ watch (() => props .maxScansPerSecond , async (maxScansPerSecond ) => {
81
+ if (qrScanner ) {
82
+ qrScanner .destroy ()
83
+ await new Promise (resolve => setTimeout (resolve , 1000 ))
84
+ }
85
+ qrScanner = new QrScanner (video .value ! , async (result ) => {
90
86
try {
91
- await scanFrame ()
87
+ await scanFrame (result )
92
88
}
93
89
catch (e ) {
94
90
error .value = e
95
91
console .error (e )
96
92
}
97
- },
98
- () => props .speed ,
99
- )
93
+ }, {
94
+ maxScansPerSecond ,
95
+ highlightCodeOutline: false ,
96
+ highlightScanRegion: true ,
97
+ calculateScanRegion : ({ videoHeight , videoWidth }) => {
98
+ const size = Math .min (videoWidth , videoHeight )
99
+ return {
100
+ x: size === videoWidth ? 0 : (videoWidth - size ) / 2 ,
101
+ y: size === videoHeight ? 0 : (videoHeight - size ) / 2 ,
102
+ width: size ,
103
+ height: size ,
104
+ }
105
+ },
106
+ preferredCamera: selectedCamera .value ,
107
+ onDecodeError(e ) {
108
+ if (e .toString () !== ' No QR code found' )
109
+ error .value = e
110
+ },
111
+ })
112
+ selectedCamera .value && qrScanner .setCamera (selectedCamera .value )
113
+ qrScanner .setInversionMode (' both' )
114
+ qrScanner .start ()
115
+ updateCameraStatus ()
116
+ }, { immediate: true })
117
+ watch (selectedCamera , () => {
118
+ if (qrScanner && selectedCamera .value ) {
119
+ qrScanner .setCamera (selectedCamera .value )
120
+ qrScanner .start ()
121
+ }
122
+ })
123
+ useIntervalFn (() => {
124
+ updateCameraStatus ()
125
+ }, 1000 )
100
126
})
127
+ onUnmounted (() => qrScanner && qrScanner .destroy ())
101
128
102
- function disconnectCamera() {
103
- stream ?.getTracks ().forEach (track => track .stop ())
104
- stream = undefined
105
- }
106
-
107
- async function connectCamera() {
129
+ async function updateCameraStatus() {
108
130
try {
109
131
if (! (navigator && ' mediaDevices' in navigator && ' getUserMedia' in navigator .mediaDevices && typeof navigator .mediaDevices .getUserMedia === ' function' )) {
110
132
cameraSignalStatus .value = CameraSignalStatus .NotSupported
111
133
return
112
134
}
113
135
114
- cameraSignalStatus .value = CameraSignalStatus .Waiting
136
+ videoHeight .value = video .value ! .videoHeight
137
+ videoWidth .value = video .value ! .videoWidth
115
138
116
- stream = await navigator .mediaDevices .getUserMedia ({
117
- audio: false ,
118
- video: {
119
- width: props .width ,
120
- height: props .height ,
121
- deviceId: selectedCamera .value ,
122
- },
123
- })
139
+ if (videoWidth .value === 0 || videoHeight .value === 0 ) {
140
+ cameraSignalStatus .value = CameraSignalStatus .Waiting
141
+ return
142
+ }
124
143
125
- video .value ! .srcObject = stream
126
- video .value ! .play ()
144
+ cameraSignalStatus .value = CameraSignalStatus .Ready
127
145
}
128
146
catch (e ) {
129
147
console .error (e )
@@ -209,39 +227,24 @@ function toDataURL(data: Uint8Array | string | any, type: string): string {
209
227
}
210
228
}
211
229
212
- async function scanFrame() {
213
- if (cameraSignalStatus .value === CameraSignalStatus .NotGranted
214
- || cameraSignalStatus .value === CameraSignalStatus .NotSupported ) {
215
- return
216
- }
217
-
230
+ async function scanFrame(result : QrScanner .ScanResult ) {
218
231
shutterCount .value += 1
219
- const canvas = document .createElement (' canvas' )
220
- canvas .width = video .value ! .videoWidth
221
- canvas .height = video .value ! .videoHeight
222
- if (video .value ! .videoWidth === 0 || video .value ! .videoHeight === 0 ) {
223
- cameraSignalStatus .value = CameraSignalStatus .Waiting
224
- return
225
- }
226
232
227
233
cameraSignalStatus .value = CameraSignalStatus .Ready
228
- const ctx = canvas .getContext (' 2d' )!
229
- ctx .drawImage (video .value ! , 0 , 0 , canvas .width , canvas .height )
230
234
231
- const result = await scan (canvas )
232
- if (! result .text )
235
+ if (! result .data )
233
236
return
234
237
235
- setFps ()
236
- bytesReceived .value += result .text .length
238
+ bytesReceived .value += result .data .length
237
239
totalValidBytesReceived .value = decoder .value .encodedCount * (decoder .value .meta ?.data .length ?? 0 )
238
240
239
241
// Do not process the same QR code twice
240
- if (cached .has (result .text ))
242
+ if (cached .has (result .data ))
241
243
return
244
+ setFps ()
242
245
243
246
error .value = undefined
244
- const binary = toUint8Array (result .text )
247
+ const binary = toUint8Array (result .data )
245
248
const data = binaryToBlock (binary )
246
249
// Data set changed, reset decoder
247
250
if (checksum .value !== data .checksum ) {
@@ -258,7 +261,7 @@ async function scanFrame() {
258
261
return
259
262
}
260
263
261
- cached .add (result .text )
264
+ cached .add (result .data )
262
265
k .value = data .k
263
266
264
267
data .indices .map (i => pluse (i ))
@@ -385,11 +388,11 @@ function now() {
385
388
/>
386
389
</div >
387
390
388
- <div relative h-full max-h-150 max-w-150 w-full text =" 10px md:sm" >
391
+ <div relative max-w-150 w-full text =" 10px md:sm" >
389
392
<video
390
393
ref =" video"
391
- autoplay muted playsinline :controls =" false"
392
- aspect-square h-full w-full rounded-lg
394
+ :controls =" false"
395
+ autoplay muted playsinline h-full w-full rounded-lg
393
396
/>
394
397
395
398
<div absolute left-1 top-1 border =" ~ gray:50 rounded-md" bg-black:75 px2 py1 text-white font-mono shadow >
0 commit comments