@mm-player/player-ui
v0.2.1
Published
播放器 UI(Renderer / Theme / Web Component / VideoMediaTech)
Maintainers
Readme
@mm-player/player-ui
mm-player UI 层:Renderer / Theme / Web Component / VideoMediaTech。依赖
@mm-player/player-core,不反向依赖。
模块用途
在 Headless 核心之上提供完整浏览器 UI:9 个独立 Renderer、CSS Variable 主题、<mm-player> Web Component(Shadow DOM 隔离)、控件/封面/Overlay/插件 API。VideoMediaTech 在此包内创建 video 元素并注入 Core。
适用场景
- 需要开箱即用的播放器界面。
- 需要声明式 Web Component(
<mm-player>)或 Shadow DOM 样式隔离。 - 需要运行时主题切换、Overlay、插件扩展。
如需纯逻辑核心(无 DOM),使用 @mm-player/player-core。
引入
pnpm add @mm-player/player-uiplayer-ui 内部创建 VideoMediaTech(含 <video>)并注入 PlayerCore,调用方无需手动注入。
最小示例
Vanilla JS
import { PlayerUI } from '@mm-player/player-ui';
const player = new PlayerUI({
container: '#player',
url: 'https://example.com/stream.mpd',
adapter: 'shaka',
autoplay: false,
ui: {
bottomBar: { progress: true, left: ['play', 'time'], right: ['volume', 'fullscreen'] }
}
});
player.on('error', (err) => console.error(err));
await player.play();
// 运行时增量切换主题;颜色令牌使用 "R, G, B" 通道值
player.setTheme({
'--mm-theme-color': '255, 85, 0',
'--mm-accent-color': '255, 85, 0',
'--mm-border-radius': '8px'
});
console.log(player.getTheme()); // 返回合并后的完整 ThemeTokens
// 释放
await player.destroy();Web Component
<mm-player
src="https://example.com/stream.flv"
protocol="flv"
controls
autoplay
muted
></mm-player>
<script type="module">
import '@mm-player/player-ui'; // 注册 <mm-player> 自定义元素
const el = document.querySelector('mm-player');
el.addEventListener('error', (e) => console.error(e.detail));
el.addEventListener('timeupdate', (e) => console.log(e.detail));
</script>关键 API
PlayerUI
| 方法 | 说明 |
| --- | --- |
| constructor(options: PlayerUIOptions) | container 必填(元素/ShadowRoot/选择器) |
| load / reload | 加载 / 重新加载媒体 |
| play / stop | 返回 Promise<void> 的播放/停止控制(经 CommandBus) |
| pause | 同步暂停;低带宽模式会在后台调用 stop |
| switchAdapter(config) | 运行时切换协议(whep/flv/shaka) |
| setVolume / setMuted | 音量(0–1)/ 静音 |
| setCover / clearCover | 封面 |
| setControlsVisible | 控制栏显隐 |
| setTheme / getTheme | 运行时主题(不重建 Renderer) |
| showOverlay / hideOverlay / setOverlay / removeOverlay | 程序化 Overlay |
| addPlugin / removePlugin | 插件(CorePlugin/UIPlugin/Legacy) |
| exportSnapshot / restoreSnapshot | 快照导出/恢复 |
| healthCheck | 健康检查 |
| getDevTools | DevTools(惰性) |
| on / off / once | 事件(代理 Core) |
| getCore | 获取 PlayerCore 实例 |
| moveTo(container) | 移动到新容器(保持进度) |
| destroy | 异步完整释放 |
主题定制
PlayerUIOptions.theme 与 setTheme() 都接受 ThemeUpdate(Partial<ThemeTokens>)。未提供的令牌沿用当前值;getTheme() 返回副本,修改返回对象不会反向改变播放器。
| 令牌 | 默认值 | 作用 |
| --- | --- | --- |
| --mm-theme-color | 97, 234, 253 | 主状态色 |
| --mm-accent-color | 97, 234, 253 | 进度、滑块和悬停强调色 |
| --mm-font-color | 255, 255, 255 | 控件前景色 |
| --mm-font-color-reverse | 0, 0, 0 | 反色区域 |
| --mm-bg-color | 0, 0, 0 | 视频区域背景色 |
| --mm-border-radius | 4px | 控件与进度条圆角 |
| --mm-control-height | 36px | 方形控制按钮尺寸 |
仓库完整示例的“主题定制”面板提供颜色选择器、圆角/控件高度滑块和实时 JSON,可直接作为可视化配置入口。
创建与使用插件
新插件应通过 type 显式声明接收的上下文,避免依赖旧版启发式识别:
import type { CorePlugin } from '@mm-player/player-core';
import type { UIPlugin, Plugin } from '@mm-player/player-ui';
let offState = () => {};
let watermarkNode: HTMLElement | null = null;
let onLegacyPlay = () => {};
const corePlugin: CorePlugin = {
type: 'core',
name: 'state-observer',
install({ stateManager }) {
offState = stateManager.subscribe((snapshot) => console.log(snapshot.status));
},
destroy() { offState(); }
};
const uiPlugin: UIPlugin = {
type: 'ui',
name: 'watermark',
install({ playerUI }) {
watermarkNode = document.createElement('span');
watermarkNode.textContent = 'LIVE';
playerUI.showOverlay(watermarkNode);
},
destroy() { watermarkNode?.remove(); }
};
const legacyPlugin: Plugin = {
type: 'legacy',
name: 'legacy-events',
install(playerUI) {
onLegacyPlay = () => console.log('play');
playerUI.on('play', onLegacyPlay);
},
uninstall(playerUI) { playerUI.off('play', onLegacyPlay); }
};
await player.addPlugin(corePlugin);
await player.addPlugin(uiPlugin);
await player.addPlugin(legacyPlugin);
await player.removePlugin('watermark');Core 插件接收 Headless 状态/命令上下文,UI 插件接收 DOM/UI 上下文,Legacy 插件直接接收 PlayerUI。removePlugin() 会执行对应清理并注销;插件必须自行对称释放订阅、DOM、计时器和外部资源。
Web Component <mm-player>
| 属性 / Attribute | 说明 |
| --- | --- |
| src / protocol / poster | 媒体源 / 协议 / 封面 |
| autoplay / loop / playsinline / muted / controls / volume | 布尔/数值属性,反射 |
方法:play / pause / stop / destroy / load / reload / switchAdapter / setVolume / setMuted / setTheme / setControlsVisible + Overlay/Slot API(appendOverlay/removeOverlay/replaceOverlay/clearOverlay/appendPoster/removePoster/appendLeftControl/appendRightControl/removeControl)+ getCore/getPlayerUI/getShadowRoot。
事件:ready / loaded / destroy / play / pause / ended / error / timeupdate / buffering / progress / statechange / themechange / overlaychange / controlchange / slotchange / resize / reconnecting / reconnected / reconnectfailed / unsupported。
生命周期与资源释放
PlayerUI构造时渲染 UI、创建VideoMediaTech、注入 Core、订阅StateManager。destroy顺序:取消 StateManager 订阅 → 销毁 Renderer → 卸载插件 → 清理 IconManager →await core.destroy()→ 清空 DOM → 释放引用。必须await。destroy幂等(destroyed标志守卫)。ended状态自动触发stop(由 state-change 驱动,仅转入 ended 时触发一次)。- Web Component 同一任务内移除再挂载视为 DOM 移动,不销毁 UI;若清理已经开始,重新连接会等待旧实例销毁后再初始化。
线程与回调约束
- UI 全部派生自
PlayerSnapshot(state-change 驱动),不直读 adapter/video/tech。 - 控件激活态由 state-change 驱动,不乐观置位(避免适配器早返回时 UI 假激活)。
- Renderer 互不引用,仅消费 Snapshot + dispatch。
- 主题/控件显隐运行时切换不重建 Renderer、不重新 render/mount。
常见错误与注意事项
container必填且必须存在;选择器找不到会抛错。volume范围 0–1(默认 1)。- 低带宽模式
pause实际执行stop(断开节省资源);高带宽模式仅暂停。 - ShadowRoot 宿主下样式经
adoptedStyleSheets隔离;HTMLElement 宿主用<style>。 destroy后不得再调用实例方法。
