import { defineComponent, nextTick, 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.png'; import iconLoopActive from '../../image/icon-loop-active.png'; import iconplay from '../../image/icon-play.svg'; import iconpause from '../../image/icon-pause.svg'; import iconSpeed from '../../image/icon-speed.png'; import { NSlider } from 'naive-ui'; import { getSecondRPM } from '@/helpers/utils'; import { Slider, showToast } from 'vant'; 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: [ 'canplay', 'pause', 'togglePlay', 'ended', 'reset', 'close', 'error', 'loadedmetadata' ], setup(props, { emit, expose }) { const videoID = 'video' + Date.now() + Math.floor(Math.random() * 100); const speedBtnId = 'speed' + Date.now() + Math.floor(Math.random() * 100); const videoItem = ref(); const { item, isEmtry } = toRefs(props); const data = reactive({ speedInKbps: '', timer: null as any, currentTime: 0, duration: 0, loop: false, playState: 'pause' as 'play' | 'pause', vudio: null as any, reload: false, speedControl: false, speedStyle: { left: '1px' }, defaultSpeed: 1 // 默认速度 }); const videoRef = ref(); const contetRef = ref(); watch( () => props.show, val => { onToggleAudio(val ? 'play' : 'pause'); } ); watch( () => props.pageVisibility, val => { if (val === 'hidden' && props.show) { onToggleAudio('pause'); } } ); let playTimer = null as any; // 切换音频播放 const onToggleAudio = (state: 'play' | 'pause') => { data.speedControl = false; clearTimeout(playTimer); if (state === 'play') { playTimer = setTimeout(() => { videoItem.value?.play(); data.playState = 'play'; }, 100); } else { videoItem.value?.pause(); data.playState = 'pause'; } emit('togglePlay', data.playState); }; // 如果视屏异常后,需要重新播放视屏 const onPlay = () => { data.speedControl = false; if (videoItem.value) { videoItem.value.src(item.value.content); emit('reset'); } }; /** 改变播放时间 */ const handleChangeTime = (val: number) => { data.speedControl = false; data.currentTime = val; clearTimeout(data.timer); data.timer = setTimeout(() => { // elRef.value.currentTime = val; // data.timer = null; videoItem.value.currentTime(val); data.timer = null; }, 300); }; let 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 = () => { // videoEle if (videoItem.value) { console.log(videoItem.value, 'item.value', item.value); videoItem.value.poster(props.item.coverImg); // 封面 videoItem.value.src(item.value.content); // url 播放地址 videoItem.value.playbackRate(data.defaultSpeed); // 初步加载时 videoItem.value.one('loadedmetadata', (e: any) => { videoItem.value.playbackRate(data.defaultSpeed); data.reload = false; // 获取时长 data.duration = videoItem.value.duration(); emit('canplay', videoItem.value); if (item.value.autoPlay && videoItem.value) { // videoItem.value?.play() nextTick(() => { videoTimerErrorCount = 0; videoItem.value.currentTime(0); nextTick(handlePlayVideo); }); } emit('loadedmetadata', videoItem.value); }); // 视频开始播放 videoItem.value.on('play', () => { emit('close'); emit('canplay'); }); // videoItem.value.on('pause', () => { data.playState = 'pause'; emit('pause'); }); // 视频播放时加载 videoItem.value.on('timeupdate', () => { if (data.timer) return; data.currentTime = videoItem.value.currentTime(); }); // 视频播放结束 videoItem.value.on('ended', () => { data.playState = 'pause'; // emit('ended'); }); videoItem.value.on('playing', () => { data.playState = 'play'; }); videoItem.value.on('canplay', (e: any) => { // console.log('canplay'); data.duration = videoItem.value.duration(); emit('canplay'); }); // 视频播放异常 videoItem.value.on('error', (e: any) => { emit('error'); console.log(e, 'error'); }); } }; watch( () => props.item, () => { videoItem.value.pause(); videoItem.value.currentTime(0); if (item.value?.id) { __init(); data.playState = 'pause'; } else { videoItem.value.pause(); } } ); watch( () => props.showModel, () => { data.speedControl = false; } ); const calculateSpeed = (element: any) => { let previousBytesLoaded = 0; let timer: any = null; let previousTime = Date.now(); let isWaiting = false; // 缓存检测状态 let isBuffering = false; // 缓存检测计时器 let bufferTimeout: any = null; // 设定一个检测缓存停止的时间间隔,这里我们设置为2500毫秒(2秒) const BUFFER_CHECK_INTERVAL = 2500; function resetDownloadSpeed() { timer = setTimeout(() => { // displayElement.textContent = `视屏下载速度: 0 KB/s`; data.speedInKbps = `0 KB/s`; }, 1500); } function buffterCatch() { // 设定一个计时器,检查是否在指定的时间内再次触发了progress事件 bufferTimeout = setTimeout(() => { if (isBuffering) { // 如果计时器到达且isBuffering仍为true,则认为缓存停止 console.log('停止缓存数据。'); isBuffering = false; data.speedInKbps = ''; } }, BUFFER_CHECK_INTERVAL); } 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); if (!element.paused) { const kbps = speed / 1024; const speedInKbps = kbps.toFixed(2); // 转换为千字节每秒并保留两位小数 if (kbps > 1024) { data.speedInKbps = `${Number((kbps / 1024).toFixed(2))} M/s`; } else { data.speedInKbps = `${Number(speedInKbps)} KB/s`; } } previousBytesLoaded = currentBytesLoaded; previousTime = currentTime; } if (!element.paused) { // 如果1秒钟没有返回就重置数据 clearTimeout(timer); resetDownloadSpeed(); } if (!isWaiting) { // 如果有缓存检测计时器,则清除它 if (bufferTimeout) { clearTimeout(bufferTimeout); } // 标记为正在缓存 isBuffering = true; buffterCatch(); } }); element.addEventListener('waiting', () => { console.log('waiting'); isWaiting = true; if (!element.paused) { // 如果1秒钟没有返回就重置数据 clearTimeout(timer); resetDownloadSpeed(); } // 如果有缓存检测计时器,则清除它 if (bufferTimeout) { clearTimeout(bufferTimeout); } }); element.addEventListener('canplay', () => { console.log('canplay'); isWaiting = false; // 如果有缓存检测计时器,则清除它 if (bufferTimeout) { clearTimeout(bufferTimeout); } // 标记为正在缓存 isBuffering = true; buffterCatch(); }); element.addEventListener('pause', () => { clearTimeout(timer); // 如果有缓存检测计时器,则清除它 if (bufferTimeout) { clearTimeout(bufferTimeout); } data.speedInKbps = ''; }); element.addEventListener('error', () => { element.pause(); videoItem.value.pause(); }); }; onMounted(() => { videoItem.value = TCPlayer(videoID, { appID: '', controls: false }); // player-container-id 为播放器容器 ID,必须与 html 中一致 __init(); document.getElementById(speedBtnId)?.addEventListener('click', e => { e.stopPropagation(); data.speedControl = !data.speedControl; }); nextTick(() => { calculateSpeed(videoRef.value); }); }); const pause = () => { videoItem.value.pause(); }; expose({ onPlay, pause }); return () => (