video.js自定义预览组件-旋转、下载、画中画、放大缩小功能

发布于:2024-03-29 ⋅ 阅读:(34) ⋅ 点赞:(0)

使用video.js实现视频播放功能

效果图 - 这里以弹窗展示为例

在这里插入图片描述
在这里插入图片描述

注意:记得安装video.js插件!!!

在这里插入图片描述

代码

父级使用:
在这里插入图片描述

videoPreview.vue文件

<!-- 视频预览组件 -->
<template>
  <el-dialog
    id="previewFileDialog"
    title="预览"
    :visible.sync="baseDialogVisible"
    :append-to-body="true"
    :close-on-click-modal="false"
    :close-on-press-escape="false"
    :class="isFull ? 'video-preview-dialog-isFull' : 'video-preview-dialog'"
  >
      <videoJs
        :options="videoOptions"
        class="video-box"
        @isFullscreen="isFullscreen"/>
  </el-dialog>
</template>

<script>
import 'video.js/dist/video-js.css';
import videoJs from "@/components/videoPreview/videoJs";

export default {
  name: "index",
  components: {
    videoJs
  },
  props: {
    viewFileUrl: { // 视频url
      type: String,
      default:''
    },
    dialogVisible:{
      type: Boolean,
      default:false
    },
    width: { // 视频宽
      type: String,
      default:'1240'
    },
    height: { // 视频高
      type: String,
      default:'760'
    },
  },
  data(){
    return{
      isFull: false, // 是否全屏
      videoOptions:{
        controls: true,// 开启交互,即是用户可控。
        muted: true,// 开启视频时是否静音
        autoplay:true, // 自动播放
        language: "zh-CN",
        // fluid: true,// 根据外层css样式大小,自动填充宽高!比较实用,可搭配响应式
        reload: "auto",// 重载
        // 其余设置根据需求添加!
        poster: '',// 视频封面
        sources: [// 视频播放源,建议本地
          {
            src: this.viewFileUrl,
            type: "video/mp4"
          }
        ],
        userActions: {
          doubleClick: false, // 限制双击
        }
      }
    }
  },
  computed: {
    baseDialogVisible: {
      get() {
        return this.dialogVisible
      },
      set(val) {
        this.$emit('update:dialogVisible', val) // visible 改变的时候通知父组件-记得父组件使用:dialogVisible.sync实现
      }
    },
  },
  mounted() {
    this.initWH();
    window.addEventListener('resize', this.calculateDialogTop)
  },

  methods:{
    initWH(){
      let dom = document.querySelector('.video-preview-dialog .el-dialog');
      const pageHeight = window.innerHeight;
      dom.style.width = `${this.width}px`;
      dom.style.height = `${this.height}px`;
      let top = (pageHeight - this.height) / 2;
      dom.style.marginTop = `${top}px`;
    },

    // 监听页面大小变化-更改top
    calculateDialogTop() {
      this.$nextTick(() => {
        let dom = document.querySelector('.video-preview-dialog .el-dialog');
        if (dom) {
          const windowHeight = window.innerHeight;
          const dialogTop = (windowHeight - this.height) / 2;
          const top = dialogTop > 0 ? dialogTop : 0;
          dom.style.marginTop = `${top}px`;
        }
      })
    },

    isFullscreen(state){
      this.isFull = state;
      if(!state){
        this.calculateDialogTop()
      }
    }
  },

  destroyed() {
    window.removeEventListener('resize', this.calculateDialogTop);
  },
}
</script>
<style scoped lang="scss">
.video-preview-dialog{
  ::v-deep .el-dialog{
    border-radius: 4px;
    .el-dialog__header{
      height: 44px;
      padding: 0 20px !important;
      line-height: 44px;
      .el-dialog__headerbtn{
        top: 14px;
        z-index: 9999;
      }
    }
    .el-dialog__body{
      padding: 20px;
      height: calc(100% - 44px);
      width: 100%;
      position: relative;
      .video-box{
        position: relative;
        width: 100%;
        height: 100%;
        overflow: hidden;
      }
      ::v-deep .video-js .vjs-tech{
        width: 100% !important;
        height: calc(100% * (1200 / 767));
      }
    }
  }
}
/* 全屏情况 */
.video-preview-dialog-isFull{
  ::v-deep .el-dialog{
    width: 100vw !important;
    height: 100vh !important;
    border-radius: 4px;
    margin-top: 0 !important;
    .el-dialog__header{
      display: none;
    }
    .el-dialog__body{
      padding: 0;
      height: 100vh;
      width: 100vw;
    }
  }
}
</style>

VideoJs.js文件

<template>
  <video
    ref="videoPlayer"
    id="myVideoPlayer"
    class="video-js"></video>
</template>

<script>
import videoJs from 'video.js';
import 'video.js/dist/video-js.css';
import video_zhCN from 'video.js/dist/lang/zh-CN.json';

export default {
  name: "VideoJs",
  props: {
    options: {
      type: Object,
      default() {
        return {};
      }
    },
  },
  data() {
    return {
      player: null,
      degree:0,
      isFullscreen:false,
      primitivePW:'', // 初始化父级元素大小
      primitivePH:''
    }
  },
  mounted() {
    this.init();
  },
  methods:{
    // 初始化播放器实例
    init(){
      videoJs.addLanguage('zh-CN', video_zhCN); // 设置为中文
      this.player = videoJs(this.$refs.videoPlayer, this.options, function onPlayerReady() {}) // 挂载到this.player
      this.customizeDom(this.player);
      this.handleFullscreenChange(this.player);
      this.handlePauseChange(this.player);
      let dom = document.querySelector('.video-preview-dialog .el-dialog .el-dialog__body .video-js');
      this.primitivePW = dom.clientWidth;
      this.primitivePH = dom.clientHeight;
    },

    // 监听全屏事件
    handleFullscreenChange(_player){
      _player.on('fullscreenchange', () => {
        if(_player.isFullscreen()) {
          // 进入全屏模式
          this.isFullscreen = true;
          this.changeWH(_player,this.isFullscreen)
        } else {
          // 退出全屏模式
          this.isFullscreen = false;
          this.changeWH(_player,this.isFullscreen)
        }
        this.$emit('isFullscreen',this.isFullscreen);
        this.customizeClose()
      });
    },

    // 控制暂停/播放的大按钮显隐
    handlePauseChange(_player){
      let bigPlayButton = document.getElementsByClassName('vjs-big-play-button')[0];
      bigPlayButton.style.display = 'none'
      _player.on('pause', () => {
        bigPlayButton.style.display = 'block'
      })
      _player.on('play', () => {
        bigPlayButton.style.display = 'none'
      })
    },

    onClose(){
      if (document.exitFullscreen) {
        document.exitFullscreen();
      } else if (document.webkitCancelFullScreen) {
        document.webkitCancelFullScreen();
      } else if (document.mozCancelFullScreen) {
        document.mozCancelFullScreen();
      } else if (document.msExitFullscreen) {
        document.msExitFullscreen();
      }
    },

    // 自定义关闭icon的显隐-全屏显示
    customizeClose(type = this.isFullscreen){
      let closeButton = this.player.el_.getElementsByClassName('close-icon')[0]
      closeButton.style.display = type ? 'flex' : 'none';
    },

    // 自定义了播放器的控制栏
    customizeDom(_player){
      let that = this;
      let Button = videoJs.getComponent('Button');
      const Component = videoJs.getComponent("Component");
      const CloseButton = videoJs.getComponent('CloseButton');

      /*
      该类继承自Video.js插件库中的Button类
      子类的构造函数只是简单地调用了父类的构造函数并没有进行其他操作
      */
      class downloadButton extends Button {
        constructor(player, options = {}) {
          super(player, options);
          this.controlText("下载"); // 内容text
        }
        handleClick() { // 下载触发
          let url = _player.options_.sources[0].src;
          that.downloadFile(url)
        }
      }
      class rotationButton extends Button {
        constructor(player, options = {}) {
          super(player, options);
          this.controlText("旋转"); // 内容text
        }
        handleClick() { // 旋转触发
          that.applyRotation(_player)
        }
      }
      CloseButton.prototype.handleClick = () => this.onClose(); // close按钮事件执行
      let closeButton = new CloseButton(this.player);
      this.player.addChild(closeButton); // 添加close按钮
      Component.registerComponent("downloadButton", downloadButton); // 注册为Component.registerComponent方法的组件
      Component.registerComponent("rotationButton", rotationButton);
      let downloadDom = that.player.getChild('controlBar').addChild('downloadButton', {}, 17); // 组件的实例-向控制栏添加下载按钮
      let rotationDom = that.player.getChild('controlBar').addChild('rotationButton', {}, 17); // 组件的实例-向控制栏添加旋转按钮
      downloadDom.addClass("download-icon"); // 添加class
      rotationDom.addClass("rotation-icon");
      closeButton.addClass("close-icon");
    },

    // 动态改变视频的宽高
    changeWH(_player,isFullscreen = this.isFullscreen){
      // 获取视频元素和父级容器元素
      let video = _player.el_.getElementsByTagName('video')[0];
      let container = _player.el_;

      // 获取父容器的宽高
      const containerWidth = !isFullscreen ? this.primitivePW : container.offsetWidth;
      const containerHeight = !isFullscreen ? this.primitivePH : container.offsetHeight;

      // 获取视频原始的宽高
      const videoWidth = video.videoWidth;
      const videoHeight = video.videoHeight;

      // 计算视频旋转后的宽高
      const angle = (this.degree || 0) * Math.PI / 180;
      const rotatedWidth = Math.abs(Math.cos(angle) * videoWidth) + Math.abs(Math.sin(angle) * videoHeight);
      const rotatedHeight = Math.abs(Math.sin(angle) * videoWidth) + Math.abs(Math.cos(angle) * videoHeight);

      // 根据父容器的宽高和视频旋转后的宽高计算缩放比例
      const scaleX = containerWidth / rotatedWidth;
      const scaleY = containerHeight / rotatedHeight;
      const scale = Math.min(scaleX, scaleY);

      // 设置视频元素的宽高
      let wd = rotatedWidth * scale;
      let hg = rotatedHeight * scale;

      let resWidth,resHeight;
      if(this.degree == 90 || this.degree == 270){
        let d = Math.max(wd, hg);
        resHeight = resWidth = d;
      }else{
        resHeight = hg;
        resWidth = wd;
      }
      video.style.width = `${resWidth}px`;
      video.style.height = `${resHeight}px`;
      video.style.left = `${(containerWidth - resWidth) / 2}px`;
      video.style.top = `${(containerHeight - resHeight) / 2}px`;
    },

    // 旋转事件
    applyRotation(_player) {
      this.degree = this.degree === 360 ? 90 : this.degree + 90
      let videoElement = _player.el_.getElementsByTagName('video')[0]; // 视频元素
      videoElement.style.transform = `rotate(${this.degree}deg)`;
      this.changeWH(_player)
    },

    // 下载
    downloadFile(url) {
      // const segments = url.split('/');
      // const lastSegment = segments.pop();
      // const link = document.createElement('a');
      // link.href = url;
      // link.download = lastSegment;
      // document.body.appendChild(link);
      // link.click();
      // document.body.removeChild(link);
      fetch(url).then(res => res.blob()).then(blob => { // 将链接地址字符内容转变成blob地址
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = '';
        link.target = '_blank';
        document.body.appendChild(link);
        link.click();
        link.remove();
      }).catch(() => {
        alert('下载文件失败');
      });
    }
  },

  beforeDestroy() {
    if (this.player) {
      this.player.dispose()
    }
  }
}
</script>
<style scoped lang="scss">
::v-deep .download-icon {
  cursor: pointer;
  height: 28px;
  background: url('~@/assets/download.png') center no-repeat;
  background-size: 18px;
}
::v-deep .rotation-icon{
  cursor: pointer;
  height: 28px;
  background: url('~@/assets/order/rotation.png') center no-repeat;
  background-size: 18px;
}
::v-deep .close-icon{
  background: url('~@/assets/order/video-close.png') center no-repeat;
  background-size: 36px;
  height: 36px !important;
  display: none;
  right: 60px !important;
  top: 60px !important;
}
::v-deep .vjs-close-button .vjs-icon-placeholder::before {
  content: none !important;
}
/* 更改全屏按钮大小 */
::v-deep .vjs-fullscreen-control .vjs-icon-placeholder:before {
  font-size: 2.1em;
  line-height: 1.5em;
}
</style>


到这里就完啦!!!后面如果接触到新需求有优化会持续更新哒
提醒自己:这是有段时间之前的代码逻辑了,或许明天的你就有不一样的想法呢!所以仅供参考

本文含有隐藏内容,请 开通VIP 后查看