vue3上传视频

90 阅读5分钟
<template>
  <div class="dyx">
    <div class="my-header">
      <div class="back-wrap"><span class="iconfont icon-dayuhao3" @click="huitui"></span></div>
      <div><span>
          发布动态
        </span></div>
    </div>
    <div class="content">
      <div class="video-wrap">
        <video src="" x5-playsinline="" playsinline="" webkit-playsinline="" preload="auto" class="video"
          ref="getVideoDom" @click="playHandler"></video>
        <p class="video-guide" v-show="xin">点击上传或者在下方输入url,推荐使用url</p>
        <input type="file" id="file" accept="video/*" class="video-input" @change="change">
      </div>
      <div class="content-item"><input placeholder="请输入视频链接(如本地上传可不填)" type="text" class="input"
          v-model="videoObj.videoUrl"></div>
      <div class="content-item" ref="yincang"><input placeholder="请输入封面链接(如本地上传默认第一帧)" type="text" class="input"></div>
      <div class="content-item"><textarea placeholder="请输入视频描述" rows="10" cols="30" class="input"></textarea>
      </div>
      <div class="content-item">
        <div class="btn">预览</div>
        <div class="btn" @click="upload">发布</div>
      </div>
    </div>
    <div class="des" v-show="lodingdh">
      <div id="cssLoader17" class="main-wrap main-wrap--white">
        <div class="cssLoader17"></div>
      </div>
      <div id="cssLoader1" class="main-wrap">
        <div class="child-common child1"></div>
        <div class="child-common child2"></div>
        <div class="child-common child3"></div>
        <div class="child-common child4"></div>
        <div class="child-common child5"></div>
        <div class="child-common child6"></div>
        <div class="child-common child7"></div>
        <div class="child-common child8"></div>
        <div class="child-common child9"></div>
        <div class="child-common child10"></div>
      </div>

    </div>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
// 三个上传视频接口
import { upLoadVideoAPI, upLoadCoverVideoAPI, publishVideoAPI } from '../api/upLoadVideo.js'

let videoObj = reactive({
  videoDesc: "",
});
let getVideoDom = ref(null);
let yincang = ref(null);
let lodingdh = ref(false);
let router = useRouter()
function huitui() {
  router.go(-1)
}

let xin = ref(true)
// 文件类型检测
function checkFileType(file, Callback) {
  // 创建 FileReader 对象
  var reader = new FileReader();
  // 读取文件
  reader.readAsArrayBuffer(file);
  // 文件读取完成时触发
  reader.onloadend = function () {
    // 获取文件的类型
    var type = getFileType(new Uint8Array(reader.result));
    // 判断文件类型
    Callback(type)
  };
}
// 监听用户选择文件的操作
function change(e) {
  let files = e.target.files
  // 如果没有选择文件终止后续代码的执行
  if (!files.length) return;
  // 把文件对象信息给到videoObj.realVideo
  videoObj.realVideo = files[0];
  // 判断选中的文件类型
  checkFileType(videoObj.realVideo, (type) => {
    if (type !== "mp4") {
      alert("文件类型不是mp4格式,别想糊弄我");
      return
    }
    // 限制文件上传大小
    if (videoObj.realVideo.size > 100 * 1024 * 1024) {
      alert("上传的视频不能大于2MB");
      return;
    }
    // 该方法返回一个blob视频地址
    videoObj.videoUrl = getObjectURL(videoObj.realVideo);
    setSrcAndCaptureImage();

  })
}
// 根据文件头判断文件类型
function getFileType(bytes) {
  if (
    bytes[0] === 0x00 &&
    bytes[1] === 0x00 &&
    bytes[2] === 0x00 &&
    bytes[3] === 0x20 &&
    bytes[4] === 0x66
  ) {
    return "mp4";
  } else {
    return "unknown";
  }
}
// 获取视频地址
function getObjectURL(file) {
  let url = null;
  if (window.createObjectURL !== undefined) {
    url = window.createObjectURL(file);
  } else if (window.URL !== undefined) {
    url = window.URL.createObjectURL(file);
  } else if (window.webkitURL !== undefined) {
    url = window.webkitURL.createObjectURL(file);
  }
  return url;
}
// 设置url和截图
function setSrcAndCaptureImage() {
  xin.value = false
  // 视频地址赋值给video的src属性
  getVideoDom.value.src = videoObj.videoUrl;
  if (getVideoDom.value.src !== '') {
    yincang.value.style = 'display:none;height:0px;'
  }
  // loadeddata当前帧的数据是可用的
  getVideoDom.value.addEventListener("loadeddata", captureImage);
  // 截图视频的第一帧作为封面图
  function captureImage() {
    // 自动播放
    getVideoDom.value.play();
    let scale = 1;
    // 创建canvas画布
    let canvas = document.createElement("canvas");
    // canvas画布大小宽度设置
    canvas.width = getVideoDom.value.videoWidth * scale;
    // canvas画布大小宽度设置
    canvas.height = getVideoDom.value.videoHeight * scale;
    // 开启2D绘图空间 drawImage()方法绘制一张图像
    canvas
      .getContext("2d")
      .drawImage(getVideoDom.value, 0, 0, canvas.width, canvas.height);
    // 返回一个包含图片展示的 data URI可以理解为是base64编码的图片
    videoObj.coverUrl = canvas.toDataURL("image/png");
    console.log(videoObj);
    // 移除事件
    getVideoDom.value.removeEventListener("loadeddata", captureImage);
  }
}
// 视频播放/暂停
function playHandler() {
  getVideoDom.value.paused
    ? getVideoDom.value.play()
    : getVideoDom.value.pause();
}
// 图片转成Buffer(用来处理二进制的一个接口,后端通过fs模块,读取一张图片获取到图片的文件流)google(上传图片buffer)
function dataURItoBlob(dataURI) {
  var byteString = atob(dataURI.split(",")[1]);
  var mimeString = dataURI.split(",")[0].split(":")[1].split(";")[0];
  var ab = new ArrayBuffer(byteString.length);
  var ia = new Uint8Array(ab);
  for (var i = 0; i < byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i);
  }
  return new Blob([ab], { type: mimeString });
}
// 上传方法
async function upload() {

  // 判断是否上传了视频
  if (!videoObj.realVideo) {
    alert("请先选择要上传的文件");
    return
  }
  lodingdh.value = true
  // 把文件传递到服务器
  let formData = new FormData();
  // 后端需要videoPath参数,videoObj.realVideo是视频文件
  formData.append("videoPath", videoObj.realVideo);
  // 请求上传接口
  let result = await upLoadVideoAPI(formData);
  // 返回上传后的结果
  console.log(result);
  if (result.status == 200) {
    console.log(11111);
    let filename = result.data.filename;
    // 视频的id截取(要.前面的作为视频的id)
    const videoId = filename.substr(0, filename.indexOf("."));
    let blob = dataURItoBlob(videoObj.coverUrl);
    let coverPic = new FormData();
    coverPic.append("videoId", videoId);
    coverPic.append("videoCover", blob);
    console.log(coverPic);
    const resultCover = await upLoadCoverVideoAPI(coverPic);
    console.log(resultCover);
    if (resultCover.status === 200) {
      const publishResult = await publishVideoAPI(localStorage.getItem('userId'), {
        videoId,
        videoCover: `http://43.138.15.137:3000/assets/videoCover/${videoId}.jpg`,
        videoPath: `http://43.138.15.137:3000/assets/videoPath/${videoId}.${filename.substr(
          filename.indexOf(".") + 1
        )}`,
        // videoId代表视频名字;
        // videoId后面的.点,就是一个字符串点
        // filename.substr(filename.indexOf('.') + 1)运行结果是后缀名(mp4等等)
        videoDesc: videoObj.videoDesc,
      });
      console.log(publishResult);


      lodingdh.value = false
      router.push('/user')



    }
  }
}
</script>

<style scoped>
.des {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(41, 40, 37, 0.4);
  z-index: 2400;
}

.dyx {
  color: white;
  background-color: #161622;
  position: fixed;
  left: 0;
  top: 0;
  right: 0;
  z-index: 1600;
  width: 100vw;
  height: 100vh;
  /* background-color: red; */
}

.my-header {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  position: relative;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  color: #e8e8e9;
  height: 44px;
  line-height: 44px;
  font-size: 16px;
  z-index: 33;
  border-bottom: 0.5px solid rgba(41, 40, 37, 0.8);
  background: #161622;
}

.my-header .back-wrap {
  position: absolute;
  left: 10px;
}

.my-header .back-wrap .icon-left {
  padding: 10px;
}

.content {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
  -ms-flex-direction: column;
  flex-direction: column;
}

.content .video-wrap {
  position: relative;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  background: #000;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  height: 50vh;
}

.content .video-wrap .video {
  width: 100%;
  height: 100%;
}

.content .video-wrap .video-guide {
  position: absolute;
}

.content .video-wrap .video-input {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  opacity: 0;
}

.content .content-item {
  position: relative;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  padding: 10px 20px;
  line-height: 44px;
  height: 44px;
  -webkit-box-pack: start;
  -ms-flex-pack: start;
  justify-content: flex-start;
}

.content .content-item .input {
  width: 100%;
  background: #161622;
  color: #e8e8e9;
  border: none;
  font-size: 14px;
}

.content .content-item:last-of-type {
  -ms-flex-pack: distribute;
  justify-content: space-around;
}

.content .content-item .btn {
  padding: 5px;
  text-align: center;
  line-height: 25px;
  font-size: 12px;
  width: 70px;
  height: 25px;
}

.content .content-item .btn {
  padding: 5px;
  text-align: center;
  line-height: 25px;
  font-size: 12px;
  width: 70px;
  height: 25px;
}






.cssLoader17:before,
.cssLoader17:after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  display: block;
  width: 0.5em;
  height: 0.5em;
  border-radius: 0.25em;
  transform: translate(-50%, -50%);
}

.cssLoader17:before {
  animation: before 2s infinite;
}

.cssLoader17:after {
  animation: after 2s infinite;
}

@keyframes before {
  0% {
    width: 0.5em;
    box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75), -1em 0.5em rgba(111, 202, 220, 0.75);
  }

  35% {
    width: 2.5em;
    box-shadow: 0 -0.5em rgba(225, 20, 98, 0.75), 0 0.5em rgba(111, 202, 220, 0.75);
  }

  70% {
    width: 0.5em;
    box-shadow: -1em -0.5em rgba(225, 20, 98, 0.75), 1em 0.5em rgba(111, 202, 220, 0.75);
  }

  100% {
    box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75), -1em 0.5em rgba(111, 202, 220, 0.75);
  }
}

@keyframes after {
  0% {
    height: 0.5em;
    box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75), -0.5em -1em rgba(233, 169, 32, 0.75);
  }

  35% {
    height: 2.5em;
    box-shadow: 0.5em 0 rgba(61, 184, 143, 0.75), -0.5em 0 rgba(233, 169, 32, 0.75);
  }

  70% {
    height: 0.5em;
    box-shadow: 0.5em -1em rgba(61, 184, 143, 0.75), -0.5em 1em rgba(233, 169, 32, 0.75);
  }

  100% {
    box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75), -0.5em -1em rgba(233, 169, 32, 0.75);
  }
}

.loader {
  position: absolute;
  top: calc(50% - 1.25em);
  left: calc(50% - 1.25em);
}
</style>