侧边栏壁纸
  • 累计撰写 53 篇文章
  • 累计创建 12 个标签
  • 累计收到 8 条评论

目 录CONTENT

文章目录

JS 视频流——摄像头调用

Kirito
2024-04-14 / 0 评论 / 0 点赞 / 25 阅读 / 11947 字 / 正在检测是否收录...

Demo地址:Verivista Temp

Markdown文档中,iframe组件无法获取音视频设备对象

HTML && CSS

如上例图所示,比较简单,就不详细描述了

<div class="webcam">
    <div class="box">
      <video ref="videoRef" id="video" autoplay></video>
      <div id="operation">
        <div class="device">
          <div class="video">
            <span>视频设备选择</span>
            <a-select
              class="select"
              ref="selectRef"
              :value="activeVideo"
              @change="
                (value: string) => {
                  handleChange('video', value)
                }
              "
            >
              <a-select-option
                v-for="device in videoDevices"
                :key="device.deviceId"
                :value="device.deviceId"
                >{{ device.label }}</a-select-option
              >
            </a-select>
          </div>

          <div class="video">
            <span>音频设备选择</span>
            <a-select
              class="select"
              ref="selectRef"
              :value="activeAudio"
              @change="
                (value: string) => {
                  handleChange('audio', value)
                }
              "
            >
              <a-select-option
                v-for="device in audioDevices"
                :key="device.deviceId"
                :value="device.deviceId"
                >{{ device.label }}</a-select-option
              >
            </a-select>
          </div>
        </div>
        <a-button class="button" @click="getDevices">刷新设备</a-button>
        <a-button
          class="button"
          @click="
            () => {
              running ? closeCam() : openCam()
            }
          "
          >{{ running ? '结束' : '开始' }}</a-button
        >
      </div>
    </div>
  </div>
.webcam {
  display: flex;
  flex: 1;
  justify-content: center;
  align-items: center;
  .box {
    display: flex;
    width: 32rem;
    height: 20rem;
    flex-direction: column;
    #video {
      width: 32rem;
      height: 18rem;
      border: 1px solid #68cad4;
    }
    #operation {
      display: flex;
      flex: 1;
      align-items: center;
      .select {
        width: 15rem;
      }
      .button {
        height: 100%;
        margin-left: auto;
      }
    }
  }
}

JS部分

首先,我们需要做一个操作来触发浏览器的摄像头、麦克风权限确认

触发权限确认气泡

// 触发浏览器权限确认气泡
const tryCam = () => {
  navigator.mediaDevices.getUserMedia({ audio: true, video: true })
}

onMounted(() => {
  tryCam()
})

获取音视频设备列表

// 获取设备列表
const getDevices = () => {
  navigator.mediaDevices.enumerateDevices().then((devices) => {
    let ds = devices
      .filter((d) => d.kind === 'videoinput')
      .map((d) => {
        const deviceObj = d.toJSON()
        const { deviceId, groupId, kind, label } = deviceObj
        return { deviceId, groupId, kind, label }
      })
    videoDevices.value = ds
    activeVideo.value = ds[0]

    ds = devices
      .filter((d) => d.kind === 'audioinput')
      .map((d) => {
        const deviceObj = d.toJSON()
        const { deviceId, groupId, kind, label } = deviceObj
        return { deviceId, groupId, kind, label }
      })
    audioDevices.value = ds
    activeAudio.value = ds[0]
  })
}

navigator.mediaDevides.enumerateDevices()会返回一个 Promise<MediaDeviceInfo[]>类型的对象,其中包含了音频设备和视频设备,通过 kind属性的 videoinput || audioinput来区分。

获取摄像头视频流

// 开始接收传输视频流
const openCam = () => {
  navigator.mediaDevices
    .getUserMedia({
      audio: {
        deviceId: activeAudio.value?.deviceId,
        groupId: activeAudio.value?.groupId
      },
      video: {
        width: 1600,
        height: 900,
        deviceId: activeVideo.value?.deviceId,
        groupId: activeVideo.value?.groupId
      }
    })
    .then((stream) => {
      let video = videoRef.value
      if (video) {
        video.srcObject = stream
        video.onloadedmetadata = () => video.play()
        saveStream = stream
        running.value = true
      }
    })
}

通过 navigator.mediaDevices.getUserMedia()来开启获取对应摄像头的视频流。

关闭数据流获取

// 全局作用域视频流
let saveStream: MediaStream

// 关闭视频流
const closeCam = () => {
  saveStream.getTracks().forEach((track) => track.stop())
  running.value = false
}

通过 saveStream.getTracks()可以获取到 MediaStreamTrack[]类型的数组对象,其中包含了开启状态的音视频设备,如果想单独操作音频设备或视频设备可筛选后再行操作。

完整代码

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

interface iDevice {
  deviceId: string
  groupId: string
  kind: string
  label: string
}

const videoRef = ref<HTMLVideoElement>()
const selectRef = ref()
const videoDevices = ref<iDevice[]>([])
const audioDevices = ref<iDevice[]>([])
const activeVideo = ref<iDevice>()
const activeAudio = ref<iDevice>()
const running = ref<boolean>(false)

// 全局作用域视频流
let saveStream: MediaStream

// 触发浏览器权限确认气泡
const tryCam = () => {
  navigator.mediaDevices.getUserMedia({ audio: true, video: true })
}

// 开始接收传输视频流
const openCam = () => {
  navigator.mediaDevices
    .getUserMedia({
      audio: {
        deviceId: activeAudio.value?.deviceId,
        groupId: activeAudio.value?.groupId
      },
      video: {
        width: 1600,
        height: 900,
        deviceId: activeVideo.value?.deviceId,
        groupId: activeVideo.value?.groupId
      }
    })
    .then((stream) => {
      let video = videoRef.value
      if (video) {
        video.srcObject = stream
        video.onloadedmetadata = () => video.play()
        saveStream = stream
        running.value = true
      }
    })
}

// 关闭视频流
const closeCam = () => {
  saveStream.getTracks().forEach((track) => track.stop())
  running.value = false
}

// 获取设备列表
const getDevices = () => {
  navigator.mediaDevices.enumerateDevices().then((devices) => {
    let ds = devices
      .filter((d) => d.kind === 'videoinput')
      .map((d) => {
        const deviceObj = d.toJSON()
        const { deviceId, groupId, kind, label } = deviceObj
        return { deviceId, groupId, kind, label }
      })
    videoDevices.value = ds
    activeVideo.value = ds[0]

    ds = devices
      .filter((d) => d.kind === 'audioinput')
      .map((d) => {
        const deviceObj = d.toJSON()
        const { deviceId, groupId, kind, label } = deviceObj
        return { deviceId, groupId, kind, label }
      })
    audioDevices.value = ds
    activeAudio.value = ds[0]
  })
}

// 切换音视频设备
const handleChange = (key: string, value: string) => {
  if (key === 'audio') {
    activeAudio.value = audioDevices.value.filter((d) => d.deviceId === value)[0]
  } else {
    activeVideo.value = videoDevices.value.filter((d) => d.deviceId === value)[0]
  }
}

onMounted(() => {
  tryCam()
})
</script>
<template>
  <div class="webcam">
    <div class="box">
      <video ref="videoRef" id="video" autoplay></video>
      <div id="operation">
        <div class="device">
          <div class="video">
            <span>视频设备选择</span>
            <a-select
              class="select"
              ref="selectRef"
              :value="activeVideo"
              @change="
                (value: string) => {
                  handleChange('video', value)
                }
              "
            >
              <a-select-option
                v-for="device in videoDevices"
                :key="device.deviceId"
                :value="device.deviceId"
                >{{ device.label }}</a-select-option
              >
            </a-select>
          </div>

          <div class="video">
            <span>音频设备选择</span>
            <a-select
              class="select"
              ref="selectRef"
              :value="activeAudio"
              @change="
                (value: string) => {
                  handleChange('audio', value)
                }
              "
            >
              <a-select-option
                v-for="device in audioDevices"
                :key="device.deviceId"
                :value="device.deviceId"
                >{{ device.label }}</a-select-option
              >
            </a-select>
          </div>
        </div>
        <a-button class="button" @click="getDevices">刷新设备</a-button>
        <a-button
          class="button"
          @click="
            () => {
              running ? closeCam() : openCam()
            }
          "
          >{{ running ? '结束' : '开始' }}</a-button
        >
      </div>
    </div>
  </div>
</template>
<style lang="less" scoped>
.webcam {
  display: flex;
  flex: 1;
  justify-content: center;
  align-items: center;
  .box {
    display: flex;
    width: 32rem;
    height: 20rem;
    flex-direction: column;
    #video {
      width: 32rem;
      height: 18rem;
      border: 1px solid #68cad4;
    }
    #operation {
      display: flex;
      flex: 1;
      align-items: center;
      .select {
        width: 15rem;
      }
      .button {
        height: 100%;
        margin-left: auto;
      }
    }
  }
}
</style>

报错及处理方式

1.Unity has not started sending image data [Capture Device #1]

Video组件提示信息

网上的教程很多都在 getUserMedia()传递配置参数的时候,仅传递了 deviceId,造成了没有正确接收到对应设备的数据流,传递 groupIddeviceId即可解决。**(坑死我了 =A=)**

0

评论区