|
@@ -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 { ref } from 'vue';
|
|
|
|
+import TCPlayer from 'tcplayer.js';
|
|
|
|
+import 'tcplayer.js/dist/tcplayer.min.css';
|
|
import styles from './index.module.less';
|
|
import styles from './index.module.less';
|
|
import iconplay from '@views/attend-class/image/icon-pause.png';
|
|
import iconplay from '@views/attend-class/image/icon-pause.png';
|
|
import iconpause from '@views/attend-class/image/icon-play.png';
|
|
import iconpause from '@views/attend-class/image/icon-play.png';
|
|
import iconReplay from '@views/attend-class/image/icon-replay.png';
|
|
import iconReplay from '@views/attend-class/image/icon-replay.png';
|
|
|
|
+import { NSlider } from 'naive-ui';
|
|
|
|
|
|
export default defineComponent({
|
|
export default defineComponent({
|
|
name: 'video-play',
|
|
name: 'video-play',
|
|
@@ -26,114 +29,91 @@ export default defineComponent({
|
|
emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
|
|
emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
|
|
setup(props, { emit, expose }) {
|
|
setup(props, { emit, expose }) {
|
|
const { src, poster, isEmtry } = toRefs(props);
|
|
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 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 {
|
|
} 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(() => {
|
|
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) {
|
|
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', () => {
|
|
videoItem.value.on('ended', () => {
|
|
|
|
+ videoFroms.paused = true;
|
|
emit('ended');
|
|
emit('ended');
|
|
- changePlayBtn('play');
|
|
|
|
- });
|
|
|
|
- videoItem.value.once('loadedmetadata', () => {
|
|
|
|
- changePlayBtn('play');
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- nextTick(() => {
|
|
|
|
- onDefault();
|
|
|
|
});
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
expose({
|
|
expose({
|
|
- changePlayBtn,
|
|
|
|
|
|
+ // changePlayBtn,
|
|
toggleHideControl
|
|
toggleHideControl
|
|
});
|
|
});
|
|
return () => (
|
|
return () => (
|
|
@@ -143,7 +123,67 @@ export default defineComponent({
|
|
src={isEmtry.value ? '' : src.value}
|
|
src={isEmtry.value ? '' : src.value}
|
|
poster={poster.value}
|
|
poster={poster.value}
|
|
ref={videoRef}
|
|
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>
|
|
</div>
|
|
);
|
|
);
|
|
}
|
|
}
|