@tribe-nest/media-client 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 +68 -0
- package/build/core/index.d.ts +17 -0
- package/build/core/index.d.ts.map +1 -0
- package/build/core/index.js +41 -0
- package/build/core/index.js.map +1 -0
- package/build/core/reconnect.d.ts +95 -0
- package/build/core/reconnect.d.ts.map +1 -0
- package/build/core/reconnect.js +160 -0
- package/build/core/reconnect.js.map +1 -0
- package/build/core/signal.d.ts +184 -0
- package/build/core/signal.d.ts.map +1 -0
- package/build/core/signal.js +416 -0
- package/build/core/signal.js.map +1 -0
- package/build/core/socket.d.ts +57 -0
- package/build/core/socket.d.ts.map +1 -0
- package/build/core/socket.js +37 -0
- package/build/core/socket.js.map +1 -0
- package/build/core/state.d.ts +67 -0
- package/build/core/state.d.ts.map +1 -0
- package/build/core/state.js +193 -0
- package/build/core/state.js.map +1 -0
- package/build/index.d.ts +29 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +51 -0
- package/build/index.js.map +1 -0
- package/build/protocol.d.ts +10 -0
- package/build/protocol.d.ts.map +1 -0
- package/build/protocol.js +26 -0
- package/build/protocol.js.map +1 -0
- package/build/react/index.d.ts +147 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +319 -0
- package/build/react/index.js.map +1 -0
- package/build/room/browserDevice.d.ts +3 -0
- package/build/room/browserDevice.d.ts.map +1 -0
- package/build/room/browserDevice.js +94 -0
- package/build/room/browserDevice.js.map +1 -0
- package/build/room/device.d.ts +114 -0
- package/build/room/device.d.ts.map +1 -0
- package/build/room/device.js +3 -0
- package/build/room/device.js.map +1 -0
- package/build/room/room.d.ts +219 -0
- package/build/room/room.d.ts.map +1 -0
- package/build/room/room.js +438 -0
- package/build/room/room.js.map +1 -0
- package/package.json +69 -0
- package/src/_tests/clientBoundary.spec.ts +110 -0
- package/src/core/_tests/coreBoundary.spec.ts +70 -0
- package/src/core/_tests/fakeSignalServer.ts +188 -0
- package/src/core/_tests/reconnect.spec.ts +180 -0
- package/src/core/_tests/signal.spec.ts +347 -0
- package/src/core/_tests/state.spec.ts +226 -0
- package/src/core/index.ts +63 -0
- package/src/core/reconnect.ts +233 -0
- package/src/core/signal.ts +527 -0
- package/src/core/socket.ts +58 -0
- package/src/core/state.ts +251 -0
- package/src/index.ts +54 -0
- package/src/protocol.ts +9 -0
- package/src/react/_tests/hooks.spec.tsx +509 -0
- package/src/react/index.tsx +439 -0
- package/src/room/_tests/room.spec.ts +595 -0
- package/src/room/browserDevice.ts +114 -0
- package/src/room/device.ts +119 -0
- package/src/room/room.ts +600 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Device } from "mediasoup-client";
|
|
2
|
+
import type { Transport } from "mediasoup-client/types";
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
IceServer,
|
|
6
|
+
MediaConsumerHandle,
|
|
7
|
+
MediaDevice,
|
|
8
|
+
MediaProducerHandle,
|
|
9
|
+
MediaTransport,
|
|
10
|
+
RtpCapabilities,
|
|
11
|
+
RtpParameters,
|
|
12
|
+
TransportDescription,
|
|
13
|
+
TransportHandlers,
|
|
14
|
+
} from "./device";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The only file in this package that imports `mediasoup-client`.
|
|
18
|
+
*
|
|
19
|
+
* Everything above it is written against `MediaDevice`, so the room logic can
|
|
20
|
+
* be tested without a browser. Keeping that true is what `clientBoundary.spec`
|
|
21
|
+
* checks, and the reason it matters is that the interesting bugs are in the
|
|
22
|
+
* ORDER of operations rather than in the library calls: consume before the
|
|
23
|
+
* device is loaded, resume before the track is attached, rebuild on a reconnect
|
|
24
|
+
* without tearing the old transport down.
|
|
25
|
+
*
|
|
26
|
+
* This file is deliberately dull. If it grows logic, that logic is in the wrong
|
|
27
|
+
* place.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const adapt = (transport: Transport): MediaTransport => ({
|
|
31
|
+
id: transport.id,
|
|
32
|
+
|
|
33
|
+
async produce({ track, appData, encodings }) {
|
|
34
|
+
const producer = await transport.produce({
|
|
35
|
+
track,
|
|
36
|
+
...(encodings ? { encodings: encodings as never } : {}),
|
|
37
|
+
...(appData ? { appData } : {}),
|
|
38
|
+
});
|
|
39
|
+
return producer as unknown as MediaProducerHandle;
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
async consume({ id, producerId, kind, rtpParameters }) {
|
|
43
|
+
const consumer = await transport.consume({ id, producerId, kind, rtpParameters });
|
|
44
|
+
return consumer as unknown as MediaConsumerHandle;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
close() {
|
|
48
|
+
transport.close();
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export function createBrowserDevice(): MediaDevice {
|
|
53
|
+
const device = new Device();
|
|
54
|
+
|
|
55
|
+
const common = (description: TransportDescription, iceServers: IceServer[]) => ({
|
|
56
|
+
// The node calls it `transportId`; mediasoup-client calls it `id`. The
|
|
57
|
+
// rename happens here and nowhere else.
|
|
58
|
+
id: description.transportId,
|
|
59
|
+
iceParameters: description.iceParameters as never,
|
|
60
|
+
iceCandidates: description.iceCandidates as never,
|
|
61
|
+
dtlsParameters: description.dtlsParameters as never,
|
|
62
|
+
...(description.sctpParameters ? { sctpParameters: description.sctpParameters as never } : {}),
|
|
63
|
+
// Relay credentials, minted per join. Without these a participant behind a
|
|
64
|
+
// symmetric NAT never connects, and the symptom is "connecting..." for ever
|
|
65
|
+
// with nothing naming TURN.
|
|
66
|
+
...(iceServers.length ? { iceServers: iceServers as never } : {}),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const wire = (transport: Transport, handlers: Partial<TransportHandlers>) => {
|
|
70
|
+
// `connect` fires once, when the DTLS handshake needs its parameters
|
|
71
|
+
// delivered to the node. `errback` is not optional: without it a failed
|
|
72
|
+
// connect leaves the transport waiting rather than failing.
|
|
73
|
+
transport.on("connect", ({ dtlsParameters }, callback, errback) => {
|
|
74
|
+
handlers.onConnect?.(dtlsParameters).then(callback, errback);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (handlers.onProduce) {
|
|
78
|
+
transport.on("produce", ({ kind, rtpParameters, appData }, callback, errback) => {
|
|
79
|
+
handlers
|
|
80
|
+
.onProduce?.({ kind, rtpParameters: rtpParameters as RtpParameters, appData: appData as Record<string, unknown> })
|
|
81
|
+
.then((id) => callback({ id }), errback);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (handlers.onConnectionStateChange) {
|
|
86
|
+
transport.on("connectionstatechange", (state) => handlers.onConnectionStateChange?.(state));
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
async load(routerRtpCapabilities: RtpCapabilities) {
|
|
92
|
+
await device.load({ routerRtpCapabilities: routerRtpCapabilities as never });
|
|
93
|
+
},
|
|
94
|
+
get loaded() {
|
|
95
|
+
return device.loaded;
|
|
96
|
+
},
|
|
97
|
+
get rtpCapabilities() {
|
|
98
|
+
return device.rtpCapabilities as RtpCapabilities;
|
|
99
|
+
},
|
|
100
|
+
canProduce(kind) {
|
|
101
|
+
return device.canProduce(kind);
|
|
102
|
+
},
|
|
103
|
+
createSendTransport({ description, iceServers, handlers }) {
|
|
104
|
+
const transport = device.createSendTransport(common(description, iceServers));
|
|
105
|
+
wire(transport, handlers);
|
|
106
|
+
return adapt(transport);
|
|
107
|
+
},
|
|
108
|
+
createRecvTransport({ description, iceServers, handlers }) {
|
|
109
|
+
const transport = device.createRecvTransport(common(description, iceServers));
|
|
110
|
+
wire(transport, handlers);
|
|
111
|
+
return adapt(transport);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { RtpCapabilities, RtpParameters } from "mediasoup-client/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The `mediasoup-client` seam.
|
|
5
|
+
*
|
|
6
|
+
* ## Why the room talks to an interface instead of to `mediasoup-client`
|
|
7
|
+
*
|
|
8
|
+
* Everything interesting about the room layer - when to create a transport,
|
|
9
|
+
* what to do when a producer appears, whether a consume is permitted, how a
|
|
10
|
+
* reconnect rebuilds - is logic, and none of it needs a `RTCPeerConnection`.
|
|
11
|
+
* `mediasoup-client` needs a browser, so a room that imported it directly could
|
|
12
|
+
* only ever be tested in one.
|
|
13
|
+
*
|
|
14
|
+
* That is not a hypothetical cost. It is the difference between a reducer bug
|
|
15
|
+
* being caught by `npm test` and being caught by a person on a call.
|
|
16
|
+
*
|
|
17
|
+
* So the browser half is this interface, `room.ts` is written against it, and
|
|
18
|
+
* `browserDevice.ts` is the ~60 lines that actually import the library. The
|
|
19
|
+
* specs supply a fake; production supplies the real one.
|
|
20
|
+
*
|
|
21
|
+
* ## What deliberately is NOT abstracted
|
|
22
|
+
*
|
|
23
|
+
* The RTP parameter shapes. Those are `mediasoup-client`'s types re-exported,
|
|
24
|
+
* because inventing a parallel vocabulary for them would mean translating in
|
|
25
|
+
* both directions and the translation is where the bugs would live.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export type { RtpCapabilities, RtpParameters };
|
|
29
|
+
|
|
30
|
+
/** What the node said when it created a WebRTC transport for us. */
|
|
31
|
+
export type TransportDescription = {
|
|
32
|
+
transportId: string;
|
|
33
|
+
iceParameters: unknown;
|
|
34
|
+
iceCandidates: unknown;
|
|
35
|
+
dtlsParameters: unknown;
|
|
36
|
+
sctpParameters?: unknown;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type IceServer = {
|
|
40
|
+
urls: string | string[];
|
|
41
|
+
username?: string;
|
|
42
|
+
credential?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* One direction of media. `send` publishes, `recv` subscribes.
|
|
47
|
+
*
|
|
48
|
+
* `onConnect` and `onProduce` are how mediasoup-client asks the application to
|
|
49
|
+
* talk to the server: the DTLS parameters and the RTP parameters are produced
|
|
50
|
+
* by the library and have to reach the node over OUR signalling channel.
|
|
51
|
+
*/
|
|
52
|
+
export type TransportHandlers = {
|
|
53
|
+
onConnect(dtlsParameters: unknown): Promise<void>;
|
|
54
|
+
/** Must resolve to the node's producer id. */
|
|
55
|
+
onProduce(input: { kind: "audio" | "video"; rtpParameters: RtpParameters; appData?: Record<string, unknown> }): Promise<string>;
|
|
56
|
+
onConnectionStateChange?(state: string): void;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export interface MediaTransport {
|
|
60
|
+
readonly id: string;
|
|
61
|
+
produce(input: {
|
|
62
|
+
track: MediaStreamTrack;
|
|
63
|
+
appData?: Record<string, unknown>;
|
|
64
|
+
encodings?: unknown[];
|
|
65
|
+
}): Promise<MediaProducerHandle>;
|
|
66
|
+
consume(input: {
|
|
67
|
+
id: string;
|
|
68
|
+
producerId: string;
|
|
69
|
+
kind: "audio" | "video";
|
|
70
|
+
rtpParameters: RtpParameters;
|
|
71
|
+
}): Promise<MediaConsumerHandle>;
|
|
72
|
+
close(): void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type MediaProducerHandle = {
|
|
76
|
+
readonly id: string;
|
|
77
|
+
readonly kind: "audio" | "video";
|
|
78
|
+
pause(): void;
|
|
79
|
+
resume(): void;
|
|
80
|
+
close(): void;
|
|
81
|
+
readonly closed: boolean;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type MediaConsumerHandle = {
|
|
85
|
+
readonly id: string;
|
|
86
|
+
readonly producerId: string;
|
|
87
|
+
readonly kind: "audio" | "video";
|
|
88
|
+
readonly track: MediaStreamTrack;
|
|
89
|
+
pause(): void;
|
|
90
|
+
resume(): void;
|
|
91
|
+
close(): void;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The device: capability negotiation plus transport construction.
|
|
96
|
+
*
|
|
97
|
+
* `rtpCapabilities` is what the node needs in order to answer `consume`, and it
|
|
98
|
+
* is only meaningful AFTER `load`, which is why `load` is separate from
|
|
99
|
+
* construction rather than folded into it.
|
|
100
|
+
*/
|
|
101
|
+
export interface MediaDevice {
|
|
102
|
+
load(routerRtpCapabilities: RtpCapabilities): Promise<void>;
|
|
103
|
+
readonly loaded: boolean;
|
|
104
|
+
readonly rtpCapabilities: RtpCapabilities;
|
|
105
|
+
/** False when the browser cannot receive this kind at all. */
|
|
106
|
+
canProduce(kind: "audio" | "video"): boolean;
|
|
107
|
+
createSendTransport(input: {
|
|
108
|
+
description: TransportDescription;
|
|
109
|
+
iceServers: IceServer[];
|
|
110
|
+
handlers: TransportHandlers;
|
|
111
|
+
}): MediaTransport;
|
|
112
|
+
createRecvTransport(input: {
|
|
113
|
+
description: TransportDescription;
|
|
114
|
+
iceServers: IceServer[];
|
|
115
|
+
handlers: Omit<TransportHandlers, "onProduce">;
|
|
116
|
+
}): MediaTransport;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type MediaDeviceFactory = () => MediaDevice;
|