ソースを参照

视频交互题增加提示点

natasha 2 日 前
コミット
33a32fa697

+ 3 - 1
package.json

@@ -20,8 +20,8 @@
     "builder:mac": "electron-builder --mac"
   },
   "dependencies": {
-    "@tinymce/tinymce-vue": "^3.2.8",
     "7zip-bin": "^5.2.0",
+    "@tinymce/tinymce-vue": "^3.2.8",
     "axios": "^1.7.2",
     "cnchar": "^3.2.6",
     "core-js": "^3.47.0",
@@ -38,6 +38,8 @@
     "three": "^0.181.2",
     "tinymce": "^5.10.9",
     "v-fit-columns": "^0.2.0",
+    "video.js": "^8.23.7",
+    "videojs-markers": "^1.0.1",
     "vis-data": "^8.0.3",
     "vis-network": "^10.0.2",
     "vue": "^2.6.14",

+ 120 - 9
src/views/book/courseware/create/components/question/video_interaction/VideoInteraction.vue

@@ -17,8 +17,18 @@
         @updateFileList="updateFileList"
       />
       <div v-if="data.video_list.length > 0" class="interaction-box">
-        <video id="interaction-video" :src="data.video_list[0].file_url" width="100%" height="400" controls></video>
-        <el-button type="primary" size="small" @click="handlePause">暂停视频编辑题目</el-button>
+        <video
+          ref="videoPlayer"
+          id="interaction-video"
+          :src="data.video_list[0].file_url"
+          width="100%"
+          height="400"
+          controls
+          class="video-js vjs-default-skin vjs-big-play-centered"
+        ></video>
+        <!-- <video ref="videoPlayer" class="video-js vjs-default-skin vjs-big-play-centered"></video> -->
+
+        <el-button type="primary" size="small" @click="handlePause" style="margin-top: 5px">暂停视频编辑题目</el-button>
         <!-- <el-button @click="handleMultilingual">多语言</el-button> -->
         <ul v-if="data.file_info_list.length > 0" class="file-list">
           <li v-for="(file, i) in data.file_info_list" :key="i">
@@ -74,6 +84,11 @@ import { getVideoInteractionData } from '@/views/book/courseware/data/videoInter
 import { GetCoursewareExercise } from '@/api/book';
 import { getSelectData } from '@/views/book/courseware/data/select';
 
+import 'videojs-markers/dist/videojs.markers.min.css';
+import videojs from 'video.js';
+import 'video.js/dist/video-js.css';
+import 'videojs-markers';
+
 export default {
   name: 'VideoInteractionPage',
   components: { UploadFile, ExerciseAdd },
@@ -91,15 +106,21 @@ export default {
       file_id_list: [],
       loading: false,
       currentTime: 0,
+      duration: 0,
       multilingualText: '',
       exerciseContent: null,
+      playTime: 0,
+      player: null,
     };
   },
   watch: {
     'data.video_list': {
       handler(val) {
         if (val.length > 0) {
-          this.handleData();
+          let _this = this;
+          this.$nextTick(() => {
+            _this.handleCreatPlayer();
+          });
         }
       },
       immediate: true,
@@ -107,6 +128,83 @@ export default {
     'data.property.file_list': 'handleMindMap',
   },
   methods: {
+    // 初始化player
+    handleCreatPlayer(time) {
+      let _this = this;
+      let options = {
+        autoplay: false, //自动播放
+        height: 500,
+        width: 918,
+        controls: true, //用户可以与之交互的控件
+        loop: true, //视频一结束就重新开始
+        muted: false, //默认情况下将使所有音频静音
+        playsinline: true,
+        webkitPlaysinline: true,
+        // aspectRatio:"16:9",//显示比率
+        playbackRates: [0.5, 1, 1.5, 2],
+        fullscreen: {
+          options: { navigationUI: 'hide' },
+        },
+        sources: [
+          {
+            src: this.data.video_list[0].file_url,
+            type: 'video/mp4',
+          },
+        ],
+      };
+      this.player = videojs(this.$refs.videoPlayer, options, function onPlayerReady() {
+        // console.log('onPlayerReady');
+      });
+      // 设置标点
+      _this.handleMarkers();
+      // 获取当前播放时间
+      this.player.on('timeupdate', function (event) {
+        _this.currentTime = this.currentTime().toFixed(3);
+        // console.log(this.currentTime());
+      });
+    },
+    // 设置标点
+    handleMarkers() {
+      let markers = [];
+      this.data.file_info_list.forEach((item) => {
+        markers.push({
+          time: item.currentTime,
+        });
+      });
+      this.player.markers({
+        // 不显示鼠标悬浮标记提示文字
+        markerTip: {
+          display: true,
+        },
+        markerStyle: {
+          width: '4px',
+          'background-color': 'red',
+          'border-radius': '50%',
+        },
+        markers: markers,
+        // markers: [
+        //   {
+        //     time: 0.694313,
+        //     class: 'custom-marker-class',
+        //     text: '标记1',
+        //   },
+        //   {
+        //     time: 5.694313,
+        //     class: 'custom-marker-class',
+        //     text: '标记2',
+        //   },
+        //   {
+        //     time: 10.694313,
+        //     class: 'custom-marker-class',
+        //     text: '标记3',
+        //   },
+        //   {
+        //     time: 15.694313,
+        //     class: 'custom-marker-class',
+        //   },
+        // ],
+      });
+    },
     updateFileList({ file_list, file_id_list, file_info_list }) {
       this.data.video_list = file_list;
       this.data.video_id_list = file_id_list;
@@ -114,6 +212,8 @@ export default {
       this.data.video_info_list = file_info_list;
       if (file_list.length === 0) {
         this.data.file_info_list = [];
+      } else {
+        this.duration = file_list[0].media_duration;
       }
     },
 
@@ -142,15 +242,16 @@ export default {
       if (file_list.length > 0 && file_list[0].file_id) {
         let obj = file_list[0];
         obj.currentTime = this.currentTime;
+        obj.position = (this.currentTime / this.duration) * 100;
         this.data.file_info_list.push(obj);
         this.sourceAddFlag = false;
-        document.getElementById('interaction-video').play();
+        this.player.play();
       }
     },
     handleCancle() {
       // this.sourceAddFlag = false;
       this.exerciseFlag = false;
-      document.getElementById('interaction-video').play();
+      this.player.play();
     },
     // 确定新增资源
     submitAdd(id, type, isAdd) {
@@ -159,6 +260,7 @@ export default {
           currentTime: this.currentTime,
           file_name: type,
           id,
+          position: (this.currentTime / this.duration) * 100,
         });
         this.file_id_list.push(id);
       } else {
@@ -167,13 +269,16 @@ export default {
       }
 
       this.exerciseFlag = false;
-      document.getElementById('interaction-video').play();
+      // document.getElementById('interaction-video').play();
+      this.player.play();
     },
     // 暂停视频上传资源
     handlePause() {
-      const video = document.getElementById('interaction-video');
-      video.pause();
-      this.currentTime = video.currentTime.toFixed(3);
+      // const video = document.getElementById('interaction-video');
+      // video.pause();
+      // this.currentTime = video.currentTime.toFixed(3);
+      this.playTime = JSON.parse(JSON.stringify(this.currentTime));
+      this.player.pause();
       this.file_list = [];
       this.file_id_list = [];
       // this.sourceAddFlag = true;
@@ -223,6 +328,12 @@ export default {
         });
     },
   },
+  mounted() {},
+  beforeDestroy() {
+    if (this.player) {
+      this.player.dispose();
+    }
+  },
 };
 </script>
 <style lang="scss" scoped>

+ 93 - 12
src/views/book/courseware/preview/components/video_interaction/VideoInteractionPreview.vue

@@ -3,7 +3,7 @@
   <div class="imageText-preview" :style="[getAreaStyle(), getComponentStyle()]">
     <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
     <div v-if="data.video_list.length > 0" class="interaction-box">
-      <video
+      <!-- <video
         id="interaction-preview-video"
         :src="data.video_list[0].file_url"
         width="100%"
@@ -11,6 +11,15 @@
         controls
         controlsList="nodownload"
         @timeupdate="handleTimeUpdate"
+      ></video> -->
+      <video
+        ref="videoPlayer"
+        id="interaction-preview-video"
+        :src="data.video_list[0].file_url"
+        width="100%"
+        height="400"
+        controls
+        class="video-js vjs-default-skin vjs-big-play-centered"
       ></video>
     </div>
     <!-- v-if="Object.keys(this.userAnswer).length === data.file_info_list.length" -->
@@ -21,6 +30,7 @@
           data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '#165dff',
         borderColor:
           data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '#165dff',
+        marginTop: '5px',
       }"
       @click="lookReport"
       >{{ convertText('查看答题报告') }}</el-button
@@ -77,6 +87,10 @@ import Report from './Report.vue';
 import PreviewMixin from '../common/PreviewMixin';
 import { getVideoInteractionData } from '@/views/book/courseware/data/videoInteraction';
 import { getConfig } from '@/utils/auth';
+import 'videojs-markers/dist/videojs.markers.min.css';
+import videojs from 'video.js';
+import 'video.js/dist/video-js.css';
+import 'videojs-markers';
 export default {
   name: 'VideoInteractionPreview',
 
@@ -107,6 +121,7 @@ export default {
         error: 0,
         rightRate: 0,
       },
+      player: null,
     };
   },
   watch: {
@@ -125,18 +140,79 @@ export default {
       if (!this.isJudgingRightWrong) {
         this.answer.answer_list[0] = this.userAnswer;
       }
+      if (this.data.video_list.length > 0) {
+        let _this = this;
+        this.$nextTick(() => {
+          _this.handleCreatPlayer();
+        });
+      }
     },
-    handleTimeUpdate(event) {
-      let currentTime = event.target.currentTime;
-
+    // 初始化player
+    handleCreatPlayer() {
+      let _this = this;
+      let options = {
+        autoplay: false, //自动播放
+        height: 500,
+        width: 918,
+        controls: true, //用户可以与之交互的控件
+        loop: true, //视频一结束就重新开始
+        muted: false, //默认情况下将使所有音频静音
+        playsinline: true,
+        webkitPlaysinline: true,
+        // aspectRatio:"16:9",//显示比率
+        playbackRates: [0.5, 1, 1.5, 2],
+        fullscreen: {
+          options: { navigationUI: 'hide' },
+        },
+        sources: [
+          {
+            src: this.data.video_list[0].file_url,
+            type: 'video/mp4',
+          },
+        ],
+      };
+      this.player = videojs(this.$refs.videoPlayer, options, function onPlayerReady() {
+        // console.log('onPlayerReady');
+      });
+      // 设置标点
+      _this.handleMarkers();
+      // 获取当前播放时间
+      this.player.on('timeupdate', function (event) {
+        _this.handleTimeUpdate(this.currentTime());
+        // console.log(this.currentTime());
+      });
+    },
+    // 设置标点
+    handleMarkers() {
+      let markers = [];
+      this.data.file_info_list.forEach((item) => {
+        markers.push({
+          time: item.currentTime,
+          text: '',
+        });
+      });
+      this.player.markers({
+        // 不显示鼠标悬浮标记提示文字
+        markerTip: {
+          display: true,
+        },
+        markerStyle: {
+          width: '4px',
+          'background-color': 'red',
+          'border-radius': '50%',
+        },
+        markers: markers,
+      });
+    },
+    handleTimeUpdate(currentTime) {
       this.data.file_info_list.forEach((item) => {
         if (
-          Number(item.currentTime) > Math.floor(currentTime) &&
-          Number(item.currentTime) < Math.floor(currentTime) + 1 &&
+          Number(item.currentTime) > Math.floor(currentTime) - 0.3 &&
+          Number(item.currentTime) < Math.floor(currentTime) + 0.3 &&
           item.id !== this.first
         ) {
           this.first = item.id;
-          document.getElementById('interaction-preview-video').pause();
+          this.player.pause();
           this.exercise_id = item.id;
           this.visible = true;
           // GetFileURLMap({ file_id_list: [item.file_id] }).then(({ url_map }) => {
@@ -148,22 +224,22 @@ export default {
     },
     handleClose() {
       this.reportFlag = false;
-      document.getElementById('interaction-preview-video').play();
+      this.player.play();
       // setTimeout(() => {
       //   this.first = '';
       // }, 1000);
     },
     submitAdd(id, answer, content) {
       this.visible = false;
-      document.getElementById('interaction-preview-video').play();
+      this.player.play();
       setTimeout(() => {
-        this.first = '';
-      }, 1000);
+        // this.first = '';
+      }, 2000);
       this.$set(this.userAnswer, id, answer);
       this.$set(this.exerciseList, id, content);
     },
     lookReport() {
-      document.getElementById('interaction-preview-video').pause();
+      this.player.pause();
       let num = 0;
       let total = this.data.file_info_list.length;
       Object.keys(this.userAnswer).forEach((key) => {
@@ -180,6 +256,11 @@ export default {
       this.reportFlag = true;
     },
   },
+  beforeDestroy() {
+    if (this.player) {
+      this.player.dispose();
+    }
+  },
 };
 </script>