@video-lab/protocol 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +80 -0
- package/dist/index.cjs +1909 -0
- package/dist/index.d.cts +11090 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +11090 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1849 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/configs.ts","../src/errors.ts","../src/observability.ts","../src/playback-context.ts","../src/events.ts","../src/methods.ts","../package.json","../src/version.ts","../src/envelope.ts","../src/locale.ts","../src/presets.ts"],"sourcesContent":["import { z } from 'zod'\n\n// ═══════════════════════════════════════════════════════════\n// DRM · v1.0 不支持(ADR-014)\n// ═══════════════════════════════════════════════════════════\n\n/**\n * v1.0 不做 DRM(ADR-014)。类型为 `never`,消费方传 drm 字段会直接编译报错,\n * 而不是运行时才发现没生效。\n *\n * v1.1+ 视需求评估 Widevine + FairPlay via HLS;PlayReady 永不做(依赖 DASH)。\n */\nexport type DrmConfig = never\n\n// ═══════════════════════════════════════════════════════════\n// 媒体源\n// ═══════════════════════════════════════════════════════════\n\n/** 媒体类型。`auto` = 从 URL 后缀推断 */\nexport const MediaTypeSchema = z.enum(['mp4', 'hls', 'flv', 'auto'])\nexport type MediaType = z.infer<typeof MediaTypeSchema>\n\n/** 已经完成归一化、可实际路由的媒体类型。`auto` 只允许停留在消费方输入侧。 */\nexport const SourceEntryTypeSchema = z.enum(['mp4', 'hls', 'flv'])\nexport type SourceEntryType = z.infer<typeof SourceEntryTypeSchema>\n\n/** 封面配置 */\nexport const PosterConfigSchema = z.union([\n z.string(),\n z.object({\n url: z.string(),\n loading: z.enum(['eager', 'lazy']).default('lazy').optional(),\n fit: z.enum(['cover', 'contain', 'fill']).default('cover').optional(),\n // 这里曾有 hideOnPlay / showOnEnded,四种接入方式一个都没实现,已按 ADR-038 删除。\n // 「封面播完要不要回来」是业务调度不是播放技术(同 ADR-025 的边界)——\n // 团队层监听 ended 自己把 <sentinel-poster> 的 visible 打回 true 即可,它是导出的。\n }),\n])\nexport type PosterConfig = z.infer<typeof PosterConfigSchema>\n\n/**\n * 暂停时盖在画面中央的静态图(ADR-043 / issue #121)。\n *\n * 形状**刻意与 {@link PosterConfigSchema} 对齐**(同为 `string | { url, fit, … }`),\n * 消费方不用学两套。\n *\n * **渲染在 iframe 内,与封面相反** —— 封面为了抢首帧挪到了宿主侧,暂停图不抢首帧\n *(暂停发生时 iframe 早已 ready),放宿主侧反而让静态 iframe 那条路拿不到,撞 cross-mode-parity。\n *\n * **边界(ADR-025 已判的那条线)**:静态图 + 可选关闭按钮进 SDK —— 可序列化、无调度逻辑,\n * 与 `poster` 同性质;**带倒计时 / 跳转 / 推荐列表的不进** —— 那是业务调度,归团队层。\n * inline 模式另有 `<sentinel-pause>` 的默认 slot(能塞任意节点),表达力更强但\n * 过不了 postMessage,所以三条 iframe 路只有本字段有效。\n */\nexport const PauseImageConfigSchema = z.union([\n z.string(),\n z.object({\n url: z.string(),\n /**\n * 默认 `contain` —— 暂停图通常是完整构图,`cover` 会把边裁掉。\n *\n * ⚠️ 默认值**由渲染侧兜**,不写成 zod 的 `.default()`:`.default(x).optional()`\n * 里 optional 会短路,默认值永远不生效(`PosterConfigSchema` 的 `loading` / `fit`\n * 就是这个形态,parse `{url}` 出来还是 `{url}`)。写在 schema 上会让消费方\n * 以为 parse 完就有值,反而误导。\n */\n fit: z.enum(['cover', 'contain', 'fill']).optional(),\n /** 给图加一个关闭按钮,用户点了本次播放不再显示。默认 `false` */\n closable: z.boolean().optional(),\n }),\n])\nexport type PauseImageConfig = z.infer<typeof PauseImageConfigSchema>\n\n/** 字幕轨 */\nexport const SubtitleTrackSchema = z.discriminatedUnion('mode', [\n z.object({\n mode: z.literal('url'),\n url: z.string(),\n locale: z.string(),\n label: z.string(),\n isDefault: z.boolean().optional(),\n }),\n z.object({\n mode: z.literal('content'),\n /**\n * 内联字幕正文,**必须是 WebVTT**(以 `WEBVTT` 行开头)。\n *\n * 字幕能力来自 xgplayer `TextTrack` 插件(wrap `xgplayer-subtitles@3.0.26`),\n * 那份产物里 `WEBVTT` 出现 1 次、`srt` **0 次** —— 引擎只认这一种格式。\n *\n * 这里曾并列一个必填的 `contentType: z.enum(['text/vtt','text/srt'])`,\n * 但 `'text/srt'` 从来不可能工作,而且没有任何一层读过这个字段。\n * 既然只剩一个合法值,它就不携带任何信息 —— 已按 ADR-039 删除。\n */\n content: z.string(),\n\n locale: z.string(),\n label: z.string(),\n isDefault: z.boolean().optional(),\n }),\n])\nexport type SubtitleTrack = z.infer<typeof SubtitleTrackSchema>\n\n/** HLS 协议专属配置 */\nexport const HlsConfigSchema = z.object({\n /**\n * LL-HLS 显式开启(ADR-024)。\n *\n * ⚠️ **所有浏览器都必须显式开**,否则不会启用低延迟模式。\n *\n * 这里从前写的是「非 Safari 必须显式开;Safari 走原生,自动检测 EXT-X-PART-INF」——\n * **那句话被 ADR-056 作废了**:HLS 内核现在按能力选,有 MSE 或 MMS 就走 hls.js,\n * 于是 Safari / iOS 也不再走原生,那条「平台白送的自动低延迟」路径没有了。\n * 后果是**静默的**:不显式开的话,原本靠它的直播只是退回普通 HLS 延迟,不报错。\n */\n lowLatencyMode: z.boolean().optional(),\n})\n/**\n * ⚠️ **这里只有一个字段,不是漏了 —— 别往里加 `cmcd`。**(ADR-076 · #472)\n *\n * 能力图原本要把 hls.js 内建的 CMCD(CTA-5004)透传进来,查下来它在本仓的内核上\n * **结构性不存在**:`xgplayer-hls.js` 硬编码引的是 hls.js 的 **light** 构建,\n * 而 light 构建的默认配置里 `cmcdController` 是 `undefined`,\n * `createController()` 因此根本不创建控制器 —— 传什么进来都不会发生任何事。\n *\n * ⚠️ **光看配置键会得出相反的结论**:light 构建里那个键**是有的**(值为 undefined)。\n * 判断上游有没有某个能力,要追到实现。这是本仓两轮里第二次撞上同一形状\n * (第一次是 ADR-075 查掉的 `bandwidth`)。\n *\n * 加一个不生效的字段就是幻影,而 ADR-075 刚花一整个模块拆掉一个。\n * `pnpm check:xgplayer` 的 F 档钉着这条,顺带在上游把这条路打开时**当场红**\n * (那时候红是好消息)。真要做,先读 ADR-076 §「什么时候重新评估」。\n */\nexport type HlsConfig = z.infer<typeof HlsConfigSchema>\n\n/** 媒体元信息 */\nexport const MediaMetadataSchema = z.object({\n title: z.string().optional(),\n description: z.string().optional(),\n duration: z.number().optional(),\n})\nexport type MediaMetadata = z.infer<typeof MediaMetadataSchema>\n\n/**\n * 多源里的单个候选源。`type` 必填——多源场景下不允许靠后缀猜。\n *\n * ⚠️ **这里曾经有 `quality` / `bitrate` / `codec`,已按 #276 删除**(判据见 ADR-039)。\n * 三个都是零消费点:清晰度档位 100% 来自 hls.js 的 `levels`(`readQualityLevels`),\n * 和消费方声明的候选源无关;选源规则只按 `type`(容器格式)走,这三个不参与任何一步。\n *\n * **别看着「候选源没有清晰度信息」好心加回来** —— `sources[]` 是「同一内容的不同封装」,\n * 不是「不同分辨率」。把它重新解释成清晰度候选是**新增能力**,要先回答「声明值和 manifest\n * 实际档位不符时听谁的」(ADR-046 在 `metadata.duration` 上答过同类问题:**听引擎的**),\n * 走 issue + ADR,不是往这里补一个字段。\n *\n * 它们此前没被发现,是因为 `contract-fields` 的数组递归有 bug(zod 3 的\n * `ZodArray._def.type` 装的是元素 schema 不是类型名),**数组元素字段整层从没进过清单**(#275 修)。\n */\nexport const SourceEntrySchema = z.object({\n url: z.string(),\n type: SourceEntryTypeSchema,\n})\nexport type SourceEntry = z.infer<typeof SourceEntrySchema>\n\n/**\n * 单源 / 多源对象共有的字段。\n *\n * ⚠️ 这里曾经有 `poster`,已按 ADR-049 删除 —— 零消费点,且**顶层 `PlayerConfig.poster`\n * 覆盖同一能力并且真的接到了 xgplayer**。墓碑见 {@link PlayerConfigSchema} 的 JSDoc。\n */\nconst sourceCommonShape = {\n /**\n * 直播标记。**SDK 不探测,完全靠消费方传。**\n *\n * 它管两件事:① 选源优先级(直播优先 FLV,点播优先 HLS,见 `source-router.ts`);\n * ② 播放器的 `isLive` —— iOS 后台切回的自愈策略、弹幕模式、进度条能不能拖。\n *\n * ⚠️ **写在 source 上,但生效范围是整个 player。** ② 是**构造期**配置,\n * 运行时改不了 —— 所以 `load()` 换源时新旧 `live` 不一致会抛\n * `E_METHOD_NOT_SUPPORTED`,要销毁重建(#220)。别被它挂在 source 上的位置误导。\n *\n * ⚠️ **漏填是静默失效**:直播不写 `live: true` → 走点播优先级选中 HLS →\n * 延迟从 ~2s 变 ~8s,不报错不告警(#217)。\n */\n live: z.boolean().optional(),\n hls: HlsConfigSchema.optional(),\n subtitles: z.array(SubtitleTrackSchema).optional(),\n metadata: MediaMetadataSchema.optional(),\n}\n\n/** 单源对象 */\nexport const SingleSourceObjectSchema = z.object({\n url: z.string(),\n /**\n * 默认 `auto`(从后缀推断)。\n *\n * ⚠️ 带 query 的 URL(如 `video.m3u8?token=xxx`)推断不出来,**必须显式传 `type`**,\n * 否则会走 MP4 处理路径。签名 URL 场景尤其注意(ADR-022)。\n */\n type: MediaTypeSchema.optional(),\n ...sourceCommonShape,\n})\nexport type SingleSourceObject = z.infer<typeof SingleSourceObjectSchema>\n\n/**\n * 多源对象。直播 FLV + HLS 双路兜底的标准形态。\n *\n * ⚠️ FLV 单源在 iOS / 微信 / UC / 夸克 下必然抛 `E_MEDIA_NOT_SUPPORTED`,\n * **FLV 永远和 HLS 一起放进 sources 数组**。\n */\nexport const MultiSourceObjectSchema = z.object({\n sources: z.array(SourceEntrySchema).min(1),\n ...sourceCommonShape,\n})\nexport type MultiSourceObject = z.infer<typeof MultiSourceObjectSchema>\n\n/**\n * 媒体源。三种写法:URL 字符串(最简)、单源对象、多源对象。\n *\n * **这是 wire schema——只含可 JSON 序列化的字段**。\n * ⛔ **`source.onBeforeRequest` 已废弃(ADR-057),下个 major 删除** ——\n * 它不在这里、也不在任何 Schema 里,而且 player-core 里从来没有消费点(#332)。\n * 认证走签名 URL + 长有效期(ADR-022)。见 {@link SourceRequestHook}。\n */\nexport const MediaSourceSchema = z.union([\n z.string(),\n MultiSourceObjectSchema,\n SingleSourceObjectSchema,\n])\nexport type MediaSource = z.infer<typeof MediaSourceSchema>\n\n/**\n * 请求信息(`onBeforeRequest` 的出入参)。\n *\n * @deprecated **ADR-057:随 `SourceRequestHook` 一起废弃,下个 major 删除。**\n * 它只被那个 hook 用,而那个 hook 从来没有消费点。\n * ⚠️ 这里的 `headers` 尤其别当真 —— **ADR-022 的决定正是移除 headers 认证**\n * (原生播放拦不到)。认证走签名 URL,有效期取够长(6–12 小时)。\n */\nexport interface RequestInfo {\n url: string\n headers?: Record<string, string>\n}\n\n/**\n * 请求拦截 hook。\n *\n * @deprecated **ADR-057:废弃,下个 major 删除。别在新代码里用。**\n *\n * **它从来没有工作过** —— `player-core` 里零消费点(没有 hls.js 的 loader / `xhrSetup`,\n * `source-normalize` 也不搬它),而且**这个字段不在任何 Schema 里**,\n * 所以传它连编译都过不了(`TS2353`)。见 #332。\n *\n * 废弃而不是补实现的核心理由:**函数搬不过 `postMessage`** ——\n * 五种接入方式里三种 iframe 的天然拿不到,补齐要在**分片加载热路径**上做跨窗口往返。\n * 一个永远只能在 5 种里的 2 种上工作的能力,过不了 cross-mode-parity 红线。\n *\n * **替代做法**:签名 URL 有效期取够长(ADR-022:6–12 小时,覆盖单次播放全过程)。\n * 这条今天就能用,零 SDK 代码,而且在五种接入方式上一致。\n *\n * 将来真出现「短有效期 + 长视频」的需求,要用**可序列化**的机制重新设计\n * (例如带刷新端点的 URL 模板),**不是把这个函数字段加回来**。\n */\nexport type SourceRequestHook = (req: RequestInfo) => Promise<RequestInfo>\n\n// ═══════════════════════════════════════════════════════════\n// 播放器配置\n// ═══════════════════════════════════════════════════════════\n\n/** i18n 配置 */\nexport const LocaleConfigSchema = z.union([\n z.string(),\n z.object({\n locale: z.string(),\n fallbackLocales: z.array(z.string()).optional(),\n /**\n * 翻译表。**SDK 不内置任何翻译,只提供注入机制** —— 覆盖层文案\n *(loading / error.* / retry / close)全部由消费方从这里传入。\n * 消费面用 `resolveLocaleMessages` 把它解析成扁平表,再把解析好的**字符串**\n * 传给覆盖层元素;缺 key 时原样返回 key(可见失败,不抛错)。\n *\n * **两个命名空间,同一张表**:\n * - 覆盖层:`loading` / `retry` / `close` / `error.<CODE>` —— **无内置翻译**\n * - 播放内核自带控件:`controls.*` —— 内置中 / 英,你传的会**覆盖**(#208 / ADR-051)\n *\n * 语言标签本身仍只有中 / 英两档(`zh-*` → 中文,其余 → 英文,见 player-core 的 `toXgLang`),\n * 但**那不再限制能翻成什么语言**:传 `locale:'vi-VN'` + `messages['vi-VN']['controls.play']`,\n * 控件就是越南语 —— 标签落在 `en` 那一档,而那一档的文案被换掉了。\n * `controls.*` 的 key 全集见 USER-GUIDE § 9.4;**表外的静默忽略**。\n *\n * ⚠️ 这段从前写着「控件不读 messages」—— 那是 #208 修掉的现状,别照着旧描述下结论。\n */\n messages: z.record(z.string(), z.record(z.string(), z.string())).optional(),\n // 这里曾有 direction: z.enum(['ltr','rtl','auto']),全仓零消费点,已按 ADR-039 删除。\n // 删它不等于承诺永不支持 RTL —— 消费方在自己的容器上写 dir=\"rtl\" 即可继承,\n // 覆盖层是普通 DOM。SDK 不需要这个字段参与(同 ADR-029:无真实需求驱动,不投机造能力)。\n }),\n])\nexport type LocaleConfig = z.infer<typeof LocaleConfigSchema>\n\n/**\n * 弹幕单条(streaming push 用)。见 ADR-028 / ARCHITECTURE § 8.3.7。\n *\n * 直播场景:业务从自己的数据源(WebSocket / 轮询,SDK 不参与)拿到一条,\n * 经 `pushDanmaku` 命令喂进来,到点即显(不绑视频时间)。\n */\nexport const DanmakuItemSchema = z.object({\n /** 唯一 id(去重 / 更新用) */\n id: z.string(),\n text: z.string(),\n /** 滚动 / 顶部固定 / 底部固定;默认 scroll */\n type: z.enum(['scroll', 'top', 'bottom']).optional(),\n /** hex / rgb 颜色;默认白 */\n color: z.string().optional(),\n /**\n * 这条弹幕出现的时间点,**毫秒**,视频时间轴(#275)。\n *\n * **不给 = 立刻出现** —— 所以 `pushDanmaku` 的现有行为一个字节都不变。\n * 只在 `mode: 'preload'` 下有调度意义:引擎按它排期,seek 时自己重排(实测:\n * seek 回 0 后整池重放,已放过的标记被清掉 —— 见 `danmaku-preload.spec.ts`)。\n *\n * ⚠️ **精度是 ±1 秒,不是精确到点。** 渲染引擎按区间匹配而非到点触发,\n * `start` 给的是**目标时刻**,实际出现可能提前或延后至多 1 秒。这是引擎行为,\n * SDK 已把窗口从它的默认 2 秒收到 1 秒(`DANMAKU_PRELOAD_WINDOW_MS`,那里写了为什么\n * 不能更小)。**要逐帧对齐的字幕型需求请用字幕轨(ADR-027),不要用弹幕。**\n *\n * ⚠️ **单位是毫秒,而契约里 `seek` / `startTime` / `currentTime` 都是秒。**\n * 这处不一致是**故意的**:这个字段直连 danmu.js 的同名同义字段,\n * 中间不做换算就不会有换算错误。代价是消费方要记住它和别的时间字段单位不同 ——\n * **这是真的代价,不是「其实没关系」**(见 `archive/docs/specs/danmaku-preload-mode.md`)。\n */\n start: z.number().nonnegative().optional(),\n})\nexport type DanmakuItem = z.infer<typeof DanmakuItemSchema>\n\n/**\n * 弹幕配置(PlayerConfig.danmaku,默认关闭)。见 ADR-028 / ARCHITECTURE § 8.3.7。\n *\n * **两种模式**:`streaming`(直播,`pushDanmaku` 逐条)/ `preload`(点播,挂载时整包给,\n * 引擎按每条的 `start` 排期,见 #275)。ADR-028 当初只做 streaming,preload 是它说的「后续 minor」。\n * **弹幕渲染是 SDK 技术**(xgplayer Danmu 引擎:轨道调度 / 防重叠);**数据源 / 发送框 / 过滤归团队层**\n * (ADR-025,同字幕菜单)——SDK 关掉 xgplayer 内置弹幕按钮/面板。\n */\nexport const DanmakuConfigSchema = z\n .object({\n enabled: z.boolean(),\n /** preload=一次性(点播,配合 `items`);streaming=pushDanmaku 逐条(直播)。默认 streaming */\n mode: z.enum(['preload', 'streaming']).optional(),\n /**\n * 预载弹幕池(**仅 `mode: 'preload'`**)。挂载时整包交给渲染引擎,\n * 由引擎按每条的 `start` 排期、seek 时重排 —— **SDK 不自己写调度器**\n * (写了就得自己处理 seek / 倍速 / 轨道冲突,那是渲染引擎的本职)。\n *\n * 想在运行时**换掉整包**目前不支持:那需要新增一个契约方法,要在五种接入方式上\n * 各实现一遍并永久维护。等有「一万条首屏太慢」这类实测需求再加,那是向后兼容的 minor。\n */\n items: z.array(DanmakuItemSchema).optional(),\n /** 显示配置(透传给渲染引擎) */\n display: z\n .object({\n /** 显示区域高度:顶部 / 半屏 / 全屏 */\n area: z.enum(['top', 'half', 'full']).optional(),\n opacity: z.number().min(0).max(1).optional(),\n /** 速度倍率 */\n speed: z.number().min(0.5).max(2).optional(),\n fontSize: z.number().min(12).max(48).optional(),\n /**\n * 最大轨道数(同时最多几行弹幕)。**与 `area` 互斥**,同传整份配置被拒。\n *\n * **是「最多」不是「精确」**:设得比播放器高度装得下的还多时,SDK 会把它钳到\n * 容器装得下的条数 —— 否则多出来的轨道整个落在可视区外,被分配到那些轨道上的\n * 弹幕会**静默看不见**(渲染引擎只有「精确 N 条」和「按高度算」两种模式,\n * 「取较小」得靠 SDK 在运行时补,见 player-core 的 `attachDanmakuLineClamp`)。\n *\n * 钳制跟随播放器尺寸变化(全屏 / 容器 resize)。\n */\n maxLines: z.number().int().positive().optional(),\n })\n .optional(),\n })\n /**\n * `items` 只在 `preload` 下成立 —— **这条必须在契约层拒绝,不能只在文档里劝阻**。\n *\n * 渲染引擎在直播模式(`isLive: true`)下**忽略每条弹幕的 `start`**:整包会一股脑\n * 全飘出来,而不是按时间轴排期。那是**静默的错** —— 不报错、不告警、画面上还挺热闹,\n * 消费方要盯着看很久才会发现「怎么全在开头」。\n *\n * 口径和 `posterFit` 非法值一致:**整份配置被拒、播放器根本不创建**,\n * 不是「当作没传照常播」(`poster-static-iframe.spec.ts` 钉着那条)。\n */\n .superRefine((cfg, ctx) => {\n if (cfg.items && cfg.items.length > 0 && cfg.mode !== 'preload') {\n ctx.addIssue({\n code: 'custom',\n path: ['items'],\n message:\n \"danmaku.items 只在 mode: 'preload' 下有效。当前 mode 是 \" +\n `${cfg.mode ?? 'streaming'}(默认)—— 渲染引擎在直播模式下会忽略每条的 start,` +\n '整包会一股脑全飘出来。要么设 mode: \"preload\",要么改用 pushDanmaku 逐条推。',\n })\n }\n // `maxLines` 与 `area` 在渲染引擎里**互斥**:给了轨道条数就按「条数 × 轨道高」定高,\n // 区域比例被整个忽略。两个都是消费方明确表达的意图(「别挡画面」/「别刷屏」),\n // 同传时只能有一个生效 —— **而越界不是边界情况**:`area: 'top'` 在 338px 高的\n // 播放器上是 112px,`maxLines: 5` 要 120px,当场超出。360px 以下的播放器很常见。\n // 所以拒绝,而不是选一个赢、把另一个的承诺悄悄作废(#279)。\n if (cfg.display?.maxLines !== undefined && cfg.display.area && cfg.display.area !== 'full') {\n ctx.addIssue({\n code: 'custom',\n path: ['display', 'maxLines'],\n message:\n `danmaku.display.maxLines 与 area: '${cfg.display.area}' 不能同时用 —— ` +\n '渲染引擎里两者互斥(给了轨道条数,区域比例就失效),同传只会有一个生效。' +\n \"要限制条数就去掉 area(或用 area: 'full'),要限制区域就去掉 maxLines。\",\n })\n }\n })\nexport type DanmakuConfig = z.infer<typeof DanmakuConfigSchema>\n\n/**\n * 场景预设。预设只是一组默认值,消费方传的单个字段永远覆盖预设\n * (见 {@link resolvePreset})。预设内容见 `presets.ts`。\n *\n * 定义在这里而不是 presets.ts,是为了让 PlayerConfig 能引用它而不产生循环依赖。\n */\nexport const PresetNameSchema = z.enum(['homepage-preview'])\nexport type PresetName = z.infer<typeof PresetNameSchema>\n\n/**\n * 播放器初始化配置。iframe 模式下握手成功后作为第一条 command 下发,\n * 所以必须整体可 JSON 序列化。\n *\n * ### 曾经存在、已删除的字段(别重新发明)\n *\n * 都是「声明了但全仓没人读」的幽灵字段,判据见 ADR-039。zod 默认 `strip`,\n * 消费方继续传不会报错也不会生效,TS 侧会编译失败 —— 这是期望的:\n * 让「传了没用」从静默失效变成显式失败。\n *\n * | 字段 | 删于 | 为什么 |\n * |---|---|---|\n * | `telemetry`(sessionId / userId / tags / mode) | ADR-037 | 零消费点。埋点由消费方监听契约事件自行上报,SDK 不代发(也发不了 —— 红线禁 HTTP 库)。**替代形态在输出侧,不是把这个字段加回来** —— 见下 |\n * | `crossOrigin` | ADR-037 | 零消费点,且语义与 CORS / 签名 URL(ADR-022)/ CDN 响应头三者耦合,接线前得先想清楚 |\n * | `consent`(analytics / thirdPartyCookies / personalization) | ADR-040 | 零消费点。`analytics` 曾门控 `stalled` 的 emit,但那个门控保护不了隐私(数据从没离开浏览器),只掐死 UI,已按 ADR-026「实现更正」移除;另两个子字段从来就没被读过。**合规过滤属于消费方的上报层** |\n * | `controls.minimal`(连同 `ControlsConfig` / `ControlsConfigSchema`) | ADR-045 | 零消费点 —— 传 `{minimal:true}` 与传 `{}` 效果完全一致(实现只看 `controls !== false`)。声明写的「遥控器 / TV 场景」在 v1.0 范围外,无任何需求在等它。它是那个 schema 唯一的字段,`controls` 一并从 union 收窄为纯布尔 |\n * | `source.poster`(单源 / 多源对象共有) | ADR-049 | 零消费点。`source-normalize` 认真地把它搬进 `NormalizedSource.poster`,而**全仓没有任何一处读那个属性** —— 从来没生效过。**顶层 `poster` 覆盖同一能力且真的接到了 xgplayer**,它也表达不了「每个候选源一张封面」(它挂在 source 对象上,不在 `SourceEntry` 上)。封面要跟着 `load()` 换,消费方改 `poster` prop 即可 |\n */\n/**\n * ⚠️ **别把 `telemetry` 加回来 —— 它的替代形态在输出侧**(ADR-074)。\n *\n * 上表删 `telemetry` 的论据是「纯粹的透传字段:SDK 收下来什么也不做」,那条论据\n * **仍然成立且永久成立**。ADR-074 加的 {@link PlaybackContext} 方向相反:\n * 它是 SDK **交出去**的、而且**只有 SDK 知道**的那几格(实际选中的内核 / 选中的候选源 /\n * 出错位置 / 当前档位 / 会话 id)。\n *\n * 判据是「消费方能不能在 SDK 外面自己拿到」。`userId` / `tags` 拿得到,所以**永久不进**;\n * 想加请先推翻 ADR-037,不要引用 ADR-074 当依据。\n */\nexport const PlayerConfigSchema = z.object({\n source: MediaSourceSchema,\n\n /** 场景预设。会被同名的显式字段覆盖 */\n preset: PresetNameSchema.optional(),\n\n // xgplayer 同名透传参数(ARCHITECTURE § 8.1 Layer B)\n autoplay: z.boolean().optional(),\n muted: z.boolean().optional(),\n loop: z.boolean().optional(),\n /** 初始倍速。运行时改倍速走 `setPlaybackRate` 命令 */\n playbackRate: z.number().min(0.25).max(4).optional(),\n volume: z.number().min(0).max(1).optional(),\n playsinline: z.boolean().optional(),\n preload: z.enum(['none', 'metadata', 'auto']).optional(),\n startTime: z.number().min(0).optional(),\n /**\n * 显示/隐藏 xgplayer 自带控件。**只有布尔**。\n *\n * 曾经是 `boolean | { minimal?: boolean }`,但 `minimal` 零消费点(传了和没传\n * 完全一样),已按 ADR-045 删除;它是那个对象里唯一的字段,只剩空对象的 union\n * 分支不携带任何信息,一并收窄。将来真要做 TV / 遥控器,重新放宽成\n * `boolean | { minimal?: boolean }` 是 **minor 不是 breaking**,不是单向门。\n */\n controls: z.boolean().optional(),\n /** 是否响应用户交互。首页预览卡片用 false,点击透传给外层卡片 */\n interactive: z.boolean().optional(),\n\n // 业务概念参数(Layer A)\n poster: PosterConfigSchema.optional(),\n /** 暂停时盖在画面中央的静态图。渲染在 iframe 内(与封面相反),见 ADR-043 */\n pauseImage: PauseImageConfigSchema.optional(),\n locale: LocaleConfigSchema.optional(),\n /** 弹幕配置(默认关闭,ADR-028)。渲染归 SDK,数据源/发送/过滤归团队层 */\n danmaku: DanmakuConfigSchema.optional(),\n /**\n * 打开 **iframe 通信层**日志(Penpal 的握手与消息收发)。\n *\n * 透传给 penpal 的 `debug` 选项,gate 的是它自己的 `console.log('[Penpal]', …)`。\n * 排查「握手超时 / 命令不达 / 事件丢失」时打开,能看到 SYN / SYN-ACK / ACK 的实际往返。\n *\n * ⚠️ **inline 模式无作用** —— 那条路没有 iframe、没有 Penpal。\n * xgplayer 3.0.26 的 `defaultConfig` 也没有任何 debug / logLevel 字段可映射,\n * 按 ADR-029「无真实需求驱动,不投机造能力」不为 inline 硬造一套日志。\n *\n * 实现细节:host 侧(frame-core)读到它后,除了给 `connectToChild` 开 debug,\n * 还会在 iframe URL 上追加 `?debug=1` —— 因为本字段是**经 Penpal 连接**送进 iframe 的,\n * 等它到达时连接早已建好,没法再回头配置那条连接。\n */\n debug: z.boolean().optional(),\n})\nexport type PlayerConfig = z.infer<typeof PlayerConfigSchema>\n","import { z } from 'zod'\n\n/**\n * 错误分类。player-core 把 xgplayer 的内部错误映射到这几类,\n * 映射规则见 `packages/protocol/CLAUDE.md § xgplayer 错误 → PlayerError 映射规则`。\n */\nexport const ErrorCategorySchema = z.enum([\n 'manifest',\n 'media',\n 'network',\n 'auth',\n 'env',\n 'autoplay',\n])\n\nexport type ErrorCategory = z.infer<typeof ErrorCategorySchema>\n\n/**\n * 全部错误码。命名规则 `E_<CATEGORY>_<SPECIFIC>`。\n * 权威来源:ARCHITECTURE.md § 8.6。\n *\n * **每一个码都必须有真实发射点**(ADR-034)。`player-core/tests/contract-coverage.test.ts`\n * 静态扫描全仓库生产代码强制这条 —— 加码不加实现,测试直接红。\n *\n * 原则:**能删不留**。一个我们暂时检测不到的错误码,留在契约里是谎言;\n * 而删掉之后将来能检测了再加回来,是向后兼容的 minor,代价极低。\n */\nexport const ErrorCodeSchema = z.enum([\n // manifest\n 'E_MANIFEST_PARSE',\n // media\n 'E_MEDIA_DECODE',\n 'E_MEDIA_ABORTED',\n 'E_MEDIA_NOT_SUPPORTED',\n 'E_SUBTITLE_LOAD_FAILED',\n // autoplay\n 'E_AUTOPLAY_BLOCKED',\n // network\n 'E_NETWORK',\n 'E_NETWORK_TIMEOUT',\n // auth\n 'E_AUTH_EXPIRED',\n // env(浏览器 / 宿主环境)\n 'E_ENV_CSP_BLOCKED',\n 'E_METHOD_NOT_SUPPORTED',\n 'E_DANMAKU_SEND_FAILED',\n 'E_INTERNAL',\n 'E_UNKNOWN',\n /**\n * 命令打进了**已销毁**的播放器(ADR-067 · #416)。\n *\n * 只有**消费方自己调过 `destroy()`** 才到得了这里 —— 组件卸载时句柄已置 `null`,\n * 那条路不下发命令(ADR-066 事实 ③)。所以它的含义很窄:\n * **「你自己拆了它,而这条命令没生效」**。\n *\n * `retryable: false` —— 重试不会好,只有重挂组件才行。\n *\n * ⚠️ **不要用 `E_METHOD_NOT_SUPPORTED` 代替它。** 那个码的含义是「这个方法在这里不支持」;\n * 合成一个,监控里就再也分不开「调了个不支持的东西」和「你自己把它拆了」——\n * 和 `E_FRAME_CRASHED` 当初拒绝并进 `E_HANDSHAKE_TIMEOUT` 是同一条理由。\n *\n * @platform 静态 iframe **结构性没有**:`embed-helper` 的导出面是\n * `on` / `onAny` / `off` / `destroy` / `createEmbedListener` —— **全是订阅侧**,\n * **一个命令都没有**(命令走 URL 参数),不存在「prop 变了 → 下发命令」这条路。\n * (这里曾漏写 `onAny` / `createEmbedListener`;实质结论不变,但枚举得准。)\n */\n 'E_PLAYER_DESTROYED',\n // iframe 传输层(host ↔ iframe)\n 'E_HANDSHAKE_TIMEOUT',\n 'E_HANDSHAKE_VERSION_MISMATCH',\n 'E_LOAD_FAILED',\n /**\n * iframe 握手**成功之后**失联(ADR-054 · #244)。\n *\n * 和 `E_HANDSHAKE_TIMEOUT` 的分别是**运维含义**,不是时序细节:\n * 前者是「一开始就没连上」(CDN 挂了 / URL 错了 / 版本目录不存在),\n * 后者是「连上之后死了」(渲染进程崩溃 / OOM / iframe 被回收)。\n * 合并成一个码,监控里就再也分不开这两类完全不同的故障。\n *\n * @platform iframe-only —— inline 模式永不产生(同上下文,崩了整页一起崩,\n * 宿主自己的 `window.onerror` 就看得见);静态 iframe 那条路\n * **结构性没有**(单向 postMessage,宿主侧没有 SDK 代码去检测)。\n * 两条已知差异在 `e2e/parity/events.parity.test.tsx` 显式固化。\n */\n 'E_FRAME_CRASHED',\n])\n\nexport type ErrorCode = z.infer<typeof ErrorCodeSchema>\n\n/**\n * 警告码。命名规则 `W_<CATEGORY>_<SPECIFIC>`。\n * 警告不中断播放,只通过 `compatwarning` 事件上抛给消费方。\n */\nexport const WarningCodeSchema = z.enum([\n 'W_BROWSER_INCOMPATIBLE',\n 'W_VERSION_MISMATCH',\n /**\n * 收到一条**过不了契约校验**的入站事件,已丢弃(ADR-087 · #728)。\n *\n * 只有 `react-frame` / `vue-frame` / 静态 iframe 三条**跨进程**的路会发 ——\n * inline 两面没有 wire,事件从 `player-core` 直接进消费方,不存在「不认识的东西」。\n *\n * ⚠️ **不复用 `W_VERSION_MISMATCH`。** 那条是「**两端版本号不同**」,这条是\n * 「**收到一条不认识的东西**」——两者可以独立发生:同版本的 iframe 也可能因为\n * 自身 bug 发出畸形 payload,而版本不同的两端也可能一条非法事件都没有。\n * 合成一个,监控里就再也分不开这两类完全不同的情况 ——\n * 和 `E_FRAME_CRASHED` 当初拒绝并进 `E_HANDSHAKE_TIMEOUT` 是同一条理由。\n *\n * ⚠️ **它最常见的成因不是「iframe 坏了」,是「iframe 比 host 新」。**\n * ADR-070 允许同 major 不同 minor 的两端通信(只发版本警告),而契约的\n * 「新增事件是向后兼容的 minor」在**严格校验**下不再自动成立:\n * 4.3.0 的 iframe 新增的事件,4.1.0 的 host 不认识,于是被丢。\n * 收到这条警告先看 `hostVersion` / `iframeVersion` 差多少,再怀疑 iframe 有 bug。\n *\n * ⚠️ **同一个事件名只报一次**(每个播放器实例内)。旧 host 遇到新 iframe 时,\n * 被拒的往往是 `timeupdate` 那种 4 次/秒的高频事件 —— 不去重的话告警本身就是噪音。\n */\n 'W_EVENT_REJECTED',\n])\n\nexport type WarningCode = z.infer<typeof WarningCodeSchema>\n\n/** 单个错误码的静态元数据 */\nexport interface ErrorMeta {\n category: ErrorCategory\n /**\n * 重试是否**有可能**成功。\n *\n * 这是给自动重连逻辑(ReconnectPlugin)看的信号,不是\"用户能不能点重试按钮\"。\n * 判断标准:同样的请求原样再发一次,有没有可能得到不同结果。\n */\n retryable: boolean\n}\n\n/**\n * 错误码 → 元数据。**唯一真相**:player-core 的 error-mapping、\n * frame-core 的 fallback、消费方的重试 UI 全部读这张表,不要各自硬编码。\n */\nexport const ERROR_META: Readonly<Record<ErrorCode, ErrorMeta>> = {\n E_MANIFEST_PARSE: { category: 'manifest', retryable: false },\n\n E_MEDIA_DECODE: { category: 'media', retryable: true },\n E_MEDIA_ABORTED: { category: 'media', retryable: true },\n E_MEDIA_NOT_SUPPORTED: { category: 'media', retryable: false },\n // retryable: true —— 字幕文件多半是临时网络问题,重新 setSubtitle 有可能成功。\n // 且字幕失败不中断播放,重试成本很低。\n E_SUBTITLE_LOAD_FAILED: { category: 'media', retryable: true },\n\n E_AUTOPLAY_BLOCKED: { category: 'autoplay', retryable: false },\n\n E_NETWORK: { category: 'network', retryable: true },\n E_NETWORK_TIMEOUT: { category: 'network', retryable: true },\n\n // retryable: false —— SDK 不做签名刷新(ADR-022),原样重试必然再次 401/403。\n // 需要业务方拿新的签名 URL 重新 load,那是新请求,不是重试。\n E_AUTH_EXPIRED: { category: 'auth', retryable: false },\n\n E_ENV_CSP_BLOCKED: { category: 'env', retryable: false },\n E_METHOD_NOT_SUPPORTED: { category: 'env', retryable: false },\n E_DANMAKU_SEND_FAILED: { category: 'env', retryable: true },\n E_INTERNAL: { category: 'env', retryable: false },\n E_UNKNOWN: { category: 'env', retryable: false },\n // category: 'env' —— 播放器这个「运行环境」被消费方自己拆了,不是网络也不是媒体\n //(同 E_FRAME_CRASHED / E_ENV_CSP_BLOCKED 的归类逻辑)。\n // retryable: false —— 重试打进的还是同一个已销毁的句柄,只有重挂组件才行。\n E_PLAYER_DESTROYED: { category: 'env', retryable: false },\n\n E_HANDSHAKE_TIMEOUT: { category: 'network', retryable: true },\n E_HANDSHAKE_VERSION_MISMATCH: { category: 'env', retryable: false },\n E_LOAD_FAILED: { category: 'network', retryable: true },\n\n // category: 'env' —— iframe 的运行环境没了,不是网络问题也不是媒体问题(同 E_ENV_CSP_BLOCKED)。\n //\n // retryable: false —— 按本字段的定义(同样的请求**原样**再发一次有没有可能得到不同结果):\n // iframe 已经死了,原样重发必然再失败。**重建 iframe 是新请求,不是重试** ——\n // 和上面 E_AUTH_EXPIRED 那条注释的判断逻辑逐字同构。\n //\n // ⚠️ 别把 retryable: false 读成「用户不该看到重试按钮」。本文件上面已明确区分过这两件事,\n // 而这恰恰是**该露重试入口**的场景 —— 伴随的 playablechange 会带 recoverable: false,\n // 那才是给 UI 的信号(ADR-043 / ADR-054)。\n E_FRAME_CRASHED: { category: 'env', retryable: false },\n}\n\n/**\n * `PlayerError.cause` 的形状。**只用于 debug**,不要在业务逻辑里依赖它 ——\n * 业务判断用 `code` / `category` / `retryable`。\n *\n * **为什么是固定结构而不是 `unknown`(#270)**:错误对象要跨 iframe 传输,\n * 而 `postMessage` 用的是**结构化克隆**。塞进来的原始错误常常挂着 DOM 对象\n * (xgplayer 的错误就带着 `MediaError`),结构化克隆遇到它**直接抛 `DataCloneError`**,\n * 整条 envelope 发不出去 —— 消费方拿不到真正的错误码。\n *\n * ⚠️ **这里曾经写着「必须可 JSON 序列化」,规矩是对的,前提是错的**:\n * `JSON.stringify` 遇到不可序列化的东西**静默丢弃**,`structuredClone` **抛异常**,\n * 两者对同一件事的处理正好相反。契约按 JSON 语义写、传输按克隆语义跑,于是没人执行。\n * 现在字段全是原始类型,**结构化克隆不可能再抛**。\n *\n * **不带 `stack`**:跨 iframe 的 stack 指向 iframe 内部的 bundle 文件,宿主侧看到的是\n * 一串陌生路径,排查价值低而体积不小(embed-app 的全局上报已用 `source`/`lineno` 提供等价信息)。\n */\nexport const ErrorCauseSchema = z\n .object({\n /** 原始错误的 `name`;取不到时是 `'Unknown'` */\n name: z.string(),\n message: z.string(),\n /** `MediaError.code`(1–4)等结构化码 —— 错误分类本来就是靠它做的,排查时缺它答不了「decode 还是 src_not_supported」 */\n code: z.number().optional(),\n /** HTTP 状态码,区分 401 / 403 用 */\n status: z.number().optional(),\n })\n /**\n * **额外的原始值字段原样留着。** `cause` 不只装「被抓到的异常」——\n * frame-core 的降级路径就往里放 `{ hostVersion, iframeVersion }` 这类**结构化诊断数据**\n * (「版本不兼容」时,排查的第一个问题就是「谁没升」)。\n *\n * 只收 string / number / boolean:**约束是\"能过结构化克隆\",不是\"只准有四个字段\"**。\n * 嵌套对象一律不收 —— 那正是不可克隆的东西藏身的地方。\n */\n .catchall(z.union([z.string(), z.number(), z.boolean()]))\n\nexport type ErrorCause = z.infer<typeof ErrorCauseSchema>\n\n/** `String(x)` 的截断上限 —— cause 是旁路信息,不该把 envelope 撑大 */\nconst CAUSE_MESSAGE_MAX = 500\n\nfunction readNumber(source: Record<string, unknown>, key: string): number | undefined {\n const v = source[key]\n return typeof v === 'number' && Number.isFinite(v) ? v : undefined\n}\n\n/**\n * 把任意 `cause` 归一成 wire-safe 的 {@link ErrorCause}。\n *\n * **幂等**:已经是这个形状的再过一遍还是它自己 —— 靠的是下面这段通用读取本身,\n * 不是靠一条「已经合规就原样返回」的快路径。**那条快路径试过,是错的**:\n * 它会把已经合规的对象整个放行,于是 `message` 的截断对它不生效\n * ——而 `new Error('x'.repeat(5000))` 恰好就能通过 schema 校验(`name`/`message`\n * 在 Error 的原型链上,zod 读得到),5000 字符原样进了 envelope。\n * `makePlayerError` 会被嵌套调用(frame-core 解包出 playerError 再重包),幂等是必须的,\n * 但它得由「每次都真的走一遍归一」来保证,不是由「认出来就跳过」。\n *\n * 取值优先级刻意和 `player-core/src/error-mapping.ts` 的分类逻辑对齐:\n * 那边靠 `mediaError.code` 分类,这边就把同一个 code 留下来,否则排查时人要去猜。\n */\nexport function serializeCause(cause: unknown): ErrorCause {\n if (cause === null || typeof cause !== 'object') {\n return { name: 'Unknown', message: String(cause).slice(0, CAUSE_MESSAGE_MAX) }\n }\n\n const obj = cause as Record<string, unknown>\n const media = (obj.mediaError ?? undefined) as Record<string, unknown> | undefined\n\n const name =\n typeof obj.name === 'string'\n ? obj.name\n : media\n ? 'MediaError'\n : typeof obj.errorType === 'string'\n ? obj.errorType\n : 'Unknown'\n\n const rawMessage =\n typeof obj.message === 'string'\n ? obj.message\n : typeof media?.message === 'string'\n ? media.message\n : String(cause)\n\n const code = readNumber(obj, 'code') ?? (media ? readNumber(media, 'code') : undefined)\n const status = readNumber(obj, 'status') ?? readNumber(obj, 'httpCode')\n\n // 自带的原始值字段先铺下去,派生字段后写 —— 派生的说了算,不受输入里的同名字段影响\n const extras: Record<string, string | number | boolean> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') extras[k] = v\n }\n\n return {\n ...extras,\n name,\n message: rawMessage.slice(0, CAUSE_MESSAGE_MAX),\n ...(code === undefined ? {} : { code }),\n ...(status === undefined ? {} : { status }),\n }\n}\n\n/**\n * 错误对象。**跨 iframe 传输,所以每个字段都必须能过结构化克隆** ——\n * `cause` 由 {@link makePlayerError} 统一归一成 {@link ErrorCause},见 #270。\n */\nexport const PlayerErrorSchema = z.object({\n code: ErrorCodeSchema,\n message: z.string(),\n retryable: z.boolean(),\n category: ErrorCategorySchema,\n cause: ErrorCauseSchema.optional(),\n})\n\nexport type PlayerError = z.infer<typeof PlayerErrorSchema>\n\n/**\n * 构造 PlayerError。category 和 retryable 一律从 {@link ERROR_META} 推出来,\n * 不接受调用方传入——避免同一个 code 在不同包里被标成不同的 retryable。\n *\n * @example\n * makePlayerError('E_NETWORK', '拉流失败', originalError)\n * // → { code: 'E_NETWORK', message: '拉流失败', category: 'network', retryable: true, cause: ... }\n */\n/**\n * `E_PLAYER_DESTROYED` 的统一文案。\n *\n * **三个包各自发射它**(`frame-core` 的 `send()` 闸、`react` / `vue` 的 prop effect)——\n * 文案分三份写就是三份会漂的副本,而这条需求的标题恰恰是「五面说同一句话」\n *(`archive/docs/specs/destroyed-player-command-signal.md`)。\n *\n * 契约只钉 `code`,不钉 `message`;放在这里是**为了不漂**,不是把文案升级成契约。\n */\nexport const PLAYER_DESTROYED_MESSAGE = '播放器已销毁,这条命令没有生效 —— 重挂组件才能继续'\n\nexport function makePlayerError(code: ErrorCode, message: string, cause?: unknown): PlayerError {\n const meta = ERROR_META[code]\n return {\n code,\n message,\n category: meta.category,\n retryable: meta.retryable,\n ...(cause === undefined ? {} : { cause: serializeCause(cause) }),\n }\n}\n","import { z } from 'zod'\n\n/**\n * 观测信号(ADR-075 · #490)。**xgplayer 的 `DefaultPreset` 一直在算,而 `player-core`\n * 从来没读过** —— `Stats` / `XGLogger` / `FpsDetect` 三个插件每次播放都在工作,\n * 而 `player-core/src` 对它们的引用数是 0。\n *\n * ─── 为什么归一函数住这里,而不是各消费面各写一份 ───\n *\n * 上游 payload **带原始 DOM 对象**(`player.js:1449` 的 `emitUserAction` 把整个\n * `event` 塞进去)。它过不了 iframe 的 `structuredClone`,Zod 也接不住 ——\n * 照搬会让 **iframe 两条通道当场炸而 inline 两条不炸**,那才是真正的 parity 事故。\n *\n * 所以归一是**纯函数**,四个消费面共用一份。形状逐字沿用 ADR-074 的\n * `redactSourceUrl()`:同样是「上游给的东西不能直接进契约」。\n *\n * ⚠️ **本模块只收上游真的会发的信号。** 原计划的第四条 `bandwidth`\n * (`DOWNLOAD_SPEED_CHANGE`)**被查掉了** —— `TestSpeed` 的 `defaultConfig` 是\n * `openSpeed: false` + `url: ''`,`afterCreate` 直接 return,而剩下唯一入口\n * `real_time_speed` **全 node_modules 没有人发**。进契约就是第二个\n * 「只在注释里存在的 `healthreport`」(#391 抓到的那种)。\n *\n * @see docs/adr/ADR-075-preset-plugin-attribution.md\n */\n\n/**\n * 首帧可见耗时(ADR-075 决策②)。来自 `XGLogger` 的 `xglog` / `type: 'firstFrame'`。\n *\n * ⚠️ **和 `pnpm test:e2e:kpi`(#148)量的不是一件事,两者不可互换。**\n * 那条 KPI 量的是**端到端**(含页面加载 / SDK 初始化),本字段是**播放器内部**口径\n * (从内核开始加载到第一帧可见)。**决定是不统一** —— 统一意味着其中一个要放弃\n * 自己的用途,而两个用途都真实存在。\n */\nexport const FirstFramePayloadSchema = z.object({\n /** 首帧可见耗时,毫秒。上游字段名就叫 `fvt`(first video time) */\n fvt: z.number(),\n})\nexport type FirstFramePayload = z.infer<typeof FirstFramePayloadSchema>\n\n/**\n * 画面冻结(ADR-075 决策②)。来自 `FpsDetect` 的 `FPS_STUCK`。\n *\n * ⚠️ **它不叫 `framedrop`,因为它不是掉帧率。** 上游的触发判据是「连续\n * `stuckCount`(默认 3)个 tick 解码帧数 ≤ `reportFrame`(默认 0),**且缓冲够、\n * 没暂停、页面没隐藏**」—— 检出的是**画面冻住**。payload 里确实带\n * `droppedVideoFrames`,但那是附带数据,不是触发判据。\n *\n * ⚠️ **和 `stalled` 不重复。** `stalled` 是**缓冲驱动**的等待,而本信号的前提\n * 恰恰是**缓冲是够的** —— 缓冲够却出不了新帧,指向的是解码侧而不是网络侧。\n *\n * ⚠️ **仅 PC。** `FpsDetect` 在 `presets/default.js` 的 `case 'pc'` 分支才装载,\n * 手机上**结构性不触发**。这不违反 cross-mode-parity(ADR-012)—— 五种接入方式在\n * 同一台设备上表现一致,差异沿的是**平台轴**不是模式轴。但形状正是 ADR-065 那个\n * 陷阱(「这条通道上没有」被读成「这个东西没用」),所以登记在 `upstream-gap-registry`。\n */\nexport const FrameFreezePayloadSchema = z.object({\n /** 冻住了多久(毫秒)= 各次采样 `checkInterval` 之和 */\n durationMs: z.number(),\n /** 累计丢帧数(`droppedVideoFrames`,取最后一次采样)。**它不是本事件的触发原因** */\n droppedFrames: z.number(),\n /** 累计解码帧数(`totalVideoFrames`,取最后一次采样) */\n totalFrames: z.number(),\n})\nexport type FrameFreezePayload = z.infer<typeof FrameFreezePayloadSchema>\n\n/**\n * 进契约的用户动作白名单(ADR-075 决策③)。\n *\n * ─── 判据是 ADR-039 的「零消费」那一条 ───\n *\n * **消费方在 SDK 外面自己拿不到的才进。** 按这条:\n *\n * - **进**:发生在 SDK 自己控件上的动作。契约里虽然有 `play` / `pause` /\n * `volumechange` / `seeking` / `qualitychange`,但它们**回答不了「是谁发起的」**\n * —— 用户点的还是代码调的,而上报侧要区分的正是这个。\n * - **不进**:`click` / `dragstart` / `dragend` / `fragment_focus` —— 这些是进度条上的\n * **指针级交互**,消费方在自己的容器上监听就有;而且拖动本身还会再发一条 `seek`,\n * 收进来等于同一个动作记两遍。\n *\n * ⚠️ **`switch_cssfullscreen` 和 `switch_css_fullscreen` 两个都要。** 上游自己就有两种拼法\n * (`cssFullScreen` 插件用前者,`keyboard` 插件用后者),**这不是笔误** ——\n * 漏掉一个的表现是「用快捷键切网页全屏收不到事件,点按钮能收到」,而那种差异没人会去查。\n *\n * ⚠️ **护栏保证不了「该进的都进了」。** `check:inventory` 能验「名单里的在上游真实存在」\n * 和「上游删了会红」,但上游**新增**一个内部动作时,这张名单不会自己长出来 ——\n * 那是静默的,已记进 ADR-075 的代价栏。\n */\nexport const USER_ACTION_ALLOWLIST = [\n 'switch_play_pause',\n 'switch_fullscreen',\n 'switch_cssfullscreen',\n 'switch_css_fullscreen',\n 'change_definition',\n 'change_rate',\n 'change_volume',\n 'change_muted',\n 'change_pip',\n 'seek',\n 'rotate',\n 'shot',\n 'download',\n 'switch_danmu',\n 'error_retry',\n] as const\n\n/**\n * 用户动作(ADR-075 决策③)。来自 `Stats` 收的 `USER_ACTION`,经白名单过滤 + 归一。\n *\n * ⚠️ **可见性依赖 `controls`。** 主要发出方是 xgplayer 自己的控件插件,而本仓\n * `controls: config.controls !== false`(`create-player.ts`)。消费方传\n * `controls: false` 时绝大多数动作不再发出 —— 这是**配置相关的差异**,不是幻影。\n */\nexport const UserActionPayloadSchema = z.object({\n /** 白名单里的动作名。见 {@link USER_ACTION_ALLOWLIST} */\n action: z.enum(USER_ACTION_ALLOWLIST),\n /** 哪个插件发起的(上游 `pluginName`);取不到时为 `'player'` */\n source: z.string(),\n /** 变化前的值。**只保留原始类型** —— 见 {@link normalizeUserAction} */\n from: z.union([z.string(), z.number(), z.boolean()]).nullable(),\n /** 变化后的值。同 `from` */\n to: z.union([z.string(), z.number(), z.boolean()]).nullable(),\n})\nexport type UserActionPayload = z.infer<typeof UserActionPayloadSchema>\n\nconst ALLOWED = new Set<string>(USER_ACTION_ALLOWLIST)\n\n/** 只让原始类型过去。**其它一律 `null`** —— 挡的就是 DOM 对象 */\nfunction primitive(v: unknown): string | number | boolean | null {\n const t = typeof v\n return t === 'string' || t === 'number' || t === 'boolean'\n ? (v as string | number | boolean)\n : null\n}\n\n/**\n * 把上游 `USER_ACTION` 的原始 payload 归一成契约 payload。\n * **不在白名单里的返回 `null`**(调用方据此不发事件)。\n *\n * ─── 这个函数存在的全部理由 ───\n *\n * 上游发出来的东西长这样(`es/player.js:1449`):\n *\n * ```js\n * this.emit(USER_ACTION, { eventType, action, currentTime, duration, ended, event, ...params })\n * ```\n *\n * 那个 `event` 是**原始 DOM 事件对象**。它:\n * - 过不了 iframe 的 `structuredClone`(postMessage 直接抛 DataCloneError)\n * - 过不了 Zod\n * - 而 **inline 两条路不过这两关**,所以照搬的表现是「iframe 炸、inline 不炸」\n *\n * 所以本函数是**白名单式**的:只挑四个字段出来,其余一律丢掉。\n * 反过来写(黑名单式地 `delete raw.event`)在上游哪天多塞一个对象时会静默漏过去。\n *\n * `from` / `to` 也**不用 `z.unknown()`** —— 那等于给 DOM 对象留了一条后门。\n * 限死原始类型,拿不准的一律 `null`。\n */\nexport function normalizeUserAction(raw: unknown): UserActionPayload | null {\n if (typeof raw !== 'object' || raw === null) return null\n const r = raw as Record<string, unknown>\n\n const action = r.action\n if (typeof action !== 'string' || !ALLOWED.has(action)) return null\n\n // `props: [{ prop, from, to }]`;上游对单个 prop 会自动包成数组(`emitUserAction` 里)\n const props = Array.isArray(r.props) ? r.props : []\n const first = (props[0] ?? null) as Record<string, unknown> | null\n\n return {\n action: action as UserActionPayload['action'],\n source: typeof r.pluginName === 'string' ? r.pluginName : 'player',\n from: primitive(first?.from),\n to: primitive(first?.to),\n }\n}\n\n/**\n * 把上游 `FPS_STUCK` 的原始 payload(**一个采样数组**)归一成契约 payload。\n * 空数组返回 `null`。\n *\n * 上游每个采样长这样(`es/plugins/fpsDetect/index.js`):\n * `{ currentTime, buffers, curDecodedFrames, totalVideoFrames, droppedVideoFrames, checkInterval }`。\n *\n * `buffers` 是缓冲区间明细,**不进契约** —— 那是 `bufferhealth`(ADR-071)的地盘,\n * 在这里再报一份就是第二个描述缓冲的入口。\n */\nexport function normalizeFrameFreeze(raw: unknown): FrameFreezePayload | null {\n if (!Array.isArray(raw) || raw.length === 0) return null\n const samples = raw as Array<Record<string, unknown>>\n const last = samples[samples.length - 1] as Record<string, unknown>\n\n const num = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) ? v : 0)\n\n return {\n durationMs: samples.reduce((sum, s) => sum + num(s.checkInterval), 0),\n droppedFrames: num(last.droppedVideoFrames),\n totalFrames: num(last.totalVideoFrames),\n }\n}\n","import { z } from 'zod'\nimport { SourceEntryTypeSchema } from './configs'\n\n/**\n * 播放内核 —— **实际选中的那个**,不是消费方声明的。\n *\n * 消费方传 `type: 'auto'` 时压根不知道结果:ADR-056 之后 HLS 按**能力**选\n * (有 MSE 或 MMS 就走 hls.js,两者都没有才回原生),而那台设备的 UA 和别的没区别。\n *\n * **住在契约层,由 player-core 的 `Kernel` 类型引用它**,不是各写一份 ——\n * 两份枚举描述同一件事,漂了没人查得出来(同 ADR-062 ③ 拒绝为 `kernelhealth`\n * 另起一套「重连原因」枚举的理由)。\n */\nexport const PlaybackKernelSchema = z.enum(['native', 'hls.js', 'flv.js'])\nexport type PlaybackKernel = z.infer<typeof PlaybackKernelSchema>\n\n/** 实际路由的直播语义。它来自归一化后的 source,而不是宿主上报时附带的 tag。 */\nexport const StreamKindSchema = z.enum(['live', 'vod'])\nexport type StreamKind = z.infer<typeof StreamKindSchema>\n\n/** 支持矩阵使用的最小运行环境分桶;禁止加入 UA、版本或任意业务字段。 */\nexport const PlaybackRuntimeSchema = z.object({\n platform: z.enum(['ios', 'android', 'desktop', 'unknown']),\n browser: z.enum(['webkit', 'gecko', 'chromium', 'embedded', 'unknown']),\n mse: z.enum(['standard', 'managed', 'unavailable']),\n})\nexport type PlaybackRuntime = z.infer<typeof PlaybackRuntimeSchema>\n\nconst SourceRouteBaseSchema = z.object({\n /** 与成功 route 后续 PlaybackContext 相同的 SDK 会话标识。 */\n sessionId: z.string(),\n /** 本次供给的候选类型集合;不含地址。 */\n candidateTypes: z.array(SourceEntryTypeSchema).min(1),\n streamKind: StreamKindSchema,\n runtime: PlaybackRuntimeSchema,\n})\n\n/** 构造前选源的事实。它与 QoE / 首帧是不同的证据链。 */\nexport const SourceRoutePayloadSchema = z.discriminatedUnion('outcome', [\n SourceRouteBaseSchema.extend({\n outcome: z.literal('selected'),\n mediaType: SourceEntryTypeSchema,\n kernel: PlaybackKernelSchema,\n reason: z.enum(['required_hls', 'live_preference', 'vod_preference', 'native_mp4_fallback']),\n }).strict(),\n SourceRouteBaseSchema.extend({\n outcome: z.literal('unsupported'),\n mediaType: z.null(),\n kernel: z.null(),\n reason: z.literal('no_supported_candidate'),\n errorCode: z.literal('E_MEDIA_NOT_SUPPORTED'),\n }).strict(),\n])\nexport type SourceRoutePayload = z.infer<typeof SourceRoutePayloadSchema>\n\n/**\n * 播放上下文(ADR-074 · #480)。**消费方写上报适配器时,自己在 SDK 外面拿不到的那几格。**\n *\n * ─── 它补的是哪一句话 ─────────────────────────────\n *\n * `error` 的 payload 是 `{ code, message, retryable, category, cause? }` ——\n * **没有一个字段回答「出错时播到哪了 / 用的哪个内核 / 这是哪一次播放」**。\n * 消费方要补齐只有一条路:自己监听 `timeupdate` / `qualitychange` / `ready`\n * 维护一份镜像,在 `error` 到达时读出来。**那正是 ADR-043 花一整条 ADR 消灭掉的形态。**\n *\n * ─── 字段判据:消费方能不能在 SDK 外面自己拿到 ───\n *\n * 拿得到的一律不进。按这条,三个候选**当场出局**:`mode`(消费方自己 import 了哪个包)、\n * `contractVersion`(直接 `import { CONTRACT_VERSION }`)、`live`(**是消费方自己传进来的**)。\n *\n * ⚠️ **`userId` / `tags` 永久不进,且不得引用本类型当加回它们的依据。** 它们逐字命中\n * ADR-037 的判据(「纯粹的透传字段:SDK 收下来什么也不做」)。本类型和 ADR-037 删掉的\n * `telemetry` **方向相反** —— 那个是输入侧透传,这个是输出侧、只有 SDK 知道的。\n *\n * ⚠️ **不要叫它 envelope。** `envelope.ts` 是 host ↔ iframe 的消息外壳,\n * 两者在 wire 上是**里外两层**关系。\n */\nexport const PlaybackContextSchema = z.object({\n /**\n * 本次播放的 id。**由 SDK 生成,消费方不可写** —— 一旦可写,它就退回成 ADR-037\n * 杀掉的那个纯透传字段,而这条边界是整个 ADR-074 成立的前提。\n *\n * `load()` 换源 = **新会话**(ADR-074 明确不决定的事之一,按此推进):CMCD 的 `cid`\n * 跟着内容走,换了内容还共用一个 sid 会让 CDN 侧的聚合失真。\n */\n sessionId: z.string(),\n /** 实际选中的内核。见 {@link PlaybackKernelSchema} */\n kernel: PlaybackKernelSchema,\n /**\n * SourceRouter 实际选中的媒体类型,不从 URL pathname 推断。\n *\n * optional 是跨 minor 的 wire 兼容边界:旧 iframe producer 不会携带它;当前\n * player-core 始终携带。消费方缺失时只能按 unknown 处理,不能从 URL 猜测。\n */\n mediaType: SourceEntryTypeSchema.optional(),\n /** 实际参与路由的直播/点播语义;旧 producer 缺失时按 unknown 处理。 */\n streamKind: StreamKindSchema.optional(),\n /** 闭集运行环境;旧 producer 缺失时按 unknown 处理。 */\n runtime: PlaybackRuntimeSchema.optional(),\n /**\n * 取这个快照那一刻的播放位置(秒)。\n *\n * ⚠️ **它不参与 `contextchange` 的触发判据** —— 每 250ms 都在变,按值变化发\n * 就等于复制一条 `timeupdate`。要「出错那一刻」的位置请在 `error` 回调里\n * 同步调 `getPlaybackContext()`,不要读 `contextchange` 缓存下来的值。\n */\n position: z.number(),\n /**\n * 当前清晰度档位(= `ready.quality[].level` 的索引);单码率源或档位未知时为 `null`。\n *\n * `null` 不是错误态 —— MP4 / 单档 HLS 结构上就没有多个 level。\n */\n qualityLevel: z.number().nullable(),\n /**\n * 实际在播的那个候选源的 `origin`(如 `https://cdn.example.com`)。\n *\n * 多源时消费方**结构性拿不到**这个信息(选取规则在 SDK 内,ADR-017)。\n */\n srcOrigin: z.string(),\n /**\n * 实际在播的那个候选源的 `pathname`(如 `/live/room-42/master.m3u8`)。\n *\n * ⚠️ **和 `srcOrigin` 刻意分成两个字段,不合成一个字符串**:拼回去的第一件事\n * 就是有人拿它当 URL 用,然后发现少了 query 于是「顺手补上」——\n * 而 query 里装的正是签名凭据。分开之后,拼接这个动作必须由消费方显式做一次。\n */\n srcPath: z.string(),\n})\nexport type PlaybackContext = z.infer<typeof PlaybackContextSchema>\n\n/**\n * 把源地址脱敏成 `origin` + `pathname` 两段。**query string 一个字符都不带出去。**\n *\n * 签名参数**全在 query 里**(ADR-022 认证仅用签名 URL),原样上报等于把凭据交给\n * 第三方 SaaS —— 而「短期有效」不等于「可以外发」。\n *\n * ⚠️ **脱敏发生在 SDK 内,不是「建议消费方自己脱敏」。** 后者等于先把凭据交出去\n * 再请人删掉,而且五个消费面各写一遍 = 五份会漂的副本。\n *\n * ⚠️ **不做正则。** 用 `URL` 的两个属性 —— 正则会漏 `;` 参数、`#` 片段这类形态,\n * 而漏掉的那部分正好是要挡的东西。\n *\n * 解析不了的地址(相对路径 / blob: / 空串)返回两个空串,**不抛** ——\n * 上下文是旁路信息,不该让一个奇怪的 URL 把播放搞崩。\n */\nexport function redactSourceUrl(url: string): { srcOrigin: string; srcPath: string } {\n try {\n const u = new URL(url)\n return { srcOrigin: u.origin, srcPath: u.pathname }\n } catch {\n return { srcOrigin: '', srcPath: '' }\n }\n}\n","import { z } from 'zod'\nimport {\n ErrorCodeSchema,\n makePlayerError,\n type PlayerError,\n PlayerErrorSchema,\n WarningCodeSchema,\n} from './errors'\nimport {\n FirstFramePayloadSchema,\n FrameFreezePayloadSchema,\n UserActionPayloadSchema,\n} from './observability'\nimport { PlaybackContextSchema, SourceRoutePayloadSchema } from './playback-context'\n\n/**\n * 清晰度档位。`level` 是索引,传给 `setQuality` 命令用。\n */\nexport const QualityLevelSchema = z.object({\n level: z.number(),\n label: z.string().optional(),\n height: z.number().optional(),\n bitrate: z.number().optional(),\n})\nexport type QualityLevel = z.infer<typeof QualityLevelSchema>\n\n/**\n * 可用字幕轨(player 加载后暴露给消费方)。`id` 是索引,传给 `setSubtitle` 命令用。\n *\n * 和输入侧的 {@link SubtitleTrack}(url/content 两种 `mode`)分开:那个是**消费方喂进来**的\n * 原始字幕描述,这个是 player **加载后回报**的、可切换的轨道清单(同 QualityLevel 之于 quality)。\n */\nexport const SubtitleTrackInfoSchema = z.object({\n /** 索引(= source.subtitles 数组下标),`setSubtitle({ id })` 用它引用轨道 */\n id: z.number(),\n /** BCP-47,如 `'en'` / `'zh-CN'` / `'th'` */\n locale: z.string(),\n /** 展示名,如 `'English'` / `'中文'` */\n label: z.string(),\n})\nexport type SubtitleTrackInfo = z.infer<typeof SubtitleTrackInfoSchema>\n\n/**\n * 「现在能不能正常出画面」的原因枚举({@link PlayerEventSchema} 的 `playablechange`)。\n *\n * 优先级(高的压低的,同时命中时报最严重的那个):\n * `error` > `frame_disconnected` > `autoplay_blocked` > `reconnecting` > `stalled`\n * > `buffering` > `initializing` > `degraded` > `ok`\n *\n * **命名**:枚举值用 `snake_case`,与事件名(全小写连写)是两套命名空间 ——\n * 事件名对齐 HTML5 媒体事件,枚举值是 payload 里的数据,`autoplay_blocked`\n * 比 `autoplayblocked` 好读。既有的 `phase: 'start' | 'end'` 是单词故看不出区别。\n */\nexport const PlayableReasonSchema = z.enum([\n /** 能正常播。`playable: true` */\n 'ok',\n /** 首次加载未完成(mount → `ready`) */\n 'initializing',\n /** 显性缓冲(裸 `waiting`,去抖 300ms 后才上报) */\n 'buffering',\n /** 冻帧卡死(HealthMonitor 轮询 `currentTime` 测出来的,坑 #13) */\n 'stalled',\n /** 重连中(ReconnectPlugin) */\n 'reconnecting',\n /** 浏览器拒绝了自动播放 */\n 'autoplay_blocked',\n /** 播放出错 */\n 'error',\n /**\n * iframe 连不上,**含握手尚未完成**。inline 模式永不出现。\n *\n * 唯一由**宿主侧**(frame-core)本地产生的 reason —— 「iframe 还没起来」这件事\n * iframe 自己没法报。握手中 `recoverable: true`,降级后 `false`。\n */\n 'frame_disconnected',\n /**\n * 掉帧严重 —— **唯一 `playable` 仍为 `true` 的非 `ok` 值**。\n * 画面还在动,但质量在掉;消费方可以选择忽略。\n */\n 'degraded',\n])\nexport type PlayableReason = z.infer<typeof PlayableReasonSchema>\n\n/** 恢复生命周期阶段(ADR-079)。 */\nexport const RecoveryPhaseSchema = z.enum([\n 'detected',\n 'attempting',\n 'validating',\n 'recovered',\n 'failed',\n 'cancelled',\n])\n\n/** SDK 实际执行的恢复策略(ADR-079)。 */\nexport const RecoveryStrategySchema = z.enum(['reconnect', 'media_recovery', 'visibility_reload'])\n\n/** 恢复由错误、用户操作还是可见性变化触发。 */\nexport const RecoveryTriggerSchema = z.enum(['error', 'manual', 'visibility'])\n\nconst RecoveryPayloadBase = {\n /** 同一播放器实例内递增的恢复关联号。 */\n recoveryId: z.number().int().positive(),\n strategy: RecoveryStrategySchema,\n trigger: RecoveryTriggerSchema,\n /** 当前实际恢复轮次,从 1 开始。 */\n attempt: z.number().int().positive(),\n /** 该策略允许的最大恢复轮次。 */\n maxAttempts: z.number().int().positive(),\n /** 有错误起因时复用既有契约错误码;手动恢复可省略。 */\n reason: ErrorCodeSchema.optional(),\n}\n\n/**\n * 可验证恢复的单条生命周期记录(ADR-079)。\n *\n * 终态字段按 `phase` 严格区分:`recovered` 只能声明位置推进这一种验证证据;\n * `failed` / `cancelled` 只能携带各自登记的结束原因。`strict()` 刻意拒绝把终态字段\n * 混入检测或验证阶段,避免上报端把“正在验证”误读成“已经恢复”。\n */\nexport const RecoveryPayloadSchema = z.discriminatedUnion('phase', [\n z.object({ ...RecoveryPayloadBase, phase: z.literal('detected') }).strict(),\n z.object({ ...RecoveryPayloadBase, phase: z.literal('attempting') }).strict(),\n z.object({ ...RecoveryPayloadBase, phase: z.literal('validating') }).strict(),\n z\n .object({\n ...RecoveryPayloadBase,\n phase: z.literal('recovered'),\n validatedBy: z.literal('playing_position_advance'),\n })\n .strict(),\n z\n .object({\n ...RecoveryPayloadBase,\n phase: z.literal('failed'),\n outcome: z.enum(['timeout', 'attempts_exhausted']),\n })\n .strict(),\n z\n .object({\n ...RecoveryPayloadBase,\n phase: z.literal('cancelled'),\n outcome: z.enum(['source_changed', 'destroyed', 'superseded']),\n })\n .strict(),\n])\nexport type RecoveryPayload = z.infer<typeof RecoveryPayloadSchema>\n\n/**\n * 事件。player → host(iframe 模式),或 player-core → 消费方(inline 模式)。\n *\n * **命名:全小写连写**(`timeupdate` / `autoplayblocked`),对齐 HTML5 媒体事件。\n * 这是 wire 上的名字;消费面各自映射——Vue emit `time-update`,React prop `onTimeUpdate`。\n * (团队约定里的 `snake.case` 指的是埋点事件名如 `playback.start`,那是另一套命名空间。)\n *\n * 权威来源:docs/protocol/events.md + ARCHITECTURE.md § 8.4。\n */\nexport const PlayerEventSchema = z.discriminatedUnion('event', [\n /**\n * SourceRouter 的选源结论(ADR-081)。它在 Player 创建前就可发生,因此不能拿 contextchange\n * 或 firstframe 替代;支持矩阵应从此事件开始计样本。\n */\n z.object({ event: z.literal('sourceroute'), payload: SourceRoutePayloadSchema }),\n // ═══ 生命周期 ═══\n z.object({\n event: z.literal('ready'),\n payload: z.object({\n duration: z.number(),\n /** 可用清晰度档位;单档源(如 MP4)是空数组 */\n quality: z.array(QualityLevelSchema),\n /**\n * 可用字幕轨;无字幕源是空数组。\n *\n * **optional**:这是契约冻结(1.0.0)后给已有 `ready` payload 新增的字段(1.2.0,见 ADR-027)。\n * 设为可选,保证 1.x 跨版本兼容——旧 producer(≤1.1.0)不发这个字段,新 consumer 当作 `[]`。\n */\n subtitles: z.array(SubtitleTrackInfoSchema).optional(),\n }),\n }),\n z.object({ event: z.literal('play'), payload: z.object({}) }),\n z.object({ event: z.literal('pause'), payload: z.object({}) }),\n z.object({ event: z.literal('ended'), payload: z.object({}) }),\n\n // ═══ HTML5 标准事件(player-core 直接透传 xgplayer)═══\n z.object({\n event: z.literal('timeupdate'),\n /**\n * ~250ms 一次。\n *\n * **直播场景 `duration` 是 0**,不是 Infinity —— Infinity 过不了 JSON 序列化\n * (会变成 null),而契约事件要跨 iframe 传,所以 player-core 在源头就归一成 0。\n * 直播场景本来也不该读 duration。\n */\n payload: z.object({ time: z.number(), duration: z.number() }),\n }),\n z.object({\n event: z.literal('volumechange'),\n payload: z.object({ volume: z.number(), muted: z.boolean() }),\n }),\n z.object({\n event: z.literal('seeking'),\n payload: z.object({ time: z.number() }),\n }),\n z.object({\n event: z.literal('seeked'),\n payload: z.object({ time: z.number() }),\n }),\n /** 缓冲开始(卡顿) */\n z.object({ event: z.literal('waiting'), payload: z.object({}) }),\n /** 缓冲结束,继续播放 */\n z.object({ event: z.literal('playing'), payload: z.object({}) }),\n\n // ═══ 扩展 ═══\n z.object({\n event: z.literal('qualitychange'),\n payload: z.object({\n level: z.number(),\n /** true = ABR 自动切的,false = 用户手动切的 */\n auto: z.boolean(),\n }),\n }),\n /**\n * 字幕轨切换(来自 `setSubtitle` 命令,或消费方点了团队层字幕菜单)。见 ADR-027。\n *\n * 字幕没有 ABR「自动」概念,所以不带 `auto`;只有「当前哪条 / 关闭」。\n * 冻结后新增事件(1.2.0 minor,向后兼容:旧消费方不监听即无影响)。\n */\n z.object({\n event: z.literal('subtitlechange'),\n payload: z.object({\n /** 当前激活字幕轨 id(= {@link SubtitleTrackInfo} 的 `id`);`null` = 字幕已关闭 */\n id: z.number().nullable(),\n }),\n }),\n z.object({\n event: z.literal('error'),\n payload: PlayerErrorSchema,\n }),\n /** 浏览器拒绝了自动播放。消费方应展示\"点击播放\"CTA(见 AutoplayGuardPlugin) */\n z.object({ event: z.literal('autoplayblocked'), payload: z.object({}) }),\n\n // ═══ 容错(来自 P0/P1 插件)═══\n z.object({\n event: z.literal('reconnectstart'),\n payload: z.object({\n attempt: z.number(),\n maxAttempts: z.number(),\n /**\n * 触发这一轮重连的**契约错误码**;手动 `reconnect()` 时缺省(没有触发它的错误)。\n *\n * ⚠️ **这里曾经是 `z.string()`,而那让它在服务端聚合不了**(ADR-069)——\n * 消费方拿到的类型是 `string`,`switch` 不了、也没有任何东西挡住将来漂成别的写法。\n * **而源头从来就是一个契约错误码**:`plugins/reconnect.ts` 传的是\n * `mapXgplayerError(err).code`,没有第二个发射点。\n *\n * **复用 `ErrorCodeSchema`,不新起一套「重连原因」枚举** —— 理由同 ADR-062 ③:\n * 两套表会让同一条内核错误在 `error` 和 `reconnectstart` 两条通道上给出**互相矛盾**的分类,\n * 而那种漂移没人查得出来。\n *\n * ⚠️ **不要照着「今天实际只会出现哪几个码」去收窄。** 那要复刻\n * `ReconnectPlugin` 的两道过滤(`retryable === true` 且 `category !== 'media'`),\n * 而那两道闸是**实现细节**,改一行就和契约对不上了 —— 那正是第二份会漂的副本。\n */\n reason: ErrorCodeSchema.optional(),\n nextDelayMs: z.number().optional(),\n }),\n }),\n z.object({\n event: z.literal('reconnectsuccess'),\n payload: z.object({ attempts: z.number() }),\n }),\n z.object({\n event: z.literal('reconnectfailed'),\n payload: z.object({\n attempts: z.number(),\n /** 最后一轮的触发错误码。语义与取值同 `reconnectstart.reason`(ADR-069) */\n reason: ErrorCodeSchema.optional(),\n }),\n }),\n /**\n * 可验证恢复生命周期。它服务于上报与归因,取代不了 `playablechange` 的实时 UI 结论,\n * 也不改变既有 `reconnect*` UI callback 的兼容语义。见 ADR-079。\n */\n z.object({\n event: z.literal('recovery'),\n payload: RecoveryPayloadSchema,\n }),\n /**\n * 兼容性警告。不中断播放。**两类来源共用这一条事件**,靠 `code` 分辨:\n *\n * - `W_BROWSER_INCOMPATIBLE` —— 浏览器内核不兼容(UC / 夸克 / 微信 等),iframe 内的\n * `CompatPlugin` 发,五面都可能收到\n * - `W_VERSION_MISMATCH` —— host 与 iframe 的契约版本**同 major 但 minor 不同**(ADR-070)。\n * **只有 `react-frame` / `vue-frame` 两面可能收到**:inline 没有 iframe 边界、\n * 静态 iframe 不做握手协商\n * - `W_EVENT_REJECTED` —— 收到一条过不了契约校验的入站事件,已丢弃(ADR-087)。\n * **三条跨进程的路都可能收到**(`react-frame` / `vue-frame` / 静态 iframe);\n * inline 两面没有 wire,不存在「不认识的东西」。携带 `rejectedEvent`\n *\n * `code` 是必需的——消费方靠它分支处理(examples 里就是\n * `payload.code === 'W_BROWSER_INCOMPATIBLE'`)。\n */\n z.object({\n event: z.literal('compatwarning'),\n payload: z.object({\n code: WarningCodeSchema,\n message: z.string(),\n /**\n * ⚠️ 必填,**对 `W_VERSION_MISMATCH` 而言是无关信息**(host 填自己的\n * `navigator.userAgent`)。留着必填是刻意的:改成可选对已有的 TS 消费方是\n * breaking(`string` → `string | undefined`),不值得为此升 major。\n * 版本不匹配真正要被聚合的两个值走下面两个专属字段。\n * 下次契约 major 时值得把本 payload 拆成判别联合,记在 ADR-070 的开放问题里。\n */\n ua: z.string(),\n /** 宿主侧契约版本。**仅 `W_VERSION_MISMATCH` 携带**(ADR-070) */\n hostVersion: z.string().optional(),\n /** iframe 侧契约版本。**仅 `W_VERSION_MISMATCH` 携带**(ADR-070) */\n iframeVersion: z.string().optional(),\n /**\n * 被丢弃那条事件的名字。**仅 `W_EVENT_REJECTED` 携带**(ADR-087)。\n *\n * ⚠️ **类型是 `string` 而不是 `EventName`,这是刻意的** —— 它装的恰恰是\n * **本地契约不认识的名字**(最常见的成因是 iframe 比 host 新)。\n * 收窄成 `EventName` 就等于说「只会是我认识的那些」,而那正好是它不成立的场合。\n * 取不到名字(连 `event` 字段都没有)时缺省。\n *\n * 冻结后新增字段(minor,向后兼容:旧消费方读不到它即无影响)。\n */\n rejectedEvent: z.string().optional(),\n }),\n }),\n\n /**\n * 测量过的卡顿(来自 HealthMonitorPlugin,P1)。见 ADR-026。\n *\n * 和裸 `waiting` / `playing` 的区别:那两个是 HTML5 事件透传,只有开始/结束两个瞬间、\n * 不带测量,且测不到隐性卡顿(冻帧时 xgplayer 连 `waiting` 都不发)。`stalled` 是\n * HealthMonitor **测量**后的结果——含时长、含主动轮询 `currentTime` 抓到的隐性卡顿。\n *\n * - `phase: 'start'` 给消费方 UI 实时反应(转圈);\n * - `phase: 'end'` 带 `durationMs`,给埋点算卡顿率(Phase-1 灰度 KPI 的数据源)。\n *\n * 冻结后新增事件(1.1.0 minor,向后兼容:旧消费方不监听即无影响)。\n * **无条件 emit**,不受 `consent.analytics` 影响 —— 它的首要用途是驱动 UI,\n * 拿埋点开关掐它等于掐掉冻帧时唯一的信号。合规过滤归消费方的上报层(ADR-026 实现更正)。\n */\n z.object({\n event: z.literal('stalled'),\n payload: z.object({\n phase: z.enum(['start', 'end']),\n /** 卡在哪个播放位置(秒) */\n position: z.number(),\n /** phase='end' 时带上:本次卡顿时长(毫秒)。卡顿率 KPI 的核心指标 */\n durationMs: z.number().optional(),\n /**\n * 这次等待属于哪一类(ADR-075 决策④)。**`start` 与 `end` 带同一个值。**\n *\n * ⚠️ **只有 `playback` 该进卡顿率。** `firstframe` 是起播等待(它的 KPI 是首帧\n * 时延,`firstframe` 事件在管),`seek` 是**用户自己拖进度条造成的** ——\n * 把它算进卡顿率等于把用户的操作记成播放器的故障。三类混在一起正是 #391 说的\n * 「只有『发生了』,没有分布」里缺的那一刀。\n *\n * **可选字段**:老消费方不改代码也不炸。\n */\n kind: z.enum(['playback', 'firstframe', 'seek']).optional(),\n }),\n }),\n\n // ═══ 聚合态 ═══\n\n /**\n * 聚合播放状态(来自 PlayableStatePlugin)。见 ADR-043 / issue #121。\n *\n * **它不是又一个原始信号,而是上面那一堆信号的唯一结论。** 消费方只写\n * `if (!playable) 盖上自己的 loading` 就覆盖了全部「播不了」的成因,不必自己拼装\n * `ready` / `waiting` / `stalled` / `reconnect*` / `autoplayblocked` / `error` / `onFallback`\n * 再兜一个握手超时 —— 那套拼装每个消费方都要重做一遍,且漏 case 只在弱网、\n * 低端机上暴露,本地全绿。\n *\n * 判断责任放 SDK 的硬理由:冻帧检测的数据源(轮询 `currentTime`)在 iframe 内,\n * 三条 iframe 路的宿主侧**根本算不出来**,放消费面必然撞 cross-mode-parity。\n *\n * 与 {@link PlayerEventSchema} 里 `stalled` 的分工:`stalled` 是**测量结果**\n * (带 `durationMs`,给埋点算卡顿率),`playablechange` 是**实时态**(给 UI 决定\n * 盖不盖 loading)。前者是后者的数据源之一,不是替代关系(ADR-026 / ADR-043)。\n *\n * 只在**结论变化时**发,不是每次采样都发。\n *\n * 冻结后新增事件(minor,向后兼容:旧消费方不监听即无影响)。\n */\n z.object({\n event: z.literal('playablechange'),\n payload: z.object({\n /** 能不能正常出画面。消费方只读这一个字段就够 */\n playable: z.boolean(),\n /** 为什么。`playable: true` 时为 `'ok'` 或 `'degraded'` */\n reason: PlayableReasonSchema,\n /**\n * 是否可自愈 —— 决定消费方盖 loading(等)还是露重试入口(不等)。\n *\n * `buffering` / `stalled` / `reconnecting` / `initializing` 为 `true`;\n * `error` / `autoplay_blocked` 为 `false`(要用户点一下,自己不会好)。\n *\n * **`frame_disconnected` 是唯一随阶段变化的一支**:iframe 正在握手时为 `true`\n *(正常启动,该等),握手失败降级后为 `false`(连不上了,该露重试入口)。\n * 两个阶段共用一个 reason,靠这个字段区分 —— 这正是它存在的意义。\n */\n recoverable: z.boolean(),\n }),\n }),\n\n // ═══ 观测 ═══\n\n /**\n * 内核健康 —— **非致命内核诊断的聚合**(来自 hls.js)。见 ADR-062 / issue #391。\n *\n * ─── 它补的是哪一句话 ─────────────────────────────\n *\n * ADR-061 只放 fatal 进 `error` 流。2026-08-25 对真实故障注入服务器实测:注入\n * 「立即断流」后 **60 秒内 hls.js 报了 10,137 条非致命诊断、一次没升 fatal**,\n * 于是消费方拿到的契约 `error` 是 **0 条**,能看到的只有 `stalled` / `playablechange`\n * —— 也就是「卡住了」,不是「为什么卡住」。\n *\n * `stalled` 有时长没成因;`playablechange` 有结论没成因;`error` 在这个场景下不发。\n * 这个事件补的就是**成因**那一格。\n *\n * ─── 聚合,不是转发 ───────────────────────────────\n *\n * 固定 5000ms 窗口,窗口内不论内核报多少条**最多产出一条**(169 条/秒 → 0.2 条/秒)。\n * 窗口写死在 SDK 里、不进配置:一旦可配,「调到 100ms 又把消费方打死」就成了 SDK 的锅\n * (ADR-029 不投机造配置)。\n *\n * ⚠️ **不要拿它驱动 UI。** 首条最迟 5 秒后到,它服务的是上报与排障;\n * 实时 UI 归 `playablechange`(ADR-043),分工不变。\n *\n * ⚠️ **FLV 源永远不发这个事件。** flv.js 没有 fatal / 非 fatal 之分,每条 `ERROR`\n * 都是终局、已经走了 `error` 流(ADR-061 / #403)。FLV 侧没有「非致命诊断」这种东西\n * 可聚合 —— 这是已知边界,**不是漏实现**,别为了对称去造一条。\n *\n * 冻结后新增事件(4.1.0 minor,向后兼容:旧消费方不监听即无影响)。\n */\n z.object({\n event: z.literal('kernelhealth'),\n payload: z.object({\n /**\n * 内核是不是在挣扎。\n *\n * `true` = 本窗口内有诊断;`false` = 安静了一整个窗口(**收尾那一条**)。\n *\n * 收尾那一条是硬要求:没有它,消费方要判断「内核安静了」只能自己兜一个超时,\n * 而「每个消费方各自兜一个超时」正是 ADR-043 花一整条 ADR 消灭的东西。\n */\n degraded: z.boolean(),\n /**\n * 成因大类。取本窗口内出现最多的那一类,并列取先出现的。\n *\n * 三档的划分判据是「**会不会让消费方做不同的事**」(ADR-061 ②):\n * `network` 提示检查网络 / 降码率(能自愈的一档)、`media` 换清晰度或换设备\n * 才可能好、`other` 只能上报。\n *\n * 归一复用 `error-mapping.ts` 那份类型表 —— 同一条内核错误在 `error` 和\n * `kernelhealth` 里给出互相矛盾的分类是没人查得出来的漂移。\n */\n reason: z.enum(['network', 'media', 'other']),\n /** 本窗口内内核报的非致命诊断条数(`degraded: false` 时恒为 0) */\n count: z.number(),\n /** 从本段故障的**首条**诊断到现在持续了多久(毫秒)。配合 `count` 得出速率 */\n durationMs: z.number(),\n /**\n * 内核原文(如 `networkError / fragLoadError`),取本窗口最近一条。\n *\n * ⚠️ **只给日志看,不许 `switch`。** 它跟着 hls.js 版本走,不是契约的一部分;\n * 要分支请用 {@link reason}。\n */\n detail: z.string(),\n }),\n }),\n\n /**\n * FLV 音频数据轨健康(ADR-078 · #520)。\n *\n * 这不是扬声器或内容是否静音:它只说明已确认存在的音频轨是否仍在接收数据。视频轨\n * 持续推进而音频轨不推进时为 `degraded: true`;音频恢复后必发一条 `false` 收尾,\n * 消费方无需自行起超时。仅 FLV 有该信号,HLS/MP4 不伪造对应事件。\n */\n z.object({\n event: z.literal('audiohealth'),\n payload: z.object({\n degraded: z.boolean(),\n reason: z.literal('audio_data_gap'),\n durationMs: z.number(),\n }),\n }),\n\n /**\n * 缓冲余量在净流失(ADR-071 · #391)。**这是契约里唯一一条「事情还没坏」的信号。**\n *\n * 其余观测信号都要等播放真的卡住才开口(`stalled`)或等内核真的报错才开口\n * (`kernelhealth`,而且窗口 5000ms)。ADR-068 实测量出那之间有一段静默期:\n * 故障发生到第一条 `stalled` 之间 **3.9 ~ 10 秒**,而消费方在那段时间里什么都收不到。\n *\n * ⚠️ **判据是「连续下降」,不是「低于某个秒数」。** ADR-068 补量的基线把绝对阈值否掉了:\n * 健康播放的余量是 **3.35 ~ 6.40 秒**(锯齿),而「源头刚挂那一刻」的余量是 **6.96 秒** ——\n * **两者重叠**,任何能抓到后者的绝对阈值都会在健康播放时一直开口。\n *\n * 三条接入路径全都经过 `HealthMonitorPlugin`,和 `stalled` 同一条路,没有新的模式差异。\n *\n * 冻结后新增事件(minor,向后兼容:旧消费方不监听即无影响)。\n */\n z.object({\n event: z.literal('bufferhealth'),\n payload: z.object({\n /**\n * 余量是不是在净流失。\n *\n * `true` = 连续 3 次采样(1s 间隔)余量严格下降,即「每秒播掉一秒、一秒也没补上」;\n * `false` = **收尾那一条**,余量重新涨回来了。\n *\n * 收尾那一条是硬要求,理由与 `kernelhealth.degraded` 逐字相同:\n * 没有它,消费方判断「缓冲回来了」只能自己兜一个超时。\n */\n draining: z.boolean(),\n /**\n * 当前余量(秒)= `buffered.end(最后一段) - currentTime`。\n *\n * ⚠️ **可以是负数,那不是非法值** —— 直播里 hls.js 会把播放头往直播边缘推,\n * 而缓冲追不上。**负号是「在追但追不上」(带宽不够)与「源头挂了」(余量停在 ~0)\n * 之间唯一的分叉点**(ADR-068 ②)。任何把负值过滤掉的实现都会删掉这条信息。\n *\n * ⚠️ 不要改用「包含播放头的那一段」那个更严谨的公式 —— 实测 100 个采样点\n * `buffered.length` 恒为 1,它没有对象;而它在播放头跑出缓冲时返回空,\n * **正好抹掉上面那个分叉点**(ADR-071 ③)。\n *\n * SDK **不替消费方判定成因** —— 报符号,归因归团队层(同 ADR-062 ③)。\n */\n marginSec: z.number(),\n }),\n }),\n\n /**\n * 播放上下文变了(ADR-074 · #480)。**它不是又一条观测信号,是上报适配器的初值与增量。**\n *\n * 消费方写上报适配器时需要「这是哪一次播放 / 用的哪个内核 / 播的哪个候选源」,\n * 而这几格在 SDK 外面拿不到。没有它,适配器只能自己维护一份状态镜像 ——\n * 正是 ADR-043 花一整条 ADR 消灭掉的形态。\n *\n * ⚠️ **只在结论变化时发,不是每次采样都发** —— 逐字沿用 `playablechange` 的做法。\n *\n * ⚠️ **`position` 不参与触发判据。** 它每 250ms 都在变,按值变化发就等于复制一条\n * `timeupdate`。payload 里的 `position` 是**发出这一刻**的值;要「出错那一刻」的位置,\n * 请在 `error` 回调里**同步**调 `getPlaybackContext()` —— iframe 三条路上句柄调用是\n * 异步的,等一个 await 回来位置已经变了,而那正是本事件要消灭的失真。\n *\n * 冻结后新增事件(minor,向后兼容:旧消费方不监听即无影响)。\n */\n z.object({\n event: z.literal('contextchange'),\n payload: PlaybackContextSchema,\n }),\n\n // ═══ 观测(ADR-075)═══\n // 三条都来自 xgplayer `DefaultPreset` 里一直在跑、而 player-core 从没读过的插件。\n // **原计划的第四条 `bandwidth` 查掉了** —— 上游 `DOWNLOAD_SPEED_CHANGE` 结构性不触发,\n // 进契约就是第二个「只在注释里存在的 healthreport」。理由见 observability.ts 头注。\n\n /**\n * 首帧可见(ADR-075)。来自 `XGLogger`。\n *\n * ⚠️ 和 `pnpm test:e2e:kpi`(#148)量的不是一件事:那条是端到端(含页面加载),\n * 这条是播放器内部口径。**决定是不统一**,两个用途都真实存在。\n */\n z.object({\n event: z.literal('firstframe'),\n payload: FirstFramePayloadSchema,\n }),\n\n /**\n * 画面冻结(ADR-075)。来自 `FpsDetect`,**仅 PC**(上游按 `sniffer.device` 分档装载)。\n *\n * ⚠️ **不叫 `framedrop`** —— 触发判据是「连续 3 tick 解码帧数 ≤ 0 **且缓冲够**」,\n * 检出的是画面冻住而不是掉帧率。也**不与 `stalled` 重复**:后者是缓冲驱动的等待,\n * 这条的前提恰恰是缓冲够,指向解码侧。\n */\n z.object({\n event: z.literal('framefreeze'),\n payload: FrameFreezePayloadSchema,\n }),\n\n /**\n * 用户动作(ADR-075)。来自 `Stats` 收的 `USER_ACTION`,经白名单过滤。\n *\n * 契约里已有 `play` / `pause` / `volumechange` / `seeking` / `qualitychange`,\n * 而它们**回答不了「是谁发起的」** —— 用户点的还是代码调的。本事件补的就是这一格。\n *\n * ⚠️ 消费方传 `controls: false` 时绝大多数动作不再发出(发出方是 xgplayer 自己的控件)。\n */\n z.object({\n event: z.literal('useraction'),\n payload: UserActionPayloadSchema,\n }),\n])\n\nexport type PlayerEvent = z.infer<typeof PlayerEventSchema>\n\n/** 全部事件名 */\nexport type EventName = PlayerEvent['event']\n\n/** 取出某个事件的 payload 类型 */\nexport type EventPayload<E extends EventName> = Extract<PlayerEvent, { event: E }>['payload']\n\n/**\n * `kernelhealth` 的 payload(ADR-062)。\n *\n * **给四个消费面共用一份。** 其余事件的 payload 在各消费面是逐字抄的内联字面量\n * (`stalled` 那个形状在仓库里有四份),这条不再抄 —— 抄一次就多一份会漂的副本。\n */\nexport type KernelHealthPayload = EventPayload<'kernelhealth'>\n\n/**\n * FLV 音频数据轨连续性(ADR-078 / #520)。\n *\n * `degraded` 的 true / false 是同一段数据缺口的开 / 收尾;它不判断内容是否静音,\n * 也不受用户静音或设备音量影响。\n */\nexport type AudioHealthPayload = EventPayload<'audiohealth'>\n\n/** 可验证恢复生命周期的 payload(ADR-079)。 */\nexport type RecoveryEventPayload = EventPayload<'recovery'>\n\n/**\n * `bufferhealth` 的 payload(ADR-071)。\n *\n * 和 {@link KernelHealthPayload} 同一条理由:**给四个消费面共用一份**,不各抄一遍。\n */\nexport type BufferHealthPayload = EventPayload<'bufferhealth'>\n\n/**\n * `contextchange` 的 payload(ADR-074)。它就是 {@link PlaybackContextSchema} 本身 ——\n * 这里给个别名,让四个消费面按同一条路径引用(同 {@link KernelHealthPayload} 的理由:\n * **给四个消费面共用一份**,不各抄一遍)。\n */\nexport type PlaybackContextPayload = EventPayload<'contextchange'>\n\n/** `sourceroute` 的 payload(ADR-081)。 */\nexport type SourceRouteEventPayload = EventPayload<'sourceroute'>\n\n/**\n * 把 `createPlayer` 同步抛出的错误(选源失败等)转成 error 事件。\n *\n * `SentinelError` 带 `.playerError`;其它未知错误兜底成 `E_INTERNAL`。\n *\n * **住在 protocol 而不是各消费面**:inline 和 iframe 内部都要在 `createPlayer` 的\n * try/catch 里做同一件事,此前 `packages/react/src/overlay.ts` 与\n * `apps/embed-app/src/bridge.ts` 各有一份逐字相同的副本 —— 同 `resolveLocaleMessages`\n * 当初被收回来的理由(#120 · PR C)。\n *\n * 这不是 schema 变更:没有新增 / 修改任何 Zod schema,只是把一个纯函数收到 `makePlayerError`\n * 旁边。\n */\n/**\n * 构造一条 `W_EVENT_REJECTED` 警告(ADR-087 · #728)。\n *\n * **住在 protocol 而不是各传输层**:`frame-core`(react-frame / vue-frame 两面)和\n * `embed-helper`(静态 iframe)都要在入站事件被契约拒绝时发同一条警告,\n * 各写一份就是两份会漂的副本 —— 同 {@link toErrorEvent} 和 `PLAYER_DESTROYED_MESSAGE`\n * 当初被收回来的理由。\n *\n * 还有一条更硬的理由:`embed-helper` 里**不许出现任何硬编码事件名**\n *(`demo-matrix` 的「静态 iframe 那一列」护栏钉着)—— 那一列「事件全绿」正建立在\n * 它靠 `on<K extends EventName>` 泛型覆盖契约全集、没有任何过滤上。\n * 警告构造放这里,那边就一个事件名字面量都不需要。\n *\n * @param rejected 被丢那条事件的名字;连 `event` 字段都取不到时传 `undefined`\n * @param ua 宿主 UA。`ua` 对本警告是无关信息,但契约里它必填(ADR-070 记过这个取舍)\n */\nexport function makeEventRejectedWarning(\n rejected: string | undefined,\n ua: string,\n): Extract<PlayerEvent, { event: 'compatwarning' }> {\n const shown = rejected ?? '(未知)'\n return {\n event: 'compatwarning',\n payload: {\n code: 'W_EVENT_REJECTED',\n message:\n `收到一条过不了契约校验的事件(${shown}),已丢弃。` +\n '最常见的成因是 iframe 比宿主新 —— 先看两端契约版本差多少,再怀疑 iframe 有 bug。',\n ua,\n ...(rejected === undefined ? {} : { rejectedEvent: rejected }),\n },\n }\n}\n\nexport function toErrorEvent(err: unknown): Extract<PlayerEvent, { event: 'error' }> {\n const playerError =\n typeof err === 'object' && err !== null && 'playerError' in err\n ? (err as { playerError: PlayerError }).playerError\n : makePlayerError('E_INTERNAL', err instanceof Error ? err.message : '内部错误', err)\n\n return { event: 'error', payload: playerError }\n}\n","import { z } from 'zod'\nimport { DanmakuItemSchema, LocaleConfigSchema, MediaSourceSchema } from './configs'\n\n/**\n * 命令。host → iframe(iframe 模式),或消费方 → player-core(inline 模式)。\n *\n * 权威来源:docs/protocol/methods.md + ARCHITECTURE.md § 8.5。\n * 每条命令的响应都是 `void`——结果通过事件回来,不通过 response payload。\n */\nexport const CommandSchema = z.discriminatedUnion('method', [\n z.object({ method: z.literal('play'), params: z.object({}).optional() }),\n z.object({ method: z.literal('pause'), params: z.object({}).optional() }),\n\n z.object({\n method: z.literal('seek'),\n params: z.object({\n /** 目标时间(秒)。负数和超过 duration 的值由 player-core 钳制,不报错 */\n time: z.number(),\n /** `keyframe` 更快但不精确;默认 `exact` */\n type: z.enum(['exact', 'keyframe']).optional(),\n }),\n }),\n\n z.object({\n method: z.literal('setVolume'),\n params: z.object({ volume: z.number().min(0).max(1) }),\n }),\n\n z.object({\n method: z.literal('setMuted'),\n params: z.object({ muted: z.boolean() }),\n }),\n\n z.object({\n method: z.literal('setPlaybackRate'),\n params: z.object({ rate: z.number().min(0.25).max(4) }),\n }),\n\n z.object({\n method: z.literal('setQuality'),\n /** `'auto'` = 交给 ABR 自适应 */\n params: z.object({ level: z.union([z.number(), z.literal('auto')]) }),\n }),\n\n z.object({\n method: z.literal('setSubtitle'),\n /**\n * `'off'` = 关闭字幕;数字 = 切到该 id 轨(见 `ready` payload 的 `subtitles[].id`)。见 ADR-027。\n *\n * 切换结果通过 `subtitlechange` 事件回来(同 setQuality → qualitychange)。\n */\n params: z.object({ id: z.union([z.number(), z.literal('off')]) }),\n }),\n\n z.object({\n method: z.literal('setLocale'),\n /**\n * 运行时切换语言,**不重建播放器、不丢播放进度**。\n *\n * 消费方通常不直接调它 —— 各消费面 watch `locale` prop 后自动下发,\n * 改 prop 即可切换(见 ADR-035)。\n *\n * 作用于两处:①播放内核自带控件的文案(**仅中 / 英**,`zh-*` → 中文,其余 → 英文);\n * ②player-ui 覆盖层文案(读 `messages`,语言不限)。\n * 传对象形式可同时换掉两者;传字符串只切控件语言、沿用原有 messages。\n */\n params: z.object({ locale: LocaleConfigSchema }),\n }),\n\n z.object({\n method: z.literal('load'),\n params: z.object({ source: MediaSourceSchema }),\n }),\n\n z.object({\n method: z.literal('reconnect'),\n params: z\n .object({\n /** true = 把重连计数清零,重新获得完整的 maxRetries 次机会 */\n resetCounter: z.boolean().optional(),\n })\n .optional(),\n }),\n\n // ═══ 弹幕(ADR-028,streaming MVP)═══\n z.object({\n method: z.literal('pushDanmaku'),\n /** 逐条推送(直播)。渲染归 SDK,数据源归团队层 */\n params: z.object({ item: DanmakuItemSchema }),\n }),\n z.object({\n method: z.literal('setDanmakuEnabled'),\n /** 开关弹幕渲染(true=start / false=stop) */\n params: z.object({ enabled: z.boolean() }),\n }),\n z.object({ method: z.literal('clearDanmaku'), params: z.object({}).optional() }),\n\n z.object({ method: z.literal('destroy'), params: z.object({}).optional() }),\n z.object({ method: z.literal('enterFullscreen'), params: z.object({}).optional() }),\n z.object({ method: z.literal('exitFullscreen'), params: z.object({}).optional() }),\n])\n\nexport type Command = z.infer<typeof CommandSchema>\n\n/** 全部命令名 */\nexport type MethodName = Command['method']\n\n/** 取出某条命令的 params 类型 */\nexport type CommandParams<M extends MethodName> = Extract<Command, { method: M }>['params']\n","","/**\n * 契约版本。\n *\n * 注意:这和 iframe URL 里的 `version`(如 `'v1'`,见 ADR-023)不是一回事——\n * 那个是 CDN 路径版本,这个是 host ↔ iframe 的通信契约版本。\n *\n * 冻结策略见 `packages/protocol/CLAUDE.md`:审查清单全过之后才升 1.0.0。\n *\n * **首发即 1.1.0**(2026-07-19):契约从未对外发布,故把首发前迭代出的全部能力面\n * **折叠进首发契约**——与 8 个包的 npm 版本对齐,避免\"包版本 1.1.0 却携带 1.0.0\n * 契约常量\"的双轴割裂(`tests/index.contract.test.ts` 有断言锁死这一致性)。\n * 首发契约面**已包含**:\n * - `stalled` 事件(HealthMonitor 卡顿测量,设计见 ADR-026)\n * - 字幕控制侧:`setSubtitle` 命令 + `subtitlechange` 事件 + `ready` 的**可选** `subtitles` 字段(ADR-027)\n * - 弹幕 streaming:`pushDanmaku` / `setDanmakuEnabled` / `clearDanmaku` 命令 + `PlayerConfig.danmaku`(可选)(ADR-028)\n * - 场景预设收敛为唯一 `homepage-preview`(ADR-032)\n *\n * 上面几个 ADR 记录的是这些能力的**设计出处**,不是\"冻结后独立发布的 minor\"——它们在首发前\n * 就已落地,故都是首发契约的一部分。**首发之后**再新增 method/event/字段才走 minor\n * (1.x 向后兼容),破坏性变更须走 ADR + major。\n *\n * 为什么是 1.1.0 而不是 1.0.0:原计划锁 1.0.0(ADR-025),但首发前两项变更\n * (全屏命令补完、preset 收敛)各带一条 minor changeset,changesets 的 `fixed` 组\n * 把 8 个包统一推到 1.1.0。契约面确有变化(删了 4 个 preset),升 minor 名实相符;\n * 且 {@link isContractCompatible} 在 `>=1.0.0` 时只比 major,1.0.0 ↔ 1.1.0 握手仍兼容。\n *\n * 值**从 package.json 派生**,不要改回硬编码 —— 契约常量必须与 npm 包版本\n * 严格相等,而 changesets 只改 package.json。理由与实测数据见\n * `docs/adr/ADR-036-contract-version-derived.md`。\n */\nimport { version } from '../package.json'\n\nexport const CONTRACT_VERSION: string = version\n\n/** 解析出的 semver 三段 */\nexport interface ParsedVersion {\n major: number\n minor: number\n patch: number\n}\n\nconst SEMVER_RE = /^(\\d+)\\.(\\d+)\\.(\\d+)$/\n\n/**\n * 解析 semver 字符串。只接受严格的 `major.minor.patch`,\n * 不接受 prerelease / build metadata —— 契约版本不需要它们。\n *\n * @returns 解析结果;格式非法时返回 `null`(不 throw,调用方决定怎么降级)\n *\n * @example\n * parseVersion('1.2.3') // { major: 1, minor: 2, patch: 3 }\n * parseVersion('v1') // null\n */\nexport function parseVersion(version: string): ParsedVersion | null {\n const m = SEMVER_RE.exec(version)\n if (!m) return null\n // 正则已保证这三组存在且是数字\n return {\n major: Number(m[1]),\n minor: Number(m[2]),\n patch: Number(m[3]),\n }\n}\n\n/**\n * 判断两侧契约版本是否兼容。host 和 iframe 必须用**同一个**规则,\n * 所以它定义在 protocol 里,而不是各自实现一遍。\n *\n * 规则(按 semver 语义):\n * - `0.x` 阶段:契约未冻结,minor 变更即为破坏性 → **minor 必须相同**\n * - `>=1.0.0`:minor 是向后兼容的新增 → **major 相同即兼容**\n * - 任一侧版本号格式非法 → 不兼容\n *\n * patch 差异永远兼容。\n *\n * @param hostVersion 宿主侧 CONTRACT_VERSION\n * @param iframeVersion iframe 侧 CONTRACT_VERSION\n *\n * @example\n * isContractCompatible('0.1.0', '0.1.3') // true · patch 差异\n * isContractCompatible('0.1.0', '0.2.0') // false · 0.x 的 minor 是破坏性的\n * isContractCompatible('1.1.0', '1.4.0') // true · 1.x 的 minor 向后兼容\n * isContractCompatible('1.0.0', '2.0.0') // false · major 不同\n */\nexport function isContractCompatible(hostVersion: string, iframeVersion: string): boolean {\n const host = parseVersion(hostVersion)\n const iframe = parseVersion(iframeVersion)\n if (!host || !iframe) return false\n\n if (host.major !== iframe.major) return false\n if (host.major === 0) return host.minor === iframe.minor\n\n return true\n}\n","import { z } from 'zod'\nimport { type PlayerError, PlayerErrorSchema } from './errors'\nimport { type PlayerEvent, PlayerEventSchema } from './events'\nimport { type Command, CommandSchema } from './methods'\nimport { CONTRACT_VERSION } from './version'\n\n/** 包裹类型 */\nexport const EnvelopeTypeSchema = z.enum(['command', 'event', 'response', 'error'])\nexport type EnvelopeType = z.infer<typeof EnvelopeTypeSchema>\n\n/**\n * 所有 host ↔ iframe 消息的公共外壳。\n *\n * `id` 用于 request-response 关联:`response` / `error` 包裹**复用**对应\n * `command` 的 id,调用方靠它把响应对上是哪条命令。`event` 是主动上抛的,\n * 它的 id 不对应任何 command。\n */\nconst envelopeShape = {\n /** 发送方的 CONTRACT_VERSION */\n version: z.string(),\n id: z.string(),\n /** Unix ms */\n timestamp: z.number(),\n}\n\n/**\n * 用任意 payload schema 组一个 Envelope schema。\n *\n * `TType` 必须是**具体的**字面量类型(`'command'` 而不是 `EnvelopeType`),\n * 否则 {@link EnvelopeSchema} 在 TS 层就没法按 `type` 收窄——\n * 运行时 zod 照样能判别,但消费方写 `if (env.type === 'event')` 拿不到窄化后的 payload。\n *\n * @example\n * const MyEnvelope = createEnvelopeSchema('event', PlayerEventSchema)\n */\nexport function createEnvelopeSchema<TType extends EnvelopeType, T extends z.ZodTypeAny>(\n type: TType,\n payload: T,\n): z.ZodObject<{\n version: z.ZodString\n id: z.ZodString\n timestamp: z.ZodNumber\n type: z.ZodLiteral<TType>\n payload: T\n}> {\n return z.object({\n ...envelopeShape,\n type: z.literal(type),\n payload,\n })\n}\n\n/** 命令包裹(host → iframe) */\nexport const CommandEnvelopeSchema = createEnvelopeSchema('command', CommandSchema)\nexport type CommandEnvelope = z.infer<typeof CommandEnvelopeSchema>\n\n/** 事件包裹(iframe → host) */\nexport const EventEnvelopeSchema = createEnvelopeSchema('event', PlayerEventSchema)\nexport type EventEnvelope = z.infer<typeof EventEnvelopeSchema>\n\n/**\n * 响应包裹(iframe → host)。命令的结果一律是 void——\n * 播放状态通过事件回来,不塞在 response 里。\n */\nexport const ResponseEnvelopeSchema = createEnvelopeSchema('response', z.object({}))\nexport type ResponseEnvelope = z.infer<typeof ResponseEnvelopeSchema>\n\n/** 错误包裹(iframe → host)。命令执行失败时替代 response 返回 */\nexport const ErrorEnvelopeSchema = createEnvelopeSchema('error', PlayerErrorSchema)\nexport type ErrorEnvelope = z.infer<typeof ErrorEnvelopeSchema>\n\n/**\n * 任意包裹。收到消息时先用它 parse,再按 `type` 分支。\n *\n * 用 discriminatedUnion 而不是 union:错误信息能精确到具体分支,\n * 而不是把四个分支的失败原因全列一遍。\n */\nexport const EnvelopeSchema = z.discriminatedUnion('type', [\n CommandEnvelopeSchema,\n EventEnvelopeSchema,\n ResponseEnvelopeSchema,\n ErrorEnvelopeSchema,\n])\nexport type Envelope = z.infer<typeof EnvelopeSchema>\n\n/** 构造包裹时必须由调用方注入的元信息 */\nexport interface EnvelopeMeta {\n /** 唯一 ID。response / error 复用对应 command 的 id */\n id: string\n /** Unix ms */\n timestamp: number\n /** 默认用本包的 CONTRACT_VERSION;只有测试或版本协商场景才需要覆盖 */\n version?: string\n}\n\n/**\n * id 和 timestamp 由调用方传入,不在这里 `crypto.randomUUID()` / `Date.now()`——\n * protocol 是纯契约层,不产生副作用(见 packages/protocol/CLAUDE.md § 严禁做的)。\n * 好处是这几个函数完全可测,不用 mock 时间。\n */\nfunction envelopeBase(meta: EnvelopeMeta) {\n return {\n version: meta.version ?? CONTRACT_VERSION,\n id: meta.id,\n timestamp: meta.timestamp,\n }\n}\n\n/** 构造命令包裹(host → iframe) */\nexport function commandEnvelope(payload: Command, meta: EnvelopeMeta): CommandEnvelope {\n return { ...envelopeBase(meta), type: 'command', payload }\n}\n\n/** 构造事件包裹(iframe → host) */\nexport function eventEnvelope(payload: PlayerEvent, meta: EnvelopeMeta): EventEnvelope {\n return { ...envelopeBase(meta), type: 'event', payload }\n}\n\n/** 构造响应包裹。`meta.id` 必须是对应 command 的 id */\nexport function responseEnvelope(meta: EnvelopeMeta): ResponseEnvelope {\n return { ...envelopeBase(meta), type: 'response', payload: {} }\n}\n\n/** 构造错误包裹。`meta.id` 必须是对应 command 的 id */\nexport function errorEnvelope(payload: PlayerError, meta: EnvelopeMeta): ErrorEnvelope {\n return { ...envelopeBase(meta), type: 'error', payload }\n}\n","import type { LocaleConfig } from './configs'\n\n/** 一个 locale 下的翻译表。key 形如 `error.network` */\nexport type ResolvedMessages = Record<string, string>\n\nexport interface ResolvedLocale {\n locale: string\n messages: ResolvedMessages\n}\n\n/**\n * 把 `LocaleConfig` 解析成\"当前 locale + 一张扁平翻译表\",供覆盖层的 `useT` 直接查。\n *\n * **为什么住在 protocol**:inline 面(`@video-lab/react`)和 iframe 面\n * (`apps/embed-app`)都要做这件事,而它俩没有别的公共依赖。此前各写了一份\n * `readLocale`,两份逐字相同 —— **四种接入方式行为漂移的经典来源**。\n * 先例:`resolvePreset` 同样是住在 protocol 的纯函数。\n *\n * **回退链是逐 key 合并,不是整表取第一个存在的。** 后者会让主语种缺一个 key 就整表\n * 退化成回退语种;而漏翻译通常是零星几条,不是整表缺失。\n *\n * **缺 key 时不做任何兜底** —— 链上都没有就让**渲染侧**原样返回 key。\n *\n * ⚠️ 这里曾经写着「见 `use-t.ts`」,而那个文件在 #120 · PR D 就随\n * `I18nProvider` / `useT()` 一起删掉了(player-ui 去 React 那次)。\n * 今天做这件事的是三个消费面各自的 `resolveText`\n *(`react/src/overlay-elements.tsx` / `vue/src/overlay-elements.ts` / `embed-app/src/app.ts`),\n * 实现都是 `messages[key] ?? key`。\n * 那是刻意的设计:漏翻译时界面上明晃晃出现 `error.network`,\n * 一眼可见;而静默替换成别的语种只会把问题藏起来。\n *\n * @example\n * resolveLocaleMessages({\n * locale: 'th-TH',\n * fallbackLocales: ['en-US'],\n * messages: { 'th-TH': { retry: 'ลองใหม่' }, 'en-US': { retry: 'Retry', close: 'Close' } },\n * })\n * // → { locale: 'th-TH', messages: { retry: 'ลองใหม่', close: 'Close' } }\n * // ↑ 主语种赢 ↑ 主语种没有,从回退链取\n */\nexport function resolveLocaleMessages(locale: LocaleConfig | undefined): ResolvedLocale {\n if (!locale) return { locale: 'en-US', messages: {} }\n if (typeof locale === 'string') return { locale, messages: {} }\n\n const table = locale.messages\n if (!table) return { locale: locale.locale, messages: {} }\n\n // Object.assign 是后写的赢,所以**按优先级从低到高**铺:\n // 回退链倒序(链尾优先级最低)→ …… → 回退链首项 → 主语种(最后写,最高)。\n // 注意 fallbackLocales 必须 reverse:`['en-US','zh-CN']` 的语义是 en 先于 zh,\n // 正序铺会让 zh 覆盖掉 en,正好反了(单测\"多级回退:靠前的赢\"钉的就是这个)。\n const chain = [...(locale.fallbackLocales ?? [])].reverse()\n chain.push(locale.locale)\n const messages: ResolvedMessages = {}\n for (const tag of chain) Object.assign(messages, table[tag])\n\n return { locale: locale.locale, messages }\n}\n","import type { PlayerConfig, PresetName } from './configs'\n\n/** 预设能设置的字段 = PlayerConfig 里除 source / preset 之外的部分 */\nexport type PresetConfig = Omit<PlayerConfig, 'source' | 'preset'>\n\n/**\n * 场景预设表。权威来源:ARCHITECTURE.md § 8.7.3。\n *\n * **只保留一个预设 `homepage-preview`(ADR-032)**:预设的价值在于打包一组\"不平凡\"的字段组合,\n * 而首页/直播预览卡片正是这样的组合(静音循环自动播 + 无控件 + 不响应点击)——高频复用、\n * 手写六个字段易错。其余场景(如房内\"自动播 + 静音\")字段太少,直接写 prop 即可,不配拥有预设。\n * 曾经的 `sea-mobile` / `sea-tv` / `desktop` / `internal-admin` 已删(首发前收敛,见 ADR-032)。\n *\n * 注意:§ 8.7.3 里预览预设还写了 `danmaku` / `wakeLock` / `visibility` 字段,但**刻意不进 preset 默认**\n * ——弹幕是直播按需能力,由消费方显式开(M1.1 D1 决策:档 C);`wakeLock` / `visibility` 是 P0/P1\n * 插件的内部行为,不走契约配置。故此表只保留通用播放字段。\n */\nexport const PRESETS: Readonly<Record<PresetName, PresetConfig>> = {\n /** 首页/直播预览卡片:静音循环自动播,无控件,不响应点击(透传给外层卡片) */\n 'homepage-preview': {\n autoplay: true,\n muted: true,\n loop: true,\n controls: false,\n interactive: false,\n playsinline: true,\n },\n}\n\n/**\n * 应用预设:预设提供默认值,消费方显式传的字段永远覆盖它。\n *\n * 只有 `undefined` 才算\"没传\"——`false` / `0` 都是有效的显式值,会覆盖预设。\n *\n * @param preset 预设名;`undefined` 时原样返回 config\n * @param config 消费方传入的配置(含 source)\n *\n * @example\n * resolvePreset('homepage-preview', { source: 'a.m3u8', autoplay: false })\n * // → autoplay: false(显式值赢),muted: true(来自预设)\n */\nexport function resolvePreset(preset: PresetName | undefined, config: PlayerConfig): PlayerConfig {\n if (!preset) return config\n\n // 不能直接 { ...defaults, ...config }:config 里显式写成 undefined 的字段\n // 会把预设值抹掉,而 undefined 的语义是\"没传\",应该落回预设。\n const merged: Record<string, unknown> = { ...PRESETS[preset] }\n for (const [key, value] of Object.entries(config)) {\n if (value !== undefined) merged[key] = value\n }\n\n return merged as PlayerConfig\n}\n"],"mappings":";;;AAmBA,MAAa,kBAAkB,EAAE,KAAK;CAAC;CAAO;CAAO;CAAO;AAAM,CAAC;;AAInE,MAAa,wBAAwB,EAAE,KAAK;CAAC;CAAO;CAAO;AAAK,CAAC;;AAIjE,MAAa,qBAAqB,EAAE,MAAM,CACxC,EAAE,OAAO,GACT,EAAE,OAAO;CACP,KAAK,EAAE,OAAO;CACd,SAAS,EAAE,KAAK,CAAC,SAAS,MAAM,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,SAAS;CAC5D,KAAK,EAAE,KAAK;EAAC;EAAS;EAAW;CAAM,CAAC,CAAC,CAAC,QAAQ,OAAO,CAAC,CAAC,SAAS;AAItE,CAAC,CACH,CAAC;;;;;;;;;;;;;;;AAiBD,MAAa,yBAAyB,EAAE,MAAM,CAC5C,EAAE,OAAO,GACT,EAAE,OAAO;CACP,KAAK,EAAE,OAAO;;;;;;;;;CASd,KAAK,EAAE,KAAK;EAAC;EAAS;EAAW;CAAM,CAAC,CAAC,CAAC,SAAS;;CAEnD,UAAU,EAAE,QAAQ,CAAC,CAAC,SAAS;AACjC,CAAC,CACH,CAAC;;AAID,MAAa,sBAAsB,EAAE,mBAAmB,QAAQ,CAC9D,EAAE,OAAO;CACP,MAAM,EAAE,QAAQ,KAAK;CACrB,KAAK,EAAE,OAAO;CACd,QAAQ,EAAE,OAAO;CACjB,OAAO,EAAE,OAAO;CAChB,WAAW,EAAE,QAAQ,CAAC,CAAC,SAAS;AAClC,CAAC,GACD,EAAE,OAAO;CACP,MAAM,EAAE,QAAQ,SAAS;;;;;;;;;;;CAWzB,SAAS,EAAE,OAAO;CAElB,QAAQ,EAAE,OAAO;CACjB,OAAO,EAAE,OAAO;CAChB,WAAW,EAAE,QAAQ,CAAC,CAAC,SAAS;AAClC,CAAC,CACH,CAAC;;AAID,MAAa,kBAAkB,EAAE,OAAO;;;;;;;;;;;AAWtC,gBAAgB,EAAE,QAAQ,CAAC,CAAC,SAAS,EACvC,CAAC;;AAoBD,MAAa,sBAAsB,EAAE,OAAO;CAC1C,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;AAChC,CAAC;;;;;;;;;;;;;;;;AAkBD,MAAa,oBAAoB,EAAE,OAAO;CACxC,KAAK,EAAE,OAAO;CACd,MAAM;AACR,CAAC;;;;;;;AASD,MAAM,oBAAoB;;;;;;;;;;;;;;CAcxB,MAAM,EAAE,QAAQ,CAAC,CAAC,SAAS;CAC3B,KAAK,gBAAgB,SAAS;CAC9B,WAAW,EAAE,MAAM,mBAAmB,CAAC,CAAC,SAAS;CACjD,UAAU,oBAAoB,SAAS;AACzC;;AAGA,MAAa,2BAA2B,EAAE,OAAO;CAC/C,KAAK,EAAE,OAAO;;;;;;;CAOd,MAAM,gBAAgB,SAAS;CAC/B,GAAG;AACL,CAAC;;;;;;;AASD,MAAa,0BAA0B,EAAE,OAAO;CAC9C,SAAS,EAAE,MAAM,iBAAiB,CAAC,CAAC,IAAI,CAAC;CACzC,GAAG;AACL,CAAC;;;;;;;;;AAWD,MAAa,oBAAoB,EAAE,MAAM;CACvC,EAAE,OAAO;CACT;CACA;AACF,CAAC;;AA0CD,MAAa,qBAAqB,EAAE,MAAM,CACxC,EAAE,OAAO,GACT,EAAE,OAAO;CACP,QAAQ,EAAE,OAAO;CACjB,iBAAiB,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;;;;;;;;;;;;;;;;;;CAkB9C,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;AAI5E,CAAC,CACH,CAAC;;;;;;;AASD,MAAa,oBAAoB,EAAE,OAAO;;CAExC,IAAI,EAAE,OAAO;CACb,MAAM,EAAE,OAAO;;CAEf,MAAM,EAAE,KAAK;EAAC;EAAU;EAAO;CAAQ,CAAC,CAAC,CAAC,SAAS;;CAEnD,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;;;;;;;;;;;;;;;;;;CAkB3B,OAAO,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,SAAS;AAC3C,CAAC;;;;;;;;;AAWD,MAAa,sBAAsB,EAChC,OAAO;CACN,SAAS,EAAE,QAAQ;;CAEnB,MAAM,EAAE,KAAK,CAAC,WAAW,WAAW,CAAC,CAAC,CAAC,SAAS;;;;;;;;;CAShD,OAAO,EAAE,MAAM,iBAAiB,CAAC,CAAC,SAAS;;CAE3C,SAAS,EACN,OAAO;;EAEN,MAAM,EAAE,KAAK;GAAC;GAAO;GAAQ;EAAM,CAAC,CAAC,CAAC,SAAS;EAC/C,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;;EAE3C,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,EAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;EAC3C,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS;;;;;;;;;;;EAW9C,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CACjD,CAAC,CAAC,CACD,SAAS;AACd,CAAC,CAAC,CAWD,aAAa,KAAK,QAAQ;CACzB,IAAI,IAAI,SAAS,IAAI,MAAM,SAAS,KAAK,IAAI,SAAS,WACpD,IAAI,SAAS;EACX,MAAM;EACN,MAAM,CAAC,OAAO;EACd,SACE,kDACG,IAAI,QAAQ,YAAY;CAE/B,CAAC;CAOH,IAAI,IAAI,SAAS,aAAa,KAAA,KAAa,IAAI,QAAQ,QAAQ,IAAI,QAAQ,SAAS,QAClF,IAAI,SAAS;EACX,MAAM;EACN,MAAM,CAAC,WAAW,UAAU;EAC5B,SACE,qCAAqC,IAAI,QAAQ,KAAK;CAG1D,CAAC;AAEL,CAAC;;;;;;;AASH,MAAa,mBAAmB,EAAE,KAAK,CAAC,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgC3D,MAAa,qBAAqB,EAAE,OAAO;CACzC,QAAQ;;CAGR,QAAQ,iBAAiB,SAAS;CAGlC,UAAU,EAAE,QAAQ,CAAC,CAAC,SAAS;CAC/B,OAAO,EAAE,QAAQ,CAAC,CAAC,SAAS;CAC5B,MAAM,EAAE,QAAQ,CAAC,CAAC,SAAS;;CAE3B,cAAc,EAAE,OAAO,CAAC,CAAC,IAAI,GAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;CACnD,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;CAC1C,aAAa,EAAE,QAAQ,CAAC,CAAC,SAAS;CAClC,SAAS,EAAE,KAAK;EAAC;EAAQ;EAAY;CAAM,CAAC,CAAC,CAAC,SAAS;CACvD,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;;;;;;;;;CAStC,UAAU,EAAE,QAAQ,CAAC,CAAC,SAAS;;CAE/B,aAAa,EAAE,QAAQ,CAAC,CAAC,SAAS;CAGlC,QAAQ,mBAAmB,SAAS;;CAEpC,YAAY,uBAAuB,SAAS;CAC5C,QAAQ,mBAAmB,SAAS;;CAEpC,SAAS,oBAAoB,SAAS;;;;;;;;;;;;;;;CAetC,OAAO,EAAE,QAAQ,CAAC,CAAC,SAAS;AAC9B,CAAC;;;;;;;ACrfD,MAAa,sBAAsB,EAAE,KAAK;CACxC;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;;;;;AAcD,MAAa,kBAAkB,EAAE,KAAK;CAEpC;CAEA;CACA;CACA;CACA;CAEA;CAEA;CACA;CAEA;CAEA;CACA;CACA;CACA;CACA;CAmBA;CAEA;CACA;CACA;CAcA;AACF,CAAC;;;;;AAQD,MAAa,oBAAoB,EAAE,KAAK;CACtC;CACA;CAsBA;AACF,CAAC;;;;;AAoBD,MAAa,aAAqD;CAChE,kBAAkB;EAAE,UAAU;EAAY,WAAW;CAAM;CAE3D,gBAAgB;EAAE,UAAU;EAAS,WAAW;CAAK;CACrD,iBAAiB;EAAE,UAAU;EAAS,WAAW;CAAK;CACtD,uBAAuB;EAAE,UAAU;EAAS,WAAW;CAAM;CAG7D,wBAAwB;EAAE,UAAU;EAAS,WAAW;CAAK;CAE7D,oBAAoB;EAAE,UAAU;EAAY,WAAW;CAAM;CAE7D,WAAW;EAAE,UAAU;EAAW,WAAW;CAAK;CAClD,mBAAmB;EAAE,UAAU;EAAW,WAAW;CAAK;CAI1D,gBAAgB;EAAE,UAAU;EAAQ,WAAW;CAAM;CAErD,mBAAmB;EAAE,UAAU;EAAO,WAAW;CAAM;CACvD,wBAAwB;EAAE,UAAU;EAAO,WAAW;CAAM;CAC5D,uBAAuB;EAAE,UAAU;EAAO,WAAW;CAAK;CAC1D,YAAY;EAAE,UAAU;EAAO,WAAW;CAAM;CAChD,WAAW;EAAE,UAAU;EAAO,WAAW;CAAM;CAI/C,oBAAoB;EAAE,UAAU;EAAO,WAAW;CAAM;CAExD,qBAAqB;EAAE,UAAU;EAAW,WAAW;CAAK;CAC5D,8BAA8B;EAAE,UAAU;EAAO,WAAW;CAAM;CAClE,eAAe;EAAE,UAAU;EAAW,WAAW;CAAK;CAWtD,iBAAiB;EAAE,UAAU;EAAO,WAAW;CAAM;AACvD;;;;;;;;;;;;;;;;;;AAmBA,MAAa,mBAAmB,EAC7B,OAAO;;CAEN,MAAM,EAAE,OAAO;CACf,SAAS,EAAE,OAAO;;CAElB,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;;CAE1B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;AAC9B,CAAC,CAAC,CASD,SAAS,EAAE,MAAM;CAAC,EAAE,OAAO;CAAG,EAAE,OAAO;CAAG,EAAE,QAAQ;AAAC,CAAC,CAAC;;AAK1D,MAAM,oBAAoB;AAE1B,SAAS,WAAW,QAAiC,KAAiC;CACpF,MAAM,IAAI,OAAO;CACjB,OAAO,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,IAAI,IAAI,KAAA;AAC3D;;;;;;;;;;;;;;;AAgBA,SAAgB,eAAe,OAA4B;CACzD,IAAI,UAAU,QAAQ,OAAO,UAAU,UACrC,OAAO;EAAE,MAAM;EAAW,SAAS,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,iBAAiB;CAAE;CAG/E,MAAM,MAAM;CACZ,MAAM,QAAS,IAAI,cAAc,KAAA;CAEjC,MAAM,OACJ,OAAO,IAAI,SAAS,WAChB,IAAI,OACJ,QACE,eACA,OAAO,IAAI,cAAc,WACvB,IAAI,YACJ;CAEV,MAAM,aACJ,OAAO,IAAI,YAAY,WACnB,IAAI,UACJ,OAAO,OAAO,YAAY,WACxB,MAAM,UACN,OAAO,KAAK;CAEpB,MAAM,OAAO,WAAW,KAAK,MAAM,MAAM,QAAQ,WAAW,OAAO,MAAM,IAAI,KAAA;CAC7E,MAAM,SAAS,WAAW,KAAK,QAAQ,KAAK,WAAW,KAAK,UAAU;CAGtE,MAAM,SAAoD,CAAC;CAC3D,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,GAAG,GACrC,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,YAAY,OAAO,MAAM,WAAW,OAAO,KAAK;CAG5F,OAAO;EACL,GAAG;EACH;EACA,SAAS,WAAW,MAAM,GAAG,iBAAiB;EAC9C,GAAI,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK;EACrC,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO;CAC3C;AACF;;;;;AAMA,MAAa,oBAAoB,EAAE,OAAO;CACxC,MAAM;CACN,SAAS,EAAE,OAAO;CAClB,WAAW,EAAE,QAAQ;CACrB,UAAU;CACV,OAAO,iBAAiB,SAAS;AACnC,CAAC;;;;;;;;;;;;;;;;;;AAqBD,MAAa,2BAA2B;AAExC,SAAgB,gBAAgB,MAAiB,SAAiB,OAA8B;CAC9F,MAAM,OAAO,WAAW;CACxB,OAAO;EACL;EACA;EACA,UAAU,KAAK;EACf,WAAW,KAAK;EAChB,GAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,eAAe,KAAK,EAAE;CAChE;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACvSA,MAAa,0BAA0B,EAAE,OAAO;;AAE9C,KAAK,EAAE,OAAO,EAChB,CAAC;;;;;;;;;;;;;;;;;AAmBD,MAAa,2BAA2B,EAAE,OAAO;;CAE/C,YAAY,EAAE,OAAO;;CAErB,eAAe,EAAE,OAAO;;CAExB,aAAa,EAAE,OAAO;AACxB,CAAC;;;;;;;;;;;;;;;;;;;;;;;AAyBD,MAAa,wBAAwB;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;;AASA,MAAa,0BAA0B,EAAE,OAAO;;CAE9C,QAAQ,EAAE,KAAK,qBAAqB;;CAEpC,QAAQ,EAAE,OAAO;;CAEjB,MAAM,EAAE,MAAM;EAAC,EAAE,OAAO;EAAG,EAAE,OAAO;EAAG,EAAE,QAAQ;CAAC,CAAC,CAAC,CAAC,SAAS;;CAE9D,IAAI,EAAE,MAAM;EAAC,EAAE,OAAO;EAAG,EAAE,OAAO;EAAG,EAAE,QAAQ;CAAC,CAAC,CAAC,CAAC,SAAS;AAC9D,CAAC;AAGD,MAAM,UAAU,IAAI,IAAY,qBAAqB;;AAGrD,SAAS,UAAU,GAA8C;CAC/D,MAAM,IAAI,OAAO;CACjB,OAAO,MAAM,YAAY,MAAM,YAAY,MAAM,YAC5C,IACD;AACN;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,oBAAoB,KAAwC;CAC1E,IAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM,OAAO;CACpD,MAAM,IAAI;CAEV,MAAM,SAAS,EAAE;CACjB,IAAI,OAAO,WAAW,YAAY,CAAC,QAAQ,IAAI,MAAM,GAAG,OAAO;CAI/D,MAAM,SADQ,MAAM,QAAQ,EAAE,KAAK,IAAI,EAAE,QAAQ,CAAC,EAAA,CAC7B,MAAM;CAE3B,OAAO;EACG;EACR,QAAQ,OAAO,EAAE,eAAe,WAAW,EAAE,aAAa;EAC1D,MAAM,UAAU,OAAO,IAAI;EAC3B,IAAI,UAAU,OAAO,EAAE;CACzB;AACF;;;;;;;;;;;AAYA,SAAgB,qBAAqB,KAAyC;CAC5E,IAAI,CAAC,MAAM,QAAQ,GAAG,KAAK,IAAI,WAAW,GAAG,OAAO;CACpD,MAAM,UAAU;CAChB,MAAM,OAAO,QAAQ,QAAQ,SAAS;CAEtC,MAAM,OAAO,MAAwB,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,IAAI,IAAI;CAEvF,OAAO;EACL,YAAY,QAAQ,QAAQ,KAAK,MAAM,MAAM,IAAI,EAAE,aAAa,GAAG,CAAC;EACpE,eAAe,IAAI,KAAK,kBAAkB;EAC1C,aAAa,IAAI,KAAK,gBAAgB;CACxC;AACF;;;;;;;;;;;;;ACzLA,MAAa,uBAAuB,EAAE,KAAK;CAAC;CAAU;CAAU;AAAQ,CAAC;;AAIzE,MAAa,mBAAmB,EAAE,KAAK,CAAC,QAAQ,KAAK,CAAC;;AAItD,MAAa,wBAAwB,EAAE,OAAO;CAC5C,UAAU,EAAE,KAAK;EAAC;EAAO;EAAW;EAAW;CAAS,CAAC;CACzD,SAAS,EAAE,KAAK;EAAC;EAAU;EAAS;EAAY;EAAY;CAAS,CAAC;CACtE,KAAK,EAAE,KAAK;EAAC;EAAY;EAAW;CAAa,CAAC;AACpD,CAAC;AAGD,MAAM,wBAAwB,EAAE,OAAO;;CAErC,WAAW,EAAE,OAAO;;CAEpB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC,CAAC,IAAI,CAAC;CACpD,YAAY;CACZ,SAAS;AACX,CAAC;;AAGD,MAAa,2BAA2B,EAAE,mBAAmB,WAAW,CACtE,sBAAsB,OAAO;CAC3B,SAAS,EAAE,QAAQ,UAAU;CAC7B,WAAW;CACX,QAAQ;CACR,QAAQ,EAAE,KAAK;EAAC;EAAgB;EAAmB;EAAkB;CAAqB,CAAC;AAC7F,CAAC,CAAC,CAAC,OAAO,GACV,sBAAsB,OAAO;CAC3B,SAAS,EAAE,QAAQ,aAAa;CAChC,WAAW,EAAE,KAAK;CAClB,QAAQ,EAAE,KAAK;CACf,QAAQ,EAAE,QAAQ,wBAAwB;CAC1C,WAAW,EAAE,QAAQ,uBAAuB;AAC9C,CAAC,CAAC,CAAC,OAAO,CACZ,CAAC;;;;;;;;;;;;;;;;;;;;;;;AAyBD,MAAa,wBAAwB,EAAE,OAAO;;;;;;;;CAQ5C,WAAW,EAAE,OAAO;;CAEpB,QAAQ;;;;;;;CAOR,WAAW,sBAAsB,SAAS;;CAE1C,YAAY,iBAAiB,SAAS;;CAEtC,SAAS,sBAAsB,SAAS;;;;;;;;CAQxC,UAAU,EAAE,OAAO;;;;;;CAMnB,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;;;;;;CAMlC,WAAW,EAAE,OAAO;;;;;;;;CAQpB,SAAS,EAAE,OAAO;AACpB,CAAC;;;;;;;;;;;;;;;;AAkBD,SAAgB,gBAAgB,KAAqD;CACnF,IAAI;EACF,MAAM,IAAI,IAAI,IAAI,GAAG;EACrB,OAAO;GAAE,WAAW,EAAE;GAAQ,SAAS,EAAE;EAAS;CACpD,QAAQ;EACN,OAAO;GAAE,WAAW;GAAI,SAAS;EAAG;CACtC;AACF;;;;;;ACtIA,MAAa,qBAAqB,EAAE,OAAO;CACzC,OAAO,EAAE,OAAO;CAChB,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;AAC/B,CAAC;;;;;;;AASD,MAAa,0BAA0B,EAAE,OAAO;;CAE9C,IAAI,EAAE,OAAO;;CAEb,QAAQ,EAAE,OAAO;;CAEjB,OAAO,EAAE,OAAO;AAClB,CAAC;;;;;;;;;;;;AAcD,MAAa,uBAAuB,EAAE,KAAK;CAEzC;CAEA;CAEA;CAEA;CAEA;CAEA;CAEA;CAOA;CAKA;AACF,CAAC;AAIkC,EAAE,KAAK;CACxC;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,MAAa,yBAAyB,EAAE,KAAK;CAAC;CAAa;CAAkB;AAAmB,CAAC;;AAGjG,MAAa,wBAAwB,EAAE,KAAK;CAAC;CAAS;CAAU;AAAY,CAAC;AAE7E,MAAM,sBAAsB;;CAE1B,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CACtC,UAAU;CACV,SAAS;;CAET,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;;CAEnC,aAAa,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;;CAEvC,QAAQ,gBAAgB,SAAS;AACnC;;;;;;;;AASA,MAAa,wBAAwB,EAAE,mBAAmB,SAAS;CACjE,EAAE,OAAO;EAAE,GAAG;EAAqB,OAAO,EAAE,QAAQ,UAAU;CAAE,CAAC,CAAC,CAAC,OAAO;CAC1E,EAAE,OAAO;EAAE,GAAG;EAAqB,OAAO,EAAE,QAAQ,YAAY;CAAE,CAAC,CAAC,CAAC,OAAO;CAC5E,EAAE,OAAO;EAAE,GAAG;EAAqB,OAAO,EAAE,QAAQ,YAAY;CAAE,CAAC,CAAC,CAAC,OAAO;CAC5E,EACG,OAAO;EACN,GAAG;EACH,OAAO,EAAE,QAAQ,WAAW;EAC5B,aAAa,EAAE,QAAQ,0BAA0B;CACnD,CAAC,CAAC,CACD,OAAO;CACV,EACG,OAAO;EACN,GAAG;EACH,OAAO,EAAE,QAAQ,QAAQ;EACzB,SAAS,EAAE,KAAK,CAAC,WAAW,oBAAoB,CAAC;CACnD,CAAC,CAAC,CACD,OAAO;CACV,EACG,OAAO;EACN,GAAG;EACH,OAAO,EAAE,QAAQ,WAAW;EAC5B,SAAS,EAAE,KAAK;GAAC;GAAkB;GAAa;EAAY,CAAC;CAC/D,CAAC,CAAC,CACD,OAAO;AACZ,CAAC;;;;;;;;;;AAYD,MAAa,oBAAoB,EAAE,mBAAmB,SAAS;CAK7D,EAAE,OAAO;EAAE,OAAO,EAAE,QAAQ,aAAa;EAAG,SAAS;CAAyB,CAAC;CAE/E,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,OAAO;EACxB,SAAS,EAAE,OAAO;GAChB,UAAU,EAAE,OAAO;;GAEnB,SAAS,EAAE,MAAM,kBAAkB;;;;;;;GAOnC,WAAW,EAAE,MAAM,uBAAuB,CAAC,CAAC,SAAS;EACvD,CAAC;CACH,CAAC;CACD,EAAE,OAAO;EAAE,OAAO,EAAE,QAAQ,MAAM;EAAG,SAAS,EAAE,OAAO,CAAC,CAAC;CAAE,CAAC;CAC5D,EAAE,OAAO;EAAE,OAAO,EAAE,QAAQ,OAAO;EAAG,SAAS,EAAE,OAAO,CAAC,CAAC;CAAE,CAAC;CAC7D,EAAE,OAAO;EAAE,OAAO,EAAE,QAAQ,OAAO;EAAG,SAAS,EAAE,OAAO,CAAC,CAAC;CAAE,CAAC;CAG7D,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,YAAY;;;;;;;;EAQ7B,SAAS,EAAE,OAAO;GAAE,MAAM,EAAE,OAAO;GAAG,UAAU,EAAE,OAAO;EAAE,CAAC;CAC9D,CAAC;CACD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,cAAc;EAC/B,SAAS,EAAE,OAAO;GAAE,QAAQ,EAAE,OAAO;GAAG,OAAO,EAAE,QAAQ;EAAE,CAAC;CAC9D,CAAC;CACD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,SAAS;EAC1B,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;CACxC,CAAC;CACD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,QAAQ;EACzB,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;CACxC,CAAC;CAED,EAAE,OAAO;EAAE,OAAO,EAAE,QAAQ,SAAS;EAAG,SAAS,EAAE,OAAO,CAAC,CAAC;CAAE,CAAC;CAE/D,EAAE,OAAO;EAAE,OAAO,EAAE,QAAQ,SAAS;EAAG,SAAS,EAAE,OAAO,CAAC,CAAC;CAAE,CAAC;CAG/D,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,eAAe;EAChC,SAAS,EAAE,OAAO;GAChB,OAAO,EAAE,OAAO;;GAEhB,MAAM,EAAE,QAAQ;EAClB,CAAC;CACH,CAAC;CAOD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,gBAAgB;EACjC,SAAS,EAAE,OAAO;;AAEhB,IAAI,EAAE,OAAO,CAAC,CAAC,SAAS,EAC1B,CAAC;CACH,CAAC;CACD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,OAAO;EACxB,SAAS;CACX,CAAC;CAED,EAAE,OAAO;EAAE,OAAO,EAAE,QAAQ,iBAAiB;EAAG,SAAS,EAAE,OAAO,CAAC,CAAC;CAAE,CAAC;CAGvE,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,gBAAgB;EACjC,SAAS,EAAE,OAAO;GAChB,SAAS,EAAE,OAAO;GAClB,aAAa,EAAE,OAAO;;;;;;;;;;;;;;;;;GAiBtB,QAAQ,gBAAgB,SAAS;GACjC,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;EACnC,CAAC;CACH,CAAC;CACD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,kBAAkB;EACnC,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;CAC5C,CAAC;CACD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,iBAAiB;EAClC,SAAS,EAAE,OAAO;GAChB,UAAU,EAAE,OAAO;;GAEnB,QAAQ,gBAAgB,SAAS;EACnC,CAAC;CACH,CAAC;CAKD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,UAAU;EAC3B,SAAS;CACX,CAAC;CAgBD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,eAAe;EAChC,SAAS,EAAE,OAAO;GAChB,MAAM;GACN,SAAS,EAAE,OAAO;;;;;;;;GAQlB,IAAI,EAAE,OAAO;;GAEb,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;;GAEjC,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS;;;;;;;;;;;GAWnC,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS;EACrC,CAAC;CACH,CAAC;CAgBD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,SAAS;EAC1B,SAAS,EAAE,OAAO;GAChB,OAAO,EAAE,KAAK,CAAC,SAAS,KAAK,CAAC;;GAE9B,UAAU,EAAE,OAAO;;GAEnB,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;;;;;;;;;;;GAWhC,MAAM,EAAE,KAAK;IAAC;IAAY;IAAc;GAAM,CAAC,CAAC,CAAC,SAAS;EAC5D,CAAC;CACH,CAAC;CAwBD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,gBAAgB;EACjC,SAAS,EAAE,OAAO;;GAEhB,UAAU,EAAE,QAAQ;;GAEpB,QAAQ;;;;;;;;;;;GAWR,aAAa,EAAE,QAAQ;EACzB,CAAC;CACH,CAAC;CAgCD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,cAAc;EAC/B,SAAS,EAAE,OAAO;;;;;;;;;GAShB,UAAU,EAAE,QAAQ;;;;;;;;;;;GAWpB,QAAQ,EAAE,KAAK;IAAC;IAAW;IAAS;GAAO,CAAC;;GAE5C,OAAO,EAAE,OAAO;;GAEhB,YAAY,EAAE,OAAO;;;;;;;GAOrB,QAAQ,EAAE,OAAO;EACnB,CAAC;CACH,CAAC;CASD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,aAAa;EAC9B,SAAS,EAAE,OAAO;GAChB,UAAU,EAAE,QAAQ;GACpB,QAAQ,EAAE,QAAQ,gBAAgB;GAClC,YAAY,EAAE,OAAO;EACvB,CAAC;CACH,CAAC;CAiBD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,cAAc;EAC/B,SAAS,EAAE,OAAO;;;;;;;;;;GAUhB,UAAU,EAAE,QAAQ;;;;;;;;;;;;;;GAcpB,WAAW,EAAE,OAAO;EACtB,CAAC;CACH,CAAC;CAkBD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,eAAe;EAChC,SAAS;CACX,CAAC;CAaD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,YAAY;EAC7B,SAAS;CACX,CAAC;CASD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,aAAa;EAC9B,SAAS;CACX,CAAC;CAUD,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ,YAAY;EAC7B,SAAS;CACX,CAAC;AACH,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2ED,SAAgB,yBACd,UACA,IACkD;CAElD,OAAO;EACL,OAAO;EACP,SAAS;GACP,MAAM;GACN,SACE,kBANQ,YAAY,OAMI;GAE1B;GACA,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,eAAe,SAAS;EAC9D;CACF;AACF;AAEA,SAAgB,aAAa,KAAwD;CAMnF,OAAO;EAAE,OAAO;EAAS,SAJvB,OAAO,QAAQ,YAAY,QAAQ,QAAQ,iBAAiB,MACvD,IAAqC,cACtC,gBAAgB,cAAc,eAAe,QAAQ,IAAI,UAAU,QAAQ,GAAG;CAEtC;AAChD;;;;;;;;;ACnrBA,MAAa,gBAAgB,EAAE,mBAAmB,UAAU;CAC1D,EAAE,OAAO;EAAE,QAAQ,EAAE,QAAQ,MAAM;EAAG,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;CAAE,CAAC;CACvE,EAAE,OAAO;EAAE,QAAQ,EAAE,QAAQ,OAAO;EAAG,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;CAAE,CAAC;CAExE,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,MAAM;EACxB,QAAQ,EAAE,OAAO;;GAEf,MAAM,EAAE,OAAO;;GAEf,MAAM,EAAE,KAAK,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,SAAS;EAC/C,CAAC;CACH,CAAC;CAED,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,WAAW;EAC7B,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;CACvD,CAAC;CAED,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,UAAU;EAC5B,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;CACzC,CAAC;CAED,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,iBAAiB;EACnC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,GAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;CACxD,CAAC;CAED,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,YAAY;;EAE9B,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,CAAC;CACtE,CAAC;CAED,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,aAAa;;;;;;EAM/B,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;CAClE,CAAC;CAED,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,WAAW;;;;;;;;;;;EAW7B,QAAQ,EAAE,OAAO,EAAE,QAAQ,mBAAmB,CAAC;CACjD,CAAC;CAED,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,MAAM;EACxB,QAAQ,EAAE,OAAO,EAAE,QAAQ,kBAAkB,CAAC;CAChD,CAAC;CAED,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,WAAW;EAC7B,QAAQ,EACL,OAAO;;AAEN,cAAc,EAAE,QAAQ,CAAC,CAAC,SAAS,EACrC,CAAC,CAAC,CACD,SAAS;CACd,CAAC;CAGD,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,aAAa;;EAE/B,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;CAC9C,CAAC;CACD,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,mBAAmB;;EAErC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;CAC3C,CAAC;CACD,EAAE,OAAO;EAAE,QAAQ,EAAE,QAAQ,cAAc;EAAG,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;CAAE,CAAC;CAE/E,EAAE,OAAO;EAAE,QAAQ,EAAE,QAAQ,SAAS;EAAG,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;CAAE,CAAC;CAC1E,EAAE,OAAO;EAAE,QAAQ,EAAE,QAAQ,iBAAiB;EAAG,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;CAAE,CAAC;CAClF,EAAE,OAAO;EAAE,QAAQ,EAAE,QAAQ,gBAAgB;EAAG,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;CAAE,CAAC;AACnF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AEpED,MAAa,mBAA2B;AASxC,MAAM,YAAY;;;;;;;;;;;AAYlB,SAAgB,aAAa,SAAuC;CAClE,MAAM,IAAI,UAAU,KAAK,OAAO;CAChC,IAAI,CAAC,GAAG,OAAO;CAEf,OAAO;EACL,OAAO,OAAO,EAAE,EAAE;EAClB,OAAO,OAAO,EAAE,EAAE;EAClB,OAAO,OAAO,EAAE,EAAE;CACpB;AACF;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,qBAAqB,aAAqB,eAAgC;CACxF,MAAM,OAAO,aAAa,WAAW;CACrC,MAAM,SAAS,aAAa,aAAa;CACzC,IAAI,CAAC,QAAQ,CAAC,QAAQ,OAAO;CAE7B,IAAI,KAAK,UAAU,OAAO,OAAO,OAAO;CACxC,IAAI,KAAK,UAAU,GAAG,OAAO,KAAK,UAAU,OAAO;CAEnD,OAAO;AACT;;;;ACtFA,MAAa,qBAAqB,EAAE,KAAK;CAAC;CAAW;CAAS;CAAY;AAAO,CAAC;;;;;;;;AAUlF,MAAM,gBAAgB;;CAEpB,SAAS,EAAE,OAAO;CAClB,IAAI,EAAE,OAAO;;CAEb,WAAW,EAAE,OAAO;AACtB;;;;;;;;;;;AAYA,SAAgB,qBACd,MACA,SAOC;CACD,OAAO,EAAE,OAAO;EACd,GAAG;EACH,MAAM,EAAE,QAAQ,IAAI;EACpB;CACF,CAAC;AACH;;AAGA,MAAa,wBAAwB,qBAAqB,WAAW,aAAa;;AAIlF,MAAa,sBAAsB,qBAAqB,SAAS,iBAAiB;;;;;AAOlF,MAAa,yBAAyB,qBAAqB,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;;AAInF,MAAa,sBAAsB,qBAAqB,SAAS,iBAAiB;;;;;;;AASlF,MAAa,iBAAiB,EAAE,mBAAmB,QAAQ;CACzD;CACA;CACA;CACA;AACF,CAAC;;;;;;AAkBD,SAAS,aAAa,MAAoB;CACxC,OAAO;EACL,SAAS,KAAK,WAAW;EACzB,IAAI,KAAK;EACT,WAAW,KAAK;CAClB;AACF;;AAGA,SAAgB,gBAAgB,SAAkB,MAAqC;CACrF,OAAO;EAAE,GAAG,aAAa,IAAI;EAAG,MAAM;EAAW;CAAQ;AAC3D;;AAGA,SAAgB,cAAc,SAAsB,MAAmC;CACrF,OAAO;EAAE,GAAG,aAAa,IAAI;EAAG,MAAM;EAAS;CAAQ;AACzD;;AAGA,SAAgB,iBAAiB,MAAsC;CACrE,OAAO;EAAE,GAAG,aAAa,IAAI;EAAG,MAAM;EAAY,SAAS,CAAC;CAAE;AAChE;;AAGA,SAAgB,cAAc,SAAsB,MAAmC;CACrF,OAAO;EAAE,GAAG,aAAa,IAAI;EAAG,MAAM;EAAS;CAAQ;AACzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACtFA,SAAgB,sBAAsB,QAAkD;CACtF,IAAI,CAAC,QAAQ,OAAO;EAAE,QAAQ;EAAS,UAAU,CAAC;CAAE;CACpD,IAAI,OAAO,WAAW,UAAU,OAAO;EAAE;EAAQ,UAAU,CAAC;CAAE;CAE9D,MAAM,QAAQ,OAAO;CACrB,IAAI,CAAC,OAAO,OAAO;EAAE,QAAQ,OAAO;EAAQ,UAAU,CAAC;CAAE;CAMzD,MAAM,QAAQ,CAAC,GAAI,OAAO,mBAAmB,CAAC,CAAE,CAAC,CAAC,QAAQ;CAC1D,MAAM,KAAK,OAAO,MAAM;CACxB,MAAM,WAA6B,CAAC;CACpC,KAAK,MAAM,OAAO,OAAO,OAAO,OAAO,UAAU,MAAM,IAAI;CAE3D,OAAO;EAAE,QAAQ,OAAO;EAAQ;CAAS;AAC3C;;;;;;;;;;;;;;;ACxCA,MAAa,UAAsD;;AAEjE,oBAAoB;CAClB,UAAU;CACV,OAAO;CACP,MAAM;CACN,UAAU;CACV,aAAa;CACb,aAAa;AACf,EACF;;;;;;;;;;;;;AAcA,SAAgB,cAAc,QAAgC,QAAoC;CAChG,IAAI,CAAC,QAAQ,OAAO;CAIpB,MAAM,SAAkC,EAAE,GAAG,QAAQ,QAAQ;CAC7D,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC9C,IAAI,UAAU,KAAA,GAAW,OAAO,OAAO;CAGzC,OAAO;AACT"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@video-lab/protocol",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Video Lab Player 契约层:Zod schema 定义命令 / 事件 / 错误码 / 配置,所有包的唯一事实源",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.cts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.mts",
|
|
14
|
+
"default": "./dist/index.mjs"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"zod": "^3.23.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"tsdown": "^0.22.9",
|
|
32
|
+
"typescript": "~5.9.0",
|
|
33
|
+
"vitest": "^4.1.10"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+ssh://git@mgit.lgroup.co/hqdf/web/x9-live-player.git",
|
|
38
|
+
"directory": "packages/protocol"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsdown src/index.ts --format esm,cjs --dts --clean",
|
|
45
|
+
"dev": "tsdown src/index.ts --format esm,cjs --dts --watch --no-clean",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json --noEmit"
|
|
49
|
+
}
|
|
50
|
+
}
|