@stinkycomputing/web-live-player 0.1.3 → 0.1.4
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 +31 -5
- package/dist/assets/audio-encoder.worker-c5c829bb.js +114 -0
- package/dist/assets/video-encoder.worker-a613b1f0.js +91 -0
- package/dist/capture/audio-encoder.worker.d.ts +7 -0
- package/dist/capture/capture-sink.d.ts +105 -0
- package/dist/capture/capture-types.d.ts +120 -0
- package/dist/capture/index.d.ts +17 -0
- package/dist/capture/media-capture.d.ts +89 -0
- package/dist/capture/media-encoder.d.ts +65 -0
- package/dist/capture/moq-sink.d.ts +55 -0
- package/dist/capture/video-encoder.worker.d.ts +7 -0
- package/dist/capture/websocket-sink.d.ts +35 -0
- package/dist/index.d.ts +3 -2
- package/dist/player/live-player.d.ts +4 -1
- package/dist/sources/moq-source.d.ts +44 -0
- package/dist/sources/standalone-moq-source.d.ts +10 -10
- package/dist/web-live-player.cjs +3 -3
- package/dist/web-live-player.mjs +1978 -1302
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ npm install @stinkycomputing/web-live-player
|
|
|
23
23
|
### Using with MoQ (Standalone)
|
|
24
24
|
|
|
25
25
|
```typescript
|
|
26
|
-
import { createPlayer,
|
|
26
|
+
import { createPlayer, createMoQSource } from '@stinkycomputing/web-live-player';
|
|
27
27
|
|
|
28
28
|
// Create player
|
|
29
29
|
const player = createPlayer({
|
|
@@ -32,7 +32,7 @@ const player = createPlayer({
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
// Create MoQ source
|
|
35
|
-
const moqSource =
|
|
35
|
+
const moqSource = createMoQSource({
|
|
36
36
|
relayUrl: 'https://moq-relay.example.com',
|
|
37
37
|
namespace: 'live/stream',
|
|
38
38
|
subscriptions: [
|
|
@@ -372,16 +372,42 @@ const player = createPlayer({
|
|
|
372
372
|
});
|
|
373
373
|
```
|
|
374
374
|
|
|
375
|
-
##
|
|
375
|
+
## Demos
|
|
376
376
|
|
|
377
|
-
|
|
377
|
+
The library includes three demo applications showcasing different use cases:
|
|
378
|
+
|
|
379
|
+
### [Player Demo](demo/player/)
|
|
380
|
+
Live stream and file playback with multiple decoder options.
|
|
381
|
+
- Connect to MoQ or WebSocket streams
|
|
382
|
+
- Play MP4 files with seeking
|
|
383
|
+
- Real-time statistics and frame timing visualization
|
|
384
|
+
|
|
385
|
+
### [Capture Demo](demo/capture/)
|
|
386
|
+
Media capture and encoding with transport publishing.
|
|
387
|
+
- Camera/microphone capture with device selection
|
|
388
|
+
- Configurable video/audio codecs and bitrates
|
|
389
|
+
- Publish to MoQ relay or WebSocket server
|
|
390
|
+
|
|
391
|
+
### [Chat Demo](demo/chat/)
|
|
392
|
+
Multi-user video chat combining capture and playback.
|
|
393
|
+
- Full duplex video/audio communication
|
|
394
|
+
- Room-based user discovery
|
|
395
|
+
- Text chat over data tracks
|
|
396
|
+
|
|
397
|
+
### Running the Demos
|
|
378
398
|
|
|
379
399
|
```bash
|
|
380
400
|
npm install
|
|
381
401
|
npm run dev
|
|
382
402
|
```
|
|
383
403
|
|
|
384
|
-
Open http://localhost:3001 to see
|
|
404
|
+
Open http://localhost:3001 to see all demos.
|
|
405
|
+
|
|
406
|
+
| Demo | URL | Description |
|
|
407
|
+
|------|-----|-------------|
|
|
408
|
+
| Player | http://localhost:3001/player/ | Stream playback & file player |
|
|
409
|
+
| Capture | http://localhost:3001/capture/ | Camera capture & streaming |
|
|
410
|
+
| Chat | http://localhost:3001/chat/ | Multi-user video chat |
|
|
385
411
|
|
|
386
412
|
## Building
|
|
387
413
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
let t, o = !1, u = !1, g = 50, p = 0;
|
|
2
|
+
self.onmessage = async (a) => {
|
|
3
|
+
const { type: s, data: e } = a.data;
|
|
4
|
+
switch (s) {
|
|
5
|
+
case "init":
|
|
6
|
+
h(e.config), e.audioLevels && (u = !0, g = e.audioLevels.interval || 50);
|
|
7
|
+
break;
|
|
8
|
+
case "stream":
|
|
9
|
+
e.readable && await b(e.readable);
|
|
10
|
+
break;
|
|
11
|
+
case "encode":
|
|
12
|
+
y(e.frame);
|
|
13
|
+
break;
|
|
14
|
+
case "close":
|
|
15
|
+
v();
|
|
16
|
+
break;
|
|
17
|
+
default:
|
|
18
|
+
console.error("Unknown message type:", s);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function h(a) {
|
|
22
|
+
t = new AudioEncoder({
|
|
23
|
+
output: (s, e) => {
|
|
24
|
+
self.postMessage({
|
|
25
|
+
type: "chunk",
|
|
26
|
+
data: s,
|
|
27
|
+
metadata: e
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
error: (s) => {
|
|
31
|
+
self.postMessage({ type: "error", data: s.message });
|
|
32
|
+
}
|
|
33
|
+
}), t.configure(a), self.postMessage({ type: "ready" });
|
|
34
|
+
}
|
|
35
|
+
function y(a) {
|
|
36
|
+
if (!a) {
|
|
37
|
+
self.postMessage({ type: "error", data: "Received null or undefined frame" });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!t) {
|
|
41
|
+
self.postMessage({ type: "error", data: "Encoder not initialized" });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (t.state !== "configured") {
|
|
45
|
+
self.postMessage({ type: "error", data: `Encoder not in configured state: ${t.state}` });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
t.encode(a), a.close();
|
|
50
|
+
} catch (s) {
|
|
51
|
+
self.postMessage({ type: "error", data: s instanceof Error ? s.message : String(s) });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function v() {
|
|
55
|
+
if (t) {
|
|
56
|
+
try {
|
|
57
|
+
t.close();
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
t = void 0;
|
|
61
|
+
}
|
|
62
|
+
o = !1, self.postMessage({ type: "closed" });
|
|
63
|
+
}
|
|
64
|
+
function M(a) {
|
|
65
|
+
const s = performance.now();
|
|
66
|
+
if (!(s - p < g)) {
|
|
67
|
+
p = s;
|
|
68
|
+
try {
|
|
69
|
+
const e = a.numberOfChannels || 2, r = new Float32Array(a.allocationSize({ planeIndex: 0 }) / 4);
|
|
70
|
+
a.copyTo(r, { planeIndex: 0 });
|
|
71
|
+
const c = [], i = r.length / e;
|
|
72
|
+
for (let n = 0; n < e; n++) {
|
|
73
|
+
let d = 0;
|
|
74
|
+
for (let l = 0; l < i; l++) {
|
|
75
|
+
const f = r[l * e + n];
|
|
76
|
+
d += f * f;
|
|
77
|
+
}
|
|
78
|
+
const m = Math.sqrt(d / i);
|
|
79
|
+
c[n] = m;
|
|
80
|
+
}
|
|
81
|
+
self.postMessage({
|
|
82
|
+
type: "audio-levels",
|
|
83
|
+
data: {
|
|
84
|
+
levels: c,
|
|
85
|
+
timestamp: s
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function b(a) {
|
|
93
|
+
if (o) {
|
|
94
|
+
self.postMessage({ type: "error", data: "Already processing a stream" });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
o = !0;
|
|
98
|
+
const s = a.getReader();
|
|
99
|
+
try {
|
|
100
|
+
for (; ; ) {
|
|
101
|
+
const { done: e, value: r } = await s.read();
|
|
102
|
+
if (e)
|
|
103
|
+
break;
|
|
104
|
+
r && (u && M(r), y(r));
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
self.postMessage({
|
|
108
|
+
type: "error",
|
|
109
|
+
data: e instanceof Error ? e.message : String(e)
|
|
110
|
+
});
|
|
111
|
+
} finally {
|
|
112
|
+
s.releaseLock(), o = !1, self.postMessage({ type: "stream-complete" });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
let s, t = !1, n = !1, o = 0, i = 60;
|
|
2
|
+
self.onmessage = async (r) => {
|
|
3
|
+
const { type: e, data: a } = r.data;
|
|
4
|
+
switch (e) {
|
|
5
|
+
case "init":
|
|
6
|
+
i = a.config.gopSize || 60, l(a.config);
|
|
7
|
+
break;
|
|
8
|
+
case "stream":
|
|
9
|
+
a.readable && await p(a.readable);
|
|
10
|
+
break;
|
|
11
|
+
case "encode":
|
|
12
|
+
f(a.frame);
|
|
13
|
+
break;
|
|
14
|
+
case "close":
|
|
15
|
+
d();
|
|
16
|
+
break;
|
|
17
|
+
case "request-keyframe":
|
|
18
|
+
n = !0;
|
|
19
|
+
break;
|
|
20
|
+
default:
|
|
21
|
+
console.error("Unknown message type:", e);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
function l(r) {
|
|
25
|
+
s = new VideoEncoder({
|
|
26
|
+
output: (e, a) => {
|
|
27
|
+
self.postMessage({
|
|
28
|
+
type: "chunk",
|
|
29
|
+
data: e,
|
|
30
|
+
metadata: a
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
error: (e) => {
|
|
34
|
+
self.postMessage({ type: "error", data: e.message });
|
|
35
|
+
}
|
|
36
|
+
}), s.configure(r), self.postMessage({ type: "ready" });
|
|
37
|
+
}
|
|
38
|
+
function f(r) {
|
|
39
|
+
if (!r) {
|
|
40
|
+
self.postMessage({ type: "error", data: "Received null or undefined frame" });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!s) {
|
|
44
|
+
self.postMessage({ type: "error", data: "Encoder not initialized" });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (s.state !== "configured") {
|
|
48
|
+
self.postMessage({ type: "error", data: `Encoder not in configured state: ${s.state}` });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
o++;
|
|
53
|
+
let e = o % i === 0;
|
|
54
|
+
n && (n = !1, e = !0, o = 0), s.encode(r, { keyFrame: e }), r.close();
|
|
55
|
+
} catch (e) {
|
|
56
|
+
self.postMessage({ type: "error", data: e instanceof Error ? e.message : String(e) });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function d() {
|
|
60
|
+
if (s) {
|
|
61
|
+
try {
|
|
62
|
+
s.close();
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
s = void 0;
|
|
66
|
+
}
|
|
67
|
+
t = !1, o = 0, self.postMessage({ type: "closed" });
|
|
68
|
+
}
|
|
69
|
+
async function p(r) {
|
|
70
|
+
if (t) {
|
|
71
|
+
self.postMessage({ type: "error", data: "Already processing a stream" });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
t = !0;
|
|
75
|
+
const e = r.getReader();
|
|
76
|
+
try {
|
|
77
|
+
for (; ; ) {
|
|
78
|
+
const { done: a, value: c } = await e.read();
|
|
79
|
+
if (a)
|
|
80
|
+
break;
|
|
81
|
+
c && f(c);
|
|
82
|
+
}
|
|
83
|
+
} catch (a) {
|
|
84
|
+
self.postMessage({
|
|
85
|
+
type: "error",
|
|
86
|
+
data: a instanceof Error ? a.message : String(a)
|
|
87
|
+
});
|
|
88
|
+
} finally {
|
|
89
|
+
e.releaseLock(), t = !1, self.postMessage({ type: "stream-complete" });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { CodecType } from './capture-types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Serialized packet ready for transmission
|
|
5
|
+
*/
|
|
6
|
+
export interface SerializedPacket {
|
|
7
|
+
data: ArrayBuffer;
|
|
8
|
+
isKeyframe: boolean;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
type: 'video' | 'audio';
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Interface for capture sinks that handle outgoing media data
|
|
14
|
+
*/
|
|
15
|
+
export interface ICaptureSink {
|
|
16
|
+
/**
|
|
17
|
+
* Whether the sink is currently connected
|
|
18
|
+
*/
|
|
19
|
+
readonly connected: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Connect to the transport
|
|
22
|
+
*/
|
|
23
|
+
connect(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Disconnect from the transport
|
|
26
|
+
*/
|
|
27
|
+
disconnect(): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Reconnect to the transport
|
|
30
|
+
*/
|
|
31
|
+
reconnect(): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Send encoded media data
|
|
34
|
+
* @param packet The serialized packet to send
|
|
35
|
+
*/
|
|
36
|
+
send(packet: SerializedPacket): void;
|
|
37
|
+
/**
|
|
38
|
+
* Set callback for keyframe requests from receiver
|
|
39
|
+
*/
|
|
40
|
+
onKeyframeRequest(callback: () => void): void;
|
|
41
|
+
/**
|
|
42
|
+
* Dispose of all resources
|
|
43
|
+
*/
|
|
44
|
+
dispose(): void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Configuration for video stream in a capture sink
|
|
48
|
+
*/
|
|
49
|
+
export interface VideoStreamConfig {
|
|
50
|
+
/** Video codec type */
|
|
51
|
+
codec: CodecType;
|
|
52
|
+
/** Video width */
|
|
53
|
+
width: number;
|
|
54
|
+
/** Video height */
|
|
55
|
+
height: number;
|
|
56
|
+
/** Timebase numerator */
|
|
57
|
+
timebaseNum?: number;
|
|
58
|
+
/** Timebase denominator */
|
|
59
|
+
timebaseDen?: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Configuration for audio stream in a capture sink
|
|
63
|
+
*/
|
|
64
|
+
export interface AudioStreamConfig {
|
|
65
|
+
/** Audio codec type */
|
|
66
|
+
codec: CodecType;
|
|
67
|
+
/** Sample rate in Hz */
|
|
68
|
+
sampleRate: number;
|
|
69
|
+
/** Number of channels */
|
|
70
|
+
channels: number;
|
|
71
|
+
/** Timebase numerator */
|
|
72
|
+
timebaseNum?: number;
|
|
73
|
+
/** Timebase denominator */
|
|
74
|
+
timebaseDen?: number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Base configuration for capture sinks
|
|
78
|
+
*/
|
|
79
|
+
export interface CaptureSinkConfig {
|
|
80
|
+
/** Video stream configuration (optional if not sending video) */
|
|
81
|
+
video?: VideoStreamConfig;
|
|
82
|
+
/** Audio stream configuration (optional if not sending audio) */
|
|
83
|
+
audio?: AudioStreamConfig;
|
|
84
|
+
/** Topic/channel identifier for routing */
|
|
85
|
+
topic?: string;
|
|
86
|
+
/** Audio timestamp offset to align audio and video (in microseconds) */
|
|
87
|
+
audioTimestampOffset?: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Base class for capture sink implementations
|
|
91
|
+
*/
|
|
92
|
+
export declare abstract class BaseCaptureSink implements ICaptureSink {
|
|
93
|
+
protected config: CaptureSinkConfig;
|
|
94
|
+
protected keyframeCallback?: () => void;
|
|
95
|
+
protected _connected: boolean;
|
|
96
|
+
constructor(config: CaptureSinkConfig);
|
|
97
|
+
get connected(): boolean;
|
|
98
|
+
abstract connect(): Promise<void>;
|
|
99
|
+
abstract disconnect(): Promise<void>;
|
|
100
|
+
abstract send(packet: SerializedPacket): void;
|
|
101
|
+
reconnect(): Promise<void>;
|
|
102
|
+
onKeyframeRequest(callback: () => void): void;
|
|
103
|
+
protected requestKeyframe(): void;
|
|
104
|
+
dispose(): void;
|
|
105
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { CodecType, PacketType } from '../protocol/sesame-binary-protocol';
|
|
2
|
+
|
|
3
|
+
export { CodecType, PacketType };
|
|
4
|
+
/**
|
|
5
|
+
* Video codec configuration options
|
|
6
|
+
*/
|
|
7
|
+
export interface VideoEncoderOptions {
|
|
8
|
+
codec: CodecType;
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
bitrate?: number;
|
|
12
|
+
frameRate?: number;
|
|
13
|
+
keyFrameInterval?: number;
|
|
14
|
+
latencyMode?: 'quality' | 'realtime';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Audio codec configuration options
|
|
18
|
+
*/
|
|
19
|
+
export interface AudioEncoderOptions {
|
|
20
|
+
codec: CodecType;
|
|
21
|
+
sampleRate?: number;
|
|
22
|
+
channels?: number;
|
|
23
|
+
bitrate?: number;
|
|
24
|
+
latencyMode?: 'quality' | 'realtime';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Media capture configuration
|
|
28
|
+
*/
|
|
29
|
+
export interface CaptureConfig {
|
|
30
|
+
/** Enable video capture */
|
|
31
|
+
video?: boolean | MediaTrackConstraints;
|
|
32
|
+
/** Enable audio capture */
|
|
33
|
+
audio?: boolean | MediaTrackConstraints;
|
|
34
|
+
/** Video encoder options */
|
|
35
|
+
videoEncoder?: VideoEncoderOptions;
|
|
36
|
+
/** Audio encoder options */
|
|
37
|
+
audioEncoder?: AudioEncoderOptions;
|
|
38
|
+
/** Enable audio level monitoring */
|
|
39
|
+
audioLevelMonitoring?: boolean;
|
|
40
|
+
/** Audio level reporting interval in ms */
|
|
41
|
+
audioLevelInterval?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Default capture configuration
|
|
45
|
+
*/
|
|
46
|
+
export declare const DEFAULT_CAPTURE_CONFIG: Required<CaptureConfig>;
|
|
47
|
+
/**
|
|
48
|
+
* Encoded chunk event from encoder
|
|
49
|
+
*/
|
|
50
|
+
export interface EncodedChunkEvent {
|
|
51
|
+
type: 'audio' | 'video';
|
|
52
|
+
chunk: EncodedAudioChunk | EncodedVideoChunk;
|
|
53
|
+
keyframe: boolean;
|
|
54
|
+
timestamp: number;
|
|
55
|
+
metadata?: EncodedChunkMetadata;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Metadata associated with an encoded chunk
|
|
59
|
+
*/
|
|
60
|
+
export interface EncodedChunkMetadata {
|
|
61
|
+
width?: number;
|
|
62
|
+
height?: number;
|
|
63
|
+
channels?: number;
|
|
64
|
+
sampleRate?: number;
|
|
65
|
+
decoderConfig?: VideoDecoderConfig | AudioDecoderConfig;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Audio level data event
|
|
69
|
+
*/
|
|
70
|
+
export interface AudioLevelEvent {
|
|
71
|
+
timestamp: number;
|
|
72
|
+
levels: number[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Capture statistics
|
|
76
|
+
*/
|
|
77
|
+
export interface CaptureStats {
|
|
78
|
+
/** Number of video frames encoded */
|
|
79
|
+
videoFramesEncoded: number;
|
|
80
|
+
/** Number of audio frames encoded */
|
|
81
|
+
audioFramesEncoded: number;
|
|
82
|
+
/** Total bytes sent */
|
|
83
|
+
bytesSent: number;
|
|
84
|
+
/** Packets sent */
|
|
85
|
+
packetsSent: number;
|
|
86
|
+
/** Current video bitrate */
|
|
87
|
+
videoBitrate: number;
|
|
88
|
+
/** Current audio bitrate */
|
|
89
|
+
audioBitrate: number;
|
|
90
|
+
/** Capture start time */
|
|
91
|
+
startTime: number;
|
|
92
|
+
/** Duration in ms */
|
|
93
|
+
duration: number;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Capture state
|
|
97
|
+
*/
|
|
98
|
+
export type CaptureState = 'idle' | 'initializing' | 'capturing' | 'paused' | 'stopped' | 'error';
|
|
99
|
+
/**
|
|
100
|
+
* Events emitted by the capture system
|
|
101
|
+
*/
|
|
102
|
+
export interface CaptureEvents {
|
|
103
|
+
'state-change': (state: CaptureState) => void;
|
|
104
|
+
'encoded-chunk': (event: EncodedChunkEvent) => void;
|
|
105
|
+
'audio-levels': (event: AudioLevelEvent) => void;
|
|
106
|
+
'stats': (stats: CaptureStats) => void;
|
|
107
|
+
'error': (error: Error) => void;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Convert CodecType to WebCodecs codec string
|
|
111
|
+
*/
|
|
112
|
+
export declare function codecTypeToString(codec: CodecType): string;
|
|
113
|
+
/**
|
|
114
|
+
* Check if a codec is a video codec
|
|
115
|
+
*/
|
|
116
|
+
export declare function isVideoCodec(codec: CodecType): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Check if a codec is an audio codec
|
|
119
|
+
*/
|
|
120
|
+
export declare function isAudioCodec(codec: CodecType): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture Module
|
|
3
|
+
*
|
|
4
|
+
* Provides media capture capabilities for video and audio streaming.
|
|
5
|
+
* Supports encoding with WebCodecs and transport over MoQ or WebSocket.
|
|
6
|
+
*/
|
|
7
|
+
export * from './capture-types';
|
|
8
|
+
export { MediaStreamEncoder } from './media-encoder';
|
|
9
|
+
export type { EncoderEventHandler } from './media-encoder';
|
|
10
|
+
export { BaseCaptureSink } from './capture-sink';
|
|
11
|
+
export type { ICaptureSink, CaptureSinkConfig, SerializedPacket, VideoStreamConfig, AudioStreamConfig, } from './capture-sink';
|
|
12
|
+
export { WebSocketCaptureSink, createWebSocketSink } from './websocket-sink';
|
|
13
|
+
export type { WebSocketSinkConfig } from './websocket-sink';
|
|
14
|
+
export { MoQCaptureSink, createMoQSink } from './moq-sink';
|
|
15
|
+
export type { MoQSinkConfig, MoQTrackConfig } from './moq-sink';
|
|
16
|
+
export { MediaCapture, createMediaCapture } from './media-capture';
|
|
17
|
+
export type { MediaCaptureConfig, CaptureEventHandler } from './media-capture';
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { CaptureConfig, CaptureState, CaptureStats, AudioLevelEvent } from './capture-types';
|
|
2
|
+
import { ICaptureSink } from './capture-sink';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Full media capture configuration
|
|
6
|
+
*/
|
|
7
|
+
export interface MediaCaptureConfig extends CaptureConfig {
|
|
8
|
+
/** Capture sink for sending encoded data */
|
|
9
|
+
sink: ICaptureSink;
|
|
10
|
+
/** Topic/channel identifier for routing packets */
|
|
11
|
+
topic?: string;
|
|
12
|
+
/** Audio timestamp offset to align with video (microseconds) */
|
|
13
|
+
audioTimestampOffset?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Event handler types
|
|
17
|
+
*/
|
|
18
|
+
export type CaptureEventHandler<T> = (event: T) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Media Capture - captures and encodes media from browser devices
|
|
21
|
+
*/
|
|
22
|
+
export declare class MediaCapture {
|
|
23
|
+
private config;
|
|
24
|
+
private sink;
|
|
25
|
+
private encoder?;
|
|
26
|
+
private mediaStream?;
|
|
27
|
+
private state;
|
|
28
|
+
private disposed;
|
|
29
|
+
private stats;
|
|
30
|
+
private videoMetadata?;
|
|
31
|
+
private audioMetadata?;
|
|
32
|
+
private handlers;
|
|
33
|
+
constructor(config: MediaCaptureConfig);
|
|
34
|
+
/**
|
|
35
|
+
* Register event handler
|
|
36
|
+
*/
|
|
37
|
+
on(event: 'state-change', handler: CaptureEventHandler<CaptureState>): this;
|
|
38
|
+
on(event: 'audio-levels', handler: CaptureEventHandler<AudioLevelEvent>): this;
|
|
39
|
+
on(event: 'stats', handler: CaptureEventHandler<CaptureStats>): this;
|
|
40
|
+
on(event: 'error', handler: CaptureEventHandler<Error>): this;
|
|
41
|
+
/**
|
|
42
|
+
* Unregister event handler
|
|
43
|
+
*/
|
|
44
|
+
off(event: string, handler: CaptureEventHandler<any>): this;
|
|
45
|
+
private emit;
|
|
46
|
+
private setState;
|
|
47
|
+
/**
|
|
48
|
+
* Get current capture state
|
|
49
|
+
*/
|
|
50
|
+
getState(): CaptureState;
|
|
51
|
+
/**
|
|
52
|
+
* Get current capture statistics
|
|
53
|
+
*/
|
|
54
|
+
getStats(): CaptureStats;
|
|
55
|
+
/**
|
|
56
|
+
* Check if media devices are available
|
|
57
|
+
*/
|
|
58
|
+
static hasMediaDevices(): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Get list of available media devices
|
|
61
|
+
*/
|
|
62
|
+
static getDevices(): Promise<MediaDeviceInfo[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Start capturing and encoding media
|
|
65
|
+
*/
|
|
66
|
+
start(): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Stop capturing
|
|
69
|
+
*/
|
|
70
|
+
stop(): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Request immediate keyframe
|
|
73
|
+
*/
|
|
74
|
+
requestKeyframe(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Get the underlying MediaStream (for preview purposes)
|
|
77
|
+
*/
|
|
78
|
+
getMediaStream(): MediaStream | undefined;
|
|
79
|
+
private handleEncodedChunk;
|
|
80
|
+
private createPacket;
|
|
81
|
+
/**
|
|
82
|
+
* Dispose of all resources
|
|
83
|
+
*/
|
|
84
|
+
dispose(): void;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Factory function to create a media capture instance
|
|
88
|
+
*/
|
|
89
|
+
export declare function createMediaCapture(config: MediaCaptureConfig): MediaCapture;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { AudioEncoderOptions, VideoEncoderOptions, EncodedChunkEvent, AudioLevelEvent } from './capture-types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Event handler types
|
|
5
|
+
*/
|
|
6
|
+
export type EncoderEventHandler<T> = (event: T) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Media stream encoder that handles both audio and video encoding
|
|
9
|
+
*/
|
|
10
|
+
export declare class MediaStreamEncoder {
|
|
11
|
+
private audioWorker;
|
|
12
|
+
private videoWorker;
|
|
13
|
+
private audioProcessor;
|
|
14
|
+
private videoProcessor;
|
|
15
|
+
private disposed;
|
|
16
|
+
private handlers;
|
|
17
|
+
private audioReady;
|
|
18
|
+
private videoReady;
|
|
19
|
+
private hasAudio;
|
|
20
|
+
private hasVideo;
|
|
21
|
+
private audioMetadata?;
|
|
22
|
+
private videoMetadata?;
|
|
23
|
+
constructor(stream: MediaStream, videoOptions?: VideoEncoderOptions, audioOptions?: AudioEncoderOptions, audioLevelConfig?: {
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
interval?: number;
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Register event handler
|
|
29
|
+
*/
|
|
30
|
+
on(event: 'chunk', handler: EncoderEventHandler<EncodedChunkEvent>): this;
|
|
31
|
+
on(event: 'audio-levels', handler: EncoderEventHandler<AudioLevelEvent>): this;
|
|
32
|
+
on(event: 'error', handler: EncoderEventHandler<Error>): this;
|
|
33
|
+
on(event: 'ready', handler: EncoderEventHandler<void>): this;
|
|
34
|
+
/**
|
|
35
|
+
* Unregister event handler
|
|
36
|
+
*/
|
|
37
|
+
off(event: 'chunk', handler: EncoderEventHandler<EncodedChunkEvent>): this;
|
|
38
|
+
off(event: 'audio-levels', handler: EncoderEventHandler<AudioLevelEvent>): this;
|
|
39
|
+
off(event: 'error', handler: EncoderEventHandler<Error>): this;
|
|
40
|
+
off(event: 'ready', handler: EncoderEventHandler<void>): this;
|
|
41
|
+
private emit;
|
|
42
|
+
private checkReady;
|
|
43
|
+
private createAudioEncoder;
|
|
44
|
+
private createVideoEncoder;
|
|
45
|
+
private startProcessingAudio;
|
|
46
|
+
private startProcessingVideo;
|
|
47
|
+
/**
|
|
48
|
+
* Request an immediate keyframe from the video encoder
|
|
49
|
+
*/
|
|
50
|
+
requestKeyframe(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Dispose of all resources
|
|
53
|
+
*/
|
|
54
|
+
dispose(): void;
|
|
55
|
+
}
|
|
56
|
+
declare global {
|
|
57
|
+
interface MediaStreamTrackProcessor<T> {
|
|
58
|
+
readable: ReadableStream<T>;
|
|
59
|
+
}
|
|
60
|
+
const MediaStreamTrackProcessor: {
|
|
61
|
+
new <T>(options: {
|
|
62
|
+
track: MediaStreamTrack;
|
|
63
|
+
}): MediaStreamTrackProcessor<T>;
|
|
64
|
+
};
|
|
65
|
+
}
|