vue的h5页面中使用视频播放插件

vue的h5页面中使用视频播放插件

h5项目中需要做视频课程播放,在网上搜了一下对应的插件,发觉xg-player西瓜播放器用起来不错.当然也踩了一些坑.
**西瓜播放器官方地址: **http://h5player.bytedance.com/
西瓜播放器githuab官网: https://github.com/bytedance/xgplayer-vue

初步使用

因为自己做的是个老项目,freemarker模板语法写的(总之是旧时代那种,你知道jsp就名字了),但是我想用自己的方式开发,所以在里面用了vue.

先写一个静态页面的demo,看下效果

1575120446634-3075155d-2183-47a7-9e46-6ed621c78f17.gif

功能:

  1. 进入页面根据id播放列表对应的视频
  2. 列表视频之间播放切换
  3. 点击正在播放的视频进行暂停
  4. 点击列表播放按钮暂停,视频暂停
  5. 点击正在播放的视频,课程列表播放暂停

视频播放逻辑

/**
 * 视频播放逻辑
 * 一、点击按钮
 * 1. 视频正在播放
 *    1.1 如果是当前列表项,暂停播放,视频暂停播放 
 * item.play_status = false
 * player.pause()  
 * 2. 视频未播放
 *    2.1 播放的列表暂停播放,点击的列表进行播放
 *    2.2 切换播放视频进行播放
 * 二、点击播放器按钮
 * 1. 视频正在播放
 *    1.1 暂停播放,获取暂停状态,列表暂停播放
 * 2. 视频停止/暂停播放
 *    2.1 播放视频,列表对应按钮播放
 */

静态demo代码

1. 引入reset.css

body, h1, h2, h3, h4, p, dl, dd, ul, ol, form, input, textarea, th, td, select {
    margin: 0;
    padding: 0;
    font-family: '微软雅黑';
}

em {
    font-style: normal;
}

li {
    list-style: none;
}

a {
    text-decoration: none;
}

img {
    border: none;
    vertical-align: middle;
}

table {
    border-collapse: collapse;
}

input, textarea {
    outline: none;
}

textarea {
    resize: none;
    overflow: auto;
}

html{
    font-size: 100px !important;
}

@media only screen and (min-width: 320px) {
    html {
        font-size: 85.4px !important
    }
}

@media only screen and (min-width: 360px) {
    html {
        font-size: 96px !important
    }
}

@media only screen and (min-width: 375px) {
    html {
        font-size: 100px !important
    }
}

@media only screen and (min-width: 410px) {
    html {
        font-size: 110.4px !important
    }
}

@media only screen and (min-width: 500px) {
    html {
        font-size: 133.4px !important
    }
}

@media only screen and (min-width: 750px) {
    html {
        font-size: 200px !important
    }
}

.nav-menu-open:checked ~ .nav-menu-bg {
    -webkit-transform: scale(42) !important;
    transform: scale(42) !important;
}

2. 引入rem.js的CDN(https://www.bootcdn.cn/rem/)





  
  
  
  播放课程
  
  
  




  
全面深化改革,新时代中国的重要命题。党的十八届三中全会以来,以习近平同志为核心的党中央总揽全局、统筹谋划,注重解决体制性的深层次障碍,推出一系列重大体制改革,有效解决了一批结构性矛盾,很多领域实现了历史性变革、系统性重塑、整体性重构;注重克服机制性的梗阻问题,打通理顺了许多堵点难点,增强了全社会发展活力和创新活力。
课程列表

3. js代码

Vue.component('courseList', {
  props: {
    course: Object,
    index: Number
  },
  data: function () {
    return {
      pauseSrc: '../agent/phoenixAcademy/phoenixAcademy/[email protected]',
      playSrc: '../agent/phoenixAcademy/phoenixAcademy/[email protected]',
      playing: false,
    }
  },
  template: `
        
{{index+1>9?index+1:'0'+(index+1)}}
{{course.title}}
点击次数:{{course.play_times}}
主讲老师:{{course.teacher}}
正在播放
`, methods: { onHandle(course_index, course) { this.$emit('onplay', course_index, course) } }, }) let player = null; var app = new Vue({ el: '#app', data: { watch_type: '1', courseList: [{ id: 1, img: '../agent/phoenixAcademy/phoenixAcademy/[email protected]', title: '初识生殖美学', play_times: '3000', teacher: 'zhangsan', play_status: false, url: 'https://st.wssqxt.com/pcImg-20191125195819.mp4', }, { id: 2, img: '../agent/phoenixAcademy/phoenixAcademy/[email protected]', title: '初识生殖美学', play_times: '3000', teacher: 'zhangsan', play_status: false, url: 'https://st.wssqxt.com/pcImg-20191125211214.mp4' }, { id: 3, img: '../agent/phoenixAcademy/phoenixAcademy/[email protected]', title: '初识生殖美学', play_times: '3000', teacher: 'zhangsan', play_status: false, url: 'https://st.wssqxt.com/pcImg-20191125195819.mp4', }], currentId: null, currentIndex: null }, methods: { onPlay(course_index, course) { const courseList = this.courseList console.log(course_index, course.play_status); // 当前是否播放 if (course.play_status) { console.log('当前正在播放'); // 如果正在播放 player.pause() this.currentId = course.id this.currentIndex = course_index courseList[course_index].play_status = false; } else { //note: 1.如果未播放 //是否是当前项 let isCurrentItem = course.id == this.currentId if (isCurrentItem) { //note: 如果是当前暂停项要进行再次播放 player.play() courseList[course_index].play_status = true; } else { //note: 如果是要播放新的视频 console.log('列表中的其他视频播放'); // console.log(this.currentIndex); courseList.forEach((item, index) => { if (item.play_status) { // 如果有正在播放的项,暂停播放 item.play_status = !item.play_status } // 切换当前项进行播放 if (course_index === index) { item.play_status = !item.play_status this.currentId = item.id this._switchVideo(course.url) } }); } } }, _switchVideo(url) { if (player) { player.destroy(); } setTimeout(() => { player = null; player = new Player({ id: 'mse', url: url, fluid: true, }); player.start(url) player.play() }, 0); }, _playVideo(url) { player = new Player({ id: 'mse', url: url, fluid: true, poster: 'http://st.wssqxt.com/pcImg-20191011173312.png' }); player.start(url) player.play() }, navTo(course_index) { console.log(course_index); } }, created() { }, mounted() { let id = 1; this.courseList.forEach(item => { // console.log(item); // 如果获取的id和列表id相同,列表中的按钮处于播放状态 if (item.id == id) { item.play_status = true; this._playVideo(item.url) } }); } })

4. less样式表

body {
  font-size: 0.12rem;
  background: #f0f0f0;
}

.reward-warpper {
  position: relative;
  padding-top: 0.2rem;
  padding-bottom: 0.9rem;
  background: linear-gradient(to right, #08abb2, #04bdc8, #00d0de);
  z-index: -1;

  .togger-btn-box {
    display: flex;
    justify-content: center;
    align-content: center;
    color: #ccc;
    font-size: 0.16rem;

    .btn-item {
      // width: 25%;
      margin: 0 0.1rem;
      padding-bottom: 0.05rem;
      text-align: center;
    }

    .active {
      color: #fff;
      border-bottom: 1px solid #fff;
    }
  }
}

.reward-panel {
  position: relative;
  width: 90%;
  margin: 0 auto;
  margin-top: -0.7rem;
  z-index: 1;
  padding: 0.1rem 0.1rem 0.3rem 0.1rem;
  background: #fff;

  .panel-date {
    font-size: 0.12rem;
  }

  .panel-bounty {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    margin-top: 0.1rem;

    .title {
      margin-bottom: 0.1rem;
    }

    .money {
      font-size: 0.25rem;
      font-weight: bold;
      margin-bottom: 0.1rem;
    }

    .opposite-money-warper {
      padding: 0.03rem 0.1rem;
      border-radius: 20px;
      background: #fafafa;
      font-size: 0.12rem;

      .opposite-money {
        margin: 0 0.03rem;
        font-size: 0.16rem;
        font-weight: 500;
        color: red;
      }
    }
  }

  .grid-warpper {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-start;
    transition: all 2s;

    .warper-item {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      // width: 30%;
      width: calc((100% - 24px)/3);
      padding: 0.1rem 0.07rem;
      margin: 0.05rem;

      // background: #ccc;
      .item-title {
        color: #afaaaa;
      }

      .item-number {
        margin-top: 0.1rem;
        font-weight: 600;
      }
    }
  }

  .btn-warper {
    position: absolute;
    bottom: -0.16rem;
    left: 50%;
    margin-left: -0.2rem;

    .btn {
      padding: 0.06rem 0.1rem;
      border-radius: 16px;
      background: #fafafa;
    }
  }
}

.award-user-warper {
  margin-top: 0.2rem;

  .award-user-title {
    padding: 0 0.1rem;
    font-size: 0.14rem;
  }
}

.award-list-box {
  .award-list {
    position: relative;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.1rem;
    background: #fff;

    // border-bottom: 1px solid #000;
    &::after {
      content: '';
      position: absolute;
      bottom: 0;
      background: #aba3a3;
      width: 100%;
      height: 1px;
      -webkit-transform: scaleY(0.5);
      transform: scaleY(0.5);
      -webkit-transform-origin: 0 0;
      transform-origin: 0 0;
    }

    &:last-child {
      &::after {
        height: 0;
      }
    }

    .list-left {
      width: 80%;

      .left-item {
        display: flex;
        // justify-content: space-around;
        align-items: center;

        .item-ranking {
          margin-right: 0.1rem;

          .ranking-img {
            width: 0.1rem;
          }
        }

        .item-user-img {
          margin-right: 0.1rem;

          img {
            width: 0.45rem;
          }
        }

        .item-user-info {
          .user-name {
            width: 1.5rem;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
          }

          .user-cont {
            margin-top: 0.1rem;

            img {
              width: 0.12rem;
              // height: 0.15rem;
            }
          }
        }
      }
    }

    .list-right {
      flex: 1;
      display: flex;
      justify-content: space-between;
      align-items: center;

      .right-cont {
        margin-right: 0.05rem;
        font-size: 0.16rem;
        font-weight: 500;
        color: #000;
      }

      .right-img {
        margin-bottom: 0.03rem;

        img {
          width: 0.05rem;
        }
      }
    }
  }
}

5. 遇到的坑

1. 西瓜播放器切换视频

官网写的老的切换方式:
(1)先销毁视频,

player.destroy(isDelDom)

(2)然后重新创建player实例

new Player({
  id:'mse',
  url:'/mp4/'
})

(3)启动播放器,播放视频

player.start(url)
player.play()

我写完了之后重新整理笔记,看到官网有更好的api切换视频,暂时还没有试过.

player.src = 'new url'

项目实战

项目中的课程播放要比这个demo复杂的多,涉及到视频播放,图文播放,视频图文的切换播放

看下项目中的效果

功能:

  1. 点击播放课程,进入播放页面立即播放课程
  2. 图文课程和视频课程的切换播放
  3. 视频课程切换到图文课程要暂停播放视频
  4. 图文课程一直处于播放状态,不可暂停
  5. 课程列表分页
  6. 记录播放次数
  7. 点击播放一次就算播放一次
  8. 记录培训进度
  9. 图文课程点击进入就算此课程已看完,进行培训记录
  10. 视频课程点击播放,播放结束表示课程已看完,进行培训记录
GIF.gif

项目课程播放逻辑

开始想的是获取课程列表,然后前台筛选判断应该播放哪一项,但是考虑到加载分页时会影响当前播放的视频,所以分页列表和正在播放视频分成了两个接口,并且前台需要处理的只是逻辑判断,不需要遍历操作整个列表,简化了查询匹配课程播放的问题.

/**
 * 初始化播放: 
 *    通过query获取id,请求要播放的视频
 * 判断当前的播放类型
 * 1. 如果历史播放和现在播放都是图文
 *    如果点击项id和历史id相同,则不作任何事情
 *    否则,请求后台,播放图文
 * 2. 如果历史播放是图文,现在播放是视频
 *    请求后台,播放视频
 * 3. 如果历史播和现在播放都是视频
 *    如果正在播放的视频url和点击项视频url相同
 *        如果选择视频正在播放,则暂停播放
 *        否则播放视频
 *    否则请求后台播放新的视频
 * 4. 如果历史播放是视频,现在播放是图文
 *    销毁视频播放实例
 *    请求后台播放图文
 * 5. 如果加载分页,当前有正在播放的视频,则不请求播放接口
 */

在视频播放中需要监听视频的暂停,继续,结束状态.




  播放课程
  <#include "../../../../common/header.ftl"/>
  
  
  



<#-- :style="watch_type==1?'margin-top: 2.8rem;' : 'margin-top: 0.1rem;'" -->
课程列表
removejscssfile('/static/css/indexAll.css', 'css');
Vue.component('courseList', {
    props: {
        course: {
            type: Array,
            required: true
        },
        index: Number
    },
    data: function () {
        return {
            pauseSrc: '/static/img/chaozhimei/agent/phoenixAcademy/[email protected]',
            playSrc: '/static/img/chaozhimei/agent/phoenixAcademy/[email protected]',
            playing: false,
        }
    },
    template: `
        
{{course.sort>9?course.sort:'0'+(course.sort)}}
{{course.title}}
点击次数:{{course.is_read}}
{{course.summary}}
正在播放
`, methods: { onHandle(course_index, course) { this.$emit('onplay', course_index, course) } }, }); let player = null; let initNum = 0; let params = { pageNo: 1, pageSize: 2, typeId: '' }; let classId = null; let playHistoryType = null; let playHistoryId = null; var app = new Vue({ el: '#app', data: { watch_type: 1, //0:图文课程 1:视频课程 courseList: [], currentId: null, // 当前播放id graphicCourseData: '', // 图文课程 playingCourseData: {}, // 播放的课程 }, methods: { onPlay(course_index, course) { utils.toTop(); // console.log(course.videourl); if (course.videourl=='') { this.watch_type = 0 } else { this.watch_type = 1 } // note:如果历史播放和现在播放都是图文 if (playHistoryType ==0 && this.watch_type == 0) { console.log('都是图文'); if(classId==course.id) { console.log('选择重复项了'); return } classId = course.id; // getPlayingCourse(); collegeRead(course); } // TODO:如果历史播放是图文,现在播放是视频 if (playHistoryType == 0 && this.watch_type == 1) { console.log('播放视频'); classId = course.id; // getPlayingCourse(); collegeRead(course); // this._switchVideo(course.videourl); return } // TODO:如果历史播和现在播放都是视频 if (playHistoryType ==1 && this.watch_type == 1) { console.log('都是视频播放'); // 如果当前视频src和点击src相同,( 继续播放 if (this.playingCourseData.videourl == course.videourl) { console.log('选择的是当前视频'); // 如果现在正在播放,( 暂停播放 if (course.play_status) { console.log('正在播放'); player.pause(); return; } // 如果现在没有播放, (继续播放 player.play(); return } classId = course.id; // getPlayingCourse(); collegeRead(course); // this._switchVideo(course.videourl) } // TODO:如果历史播放是视频,现在播放是图文,( 暂停播放 if (playHistoryType == 1 && this.watch_type == 0) { // this.currentId = course.id; console.log('播放图文'); // console.log(player); if (player!=null&&player) { player.destroy(); player = null } classId = course.id; // getPlayingCourse(); collegeRead(course); return; } }, _switchVideo(url) { if (player) { player.destroy(); } setTimeout(() => { player = null; player = new Player({ id: 'mse', url: url, fluid: true, }); //note: 播放器销毁并再次初始化后,需要先调用start方法 player.start(url); player.play(); this._monitorPlaying(); this._monitorPause(); this._monitorEnded(); this._monitorDestroy(); }, 0); }, _playVideo(url) { // console.log(url) if (player) { player.destroy(); } setTimeout(() => { player = null; player = new Player({ id: 'mse', url: url, fluid: true, }); //note: 播放器销毁并再次初始化后,需要先调用start方法 player.start(url); player.play(); player.once('ready', () => { console.log('启动') }); this._monitorPlaying(); this._monitorPause(); this._monitorEnded(); // this._monitorDestroy(); }, 0); }, // 监听继续播放 _monitorPlaying() { player.on('playing', () => { this.courseList.forEach(item => { if (item.id == classId) { item.play_status = true; } }); }) }, // 监听暂停播放 _monitorPause() { player.on('pause', () => { console.log('暂停'); this.courseList.forEach(item => { if (item.id == classId) { item.play_status = false; } }); }) }, // 监听结束播放 _monitorEnded() { player.on('ended', () => { console.log('结束') updateUserCourseSchedule() }); }, // 监听销毁 _monitorDestroy() { player.on('destroy', () => { console.log('destroy') this.courseList.forEach(item => { if (item.id == classId) { item.play_status = 0; } }); }) }, }, mounted() { loadStart(); params.typeId = utils.getQueryVariable('typeId'); classId = utils.getQueryVariable('classId'); // this.currentId = classId; initUserList(); } }); // 获取正在播放课程 function initPlayingCourse() { let isPlaying = app.courseList.some(item=>item.play_status == 1); console.log(isPlaying); if(isPlaying) return ; getPlayingCourse(); } function getPlayingCourse() { $.ajax({ url: '/college/getCourse', type: 'POST', dataType: 'json', data: { id: classId }, success: function (res) { // console.log(res.data) let courseData = res.data; if (res.code == 0) { // console.log(courseData.videourl) if(!courseData.videourl) { // TODO: 图文课程 // console.log('图文课程') app.watch_type = 0; playHistoryType = 0; playHistoryId = classId; app.graphicCourseData = courseData; app.courseList.forEach(item => { item.play_status = 0; if (item.id == classId) { item.play_status = 1; // 图文教程默认请求到就是培训完成 updateUserCourseSchedule(); } }); return } // TODO: 视频课程 app.watch_type = 1; playHistoryType = 1; playHistoryId = classId // console.log('视频课程') app._playVideo(courseData.videourl); app.playingCourseData = courseData; // 如果获取的id和列表id相同,列表中的按钮处于播放状态 app.courseList.forEach(item => { item.play_status = 0; if (item.id == classId) { item.play_status = 1; } }); } else { if (res.message) { layer.msg(res.message); } } }, error: function () { layer.msg("网络繁忙"); }, complete: function () { loadComplete(); } }) } // 初始化加载分页 function initUserList() { initNum++; var initIndex = initNum; // console.log(initIndex) $('.layui-flow-more').remove(); getPageDatas('#app', function (page, next) { // console.log(page) if (initIndex != initNum) { return; } getClassList(page, function (pages) { next(null, page < pages); }); }); } // 获取课程列表 function getClassList(page, callback) { params.pageNo = page; $.ajax({ url: '/college/selectCollegeDetails', type: 'POST', dataType: 'json', data: params, success: function (res) { // console.log(res) if (res.code == 0) { callback(res.data.pages); app.courseList = app.courseList.concat(res.data.list); // console.log(JSON.stringify(app.courseList)) initPlayingCourse(); } else { if (res.message) { layer.msg(res.message); } callback(1); } }, error: function () { layer.msg("网络繁忙"); }, complete: function () { loadComplete(); } }) } // 记录播放次数 function collegeRead(course) { $.ajax({ url: `/college/read/${course.id}`, type: 'POST', dataType: 'json', data: { id: course.id }, success: function (res) { // console.log(res) if (res.code == 0) { // window.location.href = `/view/user/play/course?classId=${course.id}&typeId=${course.train_typeid}`; getPlayingCourse(); app.courseList.forEach(item=> { if(item.id==course.id) { item.is_read++ } }) } else { if (res.message) { layer.msg(res.message); } } }, error: function () { layer.msg("网络繁忙"); }, complete: function () { loadComplete(); } }) } // 记录培训进度 function updateUserCourseSchedule() { $.ajax({ url: `/college/updateUserCourseSchedule`, type: 'POST', dataType: 'json', data: { id: classId }, success: function (res) { // console.log(res) if (res.code == 0) { // console.log('完成课程') } else { if (res.message) { layer.msg(res.message); } } }, error: function () { layer.msg("网络繁忙"); }, complete: function () { loadComplete(); } }) }

你可能感兴趣的:(vue的h5页面中使用视频播放插件)