npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mm-player/player-ui

v0.4.0

Published

播放器 UI(Renderer / Theme / Web Component / VideoMediaTech)

Readme

@mm-player/player-ui

mm-player UI 层:Renderer / Theme / Web Component / VideoMediaTech。依赖 @mm-player/player-core,不反向依赖。

模块用途

在 Headless 核心之上提供完整浏览器 UI:10 个独立 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-ui

player-ui 内部创建 VideoMediaTech(含 <video>)并注入 PlayerCore,调用方无需手动注入。

文件与流的统一入口(自 0.4.0)

推荐 adapter: 'auto'(PlayerUI 构造时省略也等价)。MP4(含独立 fMP4)、WebM、MOV、OGV、blob: 单源经 FileAdapter 使用同一个 VideoMediaTech 的原生 video.src;m3u8/mpd 经 Shaka、flv/whep 经对应适配器。load() 只换源/适配器,保留同一 video、Core、控制栏、加载态、错误态、全屏及主题。

const player = new PlayerUI({
  container: '#player', adapter: 'auto',
  url: '/api/download?path=%2Fclip.mp4', mimeType: 'video/mp4',
  isLive: false, muted: true, autoplay: false, bandwidthMode: 'high',
  ui: { bottomBar: { progress: true, left: ['play', 'time'] } }
});
await player.play();
await player.load({ url: 'https://example.com/live.flv', isLive: true });
await player.play();
await player.load({ url: 'https://example.com/vod.mpd', isLive: false });
await player.play();
await player.load({ url: '/api/download?id=opaque', mimeType: 'video/mp4', isLive: false });
await player.play();

自动判定只看显式 MIME、URL 路径扩展名或 path/filename/file 参数,不发类型探测请求。不透明文件可强制 adapter: 'file';不透明流须明确其适配器/清单 MIME。未知地址保留 WHEP 回退。空客户端列表且无新 URL 时沿用当前实际协议,reload 不重判不透明地址;后续新源仍自动识别。显式适配器会继续沿用,要恢复自动识别传 'auto'。新 URL 省略 MIME 清除旧值,reload 保留。isLive 不参与路由:文件 false,FLV 按实际传值,DASH/HLS/WHEP 按实际媒体状态显示直播。

文件模式不提供 ABR、音轨/字幕选择、播放列表或网络统计;提供播放、seek、倍速、PiP、截图,控件仍按能力驱动。外挂字幕沿用显式 Shaka 单源契约(文件模式显式非空 subtitles 早拒);原有 Shaka MP4 调用保持兼容并显式传媒体 MIME。blob: 清单请明确 Shaka/MIME。MOV/codec/fMP4 seek 能力由浏览器决定。0.3.0 / 聚合包 1.4.0 不含这些入口。

本版已知限制:Shaka → file 切源在部分时序中可能保留零倍速,当前可通过设置菜单/示例选择 1× 或 player.getCore().setPlaybackRate(1) 恢复。速率自动恢复及快照校准尚未修复;Edge/Firefox 完整播放验收未完成。

最小示例

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 | 加载 / 重新加载媒体;load(config) 为增量合并语义:先把 config 合并进实例配置,再把合并后完整实例配置的独立快照交给 Core(浅拷贝 + url、subtitles、playlist 数组及条目副本,与 switchAdapter/beforePlay 路径一致——调用方在返回前修改传入的配置对象或数组不影响已入队请求)。config 省略 adapter 时沿用实例的选择策略(自动模式每次重新判定,显式模式沿用适配器),显式传入时切换;url 与 playlist(WHEP/FLV 客户端列表)互斥(后写赢,显式提供其一即归一化退出另一形态),reload() 维持当前模式。配置在加载尝试前提交,失败保留最近一次请求配置、reload() 按其重试。并发调用仍由 Core 生命周期 FIFO 串行完成,但较早请求完成后若已被后续请求替代,不执行控件同步与自动播放(最终状态由最后一次请求收敛)。加载成功后按该次请求快照同步播放列表控件;加载失败按 Core 真实资源状态清理自动 prev/next(只删不注,见内置控制节),错误原样向调用方拒绝 |

客户端列表配置边界:UI 不会把同请求的非空 playlist + 多元素 url 数组静默归一为单一形态,也不会因同时传入 url 而吞掉非法的 playlist: null;两种请求均交由 Core 在释放旧资源前以 CONFIG_ERROR 拒绝。正常的单地址与客户端列表切换仍按上述增量合并规则执行。

仅调用 load({ playlist: [] }) 时,UI 与 Core 一起沿用当前曲目的 URL 和适配器;成功后 UI 实例配置也同步该 URL,随后 reload() 不会因进入列表时清空了 options.url 而失去媒体源。若要同时切换协议,须提供新 URL,不能把当前曲目地址隐式交给另一适配器。 | play / stop | 返回 Promise<void> 的播放/停止控制(经 CommandBus);异步 beforePlay 的迟到结果受加载请求代次与播放意图代次保护,更晚的加载请求不会被旧配置覆盖,更晚的 stop 不会被旧请求反向恢复为播放 | | pause | 同步暂停;低带宽模式会在后台调用 stop;两种模式都会同步撤销在途自动播放意图 | | switchAdapter(config) | 运行时切换协议(whep/flv/shaka);加载期间发生更晚的 pause/stop/destroy 时仍完成换源与控件同步,但不会迟到自动播放 | | setVolume / setMuted | 音量(0–1)/ 静音 | | enterFullscreen / exitFullscreen / toggleFullscreen / isFullscreen | 全屏(经 CommandBus fullscreen 命令,与按钮/F 快捷键共享同一处理器;isFullscreen 仅在当前播放器根容器全屏时为 true;拒绝路径收敛为 console.warn,不产生未处理拒绝) | | canPip / enterPip / exitPip / togglePip / isPipActive | 画中画(标准 Picture-in-Picture API;canPip 同时检查适配器能力、浏览器支持和 video.disablePictureInPicture;返回 Promise<boolean> 表示是否到达目标状态;状态与 pipchange 事件以 document.pictureInPictureElement 为唯一事实来源) | | screenshot(options?) | 截图(同步返回 CaptureResult,失败抛错);内置截图按钮经 screenshot 命令派发,成功触发下载、失败输出可理解提示 | | returnToLive | 回到直播沿(经 CommandBus seek 载荷 'live',与控制栏按钮共享链路;VOD/WHEP 为 no-op) | | isOffline | 当前设备网络是否离线(去重后状态;构造时读 navigator.onLine,此后由 window online/offline 事件驱动,与 offlinechange 事件载荷同源) | | getQualityTracks / getCurrentQuality / setAbrEnabled / isAbrEnabled / switchQuality | 清晰度 / ABR 类型安全代理(见 player-core README) | | getAudioTracks / getCurrentAudioTrack / setAudioTrack | 音轨类型安全代理(见 player-core README;language:role 分组键载荷) | | getSubtitleTracks / getCurrentSubtitleTrack / setSubtitleTrack | 字幕类型安全代理(见 player-core README;载荷为字幕轨 id 或 'off' 关闭) | | next / prev / jumpTo | 播放列表切曲代理(Shaka 原生队列与 WHEP/FLV 客户端列表共用;切曲失败输出固定诊断 [PlayerUI] next failed 等,不产生未处理 rejection;到端/越界/未启动安全返回 false) | | getStrings / setStrings | 文案字典读取(完整副本)/ 运行时增量合并(与 setTheme 同风格),已渲染标签同步刷新 | | setCover / clearCover | 封面 | | setControlsVisible | 控制栏显隐 | | setTheme / getTheme | 运行时主题(不重建 Renderer) | | showOverlay / hideOverlay / setOverlay / removeOverlay | 程序化 Overlay | | addPlugin / removePlugin | 插件(CorePlugin/UIPlugin/Legacy) | | exportSnapshot / restoreSnapshot | 快照导出/恢复 | | healthCheck | 健康检查 | | getDevTools | DevTools(惰性) | | on / off / once | 事件(offlinechange 为 PlayerUI 本地事件、不依赖适配器;其余事件代理 Core;另含 pipchange、screenshotresult、screenshotfailed、qualitychanged、adaptation、trackschanged) | | getCore | 获取 PlayerCore 实例 | | getPlayerContainer | 获取播放器根容器(video 与渲染层宿主;Web Component 的尺寸监听即基于它) | | moveTo(container) | 移动到新容器(保持进度) | | destroy | 异步完整释放(含 DOM 命令注销与 PiP/全屏/截图/网络 online-offline 监听解绑) |

内置控制与设置菜单

默认截图按钮使用线框相机和独立圆形镜头,在小尺寸控制栏中保持可辨识;可通过 iconConfig.symbols.screenshot 提供自定义 symbol,仍优先于内置图标。

控制栏内置按钮由 Capability 驱动显隐(不判断协议字符串):fullscreen(浏览器能力)、pip(canPip,浏览器不支持标准 API 时可见但禁用)、screenshot(canScreenshot)、menu(由「实际存在可展示的菜单分区」决定:倍速 canChangeSpeed / 清晰度 supportsQuality 且有轨道 / 音轨 supportsAudioTrack 且分组 > 1 / 字幕 canSubtitle 且轨道 ≥ 1,任一成立即显示——只支持音轨或字幕的第三方适配器同样有入口;全部不成立时隐藏,WHEP 等场景不显示无项可选的死菜单;轨道列表变化后的 state-change 自动重判显隐)、live(「回到直播」文字按钮:快照 live 且延迟超过「会话内最佳延迟 + 3s」自适应基线时显示,点击经 seek 载荷 'live' 派发;VOD/WHEP 恒不出现)、prev/next(播放列表控件:playlist 能力位为 true 且当前配置为长度 > 1 的 url 数组(Shaka 原生队列)或非空 playlist 客户端列表(WHEP/FLV,Core 派生能力;构造器携带 playlist 配置同样触发初始加载)时自动注入——构造器初始 load 成功后即注入(autoplay: false 无需等待首次 play),并在 load/reload/switchAdapter/play 成功后按该次请求的配置快照同步,判重以真实控件注册表为准,首次载入、重复 load、reload、播放往复均不重复注册、不输出重复注册警告;并发时较早请求完成不提前注入/删除(最终由最后一次请求收敛);初始加载失败、单源或能力位关闭的适配器(未配置客户端列表的 WHEP/FLV,URL 误传数组也不注入)不注入。注入后的禁用态由快照 playlist 派生(2026-09-23 真实状态产品化):首曲禁 prev、末曲禁 next,loop 配置(与切曲回绕判定同源)下两端保持可用;快照无 playlist(列表/队列未启动或非列表源)时恢复可用,不出现死按钮;禁用只作用于 PlayerUI 工厂创建的按钮——ui 配置声明或 addControl() 添加的自定义 prev/next 永不被触碰。加载失败时的清理规则(只删不注):失败的加载请求绝不为它注入新控件;旧自动控件按 Core 真实资源状态决定去留——Core 仍声明 playlist 能力且已合并配置仍为多 URL 队列或非空客户端列表时保留(典型:非法 adapter 在销毁旧适配器前被拒绝、播放列表/客户端列表 reload 失败——Core 仍持有旧播放列表或失败请求本就是列表形态),否则移除(典型:列表切单源后 load 失败、动态适配器加载失败 Core 无适配器)。失败后再次加载成功时控件恢复正确状态。切离播放列表形态时仅移除由 PlayerUI 自动注入的控件——归属以显式所有权记录为准(只收录自动注入成功的控件 id),ui 配置声明的自定义 prev/next 与运行时 addControl() 动态添加的 prev/next 属调用方,任何同步与失败清理都不删除、不覆盖,切回播放列表仍保留同一 DOM 元素)。默认底部栏右侧为 ['volume', 'pip', 'menu', 'screenshot', 'live', 'fullscreen'],可经 ui.bottomBar.right 自定义。

设置菜单(MenuRenderer)提供倍速(0.5×–2×)、清晰度/ABR("自动" + 轨道列表)、音轨与字幕分区:当前倍速派生自 PlayerSnapshot.playbackRate;选中"自动"派发 quality 命令载荷 'auto'(启用 ABR),固定清晰度派发轨道下标(适配器关闭 ABR 后切换轨道);音轨分区由 supportsAudioTrack 能力位门控(WHEP/FLV 不出现;单音轨源不展示),选择派发 audiotrack 命令载荷分组键(language:role,语言偏好切换不关 ABR);字幕分区由 canSubtitle 能力位门控(WHEP/FLV 不出现;支持但无轨道源不展示死分区,单轨仍展示——用户需要开/关),首项为「关闭」,选择派发 subtitle 命令载荷轨道 id 或 'off',当前选中项 aria-checked 同步;trackschanged / adaptation / qualitychanged / audiotrackchanged / subtitlechanged / subtitletracks 后菜单自动刷新(内容签名覆盖轨道全部公开显示字段:同 id/active 但 label/language/kind 变化会重建字幕菜单项,列表完全不变不重建 DOM)。触发按钮具备 tooltip、aria-label、aria-haspopup 与 aria-expanded;Escape 关闭并还原焦点,点击播放器外部或切换源时关闭;菜单项为原生 button(键盘可聚焦)。

PlayerSnapshot.subtitleVersion 是字幕轨列表的独立刷新信号:每次 subtitletracks 与换源递增。因此只实现字幕能力的第三方适配器在默认关闭字幕(subtitle 仍为 undefined)时,也能立即触发 state-change 并更新菜单入口,不需要同时派发 trackschanged。

字幕显示(第一阶段起):采用 Shaka Player 5.2.4 / 浏览器原生文本渲染(selectTextTrack 选择即显示,cue 由浏览器自绘,内嵌与外挂轨均如此)——SubtitleRenderer 为无 DOM 的显式占位(保留渲染器结构与公开导出面),未来若需自绘 cue 排版再在此实装。外挂字幕(2026-09-22 第二阶段):PlayerConfig.subtitles(SubtitleSource[])经 Shaka 单 URL 源注入侧载 WebVTT,轨道/选择/事件/菜单全部走上述既有链路;单条加载失败派发 subtitleloadfailed(载荷脱敏 PlayerError,可选字幕不阻断主视频),详见 docs/api.md 外挂字幕节。

从带外挂字幕的 Shaka 源切到 WHEP/FLV 时,若本次请求未显式传入 subtitles,UI 不会把旧字幕当成本次新配置提交;切回 Shaka 可沿用已保存的字幕。显式把非空字幕提交给 WHEP/FLV 仍以 CONFIG_ERROR 拒绝,失败后 reload() 保持同一拒绝语义;显式传 subtitles: [] 可清除旧配置。

主题定制

PlayerUIOptions.theme 与 setTheme() 都接受 ThemeUpdate(Partial<ThemeTokens>)。未提供的令牌沿用当前值;getTheme() 返回副本,修改返回对象不会反向改变播放器。

文案字典(i18n)

PlayerUIOptions.strings 与 setStrings() 都接受 Partial<PlayerStrings>(语义槽位键,与 theme 同风格增量合并);getStrings() 返回完整副本。内置 DEFAULT_STRINGS(中文,迁移自各 Renderer 现值)与 STRINGS_EN(英文整包):

import { PlayerUI, STRINGS_EN } from '@mm-player/player-ui';
const player = new PlayerUI({ container: '#player', strings: STRINGS_EN });
player.setStrings({ fullscreen: 'Pantalla completa' });  // 运行时增量

<mm-player> 经 property 设置(对象值不做属性反射):el.strings = { play: 'Play' }。唯一文案纠偏:宿主 aria-label 从 'Video Player' 统一为中文 '视频播放器'(可经 strings.playerRegion 覆盖)。

网络在线/离线联动

PlayerUI 监听 window offline/online(PlayerCore 保持 Headless 不感知 window/navigator)。navigator.onLine 只是设备网络提示,不代表媒体端点真实可达——本能力不据此伪造任何播放器状态,恢复结果由适配器真实事件回流。

  • 离线提示:offline 时显示内置提示(懒创建、role="status"、pointer-events: none 不阻挡控制栏交互),online 时移除;构造选项 offlineOverlay: false 可关闭(默认开启,仅影响展示,不影响事件与恢复协调);文案经字典 offline 槽位(默认「网络连接已断开,恢复后将自动重连」,setStrings 即时刷新);构造时已离线直接显示提示但不派发事件。
  • 事件:offlinechange(载荷 boolean,true = 当前离线),仅真实状态变迁时派发(重复事件与快速抖动去重);事件由 PlayerUI 本地事件总线交付,尚未配置 URL、适配器加载中或适配器为空时同样可观察;当前值经 isOffline() 查询;<mm-player> 桥接为同名 DOM CustomEvent。
  • 恢复协调(离线不调用 play,只记录恢复意图——离线时刻快照 playing/loading):online 后按公开事实决策——适配器自动重连退避中不干预(退避定时器自然成功,不重置 retryCount/maxAttempts、不产生重复连接),恢复意图保持到 reconnected、UI 恢复成功或用户操作,因此 online 后才到达的 reconnectfailed/FLV·Shaka 网络错误仍可恢复;网络类终态错误执行单次 reload()+play(),非网络错误不恢复。独立恢复代次使 pause/stop/destroy/load/switchAdapter/reload/play 可同步取消已经启动的旧恢复链;恢复中再次离线不调用 play,下一次 online 从已完成的 reload 阶段续接,不重复 reload。恢复失败被观察([PlayerUI] Network recovery failed:,无未处理拒绝)且不自动重试。destroy 对称解绑全部监听并清空 UI 事件总线。

| 令牌 | 默认值 | 作用 | | --- | --- | --- | | --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 + 全屏/画中画/截图(enterFullscreen / exitFullscreen / toggleFullscreen / isFullscreen、canPip / enterPip / exitPip / togglePip / isPipActive、screenshot、returnToLive)+ 清晰度/ABR(getQualityTracks / getCurrentQuality / setAbrEnabled / isAbrEnabled / switchQuality)+ 音轨(getAudioTracks / getCurrentAudioTrack / setAudioTrack)+ 字幕(getSubtitleTracks / getCurrentSubtitleTrack / setSubtitleTrack)+ 播放列表切曲(next / prev / jumpTo,2026-09-27 起)+ 网络(isOffline)+ 文案字典 property(strings,不做属性反射)+ Overlay/Slot API(appendOverlay/removeOverlay/replaceOverlay/clearOverlay/appendPoster/removePoster/appendLeftControl/appendRightControl/removeControl)+ getCore/getPlayerUI/getShadowRoot。事件含 offlinechange(detail 为布尔载荷)。

快捷键(元素聚焦时):Space/Enter 播放暂停、←/→ 快退快进、↑/↓ 音量、F 全屏、M 静音——全部经 CommandBus 派发,与控制栏按钮共享同一处理器。

事件:ready / loaded / destroy / play / pause / ended / error / timeupdate / buffering / progress / statechange / themechange / overlaychange / controlchange / slotchange / resize / reconnecting / reconnected / reconnectfailed / offlinechange / unsupported + 清晰度/音轨/字幕/播放列表事件(qualitychanged / adaptation / trackschanged / audiotrackchanged / subtitlechanged / subtitletracks / subtitleloadfailed / playlistchanged,playlistchanged 的 detail 为 { index, total })+ 画中画/截图结果事件(pipchange / screenshotresult / screenshotfailed)。

生命周期与资源释放

  • 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 + 公开查询 API(getQualityTracks / isAbrEnabled)+ dispatch。
  • 主题/控件显隐运行时切换不重建 Renderer、不重新 render/mount。
  • 全屏/画中画/截图等 DOM 命令由 PlayerUI 在 CommandBus 注册(Core 只注册媒体命令);destroy 时注销命令并解绑 PiP/全屏/截图监听,销毁后迟到的命令不再触碰已释放 UI。

常见错误与注意事项

  • container 必填且必须存在;选择器找不到会抛错。
  • volume 范围 0–1(默认 1)。
  • 低带宽模式 pause 实际执行 stop(断开节省资源);高带宽模式仅暂停。
  • ShadowRoot 宿主下样式经 adoptedStyleSheets 隔离;HTMLElement 宿主用 <style>。
  • destroy 后不得再调用实例方法。
  • 构造时传入 url 的初始加载与 autoplay 自动播放共用同一条 Promise 链:任一环节失败都会以 [PlayerUI] Initial load or autoplay failed: 记录到控制台并经 adapter error 事件进入错误态,不会产生 unhandledrejection;显式 await player.play() 的错误传播不受影响(自动播放仍受浏览器策略限制,通常需 muted: true)。

UMD / <script> 标签使用

UMD 产物(dist/player-ui.umd.js,全局名 player-ui)支持直接 script 加载,但两个外部依赖必须先以各自的全局名就位:

<script src="node_modules/@mm-custom/method/dist/method.umd.js"></script>              <!-- globalThis.method -->
<script src="node_modules/@mm-player/player-core/dist/player-core.umd.js"></script>    <!-- window['player-core'] -->
<script src="node_modules/@mm-player/player-ui/dist/player-ui.umd.js"></script>        <!-- window['player-ui'] -->

<script>
  const { PlayerUI } = window['player-ui'];
</script>

加载 player-ui.umd.js 时会立即注册 <mm-player> 自定义元素(模块作用域执行 customElements.define)。全局名在 vite 构建的 output.globals 中显式配置(player-core / method),与依赖包自身 UMD 构建的真实全局名一致;缺失任一依赖时,实例化(如 new PlayerUI(...))会在 new EventManager() 处抛 TypeError。

相关文档