移动端上传图片&视频查看预览

225 阅读3分钟
1.视频播放组件
<template>
  <div
    class="m-video"
    :class="{ 'u-video-hover': !hidden, 'u-video-play': play }"
    :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()"
      @pause="showPlay ? onPause() : () => false"
      @playing="showPlay ? onPlaying() : () => false"
      @click.prevent="onPlay"
      v-bind="$attrs"
    >
      您的浏览器不支持video标签。
    </video>
    <img
      v-show="play"
      src="@/assets/applyForOil/icon_close_youweweima.png"
      alt=""
      class="close-btn"
      @click="closeHandle()"
    />
    <svg
      v-show="originPlay"
      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>
</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: 100,
    },
    height: {
      // 视频播放器高度,单位 px
      type: [String, Number],
      default: 100,
    },

    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: 36,
    },
    zoom: {
      // video的poster默认图片和视频内容缩放规则
      type: String,
      default: "contain", // none:(默认)保存原有内容,不进行缩放; fill:不保持原有比例,内容拉伸填充整个内容容器; contain:保存原有比例,内容以包含方式缩放; cover:保存原有比例,内容以覆盖方式缩放
    },
  },
  data() {
    return {
      veoPoster: this.poster,
      originPlay: true,
      play: false,
      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: {
    /*
      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() {
      this.play = true;
      if (this.originPlay) {
        this.$refs.veoRef.currentTime = 0;
        // this.originPlay = false;
        this.$refs.veoRef.play();
      }
      if (this.autoplay) {
        this.$refs.veoRef.pause();
      } else {
        this.hidden = true;
        this.$refs.veoRef.play();
      }
    },
    closeHandle() {
      this.play = false;
    },
    onPause() {
      this.hidden = false;
    },
    onPlaying() {
      this.hidden = true;
    },
  },
};
</script>
<style lang="scss" scoped>
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
.close-btn {
  position: absolute;
  right: 20px;
  width: 30px;
  height: 30px;
  top: 30px;
  z-index: 9999;
}
.u-video-play {
  position: fixed !important;
  top: 0;
  left: 0;
  width: 100vw !important;
  height: 100vh !important;
  z-index: 999;
}
.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>

2.组件调用
 <van-field
          class="uploader"
          name="uploader"
          label="举证照片/视频"
          required
        >
          <template #input>
            <span class="tooltip">(至上少上传3张照片或3段视频)</span>
            <ul class="uploader-warp">
              <li
                class="uploader-item"
                v-for="(item, index) in uploader"
                :key="index"
              >
                <van-image
                  v-if="item.type == 'pic'"
                  :src="item.url"
                  class="van-image"
                >
                  <template v-slot:loading>
                    <van-loading type="spinner" size="20" />
                  </template>
                </van-image>
                <Video v-else :src="item.url" :second="3" />
                <!-- <vueVideoPlayer :src="item.url" /> -->
                <img
                  src="@/assets/applyForOil/icon_close_youweweima.png"
                  alt=""
                  class="deleet"
                  @click="deleteHandle(item, index)"
                />
              </li>
              <li class="uploader-item" v-if="showIs" @click="handleUpload">
                <div class="uploader-box">
                  <div class="uploader-pic">
                    <img src="@/assets/applyForOil/icon_add.png" alt="" />
                    <p>上传</p>
                  </div>
                </div>
              </li>
            </ul>
          </template>
        </van-field>
        
        ============================
           // 上传图片
    handleUpload() {
      // console.log("320", file)
      //   this.showPreloader("上传中...");
      lightAppJssdk.media.chooseVideoAndPic({
        arg: "",
        success: (data) => {
          //   this.hidePreloader();
          //成功回调
          if (data.result == "true") {
            if (data.videoPath) {
              this.uploader.push({
                url: data.videoPath,
                type: "video",
              });
            } else {
              this.uploader.push({
                url: data.picPath[0],
                type: "pic",
              });
            }

            if (this.uploader.length >= 5) {
              this.showIs = false;
            }
          }
        },
        fail: (data) => {
          console.log("fail", data);
          this.$toast({
            message: "相机/相册调用失败",
            icon: "close",
          });
          //错误返回
        },
      });
    },