效果预览


1.录制前




django调用java Django调用前端摄像头_django调用java


2.录制中


django调用java Django调用前端摄像头_django调用java_02


3.录制结束下载文件至本地


django调用java Django调用前端摄像头_前端_03


4.视频文件同时上传至后端接口


django调用java Django调用前端摄像头_django_04


django调用java Django调用前端摄像头_javascript_05


后端准备


参考博客

博主在后端这块写的十分简洁明了,可以直接参考实现开设后端简单文件上传接口的方法

前端组件实现


参考文章:




Vue 调用本地摄像头实现拍照功能,由于调用摄像头有使用权限,只能在本地运行,线上需用 https 域名才可以使用。

需求分析

点击“视频录制”按钮,打开摄像头开启录制,同时按钮中内容更换为“停止录制”

点击“停止录制”按钮,关闭摄像头并保存录制文件,同时将以录制的文件上传后端接口

使用API

MediaDevices.getUserMedia()

该 API 会提示用户给予使用媒体输入的许可,媒体输入会产生一个MediaStream,里面包含了请求的媒体类型的轨道。此流可以包含一个视频轨道(来自硬件或者虚拟视频源,比如相机、视频采集设备和屏幕共享服务等等)、一个音频轨道(同样来自硬件或虚拟音频源,比如麦克风、A/D转换器等等),也可能是其它轨道类型。

它返回一个 Promise 对象,成功后会 resolve 回调一个 MediaStream 对象。若用户拒绝了使用权限,或者需要的媒体源不可用,Promise 会 reject 回调一个 PermissionDeniedError 或者 NotFoundError。

HTML部分

<template>
  <div class="CallCamera">
    <!-- 下载按钮 -->
    <a id="downLoadLink" style="display: none"></a>
    <div class="video-box">
      <video ref="video"></video>
      <!-- 视频录制或暂停 -->
      <button @click="recordOrStop" id="record_btu">视频录制</button>
    </div>
  </div>
</template>

JS部分

1.调用开启摄像头
// 调用打开摄像头功能
    getCamera() {
      // 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象
      if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {};
      }
      // 正常支持版本
      navigator.mediaDevices
        .getUserMedia({
          video: true,
          audio: true,
        })
          .then((stream) => {
          // 摄像头开启成功
          this.mediaStreamTrack = typeof stream.stop === 'function' ? stream : stream.getTracks()[0];
          this.video_stream = stream;
          this.$refs.video.srcObject = stream;
          this.$refs.video.play();
        })
        .catch(err => {
          console.log(err);
        });
    },
2.视频录制并下载以及停止录制
// 视频录制
    record() {
      console.log("record");
      this.isRecord = !this.isRecord;
      let mediaRecorder;
      let options;
      this.recordedBlobs = [];
      if (typeof MediaRecorder.isTypeSupported === "function") {
        // 根据浏览器来设置编码参数
        if (MediaRecorder.isTypeSupported("video/webm;codecs=vp9")) {
          options = {
            MimeType: "video/webm;codecs=h264",
          };
        } else if (MediaRecorder.isTypeSupported("video/webm;codecs=h264")) {
          options = {
            MimeType: "video/webm;codecs=h264",
          };
        } else if (MediaRecorder.isTypeSupported("video/webm;codecs=vp8")) {
          options = {
            MimeType: "video/webm;codecs=vp8",
          };
        }
        mediaRecorder = new MediaRecorder(this.video_stream, options);
      } else {
        // console.log('isTypeSupported is not supported, using default codecs for browser');
        console.log("当前不支持isTypeSupported,使用浏览器的默认编解码器");
        mediaRecorder = new MediaRecorder(this.video_stream);
      }
      mediaRecorder.start();
      // 视频录制监听事件
      mediaRecorder.ondataavailable = (e) => {
        console.log(e);
        // 录制的视频数据有效
        if (e.data && e.data.size > 0) {
          this.recordedBlobs.push(e.data);
        }
      };
      // 停止录像后增加下载视频功能,将视频流转为mp4格式
      mediaRecorder.onstop = () => {
        const blob = new Blob(this.recordedBlobs, { type: "video/mp4" });
        this.recordedBlobs = [];
        // 将视频链接转换完可以用于在浏览器上预览的本地视频
        const videoUrl = window.URL.createObjectURL(blob);
        // 设置下载链接
        document.getElementById("downLoadLink").href = videoUrl;
        // 设置下载mp4格式视频
        document.getElementById("downLoadLink").download = "media.mp4";
        document.getElementById("downLoadLink").innerHTML =
          "DownLoad video file";
        // 生成随机数字
        const rand = Math.floor(Math.random() * 1000000);
        // 生成视频名
        const name = `video${rand}.mp4`;

        // setAttribute() 方法添加指定的属性,并为其赋指定的值
        document.getElementById("downLoadLink").setAttribute("download", name);
        document.getElementById("downLoadLink").setAttribute("name", name);

        console.log(name);
        const filename = "C:\\Users\\k1114\\Downloads\\" + name;
        console.log(filename);

        // 0.5s后自动下载视频
        setTimeout(() => {
          document.getElementById("downLoadLink").click();
        }, 500);

        
      };
    },
    // 停止录制
    stop() {
      this.isRecord = !this.isRecord;
      if (!this.$refs.video.srcObject) return;
      const stream = this.$refs.video.srcObject;
      const tracks = stream.getTracks();
      // 关闭摄像头和音频
      tracks.forEach((track) => {
        track.stop();
      });
    },
3.获取文件流并上传后端

直接将视频流定义为file对象,通过post方法上传接口

//上传至后端
        let fileobj = new File(
          [blob],
            name,
          {
          type: "video/mp4",
        });

        let formData = new FormData(); //创建form对象
        formData.append("filename", fileobj); //通过append向form对象添加数据
        console.log(formData.get("filename")); //FormData私有类对象,访问不到,可以通过get判断值是否传进去
        //上传
        this.$axios
          .post("http://127.0.0.1:8000/runcase/start", formData, {
            headers: { "Content-Type": "none" },
          }) //请求头要为表单
          .then((response) => {
            console.log(response.data);
          })
          .catch(function (error) {
            console.log(error);
          });
4.按钮事件

通过 document.getElementById("record_btu") 获取按钮对象,并根据点击事件修改按钮内容和样式

根据 this.isRecord (boolen)判断视频录制状态,在进入页面时声明为false,按钮触发点击事件后修改为对立状态

因为在录制时要在页面的video窗口实时回显,如果不静音页面会产生回音,所以在开启摄像头时静音页面

触发按钮事件开启摄像头后直接开始录制会触发错误(我不知道具体原因,可能是摄像头还没有初始化完毕),所以设置了延时执行录制

recordOrStop() {
      if (this.isRecord) {
        document.getElementById("record_btu").innerText = "视频录制";
        document.getElementById("record_btu").style.backgroundColor =
          "rgb(22, 204, 195)";
        document.getElementById("record_btu").style.color = "black";
        this.stop();
      } else {
        this.getCamera();  //打开摄像头
        this.mutePage();   //将页面静音
        document.getElementById("record_btu").innerText = "结束录制";
        document.getElementById("record_btu").style.backgroundColor =
          "rgb(255 99 71)";
        document.getElementById("record_btu").style.color = "white";
        setTimeout(() => {
          // 方法区
          this.record();
        }, 2000);  //设置录制延迟 2s 
      }

完整JS代码

<script>
export default {
  data() {
    return {
      mediaStreamTrack: {}, // 退出时关闭摄像头
      video_stream: "", // 视频stream
      recordedBlobs: [], // 视频音频 blobs
      isRecord: false, // 视频是否正在录制
    };
  },
  mounted() {
    // 进入页面 调用摄像头
    // this.getCamera();
    // this.mutePage();
    window.onerror = () => {
      // mes 报错信息, source 那个文件, line 第几行, column 第几列, error 错误的对象
      this.$notify({
        title: "录制失败",
        message: "请点击结束录制并重新尝试",
        type: "warning",
      });
    };
  },
  methods: {
    // Mute a singular HTML5 element
    muteMe(elem) {
      elem.muted = true;
      elem.pause();
    },
    // Try to mute all video and audio elements on the page
    mutePage() {
      document
        .querySelectorAll("video, audio")
        .forEach((elem) => this.muteMe(elem));
    },

    // 调用打开摄像头功能
    getCamera() {
      // 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象
      if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {};
      }
      navigator.mediaDevices
        .getUserMedia({
          video: true,
          audio: true,
        })
        .then((stream) => {
          // 摄像头开启成功
          this.mediaStreamTrack =
            typeof stream.stop === "function" ? stream : stream.getTracks()[0];
          this.video_stream = stream;
          this.$refs.video.srcObject = stream;
          this.$refs.video.play();
        })
        .catch((err) => {
          console.log(err);
        });
    },
    // 录制或暂停
    recordOrStop() {
      if (this.isRecord) {
        document.getElementById("record_btu").innerText = "视频录制";
        document.getElementById("record_btu").style.backgroundColor =
          "rgb(22, 204, 195)";
        document.getElementById("record_btu").style.color = "black";
        this.stop();
      } else {
        this.getCamera();
        this.mutePage();
        document.getElementById("record_btu").innerText = "结束录制";
        document.getElementById("record_btu").style.backgroundColor =
          "rgb(255 99 71)";
        document.getElementById("record_btu").style.color = "white";
        setTimeout(() => {
          // 方法区
          this.record();
        }, 2000);
      }
    },
    // 视频录制
    record() {
      console.log("record");
      this.isRecord = !this.isRecord;
      let mediaRecorder;
      let options;
      this.recordedBlobs = [];
      if (typeof MediaRecorder.isTypeSupported === "function") {
        // 根据浏览器来设置编码参数
        if (MediaRecorder.isTypeSupported("video/webm;codecs=vp9")) {
          options = {
            MimeType: "video/webm;codecs=h264",
          };
        } else if (MediaRecorder.isTypeSupported("video/webm;codecs=h264")) {
          options = {
            MimeType: "video/webm;codecs=h264",
          };
        } else if (MediaRecorder.isTypeSupported("video/webm;codecs=vp8")) {
          options = {
            MimeType: "video/webm;codecs=vp8",
          };
        }
        mediaRecorder = new MediaRecorder(this.video_stream, options);
      } else {
        // console.log('isTypeSupported is not supported, using default codecs for browser');
        console.log("当前不支持isTypeSupported,使用浏览器的默认编解码器");
        mediaRecorder = new MediaRecorder(this.video_stream);
      }
      mediaRecorder.start();
      // 视频录制监听事件
      mediaRecorder.ondataavailable = (e) => {
        console.log(e);
        // 录制的视频数据有效
        if (e.data && e.data.size > 0) {
          this.recordedBlobs.push(e.data);
        }
      };
      // 停止录像后增加下载视频功能,将视频流转为mp4格式
      mediaRecorder.onstop = () => {
        const blob = new Blob(this.recordedBlobs, { type: "video/mp4" });
        this.recordedBlobs = [];
        // 将视频链接转换完可以用于在浏览器上预览的本地视频
        const videoUrl = window.URL.createObjectURL(blob);
        // 设置下载链接
        document.getElementById("downLoadLink").href = videoUrl;
        // 设置下载mp4格式视频
        document.getElementById("downLoadLink").download = "media.mp4";
        document.getElementById("downLoadLink").innerHTML =
          "DownLoad video file";
        // 生成随机数字
        const rand = Math.floor(Math.random() * 1000000);
        // 生成视频名
        const name = `video${rand}.mp4`;

        // setAttribute() 方法添加指定的属性,并为其赋指定的值
        document.getElementById("downLoadLink").setAttribute("download", name);
        document.getElementById("downLoadLink").setAttribute("name", name);

        console.log(name);
        const filename = "C:\\Users\\k1114\\Downloads\\" + name;
        console.log(filename);

        // 0.5s后自动下载视频
        setTimeout(() => {
          document.getElementById("downLoadLink").click();
        }, 500);

        //上传至后端

        let fileobj = new File(
          [blob],
            name,
          {
          type: "video/mp4",
        });

        let formData = new FormData(); //创建form对象
        formData.append("filename", fileobj); //通过append向form对象添加数据
        console.log(formData.get("filename")); //FormData私有类对象,访问不到,可以通过get判断值是否传进去
        //上传
        this.$axios
          .post("http://127.0.0.1:8000/runcase/start", formData, {
            headers: { "Content-Type": "none" },
          }) //请求头要为表单
          .then((response) => {
            console.log(response.data);
          })
          .catch(function (error) {
            console.log(error);
          });
      };
    },
    // 停止录制
    stop() {
      this.isRecord = !this.isRecord;
      if (!this.$refs.video.srcObject) return;
      const stream = this.$refs.video.srcObject;
      const tracks = stream.getTracks();
      // 关闭摄像头和音频
      tracks.forEach((track) => {
        track.stop();
      });
    },

  },
};
</script>

CSS部分

<style>
.CallCamera {
  flex-wrap: wrap;
  align-content: space-between;
}
.video-box {
  width: 800px;
  height: 600px;
  text-align: center;
}
video {
  width: 740px;
  height: 555px;
  background-color: black;
  object-fit: fill;
  margin: auto;
  /*position: relative;*/

  /*position: absolute;*/
}
canvas {
  width: 100%;
  height: 100%;
}
.CallCamera button {
  width: 100px;
  height: 40px;
  position: relative;
  margin: auto;
  border-radius: 15px;
  background-color: rgb(22, 204, 195);
  cursor: pointer;
  transition: 0.5s;
  top: 20px;
}
.CallCamera button:hover {
  font-size: 15px;
  background-color: rgb(255 99 71);
  color: white;
}

.img_bg_camera img {
  width: 300px;
  height: 200px;
}
</style>