vue——vue中使用oss加el-upload分片上传视频组件封装


主题思路

1.封装视频播放组件。
2.封装视频上传组件。
3.如何使用。


一、封装视频播放组件。

在components公共组件里新建Videoback文件夹创建index.vue。

代码如下(示例):

<template>
  <el-dialog
    v-if="dialogVisible"
    title="预览视频"
    :close-on-click-modal="false"
    :visible.sync="dialogVisible"
    :width="width + 40 + 'px'"
  >
    <div
      class="m-video"
      :class="{ 'u-video-hover': !hidden }"
      :style="`width: ${veoWidth}; height: ${veoHeight};`"
    >
      <video
        ref="veoRef"
        class="u-video"
        :style="`object-fit: ${zoom};`"
        :src="src"
        :poster="veoPoster"
        :autoplay="autoplay"
        :controls="!originPlay && controls"
        :loop="loop"
        :muted="autoplay || muted"
        :preload="preload"
        crossorigin="anonymous"
        @loadeddata="poster ? () => false : getPoster()"
        @loadedmetadata="onVideoLoadedMetadata"
        @timeupdate="onTimeUpdate"
        @pause="showPlay ? onPause() : () => false"
        @playing="showPlay ? onPlaying() : () => false"
        @click.prevent.once="onPlay"
        v-bind="$attrs"
      >
        您的浏览器不支持video标签。
      </video>
      <svg
        v-show="originPlay || showPlay"
        class="u-play"
        :class="{ hidden: hidden }"
        :style="`width: ${playWidth}px; height: ${playWidth}px;`"
        viewBox="0 0 24 24"
      >
        <path
          stroke-linecap="round"
          stroke-linejoin="round"
          stroke-width="1.5"
          d="M4.75 6.75C4.75 5.64543 5.64543 4.75 6.75 4.75H17.25C18.3546 4.75 19.25 5.64543 19.25 6.75V17.25C19.25 18.3546 18.3546 19.25 17.25 19.25H6.75C5.64543 19.25 4.75 18.3546 4.75 17.25V6.75Z"
        ></path>
        <path
          stroke-linecap="round"
          stroke-linejoin="round"
          stroke-width="1.5"
          d="M15.25 12L9.75 8.75V15.25L15.25 12Z"
        ></path>
      </svg>
    </div>
  </el-dialog>
</template>
<script>
export default {
  name: "Video",
  props: {
    src: {
      // 视频文件url,必传,支持网络地址 https 和相对地址 require('@/assets/files/Bao.mp4')
      type: String,
      required: true,
      default: ""
    },
    poster: {
      // 视频封面url,支持网络地址 https 和相对地址 require('@/assets/images/Bao.jpg')
      type: String,
      default: ""
    },
    second: {
      // 在未设置封面时,自动截取视频第 second 秒对应帧作为视频封面
      type: Number,
      default: 0.5
    },
    width: {
      // 视频播放器宽度,单位 px
      type: [String, Number],
      default: 800
    },
    height: {
      // 视频播放器高度,单位 px
      type: [String, Number],
      default: 450
    },
    /*
      参考 MDN 自动播放指南:https://developer.mozilla.org/zh-CN/docs/Web/Media/Autoplay_guide
      Autoplay 功能
      据新政策,媒体内容将在满足以下至少一个的条件下自动播放:
      1.音频被静音或其音量设置为 0
      2.用户和网页已有交互行为(包括点击、触摸、按下某个键等等)
      3.网站已被列入白名单;如果浏览器确定用户经常与媒体互动,这可能会自动发生,也可能通过首选项或其他用户界面功能手动发生
      4.自动播放策略应用到<iframe>或者其文档上
      autoplay:由于目前在最新版的Chrome浏览器(以及所有以Chromium为内核的浏览器)中,
      已不再允许自动播放音频和视频。就算你为video或audio标签设置了autoplay属性也一样不能自动播放!
      解决方法:设置视频 autoplay 时,视频必须设置为静音 muted: true 即可实现自动播放,
      然后用户可以使用控制栏开启声音,类似某宝商品自动播放的宣传视频逻辑
    */
    autoplay: {
      // 视频就绪后是否马上播放,优先级高于 preload
      type: Boolean,
      default: false
    },
    controls: {
      // 是否向用户显示控件,比如进度条,全屏等
      type: Boolean,
      default: true
    },
    loop: {
      // 视频播放完成后,是否循环播放
      type: Boolean,
      default: false
    },
    muted: {
      // 是否静音
      type: Boolean,
      default: false
    },
    preload: {
      // 是否在页面加载后载入视频,如果设置了autoplay属性,则preload将被忽略;
      type: String,
      default: "metadata" // auto:一旦页面加载,则开始加载视频; metadata:当页面加载后仅加载视频的元数据 none:页面加载后不应加载视频
    },
    showPlay: {
      // 播放暂停时是否显示播放器中间的暂停图标
      type: Boolean,
      default: true
    },
    playWidth: {
      // 中间播放暂停按钮的边长
      type: Number,
      default: 96
    },
    zoom: {
      // video的poster默认图片和视频内容缩放规则
      type: String,
      default: "contain" // none:(默认)保存原有内容,不进行缩放; fill:不保持原有比例,内容拉伸填充整个内容容器; contain:保存原有比例,内容以包含方式缩放; cover:保存原有比例,内容以覆盖方式缩放
    }
  },
  data() {
    return {
      dialogVisible: false,
      veoPoster: this.poster,
      originPlay: true,
      hidden: false
    };
  },
  computed: {
    veoWidth() {
      if (typeof this.width === "number") {
        return this.width + "px";
      }
      return this.width;
    },
    veoHeight() {
      if (typeof this.height === "number") {
        return this.height + "px";
      }
      return this.height;
    }
  },
  mounted() {
    if (this.autoplay) {
      this.hidden = true;
      this.originPlay = false;
    }
    /*
      自定义设置播放速度,经测试:
      在vue2中需设置:this.$refs.veoRef.playbackRate = 2
      在vue3中需设置:veo.value.defaultPlaybackRate = 2
    */
    // this.$refs.veoRef.playbackRate = 2
  },
  methods: {
    show() {
      this.dialogVisible = true;
    },
    /*
      loadedmetadata 事件在元数据(metadata)被加载完成后触发
      loadeddata 事件在媒体当前播放位置的视频帧(通常是第一帧)加载完成后触发
        若在移动/平板设备的浏览器设置中开启了流量节省(data-saver)模式,该事件则不会被触发。
      preload 为 none 时不会触发
    */
    getPoster() {
      // 在未设置封面时,自动获取视频0.5s对应帧作为视频封面
      // 由于不少视频第一帧为黑屏,故设置视频开始播放时间为0.5s,即取该时刻帧作为封面图
      this.$refs.veoRef.currentTime = this.second;
      // 创建canvas元素
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");
      // canvas画图
      canvas.width = this.$refs.veoRef.videoWidth;
      canvas.height = this.$refs.veoRef.videoHeight;
      ctx.drawImage(this.$refs.veoRef, 0, 0, canvas.width, canvas.height);
      // 把canvas转成base64编码格式
      this.veoPoster = canvas.toDataURL("image/png");
    },
    onPlay() {
      if (this.originPlay) {
        this.$refs.veoRef.currentTime = 0;
        this.originPlay = false;
      }
      if (this.autoplay) {
        this.$refs.veoRef.pause();
      } else {
        this.hidden = true;
        this.$refs.veoRef.play();
      }
    },
    onPause() {
      this.hidden = false;
      // console.log("视频暂停播放");
    },
    onPlaying() {
      this.hidden = true;
    },

    /** 获取视频长度 */
    onVideoLoadedMetadata() {
      const duration = this.$refs.veoRef.duration;
      // console.log(`视频长度: ${duration} 秒`);
    },

    // 视频播放进度更新时的处理函数
    onTimeUpdate(event) {
      const playedSeconds = event.target.currentTime;
      // console.log(`已播放: ${playedSeconds} 秒`);
    }
  }
};
</script>
<style lang="scss" scoped>
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
.m-video {
  display: inline-block;
  position: relative;
  background: #000;
  cursor: pointer;
  .u-video {
    display: inline-block;
    width: 100%;
    height: 100%;
    vertical-align: bottom;
  }
  .u-play {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    margin: auto;
    fill: none;
    color: #fff;
    pointer-events: none;
    opacity: 0.7;
    transition: opacity 0.3s;
    path {
      stroke: #fff;
    }
  }
  .hidden {
    opacity: 0;
  }
}
.u-video-hover {
  &:hover {
    .u-play {
      opacity: 0.9;
    }
  }
}
</style>

二、封装视频上传组件。

1.配置oss,在utils文件夹中创建oss.js

代码如下(示例):

let OSS = require("ali-oss");

let client = new OSS({
  region: "oss-cn-hangzhou", // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
  accessKeyId: "",
  accessKeySecret: "",
  authorizationV4: true,
  bucket: "", // 填写Bucket名称,如“baidupro-oss”
});

let cdnUrl = ""; // 文件上传后的回调地址

export { client, cdnUrl };

2.在components公共组件里新建Updata文件夹创建Upvideo.vue。

代码如下(示例):

<template>
  <div class="upda fx">
    <!-- 视频可拖拽 -->
    <div class="col" v-if="videoUrled.length != 0" style="margin-right: 10px">
      <div>
        <draggable
          :disabled="disabled"
          v-model="videoUrled"
          filter=".forbid"
          animation="300"
          @update="onMove"
        >
          <transition-group>
            <div
              v-for="(item, index) in videoUrled"
              :key="index"
              style="display: inline-block; margin-right: 10px"
            >
              <div class="img-hover img_div">
                <img
                  :src="nourl ? item : item.veoPoster"
                  alt="加载中..."
                  style="width: 100px; height: 100px"
                  class="imgCSSS"
                />

                <div class="mask">
                  <h3 style="line-height: 50px">
                    <span
                      style="margin-right: 10px"
                      v-show="!disabled"
                      class="el-icon-delete click_fly"
                      @click="handleRemove(index)"
                    ></span>
                    <span
                      v-show="!preopen"
                      class="el-icon-video-play click_fly"
                      @click="handlePreview(index)"
                    ></span>
                  </h3>
                </div>
              </div>
            </div>
          </transition-group>
        </draggable>
      </div>
      <div style="margin-top: -15px">{{ UpvideItem.name }}</div>
    </div>
    <el-upload
      ref="upload"
      accept="mp4/*"
      :before-upload="handleBeforeUpload"
      v-if="videoUrled.length != vidnumber && !loading"
      :disabled="disabled"
      action=""
      :http-request="beforeUpload"
      list-type="picture-card"
      name="file"
      :auto-upload="true"
      :show-file-list="false"
      :on-progress="handleExceed"
      :on-success="
        (response, file) => {
          onSuccess(response, file);
        }
      "
    >
      <i slot="default" class="el-icon-plus" />
    </el-upload>

    <div class="loadCss" v-if="loading">
      <el-progress
        type="circle"
        :percentage="progresNum"
        :width="99"
      ></el-progress>
      <div class="absdiv" @click="OnisCancel">取消上传</div>
    </div>
    
	<!-- 用于视频回显时canvas封面 -->
    <div v-show="false">
      <video
        v-if="UpvideItem.url != ''"
        ref="veoRef"
        class="u-video"
        :src="UpvideItem.url + '#t=1'"
        :autoplay="false"
        :muted="true"
        preload="metadata"
        crossorigin="anonymous"
        @loadeddata="getPoster()"
        v-bind="$attrs"
      >
        您的浏览器不支持video标签。
      </video>
    </div>
    <!-- 第一步的视频播放组件 -->
    <Videoback ref="videoer" :src="UpvideItem.url" :second="0" />
  </div>
</template>

<script>
import { client, cdnUrl } from "@/utils/oss.js";
import { wholeUrl } from "@/api/phpUrl"; //引入接口
import draggable from "vuedraggable"
import { mapGetters } from "vuex";
import Videoback from "@/components/Videoback/index.vue";
export default {
  name: "UpvideoUrl",
  components: {
    Videoback, //视频播放,
    draggable,
  },
  props: {
    // 最多上传视频数量
    vidnumber: {
      type: Number,
      default: 1,
    },
    //是否禁用
    disabled: {
      type: Boolean,
      default: null,
    },
    //是否可预览
    preopen: {
      type: Boolean,
      default: null,
    },
    //没有key
    nourl: {
      type: Boolean,
      default: false,
    },
    //视频回显列表
    videoUrl: {
      type: Array,
      default() {
        return [];
      },
    },
    //限制上传视频大小
    videoSize: {
      type: Number,
      default: 200,
    },
  },
  computed: {},
  data() {
    return {
      loading: false,
      Actions: wholeUrl + "/basis/videoUpload",
      phpUrl: window.localStorage.getItem("phpBaseUrl"),
      //上传视频返回数据
      UpvideItem: {
        videoSize: "", //视频大小
        name: "", //视频名称
        url: "", //视频地址
        videoTime: "", //视频的时长
        videoWidth: "", //视频的宽
        videoHeight: "", //视频的高
        veoPoster: "", //视频展示图
      },

      // 视频放大显示
      dialogImageUrl: "",
      videoUrled: this.videoUrl,

      isCancel: false, //取消上传,
      progresNum: 0, //上传进度
    };
  },
  watch: {
    videoUrl: {
      handler(val) {
        this.videoUrled = val;
        if (val.length > 0) {
          console.log(val, "val");
          Object.assign(this.UpvideItem, val[0]);
        }
      },
      deep: true,
    },
  },
  methods: {
    handleExceed(file) {
      if (file.loaded > 1000000 * this.videoSize) {
        this.$refs.upload.abort();
      }
    },
    // 视频上传回显
    async onSuccess(response, file) {
      await Object.assign(this.UpvideItem, this.$options.data().UpvideItem);
      if (!response) return;
      if (response.code != 200) return this.$message.error(response.message);
      if (this.nourl) {
        this.videoUrl.push(response.data.full_url);
      } else {
        this.videoUrl.push({ url: response.data.full_url });
      }
      this.$emit("changVideo");
      this.UpvideItem.url = response.data.full_url;
      this.UpvideItem.name = file.name;
      this.UpvideItem.videoSize = (file.size / (1024 * 1024)).toFixed(2); // 将字节转换为MB;
    },
    /** 用canvas获取视频的封面 */
    getPoster() {
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");
      // canvas画图
      canvas.width = this.$refs.veoRef.videoWidth;
      canvas.height = this.$refs.veoRef.videoHeight;
      ctx.drawImage(this.$refs.veoRef, 0, 0, canvas.width, canvas.height);
      // 把canvas转成base64编码格式
      this.UpvideItem.veoPoster = canvas.toDataURL("image/png");
      this.UpvideItem.videoTime = this.$refs.veoRef.duration; // 获取视频的时长
      this.UpvideItem.videoWidth = canvas.width; //获取视频的宽
      this.UpvideItem.videoHeight = canvas.height; //获取视频的高
      this.videoUrl.splice(0, 1);
      this.videoUrl.push(this.UpvideItem);
      console.log(this.UpvideItem, "视频的资料");
    },
    //拖拽视频
    onMove() {
      this.$emit("Upimgsun", this.videoUrled);
    },
    //删除视频
    async handleRemove(index) {
      this.videoUrl.splice(index, 1);
      await Object.assign(this.UpvideItem, this.$options.data().UpvideItem);
      this.$emit("changeimge");
    },
    //预览视频
    async handlePreview(index) {
      await this.$nextTick(() => {
        this.$refs.videoer.show();
      });
    },
    // 判断上传的是否为视频
    handleBeforeUpload(file) {
      if (file.size > 1000000 * this.videoSize) {
        this.$message.error(`上传视频最大不能超过${this.videoSize}MB`);
        return false;
      }
      var img = file.name.substring(file.name.lastIndexOf(".") + 1);
      const suffix = img.toLowerCase() === "mp4";
      const suffix2 = img.toLowerCase() === "mov";
      if (!suffix && !suffix2) {
        this.$message.error("上传视频文件格式支持尺寸为.mp4、.mov");
        return false;
      }
    },
    // 上传
    async beforeUpload(file) {
      this.secureUpload(file);
    },
    async secureUpload(file) {
      this.loading = true;
      this.isCancel = false;
      //判断扩展名
      const tmpcnt = file.file.name.lastIndexOf(".");
      const exname = file.file.name.substring(tmpcnt + 1);
      //  配置路径以及文件名称
      const fileName = "files/" + file.file.uid + "." + exname; //'files/'为oss上传路径
      const OSS_DOMAIN = "https://sphuix.com/"; //视频回显地址
      try {
        const result = await client.multipartUpload(fileName, file.file, {
          progress: (p) => {
            console.log(`进度: ${(p * 100).toFixed(2)}%`);
            this.progresNum = Number((p * 100).toFixed(2));
            if (this.isCancel) {
              console.log("取消上传");
              client.cancel();
            }
          },
          partSize: 1024 * 1024, // 1MB分片
          meta: { "x-oss-forbid-overwrite": "true" }, // 禁止覆盖
          headers: { "Access-Control-Expose-Headers": "etag" }, // 显式声明
        });
        console.log("上传成功", result);
        if (result.res.status == 200) {
          this.$message.success("上传成功");
          this.OnisCancel();
          if (this.nourl) {
            this.videoUrl.push(OSS_DOMAIN + fileName);
          } else {
            this.videoUrl.push({ url: OSS_DOMAIN + fileName });
          }
          this.$emit("changVideo");
          this.UpvideItem.url = OSS_DOMAIN + fileName;
          this.UpvideItem.name = file.file.name;
          this.UpvideItem.videoSize = (file.file.size / (1024 * 1024)).toFixed(
            2
          ); // 将字节转换为MB;
        }
      } catch (err) {
        console.error("失败:", err);
      }
    },
    /** 取消上传 */
    OnisCancel() {
      this.isCancel = true;
      this.loading = false;
      this.progresNum = 0;
    },
  },
};
</script>
<style lang="scss" scoped>
::v-deep .el-upload-list--picture-card .el-upload-list__item {
  width: 100px;
  height: 100px;
}
::v-deep .el-upload--picture-card {
  width: 100px;
  height: 100px;
  line-height: 100px;
}
.mask {
  z-index: 10;
  position: absolute;
  background: rgba(101, 101, 101, 0.6);
  color: #ffffff;
  opacity: 0;
  top: 0;
  left: 0;
  width: 100px;
  height: 100px;
  border-radius: 10px;
  // pointer-events: none;
}
.mask h3 {
  text-align: center;
  margin-top: 25%;
}
.img_div:hover .mask {
  opacity: 1;
}
.img_div {
  border-radius: 10px;
  display: block;
  position: relative;
}
.loadse {
  width: 100px;
  height: 100px;
  border-radius: 10px;
  border: 1px dashed #c0ccda;
}
.loadCss {
  width: 100px;
  height: 100px;
  background-color: #fbfdff;
  border: 1px dashed #c0ccda;
  border-radius: 6px;
  position: relative;
  .absdiv {
    position: absolute;
    bottom: 15px;
    width: 100px;
    text-align: center;
    cursor: pointer;
    color: red;
  }
}
::v-deep .el-progress--circle .el-progress__text {
  top: 38%;
}
</style>

三、如何使用。

1.有视频需要回显,在Upvideo组件上定义 ref=“Upvideo”,然后在接受到视频链接后调用 this.$refs.Upvideo.getPoster()。

注意:我这里定义的入参是数组,videoUrl是数组。

<Upvideo
   :videoUrl="videoUrl"
   ref="Upvideo"
 />

2.上传完成后,videoUrl也是数组。

3.注意:配置oss控制台。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值