VUE 2.0 对接阿里云视频以及视屏站开发

本文详细介绍了一个视频站的开发过程,包括使用阿里云的视频上传和播放功能,第三方登录的实现,以及视频上传、截图插件的使用技巧。同时,文章还分享了在项目中遇到的一些坑及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

近期项目的总结--视屏站

开发了一个视频站。首先对于页面的:hover 效果添加(交互)不明确的情况下。会有很多失误

其次首次接触到了第三方较多的东西

1.第三方登录。对于QQ跟微信的登录是通过点击第三方链接传递你的QQ/微信号并且获取授权。由此获取你的QQ/微信信息,然后将回传的code发送给后台来进行绑定,绑定给相应的帐号。

2.第三方视频上传以及播放。此次运用的是阿里云的UPload以及STS的播放

<template>
  <div class="container-child">
    <!--上传视频-->
    <div class="project-video">
      <div class="title">
        <!--<p class="inblock">上传视频(选填)</p>-->
        <!--<span class="inblock">最多不超过三个视频,大小不超过50M</span>-->
      </div>
      <div v-if="videoList.length" class="video-box">
        <div class="video-list clear" v-for="(item, index) in videoList" :key="index">
          <span class="tip fl">{{ item.percent < 100? '上传中': '上传成功' }}</span>
          <el-progress class="inblock progress fl" :percentage="item.percent"></el-progress>
          <span v-if="item.percent >= 100" class="det fr" @click="deleteVideo(item, index)">
            <!--<img src="../../assets/img/logo.jpg" alt="">-->
            <!--<span>上传成功</span>-->
            <button style="border: 1px solid #f2af29;background-color: rgba(0,0,0,0);color: #333;">删除</button>
          </span>
          <span class="name fr">{{ item.name }}</span>
        </div>
      </div>
      <div v-if="videoList.length < 1" class="upload-video">
        <el-button :disabled="!canUpload" type="primary">上传作品</el-button>
        <input v-if="canUpload" type="file" @change="uploadVideo($event)"/>
      </div>
    </div>
  </div>
</template>

<script>
  let uploader = new AliyunUpload.Vod({
    //分片大小默认1M,不能小于100K
    partSize: 1048576,
    //并行上传分片个数,默认5
    parallel: 5,
    //网络原因失败时,重新上传次数,默认为3
    retryCount: 3,
    //网络原因失败时,重新上传间隔时间,默认为2秒
    retryDuration: 2,
    // 开始上传
    'onUploadstarted': function (uploadInfo) {
      console.log('1',uploadInfo);
      // console.log("onUploadStarted:" + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object);
      //上传方式1, 需要根据uploadInfo.videoId是否有值,调用点播的不同接口获取uploadauth和uploadAddress,如果videoId有值,调用刷新视频上传凭证接口,否则调用创建视频上传凭证接口
      // uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId);
      //上传方式2
      window._self.$http.get_STS({}).then(res => {
        console.log('res',res)
        if (res.data.status === 1) {
          let data = res.data.result.STS.credentials;
          uploader.setSTSToken(uploadInfo, data.accessKeyId, data.accessKeySecret, data.securityToken);
          window._self.videoList.push({ name: uploadInfo.file.name, percent: 0});
        } else {
          console.log('获取token失败');
        }
      });
    },
    // 文件上传成功
    'onUploadSucceed': function (uploadInfo) {
      // console.log("onUploadSucceed: " + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object);
      console.log('上传成功');
      window._self.canUpload = true;
      window._self.$emit('upload-success', uploadInfo.videoId);
      window._self.videoIndex++;
      window._self.listIndex++;
      // 上传成功 给父组件一个状态
      window._self.$emit('uploading-video', false);
    },
    // 文件上传失败
    'onUploadFailed': function (uploadInfo, code, message) {
      // console.log("onUploadFailed: file:" + uploadInfo.file.name + ",code:" + code + ", message:" + message);
      window._self.canUpload = true;
      window._self.$message({ message: '请上传正确的视频文件!', type: 'error' });
      // window._self.$message({ message: '上传失败,请重新上传!', type: 'error' });
      window._self.videoList.splice(window._self.videoIndex, 1);
      // window._self.videoIndex === 0? window._self.videoIndex = 0: window._self.videoIndex--;
      uploader.deleteFile(window._self.listIndex);
      // window._self.listIndex === 0? window._self.listIndex = 0: window._self.listIndex--;
      // 上传失败 给父组件一个状态
      window._self.$emit('uploading-video', false);
    },
    // 文件上传进度,单位:字节
    'onUploadProgress': function (uploadInfo, totalSize, loadedPercent) {
      // console.log("onUploadProgress:file:" + uploadInfo.file.name + ", fileSize:" + totalSize + ", percent:" + Math.ceil(loadedPercent * 100) + "%");
      console.log('上传进度');
      window._self.canUpload = false; // 讲按钮设置为不可上传
      window._self.$set(window._self.videoList[window._self.videoIndex], 'percent', Number((loadedPercent * 100).toFixed(2)));
      console.log(loadedPercent);
      // 上传中 给父组件一个状态
      window._self.$emit('uploading-video', true);
    },
    // 上传凭证超时
    'onUploadTokenExpired': function (uploadInfo) {
      window._self.$message({ message: '上传超时!', type: 'error' });
      // console.log("onUploadTokenExpired");
      //上传方式1  实现时,根据uploadInfo.videoId调用刷新视频上传凭证接口重新获取UploadAuth
      // uploader.resumeUploadWithAuth(uploadAuth);
      // 上传方式2 实现时,从新获取STS临时账号用于恢复上传
      window._self.getToken().then(res => {
        uploader.resumeUploadWithSTSToken(res.AccessKeyId, res.AccessKeySecret, res.SecurityToken);
      });
      // uploader.resumeUploadWithSTSToken(accessKeyId, accessKeySecret, secretToken, expireTime);
    },
    //全部文件上传结束
    'onUploadEnd':function(uploadInfo){
      console.log('上传结束');
    }
  });
  export default {
    name: "upload-video",
    props: {
      list: {
        type: Array,
        default: function () {
          return [];
        }
      }
    },
    data() {
      return {
        accessKeyId: '',
        accessKeySecret: '',
        secretToken: '',
        uploadInfo: '',
        videoIndex: 0,          // 视频列表个数
        videoList: this.list,
        videoIds: [],
        listIndex: 0,           // 上传列表个数
        canUpload: true,    // 允许上传
      }
    },
    created() {
      if (typeof window._self !== undefined ) {
        window._self = this;
      }
    },
    mounted() {
      this.$bus.$on('upload', data => {
        this.videoList = data;
        this.videoIndex = this.videoList.length;
      });
    },
    methods: {
      // 上传视频
      uploadVideo(e) {
        let file = e.target.files[0];
        let type = e.target.files[0].type;
        console.log(type);
        if (file.size > 2048*1e6) {
          this.$message({ message: '上传的视频不能超过2G', type: 'error' });
          return false;
        }else if(type != 'video/mp4' && type != 'video/avi' && type != 'video/rmvb' && type != 'video/rm'){
          this.$message({ message: '上传视频格式错误', type: 'error' });
          return false;
        }
        uploader.addFile(e.target.files[0], null, null, null, null);
        console.log(uploader.listFiles());
        console.log(this.listIndex);
        console.log(this.videoList);
        let info = uploader.listFiles()[this.listIndex];
        info.videoInfo = {};
        info.videoInfo.Title = file.name;
        uploader.options.onUploadstarted(info);
      },
      // 删除视频
      deleteVideo(item, index) {
        if (item.percent >= 100) {  // 上传成功
          this.videoList.splice(index, 1);
          this.videoIndex--;
          this.$emit('delete-success', index);
          // uploader.deleteFile(index);
        } else {  // 上传中
          // uploader.cancelFile(index);
        }
      },
       beforeDestroy () {
      // console.log('$route')
      // console.log(uploader.listFiles());
      // console.log(this.videoIndex)
      // uploader.cancelFile(this.videoIndex);
      // console.log('uploader', uploader)
      uploader.cleanList();
      // this.listIndex = 0;
    },
    },
    watch: {
      videoList(n, o) {
         console.log('videoList')
        // console.log(n)
      }
    }
  }
</script>

<style scoped type="text/css">
  /*.project-video{ margin-top: 60px;}*/
  /*.container-child{width: 350px;}*/
  .project-video .title p{ font-size: 16px; color: #333333;}
  .project-video .title p:before{ content: ''; @include inblock(); width: 9px; height: 9px; margin-right: 10px; margin-top: 6px; background-image: url("../../assets/img/logo.jpg"); }
  .project-video span{ font-size: 14px; color: #999999; padding: 1px; }
  .project-video .video-box{line-height: 21px;width: 350px;}
  /*.project-video .video-box .video-list{margin-top: 40px;}*/
  .project-video .video-box .video-list .tip{ color: #333333; font-size: 14px; width: 60px; }
  /*.project-video .video-box .video-list .progress{ width: 300px; margin-left: 50px; margin-top: 3px; }*/
  .project-video .video-box .video-list .name{ color: #333333; font-size: 14px; }
  .project-video .video-box .video-list .det{ width: 21px; height: 21px; margin-left: 20px; cursor: pointer;}
  .project-video .video-box .video-list .det img{ width: 100%; }
  .project-video .upload-video{ position: relative; bottom: 5px;}
  .project-video .upload-video input{ position: absolute; top: 0; left: 0; opacity: 0; cursor: pointer; width: 122px; height: 40px; padding: 0; }
  .project-video .upload-video button{ width: 160px; height: 40px; font-size: 14px;background-color: #f2af29;color: #333;cursor: pointer;border: 0;}
</style>

<style type="text/css">
  .progress .el-progress-bar{ width: 200px; padding-right: 0; }
  .progress .el-progress__text{ width: 70px; margin-left: 65px; }
</style>

以上为阿里云的UPload上传插件的代码。我是直接写成一个.vue文件然后imort引入

<upload-video @upload-success="uploadVideoSuccess" @delete-success="deleteVideoSuccess" @uploading-video="UploadingVideo" ></upload-video>

然后在页面上直接使用并且调用跟绑定数据/方法

// 上传视频成功
uploadVideoSuccess(id) {
  var that = this
  console.log(id)
  console.log(that.videoIds)
  that.uploadid = id
  that.videoIds.push(id);
  console.log(that.videoIds)
},
// 删除视频成功
deleteVideoSuccess(index) {
  var that = this
  that.videoIds.splice(index, 1);
},
// 判断视频是否正在上传
UploadingVideo(status) {
  this.videoIsUploading = status;
},

然后对视频进行判断。

3.STS播放

阿里云的STS我用了以后才发现只要不修改播发器。那么对于前端来说在PC上是一款非常实用的播发器(需要阿里云视频点播)

​
 <div class="header-info-video">
   <div class="prism-player" id="J_prismPlayer" style="height: 100%;"></div>
 </div>

​

视频点播前端代码就那么简单,实际上就是一个div。但是在外层添加一个div的原因是因为如果要设置宽高的话最好给阿里云的div设置一个宽高100%;然后在自己添加的外层div设置宽高写死。因为如果不在外层设置宽高而直接在阿里云插件设置宽高。在一部分浏览器会出现全屏播放的时候播放的区块只有你设置宽高的大小。

this.video_oss_id = res.data.result.video_info.video_oss_id;
            this.$http.get_STS({}).then(res => {
              this.accessKeyId = res.data.result.STS.credentials.accessKeyId;
              this.accessKeySecret = res.data.result.STS.credentials.accessKeySecret;
              this.securityToken = res.data.result.STS.credentials.securityToken;
              var player = new Aliplayer({
                id: 'J_prismPlayer',
                width: '100%',
                autoplay: true,
                vid : this.video_oss_id,
                accessKeyId: this.accessKeyId,
                securityToken: this.securityToken,
                accessKeySecret:  this.accessKeySecret
              },function(player){
                console.log('播放器创建好了。')
              });

阿里云的视频点播对接接口也只需要4个参数。

1.vid 视频点播上传的视频id

2.accessKeyId 后台给你的视频钥匙id

3.accessKeySecret 后台给你的临时密钥

4.securityToken 后台给的临时token

4.分享

QQ空间分享

window.open("https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url= " + window.location.href + "&sharesource=qzone&title=" + this.titleNmae + "&pics=" + this.titleImg + "&summary=" + this.storyinfo)此次运用的分享是百度来的

附上原文地址:https://blog.youkuaiyun.com/weixin_38930535/article/details/79377943

微信分享则是后台将链接生成为二维码丢过来以后有前端展示就可以了。

5.截图插件

这个截图插件是因为上一个项目的遗留。所以直接拿来使用的。

基于VueCropper修改的截图插件

<template>
  <div class="container-child mask">
    <div class="inblock" style="width: 900px;position: absolute; top: 50%; left: 50%; margin-left: -410px; margin-top: -290px;">
      <div class="box">
      <div class="cropper-box">
        <vue-cropper
          class="cropper"
          ref="cropper"
          :img="option.img"
          :outputSize="option.size"
          :outputType="option.outputType"
          :autoCrop="option.autoCrop"
          :fixedBox="option.fixedBox"
          :autoCropWidth="option.autoCropWidth"
          :autoCropHeight="option.autoCropHeight"
          @realTime="realTime">
        </vue-cropper>
        <div class="text-button clear">
          <label style="float: left;" class="btn" for="upload">重新上传</label>
          <input type="file" id="upload" style="position:absolute; clip:rect(0 0 0 0);" accept="image/png, image/jpeg, image/gif, image/jpg" @change="uploadImg($event, 1)">
          <!--<button style="float: right;" @click="refreshCrop" class="btn">refresh</button>-->
          <button style="float: right;" @click="changeScale(-1)" class="btn">-</button>
          <button style="float: right;" @click="changeScale(1)" class="btn">+</button>
        </div>
      </div>
      <div class="show-preview" :style="{'width': previews.w + 'px', 'height': previews.h + 'px',  'overflow': 'hidden', 'margin': '5px'}">
        <div :style="previews.div">
          <img :src="previews.url" alt="" :style="previews.img">
        </div>
      </div>
    </div>
      <div class="submit">
        <button class="btn" @click="upload">提交</button>
        <button class="btn" @click="cancel">取消</button>
      </div>
    </div>
  </div>
</template>

<script>
  import VueCropper from 'vue-cropper';
  export default {
    name: 'HelloWorld',
    props: {
      parentMsg: {
        type: String,
        default: ''
      },
      autoCropWidth: {
        type: Number,
        default: 300
      },
      autoCropHeight: {
        type: Number,
        default: 230
      }
    },
    components: {
      VueCropper: VueCropper
    },
    data () {
      return {
        option: {
          img: '',
          size: 1,
          outputType: 'jpg',
          autoCrop: true,
          fixedBox: true,
          autoCropWidth: this.autoCropWidth,
          autoCropHeight: this.autoCropHeight,
        },
        previews: {}
      }
    },
    created() {
      this.$set(this.option, 'img', this.parentMsg);
    },
    methods: {
      // 上传图片
      uploadImg (e, num) {
        var file = e.target.files[0]
        if (!/\.(gif|jpg|jpeg|png|bmp|GIF|JPG|PNG)$/.test(e.target.value)) {
          alert('图片类型必须是.gif,jpeg,jpg,png,bmp中的一种')
          return false
        }
        var reader = new FileReader()
        reader.onload = (e) => {
          let data
          if (typeof e.target.result === 'object') {
            // 把Array Buffer转化为blob 如果是base64不需要
            data = window.URL.createObjectURL(new Blob([e.target.result]))
          } else {
            data = e.target.result
          }
          if (num === 1) {
            this.option.img = data
          } else if (num === 2) {
            this.example2.img = data
          }
        }
        // 转化为base64
        reader.readAsDataURL(file)
        // 转化为blob
        // reader.readAsArrayBuffer(file)
      },
      // 实时预览图片函数
      realTime(data) {
        this.previews = data;
      },
      // 放大 缩小
      changeScale(num) {
        num = num || 1
        this.$refs.cropper.changeScale(num)
      },
      // 刷新
      refreshCrop() {
        this.$refs.cropper.clearCrop();
        this.$refs.cropper.startCrop()
      },
      // 上传图片到服务器 getCropBlob
      upload() {
        this.$refs.cropper.getCropData((data) => {
          this.$emit('upload', data);
        })
      },
      // 取消
      cancel() {
        this.$emit('cancel');
      }
    },
    watch: {
      parentMsg(curVal, oldVal) {
        console.log('curVal', curVal)
        // this.$set(this.option, 'img', curVal);
      }
    }
  }
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .container-child{ width: 100%; height: 100%; background: rgba(0, 0, 0, .3); position: fixed; top: 0; left: 0;z-index: 20; }
  .box{
    /*width: 100%;*/
    height: 525px;
    margin: 0 auto;
    background-color: #ffffff;
    padding: 20px 30px 0 30px;
    border-bottom: 1px solid #e6e6e6;
  }
  .cropper-box{
    width: 500px;
    margin: 0 auto;
    height: 450px;
    float: left;
  }
  .cropper{
    width: 500px;
    height: 400px;
  }
  .show-preview{
    float: right;
    border: 1px solid #e6e6e6;
  }
  .text-button{
    height: 50px;
  }
  .submit{
    width: 100%;
    height: 70px;
    margin: 0 auto;
    text-align: center;
    padding: 10px 0;
    background-color: #ffffff;
  }
  .btn {
    display: inline-block;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    text-align: center;
    box-sizing: border-box;
    outline: none;
    margin:10px 10px 0px 0px;
    padding: 9px 15px;
    font-size: 14px;
    border-radius: 4px;
    color: #fff;
    background-color: #50bfff;
    border-color: #50bfff;
    transition: all .2s ease;
    text-decoration: none;
    user-select: none;
  }
</style>

同upload方法一样直接丢vue引入项目。

<Cropper v-if="isCropper" :autoCropWidth="280" :autoCropHeight="200" :parentMsg="msgToCropper"  @upload="uploadCover" @cancel="cancelCropper"></Cropper>

前端引入项目后的调用方法。我这里是与图片进行判断。使用方法同下的input方法一致。但是input只是负责展示图片的时候把代替截图按钮执行事件。

 // 取消上传封面(截图)
      cancelCropper() {
        this.isCropper = false;
        this.coverFileList = [];
      },
      uploadCover(data){
        this.$http.upload_img({ base64_img: data }).then(res => {
          console.log('best64',res)
          this.coverPath = res.data.result.img_url;
          this.coverName = res.data.result.img_name;
          this.isCropper = false;
          this.imgopacity = 1;
          this.headSubmit();
          this.bus();
          this.$set(this.coverFileList[0], 'url', res.data.result.img_url);
        });
      },

设置上传给后台同样是best64的方式。但是取消的时候原先会有一个大坑就是因为是通过input点击上传的。

所以会出现点击第一次图片的时候截图点击(不管确定取消)

第二次点击同一张图片。会无法展示截图插件。原因是input插件无法上传同一张图片2次。

在这里就需要给input绑定value然后每次点击的时候把input的value清除干净就可以了。

6.在此次项目中遇到了一个简单有绕的坑

  <div style="margin-bottom: 50px;padding-left: 70px;position: relative;" v-for="(item,index) in textinfo" @click="flieindex(index)" v-if="index > hiddens">
              <span style="font-size: 14px;color: #333333;">职业</span>
              <select name="" style="width: 160px;height: 44px;margin-left: 8px;margin-right: 20px;background-color: #f5f5f5;border: 1px solid #dddddd;border-radius: 4px;"  v-model="textinfo[index].type">
                <option value="" checked v-if="item.career_name == '' || item.career_name == null || item.career_name == undefined">请选择</option>
                <option value="" checked v-else :value="item.career_id">{{item.career_name}}</option>
                <option v-for="(list, index1) in item.selects" :value="item.selects[index1].careercategory_id">{{list.career_category_name}}</option>
              </select>
              <span style="font-size: 14px;color: #333333;margin-right: 10px;">姓名</span>
              <input type="text"style="width: 145px;height: 40px;border: 1px solid #dddddd;background-color: #f5f5f5;padding-left: 15px;" maxlength="12" v-model="item.creator_name">
              <span style="color: red;margin-left: 10px;">*</span>
              <span style="font-size: 12px;color: #999999;">限制4字以内</span>

              <img :src="textinfo[index].creator_img_url" alt="" class="thisindeximg"  v-if="textinfo[index].creator_img_url">
              <i v-else class="el-icon-plus formupload-icon icon-border"></i>
              <input type="file" @change="uploaImage($event,index)" class="indeximgs">
              <span style="color: #D1613C;font-size: 14px;cursor: pointer;margin-left: 20px;" @click="splcecreat(index)">删除</span>

            </div>
            <div style="margin-bottom: 50px;width: 700px;text-align: right;">
              <span style="color: #f2af29;font-size: 14px;cursor: pointer;margin-right: 195px;" @click="addcreat">新增</span>
            </div>

这是一个后期添加的。所以会有行内样式。添加职业/姓名/头像

v-for="(item,index) in textinfo"在第一层for循环的时候通过增删。倒是没有太大的问题。

但是在v-model="textinfo[index].type"二层第一个循环的时候

出现的问题就是不会取index下标而引发了一系列大问题

但是组长出面过后。就解决了。给绑定的数组的下边的选中的东西进行添加。最后不需要watch的监听。直接通过获取textinfo数组就可以了。

因为textinfo数组新增的type就是这个select选中的当前

但是之后又出现了一个问题就是

  <img :src="textinfo[index].creator_img_url" alt="" class="thisindeximg"  v-if="textinfo[index].creator_img_url">
              <i v-else class="el-icon-plus formupload-icon icon-border"></i>
              <input type="file" @change="uploaImage($event,index)" class="indeximgs">

上传头像出了问题。本来是用element插件的。但是插件修改比较麻烦。另外相应的要获取index下标

所以就直接用input的上传样式来上传图片。把input设置为opacity: 0;z-index: 10;然后设置一个img的宽高

这样就会完全覆盖在img上。然后点击img的时候就会触发到input的上传事件。这样再通过best64的方式传给后台

  // 上传视频
      uploaImage (e,index) {
        let file = e.target.files[0];
        let fileR = new FileReader();
        fileR.readAsDataURL(file);
        let _this = this;
        fileR.onload = function (e) {
          _this.$http.upload_img({ base64_img: e.target.result }).then(res => {
            console.log(res);
            _this.creator_img_url.push({creator_img_url:res.data.result.img_name_url});
            _this.creator_img.push({creator_img:res.data.result.img_name});
            _this.$set(_this.textinfo[index], 'creator_img_url', res.data.result.img_url);
          })
        }

      },

因为组长将接口封装了。所以直接通过 this.http.接口名就可以访问接口了。

由此就上传给了后台并且保存了名称以及路径。然后把路径动态赋值给你的img就OK了

_this.$set(_this.textinfo[index], 'creator_img_url', res.data.result.img_url);

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值