1
+ import { html , css , LitElement } from '../../node_modules/lit-element' ;
2
+
3
+ import '../../node_modules/@material/mwc-icon' ;
4
+ import '../../node_modules/@material/mwc-ripple' ;
5
+
6
+ import './settings-pane.js' ;
7
+
8
+ class CameraCapture extends LitElement {
9
+ static styles = css `
10
+ :host {
11
+ display: block;
12
+ width: 100vw;
13
+ height: 100vh;
14
+ overflow: hidden;
15
+ }
16
+
17
+ .hidden {
18
+ display: none !important;
19
+ }
20
+
21
+ #mainContent {
22
+ margin: 0 auto;
23
+ }
24
+
25
+ video {
26
+ display: inline;
27
+ }
28
+
29
+ video:hover {
30
+ cursor: pointer;
31
+ }
32
+
33
+ #resetButton {
34
+ position: relative;
35
+ padding: 15px;
36
+ color: white;
37
+ text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
38
+ font-size: 18px;
39
+ background: none;
40
+ border: none;
41
+ z-index: 15;
42
+ outline: none;
43
+ }
44
+
45
+ .canvas-wrapper {
46
+ position: relative;
47
+ width: 100%;
48
+ }
49
+
50
+ .settings-wrapper {
51
+ position: absolute;
52
+ top: 0;
53
+ width: 100%;
54
+ height: 100%;
55
+ display: flex;
56
+ flex-direction: column;
57
+ justify-content: center;
58
+ align-items: flex-end;
59
+ }
60
+
61
+ #cameraBar {
62
+ height: 100px;
63
+ background-color: transparent;
64
+ display: flex;
65
+ flex-direction: row;
66
+ flex-wrap: nowrap;
67
+ justify-content: space-around;
68
+ align-items: center;
69
+ }
70
+
71
+ #gallery {
72
+ width: 48px;
73
+ height: 48px;
74
+ }
75
+
76
+ #takePhotoButton {
77
+ height: 72px;
78
+ width: 72px;
79
+ }
80
+
81
+ #takePhotoButton mwc-icon {
82
+ --mdc-icon-size: 48px;
83
+ }
84
+
85
+ .camera-bar-icon {
86
+ height: 52px;
87
+ width: 52px;
88
+ display: flex;
89
+ justify-content: center;
90
+ outline: none;
91
+ border: 2px solid white;
92
+ border-radius: 52px;
93
+ color: white;
94
+ background-color: transparent;
95
+ }
96
+ ` ;
97
+
98
+ facingMode = "user" ;
99
+
100
+ _onResetClicked ( e ) {
101
+ const settings = this . shadowRoot . querySelector ( 'settings-pane' ) ;
102
+ settings . reset ( ) ;
103
+
104
+ const resetButton = this . shadowRoot . querySelector ( '#resetButton' ) ;
105
+ resetButton . classList . add ( 'hidden' ) ;
106
+ }
107
+
108
+ async _onConstraintsChange ( e ) {
109
+ try {
110
+ await this . videoTrack . applyConstraints ( e . detail . constraints ) ;
111
+
112
+ const settings = this . shadowRoot . querySelector ( 'settings-pane' ) ;
113
+ settings . applyFromTrack ( this . videoTrack ) ;
114
+ } catch ( err ) {
115
+ console . error ( err ) ;
116
+ }
117
+
118
+ const resetButton = this . shadowRoot . querySelector ( '#resetButton' ) ;
119
+ resetButton . classList . remove ( 'hidden' ) ;
120
+ }
121
+
122
+ _onSettingsBackgroundClicked ( e ) {
123
+ const settings = this . shadowRoot . querySelector ( 'settings-pane' ) ;
124
+ settings . hide ( ) ;
125
+
126
+ const resetButton = this . shadowRoot . querySelector ( '#resetButton' ) ;
127
+ resetButton . classList . add ( 'hidden' ) ;
128
+ }
129
+
130
+ async _onFacingModeClicked ( ) {
131
+ this . selectedCamera = ( this . selectedCamera + 1 ) % this . cameras . length ;
132
+ const camera = this . cameras [ this . selectedCamera ] ;
133
+ this . constraints . deviceId = { exact : camera . deviceId } ;
134
+ this . facingMode = this . getFacingMode ( camera ) ;
135
+ this . requestUpdate ( ) ;
136
+
137
+ this . stopCamera ( ) ;
138
+
139
+ const videoElement = this . shadowRoot . querySelector ( 'video' ) ;
140
+ await this . startCamera ( videoElement , this . constraints ) ;
141
+
142
+ // Timeout needed in Chrome, see https://crbug.com/711524.
143
+ const settings = this . shadowRoot . querySelector ( 'settings-pane' ) ;
144
+ setTimeout ( async ( ) => {
145
+ await customElements . whenDefined ( 'settings-pane' ) ;
146
+ settings . applyFromTrack ( this . videoTrack ) ;
147
+ } , 500 ) ;
148
+ }
149
+
150
+ async takePhoto ( ) {
151
+ try {
152
+ const blob = await this . imageCapturer . takePhoto ( ) ;
153
+ const img = await createImageBitmap ( blob ) ;
154
+
155
+ const canvas = this . shadowRoot . querySelector ( '#gallery' ) ;
156
+ canvas . width = getComputedStyle ( canvas ) . width . split ( 'px' ) [ 0 ] ;
157
+ canvas . height = getComputedStyle ( canvas ) . height . split ( 'px' ) [ 0 ] ;
158
+ let ratio = Math . max ( canvas . width / img . width , canvas . height / img . height ) ;
159
+ let x = ( canvas . width - img . width * ratio ) / 2 ;
160
+ let y = ( canvas . height - img . height * ratio ) / 2 ;
161
+ canvas . getContext ( '2d' ) . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
162
+ canvas . getContext ( '2d' ) . drawImage ( img , 0 , 0 , img . width , img . height ,
163
+ x , y , img . width * ratio , img . height * ratio ) ;
164
+ } catch ( err ) {
165
+ console . error ( "takePhoto() failed: " , err )
166
+ }
167
+ }
168
+
169
+ startCamera ( target , constraints ) {
170
+ return new Promise ( async ( resolve , reject ) => {
171
+ try {
172
+ const stream = await navigator . mediaDevices . getUserMedia ( {
173
+ video : constraints ,
174
+ audio : false
175
+ } ) ;
176
+
177
+ this . video = target ;
178
+ this . stream = stream ;
179
+
180
+ target . srcObject = stream ;
181
+ target . addEventListener ( 'canplay' , resolve , { once : true } ) ;
182
+ target . play ( ) ;
183
+ } catch ( err ) {
184
+ reject ( err ) ;
185
+ }
186
+ } ) ;
187
+ }
188
+
189
+ stopCamera ( ) {
190
+ if ( this . video ) {
191
+ this . video . pause ( ) ;
192
+ this . video . srcObject = null ;
193
+ }
194
+ if ( this . stream ) {
195
+ this . stream . getVideoTracks ( ) [ 0 ] . stop ( ) ;
196
+ }
197
+ }
198
+
199
+ getFacingMode ( device ) {
200
+ if ( device . facingMode == "environment"
201
+ || device . label . indexOf ( "facing back" ) >= 0 ) {
202
+ return "environment" ;
203
+ }
204
+ // We assume by default that cameras are user facing
205
+ // which is mostly the case for desktop.
206
+ return "user" ;
207
+ }
208
+
209
+ async firstUpdated ( ) {
210
+ this . constraints = { } ;
211
+
212
+ const devices = await navigator . mediaDevices . enumerateDevices ( ) ;
213
+
214
+ this . cameras = [ ] ;
215
+ this . selectedCamera = 0 ;
216
+
217
+ devices . forEach ( device => {
218
+ if ( device . kind == 'videoinput' ) {
219
+ if ( this . getFacingMode ( device ) == "user" ) {
220
+ this . cameras . push ( device ) ;
221
+ } else {
222
+ this . cameras . unshift ( device ) ;
223
+ }
224
+ }
225
+ } ) ;
226
+
227
+ // Android bug, doesn't work with DevTools emulation.
228
+ if ( navigator . userAgent . includes ( "Android" ) &&
229
+ screen . orientation . type . includes ( "portrait" ) ) {
230
+ this . constraints . width = Math . ceil ( visualViewport . height ) ;
231
+ this . constraints . height = Math . ceil ( visualViewport . width ) ;
232
+ } else {
233
+ this . constraints . width = Math . ceil ( visualViewport . width ) ;
234
+ this . constraints . height = Math . ceil ( visualViewport . height ) ;
235
+ }
236
+
237
+ // Disable facingModeButton if there is no environment or user mode.
238
+ let facingModeButton = this . shadowRoot . getElementById ( 'facingModeButton' ) ;
239
+ if ( this . cameras . length < 2 ) {
240
+ facingModeButton . style . color = 'gray' ;
241
+ facingModeButton . style . border = '2px solid gray' ;
242
+ } else {
243
+ facingModeButton . disabled = false ;
244
+ }
245
+
246
+ this . facingMode = this . getFacingMode ( this . cameras [ 0 ] ) ;
247
+ this . requestUpdate ( ) ;
248
+ this . constraints . deviceId = { exact : this . cameras [ 0 ] . deviceId } ;
249
+
250
+ const videoElement = this . shadowRoot . querySelector ( 'video' ) ;
251
+
252
+ await this . startCamera ( videoElement , this . constraints ) ;
253
+
254
+ this . videoTrack = videoElement . srcObject . getVideoTracks ( ) [ 0 ] ;
255
+ this . imageCapturer = new ImageCapture ( this . videoTrack ) ;
256
+
257
+ let cameraBar = this . shadowRoot . querySelector ( '#cameraBar' ) ;
258
+ cameraBar . style . width = `${ videoElement . videoWidth } px` ;
259
+
260
+ let mainContent = this . shadowRoot . getElementById ( 'mainContent' ) ;
261
+ mainContent . style . width = `${ videoElement . videoWidth } px` ;
262
+ mainContent . classList . remove ( 'hidden' ) ;
263
+
264
+ this . shadowRoot . querySelector ( '.canvas-wrapper' ) . style . height =
265
+ `${ videoElement . videoHeight } px` ;
266
+
267
+ let resetButton = this . shadowRoot . querySelector ( '#resetButton' ) ;
268
+ resetButton . classList . remove ( 'hidden' ) ;
269
+ resetButton . style . left = `${ videoElement . videoWidth - resetButton . offsetWidth } px` ;
270
+ resetButton . style . bottom = `${ videoElement . videoHeight } px` ;
271
+ resetButton . classList . add ( 'hidden' ) ;
272
+
273
+ this . shadowRoot . getElementById ( 'takePhotoButton' ) . disabled = false ;
274
+
275
+ // Timeout needed in Chrome, see https://crbug.com/711524.
276
+ const settings = this . shadowRoot . querySelector ( 'settings-pane' ) ;
277
+ setTimeout ( async ( ) => {
278
+ await customElements . whenDefined ( 'settings-pane' ) ;
279
+ settings . applyFromTrack ( this . videoTrack ) ;
280
+ } , 500 ) ;
281
+ }
282
+
283
+ render ( ) {
284
+ return html `
285
+ < div id ="mainContent " class ="centered hidden ">
286
+ < div class ="canvas-wrapper ">
287
+ < video id ="videoInput "> </ video >
288
+
289
+ < button id ="resetButton " class ='hidden ' @click =${ this . _onResetClicked } >
290
+ Reset
291
+ </ button >
292
+
293
+ < div class ="settings-wrapper ">
294
+ < settings-pane
295
+ @click =${ this . _onSettingsBackgroundClicked }
296
+ @constraintschange =${ this . _onConstraintsChange } >
297
+ </ settings-pane >
298
+
299
+ < div id ="cameraBar ">
300
+ < canvas id ="gallery " class ="camera-bar-icon "> </ canvas >
301
+ < div >
302
+ < button id ="takePhotoButton " class ="camera-bar-icon " disabled
303
+ @click =${ this . takePhoto } >
304
+ < mwc-icon > photo_camera</ mwc-icon >
305
+ </ button >
306
+ < mwc-ripple unbounded > </ mwc-ripple >
307
+ </ div >
308
+ < button id ="facingModeButton " class ="camera-bar-icon " disabled
309
+ @click =${ this . _onFacingModeClicked } >
310
+ < mwc-icon > ${ this . facingMode === "user" ? "camera_front" : "camera_rear" } </ mwc-icon >
311
+ </ button >
312
+ </ div >
313
+ </ div >
314
+ </ div >
315
+ </ div >
316
+ ` ;
317
+ }
318
+ }
319
+
320
+ customElements . define ( 'camera-capture' , CameraCapture ) ;
0 commit comments