mediasfu-shared 1.1.0 → 1.2.1

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.
Files changed (138) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +17 -0
  3. package/dist/ProducerClient/producerClientEmits/createDeviceClient.d.ts +1 -1
  4. package/dist/ProducerClient/producerClientEmits/createDeviceClient.d.ts.map +1 -1
  5. package/dist/ProducerClient/producerClientEmits/updateRoomParametersClient.d.ts +1 -1
  6. package/dist/ProducerClient/producerClientEmits/updateRoomParametersClient.d.ts.map +1 -1
  7. package/dist/consumers/connectIps.d.ts +1 -1
  8. package/dist/consumers/connectIps.d.ts.map +1 -1
  9. package/dist/consumers/connectRecvTransport.d.ts +1 -1
  10. package/dist/consumers/connectRecvTransport.d.ts.map +1 -1
  11. package/dist/consumers/connectSendTransport.d.ts +1 -1
  12. package/dist/consumers/connectSendTransport.d.ts.map +1 -1
  13. package/dist/consumers/connectSendTransportAudio.d.ts +1 -1
  14. package/dist/consumers/connectSendTransportAudio.d.ts.map +1 -1
  15. package/dist/consumers/connectSendTransportScreen.d.ts +1 -1
  16. package/dist/consumers/connectSendTransportScreen.d.ts.map +1 -1
  17. package/dist/consumers/connectSendTransportVideo.d.ts +1 -1
  18. package/dist/consumers/connectSendTransportVideo.d.ts.map +1 -1
  19. package/dist/consumers/createSendTransport.d.ts +1 -1
  20. package/dist/consumers/createSendTransport.d.ts.map +1 -1
  21. package/dist/consumers/disconnectSendTransportAudio.d.ts +1 -1
  22. package/dist/consumers/disconnectSendTransportAudio.d.ts.map +1 -1
  23. package/dist/consumers/disconnectSendTransportScreen.d.ts +1 -1
  24. package/dist/consumers/disconnectSendTransportScreen.d.ts.map +1 -1
  25. package/dist/consumers/disconnectSendTransportVideo.d.ts +1 -1
  26. package/dist/consumers/disconnectSendTransportVideo.d.ts.map +1 -1
  27. package/dist/consumers/index.cjs +80 -0
  28. package/dist/consumers/index.cjs.map +1 -0
  29. package/dist/consumers/index.d.ts +62 -0
  30. package/dist/consumers/index.d.ts.map +1 -0
  31. package/dist/consumers/index.js +80 -0
  32. package/dist/consumers/index.js.map +1 -0
  33. package/dist/consumers/resumeSendTransportAudio.d.ts +1 -1
  34. package/dist/consumers/resumeSendTransportAudio.d.ts.map +1 -1
  35. package/dist/consumers/signalNewConsumerTransport.d.ts +1 -1
  36. package/dist/consumers/signalNewConsumerTransport.d.ts.map +1 -1
  37. package/dist/consumers/socketReceiveMethods/joinConsumeRoom.d.ts +1 -1
  38. package/dist/consumers/socketReceiveMethods/joinConsumeRoom.d.ts.map +1 -1
  39. package/dist/consumers/socketReceiveMethods/newPipeProducer.d.ts +1 -1
  40. package/dist/consumers/socketReceiveMethods/newPipeProducer.d.ts.map +1 -1
  41. package/dist/consumers/streamSuccessAudio.d.ts +1 -1
  42. package/dist/consumers/streamSuccessAudio.d.ts.map +1 -1
  43. package/dist/consumers/streamSuccessAudioSwitch.d.ts +1 -1
  44. package/dist/consumers/streamSuccessAudioSwitch.d.ts.map +1 -1
  45. package/dist/consumers/streamSuccessVideo.d.ts +1 -1
  46. package/dist/consumers/streamSuccessVideo.d.ts.map +1 -1
  47. package/dist/getParticipantMedia-Bu_aZlxe.js +3555 -0
  48. package/dist/getParticipantMedia-Bu_aZlxe.js.map +1 -0
  49. package/dist/getParticipantMedia-DC_Nca8S.cjs +3554 -0
  50. package/dist/getParticipantMedia-DC_Nca8S.cjs.map +1 -0
  51. package/dist/index.cjs +299 -252
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +1994 -1947
  56. package/dist/index.js.map +1 -1
  57. package/dist/index.native.cjs +230 -224
  58. package/dist/index.native.cjs.map +1 -1
  59. package/dist/index.native.js +349 -343
  60. package/dist/index.native.js.map +1 -1
  61. package/dist/joinLocalRoom-7ibqv7zZ.js +657 -0
  62. package/dist/joinLocalRoom-7ibqv7zZ.js.map +1 -0
  63. package/dist/joinLocalRoom-BJBDvTYh.cjs +673 -0
  64. package/dist/joinLocalRoom-BJBDvTYh.cjs.map +1 -0
  65. package/dist/joinRoomOnMediaSFU-CC8RVJgr.cjs +1222 -0
  66. package/dist/joinRoomOnMediaSFU-CC8RVJgr.cjs.map +1 -0
  67. package/dist/joinRoomOnMediaSFU-y6vJ1OBo.js +1223 -0
  68. package/dist/joinRoomOnMediaSFU-y6vJ1OBo.js.map +1 -0
  69. package/dist/methods/index.cjs +3975 -0
  70. package/dist/methods/index.cjs.map +1 -0
  71. package/dist/methods/index.js +3976 -0
  72. package/dist/methods/index.js.map +1 -0
  73. package/dist/methods/socketReceive/getDomains.d.ts +1 -1
  74. package/dist/methods/socketReceive/getDomains.d.ts.map +1 -1
  75. package/dist/methods/utils/headless/virtualBackground.d.ts +2 -2
  76. package/dist/methods/utils/headless/virtualBackground.d.ts.map +1 -1
  77. package/dist/methods/utils/virtualBackgroundCompositor.d.ts +21 -0
  78. package/dist/methods/utils/virtualBackgroundCompositor.d.ts.map +1 -0
  79. package/dist/methods/whiteboard/captureCanvasStream.d.ts +1 -1
  80. package/dist/methods/whiteboard/captureCanvasStream.d.ts.map +1 -1
  81. package/dist/producers/producerEmits/joinConRoom.d.ts +1 -1
  82. package/dist/producers/producerEmits/joinConRoom.d.ts.map +1 -1
  83. package/dist/producers/producerEmits/joinRoom.d.ts +1 -1
  84. package/dist/producers/producerEmits/joinRoom.d.ts.map +1 -1
  85. package/dist/translationConsumerSwitch-C20MfNNq.cjs +509 -0
  86. package/dist/translationConsumerSwitch-C20MfNNq.cjs.map +1 -0
  87. package/dist/translationConsumerSwitch-zKuS5wgH.js +510 -0
  88. package/dist/translationConsumerSwitch-zKuS5wgH.js.map +1 -0
  89. package/dist/types/index.cjs +85 -0
  90. package/dist/types/index.cjs.map +1 -0
  91. package/dist/types/index.d.ts +2 -0
  92. package/dist/types/index.d.ts.map +1 -0
  93. package/dist/types/index.js +85 -0
  94. package/dist/types/index.js.map +1 -0
  95. package/dist/types/shared-base-types.d.ts +1 -1
  96. package/dist/types/shared-base-types.d.ts.map +1 -1
  97. package/dist/updateParticipantAudioDecibels-C_MygF6q.js +5432 -0
  98. package/dist/updateParticipantAudioDecibels-C_MygF6q.js.map +1 -0
  99. package/dist/updateParticipantAudioDecibels-bZ6R8WHj.cjs +5431 -0
  100. package/dist/updateParticipantAudioDecibels-bZ6R8WHj.cjs.map +1 -0
  101. package/dist/validateAlphanumeric-DKn5BsUP.cjs +15 -0
  102. package/dist/validateAlphanumeric-DKn5BsUP.cjs.map +1 -0
  103. package/dist/validateAlphanumeric-Dk1JB80W.js +16 -0
  104. package/dist/validateAlphanumeric-Dk1JB80W.js.map +1 -0
  105. package/package.json +2 -1
  106. package/src/ProducerClient/producerClientEmits/createDeviceClient.ts +1 -2
  107. package/src/ProducerClient/producerClientEmits/updateRoomParametersClient.ts +1 -1
  108. package/src/consumers/connectIps.ts +69 -9
  109. package/src/consumers/connectRecvTransport.ts +1 -1
  110. package/src/consumers/connectSendTransport.ts +1 -1
  111. package/src/consumers/connectSendTransportAudio.ts +1 -1
  112. package/src/consumers/connectSendTransportScreen.ts +1 -1
  113. package/src/consumers/connectSendTransportVideo.ts +1 -1
  114. package/src/consumers/createSendTransport.ts +1 -1
  115. package/src/consumers/disconnectSendTransportAudio.ts +1 -1
  116. package/src/consumers/disconnectSendTransportScreen.ts +1 -1
  117. package/src/consumers/disconnectSendTransportVideo.ts +1 -1
  118. package/src/consumers/index.ts +61 -0
  119. package/src/consumers/resumeSendTransportAudio.ts +1 -1
  120. package/src/consumers/signalNewConsumerTransport.ts +1 -2
  121. package/src/consumers/socketReceiveMethods/joinConsumeRoom.ts +1 -1
  122. package/src/consumers/socketReceiveMethods/newPipeProducer.ts +1 -1
  123. package/src/consumers/streamSuccessAudio.ts +1 -2
  124. package/src/consumers/streamSuccessAudioSwitch.ts +1 -2
  125. package/src/consumers/streamSuccessVideo.ts +1 -1
  126. package/src/index.ts +2 -0
  127. package/src/methods/socketReceive/getDomains.ts +1 -1
  128. package/src/methods/utils/headless/virtualBackground.ts +45 -30
  129. package/src/methods/utils/virtualBackgroundCompositor.ts +53 -0
  130. package/src/methods/whiteboard/captureCanvasStream.ts +2 -2
  131. package/src/producers/producerEmits/joinConRoom.ts +1 -1
  132. package/src/producers/producerEmits/joinRoom.ts +1 -1
  133. package/src/types/index.ts +1 -0
  134. package/src/types/shared-base-types.ts +2 -3
  135. package/dist/getParticipantMedia-D9ChmEqq.js +0 -15265
  136. package/dist/getParticipantMedia-D9ChmEqq.js.map +0 -1
  137. package/dist/getParticipantMedia-DWvIhPPY.cjs +0 -15281
  138. package/dist/getParticipantMedia-DWvIhPPY.cjs.map +0 -1
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ const validateAlphanumeric = async ({ str }) => {
3
+ let code, i, len;
4
+ for (i = 0, len = str.length; i < len; i++) {
5
+ code = str.charCodeAt(i);
6
+ if (!(code > 47 && code < 58) && // numeric (0-9)
7
+ !(code > 64 && code < 91) && // upper alpha (A-Z)
8
+ !(code > 96 && code < 123)) {
9
+ return false;
10
+ }
11
+ }
12
+ return true;
13
+ };
14
+ exports.validateAlphanumeric = validateAlphanumeric;
15
+ //# sourceMappingURL=validateAlphanumeric-DKn5BsUP.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateAlphanumeric-DKn5BsUP.cjs","sources":["../src/methods/utils/validateAlphanumeric.ts"],"sourcesContent":["\nexport interface ValidateAlphanumericOptions {\n str: string;\n}\n\n// Export the type definition for the function\nexport type ValidateAlphanumericType = (options: ValidateAlphanumericOptions) => Promise<boolean>;\n\n/**\n * Validates if the given string contains only alphanumeric characters.\n *\n * @param {ValidateAlphanumericOptions} options - The options containing the string to validate.\n * @param {string} options.str - The string to be validated.\n * @returns {Promise<boolean>} - A promise that resolves to `true` if the string is alphanumeric, otherwise `false`.\n * \n * @example\n * ```typescript \n * const isValid = await validateAlphanumeric({ str: \"abc123\" });\n * console.log(isValid);\n * // Output: true\n * ```\n */\n\n\nconst validateAlphanumeric = async ({ str }: ValidateAlphanumericOptions): Promise<boolean> => {\n let code: number, i: number, len: number;\n\n for (i = 0, len = str.length; i < len; i++) {\n code = str.charCodeAt(i);\n if (\n !(code > 47 && code < 58) && // numeric (0-9)\n !(code > 64 && code < 91) && // upper alpha (A-Z)\n !(code > 96 && code < 123) // lower alpha (a-z)\n ) {\n return false;\n }\n }\n return true;\n};\n\nexport { validateAlphanumeric };\n"],"names":[],"mappings":";AAwBA,MAAM,uBAAuB,OAAO,EAAE,UAAyD;AAC7F,MAAI,MAAc,GAAW;AAE7B,OAAK,IAAI,GAAG,MAAM,IAAI,QAAQ,IAAI,KAAK,KAAK;AAC1C,WAAO,IAAI,WAAW,CAAC;AACvB,QACE,EAAE,OAAO,MAAM,OAAO;AAAA,IACtB,EAAE,OAAO,MAAM,OAAO;AAAA,IACtB,EAAE,OAAO,MAAM,OAAO,MACtB;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;;"}
@@ -0,0 +1,16 @@
1
+ const validateAlphanumeric = async ({ str }) => {
2
+ let code, i, len;
3
+ for (i = 0, len = str.length; i < len; i++) {
4
+ code = str.charCodeAt(i);
5
+ if (!(code > 47 && code < 58) && // numeric (0-9)
6
+ !(code > 64 && code < 91) && // upper alpha (A-Z)
7
+ !(code > 96 && code < 123)) {
8
+ return false;
9
+ }
10
+ }
11
+ return true;
12
+ };
13
+ export {
14
+ validateAlphanumeric as v
15
+ };
16
+ //# sourceMappingURL=validateAlphanumeric-Dk1JB80W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateAlphanumeric-Dk1JB80W.js","sources":["../src/methods/utils/validateAlphanumeric.ts"],"sourcesContent":["\nexport interface ValidateAlphanumericOptions {\n str: string;\n}\n\n// Export the type definition for the function\nexport type ValidateAlphanumericType = (options: ValidateAlphanumericOptions) => Promise<boolean>;\n\n/**\n * Validates if the given string contains only alphanumeric characters.\n *\n * @param {ValidateAlphanumericOptions} options - The options containing the string to validate.\n * @param {string} options.str - The string to be validated.\n * @returns {Promise<boolean>} - A promise that resolves to `true` if the string is alphanumeric, otherwise `false`.\n * \n * @example\n * ```typescript \n * const isValid = await validateAlphanumeric({ str: \"abc123\" });\n * console.log(isValid);\n * // Output: true\n * ```\n */\n\n\nconst validateAlphanumeric = async ({ str }: ValidateAlphanumericOptions): Promise<boolean> => {\n let code: number, i: number, len: number;\n\n for (i = 0, len = str.length; i < len; i++) {\n code = str.charCodeAt(i);\n if (\n !(code > 47 && code < 58) && // numeric (0-9)\n !(code > 64 && code < 91) && // upper alpha (A-Z)\n !(code > 96 && code < 123) // lower alpha (a-z)\n ) {\n return false;\n }\n }\n return true;\n};\n\nexport { validateAlphanumeric };\n"],"names":[],"mappings":"AAwBA,MAAM,uBAAuB,OAAO,EAAE,UAAyD;AAC7F,MAAI,MAAc,GAAW;AAE7B,OAAK,IAAI,GAAG,MAAM,IAAI,QAAQ,IAAI,KAAK,KAAK;AAC1C,WAAO,IAAI,WAAW,CAAC;AACvB,QACE,EAAE,OAAO,MAAM,OAAO;AAAA,IACtB,EAAE,OAAO,MAAM,OAAO;AAAA,IACtB,EAAE,OAAO,MAAM,OAAO,MACtB;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mediasfu-shared",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
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",
@@ -37,6 +37,7 @@
37
37
  "src",
38
38
  "README.md",
39
39
  "HEADLESS_GUIDE.md",
40
+ "CHANGELOG.md",
40
41
  "LICENSE"
41
42
  ],
42
43
  "scripts": {
@@ -1,5 +1,5 @@
1
1
  import * as mediasoupClient from "mediasoup-client";
2
- import type { RtpCapabilities, Device } from 'mediasoup-client/lib/types';
2
+ import type { RtpCapabilities, Device } from 'mediasoup-client/types';
3
3
 
4
4
  export interface CreateDeviceClientOptions {
5
5
  rtpCapabilities: RtpCapabilities | null;
@@ -65,4 +65,3 @@ export const createDeviceClient = async ({
65
65
  };
66
66
 
67
67
 
68
-
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable eqeqeq */
2
- import { RtpCapabilities } from 'mediasoup-client/lib/types';
2
+ import { RtpCapabilities } from 'mediasoup-client/types';
3
3
  import {
4
4
  QnHDCons,
5
5
  sdCons,
@@ -3,7 +3,7 @@ import { connectSocket } from "../../src/sockets/SocketManager";
3
3
  import { newPipeProducer } from "./socketReceiveMethods/newPipeProducer";
4
4
  import { producerClosed } from "./socketReceiveMethods/producerClosed";
5
5
  import { joinConsumeRoom } from "./socketReceiveMethods/joinConsumeRoom";
6
- import type { Device } from 'mediasoup-client/lib/types';
6
+ import type { Device } from 'mediasoup-client/types';
7
7
  import {
8
8
  ReorderStreamsParameters, ReorderStreamsType, NewPipeProducerParameters, NewPipeProducerType, ProducerClosedType,
9
9
  ProducerClosedParameters, JoinConsumeRoomType, JoinConsumeRoomParameters, ConsumeSocket
@@ -36,6 +36,63 @@ export interface ConnectIpsOptions {
36
36
  // Export the type definition for the function
37
37
  export type ConnectIpsType = (options: ConnectIpsOptions) => Promise<[Record<string, any>[], string[]]>;
38
38
 
39
+ // Keep the reservation local to one room engine's socket collection. Separate
40
+ // rooms may legitimately consume from the same endpoint and must not share a
41
+ // socket, while overlapping updates for one room must serialize that endpoint.
42
+ const pendingConsumeConnections = new WeakMap<
43
+ object,
44
+ Map<string, Promise<boolean>>
45
+ >();
46
+
47
+ const normalizeConsumeEndpoint = (ip: string): string =>
48
+ ip.trim().toLowerCase().replace(/\.$/, "");
49
+
50
+ const hasConsumeEndpoint = (
51
+ consumeSockets: ConsumeSocket[],
52
+ endpoint: string
53
+ ): boolean => consumeSockets.some((socketObj) => {
54
+ const ip = Object.keys(socketObj)[0];
55
+ return Boolean(ip) && normalizeConsumeEndpoint(ip) === endpoint;
56
+ });
57
+
58
+ async function reserveConsumeEndpoint(
59
+ consumeSockets: ConsumeSocket[],
60
+ ip: string,
61
+ reservationOwner: object = consumeSockets
62
+ ): Promise<((connected?: boolean) => void) | null> {
63
+ const endpoint = normalizeConsumeEndpoint(ip);
64
+ if (!endpoint || endpoint === "none") return null;
65
+
66
+ let pendingForSockets = pendingConsumeConnections.get(reservationOwner);
67
+ if (!pendingForSockets) {
68
+ pendingForSockets = new Map<string, Promise<boolean>>();
69
+ pendingConsumeConnections.set(reservationOwner, pendingForSockets);
70
+ }
71
+
72
+ while (true) {
73
+ if (hasConsumeEndpoint(consumeSockets, endpoint)) return null;
74
+
75
+ const pending = pendingForSockets.get(endpoint);
76
+ if (pending) {
77
+ if (await pending) return null;
78
+ continue;
79
+ }
80
+
81
+ let resolvePending!: (connected: boolean) => void;
82
+ const reservation = new Promise<boolean>((resolve) => {
83
+ resolvePending = resolve;
84
+ });
85
+ pendingForSockets.set(endpoint, reservation);
86
+
87
+ return (didConnect = false) => {
88
+ if (pendingForSockets?.get(endpoint) === reservation) {
89
+ pendingForSockets.delete(endpoint);
90
+ }
91
+ resolvePending(didConnect === true);
92
+ };
93
+ }
94
+ }
95
+
39
96
  /**
40
97
  * Connects to remote IPs and manages socket connections.
41
98
  *
@@ -105,15 +162,15 @@ export const connectIps = async ({
105
162
  }
106
163
 
107
164
  for (const ip of remIP) {
108
- try {
109
- // Check if the IP is already connected
110
- const matching = consume_sockets.find((socketObj) => Object.keys(socketObj)[0] === ip);
111
-
112
- if (matching || !ip) {
113
- // Skip if the IP is already connected or invalid
114
- continue;
115
- }
165
+ const releaseReservation = await reserveConsumeEndpoint(
166
+ consume_sockets,
167
+ ip,
168
+ parameters?.socket || consume_sockets
169
+ );
170
+ if (!releaseReservation) continue;
116
171
 
172
+ let connected = false;
173
+ try {
117
174
  // Connect to the remote socket using socket.io-client
118
175
  const remote_sock = await connectSocket({ apiUserName, apiKey, apiToken, link: `https://${ip}.mediasfu.com` });
119
176
 
@@ -161,10 +218,13 @@ export const connectIps = async ({
161
218
  // Add the remote socket to the consume_sockets array
162
219
  consume_sockets.push({ [ip]: remote_sock });
163
220
  updateConsume_sockets(consume_sockets);
221
+ connected = true;
164
222
  }
165
223
  } catch (error) {
166
224
  // Handle the error
167
225
  console.log("connectIps error", error);
226
+ } finally {
227
+ releaseReservation(connected);
168
228
  }
169
229
  }
170
230
 
@@ -1,6 +1,6 @@
1
1
  import { Socket } from "socket.io-client";
2
2
  import { ConsumerResumeType, ConsumerResumeParameters, Transport as TransportType, Participant } from "../types/types";
3
- import type { Consumer, Device, Transport } from "mediasoup-client/lib/types";
3
+ import type { Consumer, Device, Transport } from "mediasoup-client/types";
4
4
 
5
5
  interface SpeakerTranslationState {
6
6
  speakerId: string;
@@ -1,4 +1,4 @@
1
- import type { ProducerOptions } from 'mediasoup-client/lib/types';
1
+ import type { ProducerOptions } from 'mediasoup-client/types';
2
2
  import { ConnectSendTransportAudioType, ConnectSendTransportVideoType, ConnectSendTransportScreenType, ConnectSendTransportAudioParameters, ConnectSendTransportVideoParameters, ConnectSendTransportScreenParameters } from '../types/types';
3
3
  export interface ConnectSendTransportParameters extends ConnectSendTransportAudioParameters, ConnectSendTransportVideoParameters, ConnectSendTransportScreenParameters {
4
4
  audioParams: ProducerOptions
@@ -1,4 +1,4 @@
1
- import type { Transport, Producer, ProducerOptions } from 'mediasoup-client/lib/types';
1
+ import type { Transport, Producer, ProducerOptions } from 'mediasoup-client/types';
2
2
 
3
3
  export interface ConnectSendTransportAudioParameters {
4
4
  audioProducer: Producer | null;
@@ -1,4 +1,4 @@
1
- import type { Transport, Producer, Device, ProducerOptions } from 'mediasoup-client/lib/types';
1
+ import type { Transport, Producer, Device, ProducerOptions } from 'mediasoup-client/types';
2
2
 
3
3
  export interface ConnectSendTransportScreenParameters {
4
4
  screenProducer: Producer | null;
@@ -1,4 +1,4 @@
1
- import type { Device, Producer, ProducerOptions, Transport } from 'mediasoup-client/lib/types';
1
+ import type { Device, Producer, ProducerOptions, Transport } from 'mediasoup-client/types';
2
2
 
3
3
  export interface ConnectSendTransportVideoParameters {
4
4
  videoProducer: Producer | null;
@@ -1,4 +1,4 @@
1
- import type { Device, Transport, DtlsParameters } from 'mediasoup-client/lib/types';
1
+ import type { Device, Transport, DtlsParameters } from 'mediasoup-client/types';
2
2
  import { Socket } from "socket.io-client";
3
3
  import {
4
4
  ConnectSendTransportParameters,
@@ -1,6 +1,6 @@
1
1
  import { Socket } from "socket.io-client";
2
2
  import { PrepopulateUserMediaType, PrepopulateUserMediaParameters } from "../types/types";
3
- import type { Producer } from 'mediasoup-client/lib/types';
3
+ import type { Producer } from 'mediasoup-client/types';
4
4
 
5
5
  export interface DisconnectSendTransportAudioParameters extends PrepopulateUserMediaParameters {
6
6
  audioProducer: Producer | null;
@@ -1,4 +1,4 @@
1
- import type { Producer } from 'mediasoup-client/lib/types';
1
+ import type { Producer } from 'mediasoup-client/types';
2
2
  import { Socket } from "socket.io-client";
3
3
 
4
4
  export interface DisconnectSendTransportScreenParameters {
@@ -1,4 +1,4 @@
1
- import type { Producer } from 'mediasoup-client/lib/types';
1
+ import type { Producer } from 'mediasoup-client/types';
2
2
  import { Socket } from 'socket.io-client';
3
3
  import { ReorderStreamsType, ReorderStreamsParameters } from '../types/types';
4
4
 
@@ -0,0 +1,61 @@
1
+ export * from './autoAdjust';
2
+ export * from './calculateRowsAndColumns';
3
+ export * from './changeVids';
4
+ export * from './checkGrid';
5
+ export * from './checkPermission';
6
+ export * from './checkScreenShare';
7
+ export * from './closeAndResize';
8
+ export * from './compareActiveNames';
9
+ export * from './compareScreenStates';
10
+ export * from './connectIps';
11
+ export * from './connectLocalIps';
12
+ export * from './connectRecvTransport';
13
+ export * from './connectSendTransport';
14
+ export * from './connectSendTransportAudio';
15
+ export * from './connectSendTransportScreen';
16
+ export * from './connectSendTransportVideo';
17
+ export * from './consumerResume';
18
+ export * from './controlMedia';
19
+ export * from './createSendTransport';
20
+ export * from './disconnectSendTransportAudio';
21
+ export * from './disconnectSendTransportScreen';
22
+ export * from './disconnectSendTransportVideo';
23
+ export * from './dispStreams';
24
+ export * from './generatePageContent';
25
+ export * from './getEstimate';
26
+ export * from './getPipedProducersAlt';
27
+ export * from './getProducersPiped';
28
+ export * from './getVideos';
29
+ export * from './mixStreams';
30
+ export * from './onScreenChanges';
31
+ export * from './processConsumerTransports';
32
+ export * from './processConsumerTransportsAudio';
33
+ export * from './readjust';
34
+ export * from './receiveAllPipedTransports';
35
+ export * from './receiveRoomMessages';
36
+ export * from './reorderStreams';
37
+ export * from './rePort';
38
+ export * from './requestScreenShare';
39
+ export * from './resumePauseAudioStreams';
40
+ export * from './resumePauseStreams';
41
+ export * from './resumeSendTransportAudio';
42
+ export * from './reUpdateInter';
43
+ export * from './signalNewConsumerTransport';
44
+ export * from './startShareScreen';
45
+ export * from './stopShareScreen';
46
+ export * from './streamSuccessAudio';
47
+ export * from './streamSuccessAudioSwitch';
48
+ export * from './streamSuccessScreen';
49
+ export * from './streamSuccessVideo';
50
+ export * from './switchUserAudio';
51
+ export * from './switchUserVideo';
52
+ export * from './switchUserVideoAlt';
53
+ export * from './translationConsumerSwitch';
54
+ export * from './trigger';
55
+ export * from './updateMiniCardsGrid';
56
+ export * from './updateParticipantAudioDecibels';
57
+ export * from './gridLayout/addVideosGrid.engine';
58
+ export * from './gridLayout/prepopulateUserMedia.engine';
59
+ export * from './socketReceiveMethods/joinConsumeRoom';
60
+ export * from './socketReceiveMethods/newPipeProducer';
61
+ export * from './socketReceiveMethods/producerClosed';
@@ -1,4 +1,4 @@
1
- import type { Producer } from 'mediasoup-client/lib/types';
1
+ import type { Producer } from 'mediasoup-client/types';
2
2
  import { PrepopulateUserMediaParameters, PrepopulateUserMediaType } from "../types/types";
3
3
 
4
4
  export interface ResumeSendTransportAudioParameters extends PrepopulateUserMediaParameters {
@@ -1,7 +1,7 @@
1
1
  import { Socket } from "socket.io-client";
2
2
  import { ReorderStreamsParameters, ReorderStreamsType, ConnectRecvTransportType,
3
3
  ConnectRecvTransportParameters, CreateWebRTCTransportResponse } from "../types/types";
4
- import type { Device, DtlsParameters } from 'mediasoup-client/lib/types';
4
+ import type { Device, DtlsParameters } from 'mediasoup-client/types';
5
5
 
6
6
  export interface SignalNewConsumerTransportParameters extends ReorderStreamsParameters, ConnectRecvTransportParameters {
7
7
  device: Device | null;
@@ -176,4 +176,3 @@ export const signalNewConsumerTransport = async ({
176
176
  return;
177
177
  }
178
178
  };
179
-
@@ -1,5 +1,5 @@
1
1
  import { joinConRoom } from '../../producers/producerEmits/joinConRoom';
2
- import type { Device, RtpCapabilities } from 'mediasoup-client/lib/types';
2
+ import type { Device, RtpCapabilities } from 'mediasoup-client/types';
3
3
  import { Socket } from 'socket.io-client';
4
4
  import { ReceiveAllPipedTransportsParameters, ReceiveAllPipedTransportsType, CreateDeviceClientType } from '../../types/types';
5
5
 
@@ -1,7 +1,7 @@
1
1
  import { Socket } from 'socket.io-client';
2
2
  import { signalNewConsumerTransport } from '../signalNewConsumerTransport';
3
3
  import { ReorderStreamsParameters, ReorderStreamsType, SignalNewConsumerTransportParameters, ConnectRecvTransportParameters, ConnectRecvTransportType, ShowAlert } from '../../types/types';
4
- import type { Device } from 'mediasoup-client/lib/types';
4
+ import type { Device } from 'mediasoup-client/types';
5
5
 
6
6
  export interface TranslationMeta {
7
7
  speakerId: string;
@@ -1,7 +1,7 @@
1
1
 
2
2
 
3
3
  import { Socket } from "socket.io-client";
4
- import type { ProducerOptions } from 'mediasoup-client/lib/types';
4
+ import type { ProducerOptions } from 'mediasoup-client/types';
5
5
  import { Participant, PrepopulateUserMediaParameters, ShowAlert, CreateSendTransportParameters, ConnectSendTransportAudioParameters, ResumeSendTransportAudioParameters, PrepopulateUserMediaType, CreateSendTransportType, ConnectSendTransportAudioType, ResumeSendTransportAudioType } from "../types/types";
6
6
  export interface StreamSuccessAudioParameters extends CreateSendTransportParameters, ConnectSendTransportAudioParameters, ResumeSendTransportAudioParameters, PrepopulateUserMediaParameters {
7
7
  socket: Socket;
@@ -294,4 +294,3 @@ export const streamSuccessAudio = async ({
294
294
  }
295
295
  }
296
296
  };
297
-
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- import type { Producer, ProducerCodecOptions, ProducerOptions } from 'mediasoup-client/lib/types';
3
+ import type { Producer, ProducerCodecOptions, ProducerOptions } from 'mediasoup-client/types';
4
4
  import { Socket } from "socket.io-client";
5
5
  import { PrepopulateUserMediaParameters, PrepopulateUserMediaType, CreateSendTransportParameters, CreateSendTransportType, ConnectSendTransportAudioParameters, ConnectSendTransportAudioType, SleepType } from "../types/types";
6
6
 
@@ -312,4 +312,3 @@ export const streamSuccessAudioSwitch = async ({
312
312
  }
313
313
  }
314
314
  };
315
-
@@ -1,4 +1,4 @@
1
- import type { Device, Producer, ProducerOptions, RtpCodecCapability } from 'mediasoup-client/lib/types';
1
+ import type { Device, Producer, ProducerOptions, RtpCodecCapability } from 'mediasoup-client/types';
2
2
  import { Socket } from "socket.io-client";
3
3
  import { ConnectSendTransportVideoParameters, Participant, ShowAlert, CreateSendTransportParameters, ReorderStreamsParameters, SleepType, CreateSendTransportType, ConnectSendTransportVideoType, ReorderStreamsType, HParamsType, VParamsType } from "../types/types";
4
4
 
package/src/index.ts CHANGED
@@ -228,3 +228,5 @@ export * from './methods/stream/clickVideo';
228
228
  // virtualBackground, which requires an offscreen canvas and captureStream().
229
229
  export * from './methods/utils/headless';
230
230
  export * from './methods/utils/getParticipantMedia';
231
+ // Shared web compositor used by headless virtual-background processing.
232
+ export * from './methods/utils/virtualBackgroundCompositor';
@@ -1,4 +1,4 @@
1
- import type { RtpCapabilities } from 'mediasoup-client/lib/types';
1
+ import type { RtpCapabilities } from 'mediasoup-client/types';
2
2
  import type {
3
3
  AltDomains,
4
4
  ConsumeSocket,
@@ -3,6 +3,7 @@ import { HeadlessOptions } from './headlessTypes';
3
3
  import { getCurrentParams } from './getCurrentParams';
4
4
  import { getLocalVideoStream } from './getMediaStreams';
5
5
  import { HeadlessActionResult } from './roomActions';
6
+ import { compositeVirtualBackgroundFrame } from '../virtualBackgroundCompositor';
6
7
 
7
8
  const MEDIAPIPE_CDN = 'https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation';
8
9
 
@@ -15,7 +16,8 @@ const MEDIAPIPE_CDN = 'https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentati
15
16
  interface BackgroundSession {
16
17
  stop: () => void;
17
18
  stream: MediaStream;
18
- sourceTrack: MediaStreamTrack | null;
19
+ /** A live raw-camera clone that can be returned to the producer on clear. */
20
+ restoreTrack: MediaStreamTrack | null;
19
21
  }
20
22
  let session: BackgroundSession | null = null;
21
23
 
@@ -71,8 +73,8 @@ export type ApplyVirtualBackgroundType = (
71
73
  * without mounting the modal off-screen and simulating a click.
72
74
  *
73
75
  * This runs the same MediaPipe pipeline the modal uses — identical compositing:
74
- * draw the segmentation mask, `source-out` fill with the background pattern,
75
- * then `destination-atop` the camera frame — against an offscreen video and
76
+ * draw the segmentation mask, retain the person with `source-in`, then paint
77
+ * the replacement behind them with `destination-over` — against an offscreen video and
76
78
  * canvas it creates and owns.
77
79
  *
78
80
  * It also keeps the SDK's own state honest (`virtualStream`, `processedStream`,
@@ -115,21 +117,32 @@ export async function applyVirtualBackground({
115
117
  return { ok: false, error: 'Turn your camera on before applying a background.', stream: null };
116
118
  }
117
119
 
118
- // Replacing an existing session must not leave the old loop running.
119
- if (session) { session.stop(); session = null; }
120
-
120
+ const previousSession = session;
121
+ let candidateStop: (() => void) | null = null;
121
122
  try {
122
123
  const settings = sourceTrack.getSettings ? sourceTrack.getSettings() : {};
123
124
  const width = Number(settings.width) || 640;
124
125
  const height = Number(settings.height) || 360;
125
126
 
126
127
  const backgroundImage = typeof image === 'string' ? await loadImage(image) : image;
128
+ const producer = publish ? (live.videoProducer || live.localVideoProducer) : null;
129
+
130
+ // mediasoup Producers normally own their track (`stopTracks: true`). Their
131
+ // replaceTrack operation therefore stops the old raw track. Keep MediaPipe
132
+ // on an isolated clone and retain a second live clone for a clean clear.
133
+ const processingTrack = sourceTrack.clone?.();
134
+ if (!processingTrack) throw new Error('This camera cannot create the isolated track required for a virtual background.');
135
+ const restoreTrack = producer ? sourceTrack.clone?.() : sourceTrack;
136
+ if (!restoreTrack) {
137
+ processingTrack.stop();
138
+ throw new Error('This camera cannot preserve a track for restoring the original view.');
139
+ }
127
140
 
128
141
  const video = document.createElement('video');
129
142
  video.autoplay = true;
130
143
  video.muted = true;
131
144
  video.playsInline = true;
132
- video.srcObject = new MediaStream([sourceTrack]);
145
+ video.srcObject = new MediaStream([processingTrack]);
133
146
  await video.play().catch(() => {});
134
147
 
135
148
  const canvas = document.createElement('canvas');
@@ -149,18 +162,14 @@ export async function applyVirtualBackground({
149
162
  segmentation.onResults((results: any) => {
150
163
  try {
151
164
  if (!canvas.width || !canvas.height) return;
152
- ctx.save();
153
- ctx.clearRect(0, 0, canvas.width, canvas.height);
154
- ctx.drawImage(results.segmentationMask, 0, 0, canvas.width, canvas.height);
155
- ctx.globalCompositeOperation = 'source-out';
156
- if (backgroundImage) {
157
- const pattern = ctx.createPattern(backgroundImage as CanvasImageSource, 'repeat');
158
- ctx.fillStyle = pattern || 'transparent';
159
- ctx.fillRect(0, 0, canvas.width, canvas.height);
160
- }
161
- ctx.globalCompositeOperation = 'destination-atop';
162
- ctx.drawImage(results.image, 0, 0, canvas.width, canvas.height);
163
- ctx.restore();
165
+ compositeVirtualBackgroundFrame({
166
+ ctx,
167
+ segmentationMask: results.segmentationMask,
168
+ sourceImage: results.image,
169
+ backgroundImage: backgroundImage as CanvasImageSource | null,
170
+ width: canvas.width,
171
+ height: canvas.height,
172
+ });
164
173
  } catch {
165
174
  // A dropped frame must never kill the loop.
166
175
  }
@@ -190,29 +199,35 @@ export async function applyVirtualBackground({
190
199
  window.cancelAnimationFrame(frameHandle);
191
200
  try { segmentation.close(); } catch { /* already closed */ }
192
201
  processed.getTracks().forEach((track) => track.stop());
202
+ processingTrack.stop();
193
203
  video.srcObject = null;
194
204
  };
195
- session = { stop, stream: processed, sourceTrack };
196
-
197
- // Keep the SDK's own view of the world correct, so getLocalVideoStream and
198
- // getVirtualBackgroundState stay accurate without the caller intervening.
199
- live.updateVirtualStream?.(processed);
200
- live.updateProcessedStream?.(processed);
201
- live.updateKeepBackground?.(true);
205
+ candidateStop = stop;
202
206
 
203
- if (publish) {
204
- const producer = live.videoProducer || live.localVideoProducer;
207
+ if (producer) {
205
208
  const track = processed.getVideoTracks()[0];
206
209
  if (producer && typeof producer.replaceTrack === 'function' && track) {
207
210
  // replaceTrack avoids renegotiation, so remote viewers see the
208
211
  // background appear without the call visibly reconnecting.
209
212
  await producer.replaceTrack({ track });
210
213
  }
214
+ if (camera?.removeTrack && camera?.addTrack && restoreTrack !== sourceTrack) {
215
+ camera.removeTrack(sourceTrack);
216
+ camera.addTrack(restoreTrack);
217
+ if (sourceTrack.readyState === 'live') sourceTrack.stop();
218
+ }
211
219
  }
212
220
 
221
+ previousSession?.stop();
222
+ session = { stop, stream: processed, restoreTrack };
223
+ candidateStop = null;
224
+ live.updateVirtualStream?.(processed);
225
+ live.updateProcessedStream?.(processed);
226
+ live.updateKeepBackground?.(true);
227
+
213
228
  return { ok: true, error: '', stream: processed };
214
229
  } catch (error: any) {
215
- if (session) { session.stop(); session = null; }
230
+ candidateStop?.();
216
231
  return {
217
232
  ok: false,
218
233
  error: error?.message || 'The virtual background could not be applied.',
@@ -243,7 +258,7 @@ export async function clearVirtualBackground({
243
258
  }
244
259
  try {
245
260
  const producer = live.videoProducer || live.localVideoProducer;
246
- const original = active.sourceTrack;
261
+ const original = active.restoreTrack;
247
262
  if (producer && typeof producer.replaceTrack === 'function'
248
263
  && original && original.readyState === 'live') {
249
264
  await producer.replaceTrack({ track: original });
@@ -0,0 +1,53 @@
1
+ /** Options for composing one segmented camera frame. */
2
+ export interface CompositeVirtualBackgroundFrameOptions {
3
+ ctx: CanvasRenderingContext2D;
4
+ segmentationMask: CanvasImageSource;
5
+ sourceImage: CanvasImageSource;
6
+ backgroundImage?: CanvasImageSource | null;
7
+ width: number;
8
+ height: number;
9
+ repeatPattern?: 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat';
10
+ blurFallbackPixels?: number;
11
+ }
12
+
13
+ /**
14
+ * Paint the segmented person over a replacement background.
15
+ *
16
+ * Keeping this operation in the shared runtime makes headless web adapters and
17
+ * the React SDK use the same compositing contract. It is deliberately not
18
+ * exported from the native entry point; native platforms use their camera
19
+ * processor instead of a browser canvas.
20
+ */
21
+ export function compositeVirtualBackgroundFrame({
22
+ ctx,
23
+ segmentationMask,
24
+ sourceImage,
25
+ backgroundImage = null,
26
+ width,
27
+ height,
28
+ repeatPattern = 'repeat',
29
+ blurFallbackPixels = 0,
30
+ }: CompositeVirtualBackgroundFrameOptions): void {
31
+ const previousFilter = ctx.filter;
32
+ ctx.save();
33
+ try {
34
+ ctx.clearRect(0, 0, width, height);
35
+ ctx.globalCompositeOperation = 'source-over';
36
+ ctx.drawImage(segmentationMask, 0, 0, width, height);
37
+ ctx.globalCompositeOperation = 'source-in';
38
+ ctx.drawImage(sourceImage, 0, 0, width, height);
39
+ ctx.globalCompositeOperation = 'destination-over';
40
+ if (backgroundImage) {
41
+ const pattern = ctx.createPattern(backgroundImage, repeatPattern);
42
+ ctx.fillStyle = pattern || 'transparent';
43
+ ctx.fillRect(0, 0, width, height);
44
+ } else if (blurFallbackPixels > 0) {
45
+ ctx.filter = `blur(${blurFallbackPixels}px)`;
46
+ ctx.drawImage(sourceImage, 0, 0, width, height);
47
+ }
48
+ } finally {
49
+ ctx.filter = previousFilter || 'none';
50
+ ctx.globalCompositeOperation = 'source-over';
51
+ ctx.restore();
52
+ }
53
+ }
@@ -1,4 +1,4 @@
1
- import type { Producer } from 'mediasoup-client/lib/types';
1
+ import type { Producer } from 'mediasoup-client/types';
2
2
  import type {
3
3
  ConnectSendTransportScreenParameters,
4
4
  ConnectSendTransportScreenType,
@@ -125,4 +125,4 @@ export const captureCanvasStream = async ({
125
125
  } catch (error) {
126
126
  console.error(error, 'error in captureCanvasStream');
127
127
  }
128
- };
128
+ };
@@ -1,6 +1,6 @@
1
1
  import { Socket } from "socket.io-client";
2
2
  import { validateAlphanumeric } from "../../methods/utils/validateAlphanumeric"; // Import the validateAlphanumeric function from the validateAlphanumeric file
3
- import type { RtpCapabilities } from "mediasoup-client/lib/types";
3
+ import type { RtpCapabilities } from "mediasoup-client/types";
4
4
 
5
5
  export interface JoinConRoomOptions {
6
6
  socket: Socket;