y595705120 2 年之前
父节点
当前提交
ee1d2cc176

+ 316 - 0
src/containers/center/play/components/iMedia.vue

@@ -0,0 +1,316 @@
+<template>
+  <div>
+    <h2 class="tc">
+      <span>{{curTimes|useTime}}</span>
+      <strong>/</strong>
+      <span>{{media.duration|useTime}}</span>
+    </h2>
+
+    <video-player id="myVideo" class="video-player-box" ref="videoPlayer" :playsinline="true" @pause="onPlayerPause($event)"
+      @play="onPlayerStart($event)" @ready="playerReadied" @timeupdate="onPlayerTimeupdate($event)" @ended="onPlayerEnded($event)"
+      :globalOptions="{controls:true}" :options="options">
+    </video-player>
+    
+
+  </div>
+</template>
+
+<script>
+  import {
+    httpServer
+  } from "@/components/httpServer/httpServer.js";
+  import md5 from 'js-md5';
+  import {
+    videoPlayer
+  } from 'vue-video-player';
+  import 'video.js/dist/video-js.css'
+  // import html2canvas from "html2canvas";
+  import {
+    MessageBox
+  } from "element-ui";
+  export default {
+    name: "Index",
+    data() {
+      return {
+        timer: false,
+        tickNum: 0,
+        prevTime: 0,
+        isReady: false,
+        curTimes: 0,
+        onPlay: false
+      }
+    },
+    components: {
+      videoPlayer
+    },
+    watch: {
+      mediaType() {
+        if (!this.mediaType) return;
+      }
+    },
+    props: {
+      mediaType: {
+        type: String,
+        default: ''
+      },
+      media: {
+        type: Object,
+        default: () => {
+          return {
+            id: '',
+            percent: 0
+          }
+        }
+      },
+      options: {
+        type: Object,
+        default: () => {
+          return {}
+        }
+      }
+    },
+    filters: {
+      useTime(val) {
+        let timestr = ""
+        let hour = parseInt(val / 3600);
+        let min = parseInt(val / 60 % 60);
+        let sec = parseInt(val % 60);
+        if (hour < 10) hour = "0" + hour;
+        if (min < 10) min = "0" + min;
+        if (sec < 10) sec = "0" + sec;
+        return hour + ":" + min + ":" + sec
+      }
+    },
+    beforeDestroy() {
+      this.stopTick()
+      this.reportErr("play", 'destroy');
+    },
+    computed: {
+      player() {
+        return this.$refs.videoPlayer.player
+      }
+    },
+    created() {
+      this.startTick()
+      this.startMonitor();
+    },
+    methods: {
+      startTick() {
+        let tick = this.tryTick;
+        if (this.timer) clearInterval(this.timer);
+        this.timer = setInterval(tick, 5 * 1000);
+      },
+      stopTick() {
+        if (this.timer) clearInterval(this.timer);
+      },
+      tryTick() {
+        let that = this;
+        try {
+          that.tick()
+        } catch (err) {
+          that.reportErr("play", '' + err.message)
+        }
+      },
+      playerReadied(audio) {
+        console.log("playerReadied", audio)
+        let that = this;
+        let {
+          position,
+          duration
+        } = this.media
+        if (position > 5 && position < duration) {
+          setTimeout(() => {
+            this.setposition(position)
+          }, 2000)
+        }
+        this.isReady = true
+      },
+      onPlayerTimeupdate(player) {
+        let curTimes = player.cache_.currentTime;
+        if (curTimes > 30 && curTimes > this.media.position + 10) {
+          console.log("return", curTimes, this.media.position)
+          player.currentTime(this.media.position);
+          return;
+        }
+        this.curTimes = curTimes
+      },
+      setposition(position) {
+        if (position > this.media.duration) position = this.media.duration;
+        let player = this.$refs.videoPlayer.player;
+        player.currentTime(position);
+        if (this.media.isFinish) return;
+        if (this.media.position >= this.media.duration - 10 && !this.media.isFinish) {
+          this.tick(true)
+        }
+      },
+      onPlayerPause(event) {
+        this.reportErr("play", 'pause');
+        this.stopTick()
+        this.onPlay = false
+      },
+      onPlayerEnded(event) {
+        this.reportErr("play", 'end');
+        this.tick(true)
+      },
+      onClose() {
+        this.reportErr("play", 'close')
+        this.doPause()
+        this.$emit("close")
+      },
+      doPause() {
+        this.stopTick()
+        this.onPlay = false
+        let myPlayer = this.$refs.videoPlayer.player;
+        myPlayer && myPlayer.pause()
+      },
+      doPlay() {
+        this.onPlay = true
+        this.startTick();
+        if (!this.$refs.videoPlayer || !this.$refs.videoPlayer.player) return;
+        if (!this.dialog) return this.doPause();
+        let myPlayer = this.$refs.videoPlayer.player;
+        myPlayer && myPlayer.play()
+        this.tickNum = 0
+      },
+      onPlayerStart() {
+        // console.log("onPlayerStart")
+        this.reportErr("play", 'start');
+        this.startTick();
+        this.onPlay = true
+      },
+      reportErr(action, msg) {
+        httpServer("course.report", {
+          action,
+          msg
+        })
+      },
+      startMonitor() {
+        let that = this
+        document.addEventListener("visibilitychange", function() {
+          // || document.hidden
+          if (document.visibilityState == "hidden") {
+            // that.doPause( )
+            that.reportErr("play", 'hidden');
+          } else {
+            that.reportErr("play", 'show');
+            // that.doPlay()
+          }
+        });
+      },
+      tick(force = false) {
+        let media = this.media;
+        this.tickNum++
+        // 已经完成
+        if (this.media.isFinish) {
+          console.log("finish")
+          return;
+        }
+        // 主动暂停
+        let myPlayer = this.$refs.videoPlayer.player;
+        let curTimes = parseInt(myPlayer.currentTime());
+        // 后退无心跳
+        if (this.media.position > curTimes && !force) return;
+        if (curTimes < 4) {
+          console.log("curTimes")
+          return;
+        }
+
+        let isFinish = force ? 1 : 0
+        if (curTimes >= media.duration) isFinish = 1;
+        //  拉到后面
+        if (!isFinish) {
+          if (!this.onPlay) return;
+        }
+        // 强制完成
+        let param = {
+          id: media.id,
+          position: curTimes,
+          isFinish
+        };
+        httpServer("course.tick", param, true).then(res => {
+          if (res.code == 200) {
+            let {
+              skip,
+              position,
+              pause,
+              closed
+            } = res.data
+            if (pause || closed) {
+              this.doPause();
+              this.$emit("close")
+              if (closed) {
+                this.$message.errorMsg("课程关闭学习", 5);
+              } else if (pause) {
+                this.$message.errorMsg("多处同时播放视频", 5);
+              }
+              return
+            }
+            if (!skip) this.setposition(position);
+            Object.assign(param, res.data)
+            this.$emit("update", param)
+          }
+        })
+      }
+    }
+  }
+</script>
+
+<style>
+  .video-js {
+    .vjs-control-bar {
+      .vjs-icon-custombutton {
+        font-family: VideoJS;
+        font-weight: normal;
+        font-style: normal;
+      }
+
+      .vjs-icon-custombutton:before {
+        content: "\f108";
+        font-size: 1.8em;
+        line-height: 1.67;
+      }
+    }
+  }
+
+  .p-process {
+    width: 100%;
+    margin: 20px auto;
+    height: 30px;
+  }
+
+  .media-footer {
+    padding: 0px 30px;
+    text-align: left;
+    line-height: 40px !important;
+    bottom: -10px;
+  }
+
+  .media-center {
+    text-align: center;
+    padding: 0px;
+  }
+
+  .media-time {
+    font-size: 18px;
+    vertical-align: center;
+  }
+
+  .media-select {
+    white-space: nowrap;
+    text-align: right;
+    line-height: 40px !important;
+    float: right;
+    margin: 0px !important;
+  }
+
+  .bicon {
+    font-size: 28px !important;
+    padding: 4px !important;
+  }
+
+  .media-el-select {
+    font-size: 28px !important;
+    width: 80px;
+    padding: -4px auto !important;
+  }
+</style>

+ 39 - 41
src/containers/center/play/index.vue

@@ -1,19 +1,21 @@
 <template>
   <div class="m-right-block fr mh576">
-    <ICourseInfoTest  v-if="tpl.type=='检测试验人员'" :info="info" :tpl="tpl" @printCert="printCert" @startExam="startExam"></ICourseInfoTest>
-    <ICourseInfoXsExtra v-else-if="tpl.testXs>0"
-    :extraXs="extraXs"  :info="info"
-    @updateInfo="updateInfo"
-    :tpl="tpl" @startExamTest="startExamTest" @startExam="startExam"></ICourseInfoXsExtra>
-
-    <ICourseInfo v-else :info="info" :tpl="tpl" @printCert="printCert" @startExam="startExam"></ICourseInfo>
+    <div v-if="show!=4">
+      <ICourseInfoTest  v-if="tpl.type=='检测试验人员'" :info="info" :tpl="tpl" @printCert="printCert" @startExam="startExam"></ICourseInfoTest>
+      <ICourseInfoXsExtra v-else-if="tpl.testXs>0"
+      :extraXs="extraXs"  :info="info"
+      @updateInfo="updateInfo"
+      :tpl="tpl" @startExamTest="startExamTest" @startExam="startExam"></ICourseInfoXsExtra>
 
+      <ICourseInfo v-else :info="info" :tpl="tpl" @printCert="printCert" @startExam="startExam"></ICourseInfo>
+    </div>
     <div class="right-block-bd ng-scope" ui-view="myStudyContent" style="position: relative;">
       <div class="m-account">
         <div class="account-tit" style="height:30px">
           <a :class="{'current':show===1}"  @click="show=1" class="">正在学习</a>
           <a :class="{'current':show===2}"  @click="show=2" class="">已经完成</a>
           <a :class="{'current':show===3}"  @click="show=3" class="" v-if="tpl.examGroupId>0||tpl.type=='检测试验人员'">考试记录</a>
+          <a :class="{'current':show===4}" v-if="show==4" >{{media.name}}</a>
         </div>
 
         <div v-if="show <3" class="lwh-ul-form mt20">
@@ -77,6 +79,23 @@
           <exam-list :courseId="courseId"> </exam-list>
         </div>
 
+        <div v-if="show ==4" class="lwh-ul-form mt20">
+          <div style="width: 640px;margin: 0 auto;">
+            <i-media
+              :media-id="mediaId"
+              :options="options"
+              :dialog="mediaDialog"
+              :media="media"
+              :duration="info.duration"
+              @close="closeMedia"
+              :mediaType="mediaType"
+              @changeMedia="changeMedia"
+              @updateOption = "updateOption"
+              @update="update">
+            </i-media>
+          </div>
+        </div>
+
         <el-dialog class="previewDialog" :visible.sync="testExamDalog"
           top="50px" width="1024px"
           @close="testExamDalog=false">
@@ -84,26 +103,7 @@
         </el-dialog>
       </div>
      </div>
-    <el-dialog
-     class="media-dialog"
-     :close-on-click-modal="false"
-     :visible.sync="mediaDialog"
-     top="50px"
-     :title="media.name"
-     :width="mediaType=='hls'?'720px':'540px'"
-     >
-        <Media
-          :options="options"
-          :dialog="mediaDialog"
-          :media="media"
-          :duration="info.duration"
-          @close="closeMedia"
-          :mediaType="mediaType"
-          @changeMedia="changeMedia"
-          @updateOption = "updateOption"
-          @update="update">
-        </Media>
-    </el-dialog>
+
 
     <el-dialog title="输入认证" center :visible.sync="editRzcode"  >
       <el-form  label-width="120px" ref="elAdd">
@@ -122,7 +122,7 @@
 
 <script>
   import {httpServer } from "@/components/httpServer/httpServer.js";
-  import Media from "./components/media.vue";
+  import IMedia from "./components/iMedia.vue";
   import ExamList from "./components/ExamList.vue";
   import ICourseInfo from "./components/iCourseInfo.vue";
   import ICourseInfoTest from "./components/iCourseInfoTest.vue";
@@ -149,10 +149,11 @@
         extraXs:{},
         page:1,
         size:5,
+        mediaId:0,
         options:{
           controls:true,
           autoplay: true, // 如果true,浏览器准备好时开始回放。
-          muted: true, // 默认情况下将会消除任何音频。
+          muted: false, // 默认情况下将会消除任何音频。
           loop: false, // 导致视频一结束就重新开始。
           preload: "auto", // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
           language: 'zh-CN',
@@ -161,19 +162,19 @@
           sources: [],
           poster: '', // 你的封面地址
           notSupportedMessage: '无法播放媒体源', // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
-          playtimes: '',
           controlBar: {
             timeDivider: true,
             durationDisplay: true,
             remainingTimeDisplay: false,
             fullscreenToggle: true
-          }
+          },
+          playtimes:""
         },
         list: [],
         showList:[],
       };
     },
-    components:{Media,ExamList,ICourseInfo,ICourseInfoTest,ICourseAnswerTest,ICourseInfoXsExtra},
+    components:{IMedia,ExamList,ICourseInfo,ICourseInfoTest,ICourseAnswerTest,ICourseInfoXsExtra},
     beforeMount() {
       this.courseId = +this.$route.params.courseId
       this.getData()
@@ -346,6 +347,7 @@
       },
       closeMedia(){
         this.mediaType = '';
+        this.show = 1;
         this.mediaDialog=false;
       },
       // 加载媒体
@@ -354,20 +356,16 @@
         this.media.index = index
         httpServer('course.GetMedia', {id:item.id}).then( res => {
           if( res.code != 200) return;
-          let {mediaUrl, id, position, marks} = res.data||{};
+          let {mediaUrl, id, position, duration, marks} = res.data||{};
+          if( position >= duration) position = duration||0;
           this.mediaUrl = res.data.mediaUrl;
-          if(  this.mediaUrl.indexOf('/hls/') == -1){
-            this.mediaType = 'ld'
-          }else{
-            this.mediaType = 'hls'
-          }
           this.options.marks = !!marks;
           this.media.position = position;
+          this.options.playtimes = position
           this.media.id = id;
+          this.mediaId = id;
           this.options.sources = [{src:this.mediaUrl,type: "application/x-mpegURL"}];
-          this.options.playtimes = position||1;
-          this.options.autoplay  = position>0;
-          this.mediaDialog = true;
+          this.show = 4
         });
       }
     }

+ 80 - 0
src/containers/center/play/media.vue

@@ -0,0 +1,80 @@
+<template>
+ <div class="player">
+   <div style="width: 640px;margin: 0 auto;">
+     <video-player class="video-player vjs-custom-skin"
+                   ref="videoPlayer"
+                   :playsinline="true"
+                   style="object-fit:fill"
+                   :options="playerOptions"
+                   :x5-video-player-fullscreen="true"
+                   @pause="onPlayerPause($event)"
+                   @play="onPlayerPlay($event)"
+                   @fullscreenchange="onFullscreenChange($event)"
+                   @click="fullScreen"
+     >
+     </video-player>
+     </div>
+ </div>
+</template>
+
+<script>
+    import test from '@/assets/images/bg.png'
+    import {videoPlayer} from 'vue-video-player';
+
+    export default {
+      components: {
+        videoPlayer
+      },
+      data() {
+        return {
+          pictureImg: test,
+          playerOptions: {
+  //        playbackRates: [0.7, 1.0, 1.5, 2.0], //播放速度
+            autoplay: false, //如果true,浏览器准备好时开始回放。
+            muted: false, // 默认情况下将会消除任何音频。
+            loop: false, // 导致视频一结束就重新开始。
+            preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
+            language: 'zh-CN',
+            aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
+            fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
+            sources: [{
+              type: "application/x-mpegURL",
+              src: "https://media.ndjsxh.cn:18443/ld/2518/2518.m3u8"
+            }],
+            poster: "你的封面地址",
+            width: document.documentElement.clientWidth,
+            notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖Video.js无法播放媒体源时显示的默认信息。
+            controlBar: {
+              // timeDivider: true,
+              // durationDisplay: true,
+              // remainingTimeDisplay: false,
+              fullscreenToggle: true  //全屏按钮
+            }
+          }
+        }
+      },
+      methods: {
+        fullScreen() {
+          const player = this.$refs.videoPlayer.player
+          player.requestFullscreen()//调用全屏api方法
+          player.isFullscreen(true)
+          player.play()
+        },
+        onPlayerPlay(player) {
+          player.play()
+        },
+        onPlayerPause(player) {
+          // alert("pause");
+        }
+      },
+      computed: {
+        player() {
+          return this.$refs.videoPlayer.player
+        }
+      }
+    }
+
+  </script>
+
+<style>
+</style>

+ 6 - 0
src/router/index.js

@@ -9,6 +9,7 @@ import main from '@/containers/main/main'
 import center from '@/containers/center/center'
 
 import Play from '@/containers/center/play/index.vue'
+import PlayMedia from '@/containers/center/play/media.vue'
 
 import Index from '@/containers/main/index/index'
 import Profile from '@/containers/main/profile/profile'
@@ -136,6 +137,11 @@ export default new Router({
           name: 'play',
           component: Play,
         },
+        {
+          path: 'media/:courseId/:mediaId',
+          name: 'playMedia',
+          component: PlayMedia,
+        },
         {
           path: 'studyArchives',
           component: CenterStudyArchives,