Jelajahi Sumber

添加速度显示

lex 11 bulan lalu
induk
melakukan
7928136d82

+ 63 - 27
src/views/attend-class/component/audio-pay.tsx

@@ -40,6 +40,7 @@ export default defineComponent({
   setup(props, { emit, expose }) {
     const audioForms = reactive({
       paused: true,
+      speedInKbps: '0 KB/s',
       currentTimeNum: 0,
       currentTime: '00:00',
       durationNum: 0,
@@ -149,9 +150,64 @@ export default defineComponent({
       }
     );
 
-    // onMounted(() => {
-    //   console.log(props.item, 'eeeee');
-    // });
+    const calculateSpeed = (element: any) => {
+      let previousBytesLoaded = 0;
+      let timer: any = null;
+      let previousTime = Date.now();
+
+      function resetDownloadSpeed() {
+        timer = setTimeout(() => {
+          // displayElement.textContent = `视屏下载速度: 0 KB/s`;
+          audioForms.speedInKbps = `0 KB/s`;
+        }, 1000);
+      }
+
+      element.addEventListener('progress', () => {
+        const currentTime = Date.now();
+        const buffered = element.buffered;
+        let currentBytesLoaded = 0;
+
+        if (buffered.length > 0) {
+          for (let i = 0; i < buffered.length; i++) {
+            currentBytesLoaded += buffered.end(i) - buffered.start(i);
+          }
+          currentBytesLoaded *= element.duration * element.seekable.end(0); // 更精确地近似字节加载量
+        }
+
+        // console.log(
+        //   'progress',
+        //   currentBytesLoaded,
+        //   previousBytesLoaded,
+        //   currentBytesLoaded > previousBytesLoaded
+        // );
+        if (currentBytesLoaded > previousBytesLoaded) {
+          const timeDiff = (currentTime - previousTime) / 1000; // 时间差转换为秒
+          const bytesDiff = currentBytesLoaded - previousBytesLoaded; // 字节差值
+          const speed = bytesDiff / timeDiff; // 字节每秒
+
+          const kbps = speed / 1024;
+          const speedInKbps = kbps.toFixed(2); // 转换为千字节每秒并保留两位小数
+          if (kbps > 1024) {
+            audioForms.speedInKbps = `${Number((kbps / 1024).toFixed(2))} M/s`;
+          } else {
+            audioForms.speedInKbps = `${Number(speedInKbps)} KB/s`;
+          }
+
+          previousBytesLoaded = currentBytesLoaded;
+          previousTime = currentTime;
+
+          // 如果1秒钟没有返回就重置数据
+          clearTimeout(timer);
+          resetDownloadSpeed();
+        }
+      });
+    };
+
+    onMounted(() => {
+      nextTick(() => {
+        calculateSpeed(audio.value);
+      });
+    });
 
     expose({
       toggleHideControl
@@ -163,7 +219,7 @@ export default defineComponent({
           <audio
             ref={audio}
             crossorigin="anonymous"
-            src={props.item.content + '?time=1'}
+            src={props.item.content}
             onEnded={() => {
               audioForms.paused = true;
               emit('ended');
@@ -202,32 +258,10 @@ export default defineComponent({
                   emit('togglePlay', audioForms.paused);
                 };
               }
-
               emit('loadedmetadata', audio.value);
             }}
             onProgress={(e: any) => {
               console.log(e, 'loadedmetadata onProgress');
-              const currentTime = Date.now();
-              const videoElement = e.target;
-              console.log(videoElement, audio.value.buffered);
-              const currentBytesLoaded = videoElement.buffered.length
-                ? videoElement.buffered.end(0) * videoElement.duration
-                : 0;
-
-              if (currentBytesLoaded > audioForms.previousBytesLoaded) {
-                const timeDiff = (currentTime - audioForms.previousTime) / 1000; // Time difference in seconds
-                const bytesDiff =
-                  currentBytesLoaded - audioForms.previousBytesLoaded; // Bytes downloaded since last check
-                const speed = bytesDiff / timeDiff; // Bytes per second
-
-                const speedInKbps = (speed / 1024).toFixed(2); // Convert to KB/s
-
-                // speedDisplay.textContent = `Download speed: ${speedInKbps} KB/s`;
-                console.log(`Download speed: ${speedInKbps} KB/s`);
-
-                audioForms.previousBytesLoaded = currentBytesLoaded;
-                audioForms.previousTime = currentTime;
-              }
             }}></audio>
 
           <canvas ref={canvas}></canvas>
@@ -311,7 +345,9 @@ export default defineComponent({
                     <button class={styles.iconReplay} onClick={onReplay}>
                       <img src={iconReplay} />
                     </button>
-                    <div class={styles.downloadSpeed}>37.8M/s</div>
+                    <div class={styles.downloadSpeed}>
+                      {audioForms.speedInKbps}
+                    </div>
                   </div>
                 </div>
                 <div class={styles.actions}>

+ 607 - 544
src/views/attend-class/component/video-play.tsx

@@ -1,544 +1,607 @@
-import {
-  defineComponent,
-  nextTick,
-  onMounted,
-  onUnmounted,
-  reactive,
-  toRefs,
-  watch
-} from 'vue';
-import TCPlayer from 'tcplayer.js';
-import 'tcplayer.js/dist/tcplayer.min.css';
-// import 'plyr/dist/plyr.css';
-// import Plyr from 'plyr';
-import { ref } from 'vue';
-import styles from './video.module.less';
-import iconplay from '../image/icon-pause.png';
-import iconpause from '../image/icon-play.png';
-// import iconReplay from '../image/icon-replay.png';
-import iconLoop from '../image/icon-loop.svg';
-import iconLoopActive from '../image/icon-loop-active.svg';
-import iconSpeed from '../image/icon-speed.png';
-import { NSlider } from 'naive-ui';
-
-export default defineComponent({
-  name: 'video-play',
-  props: {
-    item: {
-      type: Object,
-      default: () => {
-        return {};
-      }
-    },
-    showModel: {
-      type: Boolean,
-      default: false
-    },
-    isEmtry: {
-      type: Boolean,
-      default: false
-    },
-    imagePos: {
-      type: String,
-      default: 'left'
-    }
-  },
-  emits: [
-    'canplay',
-    'pause',
-    'togglePlay',
-    'ended',
-    'reset',
-    'error',
-    'close',
-    'loadedmetadata'
-  ],
-  setup(props, { emit, expose }) {
-    const { item, isEmtry } = toRefs(props);
-    const videoFroms = reactive({
-      paused: true,
-      currentTimeNum: 0,
-      currentTime: '00:00',
-      durationNum: 0,
-      duration: '00:00',
-      showBar: true,
-      showAction: true,
-      loop: false,
-      speedControl: false,
-      speedStyle: {
-        left: '1px'
-      },
-      defaultSpeed: 1 // 默认速度
-    });
-    const videoRef = ref();
-    const videoItem = ref();
-    const videoID = ref('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 onPlay = () => {
-      if (videoItem.value) {
-        videoItem.value.src(item.value.content);
-        emit('reset');
-      }
-    };
-
-    //
-    const toggleHideControl = (isShow: false) => {
-      videoFroms.showBar = isShow;
-      videoFroms.speedControl = false;
-    };
-
-    const onReplay = () => {
-      videoFroms.speedControl = false;
-      if (!videoItem.value) return;
-      videoItem.value.currentTime(0);
-    };
-
-    // 切换音频播放
-    const onToggleVideo = (e?: MouseEvent) => {
-      e?.stopPropagation();
-      if (videoFroms.paused) {
-        videoItem.value.play();
-        videoFroms.paused = false;
-      } else {
-        videoItem.value.pause();
-        videoFroms.paused = true;
-      }
-      emit('togglePlay', videoFroms.paused);
-    };
-
-    const videoTimer = null as any;
-    let videoTimerErrorCount = 0;
-    const handlePlayVideo = () => {
-      if (videoTimerErrorCount > 5) {
-        return;
-      }
-      clearTimeout(videoTimer);
-      nextTick(() => {
-        videoItem.value?.play().catch((err: any) => {
-          // console.log('🚀 ~ err:', err)
-          // videoTimer = setTimeout(() => {
-          //   if (err?.message?.includes('play()')) {
-          //     // emit('play');
-          //   }
-          //   handlePlayVideo();
-          // }, 1000);
-        });
-      });
-      videoTimerErrorCount++;
-    };
-
-    const __init = () => {
-      if (videoItem.value) {
-        videoItem.value.poster(props.item.coverImg); // 封面
-        videoItem.value.src(item.value.content); // url 播放地址
-        videoItem.value.playbackRate(videoFroms.defaultSpeed);
-        // 初步加载时
-        videoItem.value.one('loadedmetadata', () => {
-          // console.log(' Loading metadata');
-          videoItem.value.playbackRate(videoFroms.defaultSpeed);
-
-          // 获取时长
-          videoFroms.duration = timeFormat(
-            Math.round(videoItem.value.duration())
-          );
-          videoFroms.durationNum = videoItem.value.duration();
-
-          emit('canplay');
-          emit('loadedmetadata', videoItem.value);
-
-          if (item.value.autoPlay && videoItem.value) {
-            // videoItem.value?.play()
-            nextTick(() => {
-              videoTimerErrorCount = 0;
-              videoItem.value.currentTime(0);
-              nextTick(handlePlayVideo);
-            });
-          }
-        });
-
-        // 视频开始播放
-        videoItem.value.on('play', () => {
-          emit('close');
-          emit('canplay');
-        });
-
-        // 视频播放时加载
-        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');
-        });
-
-        //
-        videoItem.value.on('pause', () => {
-          videoFroms.paused = true;
-          emit('pause');
-        });
-
-        videoItem.value.on('playing', () => {
-          videoFroms.paused = false;
-        });
-
-        videoItem.value.on('canplay', (e: any) => {
-          // 获取时长
-          videoFroms.duration = timeFormat(
-            Math.round(videoItem.value.duration())
-          );
-          videoFroms.durationNum = videoItem.value.duration();
-          emit('canplay');
-        });
-
-        // 视频播放异常
-        videoItem.value.on('error', (e: any) => {
-          emit('error');
-          console.log(e, 'error');
-        });
-      }
-    };
-
-    onMounted(() => {
-      videoItem.value = TCPlayer(videoID.value, {
-        appID: '',
-        controls: false
-      }); // player-container-id 为播放器容器 ID,必须与 html 中一致
-
-      __init();
-    });
-    const stop = () => {
-      videoItem.value.currentTime(0);
-      videoItem.value.pause();
-    };
-
-    const pause = () => {
-      videoItem.value.pause();
-    };
-
-    onUnmounted(() => {
-      if (videoItem.value) {
-        videoItem.value.pause();
-        videoItem.value.src('');
-        videoItem.value.dispose();
-      }
-    });
-
-    watch(
-      () => props.item,
-      () => {
-        // console.log(item.value, 'value----');
-        videoItem.value.pause();
-        videoItem.value.currentTime(0);
-        if (item.value?.id) {
-          // videoItem.value.poster(props.item.coverImg); // 封面
-          // videoItem.value.src(item.value.content); // url 播放地址
-          __init();
-
-          videoFroms.paused = true;
-        }
-      }
-    );
-    watch(
-      () => props.showModel,
-      () => {
-        // console.log(props.showModel, 'props.showModel')
-        videoFroms.showAction = props.showModel;
-        videoFroms.speedControl = false;
-      }
-    );
-    expose({
-      onPlay,
-      stop,
-      pause,
-      // changePlayBtn,
-      toggleHideControl
-    });
-    return () => (
-      <div class={styles.videoWrap}>
-        <video
-          style={{ width: '100%', height: '100%' }}
-          ref={videoRef}
-          id={videoID.value}
-          preload="auto"
-          playsinline
-          webkit-playsinline
-          x5-video-player-type="h5"></video>
-        <div class={styles.videoPop}></div>
-
-        <div
-          class={[
-            styles.controls,
-            videoFroms.showAction ? '' : styles.sectionAnimate
-          ]}
-          onClick={(e: MouseEvent) => {
-            e.stopPropagation();
-            if (videoItem.value.paused()) return;
-            emit('close');
-            emit('reset');
-          }}>
-          <div class={styles.slider}>
-            <NSlider
-              value={videoFroms.currentTimeNum}
-              step={0.01}
-              max={videoFroms.durationNum}
-              tooltip={false}
-              onUpdate:value={(val: number) => {
-                videoFroms.speedControl = false;
-                videoItem.value.currentTime(val);
-                videoFroms.currentTimeNum = val;
-                videoFroms.currentTime = timeFormat(Math.round(val || 0));
-              }}
-            />
-          </div>
-          <div class={styles.tools}>
-            {props.imagePos === 'right' ? (
-              <>
-                <div class={styles.actions}>
-                  <div class={styles.actionWrap}>
-                    <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>
-                <div class={styles.actions}>
-                  <div class={styles.actionWrap}>
-                    <div
-                      class={styles.actionBtnSpeed}
-                      onClick={e => {
-                        e.stopPropagation();
-                        videoFroms.speedControl = !videoFroms.speedControl;
-                      }}>
-                      <img src={iconSpeed} />
-                      <div
-                        style={{
-                          display: videoFroms.speedControl ? 'block' : 'none'
-                        }}>
-                        <div
-                          class={styles.sliderPopup}
-                          onClick={(e: Event) => {
-                            e.stopPropagation();
-                          }}>
-                          <i
-                            class={styles.iconAdd}
-                            onClick={() => {
-                              if (videoFroms.defaultSpeed >= 1.5) {
-                                return;
-                              }
-
-                              if (videoItem.value) {
-                                videoFroms.defaultSpeed =
-                                  (videoFroms.defaultSpeed * 10 + 1) / 10;
-                                videoItem.value.playbackRate(
-                                  videoFroms.defaultSpeed
-                                );
-                              }
-                            }}></i>
-                          <NSlider
-                            value={videoFroms.defaultSpeed}
-                            step={0.1}
-                            max={1.5}
-                            min={0.5}
-                            vertical
-                            tooltip={false}
-                            onUpdate:value={(val: number) => {
-                              videoFroms.defaultSpeed = val;
-
-                              if (videoItem.value) {
-                                videoItem.value.playbackRate(
-                                  videoFroms.defaultSpeed
-                                );
-                              }
-                            }}>
-                            {{
-                              thumb: () => (
-                                <div class={styles.sliderPoint}>
-                                  {videoFroms.defaultSpeed}
-                                  <span>x</span>
-                                </div>
-                              )
-                            }}
-                          </NSlider>
-                          <i
-                            class={[styles.iconCut]}
-                            onClick={() => {
-                              if (videoFroms.defaultSpeed <= 0.5) {
-                                return;
-                              }
-                              if (videoItem.value) {
-                                videoFroms.defaultSpeed =
-                                  (videoFroms.defaultSpeed * 10 - 1) / 10;
-                                videoItem.value.playbackRate(
-                                  videoFroms.defaultSpeed
-                                );
-                              }
-                            }}></i>
-                        </div>
-                      </div>
-                    </div>
-                    <button class={styles.iconReplay} onClick={onReplay}>
-                      <img src={iconLoop} />
-                    </button>
-                    <div
-                      class={styles.actionBtn}
-                      onClick={() => {
-                        videoFroms.speedControl = false;
-                        onToggleVideo();
-                      }}>
-                      {videoFroms.paused ? (
-                        <img class={styles.playIcon} src={iconplay} />
-                      ) : (
-                        <img class={styles.playIcon} src={iconpause} />
-                      )}
-                    </div>
-                  </div>
-                </div>
-              </>
-            ) : (
-              <>
-                <div class={styles.actions}>
-                  <div class={styles.actionWrap}>
-                    <div
-                      class={styles.actionBtn}
-                      onClick={() => {
-                        videoFroms.speedControl = false;
-                        onToggleVideo();
-                      }}>
-                      {videoFroms.paused ? (
-                        <img class={styles.playIcon} src={iconplay} />
-                      ) : (
-                        <img class={styles.playIcon} src={iconpause} />
-                      )}
-                    </div>
-
-                    <button class={styles.iconReplay} onClick={onReplay}>
-                      <img src={iconLoop} />
-                    </button>
-
-                    <div
-                      class={styles.actionBtnSpeed}
-                      onClick={e => {
-                        e.stopPropagation();
-                        videoFroms.speedControl = !videoFroms.speedControl;
-                      }}>
-                      <img src={iconSpeed} />
-
-                      <div
-                        style={{
-                          display: videoFroms.speedControl ? 'block' : 'none'
-                        }}>
-                        <div
-                          class={styles.sliderPopup}
-                          onClick={(e: Event) => {
-                            e.stopPropagation();
-                          }}>
-                          <i
-                            class={styles.iconAdd}
-                            onClick={() => {
-                              if (videoFroms.defaultSpeed >= 1.5) {
-                                return;
-                              }
-
-                              if (videoItem.value) {
-                                videoFroms.defaultSpeed =
-                                  (videoFroms.defaultSpeed * 10 + 1) / 10;
-                                videoItem.value.playbackRate(
-                                  videoFroms.defaultSpeed
-                                );
-                              }
-                            }}></i>
-                          <NSlider
-                            value={videoFroms.defaultSpeed}
-                            step={0.1}
-                            max={1.5}
-                            min={0.5}
-                            vertical
-                            tooltip={false}
-                            onUpdate:value={(val: number) => {
-                              videoFroms.defaultSpeed = val;
-
-                              if (videoItem.value) {
-                                videoItem.value.playbackRate(
-                                  videoFroms.defaultSpeed
-                                );
-                              }
-                            }}>
-                            {{
-                              thumb: () => (
-                                <div class={styles.sliderPoint}>
-                                  {videoFroms.defaultSpeed}
-                                  <span>x</span>
-                                </div>
-                              )
-                            }}
-                          </NSlider>
-                          <i
-                            class={[styles.iconCut]}
-                            onClick={() => {
-                              if (videoFroms.defaultSpeed <= 0.5) {
-                                return;
-                              }
-                              if (videoItem.value) {
-                                videoFroms.defaultSpeed =
-                                  (videoFroms.defaultSpeed * 10 - 1) / 10;
-                                videoItem.value.playbackRate(
-                                  videoFroms.defaultSpeed
-                                );
-                              }
-                            }}></i>
-                        </div>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-                <div class={styles.actions}>
-                  <div class={styles.actionWrap}>
-                    <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>
-              </>
-            )}
-          </div>
-        </div>
-      </div>
-    );
-  }
-});
+import {
+  defineComponent,
+  nextTick,
+  onMounted,
+  onUnmounted,
+  reactive,
+  toRefs,
+  watch
+} from 'vue';
+import TCPlayer from 'tcplayer.js';
+import 'tcplayer.js/dist/tcplayer.min.css';
+// import 'plyr/dist/plyr.css';
+// import Plyr from 'plyr';
+import { ref } from 'vue';
+import styles from './video.module.less';
+import iconplay from '../image/icon-pause.png';
+import iconpause from '../image/icon-play.png';
+// import iconReplay from '../image/icon-replay.png';
+import iconLoop from '../image/icon-loop.svg';
+import iconLoopActive from '../image/icon-loop-active.svg';
+import iconSpeed from '../image/icon-speed.png';
+import { NSlider } from 'naive-ui';
+
+export default defineComponent({
+  name: 'video-play',
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    },
+    showModel: {
+      type: Boolean,
+      default: false
+    },
+    isEmtry: {
+      type: Boolean,
+      default: false
+    },
+    imagePos: {
+      type: String,
+      default: 'left'
+    }
+  },
+  emits: [
+    'canplay',
+    'pause',
+    'togglePlay',
+    'ended',
+    'reset',
+    'error',
+    'close',
+    'loadedmetadata'
+  ],
+  setup(props, { emit, expose }) {
+    const { item, isEmtry } = toRefs(props);
+    const videoFroms = reactive({
+      paused: true,
+      speedInKbps: '0 KB/s',
+      currentTimeNum: 0,
+      currentTime: '00:00',
+      durationNum: 0,
+      duration: '00:00',
+      showBar: true,
+      showAction: true,
+      loop: false,
+      speedControl: false,
+      speedStyle: {
+        left: '1px'
+      },
+      defaultSpeed: 1 // 默认速度
+    });
+    const videoRef = ref();
+    const videoItem = ref();
+    const videoID = ref('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 onPlay = () => {
+      if (videoItem.value) {
+        videoItem.value.src(item.value.content);
+        emit('reset');
+      }
+    };
+
+    //
+    const toggleHideControl = (isShow: false) => {
+      videoFroms.showBar = isShow;
+      videoFroms.speedControl = false;
+    };
+
+    const onReplay = () => {
+      videoFroms.speedControl = false;
+      if (!videoItem.value) return;
+      videoItem.value.currentTime(0);
+    };
+
+    // 切换音频播放
+    const onToggleVideo = (e?: MouseEvent) => {
+      e?.stopPropagation();
+      if (videoFroms.paused) {
+        videoItem.value.play();
+        videoFroms.paused = false;
+      } else {
+        videoItem.value.pause();
+        videoFroms.paused = true;
+      }
+      emit('togglePlay', videoFroms.paused);
+    };
+
+    const videoTimer = null as any;
+    let videoTimerErrorCount = 0;
+    const handlePlayVideo = () => {
+      if (videoTimerErrorCount > 5) {
+        return;
+      }
+      clearTimeout(videoTimer);
+      nextTick(() => {
+        videoItem.value?.play().catch((err: any) => {
+          // console.log('🚀 ~ err:', err)
+          // videoTimer = setTimeout(() => {
+          //   if (err?.message?.includes('play()')) {
+          //     // emit('play');
+          //   }
+          //   handlePlayVideo();
+          // }, 1000);
+        });
+      });
+      videoTimerErrorCount++;
+    };
+
+    const __init = () => {
+      if (videoItem.value) {
+        videoItem.value.poster(props.item.coverImg); // 封面
+        videoItem.value.src(item.value.content + '?t=4'); // url 播放地址
+        videoItem.value.playbackRate(videoFroms.defaultSpeed);
+        // 初步加载时
+        videoItem.value.one('loadedmetadata', () => {
+          // console.log(' Loading metadata');
+          videoItem.value.playbackRate(videoFroms.defaultSpeed);
+
+          // 获取时长
+          videoFroms.duration = timeFormat(
+            Math.round(videoItem.value.duration())
+          );
+          videoFroms.durationNum = videoItem.value.duration();
+
+          emit('canplay');
+          emit('loadedmetadata', videoItem.value);
+
+          if (item.value.autoPlay && videoItem.value) {
+            // videoItem.value?.play()
+            nextTick(() => {
+              videoTimerErrorCount = 0;
+              videoItem.value.currentTime(0);
+              nextTick(handlePlayVideo);
+            });
+          }
+        });
+
+        // 视频开始播放
+        videoItem.value.on('play', () => {
+          emit('close');
+          emit('canplay');
+        });
+
+        // 视频播放时加载
+        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');
+        });
+
+        //
+        videoItem.value.on('pause', () => {
+          videoFroms.paused = true;
+          emit('pause');
+        });
+
+        videoItem.value.on('playing', () => {
+          videoFroms.paused = false;
+        });
+
+        videoItem.value.on('canplay', (e: any) => {
+          // 获取时长
+          videoFroms.duration = timeFormat(
+            Math.round(videoItem.value.duration())
+          );
+          videoFroms.durationNum = videoItem.value.duration();
+          emit('canplay');
+        });
+
+        // 视频播放异常
+        videoItem.value.on('error', (e: any) => {
+          emit('error');
+          console.log(e, 'error');
+        });
+      }
+    };
+
+    const calculateSpeed = (element: any) => {
+      let previousBytesLoaded = 0;
+      let timer: any = null;
+      let previousTime = Date.now();
+
+      function resetDownloadSpeed() {
+        timer = setTimeout(() => {
+          // displayElement.textContent = `视屏下载速度: 0 KB/s`;
+          videoFroms.speedInKbps = `0 KB/s`;
+        }, 1000);
+      }
+
+      element.addEventListener('progress', () => {
+        const currentTime = Date.now();
+        const buffered = element.buffered;
+        let currentBytesLoaded = 0;
+
+        if (buffered.length > 0) {
+          for (let i = 0; i < buffered.length; i++) {
+            currentBytesLoaded += buffered.end(i) - buffered.start(i);
+          }
+          currentBytesLoaded *= element.duration * element.seekable.end(0); // 更精确地近似字节加载量
+        }
+
+        console.log('progress', currentBytesLoaded > previousBytesLoaded);
+        if (currentBytesLoaded > previousBytesLoaded) {
+          const timeDiff = (currentTime - previousTime) / 1000; // 时间差转换为秒
+          const bytesDiff = currentBytesLoaded - previousBytesLoaded; // 字节差值
+          const speed = bytesDiff / timeDiff; // 字节每秒
+
+          console.log(timeDiff, bytesDiff, speed);
+
+          const kbps = speed / 1024;
+          const speedInKbps = kbps.toFixed(2); // 转换为千字节每秒并保留两位小数
+          if (kbps > 1024) {
+            videoFroms.speedInKbps = `${Number((kbps / 1024).toFixed(2))} M/s`;
+          } else {
+            videoFroms.speedInKbps = `${Number(speedInKbps)} KB/s`;
+          }
+
+          previousBytesLoaded = currentBytesLoaded;
+          previousTime = currentTime;
+
+          // 如果1秒钟没有返回就重置数据
+          clearTimeout(timer);
+          resetDownloadSpeed();
+        }
+      });
+    };
+
+    onMounted(() => {
+      videoItem.value = TCPlayer(videoID.value, {
+        appID: '',
+        controls: false
+      }); // player-container-id 为播放器容器 ID,必须与 html 中一致
+
+      __init();
+
+      nextTick(() => {
+        calculateSpeed(videoRef.value);
+      });
+    });
+    const stop = () => {
+      videoItem.value.currentTime(0);
+      videoItem.value.pause();
+    };
+
+    const pause = () => {
+      videoItem.value.pause();
+    };
+
+    onUnmounted(() => {
+      if (videoItem.value) {
+        videoItem.value.pause();
+        videoItem.value.src('');
+        videoItem.value.dispose();
+      }
+    });
+
+    watch(
+      () => props.item,
+      () => {
+        // console.log(item.value, 'value----');
+        videoItem.value.pause();
+        videoItem.value.currentTime(0);
+        if (item.value?.id) {
+          // videoItem.value.poster(props.item.coverImg); // 封面
+          // videoItem.value.src(item.value.content); // url 播放地址
+          __init();
+
+          videoFroms.paused = true;
+        }
+      }
+    );
+    watch(
+      () => props.showModel,
+      () => {
+        // console.log(props.showModel, 'props.showModel')
+        videoFroms.showAction = props.showModel;
+        videoFroms.speedControl = false;
+      }
+    );
+    expose({
+      onPlay,
+      stop,
+      pause,
+      // changePlayBtn,
+      toggleHideControl
+    });
+    return () => (
+      <div class={styles.videoWrap}>
+        <video
+          style={{ width: '100%', height: '100%' }}
+          ref={videoRef}
+          id={videoID.value}
+          preload="auto"
+          playsinline
+          webkit-playsinline
+          x5-video-player-type="h5"></video>
+        <div class={styles.videoPop}></div>
+
+        <div
+          class={[
+            styles.controls,
+            videoFroms.showAction ? '' : styles.sectionAnimate
+          ]}
+          onClick={(e: MouseEvent) => {
+            e.stopPropagation();
+            if (videoItem.value.paused()) return;
+            emit('close');
+            emit('reset');
+          }}>
+          <div class={styles.slider}>
+            <NSlider
+              value={videoFroms.currentTimeNum}
+              step={0.01}
+              max={videoFroms.durationNum}
+              tooltip={false}
+              onUpdate:value={(val: number) => {
+                videoFroms.speedControl = false;
+                videoItem.value.currentTime(val);
+                videoFroms.currentTimeNum = val;
+                videoFroms.currentTime = timeFormat(Math.round(val || 0));
+              }}
+            />
+          </div>
+          <div class={styles.tools}>
+            {props.imagePos === 'right' ? (
+              <>
+                <div class={styles.actions}>
+                  <div class={styles.actionWrap}>
+                    <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>
+                <div class={styles.actions}>
+                  <div class={styles.actionWrap}>
+                    <div
+                      class={styles.actionBtnSpeed}
+                      onClick={e => {
+                        e.stopPropagation();
+                        videoFroms.speedControl = !videoFroms.speedControl;
+                      }}>
+                      <img src={iconSpeed} />
+                      <div
+                        style={{
+                          display: videoFroms.speedControl ? 'block' : 'none'
+                        }}>
+                        <div
+                          class={styles.sliderPopup}
+                          onClick={(e: Event) => {
+                            e.stopPropagation();
+                          }}>
+                          <i
+                            class={styles.iconAdd}
+                            onClick={() => {
+                              if (videoFroms.defaultSpeed >= 1.5) {
+                                return;
+                              }
+
+                              if (videoItem.value) {
+                                videoFroms.defaultSpeed =
+                                  (videoFroms.defaultSpeed * 10 + 1) / 10;
+                                videoItem.value.playbackRate(
+                                  videoFroms.defaultSpeed
+                                );
+                              }
+                            }}></i>
+                          <NSlider
+                            value={videoFroms.defaultSpeed}
+                            step={0.1}
+                            max={1.5}
+                            min={0.5}
+                            vertical
+                            tooltip={false}
+                            onUpdate:value={(val: number) => {
+                              videoFroms.defaultSpeed = val;
+
+                              if (videoItem.value) {
+                                videoItem.value.playbackRate(
+                                  videoFroms.defaultSpeed
+                                );
+                              }
+                            }}>
+                            {{
+                              thumb: () => (
+                                <div class={styles.sliderPoint}>
+                                  {videoFroms.defaultSpeed}
+                                  <span>x</span>
+                                </div>
+                              )
+                            }}
+                          </NSlider>
+                          <i
+                            class={[styles.iconCut]}
+                            onClick={() => {
+                              if (videoFroms.defaultSpeed <= 0.5) {
+                                return;
+                              }
+                              if (videoItem.value) {
+                                videoFroms.defaultSpeed =
+                                  (videoFroms.defaultSpeed * 10 - 1) / 10;
+                                videoItem.value.playbackRate(
+                                  videoFroms.defaultSpeed
+                                );
+                              }
+                            }}></i>
+                        </div>
+                      </div>
+                    </div>
+                    <button class={styles.iconReplay} onClick={onReplay}>
+                      <img src={iconLoop} />
+                    </button>
+                    <div
+                      class={styles.actionBtn}
+                      onClick={() => {
+                        videoFroms.speedControl = false;
+                        onToggleVideo();
+                      }}>
+                      {videoFroms.paused ? (
+                        <img class={styles.playIcon} src={iconplay} />
+                      ) : (
+                        <img class={styles.playIcon} src={iconpause} />
+                      )}
+                    </div>
+
+                    <div class={styles.downloadSpeed}>
+                      {videoFroms.speedInKbps}
+                    </div>
+                  </div>
+                </div>
+              </>
+            ) : (
+              <>
+                <div class={styles.actions}>
+                  <div class={styles.actionWrap}>
+                    <div
+                      class={styles.actionBtn}
+                      onClick={() => {
+                        videoFroms.speedControl = false;
+                        onToggleVideo();
+                      }}>
+                      {videoFroms.paused ? (
+                        <img class={styles.playIcon} src={iconplay} />
+                      ) : (
+                        <img class={styles.playIcon} src={iconpause} />
+                      )}
+                    </div>
+
+                    <button class={styles.iconReplay} onClick={onReplay}>
+                      <img src={iconLoop} />
+                    </button>
+
+                    <div
+                      class={styles.actionBtnSpeed}
+                      onClick={e => {
+                        e.stopPropagation();
+                        videoFroms.speedControl = !videoFroms.speedControl;
+                      }}>
+                      <img src={iconSpeed} />
+
+                      <div
+                        style={{
+                          display: videoFroms.speedControl ? 'block' : 'none'
+                        }}>
+                        <div
+                          class={styles.sliderPopup}
+                          onClick={(e: Event) => {
+                            e.stopPropagation();
+                          }}>
+                          <i
+                            class={styles.iconAdd}
+                            onClick={() => {
+                              if (videoFroms.defaultSpeed >= 1.5) {
+                                return;
+                              }
+
+                              if (videoItem.value) {
+                                videoFroms.defaultSpeed =
+                                  (videoFroms.defaultSpeed * 10 + 1) / 10;
+                                videoItem.value.playbackRate(
+                                  videoFroms.defaultSpeed
+                                );
+                              }
+                            }}></i>
+                          <NSlider
+                            value={videoFroms.defaultSpeed}
+                            step={0.1}
+                            max={1.5}
+                            min={0.5}
+                            vertical
+                            tooltip={false}
+                            onUpdate:value={(val: number) => {
+                              videoFroms.defaultSpeed = val;
+
+                              if (videoItem.value) {
+                                videoItem.value.playbackRate(
+                                  videoFroms.defaultSpeed
+                                );
+                              }
+                            }}>
+                            {{
+                              thumb: () => (
+                                <div class={styles.sliderPoint}>
+                                  {videoFroms.defaultSpeed}
+                                  <span>x</span>
+                                </div>
+                              )
+                            }}
+                          </NSlider>
+                          <i
+                            class={[styles.iconCut]}
+                            onClick={() => {
+                              if (videoFroms.defaultSpeed <= 0.5) {
+                                return;
+                              }
+                              if (videoItem.value) {
+                                videoFroms.defaultSpeed =
+                                  (videoFroms.defaultSpeed * 10 - 1) / 10;
+                                videoItem.value.playbackRate(
+                                  videoFroms.defaultSpeed
+                                );
+                              }
+                            }}></i>
+                        </div>
+                      </div>
+                    </div>
+
+                    <div class={styles.downloadSpeed}>
+                      {videoFroms.speedInKbps}
+                    </div>
+                  </div>
+                </div>
+                <div class={styles.actions}>
+                  <div class={styles.actionWrap}>
+                    <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>
+              </>
+            )}
+          </div>
+        </div>
+      </div>
+    );
+  }
+});

+ 363 - 353
src/views/attend-class/component/video.module.less

@@ -1,353 +1,363 @@
-.videoWrap {
-  width: 100%;
-  height: 100%;
-  position: relative;
-
-  .videoPop {
-    position: absolute;
-    inset: 0;
-  }
-
-  :global {
-
-    .vjs-poster,
-    .vjs-text-track-display {
-      cursor: default !important;
-    }
-  }
-
-  .sectionAnimate {
-    opacity: 0;
-    pointer-events: none;
-    transform: translateY(126px);
-    transition: all .2s;
-  }
-
-  .controls {
-    position: absolute;
-    bottom: 0;
-    left: 0;
-    right: 0;
-    width: 100%;
-    background: url('../image/../image/bg.png') no-repeat;
-    background-size: 100% 100%;
-    // backdrop-filter: blur(26px);
-    height: 120px;
-    padding: 0 40px !important;
-    transition: all 0.301s;
-    display: flex;
-    justify-content: center;
-    flex-direction: column;
-
-    .time {
-      display: flex;
-      justify-content: space-between;
-      color: #fff;
-      // font-size: 10px;
-      padding: 4px 20px 4px;
-      font-size: 24px;
-      font-weight: 600;
-      line-height: 33px;
-
-      &>div {
-        font-size: 20px !important;
-        color: rgba(255,255,255,0.8);
-      }
-
-      .line {
-        font-size: 20px;
-      }
-
-      :global {
-        .plyr__time+.plyr__time:before {
-          content: '';
-          margin-right: 0;
-        }
-      }
-    }
-  }
-  .tools{
-    display: flex;
-    justify-content: space-between;
-    padding: 0 10px;
-    margin-top: 10px;
-  }
-  .actions {
-    display: flex;
-    justify-content: space-between;
-    // width: 100%;
-    height: 100%;
-    color: #fff;
-    font-size: 12px;
-    align-items: center;
-
-    .actionWrap {
-      display: flex;
-      align-items: center;
-    }
-
-    .actionBtn {
-      width: 40px;
-      height: 40px;
-      background: transparent;
-      cursor: pointer;
-
-      &>img {
-        width: 100%;
-        height: 100%;
-      }
-    }
-
-    .actionBtnSpeed {
-      position: relative;
-      width: 40px;
-      height: 40px;
-      background-color: transparent;
-      cursor: pointer;
-
-      &>img {
-        width: 100%;
-        height: 100%;
-      }
-    }
-
-
-    .iconReplay {
-      width: 40px;
-      height: 40px;
-      background-color: transparent;
-      cursor: pointer;
-      margin: 0 22px;
-
-      &>img {
-        width: 100%;
-        height: 100%;
-      }
-    }
-  }
-
-  .slider {
-    width: 100%;
-    padding-top: 6px;
-
-    :global {
-
-      .n-slider .n-slider-rail .n-slider-rail__fill,
-      .n-slider .n-slider-handles .n-slider-handle-wrapper {
-        transition: all .2s;
-      }
-    }
-  }
-
-
-}
-
-
-.sliderPopup {
-  position: absolute;
-  z-index: 9999;
-  left: -10px;
-  bottom: 55px;
-  display: flex;
-  align-items: center;
-  flex-direction: column;
-  height: 252px;
-  width: 59px;
-  padding: 12Px 0 15Px;
-  background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAI4AAAJcCAMAAAAYSmw3AAAAaVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnbPKNAAAAI3RSTlOzAAUJiqaplqF6V0c4nSevpJN+GAZtX1EQGpqDPTQVc2QhqyTybJ0AAAKuSURBVHja7NRJVsJQAAXRRxMg9BwgGWX/2xT0KCKII5M/uHcFNaqMntm25+mm2uVf7KrN9NxuR8885ozX9Sw9mNXr8Z85p2WV3lTL0+uc1SK9Wqxe5HTz9G7e/ZbTZBDN05xJnYHUk8ec4yGDORx/5hz3GdD+eJ8zOWRQh8ldTp2B1d9zmgyuueV0KUD3lTNPAeafOasUYfWRc1qkCIvTe84yhVhec8ZVClGNLznrFGM9yvAHvKkvObMUYzbKNgXZpk1B2pxTkHOmKcg0mxRkk2ImeFVll4IUFQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAG3twIAAAAAAA5P/aCKqqqqqqqqqqqqqqqtJ+nSQ3CgVRFH2XjwCjHnWoCTXe/yJrUlEljCz7R9riD/Ks4E7yRaRzzjnnnHPOOeecc845zZWQuUZKyEhTJWSqXAnJtVFCNtopITvNlJCZKJSMAvGmZLwhJkrGBJElM4SjDEGtRNQgCJWSUAUQMFYSxoAASiWgBBCQxnFN/uWw1eC2/M+h0cAa7nNCqUGVoZPDaqEBLVZ0c9iXGky552MOodFAmkA/B64axBUe5jA56OUOE+6IjvFCL7UY0yG6Ql3oZYo68DwH1qfmXS/w3pzWfCQeyGb1Ja8K/ZKiyi/1LOMB8UTWFzcH4+wBnhCRsmNEDbFErPW3e3ZEE9FCrm+p6THkWHu29BlyjD1XIthyCEt9YUMEaw7tFz0XIthzaMvnz3YUew7tQZ86ZsSx53Ce6hP5mqcsOfE9eSCePYdVpQeWhhqEwX6hnrLFQFjcej2HFgthchupY3rGRNjcCt2pVtgIo1lxV7PHSFjtl/orP2Ml7E7HuTRvTtiJH9G2/AiRlD84lRqqt/0KYQAAAABJRU5ErkJggg==') no-repeat top center;
-  background-size: contain;
-
-  .iconAdd,
-  .iconCut {
-    display: inline-block;
-    width: 24Px;
-    height: 24Px;
-    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAABMlBMVEUAAAAkqP8ckv8zu/8op/81v/8ajv8elP82v/8rrv8elP4npv8yuf8elP8elf81v/8sr/8elf8vuv8dlv8srv8jn/8zu/8wtf82wP8oqP8npf8Zjf8mpP8Zjf8hmv8bj/40vP8wtP81wP8Zjf41vv8sr/8jnf8aj/0zuf8dlf81vv8rq/4kof4vsf8imP8aj/////8usv8qrP4jn/4inP4dlP4yuf8el/4srv8pqv4gmf4bkf40vf8ssP4wtP8lov4koP4xt/8npv4mpP4zu/8hmv4ajv4ckv4wtv4op/58x/5+y/6Cxv5/zf57xP6Dx/6BxP7u+P/t9/6a1f6Bz/56w/5iuP5zwP5Bpv4yoP6x3/6w3f6d2/6Wzv5tyf5pwv5nvv5svP5Rsv5Pr/5Mq/43p/49/opPAAAAMHRSTlMACoRW+Pf38dTUurGDWEhHR0cjI/b28fHr6+vr19fQ0Lq6sbGoqKiolZWGhoZSUlKk1yinAAAB+UlEQVQ4y43S6VbaQACG4S9E9h3c933fatISI4sWYxUjAVQQ99r2/m+hGQfGMJNEHw4M+XhPTn4AjpQNLM1MhUJTM0uBrAQ/qbXQmUNoLQUve3Nngrk9uMnHjlzF8hAkw0cewklwAr98BDBg47uvjYH72oNx1/WuHfdOFgqFu3qj8bfgiT13Pmy3DbNhtr3jcB5UTNe7DdM0f6s69dRqPemcGG13dV3t2G2737b3bW2+3gUxaxh/LHJfo+eWxLcGZxa2lKqqHcsyuyph2G8aq71rdpL/yar9tW5ZbypTJ3Fd5a0C0oiiKG9W51hhaKzwRiRkFeJfVRFiQRbbx4IaiWvivo14RUBjcY9julKlKtXXGvVA4ofexWuV/T6NyWrf9b6raxZMYuhnX8s9brFgyBE33eOmI5646PN6DBZMIHpIXJCPl0vqnjT3vYuXj9+jiB8KLkl8Je5xbB0Irt5jcd9C5utxBtKwph1oBDtprPH7sASsaFpJs5XYSeMSv68AyJR4NyS+EeYMbNFvHBrzaxTEDj8/k/iZX3fwboGby4/N5mOZGxdA5cbKnxrLoUcunxMn596nDCZx8okEHNZ/+FrHgIRfmwBHHj31MCpDkJsvntqvov0eOOdzcCNHioKIDC/p5aCzDC6n4UdKby5GxoPB8cjiZlrCoP+meld2tFTGwgAAAABJRU5ErkJggg==') no-repeat center;
-    background-size: contain;
-    flex-shrink: 0;
-    cursor: pointer;
-
-    &.disabled {
-      opacity: 0.7;
-    }
-  }
-
-  .iconCut {
-    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAABGlBMVEUAAAAkqP8zu/8op/8ajv8aj/8yuP8elP4npv4dlP8elP8yuP8elP81v/8sr/8jnf8Zjf81v/81v/8jn/8zu/8wtf8gl/82wP8oqP8npf83wP8qqv8mpP8Zjf81vf8tsv8hmv8bj/41wP8qqv8lof8Zjf41vv8sr/8jnf8aj/0zuf8dlf8bjf00vP8xt/8zvf8stv8dmf8dkv8srv8trf8qq/7///8tsP4jn/4us/8srv8gmf4lov4yuf8dlf4wt/8inf4hm/4ck/4bkf4wtP8koP4mpf4fl/4op/40vP8ajv4pqf4zuv98x/5+y/6Bxf40vv80u/6Dx/6a1f5owP5PsP4/p/5zwP4oo/4yoP7u+P7u9/5svP5it/4TfkURAAAANXRSTlMACoT49+66uoaDWFJSR0dHR/j29vHx8evr69fX19fQ0NDQsbGxsaioqKiVlYZYWCMjIyP29l0yDtwAAAGnSURBVDjLjc6HVsIwGAXg27I37r33nkilWloXCGK17OH7v4YpSKSQtPnS5v7n5J6cYIyUPt1bC/j9gbW907QEN+kjf2mE/ygNnvhWacJWHCzXoWem0DUmRAPPHIEoxkSeXETgcPJEaGSx88Rxr6ZpaqOtcUVARTWt0DBNs06yoBGTSd99NV8gXeKnwDV/hYGQqrZNoqerfCH0xVXVaNr3GqqbOGybhlG3LKunG642QaQMXW9altkmaRhk42UKwKGu6xXLquteDgFpRlGUutV8VbzMSEj1h0ZL8ZZC+FVYGLsPwnaxSudWrcJQa9HCKpbehqp3TFVaWIKPzl12uUsLPviKQx12uUMLPiwWi+XBzHsGOR9YxEqZqn0y1MrUCnYy5Uyfd+4gnBEWRlK8nIR0kxN0KwEHuVzWnrNeeQAgmRWUBLEh1t2ALXYvJIa+oEg3iIHLuRdPc5f4c/6Sz+fd/3NQct6DjBHHj66O4SC7dWWMOZv94Jg9w4SL4Ps3WeRzZvACLLH1d8Iu/Od6DDyJ/emvEdP7CbiREvL28sLU1MLytpyQ4PQLtc9vYI2HRk0AAAAASUVORK5CYII=') no-repeat center;
-    background-size: contain;
-  }
-
-  .sliderPoint {
-    background: #FFFFFF;
-    box-shadow: 0px 2px 4px 0px rgba(102, 102, 102, 0.77);
-    border-radius: 14px;
-    font-size: 14Px;
-    font-weight: 500;
-    height: 22Px;
-    color: #198CFE;
-    min-width: 40px;
-    text-align: center;
-    vertical-align: text-bottom;
-
-    span {
-      font-size: 12Px;
-    }
-  }
-
-  :global {
-    .n-slider {
-      margin: 7px 0;
-      padding: 0;
-    }
-  }
-}
-
-// .videoWrap {
-//   width: 100%;
-//   height: 100%;
-//   --plyr-color-main: #198CFE;
-//   --plyr-range-track-height: 4px;
-//   --plyr-tooltip-radius: 3px;
-//   --plyr-range-thumb-height: 18px;
-
-
-//   :global {
-//     .plyr--video {
-//       width: 100%;
-//       height: 100%;
-//     }
-
-//     .plyr__time {
-//       display: block !important;
-//     }
-
-//     .plyr__video-wrapper {
-//       pointer-events: none;
-//     }
-//   }
-// }
-
-// :global(.bottomFixed).controls {
-//   width: 100%;
-//   background: rgba(0, 0, 0, 0.6);
-//   backdrop-filter: blur(26px);
-//   height: 150px;
-//   padding: 0 250px 0 40px !important;
-//   transition: all 0.5s;
-
-//   .time {
-//     display: flex;
-//     justify-content: space-between;
-//     color: #fff;
-//     // font-size: 10px;
-//     padding: 4px 0 4px 20px;
-//     font-size: 24px;
-//     font-weight: 600;
-//     line-height: 33px;
-
-//     &>div {
-//       font-size: 24px !important;
-//     }
-
-//     .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;
-//     padding-right: 20px;
-//     align-items: center;
-
-//     .actionWrap {
-//       display: flex;
-//     }
-
-//     .actionBtn {
-//       display: flex;
-//       // width: 43px !important;
-//       // height: 43px !important;
-//       width: 82px;
-//       height: 82px;
-//       padding: 4px 0;
-//       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: 43px;
-//       height: 42px;
-//       padding: 0;
-//       cursor: pointer;
-
-//       :global {
-//         .loop {
-//           display: block;
-//         }
-
-//         .loopActive {
-//           display: none;
-//         }
-//       }
-//     }
-//   }
-// }
+.videoWrap {
+  width: 100%;
+  height: 100%;
+  position: relative;
+
+  .videoPop {
+    position: absolute;
+    inset: 0;
+  }
+
+  :global {
+
+    .vjs-poster,
+    .vjs-text-track-display {
+      cursor: default !important;
+    }
+  }
+
+  .sectionAnimate {
+    opacity: 0;
+    pointer-events: none;
+    transform: translateY(126px);
+    transition: all .2s;
+  }
+
+  .controls {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    background: url('../image/../image/bg.png') no-repeat;
+    background-size: 100% 100%;
+    // backdrop-filter: blur(26px);
+    height: 120px;
+    padding: 0 40px !important;
+    transition: all 0.301s;
+    display: flex;
+    justify-content: center;
+    flex-direction: column;
+
+    .time {
+      display: flex;
+      justify-content: space-between;
+      color: #fff;
+      // font-size: 10px;
+      padding: 4px 20px 4px;
+      font-size: 24px;
+      font-weight: 600;
+      line-height: 33px;
+
+      &>div {
+        font-size: 20px !important;
+        color: rgba(255, 255, 255, 0.8);
+      }
+
+      .line {
+        font-size: 20px;
+      }
+
+      :global {
+        .plyr__time+.plyr__time:before {
+          content: '';
+          margin-right: 0;
+        }
+      }
+    }
+  }
+
+  .tools {
+    display: flex;
+    justify-content: space-between;
+    padding: 0 10px;
+    margin-top: 10px;
+  }
+
+  .actions {
+    display: flex;
+    justify-content: space-between;
+    // width: 100%;
+    height: 100%;
+    color: #fff;
+    font-size: 12px;
+    align-items: center;
+
+    .actionWrap {
+      display: flex;
+      align-items: center;
+    }
+
+    .downloadSpeed {
+      font-weight: 600;
+      font-size: max(18px, 14Px);
+      color: #FFFFFF;
+      line-height: 25px;
+      padding-left: 22px;
+    }
+
+    .actionBtn {
+      width: 40px;
+      height: 40px;
+      background: transparent;
+      cursor: pointer;
+
+      &>img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    .actionBtnSpeed {
+      position: relative;
+      width: 40px;
+      height: 40px;
+      background-color: transparent;
+      cursor: pointer;
+
+      &>img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+
+    .iconReplay {
+      width: 40px;
+      height: 40px;
+      background-color: transparent;
+      cursor: pointer;
+      margin: 0 22px;
+
+      &>img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+
+  .slider {
+    width: 100%;
+    padding-top: 6px;
+
+    :global {
+
+      .n-slider .n-slider-rail .n-slider-rail__fill,
+      .n-slider .n-slider-handles .n-slider-handle-wrapper {
+        transition: all .2s;
+      }
+    }
+  }
+
+
+}
+
+
+.sliderPopup {
+  position: absolute;
+  z-index: 9999;
+  left: -10px;
+  bottom: 55px;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  height: 252px;
+  width: 59px;
+  padding: 12Px 0 15Px;
+  background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAI4AAAJcCAMAAAAYSmw3AAAAaVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnbPKNAAAAI3RSTlOzAAUJiqaplqF6V0c4nSevpJN+GAZtX1EQGpqDPTQVc2QhqyTybJ0AAAKuSURBVHja7NRJVsJQAAXRRxMg9BwgGWX/2xT0KCKII5M/uHcFNaqMntm25+mm2uVf7KrN9NxuR8885ozX9Sw9mNXr8Z85p2WV3lTL0+uc1SK9Wqxe5HTz9G7e/ZbTZBDN05xJnYHUk8ec4yGDORx/5hz3GdD+eJ8zOWRQh8ldTp2B1d9zmgyuueV0KUD3lTNPAeafOasUYfWRc1qkCIvTe84yhVhec8ZVClGNLznrFGM9yvAHvKkvObMUYzbKNgXZpk1B2pxTkHOmKcg0mxRkk2ImeFVll4IUFQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAG3twIAAAAAAA5P/aCKqqqqqqqqqqqqqqqtJ+nSQ3CgVRFH2XjwCjHnWoCTXe/yJrUlEljCz7R9riD/Ks4E7yRaRzzjnnnHPOOeecc845zZWQuUZKyEhTJWSqXAnJtVFCNtopITvNlJCZKJSMAvGmZLwhJkrGBJElM4SjDEGtRNQgCJWSUAUQMFYSxoAASiWgBBCQxnFN/uWw1eC2/M+h0cAa7nNCqUGVoZPDaqEBLVZ0c9iXGky552MOodFAmkA/B64axBUe5jA56OUOE+6IjvFCL7UY0yG6Ql3oZYo68DwH1qfmXS/w3pzWfCQeyGb1Ja8K/ZKiyi/1LOMB8UTWFzcH4+wBnhCRsmNEDbFErPW3e3ZEE9FCrm+p6THkWHu29BlyjD1XIthyCEt9YUMEaw7tFz0XIthzaMvnz3YUew7tQZ86ZsSx53Ce6hP5mqcsOfE9eSCePYdVpQeWhhqEwX6hnrLFQFjcej2HFgthchupY3rGRNjcCt2pVtgIo1lxV7PHSFjtl/orP2Ml7E7HuTRvTtiJH9G2/AiRlD84lRqqt/0KYQAAAABJRU5ErkJggg==') no-repeat top center;
+  background-size: contain;
+
+  .iconAdd,
+  .iconCut {
+    display: inline-block;
+    width: 24Px;
+    height: 24Px;
+    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAABMlBMVEUAAAAkqP8ckv8zu/8op/81v/8ajv8elP82v/8rrv8elP4npv8yuf8elP8elf81v/8sr/8elf8vuv8dlv8srv8jn/8zu/8wtf82wP8oqP8npf8Zjf8mpP8Zjf8hmv8bj/40vP8wtP81wP8Zjf41vv8sr/8jnf8aj/0zuf8dlf81vv8rq/4kof4vsf8imP8aj/////8usv8qrP4jn/4inP4dlP4yuf8el/4srv8pqv4gmf4bkf40vf8ssP4wtP8lov4koP4xt/8npv4mpP4zu/8hmv4ajv4ckv4wtv4op/58x/5+y/6Cxv5/zf57xP6Dx/6BxP7u+P/t9/6a1f6Bz/56w/5iuP5zwP5Bpv4yoP6x3/6w3f6d2/6Wzv5tyf5pwv5nvv5svP5Rsv5Pr/5Mq/43p/49/opPAAAAMHRSTlMACoRW+Pf38dTUurGDWEhHR0cjI/b28fHr6+vr19fQ0Lq6sbGoqKiolZWGhoZSUlKk1yinAAAB+UlEQVQ4y43S6VbaQACG4S9E9h3c933fatISI4sWYxUjAVQQ99r2/m+hGQfGMJNEHw4M+XhPTn4AjpQNLM1MhUJTM0uBrAQ/qbXQmUNoLQUve3Nngrk9uMnHjlzF8hAkw0cewklwAr98BDBg47uvjYH72oNx1/WuHfdOFgqFu3qj8bfgiT13Pmy3DbNhtr3jcB5UTNe7DdM0f6s69dRqPemcGG13dV3t2G2737b3bW2+3gUxaxh/LHJfo+eWxLcGZxa2lKqqHcsyuyph2G8aq71rdpL/yar9tW5ZbypTJ3Fd5a0C0oiiKG9W51hhaKzwRiRkFeJfVRFiQRbbx4IaiWvivo14RUBjcY9julKlKtXXGvVA4ofexWuV/T6NyWrf9b6raxZMYuhnX8s9brFgyBE33eOmI5646PN6DBZMIHpIXJCPl0vqnjT3vYuXj9+jiB8KLkl8Je5xbB0Irt5jcd9C5utxBtKwph1oBDtprPH7sASsaFpJs5XYSeMSv68AyJR4NyS+EeYMbNFvHBrzaxTEDj8/k/iZX3fwboGby4/N5mOZGxdA5cbKnxrLoUcunxMn596nDCZx8okEHNZ/+FrHgIRfmwBHHj31MCpDkJsvntqvov0eOOdzcCNHioKIDC/p5aCzDC6n4UdKby5GxoPB8cjiZlrCoP+meld2tFTGwgAAAABJRU5ErkJggg==') no-repeat center;
+    background-size: contain;
+    flex-shrink: 0;
+    cursor: pointer;
+
+    &.disabled {
+      opacity: 0.7;
+    }
+  }
+
+  .iconCut {
+    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAABGlBMVEUAAAAkqP8zu/8op/8ajv8aj/8yuP8elP4npv4dlP8elP8yuP8elP81v/8sr/8jnf8Zjf81v/81v/8jn/8zu/8wtf8gl/82wP8oqP8npf83wP8qqv8mpP8Zjf81vf8tsv8hmv8bj/41wP8qqv8lof8Zjf41vv8sr/8jnf8aj/0zuf8dlf8bjf00vP8xt/8zvf8stv8dmf8dkv8srv8trf8qq/7///8tsP4jn/4us/8srv8gmf4lov4yuf8dlf4wt/8inf4hm/4ck/4bkf4wtP8koP4mpf4fl/4op/40vP8ajv4pqf4zuv98x/5+y/6Bxf40vv80u/6Dx/6a1f5owP5PsP4/p/5zwP4oo/4yoP7u+P7u9/5svP5it/4TfkURAAAANXRSTlMACoT49+66uoaDWFJSR0dHR/j29vHx8evr69fX19fQ0NDQsbGxsaioqKiVlYZYWCMjIyP29l0yDtwAAAGnSURBVDjLjc6HVsIwGAXg27I37r33nkilWloXCGK17OH7v4YpSKSQtPnS5v7n5J6cYIyUPt1bC/j9gbW907QEN+kjf2mE/ygNnvhWacJWHCzXoWem0DUmRAPPHIEoxkSeXETgcPJEaGSx88Rxr6ZpaqOtcUVARTWt0DBNs06yoBGTSd99NV8gXeKnwDV/hYGQqrZNoqerfCH0xVXVaNr3GqqbOGybhlG3LKunG642QaQMXW9altkmaRhk42UKwKGu6xXLquteDgFpRlGUutV8VbzMSEj1h0ZL8ZZC+FVYGLsPwnaxSudWrcJQa9HCKpbehqp3TFVaWIKPzl12uUsLPviKQx12uUMLPiwWi+XBzHsGOR9YxEqZqn0y1MrUCnYy5Uyfd+4gnBEWRlK8nIR0kxN0KwEHuVzWnrNeeQAgmRWUBLEh1t2ALXYvJIa+oEg3iIHLuRdPc5f4c/6Sz+fd/3NQct6DjBHHj66O4SC7dWWMOZv94Jg9w4SL4Ps3WeRzZvACLLH1d8Iu/Od6DDyJ/emvEdP7CbiREvL28sLU1MLytpyQ4PQLtc9vYI2HRk0AAAAASUVORK5CYII=') no-repeat center;
+    background-size: contain;
+  }
+
+  .sliderPoint {
+    background: #FFFFFF;
+    box-shadow: 0px 2px 4px 0px rgba(102, 102, 102, 0.77);
+    border-radius: 14px;
+    font-size: 14Px;
+    font-weight: 500;
+    height: 22Px;
+    color: #198CFE;
+    min-width: 40px;
+    text-align: center;
+    vertical-align: text-bottom;
+
+    span {
+      font-size: 12Px;
+    }
+  }
+
+  :global {
+    .n-slider {
+      margin: 7px 0;
+      padding: 0;
+    }
+  }
+}
+
+// .videoWrap {
+//   width: 100%;
+//   height: 100%;
+//   --plyr-color-main: #198CFE;
+//   --plyr-range-track-height: 4px;
+//   --plyr-tooltip-radius: 3px;
+//   --plyr-range-thumb-height: 18px;
+
+
+//   :global {
+//     .plyr--video {
+//       width: 100%;
+//       height: 100%;
+//     }
+
+//     .plyr__time {
+//       display: block !important;
+//     }
+
+//     .plyr__video-wrapper {
+//       pointer-events: none;
+//     }
+//   }
+// }
+
+// :global(.bottomFixed).controls {
+//   width: 100%;
+//   background: rgba(0, 0, 0, 0.6);
+//   backdrop-filter: blur(26px);
+//   height: 150px;
+//   padding: 0 250px 0 40px !important;
+//   transition: all 0.5s;
+
+//   .time {
+//     display: flex;
+//     justify-content: space-between;
+//     color: #fff;
+//     // font-size: 10px;
+//     padding: 4px 0 4px 20px;
+//     font-size: 24px;
+//     font-weight: 600;
+//     line-height: 33px;
+
+//     &>div {
+//       font-size: 24px !important;
+//     }
+
+//     .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;
+//     padding-right: 20px;
+//     align-items: center;
+
+//     .actionWrap {
+//       display: flex;
+//     }
+
+//     .actionBtn {
+//       display: flex;
+//       // width: 43px !important;
+//       // height: 43px !important;
+//       width: 82px;
+//       height: 82px;
+//       padding: 4px 0;
+//       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: 43px;
+//       height: 42px;
+//       padding: 0;
+//       cursor: pointer;
+
+//       :global {
+//         .loop {
+//           display: block;
+//         }
+
+//         .loopActive {
+//           display: none;
+//         }
+//       }
+//     }
+//   }
+// }