lex преди 1 година
родител
ревизия
d60dd0e2ae

+ 2 - 2
src/components/card-preview/song-modal/index.module.less

@@ -61,7 +61,7 @@
     font-size: 24px;
     font-weight: 600;
     line-height: 33px;
-    min-width: 140px;
+    min-width: 150px;
 
     .line {
       font-size: 12px;
@@ -132,4 +132,4 @@
   pointer-events: none;
   transform: translateY(100%);
   transition: all .5s;
-}
+}

+ 206 - 102
src/components/card-preview/video-modal/index.module.less

@@ -1,73 +1,43 @@
 .videoWrap {
   width: 100%;
   height: 100%;
-  --plyr-color-main: #198CFE;
-  --plyr-range-track-height: 6px;
-  --plyr-tooltip-radius: 3px;
-  --plyr-range-thumb-height: 24px;
-  --plyr-video-controls-background: #000;
-
-
-  :global {
-    .plyr--video {
-      width: 100%;
-      height: 100%;
-    }
-
-    .plyr__time {
-      display: block !important;
-    }
-
-    .plyr__video-wrapper {
-      pointer-events: none;
-    }
-
-  }
-}
-
-:global(.bottomFixed).controls {
-  width: 100% !important;
-  background: rgba(0, 0, 0, 0.6) !important;
-  // backdrop-filter: blur(26px);
-  height: 80px !important;
-  min-height: 80px !important;
-  padding: 0px 40px !important;
-  z-index: 999;
-
-  .time {
-    display: flex;
-    justify-content: space-between;
-    color: #fff;
-    padding: 4px 12px 4px;
-    font-size: 24px;
-    font-weight: 600;
-    line-height: 33px;
-    min-width: 140px;
-
-    .line {
-      font-size: 12px;
-    }
-
-    :global {
-      .plyr__time+.plyr__time:before {
-        content: '';
-        margin-right: 0;
-      }
-    }
-  }
-
-  .slider {
+  height: 518px;
+
+  .controls {
+    border-radius: 0 0 16px 16px !important;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
     width: 100%;
-    padding: 0 20px 0 12px;
+    background: rgba(0, 0, 0, 0.6);
+    backdrop-filter: blur(26px);
+    height: 80px;
+    padding: 0 40px 0 40px !important;
+    transition: all 0.5s;
+    display: flex;
+    align-items: center;
+    transition: all .5s;
 
-    :global {
-      .van-slider__button {
-        background: var(--van-primary);
+    .time {
+      display: flex;
+      justify-content: space-between;
+      color: #fff;
+      padding: 4px 12px 4px;
+      font-size: 24px;
+      font-weight: 600;
+      line-height: 33px;
+      min-width: 150px;
+
+      .line {
+        font-size: 12px;
       }
 
-      .van-loading {
-        width: 100%;
-        height: 100%;
+      :global {
+        .plyr__time+.plyr__time:before {
+          content: '';
+          margin-right: 0;
+        }
       }
     }
   }
@@ -75,6 +45,7 @@
   .actions {
     display: flex;
     justify-content: space-between;
+    height: 100%;
     color: #fff;
     font-size: 12px;
     align-items: center;
@@ -85,62 +56,195 @@
 
     .actionBtn {
       display: flex;
-
-      padding: 0;
       width: 52px;
       height: 52px;
+      padding: 0;
       background: transparent;
-    }
-
-    .actionBtn>img {
-      width: 100%;
-      height: 100%;
-    }
 
-    :global {
-      .van-loading__circular {
+      &>img {
         width: 100%;
         height: 100%;
       }
     }
 
-    .playIcon {
-      display: none;
-    }
-
-    .btnPlay img:nth-child(2) {
-      display: block;
-    }
 
-    .btnPause img:nth-child(3) {
-      display: block;
-    }
+    .iconReplay {
+      width: 31px;
+      height: 29px;
+      background-color: transparent;
 
-    .btnPlay,
-    .btnPause {
-      :global {
-        .van-loading {
-          display: none;
-        }
+      &>img {
+        width: 100%;
+        height: 100%;
       }
     }
+  }
 
-    .loopBtn {
-      background-color: transparent;
-      width: 31px !important;
-      height: 29px !important;
-      padding: 0;
-      cursor: pointer;
+  .slider {
+    width: 100%;
+    padding: 0 20px 0 12px;
 
-      :global {
-        .loop {
-          display: block;
-        }
+    :global {
 
-        .loopActive {
-          display: none;
-        }
+      .n-slider .n-slider-rail .n-slider-rail__fill,
+      .n-slider .n-slider-handles .n-slider-handle-wrapper {
+        transition: all .2s;
       }
     }
   }
+
+  .sectionAnimate {
+    opacity: 0;
+    pointer-events: none;
+    transform: translateY(100%);
+    transition: all .5s;
+  }
+
 }
+
+// .videoWrap {
+//   width: 100%;
+//   height: 100%;
+//   --plyr-color-main: #198CFE;
+//   --plyr-range-track-height: 6px;
+//   --plyr-tooltip-radius: 3px;
+//   --plyr-range-thumb-height: 24px;
+//   --plyr-video-controls-background: #000;
+
+
+//   :global {
+//     .plyr--video {
+//       width: 100%;
+//       height: 100%;
+//     }
+
+//     .plyr__time {
+//       display: block !important;
+//     }
+
+//     .plyr__video-wrapper {
+//       pointer-events: none;
+//     }
+
+//   }
+// }
+
+// :global(.bottomFixed).controls {
+//   width: 100% !important;
+//   background: rgba(0, 0, 0, 0.6) !important;
+//   // backdrop-filter: blur(26px);
+//   height: 80px !important;
+//   min-height: 80px !important;
+//   padding: 0px 40px !important;
+//   z-index: 999;
+
+//   .time {
+//     display: flex;
+//     justify-content: space-between;
+//     color: #fff;
+//     padding: 4px 12px 4px;
+//     font-size: 24px;
+//     font-weight: 600;
+//     line-height: 33px;
+//     min-width: 140px;
+
+//     .line {
+//       font-size: 12px;
+//     }
+
+//     :global {
+//       .plyr__time+.plyr__time:before {
+//         content: '';
+//         margin-right: 0;
+//       }
+//     }
+//   }
+
+//   .slider {
+//     width: 100%;
+//     padding: 0 20px 0 12px;
+
+//     :global {
+//       .van-slider__button {
+//         background: var(--van-primary);
+//       }
+
+//       .van-loading {
+//         width: 100%;
+//         height: 100%;
+//       }
+//     }
+//   }
+
+//   .actions {
+//     display: flex;
+//     justify-content: space-between;
+//     color: #fff;
+//     font-size: 12px;
+//     align-items: center;
+
+//     .actionWrap {
+//       display: flex;
+//     }
+
+//     .actionBtn {
+//       display: flex;
+
+//       padding: 0;
+//       width: 52px;
+//       height: 52px;
+//       background: transparent;
+//     }
+
+//     .actionBtn>img {
+//       width: 100%;
+//       height: 100%;
+//     }
+
+//     :global {
+//       .van-loading__circular {
+//         width: 100%;
+//         height: 100%;
+//       }
+//     }
+
+//     .playIcon {
+//       display: none;
+//     }
+
+//     .btnPlay img:nth-child(2) {
+//       display: block;
+//     }
+
+//     .btnPause img:nth-child(3) {
+//       display: block;
+//     }
+
+//     .btnPlay,
+//     .btnPause {
+//       :global {
+//         .van-loading {
+//           display: none;
+//         }
+//       }
+//     }
+
+//     .loopBtn {
+//       background-color: transparent;
+//       width: 31px !important;
+//       height: 29px !important;
+//       padding: 0;
+//       cursor: pointer;
+
+//       :global {
+//         .loop {
+//           display: block;
+//         }
+
+//         .loopActive {
+//           display: none;
+//         }
+//       }
+//     }
+//   }
+// }

+ 132 - 92
src/components/card-preview/video-modal/index.tsx

@@ -1,11 +1,14 @@
-import { defineComponent, nextTick, onMounted, toRefs } from 'vue';
-import 'plyr/dist/plyr.css';
-import Plyr from 'plyr';
+import { defineComponent, nextTick, onMounted, reactive, toRefs } from 'vue';
+// import 'plyr/dist/plyr.css';
+// import Plyr from 'plyr';
 import { ref } from 'vue';
+import TCPlayer from 'tcplayer.js';
+import 'tcplayer.js/dist/tcplayer.min.css';
 import styles from './index.module.less';
 import iconplay from '@views/attend-class/image/icon-pause.png';
 import iconpause from '@views/attend-class/image/icon-play.png';
 import iconReplay from '@views/attend-class/image/icon-replay.png';
+import { NSlider } from 'naive-ui';
 
 export default defineComponent({
   name: 'video-play',
@@ -26,114 +29,91 @@ export default defineComponent({
   emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
   setup(props, { emit, expose }) {
     const { src, poster, isEmtry } = toRefs(props);
+    const videoFroms = reactive({
+      paused: true,
+      currentTimeNum: 0,
+      currentTime: '00:00',
+      durationNum: 0,
+      duration: '00:00',
+      showBar: true
+    });
     const videoRef = ref();
-    const videoItem = ref<Plyr>();
-    const controlID = 'v' + Date.now() + Math.floor(Math.random() * 100);
-    const playBtnId = 'play' + Date.now() + Math.floor(Math.random() * 100);
-    const replayBtnId = 'replay' + Date.now() + Math.floor(Math.random() * 100);
-    const toggleHideControl = (isShow: false) => {
-      videoItem.value?.toggleControls(isShow);
-    };
-    const togglePlay = (e: Event) => {
-      e.stopPropagation();
-      videoItem.value?.togglePlay();
+    const videoItem = ref();
+    const videoID = 'video' + Date.now() + Math.floor(Math.random() * 100);
+
+    // 对时间进行格式化
+    const timeFormat = (num: number) => {
+      if (num > 0) {
+        const m = Math.floor(num / 60);
+        const s = num % 60;
+        return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
+      } else {
+        return '00:00';
+      }
     };
-    const toggleReplay = () => {
-      const replayBtn = document.getElementById(replayBtnId);
-      if (!replayBtn || !videoItem.value) return;
-      videoItem.value.restart();
+
+    //
+    const toggleHideControl = (isShow: false) => {
+      videoFroms.showBar = isShow;
     };
-    const onDefault = () => {
-      document
-        .getElementById(controlID)
-        ?.addEventListener('click', (e: Event) => {
-          e.stopPropagation();
-          emit('reset');
-        });
-      document.getElementById(playBtnId)?.addEventListener('click', togglePlay);
-      document
-        .getElementById(replayBtnId)
-        ?.addEventListener('click', toggleReplay);
+
+    const onReplay = () => {
+      if (!videoItem.value) return;
+      videoItem.value.currentTime(0);
     };
 
-    const changePlayBtn = (code: string) => {
-      const playBtn = document.getElementById(playBtnId);
-      if (!playBtn) return;
-      if (code == 'play') {
-        playBtn.classList.remove(styles.btnPause);
-        playBtn.classList.add(styles.btnPlay);
+    // 切换音频播放
+    const onToggleVideo = (e?: MouseEvent) => {
+      e?.stopPropagation();
+      if (videoFroms.paused) {
+        videoItem.value.play();
+        videoFroms.paused = false;
       } else {
-        playBtn.classList.remove(styles.btnPlay);
-        playBtn.classList.add(styles.btnPause);
+        videoItem.value.pause();
+        videoFroms.paused = true;
       }
+      emit('togglePlay', videoFroms.paused);
     };
-    const controls = `
-            <div id="${controlID}" class="plyr__controls bottomFixed ${styles.controls}">
-                <div class="${styles.actions}">
-                    <div class="${styles.actionWrap}">
-                        <button id="${playBtnId}" class="${styles.actionBtn}">
-                            <div class="van-loading van-loading--circular" aria-live="polite" aria-busy="true"><span class="van-loading__spinner van-loading__spinner--circular" style="color: rgb(255, 255, 255);"><svg class="van-loading__circular" viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
-                            <img class="${styles.playIcon}" src="${iconplay}" />
-                            <img class="${styles.playIcon}" src="${iconpause}" />
-                        </button>
-                    </div>
-                    <div class="${styles.time}">
-                        <div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div><span class="${styles.line}">/</span>
-                        <div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div>
-                    </div>
-                </div>
-                <div class="${styles.slider}">
-                    <div class="plyr__progress">
-                        <input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek">
-                        <progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress>
-                        <span role="tooltip" class="plyr__tooltip">00:00</span>
-                    </div>
-
-                </div>
-                <div class="${styles.actions}" style="padding-right: 0;">
-                    <button id="${replayBtnId}" class="${styles.actionBtn} ${styles.loopBtn}">
-                        <img class="loop" src="${iconReplay}" />
-                    </button>
-                </div>
-            </div>`;
 
     onMounted(() => {
-      videoItem.value = new Plyr(videoRef.value, {
-        autoplay: false,
-        controls: controls,
-        autopause: true, // 一次只允许
-        ratio: '16:9', // 强制所有视频的纵横比
-        // hideControls: false, // 在 2 秒没有鼠标或焦点移动、控制元素模糊(制表符退出)、播放开始或进入全屏时自动隐藏视频控件。只要移动鼠标、聚焦控制元素或暂停播放,控件就会立即重新出现。
-        clickToPlay: false, // 单击(或点击)视频容器将切换播放/暂停
-        fullscreen: { enabled: false, fallback: false, iosNative: false } // 不适用全屏
-      });
+      videoItem.value = TCPlayer(videoID, {
+        appID: '',
+        controls: false
+      }); // player-container-id 为播放器容器 ID,必须与 html 中一致
       if (videoItem.value) {
-        videoItem.value.on('play', () => {
-          if (videoItem.value) {
-            videoItem.value.muted = false;
-            videoItem.value.volume = 1;
-          }
+        videoItem.value.poster(poster.value); // 封面
+        videoItem.value.src(isEmtry.value ? '' : src.value); // url 播放地址
 
-          changePlayBtn('');
+        // 初步加载时
+        videoItem.value.one('loadedmetadata', () => {
+          console.log(' Loading metadata');
+
+          // 获取时长
+          videoFroms.duration = timeFormat(
+            Math.round(videoItem.value.duration())
+          );
+          videoFroms.durationNum = videoItem.value.duration();
+
+          emit('loadedmetadata', videoItem.value);
         });
-        videoItem.value.on('pause', () => {
-          changePlayBtn('play');
+
+        // 视频播放时加载
+        videoItem.value.on('timeupdate', () => {
+          videoFroms.currentTime = timeFormat(
+            Math.round(videoItem.value?.currentTime() || 0)
+          );
+          videoFroms.currentTimeNum = videoItem.value.currentTime();
         });
+
+        // 视频播放结束
         videoItem.value.on('ended', () => {
+          videoFroms.paused = true;
           emit('ended');
-          changePlayBtn('play');
-        });
-        videoItem.value.once('loadedmetadata', () => {
-          changePlayBtn('play');
-        });
-
-        nextTick(() => {
-          onDefault();
         });
       }
     });
     expose({
-      changePlayBtn,
+      // changePlayBtn,
       toggleHideControl
     });
     return () => (
@@ -143,7 +123,67 @@ export default defineComponent({
           src={isEmtry.value ? '' : src.value}
           poster={poster.value}
           ref={videoRef}
-          playsinline="false"></video>
+          id={videoID}
+          preload="auto"
+          playsinline
+          webkit-playsinline></video>
+
+        <div
+          class={[
+            styles.controls,
+            videoFroms.showBar ? '' : styles.sectionAnimate
+          ]}
+          onClick={(e: MouseEvent) => {
+            e.stopPropagation();
+            emit('reset');
+          }}>
+          <div class={styles.actions}>
+            <div class={styles.actionWrap}>
+              <button class={styles.actionBtn} onClick={onToggleVideo}>
+                {videoFroms.paused ? (
+                  <img class={styles.playIcon} src={iconplay} />
+                ) : (
+                  <img class={styles.playIcon} src={iconpause} />
+                )}
+              </button>
+            </div>
+            <div class={styles.time}>
+              <div
+                class="plyr__time plyr__time--current"
+                aria-label="Current time">
+                {videoFroms.currentTime}
+              </div>
+              <span class={styles.line}>/</span>
+              <div
+                class="plyr__time plyr__time--duration"
+                aria-label="Duration">
+                {videoFroms.duration}
+              </div>
+            </div>
+          </div>
+
+          <div class={styles.slider}>
+            <NSlider
+              value={videoFroms.currentTimeNum}
+              step={0.01}
+              max={videoFroms.durationNum}
+              tooltip={false}
+              onUpdate:value={(val: number) => {
+                videoItem.value.currentTime(val);
+                videoFroms.currentTimeNum = val;
+                videoFroms.currentTime = timeFormat(Math.round(val || 0));
+              }}
+            />
+          </div>
+
+          <div class={styles.actions}>
+            <div class={styles.actionWrap}>
+              <button class={styles.iconReplay} onClick={onReplay}>
+                <img src={iconReplay} />
+              </button>
+            </div>
+          </div>
+        </div>
       </div>
     );
   }

+ 2 - 1
src/views/attend-class/component/video-play.tsx

@@ -104,6 +104,7 @@ export default defineComponent({
 
         // 视频播放结束
         videoItem.value.on('ended', () => {
+          videoFroms.paused = true;
           emit('ended');
         });
       }
@@ -164,7 +165,7 @@ export default defineComponent({
               max={videoFroms.durationNum}
               tooltip={false}
               onUpdate:value={(val: number) => {
-                videoItem.value.currentTime = val;
+                videoItem.value.currentTime(val);
                 videoFroms.currentTimeNum = val;
                 videoFroms.currentTime = timeFormat(Math.round(val || 0));
               }}