@stinkycomputing/web-live-player 0.1.3 → 0.1.5
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 +428 -402
- package/dist/assets/audio-encoder.worker-c5c829bb.js +114 -0
- package/dist/assets/video-encoder.worker-a613b1f0.js +91 -0
- package/dist/audio/live-audio-player.d.ts +5 -3
- 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 +95 -0
- package/dist/capture/media-encoder.d.ts +65 -0
- package/dist/capture/moq-sink.d.ts +65 -0
- package/dist/capture/video-encoder.worker.d.ts +7 -0
- package/dist/capture/websocket-sink.d.ts +35 -0
- package/dist/decoders/decoder-interface.d.ts +3 -3
- package/dist/decoders/wasm-decoder.d.ts +3 -7
- package/dist/decoders/webcodecs-decoder.d.ts +5 -5
- package/dist/index.d.ts +3 -4
- package/dist/player/live-player.d.ts +4 -1
- package/dist/protocol/codec-utils.d.ts +6 -4
- package/dist/sources/moq-source.d.ts +44 -0
- package/dist/sources/standalone-moq-source.d.ts +10 -10
- package/dist/sources/stream-source.d.ts +2 -2
- package/dist/sources/websocket-source.d.ts +4 -35
- package/dist/web-live-player.cjs +3 -3
- package/dist/web-live-player.mjs +20407 -4891
- package/package.json +58 -57
- package/dist/protocol/sesame-binary-protocol.d.ts +0 -98
|
@@ -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
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IMediaCodecData } from '@stinkycomputing/sesame-api-client';
|
|
2
2
|
|
|
3
3
|
export interface LiveAudioConfig {
|
|
4
4
|
bufferDelayMs?: number;
|
|
@@ -16,7 +16,7 @@ export declare class LiveAudioPlayer {
|
|
|
16
16
|
/**
|
|
17
17
|
* Initialize with codec info from stream header
|
|
18
18
|
*/
|
|
19
|
-
init(codecData?:
|
|
19
|
+
init(codecData?: IMediaCodecData): Promise<void>;
|
|
20
20
|
/**
|
|
21
21
|
* Build decoder config based on codec type
|
|
22
22
|
*/
|
|
@@ -32,7 +32,9 @@ export declare class LiveAudioPlayer {
|
|
|
32
32
|
/**
|
|
33
33
|
* Queue encoded audio data for decoding
|
|
34
34
|
*/
|
|
35
|
-
decode(data: Uint8Array, pts?: bigint
|
|
35
|
+
decode(data: Uint8Array, pts?: number | bigint | null | {
|
|
36
|
+
toNumber(): number;
|
|
37
|
+
}): void;
|
|
36
38
|
/**
|
|
37
39
|
* Set buffer delay for A/V sync
|
|
38
40
|
*/
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { sesame } from '@stinkycomputing/sesame-api-client';
|
|
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: sesame.v1.wire.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: sesame.v1.wire.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, sesame } from '@stinkycomputing/sesame-api-client';
|
|
2
|
+
|
|
3
|
+
export { CodecType };
|
|
4
|
+
/**
|
|
5
|
+
* Video codec configuration options
|
|
6
|
+
*/
|
|
7
|
+
export interface VideoEncoderOptions {
|
|
8
|
+
codec: sesame.v1.wire.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: sesame.v1.wire.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: sesame.v1.wire.CodecType): string;
|
|
113
|
+
/**
|
|
114
|
+
* Check if a codec is a video codec
|
|
115
|
+
*/
|
|
116
|
+
export declare function isVideoCodec(codec: sesame.v1.wire.CodecType): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Check if a codec is an audio codec
|
|
119
|
+
*/
|
|
120
|
+
export declare function isAudioCodec(codec: sesame.v1.wire.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,95 @@
|
|
|
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
|
+
/**
|
|
80
|
+
* Set an external MoQ session on the sink (if it's a MoQCaptureSink)
|
|
81
|
+
* Allows injecting an existing MoqSessionBroadcaster instance.
|
|
82
|
+
* @param session - MoqSessionBroadcaster instance to use for broadcasting
|
|
83
|
+
*/
|
|
84
|
+
setMoQSession(session: any): void;
|
|
85
|
+
private handleEncodedChunk;
|
|
86
|
+
private createPacket;
|
|
87
|
+
/**
|
|
88
|
+
* Dispose of all resources
|
|
89
|
+
*/
|
|
90
|
+
dispose(): void;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Factory function to create a media capture instance
|
|
94
|
+
*/
|
|
95
|
+
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
|
+
}
|