rivmux 0.1.0 → 0.3.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 +199 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -12
- package/dist/index.js.map +1 -1
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -1,3 +1,202 @@
|
|
|
1
1
|
# Rivmux Player
|
|
2
2
|
|
|
3
3
|
Public browser player facade for Rivmux low-latency live playback.
|
|
4
|
+
|
|
5
|
+
`rivmux` loads HTTP-FLV streams in a dedicated worker, transmuxes supported
|
|
6
|
+
audio/video into fragmented MP4, and attaches the resulting media stream to a
|
|
7
|
+
browser `<video>` element.
|
|
8
|
+
|
|
9
|
+
M1 supports only the dedicated-worker MSE runtime path. Main-thread MSE and
|
|
10
|
+
custom precompiled WASM module injection are not exposed runtime capabilities.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pnpm add rivmux
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install rivmux
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Basic Usage
|
|
23
|
+
|
|
24
|
+
Always wait for `attach()` before calling `start()`. `attach()` initializes the
|
|
25
|
+
worker and connects the internal `MediaSourceHandle` to the video element.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { RivmuxPlayer } from 'rivmux'
|
|
29
|
+
|
|
30
|
+
const video = document.querySelector<HTMLVideoElement>('#player')
|
|
31
|
+
|
|
32
|
+
if (!video) {
|
|
33
|
+
throw new Error('Missing video element')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const player = new RivmuxPlayer('https://example.com/live.flv', {
|
|
37
|
+
playback: {
|
|
38
|
+
muted: true,
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
player.on('mediaInfo', (info) => {
|
|
43
|
+
console.log('media info', info)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
player.on('error', (error) => {
|
|
47
|
+
console.error(`[${error.code}] ${error.message}`)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
await player.attach(video)
|
|
51
|
+
await player.start()
|
|
52
|
+
|
|
53
|
+
// Later:
|
|
54
|
+
await player.stop()
|
|
55
|
+
await player.destroy()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Player Lifecycle
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const player = new RivmuxPlayer(url, options)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| Call | Description |
|
|
65
|
+
| --------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| `new RivmuxPlayer(url, options?)` | Creates a player instance for one stream URL. |
|
|
67
|
+
| `await player.attach(video)` | Attaches the player to a `<video>` element and prepares the worker/MSE pipeline. |
|
|
68
|
+
| `await player.start()` | Starts loading, transmuxing, buffering, and playback control. Call after `attach()`. |
|
|
69
|
+
| `await player.stop()` | Stops loading, detaches the media source, and keeps the instance reusable. |
|
|
70
|
+
| `await player.destroy()` | Releases the worker, timers, listeners, and video source. The instance cannot be reused after this call. |
|
|
71
|
+
| `player.on(type, listener)` | Subscribes to a player event. |
|
|
72
|
+
| `player.off(type, listener)` | Removes a previously registered listener. |
|
|
73
|
+
|
|
74
|
+
## Options
|
|
75
|
+
|
|
76
|
+
All options are optional. Missing fields are merged with
|
|
77
|
+
`DEFAULT_RIVMUX_PLAYER_OPTIONS`.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { RivmuxPlayer } from 'rivmux'
|
|
81
|
+
|
|
82
|
+
const player = new RivmuxPlayer('https://example.com/live.flv', {
|
|
83
|
+
playback: {
|
|
84
|
+
autoPlay: true,
|
|
85
|
+
muted: true,
|
|
86
|
+
},
|
|
87
|
+
latency: {
|
|
88
|
+
startupBuffer: 0.35,
|
|
89
|
+
target: 1.2,
|
|
90
|
+
max: 2.5,
|
|
91
|
+
maxForwardBuffer: 4,
|
|
92
|
+
backwardBuffer: 1.5,
|
|
93
|
+
},
|
|
94
|
+
network: {
|
|
95
|
+
credentials: 'include',
|
|
96
|
+
headers: {
|
|
97
|
+
Authorization: 'Bearer token',
|
|
98
|
+
},
|
|
99
|
+
retry: {
|
|
100
|
+
maxAttempts: 5,
|
|
101
|
+
backoffMs: 500,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
runtime: {
|
|
105
|
+
workerUrl: '/assets/rivmux-runtime-worker.js',
|
|
106
|
+
wasmUrl: '/assets/rivmux-transmux-core.wasm',
|
|
107
|
+
},
|
|
108
|
+
diagnostics: {
|
|
109
|
+
statsIntervalMs: 1000,
|
|
110
|
+
debug: false,
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `playback`
|
|
116
|
+
|
|
117
|
+
| Option | Default | Description |
|
|
118
|
+
| ---------- | ------- | --------------------------------------------------------------------------------- |
|
|
119
|
+
| `autoPlay` | `true` | Lets the runtime request `video.play()` when enough startup buffer is available. |
|
|
120
|
+
| `muted` | `false` | Sets `video.muted`. Many browsers require muted playback for autoplay with audio. |
|
|
121
|
+
|
|
122
|
+
### `latency`
|
|
123
|
+
|
|
124
|
+
Values are in seconds.
|
|
125
|
+
|
|
126
|
+
| Option | Default | Description |
|
|
127
|
+
| ------------------ | ------- | ---------------------------------------------------------------------------------------------- |
|
|
128
|
+
| `startupBuffer` | `0.35` | Buffered duration required before automatic playback starts. |
|
|
129
|
+
| `target` | `1.2` | Desired live latency. The runtime uses this when resuming fetches and restoring playback rate. |
|
|
130
|
+
| `max` | `2.5` | Maximum tolerated live latency before the runtime seeks closer to the live edge. |
|
|
131
|
+
| `maxForwardBuffer` | `4` | Forward buffer threshold where the loader may pause to avoid excessive buffering. |
|
|
132
|
+
| `backwardBuffer` | `1.5` | Amount of buffer to keep behind the current playhead during cleanup. |
|
|
133
|
+
|
|
134
|
+
### `network`
|
|
135
|
+
|
|
136
|
+
| Option | Default | Description |
|
|
137
|
+
| ------------------- | --------------- | ----------------------------------------------------------------- |
|
|
138
|
+
| `headers` | `{}` | Extra request headers for the HTTP-FLV stream. |
|
|
139
|
+
| `credentials` | `'same-origin'` | Fetch credentials mode for stream requests. |
|
|
140
|
+
| `retry.maxAttempts` | `3` | Maximum stream request attempts before reporting a network error. |
|
|
141
|
+
| `retry.backoffMs` | `500` | Base retry delay in milliseconds. |
|
|
142
|
+
|
|
143
|
+
### `runtime`
|
|
144
|
+
|
|
145
|
+
| Option | Default | Description |
|
|
146
|
+
| ----------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
147
|
+
| `preferWorkerMse` | `true` | Must remain `true` in M1. Main-thread MSE fallback is not implemented. |
|
|
148
|
+
| `workerUrl` | bundled worker URL | Overrides the worker script URL. Use this when serving worker assets from a fixed public path. |
|
|
149
|
+
| `wasmUrl` | bundled WASM module | Overrides the WASM asset URL. If provided, the matching wasm-bindgen JS glue file must be available at the same path with `.js` instead of `.wasm`. |
|
|
150
|
+
|
|
151
|
+
### `diagnostics`
|
|
152
|
+
|
|
153
|
+
| Option | Default | Description |
|
|
154
|
+
| ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
155
|
+
| `statsIntervalMs` | `1000` | Requested stats interval in milliseconds. Runtime reporting is clamped internally for stable video-state feedback. |
|
|
156
|
+
| `debug` | `false` | Enables debug-oriented behavior where supported by the runtime. |
|
|
157
|
+
|
|
158
|
+
## Events
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import type { MediaInfo, PlayerError, PlayerStats, PlayerWarning } from 'rivmux'
|
|
162
|
+
|
|
163
|
+
player.on('ready', () => {})
|
|
164
|
+
player.on('mediaInfo', (info: MediaInfo) => {})
|
|
165
|
+
player.on('stats', (stats: PlayerStats) => {})
|
|
166
|
+
player.on('warning', (warning: PlayerWarning) => {})
|
|
167
|
+
player.on('error', (error: PlayerError) => {})
|
|
168
|
+
player.on('stopped', () => {})
|
|
169
|
+
player.on('destroyed', () => {})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
| Event | Payload | Description |
|
|
173
|
+
| ----------- | --------------- | ----------------------------------------------------------------------- |
|
|
174
|
+
| `ready` | `undefined` | The worker/runtime is initialized. |
|
|
175
|
+
| `mediaInfo` | `MediaInfo` | Stream container and codec metadata has been detected. |
|
|
176
|
+
| `stats` | `PlayerStats` | Runtime diagnostics such as bytes, buffer, latency, and playback state. |
|
|
177
|
+
| `warning` | `PlayerWarning` | Recoverable issue reported by the runtime. |
|
|
178
|
+
| `error` | `PlayerError` | Runtime, network, demux, codec, mux, MSE, or unsupported-feature error. |
|
|
179
|
+
| `stopped` | `undefined` | The stream has stopped and the media source has been detached. |
|
|
180
|
+
| `destroyed` | `undefined` | The worker/runtime has been destroyed. |
|
|
181
|
+
|
|
182
|
+
## Type Imports
|
|
183
|
+
|
|
184
|
+
The player package re-exports the key public types from `@rivmux/protocol`:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import type {
|
|
188
|
+
DiagnosticsOptions,
|
|
189
|
+
LatencyOptions,
|
|
190
|
+
MediaInfo,
|
|
191
|
+
NetworkOptions,
|
|
192
|
+
PlaybackOptions,
|
|
193
|
+
PlayerError,
|
|
194
|
+
PlayerStats,
|
|
195
|
+
PlayerWarning,
|
|
196
|
+
RivmuxPlayerOptions,
|
|
197
|
+
RuntimeOptions,
|
|
198
|
+
} from 'rivmux'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Use `normalizePlayerOptions(options)` if you need to inspect a fully populated
|
|
202
|
+
options object with defaults applied.
|
package/dist/index.d.ts
CHANGED
|
@@ -7,8 +7,16 @@ type RivmuxPlayerInternals = {
|
|
|
7
7
|
detectRuntime?: () => PlayerError$1 | undefined;
|
|
8
8
|
idFactory?: () => string;
|
|
9
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* Public browser player facade for one HTTP-FLV stream.
|
|
12
|
+
*
|
|
13
|
+
* Create one instance per stream URL, call `attach(video)` first, then
|
|
14
|
+
* `start()`. Call `destroy()` when the instance is no longer needed.
|
|
15
|
+
*/
|
|
10
16
|
declare class RivmuxPlayer {
|
|
17
|
+
/** Original stream URL passed to the constructor. */
|
|
11
18
|
readonly url: string;
|
|
19
|
+
/** Fully normalized options with defaults applied. */
|
|
12
20
|
readonly options: NormalizedRivmuxPlayerOptions$1;
|
|
13
21
|
private readonly id;
|
|
14
22
|
private readonly events;
|
|
@@ -18,12 +26,41 @@ declare class RivmuxPlayer {
|
|
|
18
26
|
private video?;
|
|
19
27
|
private videoStateTimer?;
|
|
20
28
|
private state;
|
|
29
|
+
/**
|
|
30
|
+
* Creates a player instance for one stream URL.
|
|
31
|
+
*
|
|
32
|
+
* The instance does not start network loading until `start()` is called.
|
|
33
|
+
*/
|
|
21
34
|
constructor(url: string, options?: RivmuxPlayerOptions$1, internals?: RivmuxPlayerInternals);
|
|
35
|
+
/**
|
|
36
|
+
* Attaches this player to a video element and prepares the worker/MSE pipe.
|
|
37
|
+
*
|
|
38
|
+
* Await this method before calling `start()`.
|
|
39
|
+
*/
|
|
22
40
|
attach(video: HTMLVideoElement): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Starts loading, transmuxing, buffering, and playback control.
|
|
43
|
+
*
|
|
44
|
+
* Requires a successful `attach(video)` call first.
|
|
45
|
+
*/
|
|
23
46
|
start(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Stops loading and detaches the current media source.
|
|
49
|
+
*
|
|
50
|
+
* The instance remains reusable; call `start()` again to restart the same
|
|
51
|
+
* stream after the player has stopped.
|
|
52
|
+
*/
|
|
24
53
|
stop(): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Releases worker resources, timers, listeners, and the attached video source.
|
|
56
|
+
*
|
|
57
|
+
* The instance is terminal after destroy and cannot be attached or started
|
|
58
|
+
* again.
|
|
59
|
+
*/
|
|
25
60
|
destroy(): Promise<void>;
|
|
61
|
+
/** Registers an event listener for a typed player event. */
|
|
26
62
|
on<T extends PlayerEventType$1>(type: T, listener: PlayerEventListener$1<T>): void;
|
|
63
|
+
/** Removes a previously registered event listener. */
|
|
27
64
|
off<T extends PlayerEventType$1>(type: T, listener: PlayerEventListener$1<T>): void;
|
|
28
65
|
private ensureWorkerClient;
|
|
29
66
|
private handleWorkerMessage;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/player.ts","../src/options.ts","../src/errors.ts"],"mappings":";;;;KAmBY,qBAAA;EACV,aAAA,GAAgB,oBAAA;EAChB,aAAA,SAAsB,aAAW;EACjC,SAAA;AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/player.ts","../src/options.ts","../src/errors.ts"],"mappings":";;;;KAmBY,qBAAA;EACV,aAAA,GAAgB,oBAAA;EAChB,aAAA,SAAsB,aAAW;EACjC,SAAA;AAAA;;;;;;;cASW,YAAA;EATF;EAAA,SAWA,GAAA;EAFc;EAAA,SAKd,OAAA,EAAS,+BAAA;EAAA,iBACD,EAAA;EAAA,iBACA,MAAA;EAAA,iBACA,aAAA;EAAA,iBACA,aAAA;EAAA,QACT,YAAA;EAAA,QACA,KAAA;EAAA,QACA,eAAA;EAAA,QACA,KAAA;EA4HK;;;;;cArHD,GAAA,UAAa,OAAA,GAAU,qBAAA,EAAqB,SAAA,GAAW,qBAAA;EA0HG;;;;;EA7GhE,MAAA,CAAO,KAAA,EAAO,gBAAA,GAAmB,OAAA;EA5BrB;;;;;EAqDZ,KAAA,IAAS,OAAA;EA/CP;;;;;;EA0EF,IAAA,IAAQ,OAAA;EAjEqD;;;;;;EAwF7D,OAAA,IAAW,OAAA;EAlDF;EA+Ef,EAAA,WAAa,iBAAA,EAAiB,IAAA,EAAM,CAAA,EAAG,QAAA,EAAU,qBAAA,CAAoB,CAAA;EApDvD;EAyDd,GAAA,WAAc,iBAAA,EAAiB,IAAA,EAAM,CAAA,EAAG,QAAA,EAAU,qBAAA,CAAoB,CAAA;EAAA,QAI9D,kBAAA;EAAA,QAaA,mBAAA;EAAA,QAoCA,uBAAA;EAAA,QAeA,oBAAA;EAAA,QAKA,wBAAA;EAAA,QAUA,uBAAA;EAAA,QASA,cAAA;EAAA,QAoBM,oBAAA;EAAA,QAkCN,iBAAA;EAAA,QAYA,kBAAA;AAAA;;;cCvUG,6BAAA,EAA+B,+BA2B3C;AAAA,iBAEe,sBAAA,CAAuB,OAAA,GAAS,qBAAA,GAA2B,+BAA6B;;;iBC/BxF,iBAAA,CAAkB,IAAA,EAAM,iBAAA,EAAiB,IAAA,UAAc,OAAA,UAAiB,QAAA,WAAmB,KAAA,aAAkB,aAAW"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { WorkerClient, createRuntimeWorker } from "@rivmux/runtime-worker";
|
|
2
2
|
//#region src/events.ts
|
|
3
3
|
var PlayerEventEmitter = class {
|
|
4
|
-
|
|
5
|
-
this.listeners = /* @__PURE__ */ new Map();
|
|
6
|
-
}
|
|
4
|
+
listeners = /* @__PURE__ */ new Map();
|
|
7
5
|
on(type, listener) {
|
|
8
6
|
const typedListener = listener;
|
|
9
7
|
const listeners = this.listeners.get(type);
|
|
@@ -49,13 +47,27 @@ function playerErrorToException(error) {
|
|
|
49
47
|
}
|
|
50
48
|
//#endregion
|
|
51
49
|
//#region src/feature-detect.ts
|
|
52
|
-
const
|
|
50
|
+
const REQUIRED_MSE_MIME_TYPES = [{
|
|
51
|
+
mediaType: "video",
|
|
52
|
+
mimeType: createMp4Mime("video", "avc1.42C01E"),
|
|
53
|
+
unsupportedCode: "RIVMUX_UNSUPPORTED_MSE_VIDEO_MIME"
|
|
54
|
+
}, {
|
|
55
|
+
mediaType: "audio",
|
|
56
|
+
mimeType: createMp4Mime("audio", "mp4a.40.2"),
|
|
57
|
+
unsupportedCode: "RIVMUX_UNSUPPORTED_MSE_AUDIO_MIME"
|
|
58
|
+
}];
|
|
53
59
|
function detectMainThreadRuntime() {
|
|
54
60
|
if (typeof Worker === "undefined") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WORKER", "Dedicated Worker is not available in this runtime.", true);
|
|
61
|
+
if (typeof fetch !== "function") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_FETCH", "Fetch is not available in this runtime.", true);
|
|
62
|
+
if (typeof ReadableStream === "undefined") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_READABLE_STREAM", "ReadableStream is not available in this runtime.", true);
|
|
55
63
|
if (typeof WebAssembly === "undefined") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WASM", "WebAssembly is not available in this runtime.", true);
|
|
56
64
|
if (typeof MediaSource === "undefined") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_MSE", "MediaSource is not available in this runtime.", true);
|
|
57
65
|
if (MediaSource.canConstructInDedicatedWorker !== true) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WORKER_MSE", "MediaSource cannot be constructed in a dedicated worker in this runtime.", true);
|
|
58
|
-
if (
|
|
66
|
+
if (typeof MediaSource.isTypeSupported !== "function") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_MSE_TYPE_CHECK", "MediaSource.isTypeSupported is not available in this runtime.", true);
|
|
67
|
+
for (const requirement of REQUIRED_MSE_MIME_TYPES) if (!MediaSource.isTypeSupported(requirement.mimeType)) return createPlayerError("unsupported", requirement.unsupportedCode, `MSE does not support ${requirement.mimeType}.`, true);
|
|
68
|
+
}
|
|
69
|
+
function createMp4Mime(mediaType, codec) {
|
|
70
|
+
return `${mediaType}/mp4; codecs="${codec}"`;
|
|
59
71
|
}
|
|
60
72
|
//#endregion
|
|
61
73
|
//#region src/options.ts
|
|
@@ -86,7 +98,7 @@ const DEFAULT_RIVMUX_PLAYER_OPTIONS = {
|
|
|
86
98
|
}
|
|
87
99
|
};
|
|
88
100
|
function normalizePlayerOptions(options = {}) {
|
|
89
|
-
|
|
101
|
+
const normalizedOptions = {
|
|
90
102
|
playback: {
|
|
91
103
|
...DEFAULT_RIVMUX_PLAYER_OPTIONS.playback,
|
|
92
104
|
...options.playback
|
|
@@ -107,28 +119,86 @@ function normalizePlayerOptions(options = {}) {
|
|
|
107
119
|
...options.network?.retry
|
|
108
120
|
}
|
|
109
121
|
},
|
|
110
|
-
runtime:
|
|
111
|
-
...DEFAULT_RIVMUX_PLAYER_OPTIONS.runtime,
|
|
112
|
-
...options.runtime
|
|
113
|
-
},
|
|
122
|
+
runtime: normalizeRuntimeOptions(options),
|
|
114
123
|
diagnostics: {
|
|
115
124
|
...DEFAULT_RIVMUX_PLAYER_OPTIONS.diagnostics,
|
|
116
125
|
...options.diagnostics
|
|
117
126
|
}
|
|
118
127
|
};
|
|
128
|
+
validateNormalizedOptions(normalizedOptions);
|
|
129
|
+
return normalizedOptions;
|
|
130
|
+
}
|
|
131
|
+
function validateNormalizedOptions(options) {
|
|
132
|
+
validateLatencyOptions(options);
|
|
133
|
+
validateRuntimeOptions(options);
|
|
134
|
+
}
|
|
135
|
+
function validateLatencyOptions(options) {
|
|
136
|
+
const latency = options.latency;
|
|
137
|
+
assertFiniteNonNegativeLatency(latency.startupBuffer, "latency.startupBuffer");
|
|
138
|
+
assertFinitePositiveLatency(latency.target, "latency.target");
|
|
139
|
+
assertFiniteNonNegativeLatency(latency.max, "latency.max");
|
|
140
|
+
assertFiniteNonNegativeLatency(latency.maxForwardBuffer, "latency.maxForwardBuffer");
|
|
141
|
+
assertFiniteNonNegativeLatency(latency.backwardBuffer, "latency.backwardBuffer");
|
|
142
|
+
if (latency.max < latency.target) throwOptionError("runtime", "RIVMUX_INVALID_LATENCY_OPTION", "latency.max must be greater than or equal to latency.target.");
|
|
143
|
+
if (latency.maxForwardBuffer < latency.target) throwOptionError("runtime", "RIVMUX_INVALID_LATENCY_OPTION", "latency.maxForwardBuffer must be greater than or equal to latency.target.");
|
|
144
|
+
}
|
|
145
|
+
function validateRuntimeOptions(options) {
|
|
146
|
+
if (options.runtime.preferWorkerMse !== true) throwOptionError("unsupported", "RIVMUX_UNSUPPORTED_MAIN_THREAD_MSE_FALLBACK", "Main-thread MSE fallback is not implemented; runtime.preferWorkerMse must remain true.");
|
|
147
|
+
}
|
|
148
|
+
function normalizeRuntimeOptions(options) {
|
|
149
|
+
return {
|
|
150
|
+
preferWorkerMse: options.runtime?.preferWorkerMse ?? DEFAULT_RIVMUX_PLAYER_OPTIONS.runtime.preferWorkerMse,
|
|
151
|
+
...options.runtime?.workerUrl === void 0 ? {} : { workerUrl: options.runtime.workerUrl },
|
|
152
|
+
...options.runtime?.wasmUrl === void 0 ? {} : { wasmUrl: options.runtime.wasmUrl }
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function assertFiniteNonNegativeLatency(value, field) {
|
|
156
|
+
if (!Number.isFinite(value) || value < 0) throwOptionError("runtime", "RIVMUX_INVALID_LATENCY_OPTION", `${field} must be a finite number greater than or equal to 0.`);
|
|
157
|
+
}
|
|
158
|
+
function assertFinitePositiveLatency(value, field) {
|
|
159
|
+
if (!Number.isFinite(value) || value <= 0) throwOptionError("runtime", "RIVMUX_INVALID_LATENCY_OPTION", `${field} must be a finite number greater than 0.`);
|
|
160
|
+
}
|
|
161
|
+
function throwOptionError(kind, code, message) {
|
|
162
|
+
throw playerErrorToException(createPlayerError(kind, code, message, true));
|
|
119
163
|
}
|
|
120
164
|
//#endregion
|
|
121
165
|
//#region src/player.ts
|
|
166
|
+
/**
|
|
167
|
+
* Public browser player facade for one HTTP-FLV stream.
|
|
168
|
+
*
|
|
169
|
+
* Create one instance per stream URL, call `attach(video)` first, then
|
|
170
|
+
* `start()`. Call `destroy()` when the instance is no longer needed.
|
|
171
|
+
*/
|
|
122
172
|
var RivmuxPlayer = class {
|
|
173
|
+
/** Original stream URL passed to the constructor. */
|
|
174
|
+
url;
|
|
175
|
+
/** Fully normalized options with defaults applied. */
|
|
176
|
+
options;
|
|
177
|
+
id;
|
|
178
|
+
events = new PlayerEventEmitter();
|
|
179
|
+
workerFactory;
|
|
180
|
+
detectRuntime;
|
|
181
|
+
workerClient;
|
|
182
|
+
video;
|
|
183
|
+
videoStateTimer;
|
|
184
|
+
state = "idle";
|
|
185
|
+
/**
|
|
186
|
+
* Creates a player instance for one stream URL.
|
|
187
|
+
*
|
|
188
|
+
* The instance does not start network loading until `start()` is called.
|
|
189
|
+
*/
|
|
123
190
|
constructor(url, options, internals = {}) {
|
|
124
|
-
this.events = new PlayerEventEmitter();
|
|
125
|
-
this.state = "idle";
|
|
126
191
|
this.url = url;
|
|
127
192
|
this.options = normalizePlayerOptions(options);
|
|
128
193
|
this.id = internals.idFactory?.() ?? createPlayerId();
|
|
129
194
|
this.workerFactory = internals.workerFactory ?? createRuntimeWorker;
|
|
130
195
|
this.detectRuntime = internals.detectRuntime ?? detectMainThreadRuntime;
|
|
131
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Attaches this player to a video element and prepares the worker/MSE pipe.
|
|
199
|
+
*
|
|
200
|
+
* Await this method before calling `start()`.
|
|
201
|
+
*/
|
|
132
202
|
async attach(video) {
|
|
133
203
|
this.assertNotDestroyed("attach");
|
|
134
204
|
if (this.video !== void 0 && this.video !== video) throw playerErrorToException(createPlayerError("runtime", "RIVMUX_ALREADY_ATTACHED", "This player is already attached to a video element.", false));
|
|
@@ -143,6 +213,11 @@ var RivmuxPlayer = class {
|
|
|
143
213
|
await this.workerClient?.waitForMediaSourceHandle({ type: "attach-media-source" });
|
|
144
214
|
this.state = "attached";
|
|
145
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Starts loading, transmuxing, buffering, and playback control.
|
|
218
|
+
*
|
|
219
|
+
* Requires a successful `attach(video)` call first.
|
|
220
|
+
*/
|
|
146
221
|
async start() {
|
|
147
222
|
this.assertNotDestroyed("start");
|
|
148
223
|
if (this.video === void 0 || this.workerClient === void 0) throw playerErrorToException(createPlayerError("runtime", "RIVMUX_START_REQUIRES_ATTACH", "start() requires a previously attached video element.", false));
|
|
@@ -155,6 +230,12 @@ var RivmuxPlayer = class {
|
|
|
155
230
|
this.state = "started";
|
|
156
231
|
this.startVideoStateReporting();
|
|
157
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Stops loading and detaches the current media source.
|
|
235
|
+
*
|
|
236
|
+
* The instance remains reusable; call `start()` again to restart the same
|
|
237
|
+
* stream after the player has stopped.
|
|
238
|
+
*/
|
|
158
239
|
async stop() {
|
|
159
240
|
if (this.state === "destroyed") return;
|
|
160
241
|
if (this.workerClient === void 0) {
|
|
@@ -167,6 +248,12 @@ var RivmuxPlayer = class {
|
|
|
167
248
|
this.detachVideoSource();
|
|
168
249
|
this.state = "stopped";
|
|
169
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Releases worker resources, timers, listeners, and the attached video source.
|
|
253
|
+
*
|
|
254
|
+
* The instance is terminal after destroy and cannot be attached or started
|
|
255
|
+
* again.
|
|
256
|
+
*/
|
|
170
257
|
async destroy() {
|
|
171
258
|
if (this.state === "destroyed") return;
|
|
172
259
|
const workerClient = this.workerClient;
|
|
@@ -185,9 +272,11 @@ var RivmuxPlayer = class {
|
|
|
185
272
|
this.state = "destroyed";
|
|
186
273
|
this.events.clear();
|
|
187
274
|
}
|
|
275
|
+
/** Registers an event listener for a typed player event. */
|
|
188
276
|
on(type, listener) {
|
|
189
277
|
this.events.on(type, listener);
|
|
190
278
|
}
|
|
279
|
+
/** Removes a previously registered event listener. */
|
|
191
280
|
off(type, listener) {
|
|
192
281
|
this.events.off(type, listener);
|
|
193
282
|
}
|
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/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 = toError(error.cause ?? 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 const M1_VIDEO_MIME = 'video/mp4; codecs=\"avc1.42C01E\"'\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 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 (!MediaSource.isTypeSupported(M1_VIDEO_MIME)) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_M1_VIDEO_MIME', `MSE does not support ${M1_VIDEO_MIME}.`, true)\n }\n\n return undefined\n}\n","import type { NormalizedRivmuxPlayerOptions, 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 return {\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: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.runtime,\n ...options.runtime,\n },\n diagnostics: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.diagnostics,\n ...options.diagnostics,\n },\n }\n}\n","import { PlayerEventEmitter } from './events'\nimport { createPlayerError, playerErrorToException } from './errors'\nimport { detectMainThreadRuntime } from './feature-detect'\nimport { normalizePlayerOptions } from './options'\nimport { createRuntimeWorker, 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' | 'stopped' | 'destroyed'\n\nexport type RivmuxPlayerInternals = {\n workerFactory?: RuntimeWorkerFactory\n detectRuntime?: () => PlayerError | undefined\n idFactory?: () => string\n}\n\nexport class RivmuxPlayer {\n readonly url: string\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 workerClient?: WorkerClient\n private video?: HTMLVideoElement\n private videoStateTimer?: ReturnType<typeof setInterval>\n private state: PlayerState = 'idle'\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 }\n\n async attach(video: HTMLVideoElement): Promise<void> {\n this.assertNotDestroyed('attach')\n\n if (this.video !== undefined && this.video !== 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.video = video\n this.applyPlaybackOptions(video)\n this.ensureWorkerClient()\n await this.workerClient?.waitForMediaSourceHandle({ type: 'attach-media-source' })\n this.state = 'attached'\n }\n\n async start(): Promise<void> {\n this.assertNotDestroyed('start')\n\n if (this.video === undefined || 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 this.state = 'attached'\n }\n\n this.workerClient.post({ type: 'start' })\n this.state = 'started'\n this.startVideoStateReporting()\n }\n\n async stop(): Promise<void> {\n if (this.state === 'destroyed') {\n return\n }\n\n if (this.workerClient === undefined) {\n this.state = 'stopped'\n this.events.emit('stopped', undefined)\n return\n }\n\n this.stopVideoStateReporting()\n await this.workerClient.waitForStopped({ type: 'stop' })\n this.detachVideoSource()\n this.state = 'stopped'\n }\n\n async destroy(): Promise<void> {\n if (this.state === 'destroyed') {\n return\n }\n\n const workerClient = this.workerClient\n this.workerClient = undefined\n this.stopVideoStateReporting()\n\n if (workerClient !== undefined) {\n try {\n await workerClient.waitForDestroyed({ type: 'destroy' })\n } finally {\n workerClient.dispose()\n }\n } else {\n this.events.emit('destroyed', undefined)\n }\n\n if (this.video !== undefined) {\n this.detachVideoSource()\n this.video = undefined\n }\n\n this.state = 'destroyed'\n this.events.clear()\n }\n\n on<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.events.on(type, listener)\n }\n\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) => this.events.emit('error', error),\n })\n this.workerClient.post({ type: 'init', id: this.id, url: this.url, options: this.options })\n }\n\n private handleWorkerMessage(message: WorkerMessage): void {\n if (this.state === 'destroyed') {\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 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.video === undefined) {\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 Reflect.set(this.video, 'srcObject', handle)\n }\n\n private applyPlaybackOptions(video: HTMLVideoElement): void {\n video.muted = this.options.playback.muted\n video.autoplay = this.options.playback.autoPlay\n }\n\n private startVideoStateReporting(): void {\n this.stopVideoStateReporting()\n this.postVideoState()\n\n const intervalMs = Math.max(100, Math.min(this.options.diagnostics.statsIntervalMs, 250))\n this.videoStateTimer = setInterval(() => {\n this.postVideoState()\n }, intervalMs)\n }\n\n private stopVideoStateReporting(): void {\n if (this.videoStateTimer === undefined) {\n return\n }\n\n clearInterval(this.videoStateTimer)\n this.videoStateTimer = undefined\n }\n\n private postVideoState(): void {\n const video = this.video\n const workerClient = this.workerClient\n if (video === undefined || workerClient === undefined || this.state !== 'started') {\n return\n }\n\n const droppedFrames = getDroppedFrames(video)\n workerClient.post({\n type: 'video-state',\n state: {\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\n private async applyPlaybackControl(action: PlaybackControlAction): Promise<void> {\n const video = this.video\n const workerClient = this.workerClient\n if (video === undefined || workerClient === undefined || this.state !== 'started') {\n return\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\n workerClient.post({ type: 'playback-control-result', result: { type: action.type, accepted: true } })\n this.postVideoState()\n } catch (cause) {\n workerClient.post({\n type: 'playback-control-result',\n result: {\n type: action.type,\n accepted: false,\n message: cause instanceof Error ? cause.message : String(cause),\n },\n })\n }\n }\n\n private detachVideoSource(): 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 private assertNotDestroyed(method: string): void {\n if (this.state !== 'destroyed') {\n return\n }\n\n throw playerErrorToException(createPlayerError('runtime', 'RIVMUX_PLAYER_DESTROYED', `RivmuxPlayer.${method}() cannot be called after destroy().`, true))\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 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"],"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/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 = toError(error.cause ?? 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 type RequiredMseMimeType = {\n readonly mediaType: 'video' | 'audio'\n readonly mimeType: string\n readonly unsupportedCode: string\n}\n\nexport const REQUIRED_MSE_MIME_TYPES = [\n {\n mediaType: 'video',\n mimeType: createMp4Mime('video', 'avc1.42C01E'),\n unsupportedCode: 'RIVMUX_UNSUPPORTED_MSE_VIDEO_MIME',\n },\n {\n mediaType: 'audio',\n mimeType: createMp4Mime('audio', 'mp4a.40.2'),\n unsupportedCode: 'RIVMUX_UNSUPPORTED_MSE_AUDIO_MIME',\n },\n] as const satisfies readonly RequiredMseMimeType[]\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 for (const requirement of REQUIRED_MSE_MIME_TYPES) {\n if (!MediaSource.isTypeSupported(requirement.mimeType)) {\n return createPlayerError('unsupported', requirement.unsupportedCode, `MSE does not support ${requirement.mimeType}.`, true)\n }\n }\n\n return undefined\n}\n\nfunction createMp4Mime(mediaType: RequiredMseMimeType['mediaType'], codec: string): string {\n return `${mediaType}/mp4; codecs=\"${codec}\"`\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 { PlayerEventEmitter } from './events'\nimport { createPlayerError, playerErrorToException } from './errors'\nimport { detectMainThreadRuntime } from './feature-detect'\nimport { normalizePlayerOptions } from './options'\nimport { createRuntimeWorker, 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' | 'stopped' | '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 workerClient?: WorkerClient\n private video?: HTMLVideoElement\n private videoStateTimer?: ReturnType<typeof setInterval>\n private state: PlayerState = 'idle'\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 }\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.assertNotDestroyed('attach')\n\n if (this.video !== undefined && this.video !== 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.video = video\n this.applyPlaybackOptions(video)\n this.ensureWorkerClient()\n await this.workerClient?.waitForMediaSourceHandle({ type: 'attach-media-source' })\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.assertNotDestroyed('start')\n\n if (this.video === undefined || 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 this.state = 'attached'\n }\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 async stop(): Promise<void> {\n if (this.state === 'destroyed') {\n return\n }\n\n if (this.workerClient === undefined) {\n this.state = 'stopped'\n this.events.emit('stopped', undefined)\n return\n }\n\n this.stopVideoStateReporting()\n await this.workerClient.waitForStopped({ type: 'stop' })\n this.detachVideoSource()\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 const workerClient = this.workerClient\n this.workerClient = undefined\n this.stopVideoStateReporting()\n\n if (workerClient !== undefined) {\n try {\n await workerClient.waitForDestroyed({ type: 'destroy' })\n } finally {\n workerClient.dispose()\n }\n } else {\n this.events.emit('destroyed', undefined)\n }\n\n if (this.video !== undefined) {\n this.detachVideoSource()\n this.video = undefined\n }\n\n this.state = 'destroyed'\n this.events.clear()\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) => this.events.emit('error', error),\n })\n this.workerClient.post({ type: 'init', id: this.id, url: this.url, options: this.options })\n }\n\n private handleWorkerMessage(message: WorkerMessage): void {\n if (this.state === 'destroyed') {\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 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.video === undefined) {\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 Reflect.set(this.video, 'srcObject', handle)\n }\n\n private applyPlaybackOptions(video: HTMLVideoElement): void {\n video.muted = this.options.playback.muted\n video.autoplay = this.options.playback.autoPlay\n }\n\n private startVideoStateReporting(): void {\n this.stopVideoStateReporting()\n this.postVideoState()\n\n const intervalMs = Math.max(100, Math.min(this.options.diagnostics.statsIntervalMs, 250))\n this.videoStateTimer = setInterval(() => {\n this.postVideoState()\n }, intervalMs)\n }\n\n private stopVideoStateReporting(): void {\n if (this.videoStateTimer === undefined) {\n return\n }\n\n clearInterval(this.videoStateTimer)\n this.videoStateTimer = undefined\n }\n\n private postVideoState(): void {\n const video = this.video\n const workerClient = this.workerClient\n if (video === undefined || workerClient === undefined || this.state !== 'started') {\n return\n }\n\n const droppedFrames = getDroppedFrames(video)\n workerClient.post({\n type: 'video-state',\n state: {\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\n private async applyPlaybackControl(action: PlaybackControlAction): Promise<void> {\n const video = this.video\n const workerClient = this.workerClient\n if (video === undefined || workerClient === undefined || this.state !== 'started') {\n return\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\n workerClient.post({ type: 'playback-control-result', result: { type: action.type, accepted: true } })\n this.postVideoState()\n } catch (cause) {\n workerClient.post({\n type: 'playback-control-result',\n result: {\n type: action.type,\n accepted: false,\n message: cause instanceof Error ? cause.message : String(cause),\n },\n })\n }\n }\n\n private detachVideoSource(): 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 private assertNotDestroyed(method: string): void {\n if (this.state !== 'destroyed') {\n return\n }\n\n throw playerErrorToException(createPlayerError('runtime', 'RIVMUX_PLAYER_DESTROYED', `RivmuxPlayer.${method}() cannot be called after destroy().`, true))\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 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"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rivmux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Public player package for Rivmux.",
|
|
5
|
+
"homepage": "https://www.npmjs.com/package/rivmux",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/yangxu52/Rivmux/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/yangxu52/Rivmux.git",
|
|
12
|
+
"directory": "packages/player"
|
|
13
|
+
},
|
|
5
14
|
"license": "Apache-2.0",
|
|
6
15
|
"type": "module",
|
|
7
16
|
"sideEffects": false,
|
|
@@ -21,12 +30,12 @@
|
|
|
21
30
|
"module": "./dist/index.js",
|
|
22
31
|
"types": "./dist/index.d.ts",
|
|
23
32
|
"dependencies": {
|
|
24
|
-
"@rivmux/runtime-worker": "0.
|
|
25
|
-
"@rivmux/protocol": "0.
|
|
33
|
+
"@rivmux/runtime-worker": "0.3.0",
|
|
34
|
+
"@rivmux/protocol": "0.3.0"
|
|
26
35
|
},
|
|
27
36
|
"devDependencies": {
|
|
28
37
|
"tsdown": "^0.22.3",
|
|
29
|
-
"typescript": "~
|
|
38
|
+
"typescript": "~6.0.3",
|
|
30
39
|
"vitest": "^4.1.9"
|
|
31
40
|
},
|
|
32
41
|
"engines": {
|