Menggunakan Kamera Pengguna di VueJs

Dalam pembuatan aplikasi, terkadang, kita memerlukan untuk mengakses kamera pengguna. Salah satu contohnya adalah ketika membuat fitur verifikasi ID seseorang yang memerlukan foto selfie dan foto KTP. Nah pada artikel kali ini akan diberikan contoh cara menggunakan kamera pengguna di VueJS menggunakan library VueUse.

Prasyarat

Beberapa dependensi yang dibutuhkan untuk mengikuti artikel ini adalah sebagai berikut:

Saya menggunakan versi berikut dalam percobaan yang dilakukan pada artikel ini:

"vue": "^3.4.0",
"vuetify": "^3.5.8"
"@vueuse/core": "^10.9.0",
"@mdi/font": "^7.4.47"


Deteksi Kamera yang Tersedia

Langkah pertama yang dilakukan adalah mendeteksi daftar kamera yang tersedia pada device yang digunakan pengguna. Cara ini bisa dilakukan menggunakan fungsi useDevicesList dari VueUse. Lihat contoh di bawah ini:

const {videoInputs: cameras} = useDevicesList({
    requestPermissions: true,
    onUpdated() {
        if (!cameras.value.find(i => i.deviceId === currentCamera.value))
            currentCamera.value = cameras.value[0]?.deviceId
    },
})

Setelah daftar kamera tersedia didapatkan, selanjutnya kita tampilkan daftar tersebut kepada user agar bisa memilih kamera mana yang mau digunakan. Pada kesempatan kali ini, saya menggunakan v-list-item dari Vuetify.

 <v-list-item
       v-for="camera of cameras"
       :key="camera.deviceId"
       class="px-2 py-1 cursor-pointer"
       :class="{ 'text-primary': currentCamera === camera.deviceId }"
       @click="switchCamera(camera)"
>
       {{ camera.label }}
       </v-list-item>

Memilih Kamera Dari Daftar

Pada list kamera di atas, kita lihat ada sebuah fungsi switchCamera yang digunakan untuk memilih kamera saat kamera tersebut diclik dari daftar. Fungsi tersebut adalah untuk menyimpan kamera terpilih (selected camera) ke dalam variable reactive. Kita buat variabel reactive menggunakan ref() terlebih dahulu.

const currentCamera = ref()

Selanjutnya, kita gunakan kamera yang dipilih yang disimpan pada variabel currentCamera ke dalam query useUserMedia sebagai berikut:

const {stream, enabled} = useUserMedia({
    constraints: {
        video: {
            deviceId: currentCamera,
            width: {ideal: 4096},
            height: {ideal: 2160}
        }
    },
})

Selanjutnya, kita buat fungsi untuk mengganti kamera aktif menggunakan fungsi switchCamera berikut:

const switchCamera = (camera) => {
    currentCamera.value = camera.deviceId
    enabled.value = false
    setTimeout(() => {
        enabled.value = true
        showCameraOption.value = false
    }, 10)
}

Keterangan:

  • currentCamera.value digunakan untuk mengubah nilai kamera yang dipilih
  • enabled.value digunakan untuk mematikan atau menghidupkan fitur kamera
  • setTimeout digunakan untuk menunggu sementara waktu device melakukan transisi penggantian kamera aktive

Menampilkan Preview Menggunakan Kamera yang Dipilih

Langkah berikutnya adalah menampilkan preview menggunakan kamera yang dipilih pengguna. Kita bisa juga otomatis menampilkan preview secara langsung saat halaman diakses menggunakan onMounted().

Kita perlu menggunakan tag <video> untuk menampilkan stream dari kamera yang dipilih. Tambahkan tag tersebut ke dalam komponen vuejs.

 <video ref="video" muted autoplay style="width: 300px"/>

Buat referensi tag video tersebut menggunakan kode di bawah ini:

const video = ref()

Gunakan watchEffect untuk memantau perubahan kamera yang digunakan kemudian menampilkannua ke dalam tag video.

watchEffect(() => {
    if (video.value) {
        video.value.srcObject = stream.value
        setTimeout(() => {
            camSetting.value = stream.value?.getTracks()[0].getSettings()
        }, 1000)
    }
})

Aktifkan kamera saat user mengakses halaman secara langsung menggunakan onMounted().

onMounted(() => {
    enabled.value = true
})

Kode Lengkap Komponen Camera.vue

Berikut ini adalah kode lengkap Camera.vue yang digunakan untuk demo artikel ini. Demo penggunaannya bisa dilihat menggunakan link demo penggunaan kamera di vuejs.

<template>
        <v-card>
            <v-card-text v-if="enabled">
                <v-sheet class="d-flex justify-center">
                    <video ref="video" muted autoplay style="width: 300px"/>
                </v-sheet>
                <v-sheet class="d-flex justify-center mt-2">
                    <v-menu :close-on-content-click="false" v-model="showCameraOption">
                        <template v-slot:activator="{props}">
                            <v-btn prepend-icon="mdi-camera-flip" v-bind="props" variant="text">
                                Ganti
                            </v-btn>
                        </template>
                        <v-card title="Kamera Tersedia" width="300">
                            <v-card-text>
                                <v-list-item
                                    v-for="camera of cameras"
                                    :key="camera.deviceId"
                                    class="px-2 py-1 cursor-pointer"
                                    :class="{ 'text-primary': currentCamera === camera.deviceId }"
                                    @click="switchCamera(camera)"
                                >
                                    {{ camera.label }}
                                </v-list-item>
                            </v-card-text>
                        </v-card>
                    </v-menu>
                    <v-btn @click="take" prepend-icon="mdi-camera">Foto</v-btn>
                </v-sheet>

            </v-card-text>
            <v-card-text v-else>
                <v-sheet class="d-flex justify-center">
                    <v-img width="300" :src="savedImage"/>
                </v-sheet>
                <v-sheet class="d-flex justify-center">
                    <v-btn prepend-icon="mdi-camera-retake" text="Ulang" @click="enabled=true"/>
                </v-sheet>
            </v-card-text>
        </v-card>
        <canvas style="visibility: hidden;position: fixed;right: 0;bottom: 0" ref="canvas"/>
</template>

<script setup>

import GuestLayout from "@/Layouts/GuestLayout.vue";
import {onMounted, ref, watchEffect} from 'vue'
import {useDebounceFn, useDevicesList, useUserMedia} from '@vueuse/core'

const showCameraOption = ref(false)
const currentCamera = ref()
const {videoInputs: cameras} = useDevicesList({
    requestPermissions: true,
    onUpdated() {
        if (!cameras.value.find(i => i.deviceId === currentCamera.value))
            currentCamera.value = cameras.value[0]?.deviceId
    },
})
const video = ref()
const canvas = ref()
const camSetting = ref()
const savedImage = ref()
const {stream, enabled} = useUserMedia({
    constraints: {
        video: {
            deviceId: currentCamera,
            width: {ideal: 4096},
            height: {ideal: 2160}
        }
    },
})
watchEffect(() => {
    if (video.value) {
        video.value.srcObject = stream.value
        setTimeout(() => {
            camSetting.value = stream.value?.getTracks()[0].getSettings()
        }, 1000)
    }
})
const switchCamera = (camera) => {
    currentCamera.value = camera.deviceId
    enabled.value = false
    setTimeout(() => {
        enabled.value = true
        showCameraOption.value = false
    }, 10)
}
const take = () => {
    canvas.value.width = camSetting.value.width
    canvas.value.height = camSetting.value.height
    canvas.value.getContext('2d').drawImage(video.value, 0, 0, camSetting.value.width, camSetting.value.height);
    savedImage.value = canvas.value.toDataURL('image/jpeg');
    enabled.value = false
    emit('getImage', savedImage.value)
}
const getImage = () => {
    return savedImage.value
}
onMounted(() => {
    enableCamera()
})
const emit = defineEmits(['getImage'])

const enableCamera = () => {
    enabled.value = true
}

</script>

<style>
video {
    -webkit-transform: scaleX(-1);
    transform: scaleX(-1);
}
</style>
Photo of author

Ahmad Budairi

Ahmad Budairi, S.Pd. | Seorang Web Developer sekaligus kader Gerakan Pemuda Ansor yang sangat suka menulis artikel. Saya di sini akan lebih banyak menulis seputar Laravel, vue, vuetify, inertiajs, dan hal lain yang berhubungan dengan web development.Kontak: budairi.contact[et]gmail[dot]com