mxivideo/docs/video-player-usage.md

389 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# VideoPlayer 组件使用指南
基于 demo.md 重写的 VideoPlayer 组件使用说明
## 🎯 设计特点
### 参考 demo.md 的优秀设计模式
1. **useImperativeHandle**: 暴露组件方法供外部调用
2. **清晰的接口定义**: TypeScript 接口规范
3. **完善的事件处理**: 加载、播放、错误等事件回调
4. **加载状态管理**: 统一的加载状态显示
5. **资源清理机制**: 防止内存泄漏
6. **错误处理回调**: 可自定义错误处理逻辑
## 📚 接口定义
### VideoPlayerRef
```typescript
export interface VideoPlayerRef {
seek(time: number): void; // 跳转到指定时间
pause(): void; // 暂停播放
play(): void; // 开始播放
getCurrentTime(): number; // 获取当前播放时间
isPaused(): boolean; // 是否暂停状态
getDuration(): number; // 获取视频总时长
captureFrame(): string | undefined; // 截取当前帧
fullScreen(): void; // 全屏播放
reload(): void; // 重新加载视频
}
```
### 事件回调接口
```typescript
export interface OnSourceLoaded {
(player: HTMLVideoElement | null): void;
}
export interface OnPlayerError {
(err: Error, player: HTMLVideoElement | null): void;
}
export interface OnPlayerTimeChange {
(player: HTMLVideoElement | null): void;
}
```
### 组件Props
```typescript
interface VideoPlayerProps {
videoPath: string // 视频文件路径
isOpen: boolean // 是否打开播放器
onClose: () => void // 关闭回调
title?: string // 播放器标题
onSourceLoaded?: OnSourceLoaded // 视频加载完成回调
onError?: OnPlayerError // 错误处理回调
onTimeChange?: OnPlayerTimeChange // 时间变化回调
}
```
## 🔧 基本使用
### 1. 简单使用
```tsx
import VideoPlayer from '@/components/VideoPlayer'
function MyComponent() {
const [isPlayerOpen, setIsPlayerOpen] = useState(false)
const [videoPath, setVideoPath] = useState('')
return (
<>
<button onClick={() => setIsPlayerOpen(true)}>
播放视频
</button>
<VideoPlayer
videoPath={videoPath}
isOpen={isPlayerOpen}
onClose={() => setIsPlayerOpen(false)}
title="我的视频"
/>
</>
)
}
```
### 2. 带事件处理的使用
```tsx
import VideoPlayer, { VideoPlayerRef } from '@/components/VideoPlayer'
function AdvancedVideoPlayer() {
const playerRef = useRef<VideoPlayerRef>(null)
const [isPlayerOpen, setIsPlayerOpen] = useState(false)
const [videoPath, setVideoPath] = useState('')
const handleSourceLoaded = (player: HTMLVideoElement | null) => {
console.log('视频加载完成:', {
duration: player?.duration,
videoWidth: player?.videoWidth,
videoHeight: player?.videoHeight
})
}
const handleError = (err: Error, player: HTMLVideoElement | null) => {
console.error('视频播放错误:', err.message)
// 可以显示自定义错误提示
alert(`播放失败: ${err.message}`)
}
const handleTimeChange = (player: HTMLVideoElement | null) => {
if (player) {
console.log(`播放进度: ${player.currentTime}/${player.duration}`)
}
}
const handleCaptureFrame = () => {
const frameData = playerRef.current?.captureFrame()
if (frameData) {
console.log('截取帧数据:', frameData)
// 可以保存或显示截图
}
}
return (
<>
<div className="space-x-2">
<button onClick={() => setIsPlayerOpen(true)}>
播放视频
</button>
<button onClick={() => playerRef.current?.play()}>
播放
</button>
<button onClick={() => playerRef.current?.pause()}>
暂停
</button>
<button onClick={() => playerRef.current?.seek(30)}>
跳转到30
</button>
<button onClick={handleCaptureFrame}>
截取当前帧
</button>
<button onClick={() => playerRef.current?.fullScreen()}>
全屏
</button>
</div>
<VideoPlayer
ref={playerRef}
videoPath={videoPath}
isOpen={isPlayerOpen}
onClose={() => setIsPlayerOpen(false)}
title="高级视频播放器"
onSourceLoaded={handleSourceLoaded}
onError={handleError}
onTimeChange={handleTimeChange}
/>
</>
)
}
```
## 🎮 控制方法
### 播放控制
```typescript
// 播放视频
playerRef.current?.play()
// 暂停视频
playerRef.current?.pause()
// 检查播放状态
const isPaused = playerRef.current?.isPaused()
```
### 时间控制
```typescript
// 跳转到指定时间(秒)
playerRef.current?.seek(120) // 跳转到2分钟
// 获取当前播放时间
const currentTime = playerRef.current?.getCurrentTime()
// 获取视频总时长
const duration = playerRef.current?.getDuration()
```
### 高级功能
```typescript
// 截取当前帧
const frameDataUrl = playerRef.current?.captureFrame()
if (frameDataUrl) {
// 可以用于显示缩略图或保存截图
const img = new Image()
img.src = frameDataUrl
document.body.appendChild(img)
}
// 全屏播放
playerRef.current?.fullScreen()
// 重新加载视频
playerRef.current?.reload()
```
## 🔄 事件处理
### 视频加载完成
```typescript
const handleSourceLoaded = (player: HTMLVideoElement | null) => {
if (player) {
console.log('视频信息:', {
duration: player.duration,
width: player.videoWidth,
height: player.videoHeight,
readyState: player.readyState
})
// 可以在这里设置默认音量、播放速度等
player.volume = 0.8
player.playbackRate = 1.0
}
}
```
### 错误处理
```typescript
const handleError = (err: Error, player: HTMLVideoElement | null) => {
console.error('播放错误:', err)
// 根据错误类型进行不同处理
if (err.message.includes('文件不存在')) {
// 处理文件不存在的情况
showNotification('视频文件不存在,请检查文件路径')
} else if (err.message.includes('格式不支持')) {
// 处理格式不支持的情况
showNotification('视频格式不支持请转换为MP4格式')
} else {
// 通用错误处理
showNotification('视频播放失败,请重试')
}
}
```
### 播放进度监听
```typescript
const handleTimeChange = (player: HTMLVideoElement | null) => {
if (player) {
const progress = (player.currentTime / player.duration) * 100
// 更新进度条
setPlayProgress(progress)
// 记录观看进度
saveWatchProgress(videoId, player.currentTime)
// 在特定时间点触发事件
if (Math.floor(player.currentTime) === 60) {
console.log('播放到1分钟了')
}
}
}
```
## 🎨 样式定制
### 加载状态自定义
组件内置了加载状态显示,使用 Loader2 图标和半透明背景。如需自定义,可以修改组件内的样式:
```tsx
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
<div className="flex flex-col items-center space-y-2">
<Loader2 className="h-8 w-8 animate-spin text-white" />
<span className="text-white text-sm">加载中...</span>
</div>
</div>
)}
```
### 视频容器样式
```tsx
<video
className="w-full h-auto max-h-[70vh] bg-black"
crossOrigin="anonymous"
preload="auto"
/>
```
## 🔧 最佳实践
### 1. 错误处理
```typescript
// 总是提供错误处理回调
<VideoPlayer
onError={(err, player) => {
// 记录错误日志
console.error('Video error:', err)
// 显示用户友好的错误信息
toast.error('视频播放失败,请重试')
// 可选:自动重试
setTimeout(() => {
playerRef.current?.reload()
}, 2000)
}}
/>
```
### 2. 性能优化
```typescript
// 使用 useMemo 缓存回调函数
const handleSourceLoaded = useMemo(() => (player: HTMLVideoElement | null) => {
// 处理逻辑
}, [])
const handleError = useMemo(() => (err: Error, player: HTMLVideoElement | null) => {
// 错误处理逻辑
}, [])
```
### 3. 资源管理
```typescript
// 组件卸载时清理资源
useEffect(() => {
return () => {
// VideoPlayer 内部已经处理了资源清理
// 但如果有外部资源需要清理,可以在这里处理
}
}, [])
```
## 🚀 高级用法
### 视频分析和处理
```typescript
const VideoAnalyzer = () => {
const playerRef = useRef<VideoPlayerRef>(null)
const [frames, setFrames] = useState<string[]>([])
const captureFramesAtInterval = () => {
const duration = playerRef.current?.getDuration()
if (!duration) return
const interval = duration / 10 // 每10%截取一帧
const framePromises = []
for (let i = 0; i < 10; i++) {
const time = i * interval
framePromises.push(
new Promise<string>((resolve) => {
playerRef.current?.seek(time)
setTimeout(() => {
const frame = playerRef.current?.captureFrame()
resolve(frame || '')
}, 100)
})
)
}
Promise.all(framePromises).then(setFrames)
}
return (
<div>
<button onClick={captureFramesAtInterval}>
生成视频缩略图
</button>
<div className="grid grid-cols-5 gap-2 mt-4">
{frames.map((frame, index) => (
<img key={index} src={frame} alt={`Frame ${index}`} />
))}
</div>
<VideoPlayer
ref={playerRef}
// ... 其他props
/>
</div>
)
}
```
---
*重写后的 VideoPlayer 组件提供了更强大的功能和更好的开发体验!*