Skip to content

摄像机记录器

从网络摄像头或照相机拍摄照片或录制视频,并将拍摄的照片或视频上传到云端,比如阿里的OSS、腾讯的COS、七牛云、又拍云等。

目前仅实现了拍摄照片,并上传到云端。

安装相关包

安装vue@vueuse/corelodashaxios

初始化设备

JS
import { reactive, provide, ref, markRaw, watchEffect, computed, onMounted, onBeforeUnmount } from 'vue'
import { useDevicesList, useUserMedia } from '@vueuse/core'
import _ from 'lodash'

const {
    videoInputs: cameras,
    audioInputs: microphones,
    permissionGranted,
    isSupported,
    ensurePermissions,
} = useDevicesList({
    requestPermissions: true,
    constraints: { video: true, audio: true },
    onUpdated() {
        refreshCurrentDevices()
    },
})

const currentCamera = ref(cameras.value[0]?.deviceId)

function refreshCurrentDevices() {
    if (_.isNil(currentCamera) || !cameras.value.find((i) => i.deviceId === currentCamera.value)) {
        currentCamera.value = cameras.value[0]?.deviceId
    }
}

启动视频流

JS
import { useUserMedia } from '@vueuse/core'

const {
    stream,
    start,
    stop,
    enabled: isMediaStreamAvailable,
} = useUserMedia({
    constraints: computed(() => ({
        video: { deviceId: currentCamera.value },
    })),
    autoSwitch: true,
})

start()

获取视频流

Vue
<script lang="ts" setup>
import { useMediaRecorder } from './useMediaRecorder'

const { onRecordAvailable } = useMediaRecorder({
    stream,
})
</script>
TS
import { type Ref, computed, ref } from 'vue'
import { createEventHook } from '@vueuse/core'

export { useMediaRecorder }

function useMediaRecorder({ stream }: { stream: Ref<MediaStream | undefined> }): {
    isRecordingSupported: Ref<boolean>
    recordingState: Ref<'stopped' | 'recording' | 'paused'>
    startRecording: () => void
    stopRecording: () => void
    pauseRecording: () => void
    resumeRecording: () => void
    onRecordAvailable: (cb: (url: string) => void) => void
} {
    const isRecordingSupported = computed(() => MediaRecorder.isTypeSupported('video/webm'))
    const mediaRecorder = ref<MediaRecorder | null>(null)
    const recordedChunks = ref<Blob[]>([])
    const recordAvailable = createEventHook()
    const recordingState = ref<'stopped' | 'recording' | 'paused'>('stopped')

    const createVideo = () => {
        const blob = new Blob(recordedChunks.value, { type: 'video/webm' })
        const url = URL.createObjectURL(blob)
        recordedChunks.value = []
        return url
    }

    const startRecording = () => {
        if (!isRecordingSupported.value) {
            return
        }
        if (!stream.value) {
            return
        }
        if (recordingState.value !== 'stopped') {
            return
        }

        mediaRecorder.value = new MediaRecorder(stream.value, { mimeType: 'video/webm' })

        mediaRecorder.value.ondataavailable = (e) => {
            if (e.data.size > 0) {
                recordedChunks.value.push(e.data)
            }
        }

        mediaRecorder.value.onstop = () => {
            recordAvailable.trigger(createVideo())
        }

        if (mediaRecorder.value.state !== 'inactive') {
            return
        }

        mediaRecorder.value.start()
        recordingState.value = 'recording'
    }

    const stopRecording = () => {
        if (!isRecordingSupported.value) {
            return
        }
        if (!mediaRecorder.value) {
            return
        }
        if (recordingState.value === 'stopped') {
            return
        }

        mediaRecorder.value.stop()
        recordingState.value = 'stopped'
    }

    const pauseRecording = () => {
        if (!isRecordingSupported.value) {
            return
        }
        if (!mediaRecorder.value) {
            return
        }
        if (recordingState.value !== 'recording') {
            return
        }

        mediaRecorder.value.pause()
        recordingState.value = 'paused'
    }

    const resumeRecording = () => {
        if (!isRecordingSupported.value) {
            return
        }
        if (!mediaRecorder.value) {
            return
        }

        if (recordingState.value !== 'paused') {
            return
        }

        mediaRecorder.value.resume()
        recordingState.value = 'recording'
    }

    return {
        isRecordingSupported,
        startRecording,
        stopRecording,
        pauseRecording,
        resumeRecording,
        recordingState,
        onRecordAvailable: recordAvailable.on,
    }
}

申请摄像头权限

若权限因意外情况没有弹出授权框,可加一个点击按钮,申请权限。

Vue
<template>
    <el-button type="success" @click="requestPermissions">授权</el-button>
</template>

<script lang="ts" setup>
async function requestPermissions() {
    try {
        await ensurePermissions()
    } catch (e) {
        permissionCannotBePrompted.value = true
    }
}
</script>

扫码枪扫条形码拍照上传

Vue
<template>
    <div>
        <video ref="video" autoplay controls />
    </div>
</template>

<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'

const video = ref<HTMLVideoElement>()

onMounted(() => {
    document.addEventListener('keypress', handleKeypress)
})

onBeforeUnmount(() => {
    document.removeEventListener('keypress', handleKeypress)
})

// 按下扫描键的时候
const handleKeypress = (event) => {
    if (event.key === 'Enter') {
        takeScreenshot()
    } else if (event.key == undefined) {

    } else {
        scanData.value += event.key
    }
}

async function takeScreenshot() {
    if (!video.value) {
        return
    }

    const canvas = document.createElement('canvas')
    canvas.width = video.value.videoWidth
    canvas.height = video.value.videoHeight
    canvas.getContext('2d')?.drawImage(video.value, 0, 0)
    canvas.toBlob((blob) => {
        const tempFile = new File([blob], 'img.png', { type: 'image/png' })
        let fd = new FormData()
        fd.append('file', tempFile)
        // fileUpload为上传方法,需要替换为你的上传方法
        fileUpload(fd).then((res) => {
            if (res.code == 200) {
                // 上传成功
            }
        })
    })
}
</script>

Last updated: