1

遇到一个需求:当前播放的视频上面放置一个按钮,点击按钮能够截取当前视频的那一帧,三秒过后可以重新截取,视频暂停隐藏按钮。下面是代码:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>前端截取当前播放的视频帧为图片</title>
  <!--引入 element-ui 的样式,-->
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
  <script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script>
  <!-- 引入element 的组件库-->
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    #app {
      margin: 30px;
    }

    .video-wrap {
      width: 500px;
      height: 500px;
      position: relative;
      background-color: #000;
      display: inline-block;
      margin-right: 100px;
    }

    video {
      width: 100%;
      height: 100%;
    }

    .cover-btn {
      position: absolute;
      top: 0;
      right: 0;
      padding: 8px 12px;
      background: rgba(255, 255, 255, 0.8);
      z-index: 1;
      cursor: pointer;
      border-radius: 0px 6px;
      font-weight: 400;
      font-size: 14px;
      line-height: 20px;
      color: #2c66ff;
      transition: all 0.3s;

      .icon {
        width: 14px;
        height: 14px;
      }

      &:hover {
        color: rgba(44, 102, 255, 0.72);
      }

      &.success {
        color: #121314;
        cursor: text;
      }
    }
  </style>
</head>

<body>
  <div id="app">
    <div class="video-wrap">
      <!-- 视频播放才展示该按钮 -->
      <div v-if="isPlay" class="cover-btn" :class="{ success: updateImgTip }" @click="captureFrame">
        {{ updateImgTip ? "截图成功" : "将当前画面设为截图" }}
        <svg-icon v-if="updateImgTip" class="icon" icon-class="icon-duigou" />
      </div>
      <!-- 
        crossorigin 属性用于定义是否支持 CORS 请求。从另一个域或第三方服务器获取资源时。它包括音频等资源。视频、图片。链接和脚本。它用于处理 CORS 请求,检查是否允许共享来自其他域的资源是安全的。
      用法:用于<audio>、<video>、<link>、<img>、<script>等很多元素。
      也就是说如果用了 crossOrigin="anonymous" 表示是想跨域获取这张图片,但是跨域获取时需要服务器端支持,也就是请求返回头部要有:Access-Control-Allow-Origin:*。
      -->
      <video ref="video" controls crossOrigin="Anonymous" @play="updatePlayingState(true)"
        @pause="updatePlayingState(false)" loop>
        <source
          src="https://files.porsche.cn/filestore/video/multimedia/none/modelseries-718-models-overview-design-loop/video-mp4/92fb8d17-1205-11eb-80ce-005056bbdc38/porsche-video.mp4"
          type="video/mp4" preload="auto" />
        您的浏览器不支持 video 标签。
      </video>
    </div>
    <img v-if="base64String" style="width: 200px" :src="base64String" alt="">
  </div>
  <script>
    new Vue({
      el: '#app',
      data() {
        return {
          // 是否播放中
          isPlay: false,
          // 是否更新截图文案
          updateImgTip: false,
          // 防止按钮重复点击
          btnDisabled: false,
          // 定时器
          interval: null,
          // 倒计时
          captchaTime: 3,
          // 固定时间
          fixedSecond2: 3,
          base64String: null
        }
      },
      created() {

      },
      methods: {
        // 获取视频播放、暂停状态
        updatePlayingState(State) {
          this.isPlay = !this.isPlay
        },
        //  将当前画面设为素材封面
        async captureFrame() {
          // 防止重复点击
          if (this.btnDisabled) return;
          this.btnDisabled = true;
          // 创建一个canvas元素
          let canvas = document.createElement("canvas");
          const ratio = this.$refs.video.videoWidth / this.$refs.video.videoHeight;
          canvas.width = 300;
          canvas.height = canvas.width / ratio;
          // 将当前的视频帧画到canvas上
          let ctx = canvas.getContext("2d");
          ctx.drawImage(this.$refs.video, 0, 0, canvas.width, canvas.height);
          /*
          canvas.toDataURL(type, encoderOptions);
          1、type:图片格式,默认为 image/png,可以是其他image/jpeg等
          2、encoderOptions:0到1之间的取值,主要用来选定图片的质量,默认值是0.92,超出范围也会选择默认值。
          */

          // 将canvas上的图像转换为base64字符串
          this.base64String = canvas.toDataURL("image/png", 0.8);
          // 销毁canvas
          canvas = null;
          console.log(this.base64String);
          // 模拟ajax,成功之后
          setTimeout(() => {
            this.updateImgTip = true;
            this.countdownFun3();
          }, 300);
        },

        // 3秒倒计时
        countdownFun3() {
          this.interval = setInterval(() => {
            this.captchaTime = this.captchaTime - 1;
            if (this.captchaTime === 0) {
              clearInterval(this.interval);
              this.btnDisabled = false;
              this.captchaTime = this.fixedSecond2;
              this.updateImgTip = false;
            }
          }, 1000);
        },
      }
    })
  </script>

</body>

</html>

上面是需求的代码。在开发过程中,遇到几个问题

  1. video标签必须设置属性crossOrigin="Anonymous",如果不设置,会报这个错:
    Uncaught (in promise) DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.。
  2. 如果视频资源是接口获取的,需要配置资源跨域功能,比如视频是存储在阿里云的,就需要找后端或者运维在阿里云配置跨域,如果不配置跨域,但又加上了crossOrigin="Anonymous"属性,会导致视频无法播放。

上面的两个问题改了很久才解决,参考跨源相关机制综述(三):crossorigin属性

在网上浏览遇到另一段话:
crossOrigin="anonymous"是什么意思:
crossorigin 属性用于定义是否支持 CORS 请求。从另一个域或第三方服务器获取资源时。它包括音频等资源。视频、图片。链接和脚本。它用于处理 CORS 请求,检查是否允许共享来自其他域的资源是安全的。

用法:用于<audio>、<video>、<link>、<img>、<script>等很多元素。

也就是说如果用了 crossOrigin="anonymous" 表示是想跨域获取这张图片,但是跨域获取时需要服务器端支持,也就是请求返回头部要有:Access-Control-Allow-Origin:*。

跨域图片或视频能正常裁剪(图片未转化成base64),应该满足三个条件:

1、img或video元素中设置crossorigin属性

2、图片或视频资源允许跨域,设置响应头Access-Control-Allow-Origin


我的一个道姑朋友
80 声望4 粉丝

星光不问赶路人,岁月不负有心人。