Created
October 30, 2020 19:18
-
-
Save chidindu-ogbonna/5be0c41f7618eb72e99de18e12665c8b to your computer and use it in GitHub Desktop.
Control the camera in vue (nuxtjs) component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div class="camera__layout"> | |
<main class="camera__layout-content"> | |
<video ref="video" autoplay></video> | |
<input | |
ref="fileExp" | |
class="hidden" | |
type="file" | |
accept="image/*" | |
@change="photoSelected" | |
/> | |
<div ref="frame" class="opacity-0 img-container"> | |
<img :src="selectedPhoto" alt="" /> | |
<div v-if="processing" class="absolute"> | |
<app-loader></app-loader> | |
</div> | |
</div> | |
<canvas ref="canvas" class="hidden"></canvas> | |
</main> | |
<div | |
ref="flash" | |
class="fixed top-0 left-0 hidden w-full h-full transition-all duration-200 ease-out bg-white" | |
></div> | |
<camera-controls | |
@close-camera="closeCamera" | |
@take-picture="takePicture" | |
@open-photos="openPhotos" | |
></camera-controls> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: 'Camera', | |
data() { | |
return { | |
ctx: null, | |
label: '', | |
video: null, | |
imageURL: '', | |
isIOS: false, | |
canvas: null, | |
webcam: null, | |
streaming: false, | |
processing: false, | |
selectedPhoto: '', | |
isMediaStreamAPISupported: false, | |
finishAudio: null, | |
snapAudio: null, | |
infoAudio: null, | |
} | |
}, | |
watch: { | |
processing(value) { | |
if (value) { | |
setTimeout(() => { | |
if (this.finishAudio) this.finishAudio.play() | |
this.processing = false | |
}, 4000) | |
} | |
}, | |
}, | |
async mounted() { | |
this.isIOS = ['iPad', 'iPhone', 'iPod'].includes(navigator.platform) | |
this.isMediaStreamAPISupported = navigator && navigator.mediaDevices | |
await this.init() | |
this.finishAudio = new Audio('/audio/finish.mp3') | |
this.snapAudio = new Audio('/audio/snap.mp3') | |
this.$on('hook:beforeDestroy', () => { | |
if (this.webcam) { | |
this.webcam.removeEventListener('play', this.playHandler) | |
if (this.webcam.srcObject) this.webcam.srcObject.getTracks()[0].stop() | |
} | |
}) | |
}, | |
methods: { | |
async init() { | |
this.webcam = this.$refs.video | |
this.setCanvas() | |
try { | |
if (this.isMediaStreamAPISupported) { | |
this.webcam.addEventListener('play', this.playHandler, false) | |
const devices = await navigator.mediaDevices.enumerateDevices() | |
const videoDevices = devices.filter((d) => d.kind === 'videoinput') | |
await this.startCapture(videoDevices) | |
} else { | |
this.$notify({ title: 'Device Not Supported' }) | |
this.$store.dispatch('log/error', { | |
fatal: false, | |
action: 'not_supported', | |
}) | |
} | |
} catch (error) { | |
// console.log('Error: ', error.name, error.message) | |
this.$notify({ title: 'Unable to Access Camera' }) | |
this.$store.dispatch('log/error', { | |
fatal: true, | |
action: 'camera_init', | |
error, | |
}) | |
} | |
}, | |
playHandler() { | |
if (!this.streaming) { | |
this.setCanvasProps() | |
this.streaming = true | |
} | |
}, | |
/** | |
* @param {Array<MediaDeviceInfo>} devices | |
*/ | |
async startCapture(devices) { | |
const constraints = { | |
video: {}, | |
audio: false, | |
} | |
if (devices.length > 1) { | |
const device = devices[devices.length - 1].deviceId | |
constraints.video.mandatory = { sourceId: device || null } | |
if (this.isIOS) { | |
constraints.video.facingMode = 'environment' | |
} | |
await this.setMediaStream(constraints) | |
} else if (devices.length) { | |
const device = devices[0].deviceId | |
constraints.video.mandatory = { sourceId: device || null } | |
if (this.isIOS) { | |
constraints.video.facingMode = 'environment' | |
} | |
if (!constraints.video.mandatory.sourceId && !this.isIOS) { | |
await this.setMediaStream({ video: true }) | |
} else { | |
await this.setMediaStream(constraints) | |
} | |
} else { | |
await this.setMediaStream({ video: true }) | |
} | |
}, | |
async setMediaStream(constraints) { | |
const stream = await navigator.mediaDevices.getUserMedia(constraints) | |
const track = stream.getTracks()[0] | |
this.label = `${track.id} - ${track.label}` | |
this.webcam.srcObject = stream | |
this.webcam.setAttribute('playsinline', true) | |
this.webcam.setAttribute('controls', true) | |
setTimeout(() => { | |
this.$refs.video.removeAttribute('controls') | |
}) | |
}, | |
setCanvas() { | |
this.canvas = this.$refs.canvas | |
this.ctx = this.canvas.getContext('2d') | |
}, | |
setCanvasProps() { | |
this.canvas.width = window.innerWidth | |
this.canvas.height = window.innerHeight | |
}, | |
takePicture() { | |
if (this.processing) return | |
try { | |
const width = this.canvas.width | |
const height = this.canvas.height | |
this.ctx.drawImage(this.webcam, 0, 0, width, height) | |
const imageURL = this.canvas.toDataURL('image/png') | |
this.flashScreen() | |
this.setImage(imageURL) | |
this.$store.dispatch('log/cameraEvent', { | |
action: 'camera_snap', | |
label: this.label, | |
}) | |
} catch (error) { | |
this.$store.dispatch('log/error', { | |
fatal: true, | |
action: 'camera_snap', | |
error, | |
}) | |
} | |
}, | |
closeCamera() { | |
this.$store.dispatch('log/cameraEvent', { | |
action: 'camera_close', | |
label: this.label, | |
}) | |
this.$router.push('/') | |
}, | |
openPhotos() { | |
if (this.processing) return | |
const fileBtn = this.$refs.fileExp | |
fileBtn.click() | |
this.$store.dispatch('log/cameraEvent', { | |
action: 'photos_open', | |
label: this.label, | |
}) | |
}, | |
photoSelected(event) { | |
if (event.target && event.target.files.length > 0) { | |
this.$store.dispatch('log/cameraEvent', { | |
action: 'photos_selected', | |
label: this.label, | |
}) | |
const imageURL = URL.createObjectURL(event.target.files[0]) | |
this.setImage(imageURL) | |
} | |
}, | |
setImage(url) { | |
this.frame = this.$refs.frame | |
this.frame.classList.replace('opacity-0', 'opacity-100') | |
this.selectedPhoto = url | |
this.processing = true | |
}, | |
flashScreen() { | |
this.flash = this.$refs.flash | |
this.flash.classList.toggle('hidden', false) | |
if (this.snapAudio) this.snapAudio.play() | |
setTimeout(() => { | |
this.flash.classList.toggle('hidden', true) | |
}, 200) | |
}, | |
}, | |
} | |
</script> | |
<style lang="scss"> | |
.camera__layout { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
overflow: hidden; | |
background-color: var(--dark-gray); | |
&-content { | |
height: inherit; | |
video { | |
transform: translateX(-50%) translateY(-50%); | |
top: 50%; | |
left: 50%; | |
max-width: none; | |
min-width: 100%; | |
min-height: 100%; | |
width: auto; | |
height: auto; | |
position: absolute; | |
} | |
} | |
} | |
.img-container { | |
position: fixed; | |
justify-content: center; | |
align-items: center; | |
display: flex; | |
top: 0; | |
bottom: 0; | |
right: 0; | |
left: 0; | |
transition: all 0.4s ease-in-out; | |
height: 100%; | |
width: 100%; | |
background: rgba(4, 12, 20, 0.75); | |
backdrop-filter: saturate(180%) blur(10px); | |
img { | |
width: auto; | |
height: auto; | |
margin: auto; | |
} | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment