230 lines
5.8 KiB
Vue
230 lines
5.8 KiB
Vue
<template>
|
||
<div ref="videoRef" class="dplayer-container"></div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
|
||
import DPlayer from 'dplayer';
|
||
import Hls from 'hls.js';
|
||
|
||
const videoRef = ref(null);
|
||
const player = ref(null);
|
||
const hlsInstance = ref(null);
|
||
|
||
const props = defineProps({
|
||
autoplay: { type: Boolean, default: false },
|
||
videoUrl: { type: String, required: true },
|
||
danmaku: { type: Object, default: () => ({}) }
|
||
});
|
||
|
||
// 判断是否为 HLS 格式
|
||
const isHlsUrl = (url) => {
|
||
return url.includes('.m3u8') || url.includes('hls') || url.includes('application/x-mpegURL');
|
||
};
|
||
|
||
// 清理 HLS 实例
|
||
const cleanupHls = () => {
|
||
if (hlsInstance.value) {
|
||
try {
|
||
hlsInstance.value.destroy();
|
||
} catch (e) {
|
||
console.warn('清理 HLS 实例时出错:', e);
|
||
}
|
||
hlsInstance.value = null;
|
||
}
|
||
};
|
||
|
||
// 创建播放器配置
|
||
const createPlayerOptions = (url) => {
|
||
const proxyUrl = `/proxy?url=${encodeURIComponent(url)}`;
|
||
const isHls = isHlsUrl(url);
|
||
|
||
console.log('视频 URL:', url);
|
||
console.log('是否为 HLS 格式:', isHls);
|
||
console.log('代理 URL:', proxyUrl);
|
||
|
||
const options = {
|
||
container: videoRef.value,
|
||
autoplay: props.autoplay,
|
||
video: {},
|
||
danmaku: props.danmaku
|
||
};
|
||
|
||
if (isHls && Hls.isSupported()) {
|
||
// HLS 格式使用 customHls
|
||
options.video = {
|
||
url: proxyUrl,
|
||
type: 'customHls',
|
||
customType: {
|
||
customHls: function (video, player) {
|
||
cleanupHls(); // 清理旧的实例
|
||
|
||
const hls = new Hls({
|
||
maxBufferLength: 20,
|
||
maxMaxBufferLength: 60,
|
||
enableWorker: true,
|
||
lowLatencyMode: false
|
||
});
|
||
|
||
hlsInstance.value = hls;
|
||
|
||
hls.loadSource(video.src);
|
||
hls.attachMedia(video);
|
||
|
||
// 错误处理
|
||
hls.on(Hls.Events.ERROR, (event, data) => {
|
||
console.error('HLS 错误:', data);
|
||
if (data.fatal) {
|
||
switch (data.type) {
|
||
case Hls.ErrorTypes.NETWORK_ERROR:
|
||
console.error('网络错误,尝试恢复...');
|
||
hls.startLoad();
|
||
break;
|
||
case Hls.ErrorTypes.MEDIA_ERROR:
|
||
console.error('媒体错误,尝试恢复...');
|
||
hls.recoverMediaError();
|
||
break;
|
||
default:
|
||
console.error('致命错误,无法恢复');
|
||
hls.destroy();
|
||
break;
|
||
}
|
||
}
|
||
});
|
||
|
||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||
console.log('HLS 清单解析完成');
|
||
if (props.autoplay) {
|
||
video.play().catch(err => {
|
||
console.warn('自动播放失败:', err);
|
||
});
|
||
}
|
||
});
|
||
}
|
||
}
|
||
};
|
||
} else {
|
||
// 普通视频格式(MP4等)
|
||
options.video = {
|
||
url: proxyUrl,
|
||
type: 'auto' // 让 DPlayer 自动检测类型
|
||
};
|
||
}
|
||
|
||
return options;
|
||
};
|
||
|
||
onMounted(() => {
|
||
try {
|
||
const playerOptions = createPlayerOptions(props.videoUrl);
|
||
player.value = new DPlayer(playerOptions);
|
||
|
||
player.value.on('play', () => {
|
||
console.log('播放:', props.videoUrl);
|
||
});
|
||
|
||
player.value.on('pause', () => {
|
||
console.log('暂停');
|
||
});
|
||
|
||
player.value.on('ended', () => {
|
||
console.log('播放结束');
|
||
});
|
||
|
||
player.value.on('error', (error) => {
|
||
console.error('播放器错误:', error);
|
||
});
|
||
|
||
player.value.on('loadstart', () => {
|
||
console.log('开始加载视频');
|
||
});
|
||
|
||
player.value.on('canplay', () => {
|
||
console.log('视频可以播放');
|
||
});
|
||
} catch (error) {
|
||
console.error('初始化播放器失败:', error);
|
||
}
|
||
});
|
||
|
||
watch(() => props.videoUrl, (newUrl) => {
|
||
if (player.value && newUrl) {
|
||
console.log('切换视频到:', newUrl);
|
||
|
||
try {
|
||
cleanupHls(); // 清理旧的 HLS 实例
|
||
|
||
const isHls = isHlsUrl(newUrl);
|
||
const proxyUrl = `/proxy?url=${encodeURIComponent(newUrl)}`;
|
||
|
||
if (isHls && Hls.isSupported()) {
|
||
// HLS 格式切换
|
||
player.value.switchVideo({
|
||
url: proxyUrl,
|
||
type: 'customHls',
|
||
customType: {
|
||
customHls: function (video, player) {
|
||
cleanupHls();
|
||
|
||
const hls = new Hls({
|
||
maxBufferLength: 20,
|
||
maxMaxBufferLength: 60,
|
||
enableWorker: true
|
||
});
|
||
|
||
hlsInstance.value = hls;
|
||
hls.loadSource(video.src);
|
||
hls.attachMedia(video);
|
||
|
||
hls.on(Hls.Events.ERROR, (event, data) => {
|
||
console.error('HLS 错误:', data);
|
||
if (data.fatal) {
|
||
switch (data.type) {
|
||
case Hls.ErrorTypes.NETWORK_ERROR:
|
||
hls.startLoad();
|
||
break;
|
||
case Hls.ErrorTypes.MEDIA_ERROR:
|
||
hls.recoverMediaError();
|
||
break;
|
||
default:
|
||
hls.destroy();
|
||
break;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
} else {
|
||
// 普通视频格式切换
|
||
player.value.switchVideo({
|
||
url: proxyUrl,
|
||
type: 'auto'
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('切换视频失败:', error);
|
||
}
|
||
}
|
||
});
|
||
|
||
// 销毁时清理播放器和 HLS 实例
|
||
onBeforeUnmount(() => {
|
||
cleanupHls();
|
||
if (player.value) {
|
||
try {
|
||
player.value.destroy();
|
||
} catch (e) {
|
||
console.warn('销毁播放器时出错:', e);
|
||
}
|
||
player.value = null;
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.dplayer-container {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
</style> |