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 +188 -87
- package/dist/index.d.ts +15 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +251 -47
- package/dist/index.js.map +1 -1
- package/package.json +5 -9
package/README.md
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
# Rivmux Player
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`rivmux` 是 Rivmux 面向用户的浏览器播放器包。它在 Dedicated Worker 中读取 HTTP-FLV,将音视频转封装为 fragmented MP4,并通过 Worker MSE 连接到 `<video>` 元素。当前没有主线程 MSE 降级路径。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
audio/video into fragmented MP4, and attaches the resulting media stream to a
|
|
7
|
-
browser `<video>` element.
|
|
5
|
+
## 安装
|
|
8
6
|
|
|
9
|
-
|
|
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
|
-
|
|
17
|
+
当前发布目标是浏览器 ESM Bundler。Vite 已完成验证,其他 bundler 暂不形成兼容承诺。包的 `exports` 只提供 `import` 和类型条件,不提供 CommonJS、UMD 或全局变量入口。Node.js 仅支持 ESM 导入以及 SSR/能力探测调用,不提供 Node 播放运行时。
|
|
18
|
+
|
|
19
|
+
## 基本用法
|
|
26
20
|
|
|
27
|
-
|
|
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('
|
|
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('
|
|
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
|
-
//
|
|
49
|
+
// 不再使用时:
|
|
57
50
|
await player.stop()
|
|
58
51
|
await player.destroy()
|
|
59
52
|
```
|
|
60
53
|
|
|
61
|
-
##
|
|
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
|
-
|
|
|
68
|
-
| --------------------------------- |
|
|
69
|
-
| `new RivmuxPlayer(url, options?)` |
|
|
70
|
-
| `await player.attach(video)` |
|
|
71
|
-
| `await player.start()` |
|
|
72
|
-
| `await player.stop()` |
|
|
73
|
-
| `await player.destroy()` |
|
|
74
|
-
| `player.on(type, listener)` |
|
|
75
|
-
| `player.off(type, 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
|
-
|
|
123
|
+
同一启动过程中的并发 `start()` 调用会等待同一个操作,不会重复发送启动命令。播放器已经启动后再次调用 `start()` 会直接完成。若 `stop()` 或 `destroy()` 在启动确认前开始,待处理的 `start()` 会以 `RIVMUX_START_CANCELLED` 拒绝,而停止或销毁操作仍会正常完成。终止错误会使 `start()` 以相同错误码拒绝。
|
|
78
124
|
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
|
117
|
-
| ---------- | ------- |
|
|
118
|
-
| `autoPlay` | `true` |
|
|
119
|
-
| `muted` | `false` |
|
|
167
|
+
| 配置项 | 默认值 | 含义 |
|
|
168
|
+
| ---------- | ------- | ---------------------------------------------------------- |
|
|
169
|
+
| `autoPlay` | `true` | 启动缓冲足够时,由运行时请求 `video.play()`。 |
|
|
170
|
+
| `muted` | `false` | 设置 `video.muted`;浏览器通常只允许带音频的静音自动播放。 |
|
|
120
171
|
|
|
121
172
|
### `latency`
|
|
122
173
|
|
|
123
|
-
|
|
174
|
+
以下数值单位均为秒。
|
|
124
175
|
|
|
125
|
-
|
|
|
126
|
-
| ------------------ |
|
|
127
|
-
| `startupBuffer` | `0.35`
|
|
128
|
-
| `target` | `1.2`
|
|
129
|
-
| `max` | `2.5`
|
|
130
|
-
| `maxForwardBuffer` | `4`
|
|
131
|
-
| `backwardBuffer` | `1.5`
|
|
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
|
-
|
|
|
136
|
-
|
|
|
137
|
-
| `headers`
|
|
138
|
-
| `credentials`
|
|
139
|
-
| `
|
|
140
|
-
| `retry.
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
|
147
|
-
|
|
|
148
|
-
| `
|
|
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
|
-
###
|
|
205
|
+
### Worker/WASM 资产部署
|
|
151
206
|
|
|
152
|
-
|
|
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
|
-
|
|
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.
|
|
163
|
-
wasmUrl: 'https://cdn.example.com/rivmux/0.
|
|
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`
|
|
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
|
-
|
|
|
177
|
-
| ----------------- |
|
|
178
|
-
| `statsIntervalMs` | `1000`
|
|
179
|
-
| `debug` | `false` | Enables debug-oriented behavior where supported by the runtime. |
|
|
224
|
+
| 配置项 | 默认值 | 含义 |
|
|
225
|
+
| ----------------- | ------ | ------------------------------------------------------------ |
|
|
226
|
+
| `statsIntervalMs` | `1000` | 请求的统计上报间隔,单位为毫秒;运行时会在内部限制实际范围。 |
|
|
180
227
|
|
|
181
|
-
##
|
|
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('
|
|
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
|
-
|
|
|
196
|
-
|
|
|
197
|
-
| `
|
|
198
|
-
| `mediaInfo`
|
|
199
|
-
| `stats`
|
|
200
|
-
| `warning`
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
203
|
-
| `
|
|
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
|
-
##
|
|
299
|
+
## 类型导入
|
|
206
300
|
|
|
207
|
-
|
|
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
|
-
|
|
225
|
-
options object with defaults applied.
|
|
326
|
+
主包会导出上例中的 Experimental `RuntimeOptions`,但它不属于 Stable 兼容承诺。配置归一化和错误构造函数属于内部实现,未导出为应用 API。
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
-
*
|
|
38
|
+
* Waits until the worker has created the transmux core and scheduled stream
|
|
39
|
+
* consumption for this playback session.
|
|
45
40
|
*
|
|
46
|
-
*
|
|
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/
|
|
80
|
-
declare
|
|
81
|
-
declare function
|
|
77
|
+
//#region src/feature-detect.d.ts
|
|
78
|
+
export declare function getCapabilities(): RivmuxCapabilities$1;
|
|
79
|
+
export declare function isSupported(): boolean;
|
|
82
80
|
//#endregion
|
|
83
|
-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/player.ts","../src/
|
|
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) ?? [])
|
|
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 =
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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: {
|
|
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
|
-
|
|
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
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
460
|
+
* Waits until the worker has created the transmux core and scheduled stream
|
|
461
|
+
* consumption for this playback session.
|
|
310
462
|
*
|
|
311
|
-
*
|
|
463
|
+
* This does not wait for media data, `canplay`, or `video.play()`. Requires a
|
|
464
|
+
* successful `attach(video)` call first.
|
|
312
465
|
*/
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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("
|
|
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 {
|
|
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.
|
|
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.
|
|
34
|
-
"@rivmux/runtime-worker": "0.
|
|
33
|
+
"@rivmux/protocol": "1.0.0",
|
|
34
|
+
"@rivmux/runtime-worker": "1.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"tsdown": "^0.
|
|
37
|
+
"tsdown": "^0.23.0",
|
|
38
38
|
"typescript": "~6.0.3",
|
|
39
|
-
"vitest": "^4.1.
|
|
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",
|