@stinkycomputing/web-live-player 0.1.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 +323 -0
- package/dist/audio/file-audio-player.d.ts +60 -0
- package/dist/audio/live-audio-player.d.ts +64 -0
- package/dist/decoders/decoder-interface.d.ts +63 -0
- package/dist/decoders/wasm-decoder.d.ts +68 -0
- package/dist/decoders/wasm-worker/H264NALDecoder.worker.d.ts +1 -0
- package/dist/decoders/webcodecs-decoder.d.ts +82 -0
- package/dist/index.d.ts +35 -0
- package/dist/player/base-player.d.ts +54 -0
- package/dist/player/base-player.test.d.ts +8 -0
- package/dist/player/file-player.d.ts +200 -0
- package/dist/player/live-player.d.ts +219 -0
- package/dist/protocol/codec-utils.d.ts +25 -0
- package/dist/protocol/sesame-binary-protocol.d.ts +98 -0
- package/dist/scheduling/frame-scheduler.d.ts +122 -0
- package/dist/scheduling/frame-scheduler.test.d.ts +11 -0
- package/dist/sources/mp4-file-source.d.ts +143 -0
- package/dist/sources/standalone-moq-source.d.ts +44 -0
- package/dist/sources/stream-source.d.ts +73 -0
- package/dist/sources/websocket-source.d.ts +120 -0
- package/dist/types.d.ts +58 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/web-live-player.cjs +234 -0
- package/dist/web-live-player.mjs +9559 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# Web Live Player
|
|
2
|
+
|
|
3
|
+
A framework-agnostic video streaming library with support for MoQ (Media over QUIC) and WebSocket backends.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **WebCodecs-based decoding** - Hardware-accelerated video decoding
|
|
8
|
+
- **MoQ support** - Native Media over QUIC protocol support via `stinky-moq-js`
|
|
9
|
+
- **Pluggable stream sources** - Use dependency injection to provide video data from any transport
|
|
10
|
+
- **Frame scheduling** - Automatic buffering and drift correction for smooth playback
|
|
11
|
+
- **No framework dependencies** - Works with vanilla JS, React, Three.js, or any other framework
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @stinkycomputing/web-live-player
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For MoQ support, also install:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install stinky-moq-js
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### Using with MoQ (Standalone)
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { createPlayer, createStandaloneMoQSource } from '@stinkycomputing/web-live-player';
|
|
31
|
+
|
|
32
|
+
// Create player
|
|
33
|
+
const player = createPlayer({
|
|
34
|
+
preferredDecoder: 'webcodecs-hw',
|
|
35
|
+
bufferSizeFrames: 3,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Create MoQ source
|
|
39
|
+
const moqSource = createStandaloneMoQSource({
|
|
40
|
+
relayUrl: 'https://moq-relay.example.com',
|
|
41
|
+
namespace: 'live/stream',
|
|
42
|
+
subscriptions: [
|
|
43
|
+
{ trackName: 'video', streamType: 'video' },
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Connect and play
|
|
48
|
+
await moqSource.connect();
|
|
49
|
+
player.setStreamSource(moqSource);
|
|
50
|
+
player.setTrackFilter('video');
|
|
51
|
+
player.play();
|
|
52
|
+
|
|
53
|
+
// Render loop
|
|
54
|
+
function render(timestamp) {
|
|
55
|
+
const frame = player.getVideoFrame(timestamp);
|
|
56
|
+
if (frame) {
|
|
57
|
+
ctx.drawImage(frame, 0, 0);
|
|
58
|
+
}
|
|
59
|
+
requestAnimationFrame(render);
|
|
60
|
+
}
|
|
61
|
+
requestAnimationFrame(render);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Using with Elmo's MoQSession
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { createPlayer } from '@stinkycomputing/web-live-player';
|
|
68
|
+
import { MoQDiscoveryUtils } from '@elmo/core';
|
|
69
|
+
|
|
70
|
+
// Find session from Elmo's node tree
|
|
71
|
+
// MoQSessionNode implements IStreamSource directly
|
|
72
|
+
const session = MoQDiscoveryUtils.findMoQSession(currentNode, 'my-session');
|
|
73
|
+
|
|
74
|
+
// Create and configure player - session can be used directly as stream source
|
|
75
|
+
const player = createPlayer();
|
|
76
|
+
player.setStreamSource(session);
|
|
77
|
+
player.setTrackFilter('video-track');
|
|
78
|
+
player.play();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Custom Stream Source
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { createPlayer, IStreamSource, BaseStreamSource } from '@stinkycomputing/web-live-player';
|
|
85
|
+
|
|
86
|
+
class MyCustomSource extends BaseStreamSource {
|
|
87
|
+
async connect() {
|
|
88
|
+
// Your connection logic
|
|
89
|
+
this._connected = true;
|
|
90
|
+
this.emit('connected');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Call this when you receive video data
|
|
94
|
+
handleVideoData(trackName: string, data: ParsedData) {
|
|
95
|
+
this.emit('data', {
|
|
96
|
+
trackName,
|
|
97
|
+
streamType: 'video',
|
|
98
|
+
data,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const source = new MyCustomSource();
|
|
104
|
+
await source.connect();
|
|
105
|
+
|
|
106
|
+
const player = createPlayer();
|
|
107
|
+
player.setStreamSource(source);
|
|
108
|
+
player.play();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## API Reference
|
|
112
|
+
|
|
113
|
+
### `createPlayer(config?)`
|
|
114
|
+
|
|
115
|
+
Creates a new player instance.
|
|
116
|
+
|
|
117
|
+
**Config options:**
|
|
118
|
+
- `preferredDecoder`: `'webcodecs-hw'` | `'webcodecs-sw'` | `'wasm'` - Decoder preference
|
|
119
|
+
- `bufferSizeFrames`: `number` - Target buffer size (default: 3)
|
|
120
|
+
- `debugLogging`: `boolean` - Enable debug logging
|
|
121
|
+
|
|
122
|
+
### `LiveVideoPlayer`
|
|
123
|
+
|
|
124
|
+
Main player class.
|
|
125
|
+
|
|
126
|
+
**Methods:**
|
|
127
|
+
- `setStreamSource(source: IStreamSource)` - Set the stream data source
|
|
128
|
+
- `setTrackFilter(trackName: string)` - Filter for specific track
|
|
129
|
+
- `play()` - Start playback
|
|
130
|
+
- `pause()` - Pause playback
|
|
131
|
+
- `getVideoFrame(timestampMs: number)` - Get frame for current render timestamp
|
|
132
|
+
- `getStats()` - Get playback statistics
|
|
133
|
+
- `dispose()` - Clean up resources
|
|
134
|
+
|
|
135
|
+
**Events:**
|
|
136
|
+
- `frame` - Emitted when a frame is decoded
|
|
137
|
+
- `metadata` - Emitted when stream metadata is received
|
|
138
|
+
- `statechange` - Emitted when player state changes
|
|
139
|
+
- `error` - Emitted on errors
|
|
140
|
+
|
|
141
|
+
### `IStreamSource`
|
|
142
|
+
|
|
143
|
+
Interface for stream data sources.
|
|
144
|
+
|
|
145
|
+
**Events to emit:**
|
|
146
|
+
- `data` - Stream data event with `{ trackName, streamType, data }`
|
|
147
|
+
- `connected` - When connected
|
|
148
|
+
- `disconnected` - When disconnected
|
|
149
|
+
- `error` - On errors
|
|
150
|
+
|
|
151
|
+
## Rendering Frames to Canvas
|
|
152
|
+
|
|
153
|
+
The player returns `VideoFrame` objects that can be rendered in multiple ways:
|
|
154
|
+
|
|
155
|
+
### Basic Canvas Rendering
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const canvas = document.getElementById('video-canvas') as HTMLCanvasElement;
|
|
159
|
+
const ctx = canvas.getContext('2d')!;
|
|
160
|
+
|
|
161
|
+
function render(timestamp: number) {
|
|
162
|
+
const frame = player.getVideoFrame(timestamp);
|
|
163
|
+
if (frame) {
|
|
164
|
+
// Resize canvas to match video dimensions
|
|
165
|
+
if (canvas.width !== frame.displayWidth || canvas.height !== frame.displayHeight) {
|
|
166
|
+
canvas.width = frame.displayWidth;
|
|
167
|
+
canvas.height = frame.displayHeight;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Draw the frame
|
|
171
|
+
ctx.drawImage(frame, 0, 0);
|
|
172
|
+
|
|
173
|
+
// IMPORTANT: Close the frame when done to release memory
|
|
174
|
+
frame.close();
|
|
175
|
+
}
|
|
176
|
+
requestAnimationFrame(render);
|
|
177
|
+
}
|
|
178
|
+
requestAnimationFrame(render);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### WebGL / Three.js Rendering
|
|
182
|
+
|
|
183
|
+
For GPU-accelerated rendering (e.g., in Three.js):
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// Create a texture
|
|
187
|
+
const texture = new THREE.Texture();
|
|
188
|
+
texture.minFilter = THREE.LinearFilter;
|
|
189
|
+
texture.magFilter = THREE.LinearFilter;
|
|
190
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
191
|
+
|
|
192
|
+
// In your render loop
|
|
193
|
+
function render(timestamp: number) {
|
|
194
|
+
const frame = player.getVideoFrame(timestamp);
|
|
195
|
+
if (frame) {
|
|
196
|
+
// Update texture with the VideoFrame
|
|
197
|
+
texture.image = frame;
|
|
198
|
+
texture.needsUpdate = true;
|
|
199
|
+
|
|
200
|
+
// Close previous frame if stored
|
|
201
|
+
if (lastFrame) lastFrame.close();
|
|
202
|
+
lastFrame = frame;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
renderer.render(scene, camera);
|
|
206
|
+
requestAnimationFrame(render);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Handling YUV Frames (WASM Decoder)
|
|
211
|
+
|
|
212
|
+
When using the WASM decoder, the library automatically converts YUV frames to `VideoFrame` objects using the browser's native I420 support. The GPU handles YUV→RGB conversion, so you can use the same rendering code regardless of decoder:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// The player always returns VideoFrame, even with WASM decoder
|
|
216
|
+
const frame = player.getVideoFrame(timestamp);
|
|
217
|
+
if (frame) {
|
|
218
|
+
ctx.drawImage(frame, 0, 0);
|
|
219
|
+
frame.close();
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
If you need raw YUV data for custom processing, you can access the `WasmDecoder` directly:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { WasmDecoder } from '@stinkycomputing/web-live-player';
|
|
227
|
+
|
|
228
|
+
const decoder = new WasmDecoder({
|
|
229
|
+
onFrameDecoded: (yuvFrame) => {
|
|
230
|
+
// yuvFrame has: { y, u, v, width, height, stride, chromaStride, chromaHeight, timestamp }
|
|
231
|
+
// Process raw YUV data here
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Best Practices
|
|
237
|
+
|
|
238
|
+
1. **Always close VideoFrames** - Call `frame.close()` when done to prevent memory leaks
|
|
239
|
+
2. **Check for null frames** - `getVideoFrame()` returns null when no frame is ready
|
|
240
|
+
3. **Use performance.now()** - Pass accurate timestamps for proper frame scheduling
|
|
241
|
+
4. **Handle resize** - Update canvas dimensions when video dimensions change
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
## Bundler Configuration
|
|
245
|
+
|
|
246
|
+
### WASM Decoder (tinyh264)
|
|
247
|
+
|
|
248
|
+
The WASM decoder uses `tinyh264` which requires special bundler configuration for its Web Worker and WASM assets.
|
|
249
|
+
|
|
250
|
+
#### Vite
|
|
251
|
+
|
|
252
|
+
Add the following to your `vite.config.ts`:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { defineConfig } from 'vite';
|
|
256
|
+
|
|
257
|
+
export default defineConfig({
|
|
258
|
+
// Handle tinyh264's .asset files as URLs
|
|
259
|
+
assetsInclude: ['**/*.asset'],
|
|
260
|
+
|
|
261
|
+
// Ensure worker files are bundled correctly
|
|
262
|
+
worker: {
|
|
263
|
+
format: 'es',
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### Webpack
|
|
269
|
+
|
|
270
|
+
For Webpack, you may need to configure asset handling:
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
module.exports = {
|
|
274
|
+
module: {
|
|
275
|
+
rules: [
|
|
276
|
+
{
|
|
277
|
+
test: /\.asset$/,
|
|
278
|
+
type: 'asset/resource',
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### WebCodecs Decoder (Recommended)
|
|
286
|
+
|
|
287
|
+
If you only need WebCodecs-based decoding (hardware or software), no special bundler configuration is required. Simply use:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
const player = createPlayer({
|
|
291
|
+
preferredDecoder: 'webcodecs-hw', // or 'webcodecs-sw'
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Demo
|
|
296
|
+
|
|
297
|
+
Run the demo application:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
cd video-player
|
|
301
|
+
npm install
|
|
302
|
+
npm run dev
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Open http://localhost:3001 to see the demo.
|
|
306
|
+
|
|
307
|
+
## Building
|
|
308
|
+
|
|
309
|
+
Build the library:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
npm run build
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Build the demo:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
npm run build:demo
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## License
|
|
322
|
+
|
|
323
|
+
MIT
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Audio Player
|
|
3
|
+
*
|
|
4
|
+
* Decodes and plays audio from MP4 files using WebCodecs AudioDecoder
|
|
5
|
+
* and AudioWorklet for real-time playback.
|
|
6
|
+
*/
|
|
7
|
+
export interface AudioPlayerConfig {
|
|
8
|
+
sampleRate?: number;
|
|
9
|
+
channels?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class FileAudioPlayer {
|
|
12
|
+
private ctx;
|
|
13
|
+
private decoder;
|
|
14
|
+
private workletNode;
|
|
15
|
+
private gainNode;
|
|
16
|
+
private initialized;
|
|
17
|
+
private targetSampleRate;
|
|
18
|
+
private codecConfig;
|
|
19
|
+
constructor(context: AudioContext, config?: AudioPlayerConfig);
|
|
20
|
+
/**
|
|
21
|
+
* Initialize the audio player with codec info from MP4
|
|
22
|
+
*/
|
|
23
|
+
init(codec: string, sampleRate: number, numberOfChannels: number, description?: Uint8Array): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Handle decoded audio frame
|
|
26
|
+
*/
|
|
27
|
+
private handleDecodedFrame;
|
|
28
|
+
/**
|
|
29
|
+
* Resample audio to target sample rate
|
|
30
|
+
*/
|
|
31
|
+
private resampleAudio;
|
|
32
|
+
/**
|
|
33
|
+
* Decode an audio sample
|
|
34
|
+
*/
|
|
35
|
+
decode(data: Uint8Array, timestamp: number, duration: number): void;
|
|
36
|
+
/**
|
|
37
|
+
* Start audio playback
|
|
38
|
+
*/
|
|
39
|
+
start(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Stop audio playback
|
|
42
|
+
*/
|
|
43
|
+
stop(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Clear audio buffers
|
|
46
|
+
*/
|
|
47
|
+
clear(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Reset timing for seamless loop (clears worklet buffer)
|
|
50
|
+
*/
|
|
51
|
+
resetTiming(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Set volume (0-1)
|
|
54
|
+
*/
|
|
55
|
+
setVolume(volume: number): void;
|
|
56
|
+
/**
|
|
57
|
+
* Dispose of resources
|
|
58
|
+
*/
|
|
59
|
+
dispose(): void;
|
|
60
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { HeaderCodecData } from '../protocol/sesame-binary-protocol';
|
|
2
|
+
|
|
3
|
+
export interface LiveAudioConfig {
|
|
4
|
+
bufferDelayMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class LiveAudioPlayer {
|
|
7
|
+
private ctx;
|
|
8
|
+
private decoder;
|
|
9
|
+
private workletNode;
|
|
10
|
+
private gainNode;
|
|
11
|
+
private initialized;
|
|
12
|
+
private targetSampleRate;
|
|
13
|
+
private startTime;
|
|
14
|
+
private bufferDelayMs;
|
|
15
|
+
constructor(context: AudioContext, config?: LiveAudioConfig);
|
|
16
|
+
/**
|
|
17
|
+
* Initialize with codec info from stream header
|
|
18
|
+
*/
|
|
19
|
+
init(codecData?: HeaderCodecData): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Build decoder config based on codec type
|
|
22
|
+
*/
|
|
23
|
+
private buildDecoderConfig;
|
|
24
|
+
/**
|
|
25
|
+
* Handle decoded audio frame
|
|
26
|
+
*/
|
|
27
|
+
private handleDecodedFrame;
|
|
28
|
+
/**
|
|
29
|
+
* Resample audio to target sample rate
|
|
30
|
+
*/
|
|
31
|
+
private resampleAudio;
|
|
32
|
+
/**
|
|
33
|
+
* Queue encoded audio data for decoding
|
|
34
|
+
*/
|
|
35
|
+
decode(data: Uint8Array, pts?: bigint): void;
|
|
36
|
+
/**
|
|
37
|
+
* Set buffer delay for A/V sync
|
|
38
|
+
*/
|
|
39
|
+
setBufferDelay(delayMs: number): void;
|
|
40
|
+
/**
|
|
41
|
+
* Start audio playback
|
|
42
|
+
*/
|
|
43
|
+
start(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Stop audio playback
|
|
46
|
+
*/
|
|
47
|
+
stop(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Clear audio buffers (for seek/reset)
|
|
50
|
+
*/
|
|
51
|
+
clear(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Reset timing reference
|
|
54
|
+
*/
|
|
55
|
+
resetTiming(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Set volume (0-1)
|
|
58
|
+
*/
|
|
59
|
+
setVolume(volume: number): void;
|
|
60
|
+
/**
|
|
61
|
+
* Dispose resources
|
|
62
|
+
*/
|
|
63
|
+
dispose(): void;
|
|
64
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ParsedData, HeaderCodecData } from '../protocol/sesame-binary-protocol';
|
|
2
|
+
import { YUVFrame } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Decoder state (matches WebCodecs VideoDecoder states)
|
|
6
|
+
*/
|
|
7
|
+
export type DecoderState = 'unconfigured' | 'configuring' | 'configured' | 'closed';
|
|
8
|
+
/**
|
|
9
|
+
* Frame output from a decoder - can be VideoFrame or YUVFrame
|
|
10
|
+
*/
|
|
11
|
+
export type DecodedFrame = VideoFrame | YUVFrame;
|
|
12
|
+
/**
|
|
13
|
+
* Configuration options for decoder initialization
|
|
14
|
+
*/
|
|
15
|
+
export interface VideoDecoderOptions {
|
|
16
|
+
/** Callback for decoded frames */
|
|
17
|
+
onFrameDecoded?: (frame: DecodedFrame) => void;
|
|
18
|
+
/** Callback for errors */
|
|
19
|
+
onError?: (error: Error) => void;
|
|
20
|
+
/** Callback when decoder queue overflows */
|
|
21
|
+
onQueueOverflow?: (queueSize: number) => void;
|
|
22
|
+
/** Maximum decoder queue size before overflow callback */
|
|
23
|
+
maxQueueSize?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Common interface for video decoders
|
|
27
|
+
*/
|
|
28
|
+
export interface IVideoDecoder {
|
|
29
|
+
/**
|
|
30
|
+
* Current decoder state
|
|
31
|
+
*/
|
|
32
|
+
readonly state: DecoderState | string;
|
|
33
|
+
/**
|
|
34
|
+
* Configure the decoder for a specific codec
|
|
35
|
+
* @param codecData - Codec configuration from stream header
|
|
36
|
+
*/
|
|
37
|
+
configure(codecData: HeaderCodecData): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Decode a binary packet from the stream
|
|
40
|
+
* @param data - Parsed stream data containing header and payload
|
|
41
|
+
*/
|
|
42
|
+
decodeBinary(data: ParsedData): void;
|
|
43
|
+
/**
|
|
44
|
+
* Flush pending frames (synchronous reset)
|
|
45
|
+
*/
|
|
46
|
+
flush(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Reset the decoder to configured state (ready for new keyframe)
|
|
49
|
+
*/
|
|
50
|
+
reset(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Dispose the decoder and release resources
|
|
53
|
+
*/
|
|
54
|
+
dispose(): void;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Type guard to check if a frame is a VideoFrame
|
|
58
|
+
*/
|
|
59
|
+
export declare function isVideoFrame(frame: DecodedFrame): frame is VideoFrame;
|
|
60
|
+
/**
|
|
61
|
+
* Type guard to check if a frame is a YUVFrame
|
|
62
|
+
*/
|
|
63
|
+
export declare function isYUVFrame(frame: DecodedFrame): frame is YUVFrame;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ParsedData } from '../protocol/sesame-binary-protocol';
|
|
2
|
+
import { YUVFrame } from '../types';
|
|
3
|
+
import { IVideoDecoder } from './decoder-interface';
|
|
4
|
+
|
|
5
|
+
export type { YUVFrame } from '../types';
|
|
6
|
+
export interface WasmDecoderConfig {
|
|
7
|
+
onFrameDecoded?: (frame: YUVFrame) => void;
|
|
8
|
+
onError?: (error: Error) => void;
|
|
9
|
+
onQueueOverflow?: (queueSize: number) => void;
|
|
10
|
+
maxQueueSize?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class WasmDecoder implements IVideoDecoder {
|
|
13
|
+
private worker?;
|
|
14
|
+
private _queueSize;
|
|
15
|
+
private pendingTimestamps;
|
|
16
|
+
private pendingFrames;
|
|
17
|
+
private maxQueueSize;
|
|
18
|
+
private onFrameDecoded?;
|
|
19
|
+
private onError?;
|
|
20
|
+
private onQueueOverflow?;
|
|
21
|
+
configured: boolean;
|
|
22
|
+
constructor(config?: WasmDecoderConfig);
|
|
23
|
+
get queueSize(): number;
|
|
24
|
+
/**
|
|
25
|
+
* Get decoder state (for compatibility with WebCodecsDecoder)
|
|
26
|
+
*/
|
|
27
|
+
get state(): string;
|
|
28
|
+
/**
|
|
29
|
+
* Configure the decoder (initializes the worker)
|
|
30
|
+
*/
|
|
31
|
+
configure(_codecData: {
|
|
32
|
+
codec_type: number;
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Decode a binary packet (same interface as WebCodecsDecoder)
|
|
38
|
+
*/
|
|
39
|
+
decodeBinary(data: ParsedData): void;
|
|
40
|
+
/**
|
|
41
|
+
* Process next frame from queue
|
|
42
|
+
*/
|
|
43
|
+
private decodeNext;
|
|
44
|
+
/**
|
|
45
|
+
* Send frame to worker for decoding
|
|
46
|
+
*/
|
|
47
|
+
private decode;
|
|
48
|
+
/**
|
|
49
|
+
* Handle decoded picture from worker
|
|
50
|
+
*/
|
|
51
|
+
private handlePictureReady;
|
|
52
|
+
/**
|
|
53
|
+
* Flush the decoder (clear pending frames)
|
|
54
|
+
*/
|
|
55
|
+
flush(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Synchronous flush
|
|
58
|
+
*/
|
|
59
|
+
flushSync(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Reset the decoder (same as flush for WASM decoder)
|
|
62
|
+
*/
|
|
63
|
+
reset(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Dispose of the decoder
|
|
66
|
+
*/
|
|
67
|
+
dispose(): void;
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ParsedData, HeaderCodecData } from '../protocol/sesame-binary-protocol';
|
|
2
|
+
import { Logger } from '../types';
|
|
3
|
+
import { IVideoDecoder } from './decoder-interface';
|
|
4
|
+
|
|
5
|
+
export interface DecoderConfig {
|
|
6
|
+
preferHardware?: boolean;
|
|
7
|
+
logger?: Logger;
|
|
8
|
+
onFrameDecoded?: (frame: VideoFrame) => void;
|
|
9
|
+
/** Alias for onFrameDecoded */
|
|
10
|
+
onFrame?: (frame: VideoFrame) => void;
|
|
11
|
+
onError?: (error: Error) => void;
|
|
12
|
+
onQueueOverflow?: (queueSize: number) => void;
|
|
13
|
+
maxQueueSize?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Sample data for decoding (from file demuxer)
|
|
17
|
+
*/
|
|
18
|
+
export interface SampleData {
|
|
19
|
+
data: Uint8Array;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
duration?: number;
|
|
22
|
+
isKeyframe: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* WebCodecs-based video decoder
|
|
26
|
+
*/
|
|
27
|
+
export declare class WebCodecsDecoder implements IVideoDecoder {
|
|
28
|
+
private decoder;
|
|
29
|
+
private config;
|
|
30
|
+
private flushing;
|
|
31
|
+
private logger;
|
|
32
|
+
private maxQueueSize;
|
|
33
|
+
onFrameDecoded?: (frame: VideoFrame) => void;
|
|
34
|
+
onError?: (error: Error) => void;
|
|
35
|
+
onQueueOverflow?: (queueSize: number) => void;
|
|
36
|
+
chunksSentToDecoder: number;
|
|
37
|
+
framesDecoded: number;
|
|
38
|
+
constructor(options?: DecoderConfig);
|
|
39
|
+
get decodeQueueSize(): number;
|
|
40
|
+
get state(): string;
|
|
41
|
+
private createDecoder;
|
|
42
|
+
/**
|
|
43
|
+
* Configure the decoder for a specific codec (from HeaderCodecData)
|
|
44
|
+
*/
|
|
45
|
+
configure(codecData: HeaderCodecData, preferHardware?: boolean): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Configure the decoder with VideoDecoderConfig directly
|
|
48
|
+
*/
|
|
49
|
+
configure(config: VideoDecoderConfig): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Configure with VideoDecoderConfig
|
|
52
|
+
*/
|
|
53
|
+
private configureWithConfig;
|
|
54
|
+
/**
|
|
55
|
+
* Configure with HeaderCodecData (from binary protocol)
|
|
56
|
+
*/
|
|
57
|
+
private configureWithCodecData;
|
|
58
|
+
/**
|
|
59
|
+
* Decode a binary packet
|
|
60
|
+
*/
|
|
61
|
+
decodeBinary(data: ParsedData): void;
|
|
62
|
+
/**
|
|
63
|
+
* Decode a sample (from file demuxer)
|
|
64
|
+
*/
|
|
65
|
+
decode(sample: SampleData): void;
|
|
66
|
+
/**
|
|
67
|
+
* Flush the decoder (async - waits for pending frames)
|
|
68
|
+
*/
|
|
69
|
+
flush(): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Synchronous flush - resets decoder immediately (used for overflow recovery)
|
|
72
|
+
*/
|
|
73
|
+
flushSync(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Reset the decoder
|
|
76
|
+
*/
|
|
77
|
+
reset(): void;
|
|
78
|
+
/**
|
|
79
|
+
* Dispose the decoder
|
|
80
|
+
*/
|
|
81
|
+
dispose(): void;
|
|
82
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Live Player - A framework-agnostic video streaming library
|
|
3
|
+
*
|
|
4
|
+
* Supports multiple stream sources through dependency injection:
|
|
5
|
+
* - MoQSession (from Elmo or standalone)
|
|
6
|
+
* - WebSocket connections
|
|
7
|
+
* - Custom stream sources
|
|
8
|
+
* - MP4 file playback (client-side demuxing with mp4box)
|
|
9
|
+
*/
|
|
10
|
+
export * from './types';
|
|
11
|
+
export * from './sources/stream-source';
|
|
12
|
+
export { BasePlayer } from './player/base-player';
|
|
13
|
+
export type { BasePlayerConfig } from './player/base-player';
|
|
14
|
+
export { LiveVideoPlayer, createPlayer } from './player/live-player';
|
|
15
|
+
export type { PlayerConfig, PlayerStats, PlayerState } from './player/live-player';
|
|
16
|
+
export { FileVideoPlayer, createFilePlayer } from './player/file-player';
|
|
17
|
+
export type { FilePlayerConfig, FilePlayerState, FilePlayerStats, FilePlayMode } from './player/file-player';
|
|
18
|
+
export { createStandaloneMoQSource, StandaloneMoQSource } from './sources/standalone-moq-source';
|
|
19
|
+
export { WebSocketSource, createWebSocketSource } from './sources/websocket-source';
|
|
20
|
+
export type { WebSocketSourceConfig, VideoMetadata } from './sources/websocket-source';
|
|
21
|
+
export { MP4FileSource } from './sources/mp4-file-source';
|
|
22
|
+
export type { MP4FileInfo, DecodableSample, MP4FileSourceEvents } from './sources/mp4-file-source';
|
|
23
|
+
export { WebCodecsDecoder } from './decoders/webcodecs-decoder';
|
|
24
|
+
export type { SampleData } from './decoders/webcodecs-decoder';
|
|
25
|
+
export { WasmDecoder } from './decoders/wasm-decoder';
|
|
26
|
+
export type { YUVFrame, WasmDecoderConfig } from './decoders/wasm-decoder';
|
|
27
|
+
export type { IVideoDecoder, DecoderState, DecodedFrame, VideoDecoderOptions } from './decoders/decoder-interface';
|
|
28
|
+
export { isVideoFrame, isYUVFrame } from './decoders/decoder-interface';
|
|
29
|
+
export { FileAudioPlayer } from './audio/file-audio-player';
|
|
30
|
+
export { LiveAudioPlayer } from './audio/live-audio-player';
|
|
31
|
+
export type { LiveAudioConfig } from './audio/live-audio-player';
|
|
32
|
+
export { FrameScheduler } from './scheduling/frame-scheduler';
|
|
33
|
+
export type { FrameTiming, LatencyStats, SchedulerStatus, SchedulerConfig } from './scheduling/frame-scheduler';
|
|
34
|
+
export { SesameBinaryProtocol } from './protocol/sesame-binary-protocol';
|
|
35
|
+
export type { ParsedData, HeaderData, HeaderCodecData } from './protocol/sesame-binary-protocol';
|