|
@@ -1,17 +1,15 @@
|
|
|
-import { defineComponent, PropType } from 'vue';
|
|
|
-import styles from './index.module.less';
|
|
|
-import Plyr from 'plyr';
|
|
|
+import { defineComponent, nextTick, onMounted, toRefs } from 'vue';
|
|
|
import 'plyr/dist/plyr.css';
|
|
|
-import { browser } from '@/helpers/utils';
|
|
|
+import Plyr from 'plyr';
|
|
|
+import { ref } from 'vue';
|
|
|
+import styles from './index.module.less';
|
|
|
+import iconplay from '@views/attend-class/image/icon-pause.svg';
|
|
|
+import iconpause from '@views/attend-class/image/icon-play.svg';
|
|
|
+import iconReplay from '@views/attend-class/image/icon-replay.svg';
|
|
|
+
|
|
|
export default defineComponent({
|
|
|
- name: 'o-video',
|
|
|
+ name: 'video-play',
|
|
|
props: {
|
|
|
- setting: {
|
|
|
- type: Object,
|
|
|
- default: () => ({})
|
|
|
- },
|
|
|
- controls: Boolean,
|
|
|
- height: String,
|
|
|
src: {
|
|
|
type: String,
|
|
|
default: ''
|
|
@@ -20,130 +18,132 @@ export default defineComponent({
|
|
|
type: String,
|
|
|
default: ''
|
|
|
},
|
|
|
- styleValue: {
|
|
|
- type: Object,
|
|
|
- default: () => ({})
|
|
|
- },
|
|
|
- preload: {
|
|
|
- type: String as PropType<'auto' | 'metadata' | 'none'>,
|
|
|
- default: 'auto'
|
|
|
- },
|
|
|
- currentTime: {
|
|
|
+ isEmtry: {
|
|
|
type: Boolean,
|
|
|
- default: true
|
|
|
- },
|
|
|
- playsinline: {
|
|
|
- type: Boolean,
|
|
|
- default: true
|
|
|
- },
|
|
|
- onPlay: {
|
|
|
- type: Function,
|
|
|
- default: () => ({})
|
|
|
+ default: false
|
|
|
}
|
|
|
},
|
|
|
- emits: ['exitfullscreen'],
|
|
|
- data() {
|
|
|
- return {
|
|
|
- player: null as any,
|
|
|
- loading: true // 首次进入加载中
|
|
|
+ emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
|
|
|
+ setup(props, { emit, expose }) {
|
|
|
+ const { src, poster, isEmtry } = toRefs(props);
|
|
|
+ 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 toggleReplay = () => {
|
|
|
+ const replayBtn = document.getElementById(replayBtnId);
|
|
|
+ if (!replayBtn || !videoItem.value) return;
|
|
|
+ videoItem.value.restart();
|
|
|
+ };
|
|
|
+ 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);
|
|
|
};
|
|
|
- },
|
|
|
- mounted() {
|
|
|
- this._init();
|
|
|
- },
|
|
|
- methods: {
|
|
|
- _init() {
|
|
|
- // controls: [
|
|
|
- // 'play-large' , // 中间的大播放按钮
|
|
|
- // 'restart' , // 重新开始播放
|
|
|
- // 'rewind' , // 按寻道时间倒带(默认 10 秒)
|
|
|
- // 'play' , // 播放/暂停播放
|
|
|
- // 'fast-forward' , // 快进查找时间(默认 10 秒)
|
|
|
- // 'progress' , // 播放和缓冲的进度条和滑动条
|
|
|
- // 'current-time' , // 播放的当前时间
|
|
|
- // ' duration' , // 媒体的完整持续时间
|
|
|
- // 'mute' , // 切换静音
|
|
|
- // 'volume', // 音量控制
|
|
|
- // 'captions' , // 切换字幕
|
|
|
- // 'settings' , // 设置菜单
|
|
|
- // 'pip' , // 画中画(当前仅 Safari)
|
|
|
- // 'airplay' , // Airplay(当前仅 Safari)
|
|
|
- // 'download ' , // 显示一个下载按钮,其中包含指向当前源或您在选项中指定的自定义 URL 的链接
|
|
|
- // 'fullscreen' , // 切换全屏
|
|
|
- // ] ;
|
|
|
- const controls = [
|
|
|
- 'play-large',
|
|
|
- 'play',
|
|
|
- 'progress',
|
|
|
- 'captions',
|
|
|
- 'fullscreen'
|
|
|
- ];
|
|
|
- if (this.currentTime) {
|
|
|
- controls.push('current-time');
|
|
|
- }
|
|
|
- const params: any = {
|
|
|
- controls: controls,
|
|
|
- ...this.setting,
|
|
|
- invertTime: false
|
|
|
- };
|
|
|
|
|
|
- if (browser().iPhone) {
|
|
|
- params.fullscreen = {
|
|
|
- enabled: true,
|
|
|
- fallback: 'force',
|
|
|
- iosNative: true
|
|
|
- };
|
|
|
+ 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);
|
|
|
+ } else {
|
|
|
+ playBtn.classList.remove(styles.btnPlay);
|
|
|
+ playBtn.classList.add(styles.btnPause);
|
|
|
}
|
|
|
+ };
|
|
|
+ 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>
|
|
|
|
|
|
- this.player = new Plyr((this as any).$refs.video, params);
|
|
|
+ </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>`;
|
|
|
|
|
|
- this.player.on('play', () => {
|
|
|
- this.onPlay && this.onPlay(this.player);
|
|
|
+ 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 } // 不适用全屏
|
|
|
});
|
|
|
+ if (videoItem.value) {
|
|
|
+ videoItem.value.on('play', () => {
|
|
|
+ if (videoItem.value) {
|
|
|
+ videoItem.value.muted = false;
|
|
|
+ videoItem.value.volume = 1;
|
|
|
+ }
|
|
|
|
|
|
- this.player.on('enterfullscreen', () => {
|
|
|
- console.log('fullscreen');
|
|
|
- const i = document.createElement('i');
|
|
|
- i.id = 'fullscreen-back';
|
|
|
- i.className = 'van-icon van-icon-arrow-left video-back';
|
|
|
- i.addEventListener('click', () => {
|
|
|
- this.player.fullscreen.exit();
|
|
|
+ changePlayBtn('');
|
|
|
+ });
|
|
|
+ videoItem.value.on('pause', () => {
|
|
|
+ changePlayBtn('play');
|
|
|
+ });
|
|
|
+ videoItem.value.on('ended', () => {
|
|
|
+ emit('ended');
|
|
|
+ changePlayBtn('play');
|
|
|
+ });
|
|
|
+ videoItem.value.once('loadedmetadata', () => {
|
|
|
+ changePlayBtn('play');
|
|
|
});
|
|
|
- console.log(document.getElementsByClassName('plyr'));
|
|
|
- document.getElementsByClassName('plyr')[0].appendChild(i);
|
|
|
- });
|
|
|
-
|
|
|
- this.player.on('exitfullscreen', () => {
|
|
|
- console.log('exitfullscreen');
|
|
|
- const i = document.getElementById('fullscreen-back');
|
|
|
- i && i.remove();
|
|
|
- this.$emit('exitfullscreen');
|
|
|
- });
|
|
|
- },
|
|
|
|
|
|
- onReplay() {
|
|
|
- this.player.restart();
|
|
|
- this.player.play();
|
|
|
- },
|
|
|
- onStop() {
|
|
|
- this.player.stop();
|
|
|
- }
|
|
|
- },
|
|
|
- unmounted() {
|
|
|
- this.player?.destroy();
|
|
|
- },
|
|
|
- render() {
|
|
|
- return (
|
|
|
- <div class={styles['video-container']}>
|
|
|
+ nextTick(() => {
|
|
|
+ onDefault();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ expose({
|
|
|
+ changePlayBtn,
|
|
|
+ toggleHideControl
|
|
|
+ });
|
|
|
+ return () => (
|
|
|
+ <div class={styles.videoWrap}>
|
|
|
<video
|
|
|
- ref="video"
|
|
|
- class={styles['video']}
|
|
|
- src={this.src}
|
|
|
- playsinline={this.playsinline}
|
|
|
- poster={this.poster}
|
|
|
- preload={this.preload}
|
|
|
- style={{ ...this.styleValue }}></video>
|
|
|
- {/* </div> */}
|
|
|
+ style={{ width: '100%', height: '100%' }}
|
|
|
+ src={isEmtry.value ? '' : src.value}
|
|
|
+ poster={poster.value}
|
|
|
+ ref={videoRef}
|
|
|
+ playsinline="false"></video>
|
|
|
</div>
|
|
|
);
|
|
|
}
|