-
Notifications
You must be signed in to change notification settings - Fork 6
/
qr-reader.js
132 lines (118 loc) · 3.26 KB
/
qr-reader.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
const {
HTMLElement,
BarcodeDetector
} = window
export default class QRReader extends HTMLElement {
#initialized = false
#barcodeDetector = new BarcodeDetector({ formats: ['qr_code'] })
#timer = 0
#interval = null
video = null
canvas = null
constructor () {
super()
// method bindings
this._defineProperty = this._defineProperty.bind(this)
this.detectCode = this.detectCode.bind(this)
// Shadow DOM
this.attachShadow({ mode: 'open' })
// Define Properties
Object.keys(QRReader.defaultAttributes).map(this._defineProperty)
}
static get defaultAttributes () {
return {
scanInterval: 100,
debounceTime: 2000
}
}
static get observedAttributes () {
return Object.keys(QRReader.defaultAttributes)
}
// LifeCycle Callbacks
//
connectedCallback () {
if (!this.#initialized) {
this.initialize()
this.#initialized = true
}
}
disconnectedCallback () {
clearInterval(this.#interval)
this.#interval = null
if (this.video.srcObject) {
const tracks = this.video.srcObject.getTracks()
for (const track of tracks) {
track.stop()
}
this.video.srcObject = null
}
}
attributeChangedCallback (attributeName, oldValue, newValue) {
const fn = this[attributeName + 'Changed']
if (fn && typeof fn === 'function') {
fn.call(this, oldValue, newValue)
}
}
// Methods
//
_defineProperty (attributeName) {
Object.defineProperty(this, attributeName, {
get: () => {
const value = this.getAttribute(attributeName)
return value === null ? QRReader.defaultAttributes[attributeName] : value
},
set: value => {
this.setAttribute(attributeName, value)
}
})
}
async initialize () {
// Create dom elements
const div = document.createElement('div')
div.style = 'position: relative;'
this.canvas = document.createElement('canvas')
this.canvas.style = 'position: absolute; z-index: 1; width: 100%; height: 100%;'
this.video = document.createElement('video')
this.video.style = 'width: 100%; height: 100%;'
this.video.setAttribute('part', 'video')
this.video.autoplay = true
div.appendChild(this.canvas)
div.appendChild(this.video)
this.shadowRoot.appendChild(div)
// Start video stream
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
this.video.addEventListener('loadeddata', event => {
this.#interval = setInterval(this.detectCode, this.scanInterval)
})
this.video.srcObject = await navigator.mediaDevices.getUserMedia({
video: true,
audio: false
})
}
}
async detectCode () {
try {
const codes = await this.#barcodeDetector.detect(this.video)
if (codes.length === 0) {
return this.paintFrame()
}
for (const code of codes) {
this.paintFrame(code)
if (Date.now() - this.#timer > this.debounceTime) {
this.dispatchEvent(
new CustomEvent('read', {
bubbles: true,
detail: code.rawValue
})
)
this.#timer = Date.now()
}
}
} catch (e) {
console.error(e)
}
}
paintFrame ({ cornerPoints } = {}) {
// Not yet implemented
}
}