rivmux 0.5.0 → 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/README.md CHANGED
@@ -1,18 +1,10 @@
1
1
  # Rivmux Player
2
2
 
3
- Public browser player facade for Rivmux low-latency live playback.
3
+ `rivmux` Rivmux 面向用户的浏览器播放器包。它在 Dedicated Worker 中读取 HTTP-FLV,将音视频转封装为 fragmented MP4,并通过 Worker MSE 连接到 `<video>` 元素。当前没有主线程 MSE 降级路径。
4
4
 
5
- `rivmux` loads HTTP-FLV streams in a dedicated worker, transmuxes supported
6
- audio/video into fragmented MP4, and attaches the resulting media stream to a
7
- browser `<video>` element.
5
+ ## 安装
8
6
 
9
- M1 supports only the dedicated-worker MSE runtime path. Main-thread MSE is not available.
10
-
11
- The first-release support contract is HTTP-FLV with H.264/AVC video and optional
12
- AAC-LC audio. MPEG-TS is roadmap work. Existing HEVC, AV1, and Opus core paths
13
- are experimental internals and are not supported public inputs.
14
-
15
- ## Install
7
+ 当前发布格式为 ESM-only,不提供 CommonJS、UMD 或全局变量构建。
16
8
 
17
9
  ```sh
18
10
  pnpm add rivmux
@@ -22,10 +14,11 @@ pnpm add rivmux
22
14
  npm install rivmux
23
15
  ```
24
16
 
25
- ## Basic Usage
17
+ 当前发布目标是浏览器 ESM Bundler。Vite 已完成验证,其他 bundler 暂不形成兼容承诺。包的 `exports` 只提供 `import` 和类型条件,不提供 CommonJS、UMD 或全局变量入口。Node.js 仅支持 ESM 导入以及 SSR/能力探测调用,不提供 Node 播放运行时。
18
+
19
+ ## 基本用法
26
20
 
27
- Always wait for `attach()` before calling `start()`. `attach()` initializes the
28
- worker and connects the internal `MediaSourceHandle` to the video element.
21
+ 调用 `start()` 前必须等待 `attach()` 完成。`attach()` 会初始化 Worker,并把内部 `MediaSourceHandle` 连接到 video 元素。
29
22
 
30
23
  ```ts
31
24
  import { RivmuxPlayer } from 'rivmux'
@@ -33,7 +26,7 @@ import { RivmuxPlayer } from 'rivmux'
33
26
  const video = document.querySelector<HTMLVideoElement>('#player')
34
27
 
35
28
  if (!video) {
36
- throw new Error('Missing video element')
29
+ throw new Error('未找到 video 元素')
37
30
  }
38
31
 
39
32
  const player = new RivmuxPlayer('https://example.com/live.flv', {
@@ -43,7 +36,7 @@ const player = new RivmuxPlayer('https://example.com/live.flv', {
43
36
  })
44
37
 
45
38
  player.on('mediaInfo', (info) => {
46
- console.log('media info', info)
39
+ console.log('媒体信息', info)
47
40
  })
48
41
 
49
42
  player.on('error', (error) => {
@@ -53,31 +46,87 @@ player.on('error', (error) => {
53
46
  await player.attach(video)
54
47
  await player.start()
55
48
 
56
- // Later:
49
+ // 不再使用时:
57
50
  await player.stop()
58
51
  await player.destroy()
59
52
  ```
60
53
 
61
- ## Player Lifecycle
54
+ ## 能力探测
55
+
56
+ 创建播放器前可同步读取基础运行环境和解码能力:
57
+
58
+ ```ts
59
+ import { getCapabilities, isSupported } from 'rivmux'
60
+
61
+ if (!isSupported()) {
62
+ throw new Error('当前环境不具备 Rivmux 基础运行能力')
63
+ }
64
+
65
+ const capabilities = getCapabilities()
66
+ console.log(capabilities.decoding.stableProfiles.hevcAac)
67
+ ```
68
+
69
+ ```ts
70
+ type SupportStatus = 'supported' | 'unsupported' | 'unknown'
71
+
72
+ type RivmuxCapabilities = {
73
+ supported: boolean
74
+ runtime: {
75
+ dedicatedWorker: boolean
76
+ workerMse: boolean
77
+ fetchStreaming: boolean
78
+ readableStream: boolean
79
+ webAssembly: boolean
80
+ }
81
+ decoding: {
82
+ video: {
83
+ avc: SupportStatus
84
+ hevc: SupportStatus
85
+ av1: SupportStatus
86
+ }
87
+ audio: {
88
+ aac: SupportStatus
89
+ opus: SupportStatus
90
+ }
91
+ stableProfiles: {
92
+ avcAac: SupportStatus
93
+ hevcAac: SupportStatus
94
+ }
95
+ }
96
+ }
97
+ ```
98
+
99
+ `isSupported()` 始终等于 `getCapabilities().supported`。这里的 `supported` 是基础运行能力结果,只表示 Dedicated Worker、Worker MSE、流式 Fetch、ReadableStream 和 WebAssembly 等条件可用,不保证具体媒体流能够播放。下面解码矩阵中的 `supported` 是另一层 codec 前置探测结果,二者都不等于实际播放成功。
100
+
101
+ 解码矩阵通过同步的 `MediaSource.isTypeSupported()` 对代表性 codec 执行前置判断:`supported` 表示环境报告支持,`unsupported` 表示环境明确拒绝,`unknown` 表示 API 不可用或无法可靠判断。该结果不会改变输入的 Stable、Experimental 或不支持边界,也不替代真实流校验。Rivmux 取得实际 codec、profile 和 level 后,仍会使用准确 MIME 做最终 MSE 校验。
102
+
103
+ 能力探测不会创建 Worker、发起网络请求或修改 DOM。在 SSR 和 Node.js 环境中调用不会抛错:基础能力为 `false`,无法判断的解码能力为 `unknown`。
104
+
105
+ ## 播放器生命周期
62
106
 
63
107
  ```ts
64
108
  const player = new RivmuxPlayer(url, options)
65
109
  ```
66
110
 
67
- | Call | Description |
68
- | --------------------------------- | -------------------------------------------------------------------------------------------------------- |
69
- | `new RivmuxPlayer(url, options?)` | Creates a player instance for one stream URL. |
70
- | `await player.attach(video)` | Attaches the player to a `<video>` element and prepares the worker/MSE pipeline. |
71
- | `await player.start()` | Starts loading, transmuxing, buffering, and playback control. Call after `attach()`. |
72
- | `await player.stop()` | Stops loading, detaches the media source, and keeps the instance reusable. |
73
- | `await player.destroy()` | Releases the worker, timers, listeners, and video source. The instance cannot be reused after this call. |
74
- | `player.on(type, listener)` | Subscribes to a player event. |
75
- | `player.off(type, listener)` | Removes a previously registered listener. |
111
+ | 调用 | 含义 |
112
+ | --------------------------------- | ------------------------------------------------------------ |
113
+ | `new RivmuxPlayer(url, options?)` | 为一个流地址创建播放器实例。 |
114
+ | `await player.attach(video)` | 绑定 `<video>` 元素并准备 Worker/MSE 链路。 |
115
+ | `await player.start()` | 启动加载、转封装、缓冲与播放控制;必须在 `attach()` 后调用。 |
116
+ | `await player.stop()` | 停止加载并解绑媒体源;实例可以再次启动。 |
117
+ | `await player.destroy()` | 释放 Worker、定时器、监听器和媒体源;实例不可再用。 |
118
+ | `player.on(type, listener)` | 订阅播放器事件。 |
119
+ | `player.off(type, listener)` | 移除先前注册的监听器。 |
120
+
121
+ `attach()` 完成只表示媒体源句柄已经连接;`start()` 完成只表示 Worker 已建立本次启动所需的内部加载、转封装和 MSE 会话。两者都不表示已经解析出媒体信息、已经追加首个媒体片段,也不表示 `<video>` 已触发 `canplay` 或开始播放。请通过 `mediaInfo`、`stats`、`error` 和视频元素事件观察后续结果。
76
122
 
77
- ## Options
123
+ 同一启动过程中的并发 `start()` 调用会等待同一个操作,不会重复发送启动命令。播放器已经启动后再次调用 `start()` 会直接完成。若 `stop()` 或 `destroy()` 在启动确认前开始,待处理的 `start()` 会以 `RIVMUX_START_CANCELLED` 拒绝,而停止或销毁操作仍会正常完成。终止错误会使 `start()` 以相同错误码拒绝。
78
124
 
79
- All options are optional. Missing fields are merged with
80
- `DEFAULT_RIVMUX_PLAYER_OPTIONS`.
125
+ 上述规则收紧了旧版本中“发送启动命令后立即完成”的时序;依赖旧时序的调用方应改为等待 `start()`,并将媒体就绪逻辑放到对应事件中。
126
+
127
+ ## 配置项
128
+
129
+ 所有配置均为可选项,缺省字段会由播放器内部默认值补齐。
81
130
 
82
131
  ```ts
83
132
  import { RivmuxPlayer } from 'rivmux'
@@ -96,130 +145,182 @@ const player = new RivmuxPlayer('https://example.com/live.flv', {
96
145
  },
97
146
  network: {
98
147
  credentials: 'include',
148
+ readIdleTimeoutMs: 10000,
99
149
  headers: {
100
150
  Authorization: 'Bearer token',
101
151
  },
102
152
  retry: {
103
153
  maxAttempts: 5,
104
154
  backoffMs: 500,
155
+ maxBackoffMs: 8000,
156
+ jitterRatio: 0.2,
105
157
  },
106
158
  },
107
159
  diagnostics: {
108
160
  statsIntervalMs: 1000,
109
- debug: false,
110
161
  },
111
162
  })
112
163
  ```
113
164
 
114
165
  ### `playback`
115
166
 
116
- | Option | Default | Description |
117
- | ---------- | ------- | --------------------------------------------------------------------------------- |
118
- | `autoPlay` | `true` | Lets the runtime request `video.play()` when enough startup buffer is available. |
119
- | `muted` | `false` | Sets `video.muted`. Many browsers require muted playback for autoplay with audio. |
167
+ | 配置项 | 默认值 | 含义 |
168
+ | ---------- | ------- | ---------------------------------------------------------- |
169
+ | `autoPlay` | `true` | 启动缓冲足够时,由运行时请求 `video.play()`。 |
170
+ | `muted` | `false` | 设置 `video.muted`;浏览器通常只允许带音频的静音自动播放。 |
120
171
 
121
172
  ### `latency`
122
173
 
123
- Values are in seconds.
174
+ 以下数值单位均为秒。
124
175
 
125
- | Option | Default | Description |
126
- | ------------------ | ------- | ---------------------------------------------------------------------------------------------- |
127
- | `startupBuffer` | `0.35` | Buffered duration required before automatic playback starts. |
128
- | `target` | `1.2` | Desired live latency. The runtime uses this when resuming fetches and restoring playback rate. |
129
- | `max` | `2.5` | Maximum tolerated live latency before the runtime seeks closer to the live edge. |
130
- | `maxForwardBuffer` | `4` | Forward buffer threshold where the loader may pause to avoid excessive buffering. |
131
- | `backwardBuffer` | `1.5` | Amount of buffer to keep behind the current playhead during cleanup. |
176
+ | 配置项 | 默认值 | 含义 |
177
+ | ------------------ | ------ | ------------------------------------------------ |
178
+ | `startupBuffer` | `0.35` | 发起自动播放前需要的缓冲时长。 |
179
+ | `target` | `1.2` | 目标直播延迟;恢复读取和播放速率时使用。 |
180
+ | `max` | `2.5` | 最大可接受直播延迟;超过后向直播边缘追帧。 |
181
+ | `maxForwardBuffer` | `4` | 前向缓冲上限;Loader 可暂停读取以避免过度缓冲。 |
182
+ | `backwardBuffer` | `1.5` | 清理缓冲时在当前播放位置之前保留的历史缓冲时长。 |
132
183
 
133
184
  ### `network`
134
185
 
135
- | Option | Default | Description |
136
- | ------------------- | --------------- | ----------------------------------------------------------------- |
137
- | `headers` | `{}` | Extra request headers for the HTTP-FLV stream. |
138
- | `credentials` | `'same-origin'` | Fetch credentials mode for stream requests. |
139
- | `retry.maxAttempts` | `3` | Maximum stream request attempts before reporting a network error. |
140
- | `retry.backoffMs` | `500` | Base retry delay in milliseconds. |
186
+ | 配置项 | 默认值 | 含义 |
187
+ | -------------------- | --------------- | ---------------------------------------------------------------------------------------- |
188
+ | `headers` | `{}` | HTTP-FLV 请求附带的额外请求头。 |
189
+ | `credentials` | `'same-origin'` | Fetch 请求的 credentials 模式。 |
190
+ | `readIdleTimeoutMs` | `10000` | 主动读取期间持续无数据的超时时间;Loader 因背压暂停时不计时。 |
191
+ | `retry.maxAttempts` | `3` | 单次故障恢复周期允许的连接总次数,包含发生故障的当前连接;成功恢复后下一次故障重新计数。 |
192
+ | `retry.backoffMs` | `500` | 指数退避的基础延迟,单位为毫秒。 |
193
+ | `retry.maxBackoffMs` | `8000` | 应用抖动后的最大退避延迟,单位为毫秒。 |
194
+ | `retry.jitterRatio` | `0.2` | 对称抖动比例,取值范围为 `0` 至 `1`;`0` 表示关闭抖动。 |
141
195
 
142
196
  ### `runtime`
143
197
 
144
- | Option | Default | Description |
145
- | ----------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------- |
146
- | `preferWorkerMse` | `true` | Must remain `true` in M1. Main-thread MSE fallback is not implemented. |
147
- | `workerUrl` | bundled worker URL | Advanced override for the packaged Dedicated Worker script URL. |
148
- | `wasmUrl` | bundled WASM module | Advanced override for the packaged WASM binary URL. It must match the Worker release that contains the wasm-bindgen glue. |
198
+ `runtime.workerUrl` `runtime.wasmUrl` 是 Experimental 资产部署覆盖项;它们嵌套在稳定的 `RivmuxPlayerOptions` 形状中,但不改变当前固定的 Worker MSE runtime,也不承诺跨 bundler、CSP 或部署方式的长期兼容性。
199
+
200
+ | 配置项 | 默认值 | 含义 |
201
+ | ----------- | ------------------ | ----------------------------------------------------------------------------- |
202
+ | `workerUrl` | 包内 Worker URL | 高级配置,用于覆盖包内 Dedicated Worker 脚本 URL。 |
203
+ | `wasmUrl` | 包内 WASM 模块 URL | 高级配置,用于覆盖 WASM URL;必须与包含 wasm-bindgen 胶水的 Worker 版本匹配。 |
149
204
 
150
- ### Advanced Asset Deployment
205
+ ### Worker/WASM 资产部署
151
206
 
152
- Most applications should omit `runtime.workerUrl` and `runtime.wasmUrl`. The
153
- default package assets are tracked by Vite and emitted with the application
154
- build.
207
+ 多数应用无需设置 `runtime.workerUrl` `runtime.wasmUrl`。默认资产 URL 会被 Vite 等打包工具跟踪,并随应用构建输出。
155
208
 
156
- Set these options only when your deployment serves Rivmux assets from a fixed
157
- public path or CDN:
209
+ 只有在固定公共路径或 CDN 部署 Rivmux 资产时才需要覆盖:
158
210
 
159
211
  ```ts
160
212
  const player = new RivmuxPlayer('https://example.com/live.flv', {
161
213
  runtime: {
162
- workerUrl: 'https://cdn.example.com/rivmux/0.4.0/rivmux-runtime-worker.js',
163
- wasmUrl: 'https://cdn.example.com/rivmux/0.4.0/rivmux-transmux-core.wasm',
214
+ workerUrl: 'https://cdn.example.com/rivmux/0.5.0/rivmux-runtime-worker.js',
215
+ wasmUrl: 'https://cdn.example.com/rivmux/0.5.0/rivmux-transmux-core.wasm',
164
216
  },
165
217
  })
166
218
  ```
167
219
 
168
- `workerUrl` does not derive `wasmUrl`; when you override both assets, publish
169
- them as a matching release pair. Treat both URLs as trusted executable asset
170
- locations and never construct them from untrusted input. The host application
171
- is responsible for compatible CSP and CORS policies, serving the WASM asset as
172
- `application/wasm`, and cache-busting the Worker/WASM pair together.
220
+ `workerUrl` 不会推导 `wasmUrl`。覆盖两项时必须发布同一版本的 Worker/WASM 资产对,并使用相同缓存版本策略。两者都是可执行资产地址,不得由不可信输入拼接。宿主应用需配置兼容的 CSP CORS,并以 `application/wasm` 提供 WASM 文件。
173
221
 
174
222
  ### `diagnostics`
175
223
 
176
- | Option | Default | Description |
177
- | ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------ |
178
- | `statsIntervalMs` | `1000` | Requested stats interval in milliseconds. Runtime reporting is clamped internally for stable video-state feedback. |
179
- | `debug` | `false` | Enables debug-oriented behavior where supported by the runtime. |
224
+ | 配置项 | 默认值 | 含义 |
225
+ | ----------------- | ------ | ------------------------------------------------------------ |
226
+ | `statsIntervalMs` | `1000` | 请求的统计上报间隔,单位为毫秒;运行时会在内部限制实际范围。 |
180
227
 
181
- ## Events
228
+ ## 事件
182
229
 
183
230
  ```ts
184
- import type { MediaInfo, PlayerError, PlayerStats, PlayerWarning } from 'rivmux'
231
+ import type { MediaInfo, PlayerError, PlayerStats, PlayerWarning, ReconnectInfo, RecoveryInfo } from 'rivmux'
185
232
 
186
- player.on('ready', () => {})
233
+ player.on('initialized', () => {})
187
234
  player.on('mediaInfo', (info: MediaInfo) => {})
188
235
  player.on('stats', (stats: PlayerStats) => {})
189
236
  player.on('warning', (warning: PlayerWarning) => {})
237
+ player.on('reconnecting', (info: ReconnectInfo) => {})
238
+ player.on('recovered', (info: RecoveryInfo) => {})
190
239
  player.on('error', (error: PlayerError) => {})
191
240
  player.on('stopped', () => {})
192
241
  player.on('destroyed', () => {})
193
242
  ```
194
243
 
195
- | Event | Payload | Description |
196
- | ----------- | --------------- | ----------------------------------------------------------------------- |
197
- | `ready` | `undefined` | The worker/runtime is initialized. |
198
- | `mediaInfo` | `MediaInfo` | Stream container and codec metadata has been detected. |
199
- | `stats` | `PlayerStats` | Runtime diagnostics such as bytes, buffer, latency, and playback state. |
200
- | `warning` | `PlayerWarning` | Recoverable issue reported by the runtime. |
201
- | `error` | `PlayerError` | Runtime, network, demux, codec, mux, MSE, or unsupported-feature error. |
202
- | `stopped` | `undefined` | The stream has stopped and the media source has been detached. |
203
- | `destroyed` | `undefined` | The worker/runtime has been destroyed. |
244
+ | 事件 | 数据 | 含义 |
245
+ | -------------- | --------------- | ------------------------------------------------------------------------------------------------------------ |
246
+ | `initialized` | `undefined` | `attach()` 期间所选 runtime 完成 `init` 后触发;同一 runtime 实例只触发一次,不表示 `canplay` 或 `playing`。 |
247
+ | `mediaInfo` | `MediaInfo` | 已识别媒体容器和 codec 信息。 |
248
+ | `stats` | `PlayerStats` | 字节数、缓冲、延迟和播放状态等运行时统计。 |
249
+ | `warning` | `PlayerWarning` | Runtime 报告的可恢复问题。 |
250
+ | `reconnecting` | `ReconnectInfo` | 已确定执行下一次连接,并给出连接序号、最大次数、延迟和故障原因。 |
251
+ | `recovered` | `RecoveryInfo` | 新会话的首个媒体片段已经成功追加;仅建立 HTTP 连接不会触发该事件。 |
252
+ | `error` | `PlayerError` | Runtime、网络、demux、codec、mux、MSE 或环境不支持错误。 |
253
+ | `stopped` | `undefined` | 播放已停止,媒体源已解绑。 |
254
+ | `destroyed` | `undefined` | Worker runtime 已销毁。 |
255
+
256
+ 用户事件监听器中的异常属于宿主应用异常,不属于 Rivmux 播放错误,也不会转换为 `PlayerError`。单个监听器抛出异常时,其他监听器仍会继续执行,`stop()`、`destroy()` 等生命周期 Promise 也会按内部状态正常完成。Rivmux 会优先通过平台的 `globalThis.reportError()` 报告该异常;平台不支持时使用 `console.error()` 降级报告。
257
+
258
+ ## 自动播放拒绝
259
+
260
+ 浏览器可能根据自动播放策略拒绝 Rivmux 请求的 `video.play()`。无用户手势的直播场景建议设置 `playback.muted: true`,以提高自动播放成功率。拒绝会产生非终止 warning `RIVMUX_AUTOPLAY_REJECTED`,其 `cause` 保留浏览器错误的 `name` 和 `message`;该 warning 不会中断网络加载、转封装或缓冲,也不会触发 `error` 事件。
261
+
262
+ Rivmux 在同一播放会话中不会自动重复调用 `play()`。调用方可以在按钮点击等真实用户手势处理器内直接恢复:
263
+
264
+ ```ts
265
+ button.addEventListener('click', () => {
266
+ void video.play()
267
+ })
268
+ ```
269
+
270
+ 设置 `playback.autoPlay: false` 后,Rivmux 不会主动请求启动播放,也不会产生该 warning。
271
+
272
+ ## 直播连接恢复
273
+
274
+ Rivmux 会为以下直播网络故障重建 Loader、转封装核心和 MSE 播放会话:
275
+
276
+ - HTTP `408`、`429` 和 `5xx`。
277
+ - 建连或读取阶段的网络异常。
278
+ - 已建立直播流后的异常 EOF。
279
+ - 主动读取期间超过 `network.readIdleTimeoutMs` 未收到数据。
280
+
281
+ HTTP `401`、`403` 和其他不可恢复的 `4xx` 不会重试。媒体解析、codec、mux 和 MSE 错误也不进入网络恢复流程。恢复次数耗尽后会产生一次终止错误 `RIVMUX_RECONNECT_EXHAUSTED`。
282
+
283
+ 恢复采用指数退避、最大延迟和抖动。等待重连或重建会话期间调用 `stop()` 或 `destroy()` 会立即取消恢复,不会继续创建连接。恢复会更换 `MediaSourceHandle`,因此可能出现短暂中断和时间线重置;当前不承诺无缝续播或跨连接连续时间线。
284
+
285
+ ## Codec 支持边界
286
+
287
+ | 输入 | 等级 | 说明 |
288
+ | ---------------------------------------- | ------------ | ---------------------------------- |
289
+ | HTTP-FLV + AVC/H.264 + AAC-LC | Stable | 受浏览器基础 MSE 能力约束 |
290
+ | Enhanced HTTP-FLV + HEVC/`hvc1` + AAC-LC | Stable | 浏览器解码能力是条件化的 |
291
+ | Enhanced HTTP-FLV + AV1 | Experimental | 实现层实验能力,组合与兼容性未承诺 |
292
+ | Enhanced HTTP-FLV + Opus | Experimental | 实现层实验能力,组合与兼容性未承诺 |
293
+ | MPEG-TS | 不支持 | 不属于当前产品输入边界 |
294
+
295
+ HEVC Stable 的具体范围是单视频轨、固定 codec 配置、Enhanced FLV `SequenceStart`、`CodedFrames` 和 HEVC `CodedFramesX`,输出 sample entry 为 `hvc1`。最终解码能力取决于浏览器、操作系统、设备以及具体 HEVC profile、level、bit depth 和 chroma format;Rivmux 不维护固定 profile/level allowlist。结构合法但环境不支持准确 MIME 时会产生终止错误 `RIVMUX_UNSUPPORTED_MSE_CODEC`。
296
+
297
+ `hev1`、多轨 HEVC、播放期间动态 codec 配置切换和 HEVC + Opus 不属于 Stable 范围。AV1 与 Opus 仍为 Experimental;当前未承诺它们与其他音视频 codec 的组合、浏览器兼容矩阵或稳定错误语义。能力矩阵中的 `supported` 不会把它们提升为 Stable。
204
298
 
205
- ## Type Imports
299
+ ## 类型导入
206
300
 
207
- The player package re-exports the key public types from `@rivmux/protocol`:
301
+ 主包会重新导出用户需要的主要类型:
208
302
 
209
303
  ```ts
210
304
  import type {
211
305
  DiagnosticsOptions,
306
+ DecodingCapabilities,
212
307
  LatencyOptions,
213
308
  MediaInfo,
214
309
  NetworkOptions,
215
310
  PlaybackOptions,
216
311
  PlayerError,
312
+ PlayerErrorCause,
217
313
  PlayerStats,
218
314
  PlayerWarning,
315
+ ReconnectInfo,
316
+ ReconnectReason,
317
+ RecoveryInfo,
318
+ RivmuxCapabilities,
219
319
  RivmuxPlayerOptions,
320
+ RuntimeCapabilities,
220
321
  RuntimeOptions,
322
+ SupportStatus,
221
323
  } from 'rivmux'
222
324
  ```
223
325
 
224
- Use `normalizePlayerOptions(options)` if you need to inspect a fully populated
225
- options object with defaults applied.
326
+ 主包会导出上例中的 Experimental `RuntimeOptions`,但它不属于 Stable 兼容承诺。配置归一化和错误构造函数属于内部实现,未导出为应用 API。
package/dist/index.d.ts CHANGED
@@ -1,22 +1,14 @@
1
- import { RuntimeWorkerFactory } from "@rivmux/runtime-worker";
2
- import { DiagnosticsOptions, LatencyOptions, MediaInfo, NetworkOptions, NormalizedRivmuxPlayerOptions, NormalizedRivmuxPlayerOptions as NormalizedRivmuxPlayerOptions$1, PlaybackOptions, PlayerError, PlayerError as PlayerError$1, PlayerErrorKind, PlayerErrorKind as PlayerErrorKind$1, PlayerEventListener, PlayerEventListener as PlayerEventListener$1, PlayerEventMap, PlayerEventType, PlayerEventType as PlayerEventType$1, PlayerStats, PlayerWarning, RivmuxPlayerOptions, RivmuxPlayerOptions as RivmuxPlayerOptions$1, RuntimeOptions } from "@rivmux/protocol";
1
+ import { DecodingCapabilities, DiagnosticsOptions, LatencyOptions, MediaInfo, NetworkOptions, PlaybackOptions, PlayerError, PlayerErrorCause, PlayerErrorKind, PlayerEventListener, PlayerEventListener as PlayerEventListener$1, PlayerEventMap, PlayerEventType, PlayerEventType as PlayerEventType$1, PlayerStats, PlayerWarning, ReconnectInfo, ReconnectReason, RecoveryInfo, RivmuxCapabilities, RivmuxCapabilities as RivmuxCapabilities$1, RivmuxPlayerOptions, RivmuxPlayerOptions as RivmuxPlayerOptions$1, RuntimeCapabilities, RuntimeOptions, SupportStatus } from "@rivmux/protocol";
3
2
  //#region src/player.d.ts
4
- type RivmuxPlayerInternals = {
5
- workerFactory?: RuntimeWorkerFactory;
6
- detectRuntime?: () => PlayerError$1 | undefined;
7
- idFactory?: () => string;
8
- };
9
3
  /**
10
4
  * Public browser player facade for one HTTP-FLV stream.
11
5
  *
12
6
  * Create one instance per stream URL, call `attach(video)` first, then
13
7
  * `start()`. Call `destroy()` when the instance is no longer needed.
14
8
  */
15
- declare class RivmuxPlayer {
9
+ export declare class RivmuxPlayer {
16
10
  /** Original stream URL passed to the constructor. */
17
11
  readonly url: string;
18
- /** Fully normalized options with defaults applied. */
19
- readonly options: NormalizedRivmuxPlayerOptions$1;
20
12
  private readonly id;
21
13
  private readonly events;
22
14
  private readonly workerFactory;
@@ -26,6 +18,8 @@ declare class RivmuxPlayer {
26
18
  private state;
27
19
  private lifecycleGeneration;
28
20
  private terminalError?;
21
+ private autoplayWarningEmitted;
22
+ private startPromise?;
29
23
  private stopPromise?;
30
24
  private destroyPromise?;
31
25
  /**
@@ -33,7 +27,7 @@ declare class RivmuxPlayer {
33
27
  *
34
28
  * The instance does not start network loading until `start()` is called.
35
29
  */
36
- constructor(url: string, options?: RivmuxPlayerOptions$1, internals?: RivmuxPlayerInternals);
30
+ constructor(url: string, options?: RivmuxPlayerOptions$1);
37
31
  /**
38
32
  * Attaches this player to a video element and prepares the worker/MSE pipe.
39
33
  *
@@ -41,11 +35,14 @@ declare class RivmuxPlayer {
41
35
  */
42
36
  attach(video: HTMLVideoElement): Promise<void>;
43
37
  /**
44
- * Starts loading, transmuxing, buffering, and playback control.
38
+ * Waits until the worker has created the transmux core and scheduled stream
39
+ * consumption for this playback session.
45
40
  *
46
- * Requires a successful `attach(video)` call first.
41
+ * This does not wait for media data, `canplay`, or `video.play()`. Requires a
42
+ * successful `attach(video)` call first.
47
43
  */
48
44
  start(): Promise<void>;
45
+ private performStart;
49
46
  /**
50
47
  * Stops loading and detaches the current media source.
51
48
  *
@@ -74,14 +71,12 @@ declare class RivmuxPlayer {
74
71
  private enterFatalErrorState;
75
72
  private assertOperational;
76
73
  private isLifecycleOperationCurrent;
74
+ private assertStartOperationCurrent;
77
75
  }
78
76
  //#endregion
79
- //#region src/options.d.ts
80
- declare const DEFAULT_RIVMUX_PLAYER_OPTIONS: NormalizedRivmuxPlayerOptions$1;
81
- declare function normalizePlayerOptions(options?: RivmuxPlayerOptions$1): NormalizedRivmuxPlayerOptions$1;
77
+ //#region src/feature-detect.d.ts
78
+ export declare function getCapabilities(): RivmuxCapabilities$1;
79
+ export declare function isSupported(): boolean;
82
80
  //#endregion
83
- //#region src/errors.d.ts
84
- declare function createPlayerError(kind: PlayerErrorKind$1, code: string, message: string, terminal: boolean, cause?: unknown): PlayerError$1;
85
- //#endregion
86
- export { DEFAULT_RIVMUX_PLAYER_OPTIONS, type DiagnosticsOptions, type LatencyOptions, type MediaInfo, type NetworkOptions, type NormalizedRivmuxPlayerOptions, type PlaybackOptions, type PlayerError, type PlayerErrorKind, type PlayerEventListener, type PlayerEventMap, type PlayerEventType, type PlayerStats, type PlayerWarning, RivmuxPlayer, type RivmuxPlayerOptions, type RuntimeOptions, createPlayerError, normalizePlayerOptions };
81
+ export type { DecodingCapabilities, DiagnosticsOptions, LatencyOptions, MediaInfo, NetworkOptions, PlaybackOptions, PlayerError, PlayerErrorCause, PlayerErrorKind, PlayerEventListener, PlayerEventMap, PlayerEventType, PlayerStats, PlayerWarning, ReconnectInfo, ReconnectReason, RecoveryInfo, RivmuxCapabilities, RivmuxPlayerOptions, RuntimeCapabilities, RuntimeOptions, SupportStatus };
87
82
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/player.ts","../src/options.ts","../src/errors.ts"],"mappings":";;;KAoBY;EACV,gBAAgB;EAChB,sBAAsB;EACtB;;;;;;;;cASW;;WAEF;;WAGA,SAAS;mBACD;mBACA;mBACA;mBACA;mBACA;UACT;UACA;UACA;UACA;UACA;UACA;;;;;;cAOI,aAAa,UAAU,uBAAqB,YAAW;;;;;;EAc7D,OAAO,OAAO,mBAAmB;;;;;;EA4BjC,SAAS;;;;;;;EAkCf,QAAQ;UAyBM;;;;;;;EAgCR,WAAW;UAeH;;EA6Bd,GAAG,UAAU,mBAAiB,MAAM,GAAG,UAAU,sBAAoB;;EAKrE,IAAI,UAAU,mBAAiB,MAAM,GAAG,UAAU,sBAAoB;UAI9D;UAgBA;UA2CA;UAaA;UAcM;UAcN;UAWA;UAmBA;;;;cC/WG,+BAA+B;iBA6B5B,uBAAuB,UAAS,wBAA2B;;;iBC/B3D,kBAAkB,MAAM,mBAAiB,cAAc,iBAAiB,mBAAmB,kBAAkB"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/player.ts","../src/feature-detect.ts"],"mappings":";;;;;;;;qBAgCa;;WAEF;mBAIQ;mBACA;mBACA;mBACA;mBACA;UACT;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;;;;;EAOI,YAAA,aAAa,UAAU;;;;;;EAkB7B,OAAO,OAAO,mBAAmB;;;;;;;;EA8BvC,SAAS;UAiCK;;;;;;;EA0Bd,QAAQ;UAyBM;;;;;;;EAkCR,WAAW;UAeH;;EA6Bd,GAAG,UAAU,mBAAiB,MAAM,GAAG,UAAU,sBAAoB;;EAKrE,IAAI,UAAU,mBAAiB,MAAM,GAAG,UAAU,sBAAoB;UAI9D;UAgBA;UAmDA;UAiBA;UAcM;UA4BN;UAWA;UAmBA;UAIA;;;;wBCrUM,mBAAmB;wBAInB"}
package/dist/index.js CHANGED
@@ -15,12 +15,28 @@ var PlayerEventEmitter = class {
15
15
  this.listeners.get(type)?.delete(listener);
16
16
  }
17
17
  emit(type, payload) {
18
- for (const listener of this.listeners.get(type) ?? []) listener(payload);
18
+ for (const listener of this.listeners.get(type) ?? []) try {
19
+ listener(payload);
20
+ } catch (error) {
21
+ reportListenerError(error);
22
+ }
19
23
  }
20
24
  clear() {
21
25
  this.listeners.clear();
22
26
  }
23
27
  };
28
+ function reportListenerError(error) {
29
+ try {
30
+ const reportError = globalThis.reportError;
31
+ if (typeof reportError === "function") {
32
+ reportError(error);
33
+ return;
34
+ }
35
+ } catch {}
36
+ try {
37
+ globalThis.console?.error(error);
38
+ } catch {}
39
+ }
24
40
  //#endregion
25
41
  //#region src/errors.ts
26
42
  function createPlayerError(kind, code, message, terminal, cause) {
@@ -34,24 +50,124 @@ function createPlayerError(kind, code, message, terminal, cause) {
34
50
  code,
35
51
  message,
36
52
  terminal,
37
- cause
53
+ cause: serializeCause(cause)
38
54
  };
39
55
  }
40
56
  function playerErrorToException(error) {
41
- const exception = error.cause instanceof Error ? error.cause : new Error(error.message);
57
+ const exception = new Error(error.message);
42
58
  exception.name = error.code;
43
59
  return exception;
44
60
  }
61
+ function serializeCause(cause) {
62
+ if (cause instanceof Error) return {
63
+ name: cause.name,
64
+ message: cause.message
65
+ };
66
+ if (typeof cause === "object" && cause !== null) {
67
+ const value = cause;
68
+ return {
69
+ name: typeof value.name === "string" ? value.name : "Error",
70
+ message: typeof value.message === "string" ? value.message : String(cause)
71
+ };
72
+ }
73
+ return {
74
+ name: "Error",
75
+ message: String(cause)
76
+ };
77
+ }
45
78
  //#endregion
46
79
  //#region src/feature-detect.ts
47
- function detectMainThreadRuntime() {
48
- if (typeof Worker === "undefined") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WORKER", "Dedicated Worker is not available in this runtime.", true);
49
- if (typeof fetch !== "function") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_FETCH", "Fetch is not available in this runtime.", true);
50
- if (typeof ReadableStream === "undefined") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_READABLE_STREAM", "ReadableStream is not available in this runtime.", true);
51
- if (typeof WebAssembly === "undefined") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WASM", "WebAssembly is not available in this runtime.", true);
52
- if (typeof MediaSource === "undefined") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_MSE", "MediaSource is not available in this runtime.", true);
53
- if (MediaSource.canConstructInDedicatedWorker !== true) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WORKER_MSE", "MediaSource cannot be constructed in a dedicated worker in this runtime.", true);
54
- if (typeof MediaSource.isTypeSupported !== "function") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_MSE_TYPE_CHECK", "MediaSource.isTypeSupported is not available in this runtime.", true);
80
+ const MIME = {
81
+ avc: "video/mp4; codecs=\"avc1.42E01E\"",
82
+ hevc: "video/mp4; codecs=\"hvc1.1.6.L93.B0\"",
83
+ av1: "video/mp4; codecs=\"av01.0.08M.08\"",
84
+ aac: "audio/mp4; codecs=\"mp4a.40.2\"",
85
+ opus: "audio/mp4; codecs=\"opus\""
86
+ };
87
+ function safelyRead(read, fallback) {
88
+ try {
89
+ return read();
90
+ } catch {
91
+ return fallback;
92
+ }
93
+ }
94
+ function readCapabilityEnvironment() {
95
+ const dedicatedWorker = safelyRead(() => typeof Worker === "function", false);
96
+ const fetchAvailable = safelyRead(() => typeof fetch === "function", false);
97
+ const responseBodyAvailable = safelyRead(() => typeof Response === "function" && Response.prototype !== void 0 && "body" in Response.prototype, false);
98
+ const readableStream = safelyRead(() => typeof ReadableStream === "function", false);
99
+ const webAssembly = safelyRead(() => typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function", false);
100
+ const mediaSourceConstructor = safelyRead(() => typeof MediaSource === "function" ? MediaSource : void 0, void 0);
101
+ const mediaSource = mediaSourceConstructor !== void 0;
102
+ const workerMseConstructable = mediaSource && safelyRead(() => mediaSourceConstructor.canConstructInDedicatedWorker === true, false);
103
+ const isTypeSupported = mediaSource ? safelyRead(() => typeof mediaSourceConstructor.isTypeSupported === "function" ? mediaSourceConstructor.isTypeSupported.bind(mediaSourceConstructor) : void 0, void 0) : void 0;
104
+ return {
105
+ dedicatedWorker,
106
+ fetchStreaming: fetchAvailable && responseBodyAvailable,
107
+ readableStream,
108
+ webAssembly,
109
+ mediaSource,
110
+ workerMseConstructable,
111
+ isTypeSupported
112
+ };
113
+ }
114
+ function probeMime(isTypeSupported, mime) {
115
+ if (isTypeSupported === void 0) return "unknown";
116
+ try {
117
+ return isTypeSupported(mime) ? "supported" : "unsupported";
118
+ } catch {
119
+ return "unknown";
120
+ }
121
+ }
122
+ function combineProfile(video, audio) {
123
+ if (video === "unsupported" || audio === "unsupported") return "unsupported";
124
+ if (video === "supported" && audio === "supported") return "supported";
125
+ return "unknown";
126
+ }
127
+ function getCapabilitiesForEnvironment(environment) {
128
+ const runtime = {
129
+ dedicatedWorker: environment.dedicatedWorker,
130
+ workerMse: environment.workerMseConstructable && environment.isTypeSupported !== void 0,
131
+ fetchStreaming: environment.fetchStreaming,
132
+ readableStream: environment.readableStream,
133
+ webAssembly: environment.webAssembly
134
+ };
135
+ const video = {
136
+ avc: probeMime(environment.isTypeSupported, MIME.avc),
137
+ hevc: probeMime(environment.isTypeSupported, MIME.hevc),
138
+ av1: probeMime(environment.isTypeSupported, MIME.av1)
139
+ };
140
+ const audio = {
141
+ aac: probeMime(environment.isTypeSupported, MIME.aac),
142
+ opus: probeMime(environment.isTypeSupported, MIME.opus)
143
+ };
144
+ return {
145
+ supported: Object.values(runtime).every(Boolean),
146
+ runtime,
147
+ decoding: {
148
+ video,
149
+ audio,
150
+ stableProfiles: {
151
+ avcAac: combineProfile(video.avc, audio.aac),
152
+ hevcAac: combineProfile(video.hevc, audio.aac)
153
+ }
154
+ }
155
+ };
156
+ }
157
+ function getCapabilities() {
158
+ return getCapabilitiesForEnvironment(readCapabilityEnvironment());
159
+ }
160
+ function isSupported() {
161
+ return getCapabilities().supported;
162
+ }
163
+ function detectMainThreadRuntime(environment = readCapabilityEnvironment()) {
164
+ if (!environment.dedicatedWorker) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WORKER", "Dedicated Worker is not available in this runtime.", true);
165
+ if (!environment.fetchStreaming) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_FETCH", "Fetch is not available in this runtime.", true);
166
+ if (!environment.readableStream) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_READABLE_STREAM", "ReadableStream is not available in this runtime.", true);
167
+ if (!environment.webAssembly) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WASM", "WebAssembly is not available in this runtime.", true);
168
+ if (!environment.mediaSource) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_MSE", "MediaSource is not available in this runtime.", true);
169
+ if (!environment.workerMseConstructable) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WORKER_MSE", "MediaSource cannot be constructed in a dedicated worker in this runtime.", true);
170
+ if (environment.isTypeSupported === void 0) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_MSE_TYPE_CHECK", "MediaSource.isTypeSupported is not available in this runtime.", true);
55
171
  }
56
172
  //#endregion
57
173
  //#region src/options.ts
@@ -70,16 +186,16 @@ const DEFAULT_RIVMUX_PLAYER_OPTIONS = {
70
186
  network: {
71
187
  headers: {},
72
188
  credentials: "same-origin",
189
+ readIdleTimeoutMs: 1e4,
73
190
  retry: {
74
191
  maxAttempts: 3,
75
- backoffMs: 500
192
+ backoffMs: 500,
193
+ maxBackoffMs: 8e3,
194
+ jitterRatio: .2
76
195
  }
77
196
  },
78
- runtime: { preferWorkerMse: true },
79
- diagnostics: {
80
- statsIntervalMs: 1e3,
81
- debug: false
82
- }
197
+ runtime: {},
198
+ diagnostics: { statsIntervalMs: 1e3 }
83
199
  };
84
200
  function normalizePlayerOptions(options = {}) {
85
201
  const normalizedOptions = {
@@ -114,7 +230,7 @@ function normalizePlayerOptions(options = {}) {
114
230
  }
115
231
  function validateNormalizedOptions(options) {
116
232
  validateLatencyOptions(options);
117
- validateRuntimeOptions(options);
233
+ validateNetworkOptions(options);
118
234
  }
119
235
  function validateLatencyOptions(options) {
120
236
  const latency = options.latency;
@@ -126,12 +242,18 @@ function validateLatencyOptions(options) {
126
242
  if (latency.max < latency.target) throwOptionError("runtime", "RIVMUX_INVALID_LATENCY_OPTION", "latency.max must be greater than or equal to latency.target.");
127
243
  if (latency.maxForwardBuffer < latency.target) throwOptionError("runtime", "RIVMUX_INVALID_LATENCY_OPTION", "latency.maxForwardBuffer must be greater than or equal to latency.target.");
128
244
  }
129
- function validateRuntimeOptions(options) {
130
- if (options.runtime.preferWorkerMse !== true) throwOptionError("unsupported", "RIVMUX_UNSUPPORTED_MAIN_THREAD_MSE_FALLBACK", "Main-thread MSE fallback is not implemented; runtime.preferWorkerMse must remain true.");
245
+ function validateNetworkOptions(options) {
246
+ const network = options.network;
247
+ const retry = network.retry;
248
+ assertPositiveInteger(network.readIdleTimeoutMs, "network.readIdleTimeoutMs");
249
+ assertPositiveInteger(retry.maxAttempts, "network.retry.maxAttempts");
250
+ assertNonNegativeInteger(retry.backoffMs, "network.retry.backoffMs");
251
+ assertNonNegativeInteger(retry.maxBackoffMs, "network.retry.maxBackoffMs");
252
+ if (retry.maxBackoffMs < retry.backoffMs) throwOptionError("runtime", "RIVMUX_INVALID_NETWORK_OPTION", "network.retry.maxBackoffMs must be greater than or equal to network.retry.backoffMs.");
253
+ if (!Number.isFinite(retry.jitterRatio) || retry.jitterRatio < 0 || retry.jitterRatio > 1) throwOptionError("runtime", "RIVMUX_INVALID_NETWORK_OPTION", "network.retry.jitterRatio must be a finite number between 0 and 1.");
131
254
  }
132
255
  function normalizeRuntimeOptions(options) {
133
256
  return {
134
- preferWorkerMse: options.runtime?.preferWorkerMse ?? DEFAULT_RIVMUX_PLAYER_OPTIONS.runtime.preferWorkerMse,
135
257
  ...options.runtime?.workerUrl === void 0 ? {} : { workerUrl: options.runtime.workerUrl },
136
258
  ...options.runtime?.wasmUrl === void 0 ? {} : { wasmUrl: options.runtime.wasmUrl }
137
259
  };
@@ -142,6 +264,12 @@ function assertFiniteNonNegativeLatency(value, field) {
142
264
  function assertFinitePositiveLatency(value, field) {
143
265
  if (!Number.isFinite(value) || value <= 0) throwOptionError("runtime", "RIVMUX_INVALID_LATENCY_OPTION", `${field} must be a finite number greater than 0.`);
144
266
  }
267
+ function assertPositiveInteger(value, field) {
268
+ if (!Number.isInteger(value) || value < 1) throwOptionError("runtime", "RIVMUX_INVALID_NETWORK_OPTION", `${field} must be an integer greater than or equal to 1.`);
269
+ }
270
+ function assertNonNegativeInteger(value, field) {
271
+ if (!Number.isInteger(value) || value < 0) throwOptionError("runtime", "RIVMUX_INVALID_NETWORK_OPTION", `${field} must be an integer greater than or equal to 0.`);
272
+ }
145
273
  function throwOptionError(kind, code, message) {
146
274
  throw playerErrorToException(createPlayerError(kind, code, message, true));
147
275
  }
@@ -190,7 +318,10 @@ var PlaybackController = class {
190
318
  if (video === void 0) return {
191
319
  type: action.type,
192
320
  accepted: false,
193
- message: "No video element is attached."
321
+ error: {
322
+ name: "NotAttachedError",
323
+ message: "No video element is attached."
324
+ }
194
325
  };
195
326
  try {
196
327
  switch (action.type) {
@@ -200,9 +331,7 @@ var PlaybackController = class {
200
331
  case "set-playback-rate":
201
332
  video.playbackRate = action.playbackRate;
202
333
  break;
203
- case "seek":
204
- video.currentTime = action.targetTime;
205
- break;
334
+ case "seek": video.currentTime = action.targetTime;
206
335
  }
207
336
  return {
208
337
  type: action.type,
@@ -212,7 +341,7 @@ var PlaybackController = class {
212
341
  return {
213
342
  type: action.type,
214
343
  accepted: false,
215
- message: cause instanceof Error ? cause.message : String(cause)
344
+ error: normalizePlaybackControlError(cause)
216
345
  };
217
346
  }
218
347
  }
@@ -242,6 +371,30 @@ var PlaybackController = class {
242
371
  };
243
372
  }
244
373
  };
374
+ function normalizePlaybackControlError(cause) {
375
+ if (cause instanceof Error) return {
376
+ name: safelyStringifyErrorField(() => cause.name, "Error"),
377
+ message: safelyStringifyErrorField(() => cause.message, "Unknown playback control error.")
378
+ };
379
+ try {
380
+ return {
381
+ name: "Error",
382
+ message: String(cause)
383
+ };
384
+ } catch {
385
+ return {
386
+ name: "Error",
387
+ message: "Unknown playback control error."
388
+ };
389
+ }
390
+ }
391
+ function safelyStringifyErrorField(read, fallback) {
392
+ try {
393
+ return String(read());
394
+ } catch {
395
+ return fallback;
396
+ }
397
+ }
245
398
  function getDroppedFrames(video) {
246
399
  const quality = video.getVideoPlaybackQuality?.();
247
400
  if (quality !== void 0 && Number.isFinite(quality.droppedVideoFrames)) return quality.droppedVideoFrames;
@@ -259,7 +412,7 @@ function getDroppedFrames(video) {
259
412
  var RivmuxPlayer = class {
260
413
  /** Original stream URL passed to the constructor. */
261
414
  url;
262
- /** Fully normalized options with defaults applied. */
415
+ /** @internal Fully normalized options with defaults applied. */
263
416
  options;
264
417
  id;
265
418
  events = new PlayerEventEmitter();
@@ -270,14 +423,12 @@ var RivmuxPlayer = class {
270
423
  state = "idle";
271
424
  lifecycleGeneration = 0;
272
425
  terminalError;
426
+ autoplayWarningEmitted = false;
427
+ startPromise;
273
428
  stopPromise;
274
429
  destroyPromise;
275
- /**
276
- * Creates a player instance for one stream URL.
277
- *
278
- * The instance does not start network loading until `start()` is called.
279
- */
280
- constructor(url, options, internals = {}) {
430
+ constructor(url, options, internals) {
431
+ internals ??= {};
281
432
  this.url = url;
282
433
  this.options = normalizePlayerOptions(options);
283
434
  this.id = internals.idFactory?.() ?? createPlayerId();
@@ -306,22 +457,42 @@ var RivmuxPlayer = class {
306
457
  this.state = "attached";
307
458
  }
308
459
  /**
309
- * Starts loading, transmuxing, buffering, and playback control.
460
+ * Waits until the worker has created the transmux core and scheduled stream
461
+ * consumption for this playback session.
310
462
  *
311
- * Requires a successful `attach(video)` call first.
463
+ * This does not wait for media data, `canplay`, or `video.play()`. Requires a
464
+ * successful `attach(video)` call first.
312
465
  */
313
- async start() {
314
- this.assertOperational("start");
466
+ start() {
467
+ try {
468
+ this.assertOperational("start");
469
+ } catch (cause) {
470
+ return Promise.reject(cause);
471
+ }
472
+ if (!this.playback.attached || this.workerClient === void 0) return Promise.reject(playerErrorToException(createPlayerError("runtime", "RIVMUX_START_REQUIRES_ATTACH", "start() requires a previously attached video element.", false)));
473
+ if (this.state === "started") return Promise.resolve();
474
+ if (this.startPromise !== void 0) return this.startPromise;
315
475
  const lifecycleGeneration = this.lifecycleGeneration;
316
- if (!this.playback.attached || this.workerClient === void 0) throw playerErrorToException(createPlayerError("runtime", "RIVMUX_START_REQUIRES_ATTACH", "start() requires a previously attached video element.", false));
317
- if (this.state === "started") return;
318
- if (this.state === "stopped") {
319
- await this.workerClient.waitForMediaSourceHandle({ type: "attach-media-source" });
320
- if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) return;
321
- this.state = "attached";
476
+ const previousState = this.state;
477
+ this.state = "starting";
478
+ const starting = this.performStart(this.workerClient, lifecycleGeneration, previousState === "stopped").finally(() => {
479
+ if (this.startPromise === starting) this.startPromise = void 0;
480
+ });
481
+ this.startPromise = starting;
482
+ return starting;
483
+ }
484
+ async performStart(workerClient, lifecycleGeneration, restart) {
485
+ try {
486
+ if (restart) {
487
+ await workerClient.waitForMediaSourceHandle({ type: "attach-media-source" });
488
+ this.assertStartOperationCurrent(lifecycleGeneration);
489
+ }
490
+ await workerClient.waitForStarted({ type: "start" });
491
+ this.assertStartOperationCurrent(lifecycleGeneration);
492
+ } catch (cause) {
493
+ if (this.isLifecycleOperationCurrent(lifecycleGeneration) && this.state === "starting") this.state = restart ? "stopped" : "attached";
494
+ throw toPublicStartError(cause);
322
495
  }
323
- if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) return;
324
- this.workerClient.post({ type: "start" });
325
496
  this.state = "started";
326
497
  this.startVideoStateReporting();
327
498
  }
@@ -348,6 +519,7 @@ var RivmuxPlayer = class {
348
519
  const workerClient = this.workerClient;
349
520
  if (workerClient === void 0) {
350
521
  if (this.isLifecycleOperationCurrent(lifecycleGeneration)) {
522
+ this.autoplayWarningEmitted = false;
351
523
  this.state = "stopped";
352
524
  this.events.emit("stopped", void 0);
353
525
  }
@@ -362,6 +534,7 @@ var RivmuxPlayer = class {
362
534
  }
363
535
  if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) return;
364
536
  this.playback.detachSource();
537
+ this.autoplayWarningEmitted = false;
365
538
  this.state = "stopped";
366
539
  }
367
540
  /**
@@ -426,11 +599,12 @@ var RivmuxPlayer = class {
426
599
  if (this.state === "destroying" && message.type !== "destroyed" || this.state === "fatal-error") return;
427
600
  switch (message.type) {
428
601
  case "ready":
429
- this.events.emit("ready", void 0);
602
+ this.events.emit("initialized", void 0);
430
603
  return;
431
604
  case "media-source-handle":
432
605
  this.attachMediaSourceHandle(message.handle);
433
606
  return;
607
+ case "started": return;
434
608
  case "media-info":
435
609
  this.events.emit("mediaInfo", message.mediaInfo);
436
610
  return;
@@ -440,6 +614,12 @@ var RivmuxPlayer = class {
440
614
  case "warning":
441
615
  this.events.emit("warning", message.warning);
442
616
  return;
617
+ case "reconnecting":
618
+ this.events.emit("reconnecting", message.info);
619
+ return;
620
+ case "recovered":
621
+ this.events.emit("recovered", message.info);
622
+ return;
443
623
  case "error":
444
624
  if (message.error.terminal) this.enterFatalErrorState(message.error);
445
625
  this.events.emit("error", message.error);
@@ -456,6 +636,10 @@ var RivmuxPlayer = class {
456
636
  }
457
637
  }
458
638
  attachMediaSourceHandle(handle) {
639
+ if (this.state === "started") {
640
+ this.autoplayWarningEmitted = false;
641
+ this.playback.detachSource();
642
+ }
459
643
  if (!this.playback.attachMediaSourceHandle(handle)) {
460
644
  const error = createPlayerError("runtime", "RIVMUX_ATTACH_HANDLE_WITHOUT_VIDEO", "Worker returned a MediaSourceHandle before a video element was attached.", true);
461
645
  this.events.emit("error", error);
@@ -483,6 +667,14 @@ var RivmuxPlayer = class {
483
667
  type: "playback-control-result",
484
668
  result
485
669
  });
670
+ if (action.type === "play" && action.reason === "startup-buffer-ready" && this.options.playback.autoPlay && !result.accepted && !this.autoplayWarningEmitted) {
671
+ this.autoplayWarningEmitted = true;
672
+ this.events.emit("warning", {
673
+ code: "RIVMUX_AUTOPLAY_REJECTED",
674
+ message: "浏览器拒绝了自动播放;请在用户手势处理器中调用 video.play() 恢复播放。",
675
+ cause: result.error
676
+ });
677
+ }
486
678
  }
487
679
  enterFatalErrorState(error) {
488
680
  if (this.state === "destroyed" || this.state === "destroying" || this.state === "fatal-error") return;
@@ -500,7 +692,19 @@ var RivmuxPlayer = class {
500
692
  isLifecycleOperationCurrent(generation) {
501
693
  return generation === this.lifecycleGeneration && this.state !== "fatal-error" && this.state !== "destroying" && this.state !== "destroyed";
502
694
  }
695
+ assertStartOperationCurrent(generation) {
696
+ if (this.isLifecycleOperationCurrent(generation) && this.state === "starting") return;
697
+ throw createPlayerError("runtime", "RIVMUX_START_CANCELLED", `Start was cancelled by ${this.state === "destroying" || this.state === "destroyed" ? "destroy" : "stop"}.`, false);
698
+ }
503
699
  };
700
+ function toPublicStartError(cause) {
701
+ return isPlayerError(cause) ? playerErrorToException(cause) : cause instanceof Error ? cause : new Error(String(cause));
702
+ }
703
+ function isPlayerError(value) {
704
+ if (typeof value !== "object" || value === null) return false;
705
+ const candidate = value;
706
+ return typeof candidate.kind === "string" && typeof candidate.code === "string" && typeof candidate.message === "string" && typeof candidate.terminal === "boolean";
707
+ }
504
708
  function createPlayerId() {
505
709
  return globalThis.crypto?.randomUUID?.() ?? `rivmux-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
506
710
  }
@@ -515,6 +719,6 @@ function withBundledWasmUrl(options) {
515
719
  };
516
720
  }
517
721
  //#endregion
518
- export { DEFAULT_RIVMUX_PLAYER_OPTIONS, RivmuxPlayer, createPlayerError, normalizePlayerOptions };
722
+ export { RivmuxPlayer, getCapabilities, isSupported };
519
723
 
520
724
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/events.ts","../src/errors.ts","../src/feature-detect.ts","../src/options.ts","../src/playback-controller.ts","../src/player.ts"],"sourcesContent":["import type { PlayerEventListener, PlayerEventMap, PlayerEventType } from '@rivmux/protocol'\n\nexport class PlayerEventEmitter {\n private readonly listeners = new Map<PlayerEventType, Set<PlayerEventListener<PlayerEventType>>>()\n\n on<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n const typedListener = listener as PlayerEventListener<PlayerEventType>\n const listeners = this.listeners.get(type)\n\n if (listeners === undefined) {\n this.listeners.set(type, new Set([typedListener]))\n return\n }\n\n listeners.add(typedListener)\n }\n\n off<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.listeners.get(type)?.delete(listener as PlayerEventListener<PlayerEventType>)\n }\n\n emit<T extends PlayerEventType>(type: T, payload: PlayerEventMap[T]): void {\n for (const listener of this.listeners.get(type) ?? []) {\n listener(payload)\n }\n }\n\n clear(): void {\n this.listeners.clear()\n }\n}\n","import type { PlayerError, PlayerErrorKind } from '@rivmux/protocol'\n\nexport function createPlayerError(kind: PlayerErrorKind, code: string, message: string, terminal: boolean, cause?: unknown): PlayerError {\n return cause === undefined ? { kind, code, message, terminal } : { kind, code, message, terminal, cause }\n}\n\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\nexport function playerErrorToException(error: PlayerError): Error {\n const exception = error.cause instanceof Error ? error.cause : new Error(error.message)\n exception.name = error.code\n return exception\n}\n","import { createPlayerError } from './errors'\n\nimport type { PlayerError } from '@rivmux/protocol'\n\nexport function detectMainThreadRuntime(): PlayerError | undefined {\n if (typeof Worker === 'undefined') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_WORKER', 'Dedicated Worker is not available in this runtime.', true)\n }\n\n if (typeof fetch !== 'function') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_FETCH', 'Fetch is not available in this runtime.', true)\n }\n\n if (typeof ReadableStream === 'undefined') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_READABLE_STREAM', 'ReadableStream is not available in this runtime.', true)\n }\n\n if (typeof WebAssembly === 'undefined') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_WASM', 'WebAssembly is not available in this runtime.', true)\n }\n\n if (typeof MediaSource === 'undefined') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_MSE', 'MediaSource is not available in this runtime.', true)\n }\n\n if (MediaSource.canConstructInDedicatedWorker !== true) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_WORKER_MSE', 'MediaSource cannot be constructed in a dedicated worker in this runtime.', true)\n }\n\n if (typeof MediaSource.isTypeSupported !== 'function') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_MSE_TYPE_CHECK', 'MediaSource.isTypeSupported is not available in this runtime.', true)\n }\n\n return undefined\n}\n","import { createPlayerError, playerErrorToException } from './errors'\n\nimport type { NormalizedRivmuxPlayerOptions, PlayerErrorKind, RivmuxPlayerOptions } from '@rivmux/protocol'\n\nexport const DEFAULT_RIVMUX_PLAYER_OPTIONS: NormalizedRivmuxPlayerOptions = {\n playback: {\n autoPlay: true,\n muted: false,\n },\n latency: {\n startupBuffer: 0.35,\n target: 1.2,\n max: 2.5,\n maxForwardBuffer: 4,\n backwardBuffer: 1.5,\n },\n network: {\n headers: {},\n credentials: 'same-origin',\n retry: {\n maxAttempts: 3,\n backoffMs: 500,\n },\n },\n runtime: {\n preferWorkerMse: true,\n },\n diagnostics: {\n statsIntervalMs: 1000,\n debug: false,\n },\n}\n\nexport function normalizePlayerOptions(options: RivmuxPlayerOptions = {}): NormalizedRivmuxPlayerOptions {\n const normalizedOptions = {\n playback: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.playback,\n ...options.playback,\n },\n latency: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.latency,\n ...options.latency,\n },\n network: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.network,\n ...options.network,\n headers: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.network.headers,\n ...options.network?.headers,\n },\n retry: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.network.retry,\n ...options.network?.retry,\n },\n },\n runtime: normalizeRuntimeOptions(options),\n diagnostics: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.diagnostics,\n ...options.diagnostics,\n },\n }\n\n validateNormalizedOptions(normalizedOptions)\n return normalizedOptions\n}\n\nfunction validateNormalizedOptions(options: NormalizedRivmuxPlayerOptions): void {\n validateLatencyOptions(options)\n validateRuntimeOptions(options)\n}\n\nfunction validateLatencyOptions(options: NormalizedRivmuxPlayerOptions): void {\n const latency = options.latency\n\n assertFiniteNonNegativeLatency(latency.startupBuffer, 'latency.startupBuffer')\n assertFinitePositiveLatency(latency.target, 'latency.target')\n assertFiniteNonNegativeLatency(latency.max, 'latency.max')\n assertFiniteNonNegativeLatency(latency.maxForwardBuffer, 'latency.maxForwardBuffer')\n assertFiniteNonNegativeLatency(latency.backwardBuffer, 'latency.backwardBuffer')\n\n if (latency.max < latency.target) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', 'latency.max must be greater than or equal to latency.target.')\n }\n\n if (latency.maxForwardBuffer < latency.target) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', 'latency.maxForwardBuffer must be greater than or equal to latency.target.')\n }\n}\n\nfunction validateRuntimeOptions(options: NormalizedRivmuxPlayerOptions): void {\n if (options.runtime.preferWorkerMse !== true) {\n throwOptionError(\n 'unsupported',\n 'RIVMUX_UNSUPPORTED_MAIN_THREAD_MSE_FALLBACK',\n 'Main-thread MSE fallback is not implemented; runtime.preferWorkerMse must remain true.'\n )\n }\n}\n\nfunction normalizeRuntimeOptions(options: RivmuxPlayerOptions): NormalizedRivmuxPlayerOptions['runtime'] {\n return {\n preferWorkerMse: options.runtime?.preferWorkerMse ?? DEFAULT_RIVMUX_PLAYER_OPTIONS.runtime.preferWorkerMse,\n ...(options.runtime?.workerUrl === undefined ? {} : { workerUrl: options.runtime.workerUrl }),\n ...(options.runtime?.wasmUrl === undefined ? {} : { wasmUrl: options.runtime.wasmUrl }),\n }\n}\n\nfunction assertFiniteNonNegativeLatency(value: number, field: string): void {\n if (!Number.isFinite(value) || value < 0) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', `${field} must be a finite number greater than or equal to 0.`)\n }\n}\n\nfunction assertFinitePositiveLatency(value: number, field: string): void {\n if (!Number.isFinite(value) || value <= 0) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', `${field} must be a finite number greater than 0.`)\n }\n}\n\nfunction throwOptionError(kind: PlayerErrorKind, code: string, message: string): never {\n throw playerErrorToException(createPlayerError(kind, code, message, true))\n}\n","import type { NormalizedPlaybackOptions, PlaybackControlAction, PlaybackControlResult, VideoElementState } from '@rivmux/protocol'\n\nexport class PlaybackController {\n private readonly options: NormalizedPlaybackOptions\n private video?: HTMLVideoElement\n private stateTimer?: ReturnType<typeof setInterval>\n\n constructor(options: NormalizedPlaybackOptions) {\n this.options = options\n }\n\n get attached(): boolean {\n return this.video !== undefined\n }\n\n isAttachedTo(video: HTMLVideoElement): boolean {\n return this.video === video\n }\n\n attach(video: HTMLVideoElement): void {\n this.video = video\n video.muted = this.options.muted\n video.autoplay = this.options.autoPlay\n }\n\n attachMediaSourceHandle(handle: MediaSourceHandle): boolean {\n if (this.video === undefined) {\n return false\n }\n\n Reflect.set(this.video, 'srcObject', handle)\n return true\n }\n\n startStateReporting(intervalMs: number, report: (state: VideoElementState) => void): void {\n this.stopStateReporting()\n const reportCurrentState = (): void => {\n const state = this.snapshot()\n if (state !== undefined) {\n report(state)\n }\n }\n\n reportCurrentState()\n this.stateTimer = setInterval(reportCurrentState, intervalMs)\n }\n\n stopStateReporting(): void {\n if (this.stateTimer !== undefined) {\n clearInterval(this.stateTimer)\n this.stateTimer = undefined\n }\n }\n\n async applyControl(action: PlaybackControlAction): Promise<PlaybackControlResult> {\n const video = this.video\n if (video === undefined) {\n return { type: action.type, accepted: false, message: 'No video element is attached.' }\n }\n\n try {\n switch (action.type) {\n case 'play':\n await video.play()\n break\n case 'set-playback-rate':\n video.playbackRate = action.playbackRate\n break\n case 'seek':\n video.currentTime = action.targetTime\n break\n }\n return { type: action.type, accepted: true }\n } catch (cause) {\n return {\n type: action.type,\n accepted: false,\n message: cause instanceof Error ? cause.message : String(cause),\n }\n }\n }\n\n detachSource(): void {\n if (this.video === undefined) {\n return\n }\n\n this.video.pause()\n this.video.playbackRate = 1\n this.video.removeAttribute('src')\n this.video.srcObject = null\n this.video.load()\n }\n\n release(): void {\n this.stopStateReporting()\n this.detachSource()\n this.video = undefined\n }\n\n private snapshot(): VideoElementState | undefined {\n const video = this.video\n if (video === undefined) {\n return undefined\n }\n\n const droppedFrames = getDroppedFrames(video)\n return {\n currentTime: Number.isFinite(video.currentTime) ? video.currentTime : 0,\n readyState: video.readyState,\n playbackRate: video.playbackRate,\n paused: video.paused,\n ...(droppedFrames === undefined ? {} : { droppedFrames }),\n }\n }\n}\n\nfunction getDroppedFrames(video: HTMLVideoElement): number | undefined {\n const quality = video.getVideoPlaybackQuality?.()\n if (quality !== undefined && Number.isFinite(quality.droppedVideoFrames)) {\n return quality.droppedVideoFrames\n }\n\n const webkitDroppedFrameCount = (video as HTMLVideoElement & { webkitDroppedFrameCount?: number }).webkitDroppedFrameCount\n return Number.isFinite(webkitDroppedFrameCount) ? webkitDroppedFrameCount : undefined\n}\n","import { PlayerEventEmitter } from './events'\nimport { createPlayerError, playerErrorToException } from './errors'\nimport { detectMainThreadRuntime } from './feature-detect'\nimport { normalizePlayerOptions } from './options'\nimport { PlaybackController } from './playback-controller'\nimport { createRuntimeWorker, getBundledWasmUrl, WorkerClient } from '@rivmux/runtime-worker'\n\nimport type {\n NormalizedRivmuxPlayerOptions,\n PlaybackControlAction,\n PlayerError,\n PlayerEventListener,\n PlayerEventType,\n RivmuxPlayerOptions,\n WorkerMessage,\n} from '@rivmux/protocol'\nimport type { RuntimeWorkerFactory } from '@rivmux/runtime-worker'\n\ntype PlayerState = 'idle' | 'attached' | 'started' | 'stopping' | 'stopped' | 'fatal-error' | 'destroying' | 'destroyed'\n\nexport type RivmuxPlayerInternals = {\n workerFactory?: RuntimeWorkerFactory\n detectRuntime?: () => PlayerError | undefined\n idFactory?: () => string\n}\n\n/**\n * Public browser player facade for one HTTP-FLV stream.\n *\n * Create one instance per stream URL, call `attach(video)` first, then\n * `start()`. Call `destroy()` when the instance is no longer needed.\n */\nexport class RivmuxPlayer {\n /** Original stream URL passed to the constructor. */\n readonly url: string\n\n /** Fully normalized options with defaults applied. */\n readonly options: NormalizedRivmuxPlayerOptions\n private readonly id: string\n private readonly events = new PlayerEventEmitter()\n private readonly workerFactory: RuntimeWorkerFactory\n private readonly detectRuntime: () => PlayerError | undefined\n private readonly playback: PlaybackController\n private workerClient?: WorkerClient\n private state: PlayerState = 'idle'\n private lifecycleGeneration = 0\n private terminalError?: PlayerError\n private stopPromise?: Promise<void>\n private destroyPromise?: Promise<void>\n\n /**\n * Creates a player instance for one stream URL.\n *\n * The instance does not start network loading until `start()` is called.\n */\n constructor(url: string, options?: RivmuxPlayerOptions, internals: RivmuxPlayerInternals = {}) {\n this.url = url\n this.options = normalizePlayerOptions(options)\n this.id = internals.idFactory?.() ?? createPlayerId()\n this.workerFactory = internals.workerFactory ?? createRuntimeWorker\n this.detectRuntime = internals.detectRuntime ?? detectMainThreadRuntime\n this.playback = new PlaybackController(this.options.playback)\n }\n\n /**\n * Attaches this player to a video element and prepares the worker/MSE pipe.\n *\n * Await this method before calling `start()`.\n */\n async attach(video: HTMLVideoElement): Promise<void> {\n this.assertOperational('attach')\n const lifecycleGeneration = this.lifecycleGeneration\n\n if (this.playback.attached && !this.playback.isAttachedTo(video)) {\n throw playerErrorToException(createPlayerError('runtime', 'RIVMUX_ALREADY_ATTACHED', 'This player is already attached to a video element.', false))\n }\n\n const runtimeError = this.detectRuntime()\n if (runtimeError !== undefined) {\n this.events.emit('error', runtimeError)\n throw playerErrorToException(runtimeError)\n }\n\n this.playback.attach(video)\n this.ensureWorkerClient()\n await this.workerClient?.waitForMediaSourceHandle({ type: 'attach-media-source' })\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\n }\n this.state = 'attached'\n }\n\n /**\n * Starts loading, transmuxing, buffering, and playback control.\n *\n * Requires a successful `attach(video)` call first.\n */\n async start(): Promise<void> {\n this.assertOperational('start')\n const lifecycleGeneration = this.lifecycleGeneration\n\n if (!this.playback.attached || this.workerClient === undefined) {\n throw playerErrorToException(createPlayerError('runtime', 'RIVMUX_START_REQUIRES_ATTACH', 'start() requires a previously attached video element.', false))\n }\n\n if (this.state === 'started') {\n return\n }\n\n if (this.state === 'stopped') {\n await this.workerClient.waitForMediaSourceHandle({ type: 'attach-media-source' })\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\n }\n this.state = 'attached'\n }\n\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\n }\n this.workerClient.post({ type: 'start' })\n this.state = 'started'\n this.startVideoStateReporting()\n }\n\n /**\n * Stops loading and detaches the current media source.\n *\n * The instance remains reusable; call `start()` again to restart the same\n * stream after the player has stopped.\n */\n stop(): Promise<void> {\n if (this.state === 'destroyed' || this.state === 'destroying') {\n return Promise.resolve()\n }\n\n if (this.state === 'fatal-error') {\n return Promise.resolve()\n }\n\n if (this.stopPromise !== undefined) {\n return this.stopPromise\n }\n\n this.lifecycleGeneration += 1\n this.state = 'stopping'\n const lifecycleGeneration = this.lifecycleGeneration\n const stopping = this.performStop(lifecycleGeneration).finally(() => {\n if (this.stopPromise === stopping) {\n this.stopPromise = undefined\n }\n })\n this.stopPromise = stopping\n return stopping\n }\n\n private async performStop(lifecycleGeneration: number): Promise<void> {\n const workerClient = this.workerClient\n if (workerClient === undefined) {\n if (this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n this.state = 'stopped'\n this.events.emit('stopped', undefined)\n }\n return\n }\n\n this.playback.stopStateReporting()\n try {\n await workerClient.waitForStopped({ type: 'stop' })\n } catch (error) {\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\n }\n throw error\n }\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\n }\n this.playback.detachSource()\n this.state = 'stopped'\n }\n\n /**\n * Releases worker resources, timers, listeners, and the attached video source.\n *\n * The instance is terminal after destroy and cannot be attached or started\n * again.\n */\n async destroy(): Promise<void> {\n if (this.state === 'destroyed') {\n return\n }\n\n if (this.destroyPromise !== undefined) {\n return this.destroyPromise\n }\n\n this.lifecycleGeneration += 1\n this.state = 'destroying'\n this.destroyPromise = this.performDestroy()\n return this.destroyPromise\n }\n\n private async performDestroy(): Promise<void> {\n const workerClient = this.workerClient\n this.workerClient = undefined\n this.playback.stopStateReporting()\n let destroyError: unknown\n\n if (workerClient !== undefined) {\n try {\n await workerClient.waitForDestroyed({ type: 'destroy' })\n } catch (error) {\n destroyError = error\n } finally {\n workerClient.dispose()\n }\n } else {\n this.events.emit('destroyed', undefined)\n }\n\n this.playback.release()\n\n this.state = 'destroyed'\n this.events.clear()\n\n if (destroyError !== undefined) {\n throw destroyError\n }\n }\n\n /** Registers an event listener for a typed player event. */\n on<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.events.on(type, listener)\n }\n\n /** Removes a previously registered event listener. */\n off<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.events.off(type, listener)\n }\n\n private ensureWorkerClient(): void {\n if (this.workerClient !== undefined) {\n return\n }\n\n const worker = this.workerFactory(this.options)\n this.workerClient = new WorkerClient(worker, {\n onMessage: (message) => this.handleWorkerMessage(message),\n onError: (error) => {\n this.enterFatalErrorState(error)\n this.events.emit('error', error)\n },\n })\n this.workerClient.post({ type: 'init', id: this.id, url: this.url, options: withBundledWasmUrl(this.options) })\n }\n\n private handleWorkerMessage(message: WorkerMessage): void {\n if (this.state === 'destroyed') {\n return\n }\n\n if ((this.state === 'destroying' && message.type !== 'destroyed') || this.state === 'fatal-error') {\n return\n }\n\n switch (message.type) {\n case 'ready':\n this.events.emit('ready', undefined)\n return\n case 'media-source-handle':\n this.attachMediaSourceHandle(message.handle)\n return\n case 'media-info':\n this.events.emit('mediaInfo', message.mediaInfo)\n return\n case 'stats':\n this.events.emit('stats', message.stats)\n return\n case 'warning':\n this.events.emit('warning', message.warning)\n return\n case 'error':\n if (message.error.terminal) {\n this.enterFatalErrorState(message.error)\n }\n this.events.emit('error', message.error)\n return\n case 'playback-control':\n void this.applyPlaybackControl(message.action)\n return\n case 'stopped':\n this.events.emit('stopped', undefined)\n return\n case 'destroyed':\n this.events.emit('destroyed', undefined)\n return\n }\n }\n\n private attachMediaSourceHandle(handle: MediaSourceHandle): void {\n if (!this.playback.attachMediaSourceHandle(handle)) {\n const error = createPlayerError(\n 'runtime',\n 'RIVMUX_ATTACH_HANDLE_WITHOUT_VIDEO',\n 'Worker returned a MediaSourceHandle before a video element was attached.',\n true\n )\n this.events.emit('error', error)\n throw playerErrorToException(error)\n }\n }\n\n private startVideoStateReporting(): void {\n const intervalMs = Math.max(100, Math.min(this.options.diagnostics.statsIntervalMs, 250))\n const workerClient = this.workerClient\n if (workerClient === undefined) {\n return\n }\n\n this.playback.startStateReporting(intervalMs, (state) => {\n if (this.state === 'started' && this.workerClient === workerClient) {\n workerClient.post({ type: 'video-state', state })\n }\n })\n }\n\n private async applyPlaybackControl(action: PlaybackControlAction): Promise<void> {\n const workerClient = this.workerClient\n const lifecycleGeneration = this.lifecycleGeneration\n if (!this.playback.attached || workerClient === undefined || this.state !== 'started') {\n return\n }\n\n const result = await this.playback.applyControl(action)\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration) || this.state !== 'started' || this.workerClient !== workerClient) {\n return\n }\n workerClient.post({ type: 'playback-control-result', result })\n }\n\n private enterFatalErrorState(error: PlayerError): void {\n if (this.state === 'destroyed' || this.state === 'destroying' || this.state === 'fatal-error') {\n return\n }\n\n this.lifecycleGeneration += 1\n this.terminalError = error\n this.state = 'fatal-error'\n this.playback.release()\n }\n\n private assertOperational(method: string): void {\n if (this.state === 'fatal-error' && this.terminalError !== undefined) {\n throw playerErrorToException(this.terminalError)\n }\n\n if (this.state === 'stopping') {\n throw playerErrorToException(\n createPlayerError('runtime', 'RIVMUX_PLAYER_STOPPING', `RivmuxPlayer.${method}() cannot be called while stop() is pending.`, false)\n )\n }\n\n if (this.state !== 'destroyed' && this.state !== 'destroying') {\n return\n }\n\n const code = this.state === 'destroying' ? 'RIVMUX_PLAYER_DESTROYING' : 'RIVMUX_PLAYER_DESTROYED'\n throw playerErrorToException(createPlayerError('runtime', code, `RivmuxPlayer.${method}() cannot be called after destroy() begins.`, true))\n }\n\n private isLifecycleOperationCurrent(generation: number): boolean {\n return generation === this.lifecycleGeneration && this.state !== 'fatal-error' && this.state !== 'destroying' && this.state !== 'destroyed'\n }\n}\n\nfunction createPlayerId(): string {\n const random = globalThis.crypto?.randomUUID?.()\n return random ?? `rivmux-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`\n}\n\nfunction withBundledWasmUrl(options: NormalizedRivmuxPlayerOptions): NormalizedRivmuxPlayerOptions {\n if (options.runtime.wasmUrl !== undefined) {\n return options\n }\n\n return {\n ...options,\n runtime: {\n ...options.runtime,\n wasmUrl: getBundledWasmUrl(),\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/events.ts","../src/errors.ts","../src/feature-detect.ts","../src/options.ts","../src/playback-controller.ts","../src/player.ts"],"sourcesContent":["import type { PlayerEventListener, PlayerEventMap, PlayerEventType } from '@rivmux/protocol'\n\nexport class PlayerEventEmitter {\n private readonly listeners = new Map<PlayerEventType, Set<PlayerEventListener<PlayerEventType>>>()\n\n on<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n const typedListener = listener as PlayerEventListener<PlayerEventType>\n const listeners = this.listeners.get(type)\n\n if (listeners === undefined) {\n this.listeners.set(type, new Set([typedListener]))\n return\n }\n\n listeners.add(typedListener)\n }\n\n off<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.listeners.get(type)?.delete(listener as PlayerEventListener<PlayerEventType>)\n }\n\n emit<T extends PlayerEventType>(type: T, payload: PlayerEventMap[T]): void {\n for (const listener of this.listeners.get(type) ?? []) {\n try {\n listener(payload)\n } catch (error) {\n reportListenerError(error)\n }\n }\n }\n\n clear(): void {\n this.listeners.clear()\n }\n}\n\nfunction reportListenerError(error: unknown): void {\n try {\n const reportError = (globalThis as typeof globalThis & { reportError?: (error: unknown) => void }).reportError\n if (typeof reportError === 'function') {\n reportError(error)\n return\n }\n } catch {\n // Fall through to the non-throwing console fallback.\n }\n\n try {\n globalThis.console?.error(error)\n } catch {\n // Error reporting must never affect player behavior.\n }\n}\n","import type { PlayerError, PlayerErrorKind } from '@rivmux/protocol'\n\nexport function createPlayerError(kind: PlayerErrorKind, code: string, message: string, terminal: boolean, cause?: unknown): PlayerError {\n return cause === undefined ? { kind, code, message, terminal } : { kind, code, message, terminal, cause: serializeCause(cause) }\n}\n\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\nexport function playerErrorToException(error: PlayerError): Error {\n const exception = new Error(error.message)\n exception.name = error.code\n return exception\n}\n\nfunction serializeCause(cause: unknown): { name: string; message: string } {\n if (cause instanceof Error) {\n return { name: cause.name, message: cause.message }\n }\n\n if (typeof cause === 'object' && cause !== null) {\n const value = cause as { name?: unknown; message?: unknown }\n return {\n name: typeof value.name === 'string' ? value.name : 'Error',\n message: typeof value.message === 'string' ? value.message : String(cause),\n }\n }\n\n return { name: 'Error', message: String(cause) }\n}\n","import { createPlayerError } from './errors'\n\nimport type { PlayerError, RivmuxCapabilities, RuntimeCapabilities, SupportStatus } from '@rivmux/protocol'\n\ninterface CapabilityEnvironment {\n dedicatedWorker: boolean\n fetchStreaming: boolean\n readableStream: boolean\n webAssembly: boolean\n mediaSource: boolean\n workerMseConstructable: boolean\n isTypeSupported?: (mime: string) => boolean\n}\n\nconst MIME = {\n avc: 'video/mp4; codecs=\"avc1.42E01E\"',\n hevc: 'video/mp4; codecs=\"hvc1.1.6.L93.B0\"',\n av1: 'video/mp4; codecs=\"av01.0.08M.08\"',\n aac: 'audio/mp4; codecs=\"mp4a.40.2\"',\n opus: 'audio/mp4; codecs=\"opus\"',\n} as const\n\nfunction safelyRead<T>(read: () => T, fallback: T): T {\n try {\n return read()\n } catch {\n return fallback\n }\n}\n\nfunction readCapabilityEnvironment(): CapabilityEnvironment {\n const dedicatedWorker = safelyRead(() => typeof Worker === 'function', false)\n const fetchAvailable = safelyRead(() => typeof fetch === 'function', false)\n const responseBodyAvailable = safelyRead(() => typeof Response === 'function' && Response.prototype !== undefined && 'body' in Response.prototype, false)\n const readableStream = safelyRead(() => typeof ReadableStream === 'function', false)\n const webAssembly = safelyRead(() => typeof WebAssembly === 'object' && typeof WebAssembly.instantiate === 'function', false)\n const mediaSourceConstructor = safelyRead(() => (typeof MediaSource === 'function' ? MediaSource : undefined), undefined as typeof MediaSource | undefined)\n const mediaSource = mediaSourceConstructor !== undefined\n const workerMseConstructable = mediaSource && safelyRead(() => mediaSourceConstructor.canConstructInDedicatedWorker === true, false)\n const isTypeSupported = mediaSource\n ? safelyRead(\n () => (typeof mediaSourceConstructor.isTypeSupported === 'function' ? mediaSourceConstructor.isTypeSupported.bind(mediaSourceConstructor) : undefined),\n undefined\n )\n : undefined\n\n return {\n dedicatedWorker,\n fetchStreaming: fetchAvailable && responseBodyAvailable,\n readableStream,\n webAssembly,\n mediaSource,\n workerMseConstructable,\n isTypeSupported,\n }\n}\n\nfunction probeMime(isTypeSupported: CapabilityEnvironment['isTypeSupported'], mime: string): SupportStatus {\n if (isTypeSupported === undefined) {\n return 'unknown'\n }\n\n try {\n return isTypeSupported(mime) ? 'supported' : 'unsupported'\n } catch {\n return 'unknown'\n }\n}\n\nfunction combineProfile(video: SupportStatus, audio: SupportStatus): SupportStatus {\n if (video === 'unsupported' || audio === 'unsupported') {\n return 'unsupported'\n }\n if (video === 'supported' && audio === 'supported') {\n return 'supported'\n }\n return 'unknown'\n}\n\nfunction getCapabilitiesForEnvironment(environment: CapabilityEnvironment): RivmuxCapabilities {\n const runtime: RuntimeCapabilities = {\n dedicatedWorker: environment.dedicatedWorker,\n workerMse: environment.workerMseConstructable && environment.isTypeSupported !== undefined,\n fetchStreaming: environment.fetchStreaming,\n readableStream: environment.readableStream,\n webAssembly: environment.webAssembly,\n }\n const video = {\n avc: probeMime(environment.isTypeSupported, MIME.avc),\n hevc: probeMime(environment.isTypeSupported, MIME.hevc),\n av1: probeMime(environment.isTypeSupported, MIME.av1),\n }\n const audio = {\n aac: probeMime(environment.isTypeSupported, MIME.aac),\n opus: probeMime(environment.isTypeSupported, MIME.opus),\n }\n\n return {\n supported: Object.values(runtime).every(Boolean),\n runtime,\n decoding: {\n video,\n audio,\n stableProfiles: {\n avcAac: combineProfile(video.avc, audio.aac),\n hevcAac: combineProfile(video.hevc, audio.aac),\n },\n },\n }\n}\n\nexport function getCapabilities(): RivmuxCapabilities {\n return getCapabilitiesForEnvironment(readCapabilityEnvironment())\n}\n\nexport function isSupported(): boolean {\n return getCapabilities().supported\n}\n\nexport function detectMainThreadRuntime(environment = readCapabilityEnvironment()): PlayerError | undefined {\n if (!environment.dedicatedWorker) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_WORKER', 'Dedicated Worker is not available in this runtime.', true)\n }\n\n if (!environment.fetchStreaming) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_FETCH', 'Fetch is not available in this runtime.', true)\n }\n\n if (!environment.readableStream) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_READABLE_STREAM', 'ReadableStream is not available in this runtime.', true)\n }\n\n if (!environment.webAssembly) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_WASM', 'WebAssembly is not available in this runtime.', true)\n }\n\n if (!environment.mediaSource) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_MSE', 'MediaSource is not available in this runtime.', true)\n }\n\n if (!environment.workerMseConstructable) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_WORKER_MSE', 'MediaSource cannot be constructed in a dedicated worker in this runtime.', true)\n }\n\n if (environment.isTypeSupported === undefined) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_MSE_TYPE_CHECK', 'MediaSource.isTypeSupported is not available in this runtime.', true)\n }\n\n return undefined\n}\n","import { createPlayerError, playerErrorToException } from './errors'\n\nimport type { NormalizedRivmuxPlayerOptions, PlayerErrorKind, RivmuxPlayerOptions } from '@rivmux/protocol'\n\nconst DEFAULT_RIVMUX_PLAYER_OPTIONS: NormalizedRivmuxPlayerOptions = {\n playback: {\n autoPlay: true,\n muted: false,\n },\n latency: {\n startupBuffer: 0.35,\n target: 1.2,\n max: 2.5,\n maxForwardBuffer: 4,\n backwardBuffer: 1.5,\n },\n network: {\n headers: {},\n credentials: 'same-origin',\n readIdleTimeoutMs: 10_000,\n retry: {\n maxAttempts: 3,\n backoffMs: 500,\n maxBackoffMs: 8_000,\n jitterRatio: 0.2,\n },\n },\n runtime: {},\n diagnostics: { statsIntervalMs: 1000 },\n}\n\nexport function normalizePlayerOptions(options: RivmuxPlayerOptions = {}): NormalizedRivmuxPlayerOptions {\n const normalizedOptions = {\n playback: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.playback,\n ...options.playback,\n },\n latency: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.latency,\n ...options.latency,\n },\n network: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.network,\n ...options.network,\n headers: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.network.headers,\n ...options.network?.headers,\n },\n retry: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.network.retry,\n ...options.network?.retry,\n },\n },\n runtime: normalizeRuntimeOptions(options),\n diagnostics: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.diagnostics,\n ...options.diagnostics,\n },\n }\n\n validateNormalizedOptions(normalizedOptions)\n return normalizedOptions\n}\n\nfunction validateNormalizedOptions(options: NormalizedRivmuxPlayerOptions): void {\n validateLatencyOptions(options)\n validateNetworkOptions(options)\n}\n\nfunction validateLatencyOptions(options: NormalizedRivmuxPlayerOptions): void {\n const latency = options.latency\n\n assertFiniteNonNegativeLatency(latency.startupBuffer, 'latency.startupBuffer')\n assertFinitePositiveLatency(latency.target, 'latency.target')\n assertFiniteNonNegativeLatency(latency.max, 'latency.max')\n assertFiniteNonNegativeLatency(latency.maxForwardBuffer, 'latency.maxForwardBuffer')\n assertFiniteNonNegativeLatency(latency.backwardBuffer, 'latency.backwardBuffer')\n\n if (latency.max < latency.target) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', 'latency.max must be greater than or equal to latency.target.')\n }\n\n if (latency.maxForwardBuffer < latency.target) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', 'latency.maxForwardBuffer must be greater than or equal to latency.target.')\n }\n}\n\nfunction validateNetworkOptions(options: NormalizedRivmuxPlayerOptions): void {\n const network = options.network\n const retry = network.retry\n\n assertPositiveInteger(network.readIdleTimeoutMs, 'network.readIdleTimeoutMs')\n assertPositiveInteger(retry.maxAttempts, 'network.retry.maxAttempts')\n assertNonNegativeInteger(retry.backoffMs, 'network.retry.backoffMs')\n assertNonNegativeInteger(retry.maxBackoffMs, 'network.retry.maxBackoffMs')\n\n if (retry.maxBackoffMs < retry.backoffMs) {\n throwOptionError('runtime', 'RIVMUX_INVALID_NETWORK_OPTION', 'network.retry.maxBackoffMs must be greater than or equal to network.retry.backoffMs.')\n }\n\n if (!Number.isFinite(retry.jitterRatio) || retry.jitterRatio < 0 || retry.jitterRatio > 1) {\n throwOptionError('runtime', 'RIVMUX_INVALID_NETWORK_OPTION', 'network.retry.jitterRatio must be a finite number between 0 and 1.')\n }\n}\n\nfunction normalizeRuntimeOptions(options: RivmuxPlayerOptions): NormalizedRivmuxPlayerOptions['runtime'] {\n return {\n ...(options.runtime?.workerUrl === undefined ? {} : { workerUrl: options.runtime.workerUrl }),\n ...(options.runtime?.wasmUrl === undefined ? {} : { wasmUrl: options.runtime.wasmUrl }),\n }\n}\n\nfunction assertFiniteNonNegativeLatency(value: number, field: string): void {\n if (!Number.isFinite(value) || value < 0) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', `${field} must be a finite number greater than or equal to 0.`)\n }\n}\n\nfunction assertFinitePositiveLatency(value: number, field: string): void {\n if (!Number.isFinite(value) || value <= 0) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', `${field} must be a finite number greater than 0.`)\n }\n}\n\nfunction assertPositiveInteger(value: number, field: string): void {\n if (!Number.isInteger(value) || value < 1) {\n throwOptionError('runtime', 'RIVMUX_INVALID_NETWORK_OPTION', `${field} must be an integer greater than or equal to 1.`)\n }\n}\n\nfunction assertNonNegativeInteger(value: number, field: string): void {\n if (!Number.isInteger(value) || value < 0) {\n throwOptionError('runtime', 'RIVMUX_INVALID_NETWORK_OPTION', `${field} must be an integer greater than or equal to 0.`)\n }\n}\n\nfunction throwOptionError(kind: PlayerErrorKind, code: string, message: string): never {\n throw playerErrorToException(createPlayerError(kind, code, message, true))\n}\n","import type { NormalizedPlaybackOptions, PlaybackControlAction, PlaybackControlResult, VideoElementState } from '@rivmux/protocol'\n\nexport class PlaybackController {\n private readonly options: NormalizedPlaybackOptions\n private video?: HTMLVideoElement\n private stateTimer?: ReturnType<typeof setInterval>\n\n constructor(options: NormalizedPlaybackOptions) {\n this.options = options\n }\n\n get attached(): boolean {\n return this.video !== undefined\n }\n\n isAttachedTo(video: HTMLVideoElement): boolean {\n return this.video === video\n }\n\n attach(video: HTMLVideoElement): void {\n this.video = video\n video.muted = this.options.muted\n video.autoplay = this.options.autoPlay\n }\n\n attachMediaSourceHandle(handle: MediaSourceHandle): boolean {\n if (this.video === undefined) {\n return false\n }\n\n Reflect.set(this.video, 'srcObject', handle)\n return true\n }\n\n startStateReporting(intervalMs: number, report: (state: VideoElementState) => void): void {\n this.stopStateReporting()\n const reportCurrentState = (): void => {\n const state = this.snapshot()\n if (state !== undefined) {\n report(state)\n }\n }\n\n reportCurrentState()\n this.stateTimer = setInterval(reportCurrentState, intervalMs)\n }\n\n stopStateReporting(): void {\n if (this.stateTimer !== undefined) {\n clearInterval(this.stateTimer)\n this.stateTimer = undefined\n }\n }\n\n async applyControl(action: PlaybackControlAction): Promise<PlaybackControlResult> {\n const video = this.video\n if (video === undefined) {\n return {\n type: action.type,\n accepted: false,\n error: {\n name: 'NotAttachedError',\n message: 'No video element is attached.',\n },\n }\n }\n\n try {\n switch (action.type) {\n case 'play':\n await video.play()\n break\n case 'set-playback-rate':\n video.playbackRate = action.playbackRate\n break\n case 'seek':\n video.currentTime = action.targetTime\n break\n }\n return { type: action.type, accepted: true }\n } catch (cause) {\n return {\n type: action.type,\n accepted: false,\n error: normalizePlaybackControlError(cause),\n }\n }\n }\n\n detachSource(): void {\n if (this.video === undefined) {\n return\n }\n\n this.video.pause()\n this.video.playbackRate = 1\n this.video.removeAttribute('src')\n this.video.srcObject = null\n this.video.load()\n }\n\n release(): void {\n this.stopStateReporting()\n this.detachSource()\n this.video = undefined\n }\n\n private snapshot(): VideoElementState | undefined {\n const video = this.video\n if (video === undefined) {\n return undefined\n }\n\n const droppedFrames = getDroppedFrames(video)\n return {\n currentTime: Number.isFinite(video.currentTime) ? video.currentTime : 0,\n readyState: video.readyState,\n playbackRate: video.playbackRate,\n paused: video.paused,\n ...(droppedFrames === undefined ? {} : { droppedFrames }),\n }\n }\n}\n\nfunction normalizePlaybackControlError(cause: unknown): { name: string; message: string } {\n if (cause instanceof Error) {\n return {\n name: safelyStringifyErrorField(() => cause.name, 'Error'),\n message: safelyStringifyErrorField(() => cause.message, 'Unknown playback control error.'),\n }\n }\n\n try {\n return {\n name: 'Error',\n message: String(cause),\n }\n } catch {\n return {\n name: 'Error',\n message: 'Unknown playback control error.',\n }\n }\n}\n\nfunction safelyStringifyErrorField(read: () => unknown, fallback: string): string {\n try {\n return String(read())\n } catch {\n return fallback\n }\n}\n\nfunction getDroppedFrames(video: HTMLVideoElement): number | undefined {\n const quality = video.getVideoPlaybackQuality?.()\n if (quality !== undefined && Number.isFinite(quality.droppedVideoFrames)) {\n return quality.droppedVideoFrames\n }\n\n const webkitDroppedFrameCount = (video as HTMLVideoElement & { webkitDroppedFrameCount?: number }).webkitDroppedFrameCount\n return Number.isFinite(webkitDroppedFrameCount) ? webkitDroppedFrameCount : undefined\n}\n","import { PlayerEventEmitter } from './events'\nimport { createPlayerError, playerErrorToException } from './errors'\nimport { detectMainThreadRuntime } from './feature-detect'\nimport { normalizePlayerOptions } from './options'\nimport { PlaybackController } from './playback-controller'\nimport { createRuntimeWorker, getBundledWasmUrl, WorkerClient } from '@rivmux/runtime-worker'\n\nimport type {\n NormalizedRivmuxPlayerOptions,\n PlaybackControlAction,\n PlayerError,\n PlayerEventListener,\n PlayerEventType,\n RivmuxPlayerOptions,\n WorkerMessage,\n} from '@rivmux/protocol'\nimport type { RuntimeWorkerFactory } from '@rivmux/runtime-worker'\n\ntype PlayerState = 'idle' | 'attached' | 'starting' | 'started' | 'stopping' | 'stopped' | 'fatal-error' | 'destroying' | 'destroyed'\n\ntype RivmuxPlayerInternals = {\n workerFactory?: RuntimeWorkerFactory\n detectRuntime?: () => PlayerError | undefined\n idFactory?: () => string\n}\n\n/**\n * Public browser player facade for one HTTP-FLV stream.\n *\n * Create one instance per stream URL, call `attach(video)` first, then\n * `start()`. Call `destroy()` when the instance is no longer needed.\n */\nexport class RivmuxPlayer {\n /** Original stream URL passed to the constructor. */\n readonly url: string\n\n /** @internal Fully normalized options with defaults applied. */\n readonly options: NormalizedRivmuxPlayerOptions\n private readonly id: string\n private readonly events = new PlayerEventEmitter()\n private readonly workerFactory: RuntimeWorkerFactory\n private readonly detectRuntime: () => PlayerError | undefined\n private readonly playback: PlaybackController\n private workerClient?: WorkerClient\n private state: PlayerState = 'idle'\n private lifecycleGeneration = 0\n private terminalError?: PlayerError\n private autoplayWarningEmitted = false\n private startPromise?: Promise<void>\n private stopPromise?: Promise<void>\n private destroyPromise?: Promise<void>\n\n /**\n * Creates a player instance for one stream URL.\n *\n * The instance does not start network loading until `start()` is called.\n */\n constructor(url: string, options?: RivmuxPlayerOptions)\n /** @internal Test-only dependency injection; omitted from generated declarations. */\n constructor(url: string, options?: RivmuxPlayerOptions, internals?: RivmuxPlayerInternals)\n constructor(url: string, options?: RivmuxPlayerOptions, internals?: RivmuxPlayerInternals) {\n internals ??= {}\n this.url = url\n this.options = normalizePlayerOptions(options)\n this.id = internals.idFactory?.() ?? createPlayerId()\n this.workerFactory = internals.workerFactory ?? createRuntimeWorker\n this.detectRuntime = internals.detectRuntime ?? detectMainThreadRuntime\n this.playback = new PlaybackController(this.options.playback)\n }\n\n /**\n * Attaches this player to a video element and prepares the worker/MSE pipe.\n *\n * Await this method before calling `start()`.\n */\n async attach(video: HTMLVideoElement): Promise<void> {\n this.assertOperational('attach')\n const lifecycleGeneration = this.lifecycleGeneration\n\n if (this.playback.attached && !this.playback.isAttachedTo(video)) {\n throw playerErrorToException(createPlayerError('runtime', 'RIVMUX_ALREADY_ATTACHED', 'This player is already attached to a video element.', false))\n }\n\n const runtimeError = this.detectRuntime()\n if (runtimeError !== undefined) {\n this.events.emit('error', runtimeError)\n throw playerErrorToException(runtimeError)\n }\n\n this.playback.attach(video)\n this.ensureWorkerClient()\n await this.workerClient?.waitForMediaSourceHandle({ type: 'attach-media-source' })\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\n }\n this.state = 'attached'\n }\n\n /**\n * Waits until the worker has created the transmux core and scheduled stream\n * consumption for this playback session.\n *\n * This does not wait for media data, `canplay`, or `video.play()`. Requires a\n * successful `attach(video)` call first.\n */\n start(): Promise<void> {\n try {\n this.assertOperational('start')\n } catch (cause) {\n return Promise.reject(cause)\n }\n\n if (!this.playback.attached || this.workerClient === undefined) {\n return Promise.reject(\n playerErrorToException(createPlayerError('runtime', 'RIVMUX_START_REQUIRES_ATTACH', 'start() requires a previously attached video element.', false))\n )\n }\n\n if (this.state === 'started') {\n return Promise.resolve()\n }\n\n if (this.startPromise !== undefined) {\n return this.startPromise\n }\n\n const lifecycleGeneration = this.lifecycleGeneration\n const previousState = this.state\n this.state = 'starting'\n const starting = this.performStart(this.workerClient, lifecycleGeneration, previousState === 'stopped').finally(() => {\n if (this.startPromise === starting) {\n this.startPromise = undefined\n }\n })\n this.startPromise = starting\n return starting\n }\n\n private async performStart(workerClient: WorkerClient, lifecycleGeneration: number, restart: boolean): Promise<void> {\n try {\n if (restart) {\n await workerClient.waitForMediaSourceHandle({ type: 'attach-media-source' })\n this.assertStartOperationCurrent(lifecycleGeneration)\n }\n\n await workerClient.waitForStarted({ type: 'start' })\n this.assertStartOperationCurrent(lifecycleGeneration)\n } catch (cause) {\n if (this.isLifecycleOperationCurrent(lifecycleGeneration) && this.state === 'starting') {\n this.state = restart ? 'stopped' : 'attached'\n }\n throw toPublicStartError(cause)\n }\n\n this.state = 'started'\n this.startVideoStateReporting()\n }\n\n /**\n * Stops loading and detaches the current media source.\n *\n * The instance remains reusable; call `start()` again to restart the same\n * stream after the player has stopped.\n */\n stop(): Promise<void> {\n if (this.state === 'destroyed' || this.state === 'destroying') {\n return Promise.resolve()\n }\n\n if (this.state === 'fatal-error') {\n return Promise.resolve()\n }\n\n if (this.stopPromise !== undefined) {\n return this.stopPromise\n }\n\n this.lifecycleGeneration += 1\n this.state = 'stopping'\n const lifecycleGeneration = this.lifecycleGeneration\n const stopping = this.performStop(lifecycleGeneration).finally(() => {\n if (this.stopPromise === stopping) {\n this.stopPromise = undefined\n }\n })\n this.stopPromise = stopping\n return stopping\n }\n\n private async performStop(lifecycleGeneration: number): Promise<void> {\n const workerClient = this.workerClient\n if (workerClient === undefined) {\n if (this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n this.autoplayWarningEmitted = false\n this.state = 'stopped'\n this.events.emit('stopped', undefined)\n }\n return\n }\n\n this.playback.stopStateReporting()\n try {\n await workerClient.waitForStopped({ type: 'stop' })\n } catch (error) {\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\n }\n throw error\n }\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\n }\n this.playback.detachSource()\n this.autoplayWarningEmitted = false\n this.state = 'stopped'\n }\n\n /**\n * Releases worker resources, timers, listeners, and the attached video source.\n *\n * The instance is terminal after destroy and cannot be attached or started\n * again.\n */\n async destroy(): Promise<void> {\n if (this.state === 'destroyed') {\n return\n }\n\n if (this.destroyPromise !== undefined) {\n return this.destroyPromise\n }\n\n this.lifecycleGeneration += 1\n this.state = 'destroying'\n this.destroyPromise = this.performDestroy()\n return this.destroyPromise\n }\n\n private async performDestroy(): Promise<void> {\n const workerClient = this.workerClient\n this.workerClient = undefined\n this.playback.stopStateReporting()\n let destroyError: unknown\n\n if (workerClient !== undefined) {\n try {\n await workerClient.waitForDestroyed({ type: 'destroy' })\n } catch (error) {\n destroyError = error\n } finally {\n workerClient.dispose()\n }\n } else {\n this.events.emit('destroyed', undefined)\n }\n\n this.playback.release()\n\n this.state = 'destroyed'\n this.events.clear()\n\n if (destroyError !== undefined) {\n throw destroyError\n }\n }\n\n /** Registers an event listener for a typed player event. */\n on<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.events.on(type, listener)\n }\n\n /** Removes a previously registered event listener. */\n off<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.events.off(type, listener)\n }\n\n private ensureWorkerClient(): void {\n if (this.workerClient !== undefined) {\n return\n }\n\n const worker = this.workerFactory(this.options)\n this.workerClient = new WorkerClient(worker, {\n onMessage: (message) => this.handleWorkerMessage(message),\n onError: (error) => {\n this.enterFatalErrorState(error)\n this.events.emit('error', error)\n },\n })\n this.workerClient.post({ type: 'init', id: this.id, url: this.url, options: withBundledWasmUrl(this.options) })\n }\n\n private handleWorkerMessage(message: WorkerMessage): void {\n if (this.state === 'destroyed') {\n return\n }\n\n if ((this.state === 'destroying' && message.type !== 'destroyed') || this.state === 'fatal-error') {\n return\n }\n\n switch (message.type) {\n case 'ready':\n this.events.emit('initialized', undefined)\n return\n case 'media-source-handle':\n this.attachMediaSourceHandle(message.handle)\n return\n case 'started':\n return\n case 'media-info':\n this.events.emit('mediaInfo', message.mediaInfo)\n return\n case 'stats':\n this.events.emit('stats', message.stats)\n return\n case 'warning':\n this.events.emit('warning', message.warning)\n return\n case 'reconnecting':\n this.events.emit('reconnecting', message.info)\n return\n case 'recovered':\n this.events.emit('recovered', message.info)\n return\n case 'error':\n if (message.error.terminal) {\n this.enterFatalErrorState(message.error)\n }\n this.events.emit('error', message.error)\n return\n case 'playback-control':\n void this.applyPlaybackControl(message.action)\n return\n case 'stopped':\n this.events.emit('stopped', undefined)\n return\n case 'destroyed':\n this.events.emit('destroyed', undefined)\n return\n }\n }\n\n private attachMediaSourceHandle(handle: MediaSourceHandle): void {\n if (this.state === 'started') {\n this.autoplayWarningEmitted = false\n this.playback.detachSource()\n }\n if (!this.playback.attachMediaSourceHandle(handle)) {\n const error = createPlayerError(\n 'runtime',\n 'RIVMUX_ATTACH_HANDLE_WITHOUT_VIDEO',\n 'Worker returned a MediaSourceHandle before a video element was attached.',\n true\n )\n this.events.emit('error', error)\n throw playerErrorToException(error)\n }\n }\n\n private startVideoStateReporting(): void {\n const intervalMs = Math.max(100, Math.min(this.options.diagnostics.statsIntervalMs, 250))\n const workerClient = this.workerClient\n if (workerClient === undefined) {\n return\n }\n\n this.playback.startStateReporting(intervalMs, (state) => {\n if (this.state === 'started' && this.workerClient === workerClient) {\n workerClient.post({ type: 'video-state', state })\n }\n })\n }\n\n private async applyPlaybackControl(action: PlaybackControlAction): Promise<void> {\n const workerClient = this.workerClient\n const lifecycleGeneration = this.lifecycleGeneration\n if (!this.playback.attached || workerClient === undefined || this.state !== 'started') {\n return\n }\n\n const result = await this.playback.applyControl(action)\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration) || this.state !== 'started' || this.workerClient !== workerClient) {\n return\n }\n workerClient.post({ type: 'playback-control-result', result })\n if (\n action.type === 'play' &&\n action.reason === 'startup-buffer-ready' &&\n this.options.playback.autoPlay &&\n !result.accepted &&\n !this.autoplayWarningEmitted\n ) {\n this.autoplayWarningEmitted = true\n this.events.emit('warning', {\n code: 'RIVMUX_AUTOPLAY_REJECTED',\n message: '浏览器拒绝了自动播放;请在用户手势处理器中调用 video.play() 恢复播放。',\n cause: result.error,\n })\n }\n }\n\n private enterFatalErrorState(error: PlayerError): void {\n if (this.state === 'destroyed' || this.state === 'destroying' || this.state === 'fatal-error') {\n return\n }\n\n this.lifecycleGeneration += 1\n this.terminalError = error\n this.state = 'fatal-error'\n this.playback.release()\n }\n\n private assertOperational(method: string): void {\n if (this.state === 'fatal-error' && this.terminalError !== undefined) {\n throw playerErrorToException(this.terminalError)\n }\n\n if (this.state === 'stopping') {\n throw playerErrorToException(\n createPlayerError('runtime', 'RIVMUX_PLAYER_STOPPING', `RivmuxPlayer.${method}() cannot be called while stop() is pending.`, false)\n )\n }\n\n if (this.state !== 'destroyed' && this.state !== 'destroying') {\n return\n }\n\n const code = this.state === 'destroying' ? 'RIVMUX_PLAYER_DESTROYING' : 'RIVMUX_PLAYER_DESTROYED'\n throw playerErrorToException(createPlayerError('runtime', code, `RivmuxPlayer.${method}() cannot be called after destroy() begins.`, true))\n }\n\n private isLifecycleOperationCurrent(generation: number): boolean {\n return generation === this.lifecycleGeneration && this.state !== 'fatal-error' && this.state !== 'destroying' && this.state !== 'destroyed'\n }\n\n private assertStartOperationCurrent(generation: number): void {\n if (this.isLifecycleOperationCurrent(generation) && this.state === 'starting') {\n return\n }\n\n const action = this.state === 'destroying' || this.state === 'destroyed' ? 'destroy' : 'stop'\n throw createPlayerError('runtime', 'RIVMUX_START_CANCELLED', `Start was cancelled by ${action}.`, false)\n }\n}\n\nfunction toPublicStartError(cause: unknown): Error {\n return isPlayerError(cause) ? playerErrorToException(cause) : cause instanceof Error ? cause : new Error(String(cause))\n}\n\nfunction isPlayerError(value: unknown): value is PlayerError {\n if (typeof value !== 'object' || value === null) {\n return false\n }\n\n const candidate = value as Partial<PlayerError>\n return (\n typeof candidate.kind === 'string' && typeof candidate.code === 'string' && typeof candidate.message === 'string' && typeof candidate.terminal === 'boolean'\n )\n}\n\nfunction createPlayerId(): string {\n const random = globalThis.crypto?.randomUUID?.()\n return random ?? `rivmux-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`\n}\n\nfunction withBundledWasmUrl(options: NormalizedRivmuxPlayerOptions): NormalizedRivmuxPlayerOptions {\n if (options.runtime.wasmUrl !== undefined) {\n return options\n }\n\n return {\n ...options,\n runtime: {\n ...options.runtime,\n wasmUrl: getBundledWasmUrl(),\n },\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rivmux",
3
- "version": "0.5.0",
3
+ "version": "1.0.0",
4
4
  "description": "Public player package for Rivmux.",
5
5
  "homepage": "https://www.npmjs.com/package/rivmux",
6
6
  "bugs": {
@@ -30,17 +30,13 @@
30
30
  "module": "./dist/index.js",
31
31
  "types": "./dist/index.d.ts",
32
32
  "dependencies": {
33
- "@rivmux/protocol": "0.5.0",
34
- "@rivmux/runtime-worker": "0.5.0"
33
+ "@rivmux/protocol": "1.0.0",
34
+ "@rivmux/runtime-worker": "1.0.0"
35
35
  },
36
36
  "devDependencies": {
37
- "tsdown": "^0.22.7",
37
+ "tsdown": "^0.23.0",
38
38
  "typescript": "~6.0.3",
39
- "vitest": "^4.1.10"
40
- },
41
- "engines": {
42
- "node": "^22.18.0 || >=24.11.0",
43
- "pnpm": ">=10.0.0"
39
+ "vitest": "^4.1.11"
44
40
  },
45
41
  "scripts": {
46
42
  "build": "tsdown",