Skip to content

Commit b0e5d86

Browse files
committed
feat: video update
1 parent e044e8c commit b0e5d86

File tree

10 files changed

+1449
-75
lines changed

10 files changed

+1449
-75
lines changed

docs/VIDEO_PLAYER_GUIDE.md

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
# SmartVideoPlayer 开发与使用指南
2+
3+
本文档面向项目内的 `SmartVideoPlayer`(视频页播放器组件),用于说明:
4+
- 组件能力与整体架构
5+
- HTML5 / YouTube / 通用 Embed 三种模式的差异
6+
- 配置项(受控/非受控)与对接方式
7+
- 常见问题与踩坑记录(本次迭代中实际遇到的问题)
8+
9+
> 适用范围:`src/components/stateless/SmartVideoPlayer/` 以及 `src/pages/video/`
10+
11+
---
12+
13+
## 1. 组件目标与边界
14+
15+
### 1.1 目标
16+
17+
- 提供接近 YouTube 风格的播放器控制条(按钮 + tooltip + 统一布局)。
18+
- HTML5 video 模式下支持:
19+
- 播放/暂停、快进/快退
20+
- 音量控制(滑杆 + 静音切换 + 音量记忆)
21+
- 进度条与时间显示
22+
- 全屏与画中画(PiP)
23+
- HLS(`.m3u8`)可选接入(通过 `hls.js`,浏览器支持时自动启用)
24+
- 字幕 UI(设置面板里可开关/选择字幕轨道)
25+
- 懒播放(IntersectionObserver)与滚出视口的 mini 小窗
26+
- 设置面板(Portal 渲染,避免被父容器裁切)
27+
- YouTube / Embed 模式下:以 `iframe` 方式嵌入展示,并提供「新窗口打开」与设置入口。
28+
29+
### 1.2 边界(刻意不做/不承诺)
30+
31+
- **Embed / YouTube iframe 并不一定可控**:默认不提供播放/暂停/进度/音量等“看似可控但实际上不可控”的 UI。
32+
- 对于第三方站点嵌入:可能被 CSP 或 `X-Frame-Options` 禁止 iframe。
33+
- 不提供 DASH/DRM、广告、章节、缩略图等高级功能(可作为后续扩展)。
34+
35+
---
36+
37+
## 2. 代码位置与关键文件
38+
39+
- 组件:`src/components/stateless/SmartVideoPlayer/index.jsx`
40+
- 样式:`src/components/stateless/SmartVideoPlayer/index.module.css`
41+
- HTML5 video Hook:`src/components/hooks/useVideo/index.tsx`
42+
- 示例页面:`src/pages/video/index.jsx`
43+
44+
---
45+
46+
## 3. Provider 模式说明
47+
48+
`provider` 用于选择渲染策略:
49+
50+
### 3.1 `provider="html5"`
51+
52+
- 渲染 `<video>` 元素。
53+
- 启用 `useVideo(..., { enabled: true })`,负责订阅事件、维护状态(paused/muted/currentTime/volume 等)。
54+
- 启用懒播放 / mini / PiP / 进度条 / 音量条等完整控制条。
55+
56+
### 3.2 `provider="youtube"`
57+
58+
- 渲染 `<iframe>`,src 为 `https://www.youtube.com/embed/${youtubeId}?...`
59+
- 设置项 `ytControls` 控制 YouTube 原生控制条是否显示。
60+
- 控制条不展示 play/pause、进度、音量等(避免误导)。
61+
- 提供「新窗口打开」:打开 `https://www.youtube.com/watch?v=${youtubeId}`
62+
63+
### 3.3 `provider="embed"`
64+
65+
- 渲染 `<iframe>`,src 由 `embedUrl``getEmbedUrl(config)` 生成。
66+
- 控制条不展示 play/pause、进度、音量等(避免误导)。
67+
- 提供「新窗口打开」:优先打开 `externalUrl`,否则打开 `sourceUrl`,再否则 fallback 为 `embedUrl`
68+
69+
---
70+
71+
## 4. Props(使用说明)
72+
73+
> 以实际实现为准;此处描述的是本次迭代后的关键对外接口。
74+
75+
### 4.1 基础 Props
76+
77+
- `provider?: 'html5' | 'youtube' | 'embed'`
78+
- `title?: string`
79+
80+
### 4.2 HTML5 video 相关
81+
82+
- `src?: string`:支持 mp4;当为 `.m3u8` 时,会尝试走 HLS 播放(原生支持则直接播,否则使用 `hls.js`)。
83+
- `trackSrc?: string`:字幕 vtt 地址(可选)。
84+
- `trackLang?: string`:字幕语言(默认 `en`)。
85+
86+
### 4.3 YouTube 相关
87+
88+
- `youtubeId?: string`
89+
90+
### 4.4 Embed 相关
91+
92+
- `embedUrl?: string`:直接传 iframe src。
93+
- `getEmbedUrl?: (config) => string`:用配置动态生成 iframe src(更通用)。
94+
- `externalUrl?: string`**最高优先级**,用于「新窗口打开」按钮。
95+
- `sourceUrl?: string`:当没有 externalUrl 时使用(例如“源站播放页”)。
96+
97+
### 4.5 配置(受控/非受控)
98+
99+
- `initialConfig?: Partial<Config>`:非受控模式下的初始值。
100+
- `config?: Config`:受控模式配置。
101+
- `onConfigChange?: (nextConfig: Config) => void`:受控模式的变更回调。
102+
103+
配置项(当前):
104+
- `lazyPlay: boolean`:懒播放(滚出视口自动暂停)
105+
- `miniPlayer: boolean`:滚出视口右下角 mini 小窗
106+
- `autoPlay: boolean`:自动播放
107+
- `autoMute: boolean`:自动静音(为自动播放策略服务)
108+
- `playbackRate: number`:播放速度(仅 HTML5 生效)
109+
- `ytControls: boolean`:YouTube 控制条开关(仅 YouTube 生效)
110+
111+
### 4.6 事件回调(埋点/排障)
112+
113+
- `onEvent?: (name: string, detail?: any) => void`
114+
- `onError?: (payload: { message: string; provider: string; src?: string; youtubeId?: string; embedUrl?: string; ... }) => void`
115+
116+
`onEvent` 事件名(目前实现):
117+
- `play` / `pause`
118+
- `seek`(绝对定位) / `seekRelative`(相对跳转)
119+
- `volume` / `mute`
120+
- `fullscreen`
121+
- `captions`(字幕开关/切换)
122+
- `pipEnter` / `pipLeave` / `pipToggle`
123+
- `openExternal`
124+
- `embedLoad` / `embedTimeout`
125+
- `hlsNative` / `hlsAttach` / `hlsError`
126+
- `error`(与 `onError` 同步触发)
127+
128+
### 4.7 键盘快捷键(需先让播放器获得焦点)
129+
130+
播放器外层容器已支持 `tabIndex=0`:可以用 Tab 聚焦,或鼠标点一下播放器区域再按键。
131+
132+
- Space / K:播放/暂停
133+
- ← / →:后退/前进 5 秒
134+
- ↑ / ↓:音量 +5% / -5%
135+
- M:静音切换
136+
- F:全屏
137+
- P:画中画(PiP)
138+
- S:打开/关闭设置
139+
- Esc:关闭设置
140+
- Embed/YouTube 模式:O 打开「新窗口」
141+
142+
---
143+
144+
## 5. 示例
145+
146+
### 5.1 HTML5 模式
147+
148+
```jsx
149+
<SmartVideoPlayer
150+
provider="html5"
151+
src={trailerSource}
152+
title="预告片"
153+
/>
154+
```
155+
156+
### 5.2 YouTube 模式
157+
158+
```jsx
159+
<SmartVideoPlayer
160+
provider="youtube"
161+
youtubeId="xJyWbjATtIE"
162+
title="科幻预告片"
163+
initialConfig={{ autoPlay: true, autoMute: true, ytControls: true }}
164+
/>
165+
```
166+
167+
### 5.3 通用 embed(直接传 embedUrl + 外部打开)
168+
169+
```jsx
170+
<SmartVideoPlayer
171+
provider="embed"
172+
title="Embed 示例"
173+
embedUrl="https://player.vimeo.com/video/76979871?autoplay=1&muted=1"
174+
externalUrl="https://vimeo.com/76979871"
175+
/>
176+
```
177+
178+
### 5.4 通用 embed(用 getEmbedUrl 动态拼接)
179+
180+
```jsx
181+
<SmartVideoPlayer
182+
provider="embed"
183+
title="动态 Embed"
184+
sourceUrl="https://example.com/watch/123"
185+
getEmbedUrl={(cfg) => {
186+
const base = 'https://example.com/embed/123'
187+
const params = new URLSearchParams({
188+
autoplay: cfg.autoPlay ? '1' : '0',
189+
muted: cfg.autoMute ? '1' : '0',
190+
})
191+
return `${base}?${params.toString()}`
192+
}}
193+
/>
194+
```
195+
196+
### 5.5 受控 config(推荐用于外部统一设置)
197+
198+
```jsx
199+
const [playerConfig, setPlayerConfig] = useState({
200+
lazyPlay: true,
201+
miniPlayer: true,
202+
autoPlay: true,
203+
autoMute: true,
204+
playbackRate: 1,
205+
ytControls: true,
206+
})
207+
208+
<SmartVideoPlayer
209+
provider={provider}
210+
src={src}
211+
config={playerConfig}
212+
onConfigChange={setPlayerConfig}
213+
/>
214+
```
215+
216+
---
217+
218+
## 6. 开发流程(本次迭代实践总结)
219+
220+
这部分是“从需求到稳定上线”的流程复盘,方便后续继续维护。
221+
222+
### 6.1 需求拆分
223+
224+
1) UI 风格:参考 YouTube(按钮图标、tooltip、布局、hover 行为)
225+
2) 行为增强:IntersectionObserver 懒播放、mini、小窗恢复/关闭
226+
3) 兼容性:autoplay 策略(muted 兜底)、play() Promise 错误处理
227+
4) 设置面板:Portal 避免父容器裁切;定位与最大高度策略
228+
5) provider 抽象:统一到 SmartVideoPlayer 内部,支持 youtube + 通用 embed
229+
230+
### 6.2 关键问题与处理方式
231+
232+
- 自动播放不触发:
233+
- 浏览器通常要求 muted 才允许 autoplay。
234+
- 方案:在 play 前同步确保 `video.muted = true`(当 autoMute 打开时),并在 `canplay/loadedmetadata` 时机触发。
235+
236+
- 切换播放列表点击后不播放:
237+
- `<source src>` 更新不等价于媒体已加载。
238+
- 方案:切源后执行 `video.load()`;并在用户点击手势内调用播放(避免被策略拦截)。
239+
240+
- 设置面板被裁切/高度不对:
241+
- 父容器 overflow/布局容易裁切绝对定位弹层。
242+
- 方案:Portal 到 `document.body`,再用按钮 `getBoundingClientRect()` 计算 fixed 定位。
243+
- 高度:不要固定高度,使用“靠近按钮可用空间”的 maxHeight,并让内部滚动区滚动。
244+
245+
- TDZ(Cannot access ... before initialization):
246+
- 常见于 hook 解构出来的函数在声明前被引用。
247+
- 方案:确保依赖函数(例如 `unmute`)解构完成后再创建 callback。
248+
249+
- CSS Module 污染导致页面异常:
250+
- 典型问题:写了裸 `article { ... }` 这样的全局选择器。
251+
- 方案:所有选择器都挂在 module 根 class 下(例如 `.section-center article`)。
252+
253+
- Embed 模式控件遮挡原站点播放器控制区:
254+
- 方案:embed 模式不渲染底部覆盖式控制条,改为右上角小工具条,并用 pointer-events 让 iframe 交互不被拦截。
255+
256+
---
257+
258+
## 7. 已知限制与建议
259+
260+
- 站点是否可嵌入:被 CSP / `X-Frame-Options` 限制时 iframe 会失败。
261+
- 建议:提供 `externalUrl/sourceUrl` 的“新窗口打开”作为兜底路径。
262+
263+
- 若需要“可控的 YouTube/Vimeo”:
264+
- 建议:接入官方 Player API(iframe + postMessage),做成 provider adapter(见改进文档)。
265+
266+
---
267+
268+
## 8. 调试建议
269+
270+
- HTML5 模式:
271+
- 查看 `video.play()` 的 Promise 报错(策略/AbortError/网络错误)。
272+
- 切源后是否执行 `load()`
273+
274+
- Embed 模式:
275+
- 如果 iframe 空白:先看浏览器控制台是否提示 `refused to display` / CSP。
276+
- 优先保证 “新窗口打开” 路径可用。

0 commit comments

Comments
 (0)