mediasfu-shared 1.2.2 → 1.2.3
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/CHANGELOG.md +5 -0
- package/dist/consumers/connectIps.d.ts.map +1 -1
- package/dist/consumers/index.cjs +1 -1
- package/dist/consumers/index.js +1 -1
- package/dist/{getParticipantMedia-BH3d_-Q1.cjs → getParticipantMedia-AwwtMCTz.cjs} +3 -3
- package/dist/{getParticipantMedia-BH3d_-Q1.cjs.map → getParticipantMedia-AwwtMCTz.cjs.map} +1 -1
- package/dist/{getParticipantMedia-CGqnSb93.js → getParticipantMedia-CyaOk2oD.js} +3 -3
- package/dist/{getParticipantMedia-CGqnSb93.js.map → getParticipantMedia-CyaOk2oD.js.map} +1 -1
- package/dist/index.cjs +36 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +37 -8
- package/dist/index.js.map +1 -1
- package/dist/index.native.cjs +3 -3
- package/dist/index.native.js +3 -3
- package/dist/{joinLocalRoom-7ibqv7zZ.js → joinLocalRoom-Bu3gz27d.js} +2 -2
- package/dist/{joinLocalRoom-7ibqv7zZ.js.map → joinLocalRoom-Bu3gz27d.js.map} +1 -1
- package/dist/{joinLocalRoom-BJBDvTYh.cjs → joinLocalRoom-Cii5D5IG.cjs} +2 -2
- package/dist/{joinLocalRoom-BJBDvTYh.cjs.map → joinLocalRoom-Cii5D5IG.cjs.map} +1 -1
- package/dist/methods/utils/headless/virtualBackground.d.ts +13 -1
- package/dist/methods/utils/headless/virtualBackground.d.ts.map +1 -1
- package/dist/methods/utils/virtualBackgroundCompositor.d.ts +4 -0
- package/dist/methods/utils/virtualBackgroundCompositor.d.ts.map +1 -1
- package/dist/sockets/SocketManager.d.ts +0 -27
- package/dist/sockets/SocketManager.d.ts.map +1 -1
- package/dist/types/index.cjs +2 -2
- package/dist/types/index.js +2 -2
- package/dist/{updateParticipantAudioDecibels-C_MygF6q.js → updateParticipantAudioDecibels-CnEXJBmc.js} +85 -13
- package/dist/updateParticipantAudioDecibels-CnEXJBmc.js.map +1 -0
- package/dist/{updateParticipantAudioDecibels-bZ6R8WHj.cjs → updateParticipantAudioDecibels-QZyahisU.cjs} +85 -13
- package/dist/updateParticipantAudioDecibels-QZyahisU.cjs.map +1 -0
- package/package.json +1 -1
- package/src/consumers/connectIps.ts +46 -7
- package/src/methods/utils/headless/virtualBackground.ts +34 -2
- package/src/methods/utils/virtualBackgroundCompositor.ts +18 -1
- package/src/sockets/SocketManager.ts +54 -7
- package/dist/updateParticipantAudioDecibels-C_MygF6q.js.map +0 -1
- package/dist/updateParticipantAudioDecibels-bZ6R8WHj.cjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mediasfu-shared",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "mediasfu-shared – framework-agnostic WebRTC runtime for MediaSFU. Room helpers, mediasoup signaling, socket management, media state, and TypeScript types for React, Vue, Angular, Svelte, and plain TS.",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -50,10 +50,22 @@ const normalizeConsumeEndpoint = (ip: string): string =>
|
|
|
50
50
|
const hasConsumeEndpoint = (
|
|
51
51
|
consumeSockets: ConsumeSocket[],
|
|
52
52
|
endpoint: string
|
|
53
|
-
): boolean =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
): boolean => {
|
|
54
|
+
for (let index = consumeSockets.length - 1; index >= 0; index -= 1) {
|
|
55
|
+
const socketObj = consumeSockets[index];
|
|
56
|
+
const ip = Object.keys(socketObj)[0];
|
|
57
|
+
if (!ip || normalizeConsumeEndpoint(ip) !== endpoint) continue;
|
|
58
|
+
|
|
59
|
+
const socket = socketObj[ip];
|
|
60
|
+
if (socket?.connected && socket.id) return true;
|
|
61
|
+
|
|
62
|
+
socket?.removeAllListeners?.();
|
|
63
|
+
socket?.disconnect?.();
|
|
64
|
+
consumeSockets.splice(index, 1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return false;
|
|
68
|
+
};
|
|
57
69
|
|
|
58
70
|
async function reserveConsumeEndpoint(
|
|
59
71
|
consumeSockets: ConsumeSocket[],
|
|
@@ -87,6 +99,9 @@ async function reserveConsumeEndpoint(
|
|
|
87
99
|
return (didConnect = false) => {
|
|
88
100
|
if (pendingForSockets?.get(endpoint) === reservation) {
|
|
89
101
|
pendingForSockets.delete(endpoint);
|
|
102
|
+
if (pendingForSockets.size === 0) {
|
|
103
|
+
pendingConsumeConnections.delete(reservationOwner);
|
|
104
|
+
}
|
|
90
105
|
}
|
|
91
106
|
resolvePending(didConnect === true);
|
|
92
107
|
};
|
|
@@ -170,9 +185,10 @@ export const connectIps = async ({
|
|
|
170
185
|
if (!releaseReservation) continue;
|
|
171
186
|
|
|
172
187
|
let connected = false;
|
|
188
|
+
let remote_sock: Awaited<ReturnType<typeof connectSocket>> | null = null;
|
|
173
189
|
try {
|
|
174
190
|
// Connect to the remote socket using socket.io-client
|
|
175
|
-
|
|
191
|
+
remote_sock = await connectSocket({ apiUserName, apiKey, apiToken, link: `https://${ip}.mediasfu.com` });
|
|
176
192
|
|
|
177
193
|
if (remote_sock.id) {
|
|
178
194
|
// Check if the IP is in the roomRecvIPs, if not, add it
|
|
@@ -187,7 +203,7 @@ export const connectIps = async ({
|
|
|
187
203
|
await newProducerMethod({
|
|
188
204
|
producerId,
|
|
189
205
|
islevel,
|
|
190
|
-
nsock: remote_sock
|
|
206
|
+
nsock: remote_sock!,
|
|
191
207
|
parameters,
|
|
192
208
|
isTranslation,
|
|
193
209
|
translationMeta,
|
|
@@ -211,7 +227,7 @@ export const connectIps = async ({
|
|
|
211
227
|
parameters,
|
|
212
228
|
});
|
|
213
229
|
if (!data.rtpCapabilities) {
|
|
214
|
-
return
|
|
230
|
+
throw new Error(`Consume room ${ip} did not return RTP capabilities.`);
|
|
215
231
|
}
|
|
216
232
|
}
|
|
217
233
|
|
|
@@ -219,11 +235,34 @@ export const connectIps = async ({
|
|
|
219
235
|
consume_sockets.push({ [ip]: remote_sock });
|
|
220
236
|
updateConsume_sockets(consume_sockets);
|
|
221
237
|
connected = true;
|
|
238
|
+
|
|
239
|
+
remote_sock.on("disconnect", () => {
|
|
240
|
+
const socketIndex = consume_sockets.findIndex(
|
|
241
|
+
(socketObj) => socketObj[ip] === remote_sock
|
|
242
|
+
);
|
|
243
|
+
if (socketIndex >= 0) {
|
|
244
|
+
consume_sockets.splice(socketIndex, 1);
|
|
245
|
+
updateConsume_sockets(consume_sockets);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const recvIndex = roomRecvIPs.indexOf(ip);
|
|
249
|
+
if (recvIndex >= 0) {
|
|
250
|
+
roomRecvIPs.splice(recvIndex, 1);
|
|
251
|
+
updateRoomRecvIPs(roomRecvIPs);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
remote_sock?.removeAllListeners?.();
|
|
255
|
+
remote_sock?.disconnect();
|
|
256
|
+
});
|
|
222
257
|
}
|
|
223
258
|
} catch (error) {
|
|
224
259
|
// Handle the error
|
|
225
260
|
console.log("connectIps error", error);
|
|
226
261
|
} finally {
|
|
262
|
+
if (!connected && remote_sock) {
|
|
263
|
+
remote_sock.removeAllListeners?.();
|
|
264
|
+
remote_sock.disconnect();
|
|
265
|
+
}
|
|
227
266
|
releaseReservation(connected);
|
|
228
267
|
}
|
|
229
268
|
}
|
|
@@ -3,7 +3,10 @@ import { HeadlessOptions } from './headlessTypes';
|
|
|
3
3
|
import { getCurrentParams } from './getCurrentParams';
|
|
4
4
|
import { getLocalVideoStream } from './getMediaStreams';
|
|
5
5
|
import { HeadlessActionResult } from './roomActions';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
compositeVirtualBackgroundFrame,
|
|
8
|
+
DEFAULT_BACKGROUND_BLUR_PIXELS,
|
|
9
|
+
} from '../virtualBackgroundCompositor';
|
|
7
10
|
|
|
8
11
|
const MEDIAPIPE_CDN = 'https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation';
|
|
9
12
|
|
|
@@ -57,6 +60,11 @@ export interface ApplyVirtualBackgroundOptions extends HeadlessOptions {
|
|
|
57
60
|
* A trailing slash is added if missing.
|
|
58
61
|
*/
|
|
59
62
|
assetPath?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Blur the area behind the segmented person by this many pixels. When this
|
|
65
|
+
* is greater than zero it takes precedence over `image`. Defaults to 0.
|
|
66
|
+
*/
|
|
67
|
+
blurPixels?: number;
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
export type ApplyVirtualBackgroundType = (
|
|
@@ -98,6 +106,7 @@ export async function applyVirtualBackground({
|
|
|
98
106
|
modelSelection = 1,
|
|
99
107
|
publish = true,
|
|
100
108
|
assetPath = MEDIAPIPE_CDN,
|
|
109
|
+
blurPixels = 0,
|
|
101
110
|
}: ApplyVirtualBackgroundOptions): Promise<HeadlessActionResult & { stream: MediaStream | null }> {
|
|
102
111
|
const live = getCurrentParams({ parameters });
|
|
103
112
|
if (live.audioOnlyRoom) {
|
|
@@ -124,7 +133,10 @@ export async function applyVirtualBackground({
|
|
|
124
133
|
const width = Number(settings.width) || 640;
|
|
125
134
|
const height = Number(settings.height) || 360;
|
|
126
135
|
|
|
127
|
-
const
|
|
136
|
+
const resolvedBlurPixels = Math.max(0, Number(blurPixels) || 0);
|
|
137
|
+
const backgroundImage = resolvedBlurPixels > 0
|
|
138
|
+
? null
|
|
139
|
+
: (typeof image === 'string' ? await loadImage(image) : image);
|
|
128
140
|
const producer = publish ? (live.videoProducer || live.localVideoProducer) : null;
|
|
129
141
|
|
|
130
142
|
// mediasoup Producers normally own their track (`stopTracks: true`). Their
|
|
@@ -169,6 +181,7 @@ export async function applyVirtualBackground({
|
|
|
169
181
|
backgroundImage: backgroundImage as CanvasImageSource | null,
|
|
170
182
|
width: canvas.width,
|
|
171
183
|
height: canvas.height,
|
|
184
|
+
blurFallbackPixels: resolvedBlurPixels,
|
|
172
185
|
});
|
|
173
186
|
} catch {
|
|
174
187
|
// A dropped frame must never kill the loop.
|
|
@@ -236,6 +249,25 @@ export async function applyVirtualBackground({
|
|
|
236
249
|
}
|
|
237
250
|
}
|
|
238
251
|
|
|
252
|
+
export interface ApplyBackgroundBlurOptions extends Omit<ApplyVirtualBackgroundOptions, 'image'> {
|
|
253
|
+
/** Blur strength in CSS pixels. Defaults to 16. */
|
|
254
|
+
blurPixels?: number;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Apply person-aware background blur through the standard processed-track lifecycle. */
|
|
258
|
+
export function applyBackgroundBlur({
|
|
259
|
+
blurPixels = DEFAULT_BACKGROUND_BLUR_PIXELS,
|
|
260
|
+
...options
|
|
261
|
+
}: ApplyBackgroundBlurOptions): ReturnType<ApplyVirtualBackgroundType> {
|
|
262
|
+
return applyVirtualBackground({
|
|
263
|
+
...options,
|
|
264
|
+
image: null,
|
|
265
|
+
blurPixels,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export type ApplyBackgroundBlurType = typeof applyBackgroundBlur;
|
|
270
|
+
|
|
239
271
|
export type ClearVirtualBackgroundType = (
|
|
240
272
|
options: HeadlessOptions
|
|
241
273
|
) => Promise<HeadlessActionResult>;
|
|
@@ -10,6 +10,14 @@ export interface CompositeVirtualBackgroundFrameOptions {
|
|
|
10
10
|
blurFallbackPixels?: number;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** Stable room-state value used by every web UI surface for background blur. */
|
|
14
|
+
export const VIRTUAL_BACKGROUND_BLUR = 'blur';
|
|
15
|
+
export const DEFAULT_BACKGROUND_BLUR_PIXELS = 16;
|
|
16
|
+
|
|
17
|
+
export function isVirtualBackgroundBlur(value: unknown): boolean {
|
|
18
|
+
return value === VIRTUAL_BACKGROUND_BLUR;
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
/**
|
|
14
22
|
* Paint the segmented person over a replacement background.
|
|
15
23
|
*
|
|
@@ -43,7 +51,16 @@ export function compositeVirtualBackgroundFrame({
|
|
|
43
51
|
ctx.fillRect(0, 0, width, height);
|
|
44
52
|
} else if (blurFallbackPixels > 0) {
|
|
45
53
|
ctx.filter = `blur(${blurFallbackPixels}px)`;
|
|
46
|
-
|
|
54
|
+
// Draw beyond the visible bounds so the blur kernel does not expose a
|
|
55
|
+
// transparent/black fringe around the processed frame.
|
|
56
|
+
const bleed = Math.max(2, blurFallbackPixels * 2);
|
|
57
|
+
ctx.drawImage(
|
|
58
|
+
sourceImage,
|
|
59
|
+
-bleed,
|
|
60
|
+
-bleed,
|
|
61
|
+
width + bleed * 2,
|
|
62
|
+
height + bleed * 2,
|
|
63
|
+
);
|
|
47
64
|
}
|
|
48
65
|
} finally {
|
|
49
66
|
ctx.filter = previousFilter || 'none';
|
|
@@ -80,6 +80,9 @@ export type ConnectLocalSocketType = (options: ConnectLocalSocketOptions) => Pro
|
|
|
80
80
|
* ```
|
|
81
81
|
*/
|
|
82
82
|
|
|
83
|
+
/** How long to wait for `connection-success` before giving up. */
|
|
84
|
+
const CONNECT_TIMEOUT_MS = 12000;
|
|
85
|
+
|
|
83
86
|
async function connectSocket(
|
|
84
87
|
{ apiUserName, apiKey, apiToken, link }: ConnectSocketOptions,
|
|
85
88
|
): Promise<Socket> {
|
|
@@ -132,8 +135,24 @@ async function connectSocket(
|
|
|
132
135
|
});
|
|
133
136
|
}
|
|
134
137
|
|
|
135
|
-
//
|
|
136
|
-
|
|
138
|
+
// Dispose failed handshakes immediately. Otherwise Socket.IO continues
|
|
139
|
+
// reconnecting a client that was never handed to the room lifecycle.
|
|
140
|
+
let settled = false;
|
|
141
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
142
|
+
const cleanupHandshake = () => {
|
|
143
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
144
|
+
socket.off('connection-success', handleConnectionSuccess);
|
|
145
|
+
socket.off('connect_error', handleConnectError);
|
|
146
|
+
socket.off('disconnect', handleDisconnectBeforeReady);
|
|
147
|
+
};
|
|
148
|
+
const settle = (action: () => void) => {
|
|
149
|
+
if (settled) return;
|
|
150
|
+
settled = true;
|
|
151
|
+
cleanupHandshake();
|
|
152
|
+
action();
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const handleConnectionSuccess = ({ socketId }: { socketId: string }) => {
|
|
137
156
|
//check if link contains mediasfu.com and contains more than one c
|
|
138
157
|
let conn = 'media';
|
|
139
158
|
try {
|
|
@@ -145,12 +164,40 @@ async function connectSocket(
|
|
|
145
164
|
}
|
|
146
165
|
|
|
147
166
|
console.log(`Connected to ${conn} socket with ID: ${socketId}`);
|
|
148
|
-
resolve(socket);
|
|
149
|
-
}
|
|
167
|
+
settle(() => resolve(socket));
|
|
168
|
+
};
|
|
150
169
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
170
|
+
const handleConnectError = (error: Error) => {
|
|
171
|
+
settle(() => {
|
|
172
|
+
socket.disconnect();
|
|
173
|
+
reject(new Error('Error connecting to media socket: ' + error.message));
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const handleDisconnectBeforeReady = (reason: string) => {
|
|
178
|
+
settle(() =>
|
|
179
|
+
reject(
|
|
180
|
+
new Error(
|
|
181
|
+
'Media socket closed before the room was ready (' + reason + ').'
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
socket.on('connection-success', handleConnectionSuccess);
|
|
188
|
+
socket.on('connect_error', handleConnectError);
|
|
189
|
+
socket.on('disconnect', handleDisconnectBeforeReady);
|
|
190
|
+
|
|
191
|
+
timeoutHandle = setTimeout(() => {
|
|
192
|
+
settle(() => {
|
|
193
|
+
try {
|
|
194
|
+
socket.disconnect();
|
|
195
|
+
} catch {
|
|
196
|
+
// Nothing useful to do here.
|
|
197
|
+
}
|
|
198
|
+
reject(new Error('Timed out connecting to media socket.'));
|
|
199
|
+
});
|
|
200
|
+
}, CONNECT_TIMEOUT_MS);
|
|
154
201
|
});
|
|
155
202
|
}
|
|
156
203
|
|