feat(mobile): add local QR scanning and desktop pairing invitations
This commit is contained in:
1 parent
c1929957a2
commit
4115f1b4c0
17 files changed
+178
-17
No files matched your search
@@ -0,0 +1,50 @@
|
||||
import qrcode from './vendor/qrcode.mjs'
|
||||
|
||||
export function drawQr(canvas, text) {
|
||||
const qr = qrcode(0, 'M'); qr.addData(text); qr.make()
|
||||
const count = qr.getModuleCount(), scale = 5, margin = 4
|
||||
canvas.width = canvas.height = (count + margin * 2) * scale
|
||||
const ctx = canvas.getContext('2d'); ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||
ctx.fillStyle = '#000'
|
||||
for (let y = 0; y < count; y++) for (let x = 0; x < count; x++)
|
||||
if (qr.isDark(y, x)) ctx.fillRect((x + margin) * scale, (y + margin) * scale, scale, scale)
|
||||
}
|
||||
|
||||
export class InviteScanner {
|
||||
constructor(video, onCode) { this.video = video; this.onCode = onCode; this.epoch = 0; this.canvas = document.createElement('canvas') }
|
||||
decode(source, width, height) {
|
||||
if (!width || !height) return null
|
||||
const scale = Math.min(1, 1280 / Math.max(width, height))
|
||||
this.canvas.width = Math.round(width * scale); this.canvas.height = Math.round(height * scale)
|
||||
const ctx = this.canvas.getContext('2d', { willReadFrequently: true })
|
||||
ctx.drawImage(source, 0, 0, this.canvas.width, this.canvas.height)
|
||||
const pixels = ctx.getImageData(0, 0, this.canvas.width, this.canvas.height)
|
||||
return globalThis.jsQR(pixels.data, pixels.width, pixels.height)?.data
|
||||
}
|
||||
async image(file) {
|
||||
this.stop(); const epoch = this.epoch
|
||||
if (!file || file.size > 8 * 1024 * 1024) throw new Error('QR resmi en fazla 8 MB olabilir.')
|
||||
const bitmap = await createImageBitmap(file)
|
||||
let result
|
||||
try { result = this.decode(bitmap, bitmap.width, bitmap.height) } finally { bitmap.close() }
|
||||
if (epoch !== this.epoch) return
|
||||
if (!result) throw new Error('Resimde QR okunamadı; daha net bir görüntü deneyin.')
|
||||
await this.onCode(result)
|
||||
}
|
||||
async start() {
|
||||
this.stop(); const epoch = this.epoch
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' }, audio: false })
|
||||
if (epoch !== this.epoch) { stream.getTracks().forEach(t => t.stop()); return }
|
||||
this.stream = stream; this.video.srcObject = stream; this.video.hidden = false
|
||||
try {
|
||||
await this.video.play()
|
||||
if (epoch !== this.epoch) return
|
||||
this.timer = setInterval(() => {
|
||||
const result = this.decode(this.video, this.video.videoWidth, this.video.videoHeight)
|
||||
if (result) { this.stop(); void this.onCode(result).catch(error => this.onError?.(error)) }
|
||||
}, 250)
|
||||
this.limit = setTimeout(() => this.stop(), 60000)
|
||||
} catch (error) { this.stop(); throw error }
|
||||
}
|
||||
stop() { this.epoch++; clearInterval(this.timer); clearTimeout(this.limit); this.stream?.getTracks().forEach(t => t.stop()); this.stream = null; this.video.srcObject = null; this.video.hidden = true }
|
||||
}
|
||||
Reference in new issue
Block a user