@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,319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MediaRoomProvider = MediaRoomProvider;
|
|
4
|
+
exports.useMediaRoom = useMediaRoom;
|
|
5
|
+
exports.useRoomState = useRoomState;
|
|
6
|
+
exports.useParticipants = useParticipants;
|
|
7
|
+
exports.useActiveSpeakers = useActiveSpeakers;
|
|
8
|
+
exports.useVisibleProducers = useVisibleProducers;
|
|
9
|
+
exports.useLocalPublications = useLocalPublications;
|
|
10
|
+
exports.useGrants = useGrants;
|
|
11
|
+
exports.useRecording = useRecording;
|
|
12
|
+
exports.useRemoteTrack = useRemoteTrack;
|
|
13
|
+
exports.useLocalMedia = useLocalMedia;
|
|
14
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
15
|
+
const react_1 = require("react");
|
|
16
|
+
const browserDevice_1 = require("../room/browserDevice");
|
|
17
|
+
const room_1 = require("../room/room");
|
|
18
|
+
const core_1 = require("../core");
|
|
19
|
+
const MediaRoomContext = (0, react_1.createContext)(null);
|
|
20
|
+
function MediaRoomProvider({ children, fallback, device, ...options }) {
|
|
21
|
+
const [room, setRoom] = (0, react_1.useState)(null);
|
|
22
|
+
const [connectionState, setConnectionState] = (0, react_1.useState)("idle");
|
|
23
|
+
const [error, setError] = (0, react_1.useState)(undefined);
|
|
24
|
+
const [recovering, setRecovering] = (0, react_1.useState)(false);
|
|
25
|
+
const [attempt, setAttempt] = (0, react_1.useState)(0);
|
|
26
|
+
// Held in a ref so changing a callback identity does not tear down a live
|
|
27
|
+
// call. A parent that re-renders every second must not reconnect the room
|
|
28
|
+
// every second, and inline arrow props make that the DEFAULT mistake.
|
|
29
|
+
const optionsRef = (0, react_1.useRef)(options);
|
|
30
|
+
optionsRef.current = options;
|
|
31
|
+
(0, react_1.useEffect)(() => {
|
|
32
|
+
let cancelled = false;
|
|
33
|
+
const instance = new room_1.MediaRoom({
|
|
34
|
+
...optionsRef.current,
|
|
35
|
+
getCredentials: () => optionsRef.current.getCredentials(),
|
|
36
|
+
device: device ?? browserDevice_1.createBrowserDevice,
|
|
37
|
+
});
|
|
38
|
+
const unsubscribe = instance.onChange(() => {
|
|
39
|
+
if (cancelled)
|
|
40
|
+
return;
|
|
41
|
+
setConnectionState(instance.connectionState);
|
|
42
|
+
setError(instance.error);
|
|
43
|
+
setRecovering(instance.isRecovering);
|
|
44
|
+
});
|
|
45
|
+
setRoom(instance);
|
|
46
|
+
instance.connect().catch(() => {
|
|
47
|
+
// The cause is on the room; `error` is already mirrored by the listener
|
|
48
|
+
// above. Swallowed here so a failed connect does not surface as an
|
|
49
|
+
// unhandled rejection in the host application's console.
|
|
50
|
+
if (!cancelled)
|
|
51
|
+
setConnectionState(instance.connectionState);
|
|
52
|
+
});
|
|
53
|
+
return () => {
|
|
54
|
+
cancelled = true;
|
|
55
|
+
unsubscribe();
|
|
56
|
+
void instance.close().catch(() => undefined);
|
|
57
|
+
};
|
|
58
|
+
// `attempt` is what `retry()` bumps. `device` is read once per room.
|
|
59
|
+
}, [attempt, device]);
|
|
60
|
+
const value = (0, react_1.useMemo)(() => ({ room, connectionState, error, recovering, retry: () => setAttempt((n) => n + 1) }), [room, connectionState, error, recovering]);
|
|
61
|
+
return ((0, jsx_runtime_1.jsx)(MediaRoomContext.Provider, { value: value, children: connectionState === "connecting" && fallback ? fallback : children }));
|
|
62
|
+
}
|
|
63
|
+
function useRoomContext() {
|
|
64
|
+
const value = (0, react_1.useContext)(MediaRoomContext);
|
|
65
|
+
if (!value)
|
|
66
|
+
throw new Error("useMediaRoom must be used inside a <MediaRoomProvider>");
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
function useMediaRoom() {
|
|
70
|
+
return useRoomContext();
|
|
71
|
+
}
|
|
72
|
+
/** Subscribe to the room's own change notifications. */
|
|
73
|
+
function useRoomSnapshot(select, fallback) {
|
|
74
|
+
// `select` and `fallback` must be stable across renders - see the note on the
|
|
75
|
+
// module-level selectors below.
|
|
76
|
+
const { room } = useRoomContext();
|
|
77
|
+
const subscribe = (0, react_1.useCallback)((onChange) => (room ? room.onChange(onChange) : () => undefined), [room]);
|
|
78
|
+
// The selector must return a STABLE reference for unchanged state, or
|
|
79
|
+
// `useSyncExternalStore` re-renders forever. `RoomState` is produced by a
|
|
80
|
+
// reducer that returns the same object when nothing changed, which is what
|
|
81
|
+
// makes reading it directly safe.
|
|
82
|
+
const getSnapshot = (0, react_1.useCallback)(() => (room ? select(room) : fallback), [room, select, fallback]);
|
|
83
|
+
return (0, react_1.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Module-level selectors, and that is not a style preference.
|
|
87
|
+
*
|
|
88
|
+
* `useRoomSnapshot` memoises `getSnapshot` on the selector's identity. An
|
|
89
|
+
* inline arrow is a new function every render, so the memo never holds, and
|
|
90
|
+
* `useSyncExternalStore` re-subscribes and re-reads on every pass.
|
|
91
|
+
*/
|
|
92
|
+
const selectState = (room) => room.state;
|
|
93
|
+
const selectTracks = (room) => room.tracks;
|
|
94
|
+
const selectPublications = (room) => room.localPublications;
|
|
95
|
+
function useRoomState() {
|
|
96
|
+
return useRoomSnapshot(selectState, EMPTY_STATE);
|
|
97
|
+
}
|
|
98
|
+
function useParticipants() {
|
|
99
|
+
return useRoomState().peers;
|
|
100
|
+
}
|
|
101
|
+
function useActiveSpeakers() {
|
|
102
|
+
return useRoomState().activeSpeakers;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The producers this participant may actually receive. What a UI renders.
|
|
106
|
+
*
|
|
107
|
+
* NOT `useRoomState().producers`, and the difference is the subscribe barrier.
|
|
108
|
+
* The reducer keeps every producer the node has ever announced, deliberately,
|
|
109
|
+
* so that a rule which narrows mid-call and later widens is reversible - there
|
|
110
|
+
* is no `producerAppeared` replay to bring back a producer it had deleted. The
|
|
111
|
+
* price of keeping them is that the raw list contains people this seat has been
|
|
112
|
+
* barred from, and on a sealed consultation room the mere PRESENCE of the other
|
|
113
|
+
* party is most of what the barrier was hiding. A UI mapping over the raw list
|
|
114
|
+
* therefore draws a tile carrying their identity and mounts an `<audio>` for
|
|
115
|
+
* them, and the node's own filtering does not save it: `revokeNewlyForbidden`
|
|
116
|
+
* only closes producers this session had a live consumer for, so one that was
|
|
117
|
+
* announced and never consumed stays in the list for the rest of the call.
|
|
118
|
+
*
|
|
119
|
+
* Memoised on the state OBJECT rather than used as a store selector: the filter
|
|
120
|
+
* builds a new array per call, and `useSyncExternalStore` compares snapshots
|
|
121
|
+
* with `Object.is`, so reading it through `useRoomSnapshot` would re-render for
|
|
122
|
+
* ever. The reducer returns the same state object when nothing changed, which
|
|
123
|
+
* is what makes this settle.
|
|
124
|
+
*/
|
|
125
|
+
function useVisibleProducers() {
|
|
126
|
+
const state = useRoomState();
|
|
127
|
+
return (0, react_1.useMemo)(() => (0, core_1.visibleProducers)(state), [state]);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* What THIS participant is publishing right now, with the live tracks.
|
|
131
|
+
*
|
|
132
|
+
* `useLocalMedia()` answers "is the camera on" and that is enough for a button,
|
|
133
|
+
* but a local preview needs the `MediaStreamTrack` itself, and reading
|
|
134
|
+
* `room.localPublications` from `useMediaRoom()` is not reactive: the preview
|
|
135
|
+
* would stay black until something else happened to re-render it.
|
|
136
|
+
*
|
|
137
|
+
* The snapshot is the room's cached array, rebuilt only when a publication
|
|
138
|
+
* changed, which is what keeps this out of the `getSnapshot` loop.
|
|
139
|
+
*/
|
|
140
|
+
function useLocalPublications() {
|
|
141
|
+
return useRoomSnapshot(selectPublications, EMPTY_PUBLICATIONS);
|
|
142
|
+
}
|
|
143
|
+
/** What the NODE says this token may do. For rendering, never for a decision:
|
|
144
|
+
* the node enforces independently and is the only side that counts. */
|
|
145
|
+
function useGrants() {
|
|
146
|
+
const { room } = useRoomContext();
|
|
147
|
+
return room?.grants;
|
|
148
|
+
}
|
|
149
|
+
/** True while any egress participant is present. A consent signal. */
|
|
150
|
+
function useRecording() {
|
|
151
|
+
return useRoomState().recording;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* One remote track, and a ref callback that attaches it.
|
|
155
|
+
*
|
|
156
|
+
* `attach` exists because `srcObject` cannot be set through JSX: it takes a
|
|
157
|
+
* `MediaStream` object, not a string, so every consumer of this hook would
|
|
158
|
+
* otherwise write the same `useEffect`.
|
|
159
|
+
*
|
|
160
|
+
* ## Why it is keyed on the TRACK
|
|
161
|
+
*
|
|
162
|
+
* A ref callback runs when the element mounts and again only when the callback
|
|
163
|
+
* IDENTITY changes. The track almost never exists at mount: a producer reaches
|
|
164
|
+
* `state.producers` the moment the node announces it, and the consume round
|
|
165
|
+
* trip that produces the track finishes some milliseconds later. So a callback
|
|
166
|
+
* memoised on `[room, producerId]` - both stable - ran exactly once, against no
|
|
167
|
+
* track, set `srcObject` to `null` and was never called again. An element that
|
|
168
|
+
* is only ever mounted once therefore stayed silent for the whole call, which
|
|
169
|
+
* is what happened to remote AUDIO: a `<video>` escapes it because a tile
|
|
170
|
+
* renders one only after the track arrives, and every remote `<video>` is
|
|
171
|
+
* `muted`, so a call had picture and no sound in either direction.
|
|
172
|
+
*
|
|
173
|
+
* Depending on `track` is what makes React re-run it: the identity changes when
|
|
174
|
+
* the track appears, React calls the old callback with `null` and the new one
|
|
175
|
+
* with the element. `MediaTrack` objects are created once per consumer and the
|
|
176
|
+
* room's snapshot array is rebuilt only when a track changes, so this settles
|
|
177
|
+
* rather than churning.
|
|
178
|
+
*/
|
|
179
|
+
function useRemoteTrack(producerId) {
|
|
180
|
+
const tracks = useRoomSnapshot(selectTracks, EMPTY_TRACKS);
|
|
181
|
+
const track = tracks.find((t) => t.producerId === producerId);
|
|
182
|
+
const attach = (0, react_1.useCallback)((element) => {
|
|
183
|
+
if (!element)
|
|
184
|
+
return;
|
|
185
|
+
element.srcObject = track ? new MediaStream([track.track]) : null;
|
|
186
|
+
}, [track]);
|
|
187
|
+
return { track, attach };
|
|
188
|
+
}
|
|
189
|
+
const NOTHING_PENDING = { camera: false, microphone: false, screen: false };
|
|
190
|
+
/**
|
|
191
|
+
* Capture and publish.
|
|
192
|
+
*
|
|
193
|
+
* `getUserMedia` lives here rather than in the room because permission is a
|
|
194
|
+
* BROWSER concern with a UI consequence - a denied prompt is something a person
|
|
195
|
+
* has to be told about - and the room has no way to say so.
|
|
196
|
+
*/
|
|
197
|
+
function useLocalMedia() {
|
|
198
|
+
const { room } = useRoomContext();
|
|
199
|
+
const [error, setError] = (0, react_1.useState)(undefined);
|
|
200
|
+
const [pending, setPending] = (0, react_1.useState)(NOTHING_PENDING);
|
|
201
|
+
const publications = useRoomSnapshot(selectPublications, EMPTY_PUBLICATIONS);
|
|
202
|
+
const has = (source) => publications.some((p) => p.source === source);
|
|
203
|
+
/**
|
|
204
|
+
* One operation per source at a time, held in a REF.
|
|
205
|
+
*
|
|
206
|
+
* Every control above this is a read-then-write: it reads
|
|
207
|
+
* `isMicrophoneEnabled` and calls publish or unpublish accordingly. That flag
|
|
208
|
+
* is derived from `publications`, which does not move until the publish has
|
|
209
|
+
* resolved - and a publish begins with a `getUserMedia` permission prompt
|
|
210
|
+
* that can sit on screen for seconds. So the ordinary human response to a
|
|
211
|
+
* button that appears to have done nothing (press it again) put two captures
|
|
212
|
+
* through two `room.publish` calls and produced TWO audio producers from one
|
|
213
|
+
* identity: everyone else mounts two `<audio>` elements and hears the person
|
|
214
|
+
* doubled, and unpublishing both later does not undo the seconds of garbled
|
|
215
|
+
* audio. The screen-share shape is worse, opening a second picker.
|
|
216
|
+
*
|
|
217
|
+
* A ref rather than the `pending` state because the check has to be true
|
|
218
|
+
* SYNCHRONOUSLY: two clicks in one tick are both handled before React has
|
|
219
|
+
* re-rendered anything, so a state flag would be `false` in both of them.
|
|
220
|
+
* `pending` exists to grey the button out, not to guard it.
|
|
221
|
+
*/
|
|
222
|
+
const inFlight = (0, react_1.useRef)(new Set());
|
|
223
|
+
const exclusively = (0, react_1.useCallback)(async (source, work) => {
|
|
224
|
+
if (inFlight.current.has(source))
|
|
225
|
+
return;
|
|
226
|
+
inFlight.current.add(source);
|
|
227
|
+
setPending((current) => ({ ...current, [source]: true }));
|
|
228
|
+
try {
|
|
229
|
+
await work();
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
inFlight.current.delete(source);
|
|
233
|
+
setPending((current) => ({ ...current, [source]: false }));
|
|
234
|
+
}
|
|
235
|
+
}, []);
|
|
236
|
+
/**
|
|
237
|
+
* Captured tracks are OWNED here until the room takes one.
|
|
238
|
+
*
|
|
239
|
+
* `getUserMedia` turns the camera light on. If the publish that follows
|
|
240
|
+
* fails - the node refusing the grant, the transport failing, the browser
|
|
241
|
+
* unable to produce the kind - the capture is still live, and nothing in the
|
|
242
|
+
* UI can stop it: no publication was recorded, so the button still reads
|
|
243
|
+
* "Start camera" and `unpublish("camera")` finds nothing to close. The light
|
|
244
|
+
* stays on until the provider unmounts, and every retry opens another one.
|
|
245
|
+
*
|
|
246
|
+
* So everything captured is stopped unless it was handed over, including the
|
|
247
|
+
* extra tracks a capture can carry (Chrome adds a system-audio track to a
|
|
248
|
+
* screen share when the person ticks "share audio", and only one track is
|
|
249
|
+
* published).
|
|
250
|
+
*/
|
|
251
|
+
const publish = (0, react_1.useCallback)(async (source) => exclusively(source, async () => {
|
|
252
|
+
if (!room)
|
|
253
|
+
return;
|
|
254
|
+
setError(undefined);
|
|
255
|
+
let captured = [];
|
|
256
|
+
let published;
|
|
257
|
+
try {
|
|
258
|
+
const stream = source === "screen"
|
|
259
|
+
? await navigator.mediaDevices.getDisplayMedia({ video: true })
|
|
260
|
+
: await navigator.mediaDevices.getUserMedia(source === "camera" ? { video: true } : { audio: true });
|
|
261
|
+
captured = stream.getTracks();
|
|
262
|
+
const track = captured[0];
|
|
263
|
+
if (!track)
|
|
264
|
+
throw new Error(`no ${source} track was captured`);
|
|
265
|
+
await room.publish(track, source);
|
|
266
|
+
published = track;
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
// Surfaced rather than thrown: a denied permission prompt is an
|
|
270
|
+
// ordinary outcome that the UI has to explain, not an exception.
|
|
271
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
for (const track of captured) {
|
|
275
|
+
if (track !== published)
|
|
276
|
+
track.stop();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}), [room, exclusively]);
|
|
280
|
+
// Under the same lock as `publish`, because the pair is what a toggle
|
|
281
|
+
// alternates between: a stop pressed while a start is still capturing would
|
|
282
|
+
// otherwise find no publication to close and leave the capture that lands a
|
|
283
|
+
// moment later running with the button reading "Mute".
|
|
284
|
+
const unpublish = (0, react_1.useCallback)(async (source) => exclusively(source, async () => {
|
|
285
|
+
if (!room)
|
|
286
|
+
return;
|
|
287
|
+
for (const publication of room.localPublications.filter((p) => p.source === source)) {
|
|
288
|
+
await room.unpublish(publication.producerId);
|
|
289
|
+
}
|
|
290
|
+
}), [room, exclusively]);
|
|
291
|
+
return {
|
|
292
|
+
publishCamera: (0, react_1.useCallback)(() => publish("camera"), [publish]),
|
|
293
|
+
publishMicrophone: (0, react_1.useCallback)(() => publish("microphone"), [publish]),
|
|
294
|
+
publishScreen: (0, react_1.useCallback)(() => publish("screen"), [publish]),
|
|
295
|
+
unpublish,
|
|
296
|
+
isCameraEnabled: has("camera"),
|
|
297
|
+
isMicrophoneEnabled: has("microphone"),
|
|
298
|
+
screenSharing: has("screen"),
|
|
299
|
+
pending,
|
|
300
|
+
error,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/** Stable empty values, so an unmounted or pre-connect room does not hand
|
|
304
|
+
* `useSyncExternalStore` a new array on every read and loop it. */
|
|
305
|
+
const EMPTY_TRACKS = [];
|
|
306
|
+
const EMPTY_PUBLICATIONS = [];
|
|
307
|
+
const EMPTY_STATE = {
|
|
308
|
+
phase: "idle",
|
|
309
|
+
identity: null,
|
|
310
|
+
room: null,
|
|
311
|
+
peers: [],
|
|
312
|
+
producers: [],
|
|
313
|
+
activeSpeakers: [],
|
|
314
|
+
recording: false,
|
|
315
|
+
subscribeRule: null,
|
|
316
|
+
draining: null,
|
|
317
|
+
closedReason: null,
|
|
318
|
+
};
|
|
319
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserDevice.d.ts","sourceRoot":"","sources":["../../src/room/browserDevice.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGV,WAAW,EAOZ,MAAM,UAAU,CAAC;AAsClB,wBAAgB,mBAAmB,IAAI,WAAW,CA8DjD"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createBrowserDevice = createBrowserDevice;
|
|
4
|
+
const mediasoup_client_1 = require("mediasoup-client");
|
|
5
|
+
/**
|
|
6
|
+
* The only file in this package that imports `mediasoup-client`.
|
|
7
|
+
*
|
|
8
|
+
* Everything above it is written against `MediaDevice`, so the room logic can
|
|
9
|
+
* be tested without a browser. Keeping that true is what `clientBoundary.spec`
|
|
10
|
+
* checks, and the reason it matters is that the interesting bugs are in the
|
|
11
|
+
* ORDER of operations rather than in the library calls: consume before the
|
|
12
|
+
* device is loaded, resume before the track is attached, rebuild on a reconnect
|
|
13
|
+
* without tearing the old transport down.
|
|
14
|
+
*
|
|
15
|
+
* This file is deliberately dull. If it grows logic, that logic is in the wrong
|
|
16
|
+
* place.
|
|
17
|
+
*/
|
|
18
|
+
const adapt = (transport) => ({
|
|
19
|
+
id: transport.id,
|
|
20
|
+
async produce({ track, appData, encodings }) {
|
|
21
|
+
const producer = await transport.produce({
|
|
22
|
+
track,
|
|
23
|
+
...(encodings ? { encodings: encodings } : {}),
|
|
24
|
+
...(appData ? { appData } : {}),
|
|
25
|
+
});
|
|
26
|
+
return producer;
|
|
27
|
+
},
|
|
28
|
+
async consume({ id, producerId, kind, rtpParameters }) {
|
|
29
|
+
const consumer = await transport.consume({ id, producerId, kind, rtpParameters });
|
|
30
|
+
return consumer;
|
|
31
|
+
},
|
|
32
|
+
close() {
|
|
33
|
+
transport.close();
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
function createBrowserDevice() {
|
|
37
|
+
const device = new mediasoup_client_1.Device();
|
|
38
|
+
const common = (description, iceServers) => ({
|
|
39
|
+
// The node calls it `transportId`; mediasoup-client calls it `id`. The
|
|
40
|
+
// rename happens here and nowhere else.
|
|
41
|
+
id: description.transportId,
|
|
42
|
+
iceParameters: description.iceParameters,
|
|
43
|
+
iceCandidates: description.iceCandidates,
|
|
44
|
+
dtlsParameters: description.dtlsParameters,
|
|
45
|
+
...(description.sctpParameters ? { sctpParameters: description.sctpParameters } : {}),
|
|
46
|
+
// Relay credentials, minted per join. Without these a participant behind a
|
|
47
|
+
// symmetric NAT never connects, and the symptom is "connecting..." for ever
|
|
48
|
+
// with nothing naming TURN.
|
|
49
|
+
...(iceServers.length ? { iceServers: iceServers } : {}),
|
|
50
|
+
});
|
|
51
|
+
const wire = (transport, handlers) => {
|
|
52
|
+
// `connect` fires once, when the DTLS handshake needs its parameters
|
|
53
|
+
// delivered to the node. `errback` is not optional: without it a failed
|
|
54
|
+
// connect leaves the transport waiting rather than failing.
|
|
55
|
+
transport.on("connect", ({ dtlsParameters }, callback, errback) => {
|
|
56
|
+
handlers.onConnect?.(dtlsParameters).then(callback, errback);
|
|
57
|
+
});
|
|
58
|
+
if (handlers.onProduce) {
|
|
59
|
+
transport.on("produce", ({ kind, rtpParameters, appData }, callback, errback) => {
|
|
60
|
+
handlers
|
|
61
|
+
.onProduce?.({ kind, rtpParameters: rtpParameters, appData: appData })
|
|
62
|
+
.then((id) => callback({ id }), errback);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (handlers.onConnectionStateChange) {
|
|
66
|
+
transport.on("connectionstatechange", (state) => handlers.onConnectionStateChange?.(state));
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
async load(routerRtpCapabilities) {
|
|
71
|
+
await device.load({ routerRtpCapabilities: routerRtpCapabilities });
|
|
72
|
+
},
|
|
73
|
+
get loaded() {
|
|
74
|
+
return device.loaded;
|
|
75
|
+
},
|
|
76
|
+
get rtpCapabilities() {
|
|
77
|
+
return device.rtpCapabilities;
|
|
78
|
+
},
|
|
79
|
+
canProduce(kind) {
|
|
80
|
+
return device.canProduce(kind);
|
|
81
|
+
},
|
|
82
|
+
createSendTransport({ description, iceServers, handlers }) {
|
|
83
|
+
const transport = device.createSendTransport(common(description, iceServers));
|
|
84
|
+
wire(transport, handlers);
|
|
85
|
+
return adapt(transport);
|
|
86
|
+
},
|
|
87
|
+
createRecvTransport({ description, iceServers, handlers }) {
|
|
88
|
+
const transport = device.createRecvTransport(common(description, iceServers));
|
|
89
|
+
wire(transport, handlers);
|
|
90
|
+
return adapt(transport);
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=browserDevice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserDevice.js","sourceRoot":"","sources":["../../src/room/browserDevice.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { RtpCapabilities, RtpParameters } from "mediasoup-client/types";
|
|
2
|
+
/**
|
|
3
|
+
* The `mediasoup-client` seam.
|
|
4
|
+
*
|
|
5
|
+
* ## Why the room talks to an interface instead of to `mediasoup-client`
|
|
6
|
+
*
|
|
7
|
+
* Everything interesting about the room layer - when to create a transport,
|
|
8
|
+
* what to do when a producer appears, whether a consume is permitted, how a
|
|
9
|
+
* reconnect rebuilds - is logic, and none of it needs a `RTCPeerConnection`.
|
|
10
|
+
* `mediasoup-client` needs a browser, so a room that imported it directly could
|
|
11
|
+
* only ever be tested in one.
|
|
12
|
+
*
|
|
13
|
+
* That is not a hypothetical cost. It is the difference between a reducer bug
|
|
14
|
+
* being caught by `npm test` and being caught by a person on a call.
|
|
15
|
+
*
|
|
16
|
+
* So the browser half is this interface, `room.ts` is written against it, and
|
|
17
|
+
* `browserDevice.ts` is the ~60 lines that actually import the library. The
|
|
18
|
+
* specs supply a fake; production supplies the real one.
|
|
19
|
+
*
|
|
20
|
+
* ## What deliberately is NOT abstracted
|
|
21
|
+
*
|
|
22
|
+
* The RTP parameter shapes. Those are `mediasoup-client`'s types re-exported,
|
|
23
|
+
* because inventing a parallel vocabulary for them would mean translating in
|
|
24
|
+
* both directions and the translation is where the bugs would live.
|
|
25
|
+
*/
|
|
26
|
+
export type { RtpCapabilities, RtpParameters };
|
|
27
|
+
/** What the node said when it created a WebRTC transport for us. */
|
|
28
|
+
export type TransportDescription = {
|
|
29
|
+
transportId: string;
|
|
30
|
+
iceParameters: unknown;
|
|
31
|
+
iceCandidates: unknown;
|
|
32
|
+
dtlsParameters: unknown;
|
|
33
|
+
sctpParameters?: unknown;
|
|
34
|
+
};
|
|
35
|
+
export type IceServer = {
|
|
36
|
+
urls: string | string[];
|
|
37
|
+
username?: string;
|
|
38
|
+
credential?: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* One direction of media. `send` publishes, `recv` subscribes.
|
|
42
|
+
*
|
|
43
|
+
* `onConnect` and `onProduce` are how mediasoup-client asks the application to
|
|
44
|
+
* talk to the server: the DTLS parameters and the RTP parameters are produced
|
|
45
|
+
* by the library and have to reach the node over OUR signalling channel.
|
|
46
|
+
*/
|
|
47
|
+
export type TransportHandlers = {
|
|
48
|
+
onConnect(dtlsParameters: unknown): Promise<void>;
|
|
49
|
+
/** Must resolve to the node's producer id. */
|
|
50
|
+
onProduce(input: {
|
|
51
|
+
kind: "audio" | "video";
|
|
52
|
+
rtpParameters: RtpParameters;
|
|
53
|
+
appData?: Record<string, unknown>;
|
|
54
|
+
}): Promise<string>;
|
|
55
|
+
onConnectionStateChange?(state: string): void;
|
|
56
|
+
};
|
|
57
|
+
export interface MediaTransport {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
produce(input: {
|
|
60
|
+
track: MediaStreamTrack;
|
|
61
|
+
appData?: Record<string, unknown>;
|
|
62
|
+
encodings?: unknown[];
|
|
63
|
+
}): Promise<MediaProducerHandle>;
|
|
64
|
+
consume(input: {
|
|
65
|
+
id: string;
|
|
66
|
+
producerId: string;
|
|
67
|
+
kind: "audio" | "video";
|
|
68
|
+
rtpParameters: RtpParameters;
|
|
69
|
+
}): Promise<MediaConsumerHandle>;
|
|
70
|
+
close(): void;
|
|
71
|
+
}
|
|
72
|
+
export type MediaProducerHandle = {
|
|
73
|
+
readonly id: string;
|
|
74
|
+
readonly kind: "audio" | "video";
|
|
75
|
+
pause(): void;
|
|
76
|
+
resume(): void;
|
|
77
|
+
close(): void;
|
|
78
|
+
readonly closed: boolean;
|
|
79
|
+
};
|
|
80
|
+
export type MediaConsumerHandle = {
|
|
81
|
+
readonly id: string;
|
|
82
|
+
readonly producerId: string;
|
|
83
|
+
readonly kind: "audio" | "video";
|
|
84
|
+
readonly track: MediaStreamTrack;
|
|
85
|
+
pause(): void;
|
|
86
|
+
resume(): void;
|
|
87
|
+
close(): void;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* The device: capability negotiation plus transport construction.
|
|
91
|
+
*
|
|
92
|
+
* `rtpCapabilities` is what the node needs in order to answer `consume`, and it
|
|
93
|
+
* is only meaningful AFTER `load`, which is why `load` is separate from
|
|
94
|
+
* construction rather than folded into it.
|
|
95
|
+
*/
|
|
96
|
+
export interface MediaDevice {
|
|
97
|
+
load(routerRtpCapabilities: RtpCapabilities): Promise<void>;
|
|
98
|
+
readonly loaded: boolean;
|
|
99
|
+
readonly rtpCapabilities: RtpCapabilities;
|
|
100
|
+
/** False when the browser cannot receive this kind at all. */
|
|
101
|
+
canProduce(kind: "audio" | "video"): boolean;
|
|
102
|
+
createSendTransport(input: {
|
|
103
|
+
description: TransportDescription;
|
|
104
|
+
iceServers: IceServer[];
|
|
105
|
+
handlers: TransportHandlers;
|
|
106
|
+
}): MediaTransport;
|
|
107
|
+
createRecvTransport(input: {
|
|
108
|
+
description: TransportDescription;
|
|
109
|
+
iceServers: IceServer[];
|
|
110
|
+
handlers: Omit<TransportHandlers, "onProduce">;
|
|
111
|
+
}): MediaTransport;
|
|
112
|
+
}
|
|
113
|
+
export type MediaDeviceFactory = () => MediaDevice;
|
|
114
|
+
//# sourceMappingURL=device.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../../src/room/device.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.js","sourceRoot":"","sources":["../../src/room/device.ts"],"names":[],"mappings":""}
|