Browse Source

添加播放器

lex 1 year ago
parent
commit
95c6e46b9d

+ 2 - 0
src/shims-vue.d.ts

@@ -8,3 +8,5 @@ declare module 'vudio.js';
 declare module 'numeral';
 
 declare module 'howler';
+
+declare module 'tcplayer.js';

+ 132 - 0
src/views/courseware-play/component/video-item copy/index.module.less

@@ -0,0 +1,132 @@
+.videoWrap {
+    position: relative;
+    width: 100%;
+    height: 100%;
+}
+.content{
+    height: 100%;
+}
+.contentWrap{
+    height: 100%;
+    video{
+        width: 100%;
+        height: 100%;
+    }
+}
+
+.controls {
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    height: 80px;
+    background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    transition: all 0.5s;
+
+    &.hide{
+        transform: translateY(100%);
+    }
+
+    .time {
+        display: flex;
+        justify-content: space-between;
+        width: 100%;
+        color: #fff;
+        font-size: 10px;
+        padding: 4px 20px;
+    }
+
+    .slider {
+        width: 100%;
+        padding: 0 20px;
+        :global {
+            .n-slider{
+                --n-handle-size: 13px !important;
+                --n-fill-color: var(--van-primary-color) !important;
+                --n-fill-color-hover: var(--van-primary-color) !important;
+            }
+            .van-loading {
+                width: 100%;
+                height: 100%;
+            }
+        }
+    }
+
+    .actions {
+        display: flex;
+        width: 100%;
+        color: #fff;
+        font-size: 12px;
+        padding: 0 20px;
+        align-items: center;
+
+        .actionWrap {
+            display: flex;
+        }
+
+        .actionBtn {
+            display: flex;
+            width: 38px;
+            height: 38px;
+            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{
+            :global{
+                .loop{
+                    display: block;
+                }
+                .loopActive{
+                    display: none;
+                }
+            }
+        }
+        .loopBtn.active{
+            :global{
+                .loop{
+                    display: none;
+                }
+                .loopActive{
+                    display: block;
+                }
+            }
+        }
+
+    }
+}

+ 163 - 0
src/views/courseware-play/component/video-item copy/index.tsx

@@ -0,0 +1,163 @@
+import { defineComponent, onMounted, reactive, toRefs, watch } from 'vue';
+import { ref } from 'vue';
+import styles from './index.module.less';
+
+import iconLoop from '../../image/icon-loop.svg';
+import iconLoopActive from '../../image/icon-loop-active.svg';
+import iconplay from '../../image/icon-play.svg';
+import iconpause from '../../image/icon-pause.svg';
+import { NSlider } from 'naive-ui';
+import { getSecondRPM } from '@/helpers/utils';
+
+export default defineComponent({
+  name: 'video-play',
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    },
+    pageVisibility: {
+      type: String,
+      default: ''
+    },
+    show: {
+      type: Boolean,
+      default: false
+    },
+    showModel: {
+      type: Boolean,
+      default: false
+    },
+    isEmtry: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
+  setup(props, { emit }) {
+    const { item, isEmtry } = toRefs(props);
+    const data = reactive({
+      timer: null as any,
+      currentTime: 0,
+      duration: 0,
+      loop: false,
+      playState: 'pause' as 'play' | 'pause',
+      vudio: null as any
+    });
+    const canvasRef: any = ref();
+    const elRef: any = ref();
+    const contetRef = ref();
+
+    watch(
+      () => props.show,
+      val => {
+        onToggleAudio('pause');
+      }
+    );
+    watch(
+      () => props.pageVisibility,
+      val => {
+        if (val === 'hidden' && props.show) {
+          onToggleAudio('pause');
+        }
+      }
+    );
+
+    let playTimer = null as any;
+    // 切换音频播放
+    const onToggleAudio = (state: 'play' | 'pause') => {
+      clearTimeout(playTimer);
+      if (state === 'play') {
+        playTimer = setTimeout(() => {
+          elRef.value.play();
+          data.playState = 'play';
+        }, 100);
+      } else {
+        elRef.value.pause();
+        data.playState = 'pause';
+      }
+    };
+
+    /** 加载成功 */
+    const onLoadedmetadata = () => {
+      data.duration = elRef.value.duration;
+      // // 加载成功后台, 如果是第一次加载, 且是show状态, 则播放
+      // if (props.show) {
+      //   onToggleAudio('play');
+      // }
+      emit('loadedmetadata');
+    };
+
+    /** 改变播放时间 */
+    const handleChangeTime = (val: number) => {
+      data.currentTime = val;
+      clearTimeout(data.timer);
+      data.timer = setTimeout(() => {
+        elRef.value.currentTime = val;
+        data.timer = null;
+      }, 300);
+    };
+
+    /** 播放结束 */
+    const onEnded = () => {
+      data.playState = 'pause';
+      // emit('ended');
+    };
+    onMounted(() => {
+      console.log('加载');
+    });
+    return () => (
+      <div class={styles.videoWrap}>
+        <div class={styles.content}>
+          <div ref={contetRef} class={styles.contentWrap}>
+            <video
+              poster={props.item.coverImg}
+              src={isEmtry.value ? '' : item.value.content}
+              ref={elRef}
+              loop={data.loop}
+              onLoadedmetadata={onLoadedmetadata}
+              onTimeupdate={() => {
+                if (data.timer) return;
+                data.currentTime = elRef.value.currentTime;
+              }}
+              onEnded={onEnded}
+              playsinline="false"></video>
+          </div>
+        </div>
+
+        <div class={[styles.controls, props.showModel ? '' : styles.hide]}>
+          <div class={styles.time}>
+            <div>{getSecondRPM(data.currentTime)}</div>
+            <div>{getSecondRPM(data.duration)}</div>
+          </div>
+          <div class={styles.slider}>
+            <NSlider
+              tooltip={false}
+              step={0.01}
+              class={styles.timeProgress}
+              value={data.currentTime}
+              max={data.duration}
+              onUpdate:value={val => handleChangeTime(val)}
+            />
+          </div>
+          <div class={styles.actions}>
+            <div
+              class={styles.actionBtn}
+              onClick={() =>
+                onToggleAudio(data.playState === 'pause' ? 'play' : 'pause')
+              }>
+              <img src={data.playState === 'pause' ? iconplay : iconpause} />
+            </div>
+            <div
+              class={styles.actionBtn}
+              onClick={() => (data.loop = !data.loop)}>
+              <img src={data.loop ? iconLoopActive : iconLoop} />
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+});

+ 47 - 20
src/views/courseware-play/component/video-item/index.tsx

@@ -2,6 +2,8 @@ import { defineComponent, onMounted, reactive, toRefs, watch } from 'vue';
 import { ref } from 'vue';
 import styles from './index.module.less';
 
+import TCPlayer from 'tcplayer.js';
+import 'tcplayer.js/dist/tcplayer.min.css';
 import iconLoop from '../../image/icon-loop.svg';
 import iconLoopActive from '../../image/icon-loop-active.svg';
 import iconplay from '../../image/icon-play.svg';
@@ -37,6 +39,8 @@ export default defineComponent({
   },
   emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
   setup(props, { emit }) {
+    const videoID = 'video' + Date.now() + Math.floor(Math.random() * 100);
+    const videoItem = ref();
     const { item, isEmtry } = toRefs(props);
     const data = reactive({
       timer: null as any,
@@ -71,23 +75,14 @@ export default defineComponent({
       clearTimeout(playTimer);
       if (state === 'play') {
         playTimer = setTimeout(() => {
-          elRef.value.play();
+          videoItem.value.play();
           data.playState = 'play';
         }, 100);
       } else {
-        elRef.value.pause();
+        videoItem.value.pause();
         data.playState = 'pause';
       }
-    };
-
-    /** 加载成功 */
-    const onLoadedmetadata = () => {
-      data.duration = elRef.value.duration;
-      // // 加载成功后台, 如果是第一次加载, 且是show状态, 则播放
-      // if (props.show) {
-      //   onToggleAudio('play');
-      // }
-      emit('loadedmetadata');
+      emit('togglePlay', data.playState);
     };
 
     /** 改变播放时间 */
@@ -100,19 +95,42 @@ export default defineComponent({
       }, 300);
     };
 
-    /** 播放结束 */
-    const onEnded = () => {
-      data.playState = 'pause';
-      // emit('ended');
-    };
     onMounted(() => {
-      console.log('加载');
+      videoItem.value = TCPlayer(videoID, {
+        appID: '',
+        controls: false
+      }); // player-container-id 为播放器容器 ID,必须与 html 中一致
+      if (videoItem.value) {
+        videoItem.value.poster(props.item.coverImg); // 封面
+        videoItem.value.src(isEmtry.value ? '' : item.value.content); // url 播放地址
+
+        // 初步加载时
+        videoItem.value.one('loadedmetadata', () => {
+          console.log(' Loading metadata');
+
+          // 获取时长
+          data.duration = videoItem.value.duration();
+
+          emit('loadedmetadata', videoItem.value);
+        });
+
+        // 视频播放时加载
+        videoItem.value.on('timeupdate', () => {
+          data.currentTime = videoItem.value.currentTime();
+        });
+
+        // 视频播放结束
+        videoItem.value.on('ended', () => {
+          data.playState = 'pause';
+          emit('ended');
+        });
+      }
     });
     return () => (
       <div class={styles.videoWrap}>
         <div class={styles.content}>
           <div ref={contetRef} class={styles.contentWrap}>
-            <video
+            {/* <video
               poster={props.item.coverImg}
               src={isEmtry.value ? '' : item.value.content}
               ref={elRef}
@@ -123,7 +141,16 @@ export default defineComponent({
                 data.currentTime = elRef.value.currentTime;
               }}
               onEnded={onEnded}
-              playsinline="false"></video>
+              playsinline="false"></video> */}
+            <video
+              style={{ width: '100%', height: '100%' }}
+              poster={props.item.coverImg}
+              src={isEmtry.value ? '' : item.value.content}
+              ref={elRef}
+              id={videoID}
+              preload="auto"
+              playsinline
+              webkit-playsinline></video>
           </div>
         </div>
 

+ 4 - 1
src/views/courseware-play/index.tsx

@@ -123,7 +123,10 @@ export default defineComponent({
         data.knowledgePointList = res.data.map((item: any) => {
           return {
             ...item,
-            url: item.type === 'SONG' ? 'https://gyt.ks3-cn-beijing.ksyuncs.com/courseware/1687916228530.png' : item.coverImg
+            url:
+              item.type === 'SONG'
+                ? 'https://gyt.ks3-cn-beijing.ksyuncs.com/courseware/1687916228530.png'
+                : item.coverImg
           };
         });
       }