上一篇讲了 Feature 即 Slice,这篇看预设(preset)——v10 怎么把 16 个 feature 组装成开箱即用的播放器形态。预设不是魔法,就是一个类型化元组。但这个元组的顺序和取舍很有讲究:live 预设为什么去掉 playback-rate?audio 为什么没有全屏?我读源码时发现的这些细节,比想象中有意思。
预设就是元组
先看 packages/core/src/dom/store/features/presets.ts:24-39 的 videoFeatures:
export const videoFeatures: VideoFeatures = [
playbackFeature, // L25
playbackRateFeature, // L26
qualityFeature, // L27
audioTrackFeature, // L28
volumeFeature, // L29
timeFeature, // L30
sourceFeature, // L31
bufferFeature, // L32
fullscreenFeature, // L33
pipFeature, // L34
remotePlaybackFeature,// L35
controlsFeature, // L36
textTrackFeature, // L37
errorFeature, // L38
];
就是个数组,14 个 feature。对应的类型在 player.ts:40-55(VideoFeatures 14 元组)。其余预设:
| 预设 | 元组长度 | 内容 | 行号 |
|---|---|---|---|
videoFeatures | 14 | 全家桶 | presets.ts:24-39 |
audioFeatures | 7 | playback、playbackRate、volume、time、source、buffer、error | presets.ts:41-49 |
backgroundFeatures | 0 | 空数组(L52,带 TODO:playback/source/buffer 待定) | presets.ts:51-52 |
liveVideoFeatures | 12 | video 版去掉 playbackRate/quality/audioTrack,加 live | presets.ts:60-73 |
liveAudioFeatures | 7 | audio 版加 live | presets.ts:81-89 |
features/index.ts 是 barrel,feature.parts.ts L21-38 还提供 features.playback、features.volume 这样的短别名。
配合 player.ts:107-115 的 store 别名:
export type VideoPlayerStore = PlayerStore<VideoFeatures>;
export type AudioPlayerStore = PlayerStore<AudioFeatures>;
// ... LiveVideoPlayerStore, LiveAudioPlayerStore, BackgroundPlayerStore
预设之间的取舍逻辑
我对比了几个预设的差异,发现每处取舍都有明确的领域理由:
live 为什么去掉 playback-rate
player.ts:70-75 的 doc 注释直接说明了:直播下调速没有意义(你不能让直播快进,那会追不上 live edge)。同时 live 预设加了 liveFeature——暴露 liveEdgeStart/targetLiveWindow(直播边缘窗口信息,UI 用它显示「回到直播」按钮)。
这个取舍很能说明问题:预设不是「能力全集」,是「领域合理的能力集」。直播播放器的 UI 不该出现倍速按钮——不是隐藏它,而是根本不装这个 feature。selectPlaybackRate 在 live store 上会返回 undefined(12 篇讲的存在性检查),倍速按钮自动禁用/消失。
audio 为什么没有 fullscreen/pip/controls
AudioFeatures(player.ts:57-65)只有 7 个:playback、playbackRate、volume、time、source、buffer、error。没有 fullscreen、pip、remotePlayback、controls、textTrack、quality、audioTrack。
原因很直白:音频没有画面,全屏和画中画无从谈起;没有画面也就没有视觉控件容器(controls feature 依赖 container,音频播放器常常没有);quality/audioTrack 在纯音频场景多数引擎不提供。
video 为什么有 quality/audioTrack 而 live 没有
liveVideoFeatures 去掉了 qualityFeature 和 audioTrackFeature。quality 依赖 videoRenditions(清晰度列表)——直播 HLS 的 rendition 切换逻辑和点播不同(ABR 主导),v10 当前把 quality feature 限定在点播场景。audioTrack 同理,直播多音轨的当前支持还不完整。
这些「没有」反映的是 beta 阶段的务实边界,不是永久设计。
backgroundFeatures 是空的
presets.ts:51-52:
// TODO: playback/source/buffer - to be determined
export const backgroundFeatures: BackgroundFeatures = [];
背景视频(ambient video,自动播放静音循环那种)当前是空元组。player.ts:67-68 同样 TODO。这是明确的「还没想好」——背景视频需要哪些状态还在设计。
一个空预设也值得导出,这让类型系统先行:BackgroundPlayerStore 已经存在,后续填充不用改调用方。
预设怎么被消费
04 篇追过调用链:createPlayer({ features })(html/react)把元组 combine() 成一个大 slice。预设就是 createPlayer({ features: videoFeatures }) 的标准参数。
但预设不是唯一用法——你可以自由组装:
// 只想要播放和音量的极简播放器
createPlayer({ features: [playbackFeature, volumeFeature] });
// video 预设基础上加方向锁
createPlayer({ features: [...videoFeatures, orientationLockFeature({ type: 'portrait' })] });
第二种模式(预设 + 追加)是推荐的扩展方式——19 篇讲的 configurable feature 在这里发挥作用。类型上 [...VideoFeatures, PlayerFeature<OrientationState>] 照样推导出正确的 store 类型。
顺序重要吗
videoFeatures 的顺序看起来随意(playback 在最前,error 在最后),但实际上:
combine的Object.assign按序合并(12 篇)——后面的覆盖前面的。所以顺序在「重复状态键」时才关键。当前 16 个 feature 的状态键互不重叠(__DEV__下重复会 warn),所以顺序不影响行为。- attach 按序执行(combine 的循环)。
controlsFeature排在fullscreenFeature后面没什么特别含义,但如果你写的 feature 依赖另一个 feature 先 attach,就要注意顺序。
诚实地讲:当前预设的顺序主要是按重要性排列的可读性约定,不是行为契约。但 combine 的语义保证了「顺序可能影响覆盖」,写自定义元组时要记住这点。
小结
- 预设是类型化元组,不是类、不是配置对象。
- 取舍有领域理由:live 去掉 playback-rate(直播不能调速)、audio 没有全屏(没画面)、background 为空(待定)。
- 「不装 feature」比「隐藏 UI」更彻底——selector 返回 undefined,按钮自动消失。
- 预设可自由组装/追加,configurable feature 是扩展点。
- 空预设也导出,类型先行。
下一篇逐个拆解 16 个 feature 的实现——从最简单的 playback 到最复杂的 controls(202 行,处理触摸/指针/投屏一堆边界)。
18



被折叠的 条评论
为什么被折叠?



