帮忙看下这个vue3调用摄像头的代码有什么逻辑错误?

<script setup lang="tsx">
import { ref, onMounted, inject, onActivated } from 'vue'
import { ElMessage } from 'element-plus'
import { uploadFile } from '@/api/materials'
import { ContentWrap } from '@/components/ContentWrap'
import { uploadReportFile } from '@/api/report'
const updateImgList = inject('updateImgList') as (() => void) | undefined
const setIsVideo = inject('setIsVideo') as ((yesno: boolean) => void) | undefined

const props = defineProps({
  edittype: {
    type: String,
    default: ''
  },
  reportinfo: {
    type: Object,
    default: () => ({})
  },
  patientInfo: {
    type: Object,
    default: () => ({})
  }
})
const video = ref<HTMLVideoElement | null>(null)
const canvasVideo = ref<HTMLCanvasElement | null>(null)
const context = ref<CanvasRenderingContext2D | null>(null)
let vstream: MediaStream | null = null
let deviceVideos: string[] = []
let deviceVideosIndex = 0
let chunks: Blob[] = []
let recording = false
let mediaRecorder: MediaRecorder | null = null
let recordedVideoUrl: string | null = null

const getUserMedia = async (constraints: MediaStreamConstraints): Promise<MediaStream> => {
  if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
    return await navigator.mediaDevices.getUserMedia(constraints)
  } else {
    throw new Error('不支持访问用户媒体')
  }
}

const closeCamera = () => {
  return new Promise((resolve, reject) => {
    if (video.value && video.value.srcObject) {
      try {
        const tracks = (video.value.srcObject as MediaStream).getTracks()
        console.log(tracks)
        tracks.forEach((track) => {
          track.stop()
        })
        video.value.srcObject = null
      } catch (e) {
        console.error(e)
      }
    } else {
      resolve('ok')
    }
  })
}

const openCamera = async () => {
  await start()
  try {
    await closeCamera()
    if (deviceVideos.length === 0) {
      ElMessage.error('没有找到摄像头设备')
      return
    }
    console.log(deviceVideos)
    console.log('摄像头数量:' + deviceVideos.length)
    const stream = await getUserMedia({
      video: {
        width: 1920,
        height: 1080,
        deviceId: deviceVideos[deviceVideosIndex++ % deviceVideos.length]
      },
      audio: true
    })
    vstream = stream
    if (video.value) {
      video.value.srcObject = stream
      video.value.play()
    }
    setIsVideo?.(true)
  } catch (e) {
    console.log(e)
    ElMessage.error(e + '!')
  }
}
const keyDown = (event: KeyboardEvent) => {
  console.log('ssss')
  if (event.key === 'Enter') {
    console.log('xxxx')
  }
}
const captureSavePhoto = (event: KeyboardEvent) => {
  capturePhoto()
  savePhotox()
}
const capturePhoto = () => {
  if (context.value && video.value) {
    context.value.drawImage(video.value, 0, 0, 1920, 1080, 0, 0, 800, 600)
  }
}

const saveAs = (data: string, filename: string) => {
  const link = document.createElement('a')
  link.href = data
  link.download = filename
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
}
const saveAsx = (data: string, filename: string) => {
  const link = document.createElement('a')
  link.href = data
  link.download = filename
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
}
const savePhotox = async () => {
  const canvas1 = document.createElement('canvas')
  if (video.value) {
    canvas1.width = video.value.videoWidth
    canvas1.height = video.value.videoHeight
    const context1 = canvas1.getContext('2d')
    if (context1) {
      context1.drawImage(video.value, 0, 0, 1920, 1080)
      const fd = new FormData()
      fd.append('file', blobtoFile(base64toBlob(canvas1.toDataURL()), 'xxx.png'))
      console.log('xxxxxx')
      if (props.edittype === 'reg') {
        console.log('取材在操作SetImage')
        fd.append('RegID', props.patientInfo.RegID)
        const res = await uploadFile(fd)
        if (res) {
          ElMessage.success('保存成功')
          updateImgList()
        } else {
          ElMessage.error('保存失败')
        }
      }
      if (props.edittype === 'report') {
        fd.append('RegID', props.reportinfo.RegID)
        const res = await uploadReportFile(fd)
        if (res) {
          ElMessage.success('保存成功')
          updateImgList()
        } else {
          ElMessage.error('保存失败')
        }
      }
    }
  }
}
const blobtoFile = (blob, fileName) => {
  const file = new File([blob], fileName, { type: blob.type })
  return file
}
const base64toBlob = (dataurl) => {
  let arr = dataurl.split(',')
  let mime = arr[0].match(/:(.*?);/)[1]
  let bstr = atob(arr[1])
  let n = bstr.length
  let u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n)
  }
  return new Blob([u8arr], { type: mime })
}
const savePhoto = () => {
  const canvas1 = document.createElement('canvas')
  if (video.value) {
    canvas1.width = video.value.videoWidth
    canvas1.height = video.value.videoHeight
    const context1 = canvas1.getContext('2d')
    if (context1) {
      context1.drawImage(video.value, 0, 0, 1920, 1080)
      saveAs(canvas1.toDataURL(), 'photo.jpg')
    }
  }
}
const startRecording = () => {
  if (vstream && MediaRecorder.isTypeSupported('video/webm')) {
    chunks = []
    mediaRecorder = new MediaRecorder(vstream, { mimeType: 'video/webm' })
    mediaRecorder.ondataavailable = (e) => {
      if (e.data && e.data.size > 0) {
        chunks.push(e.data)
      }
    }
    mediaRecorder.onstop = () => {
      const recordedBlob = new Blob(chunks, { type: 'video/webm' })
      chunks = []
      recordedVideoUrl = URL.createObjectURL(recordedBlob)
      console.log('录像文件路径1:' + recordedVideoUrl)
    }
    mediaRecorder.start()
    recording = true
  } else {
    console.error('不支持录制视频')
  }
}

const stopRecording = () => {
  if (recording && mediaRecorder) {
    mediaRecorder.stop()
    mediaRecorder = null
    recording = false
  }
}

const saveRecording = () => {
  console.log('录像文件路径2:' + recordedVideoUrl)
  if (recordedVideoUrl) {
    const a = document.createElement('a')
    a.href = recordedVideoUrl
    a.download = '录像.webm'
    a.click()
  }
}
const start = () => {
  if (canvasVideo.value) {
    context.value = canvasVideo.value.getContext('2d')
  }
  console.log('onMounted')
  console.log(navigator.mediaDevices)
  if (navigator.mediaDevices) {
    navigator.mediaDevices
      .enumerateDevices()
      .then((devices) => {
        devices.forEach((device) => {
          if (device.kind.startsWith('video')) {
            deviceVideos.push(device.deviceId)
          }
        })
      })
      .catch((error) => {
        console.error('Failed to enumerate devices:', error)
      })
  }
}
onMounted(async () => {
  await start()
})
onActivated(async () => {
  await start()
})
defineExpose({
  openCamera,
  closeCamera,
  capturePhoto,
  savePhoto,
  startRecording,
  stopRecording,
  saveRecording,
  captureSavePhoto
})
</script>
<template>
  <ContentWrap style="border: 0px; position: relative">
    <div class="flex justify-center mb-10px">
      <BaseButton size="large" @keydown="keyDown" @click="captureSavePhoto">拍照并保存</BaseButton>
      <!--
      <BaseButton @click="openCamera">打开摄像头</BaseButton>
      <BaseButton @click="closeCamera">关闭摄像头</BaseButton>
      <BaseButton @click="capturePhoto">拍照</BaseButton>
      <BaseButton @click="savePhoto">拍照另存为</BaseButton>
      <BaseButton @click="startRecording">开始录像</BaseButton>
      <BaseButton @click="stopRecording">停止录像</BaseButton>
      <BaseButton @click="saveRecording">录像另存为</BaseButton>
      -->
    </div>
    <div class="flex justify-center">
      <video ref="video" width="80%" height="80%" controls></video>
    </div>

    <canvas id="canvasVideo" ref="canvasVideo" width="400" height="300"></canvas>
  </ContentWrap>
</template>

<style scoped lang="less">
#canvasVideo {
  background-color: #1c2518;
  position: absolute;
  bottom: 0px;
  left: 0px;
  display: none;
}
</style>
阅读 925
avatarAI BotBETA

在你提供的 Vue 3 调用摄像头的代码中,主要逻辑和功能实现看起来是合理的,但有几个潜在的问题和改进点需要注意:

  1. 键盘事件处理 (keydown):

    • 你尝试在 BaseButton 组件上使用 @keydown="keyDown" 事件监听器,但通常按钮(<button> 或类似组件)不支持 keydown 事件。这种事件通常用于可获取焦点的元素,如输入框。如果你希望通过键盘触发拍照,可能需要考虑其他方法,比如监听整个文档的 keydown 事件或使用可聚焦的按钮。
  2. savePhotoxsavePhoto 函数的重复:

    • savePhotoxsavePhoto 函数在功能上有重叠,但处理方式略有不同(例如,savePhotox 使用 FormData 上传,而 savePhoto 直接使用 saveAs)。你可能想根据实际需求合并或区分这两个函数的功能。
  3. canvasVideo 的使用:

    • 你在模板中定义了一个 canvas 元素,并为其设置了 ref="canvasVideo",但在实际代码中,canvasVideo 主要用于获取绘图上下文(context.value = canvasVideo.value.getContext('2d')),并未在拍照或录像功能中直接使用。如果 canvasVideo 不是必需的,可以考虑移除以简化代码。
  4. 异常处理:

    • 你在 getUserMediacloseCamera 等函数中使用了 try...catch 来处理可能的错误,这是一个好的做法。然而,确保所有可能引发错误的异步操作都被适当捕获和处理是很重要的。
  5. 代码组织和可维护性:

    • 考虑将相关功能(如摄像头操作、图片处理、视频处理等)分组到单独的 Vue 组合式 API 函数或方法中,以提高代码的可读性和可维护性。
  6. 样式和显示:

    • 你的 canvasVideo 设置为 display: none;,这意味着它不会在页面上显示。如果你打算在某个时候使用它(例如进行图像处理预览),请确保在需要时更改其显示属性。
  7. 设备权限和用户提示:

    • 当请求用户媒体(如摄像头和麦克风)时,确保在用户界面上有适当的提示,以告知用户即将发生的操作,并请求他们的许可。
  8. 性能优化:

    • 在处理视频流和大量图像数据时,注意内存和 CPU 使用情况,确保不会因资源耗尽而导致应用崩溃。

这些是一些基本的审查点和建议,希望能帮助你改进和优化你的 Vue 3 摄像头调用代码。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏