503 lines
12 KiB
Vue
503 lines
12 KiB
Vue
<template>
|
||
<div class="dplayer-test">
|
||
<div class="container">
|
||
<h1>DPlayer 测试页面</h1>
|
||
<p>测试 DPlayer 视频播放器功能</p>
|
||
|
||
<div class="video-section">
|
||
<div ref="dplayerContainer" class="dplayer-container"></div>
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<div class="control-group">
|
||
<h3>基础控制</h3>
|
||
<button @click="play">播放</button>
|
||
<button @click="pause">暂停</button>
|
||
<button @click="seek(30)">跳转30秒</button>
|
||
<button @click="setVolume(50)">音量50%</button>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<h3>快进控制</h3>
|
||
<button @click="seekBackward(10)">后退10秒</button>
|
||
<button @click="seekForward(10)">前进10秒</button>
|
||
<button @click="seekBackward(30)">后退30秒</button>
|
||
<button @click="seekForward(30)">前进30秒</button>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<h3>倍速控制</h3>
|
||
<button @click="setPlaybackRate(0.5)">0.5x</button>
|
||
<button @click="setPlaybackRate(0.75)">0.75x</button>
|
||
<button @click="setPlaybackRate(1)">1x</button>
|
||
<button @click="setPlaybackRate(1.25)">1.25x</button>
|
||
<button @click="setPlaybackRate(1.5)">1.5x</button>
|
||
<button @click="setPlaybackRate(2)">2x</button>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<h3>清晰度切换</h3>
|
||
<button @click="switchQuality(0)">1080P</button>
|
||
<button @click="switchQuality(1)">720P</button>
|
||
<button @click="switchQuality(2)">480P</button>
|
||
<button @click="switchQuality(3)">360P</button>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<h3>字幕控制</h3>
|
||
<button @click="toggleSubtitle">切换字幕</button>
|
||
<button @click="showSubtitle">显示字幕</button>
|
||
<button @click="hideSubtitle">隐藏字幕</button>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<h3>弹幕控制 (模拟)</h3>
|
||
<button @click="toggleDanmaku">切换弹幕</button>
|
||
<button @click="showDanmaku">显示弹幕</button>
|
||
<button @click="hideDanmaku">隐藏弹幕</button>
|
||
<button @click="sendDanmaku('测试弹幕', '#fff', 0)">发送白色弹幕</button>
|
||
<button @click="sendDanmaku('红色弹幕', '#e54256', 0)">发送红色弹幕</button>
|
||
<button @click="sendDanmaku('顶部弹幕', '#ffe133', 1)">发送顶部弹幕</button>
|
||
<button @click="sendDanmaku('底部弹幕', '#64DD17', 2)">发送底部弹幕</button>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<h3>弹幕设置 (模拟)</h3>
|
||
<button @click="setDanmakuOpacity(0.5)">透明度50%</button>
|
||
<button @click="setDanmakuOpacity(0.8)">透明度80%</button>
|
||
<button @click="setDanmakuOpacity(1)">透明度100%</button>
|
||
<button @click="setDanmakuFontSize(20)">字体20px</button>
|
||
<button @click="setDanmakuFontSize(24)">字体24px</button>
|
||
<button @click="setDanmakuFontSize(28)">字体28px</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="status">
|
||
<h3>播放状态</h3>
|
||
<p>播放状态: {{ isPlaying ? '播放中' : '已暂停' }}</p>
|
||
<p>当前倍速: {{ currentPlaybackRate }}x</p>
|
||
<p>当前清晰度: {{ currentQuality }}</p>
|
||
<p>字幕状态: {{ subtitleVisible ? '显示' : '隐藏' }}</p>
|
||
<p>弹幕状态: {{ danmakuVisible ? '显示' : '隐藏' }} (模拟)</p>
|
||
<p>错误信息: {{ errorMessage || '无' }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, onUnmounted } from 'vue'
|
||
|
||
const dplayerContainer = ref<HTMLDivElement>()
|
||
let player: any = null
|
||
const isPlaying = ref(false)
|
||
const errorMessage = ref('')
|
||
const currentPlaybackRate = ref(1)
|
||
const currentQuality = ref('1080P')
|
||
const subtitleVisible = ref(false)
|
||
const danmakuVisible = ref(true)
|
||
|
||
// 视频质量配置
|
||
const videoQualities = [
|
||
{ name: '1080P', url: '/video/first.mp4' },
|
||
{ name: '720P', url: '/video/first.mp4' }, // 实际项目中应该是不同的视频文件
|
||
{ name: '480P', url: '/video/first.mp4' },
|
||
{ name: '360P', url: '/video/first.mp4' }
|
||
]
|
||
|
||
// 字幕配置
|
||
const subtitleConfig = {
|
||
url: '/subtitle/sample.vtt', // 示例字幕文件
|
||
type: 'webvtt',
|
||
fontSize: '20px',
|
||
bottom: '10%',
|
||
color: '#fff'
|
||
}
|
||
|
||
// 加载 DPlayer
|
||
const loadDPlayer = (): Promise<void> => {
|
||
return new Promise((resolve, reject) => {
|
||
if ((window as any).DPlayer) {
|
||
resolve()
|
||
return
|
||
}
|
||
|
||
// 加载 CSS
|
||
const cssLink = document.createElement('link')
|
||
cssLink.rel = 'stylesheet'
|
||
cssLink.href = 'https://cdn.jsdelivr.net/npm/dplayer@1.27.1/dist/DPlayer.min.css'
|
||
document.head.appendChild(cssLink)
|
||
|
||
// 加载 JS
|
||
const script = document.createElement('script')
|
||
script.src = 'https://cdn.jsdelivr.net/npm/dplayer@1.27.1/dist/DPlayer.min.js'
|
||
script.onload = () => resolve()
|
||
script.onerror = () => reject(new Error('Failed to load DPlayer'))
|
||
document.head.appendChild(script)
|
||
})
|
||
}
|
||
|
||
// 初始化播放器
|
||
const initPlayer = async () => {
|
||
try {
|
||
await loadDPlayer()
|
||
|
||
if (!dplayerContainer.value) return
|
||
|
||
const DPlayer = (window as any).DPlayer
|
||
player = new DPlayer({
|
||
container: dplayerContainer.value,
|
||
video: {
|
||
url: '/video/first.mp4',
|
||
type: 'auto'
|
||
},
|
||
subtitle: subtitleConfig,
|
||
// 暂时禁用弹幕 API,避免网络错误
|
||
// danmaku: {
|
||
// id: 'dplayer-danmaku',
|
||
// api: 'https://dplayer-mate.vercel.app/api/dplayer',
|
||
// token: 'tokendemo',
|
||
// maximum: 1000,
|
||
// user: 'DIYGod',
|
||
// bottom: '15%',
|
||
// unlimited: true
|
||
// },
|
||
autoplay: false,
|
||
theme: '#007bff',
|
||
lang: 'zh-cn',
|
||
hotkey: true,
|
||
preload: 'auto',
|
||
volume: 0.8,
|
||
playbackSpeed: [0.5, 0.75, 1, 1.25, 1.5, 2],
|
||
loop: false,
|
||
showDanmaku: true,
|
||
danmakuUnlimited: true,
|
||
contextmenu: [
|
||
{
|
||
text: '关于 DPlayer',
|
||
link: 'https://github.com/DIYGod/DPlayer'
|
||
}
|
||
]
|
||
})
|
||
|
||
// 事件监听
|
||
player.on('play', () => {
|
||
isPlaying.value = true
|
||
console.log('播放开始')
|
||
})
|
||
|
||
player.on('pause', () => {
|
||
isPlaying.value = false
|
||
console.log('播放暂停')
|
||
})
|
||
|
||
player.on('ended', () => {
|
||
isPlaying.value = false
|
||
console.log('播放结束')
|
||
})
|
||
|
||
player.on('error', () => {
|
||
errorMessage.value = '播放出错'
|
||
console.error('播放错误')
|
||
})
|
||
|
||
player.on('ratechange', () => {
|
||
currentPlaybackRate.value = player.speed()
|
||
console.log('倍速改变:', currentPlaybackRate.value)
|
||
})
|
||
|
||
// 移除可能导致错误的弹幕相关事件监听
|
||
// player.on('quality_start', () => {
|
||
// console.log('清晰度切换开始')
|
||
// })
|
||
|
||
// player.on('quality_end', () => {
|
||
// const quality = player.video.currentQuality
|
||
// currentQuality.value = videoQualities[quality].name
|
||
// console.log('清晰度切换完成:', currentQuality.value)
|
||
// })
|
||
|
||
} catch (err) {
|
||
console.error('初始化 DPlayer 失败:', err)
|
||
errorMessage.value = '初始化失败'
|
||
}
|
||
}
|
||
|
||
// 基础控制方法
|
||
const play = () => {
|
||
if (player) {
|
||
player.play()
|
||
}
|
||
}
|
||
|
||
const pause = () => {
|
||
if (player) {
|
||
player.pause()
|
||
}
|
||
}
|
||
|
||
const seek = (time: number) => {
|
||
if (player) {
|
||
player.seek(time)
|
||
}
|
||
}
|
||
|
||
const setVolume = (volume: number) => {
|
||
if (player) {
|
||
player.volume(volume / 100)
|
||
}
|
||
}
|
||
|
||
// 快进控制方法
|
||
const seekBackward = (seconds: number) => {
|
||
if (player) {
|
||
const currentTime = player.video.currentTime
|
||
const newTime = Math.max(0, currentTime - seconds)
|
||
player.seek(newTime)
|
||
}
|
||
}
|
||
|
||
const seekForward = (seconds: number) => {
|
||
if (player) {
|
||
const currentTime = player.video.currentTime
|
||
const duration = player.video.duration
|
||
const newTime = Math.min(duration, currentTime + seconds)
|
||
player.seek(newTime)
|
||
}
|
||
}
|
||
|
||
// 倍速控制方法
|
||
const setPlaybackRate = (rate: number) => {
|
||
if (player) {
|
||
player.speed(rate)
|
||
currentPlaybackRate.value = rate
|
||
}
|
||
}
|
||
|
||
// 清晰度切换方法
|
||
const switchQuality = (qualityIndex: number) => {
|
||
if (player && player.video) {
|
||
// 由于现在使用单一视频源,这里只是模拟切换
|
||
const quality = videoQualities[qualityIndex]
|
||
currentQuality.value = quality.name
|
||
console.log(`切换到清晰度: ${quality.name}`)
|
||
alert(`模拟切换到清晰度: ${quality.name}`)
|
||
}
|
||
}
|
||
|
||
// 字幕控制方法
|
||
const toggleSubtitle = () => {
|
||
if (player) {
|
||
if (subtitleVisible.value) {
|
||
player.hideSubtitle()
|
||
subtitleVisible.value = false
|
||
} else {
|
||
player.showSubtitle()
|
||
subtitleVisible.value = true
|
||
}
|
||
}
|
||
}
|
||
|
||
const showSubtitle = () => {
|
||
if (player) {
|
||
player.showSubtitle()
|
||
subtitleVisible.value = true
|
||
}
|
||
}
|
||
|
||
const hideSubtitle = () => {
|
||
if (player) {
|
||
player.hideSubtitle()
|
||
subtitleVisible.value = false
|
||
}
|
||
}
|
||
|
||
// 弹幕控制方法
|
||
const sendDanmaku = (text: string, color: string = '#fff', type: number = 0) => {
|
||
if (player && player.danmaku) {
|
||
player.danmaku.send({
|
||
text: text,
|
||
color: color,
|
||
type: type
|
||
})
|
||
} else {
|
||
console.log('弹幕功能暂不可用,请检查网络连接')
|
||
alert(`模拟发送弹幕: ${text} (颜色: ${color}, 类型: ${type})`)
|
||
}
|
||
}
|
||
|
||
const showDanmaku = () => {
|
||
if (player && player.danmaku) {
|
||
player.danmaku.show()
|
||
danmakuVisible.value = true
|
||
} else {
|
||
danmakuVisible.value = true
|
||
console.log('弹幕显示')
|
||
}
|
||
}
|
||
|
||
const hideDanmaku = () => {
|
||
if (player && player.danmaku) {
|
||
player.danmaku.hide()
|
||
danmakuVisible.value = false
|
||
} else {
|
||
danmakuVisible.value = false
|
||
console.log('弹幕隐藏')
|
||
}
|
||
}
|
||
|
||
const toggleDanmaku = () => {
|
||
if (player && player.danmaku) {
|
||
if (player.danmaku.visible) {
|
||
player.danmaku.hide()
|
||
danmakuVisible.value = false
|
||
} else {
|
||
player.danmaku.show()
|
||
danmakuVisible.value = true
|
||
}
|
||
} else {
|
||
danmakuVisible.value = !danmakuVisible.value
|
||
console.log(`弹幕${danmakuVisible.value ? '显示' : '隐藏'}`)
|
||
}
|
||
}
|
||
|
||
const setDanmakuOpacity = (opacity: number) => {
|
||
if (player && player.danmaku) {
|
||
player.danmaku.opacity(opacity)
|
||
} else {
|
||
console.log(`设置弹幕透明度: ${opacity}`)
|
||
}
|
||
}
|
||
|
||
const setDanmakuFontSize = (size: number) => {
|
||
if (player && player.danmaku) {
|
||
player.danmaku.fontSize(size)
|
||
} else {
|
||
console.log(`设置弹幕字体大小: ${size}px`)
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
initPlayer()
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
if (player) {
|
||
player.destroy()
|
||
player = null
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.dplayer-test {
|
||
min-height: 100vh;
|
||
background: #f5f5f5;
|
||
padding: 20px 0;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1000px;
|
||
margin: 0 auto;
|
||
padding: 0 20px;
|
||
}
|
||
|
||
h1 {
|
||
text-align: center;
|
||
color: #333;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
p {
|
||
text-align: center;
|
||
color: #666;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.video-section {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin-bottom: 20px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.dplayer-container {
|
||
width: 100%;
|
||
aspect-ratio: 16/9;
|
||
background: #000;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.controls {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.control-group {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.control-group h3 {
|
||
margin: 0 0 15px 0;
|
||
color: #333;
|
||
font-size: 16px;
|
||
text-align: center;
|
||
}
|
||
|
||
.control-group button {
|
||
display: block;
|
||
width: 100%;
|
||
padding: 10px 15px;
|
||
margin: 5px 0;
|
||
background: #007bff;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: background-color 0.3s;
|
||
}
|
||
|
||
.control-group button:hover {
|
||
background: #0056b3;
|
||
}
|
||
|
||
.status {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.status h3 {
|
||
margin: 0 0 15px 0;
|
||
color: #333;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.status p {
|
||
margin: 10px 0;
|
||
text-align: left;
|
||
color: #333;
|
||
font-size: 14px;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.controls {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.control-group button {
|
||
font-size: 12px;
|
||
padding: 8px 12px;
|
||
}
|
||
}
|
||
</style>
|