mediasfu-shared 1.0.1 → 1.0.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.
Files changed (127) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +103 -222
  3. package/dist/index.cjs +7634 -2180
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +4203 -273
  6. package/dist/index.js +7663 -2209
  7. package/dist/index.js.map +1 -1
  8. package/package.json +96 -78
  9. package/src/ProducerClient/producerClientEmits/joinRoomClient.ts +57 -0
  10. package/src/ProducerClient/producerClientEmits/updateRoomParametersClient.ts +401 -0
  11. package/src/consumers/addVideosGrid.ts +3 -2
  12. package/src/consumers/changeVids.ts +111 -41
  13. package/src/consumers/checkPermission.ts +35 -1
  14. package/src/consumers/connectIps.ts +3 -1
  15. package/src/consumers/connectLocalIps.ts +3 -1
  16. package/src/consumers/connectRecvTransport.ts +96 -1
  17. package/src/consumers/consumerResume.ts +2 -2
  18. package/src/consumers/dispStreams.ts +83 -37
  19. package/src/consumers/frameworkConsumerContract.ts +6 -0
  20. package/src/consumers/generatePageContent.ts +24 -10
  21. package/src/consumers/getPipedProducersAlt.ts +112 -16
  22. package/src/consumers/gridLayout/addVideosGrid.engine.ts +42 -0
  23. package/src/consumers/gridLayout/prepopulateUserMedia.engine.ts +444 -0
  24. package/src/consumers/mixStreams.ts +45 -14
  25. package/src/consumers/onScreenChanges.ts +25 -10
  26. package/src/consumers/prepopulateUserMedia.ts +3 -2
  27. package/src/consumers/processConsumerTransports.ts +68 -23
  28. package/src/consumers/processConsumerTransportsAudio.ts +146 -17
  29. package/src/consumers/reUpdateInter.ts +61 -21
  30. package/src/consumers/readjust.ts +30 -14
  31. package/src/consumers/reorderStreams.ts +76 -42
  32. package/src/consumers/resumePauseAudioStreams.ts +66 -17
  33. package/src/consumers/resumePauseStreams.ts +53 -10
  34. package/src/consumers/socketReceiveMethods/joinConsumeRoom.ts +8 -0
  35. package/src/consumers/socketReceiveMethods/newPipeProducer.ts +114 -0
  36. package/src/consumers/socketReceiveMethods/producerClosed.ts +13 -0
  37. package/src/consumers/streamSuccessScreen.ts +2 -2
  38. package/src/consumers/streamSuccessVideo.ts +5 -0
  39. package/src/consumers/translationConsumerSwitch.ts +321 -0
  40. package/src/index.ts +85 -1
  41. package/src/methods/coHostMethods/modifyCoHostSettings.ts +9 -9
  42. package/src/methods/displaySettings/modifyDisplaySettings.ts +5 -0
  43. package/src/methods/index.ts +66 -0
  44. package/src/methods/message/sendMessage.ts +12 -29
  45. package/src/methods/panelists/focusPanelists.ts +83 -0
  46. package/src/methods/panelists/index.ts +3 -0
  47. package/src/methods/panelists/launchPanelists.ts +13 -0
  48. package/src/methods/panelists/updatePanelists.ts +135 -0
  49. package/src/methods/permissions/index.ts +3 -0
  50. package/src/methods/permissions/launchPermissions.ts +13 -0
  51. package/src/methods/permissions/updateParticipantPermission.ts +127 -0
  52. package/src/methods/permissions/updatePermissionConfig.ts +52 -0
  53. package/src/methods/polls/pollUpdated.ts +88 -0
  54. package/src/methods/recording/confirmRecording.ts +15 -12
  55. package/src/methods/recording/recordResumeTimer.ts +2 -2
  56. package/src/methods/recording/recordStartTimer.ts +2 -2
  57. package/src/methods/recording/timeLeftRecording.ts +25 -0
  58. package/src/methods/requests/hostRequestResponse.ts +158 -0
  59. package/src/methods/settings/modifySettings.ts +17 -17
  60. package/src/methods/socketReceive/allMembers.ts +450 -0
  61. package/src/methods/socketReceive/allMembersRest.ts +480 -0
  62. package/src/methods/socketReceive/allWaitingRoomMembers.ts +35 -0
  63. package/src/methods/socketReceive/banParticipant.ts +73 -0
  64. package/src/methods/socketReceive/controlMediaHost.ts +280 -0
  65. package/src/methods/socketReceive/disconnect.ts +40 -0
  66. package/src/methods/socketReceive/disconnectUserSelf.ts +56 -0
  67. package/src/methods/socketReceive/getDomains.ts +112 -0
  68. package/src/methods/socketReceive/meetingEnded.ts +49 -0
  69. package/src/methods/socketReceive/meetingStillThere.ts +26 -0
  70. package/src/methods/socketReceive/panelistReceiveMethods.ts +195 -0
  71. package/src/methods/socketReceive/participantRequested.ts +48 -0
  72. package/src/methods/socketReceive/permissionReceiveMethods.ts +59 -0
  73. package/src/methods/socketReceive/personJoined.ts +35 -0
  74. package/src/methods/socketReceive/producerMediaClosed.ts +223 -0
  75. package/src/methods/socketReceive/producerMediaPaused.ts +267 -0
  76. package/src/methods/socketReceive/producerMediaResumed.ts +157 -0
  77. package/src/methods/socketReceive/reInitiateRecording.ts +53 -0
  78. package/src/methods/socketReceive/receiveMessage.ts +117 -0
  79. package/src/methods/socketReceive/recordingNotice.ts +286 -0
  80. package/src/methods/socketReceive/roomRecordParams.ts +122 -0
  81. package/src/methods/socketReceive/screenProducerId.ts +61 -0
  82. package/src/methods/socketReceive/startRecords.ts +46 -0
  83. package/src/methods/socketReceive/stoppedRecording.ts +44 -0
  84. package/src/methods/socketReceive/translationReceiveMethods.ts +581 -0
  85. package/src/methods/socketReceive/updateConsumingDomains.ts +128 -0
  86. package/src/methods/socketReceive/updateMediaSettings.ts +45 -0
  87. package/src/methods/socketReceive/updatedCoHost.ts +75 -0
  88. package/src/methods/socketReceive/userWaiting.ts +45 -0
  89. package/src/methods/stream/clickAudio.ts +380 -0
  90. package/src/methods/stream/clickChat.ts +36 -0
  91. package/src/methods/stream/clickScreenShare.ts +173 -0
  92. package/src/methods/stream/clickVideo.ts +28 -6
  93. package/src/methods/stream/index.ts +1 -0
  94. package/src/methods/utils/SoundPlayer.ts +31 -0
  95. package/src/methods/utils/checkLimitsAndMakeRequest.ts +156 -2
  96. package/src/methods/utils/createResponseJoinRoom.ts +47 -0
  97. package/src/methods/utils/createRoomOnMediaSFU.ts +160 -0
  98. package/src/methods/utils/formatNumber.ts +42 -0
  99. package/src/methods/utils/generateRandomMessages.ts +70 -0
  100. package/src/methods/utils/generateRandomParticipants.ts +100 -0
  101. package/src/methods/utils/generateRandomPolls.ts +43 -0
  102. package/src/methods/utils/generateRandomRequestList.ts +51 -0
  103. package/src/methods/utils/generateRandomWaitingRoomList.ts +17 -0
  104. package/src/methods/utils/getModalPosition.ts +23 -0
  105. package/src/methods/utils/getOverlayPosition.ts +37 -0
  106. package/src/methods/utils/initialValuesState.ts +405 -0
  107. package/src/methods/utils/joinRoomOnMediaSFU.ts +124 -0
  108. package/src/methods/utils/liveSubtitle.ts +107 -0
  109. package/src/methods/utils/meetingTimeRemaining.ts +33 -0
  110. package/src/methods/utils/meetingTimer/startMeetingProgressTimer.ts +72 -0
  111. package/src/methods/utils/producer/aParams.ts +10 -0
  112. package/src/methods/utils/producer/hParams.ts +26 -0
  113. package/src/methods/utils/producer/screenParams.ts +13 -0
  114. package/src/methods/utils/producer/vParams.ts +26 -0
  115. package/src/methods/utils/producer/videoCaptureConstraints.ts +65 -0
  116. package/src/methods/utils/resolveMediaSFURoomApi.ts +28 -0
  117. package/src/methods/utils/sleep.ts +24 -0
  118. package/src/methods/utils/translationLanguages.ts +308 -0
  119. package/src/methods/utils/webrtc.ts +44 -0
  120. package/src/methods/welcome/handleWelcomeRequest.ts +11 -2
  121. package/src/methods/welcome/index.ts +5 -1
  122. package/src/methods/whiteboard/captureCanvasStream.ts +128 -0
  123. package/src/producers/producerEmits/joinConRoom.ts +2 -2
  124. package/src/producers/producerEmits/joinLocalRoom.ts +240 -0
  125. package/src/producers/producerEmits/joinRoom.ts +129 -0
  126. package/src/types/shared-base-types.ts +14 -3
  127. package/src/types/types.ts +255 -0
@@ -1,24 +1,62 @@
1
1
  import { Participant, Transport, Stream } from "../types/types";
2
2
 
3
- export interface ResumePauseStreamsParameters {
4
- participants: Participant[];
3
+ interface ParticipantLike {
4
+ name: string;
5
+ islevel?: string | null;
6
+ videoID?: string | null;
7
+ }
8
+
9
+ interface ConsumerLike {
10
+ kind?: string;
11
+ resume: () => unknown;
12
+ }
13
+
14
+ interface SocketLike {
15
+ emit: (
16
+ event: string,
17
+ payload: { serverConsumerId: string },
18
+ callback?: ((payload: { resumed: boolean }) => void | Promise<unknown>)
19
+ ) => void;
20
+ }
21
+
22
+ interface TransportLike {
23
+ producerId?: string | null;
24
+ consumer: ConsumerLike;
25
+ socket_: SocketLike;
26
+ serverConsumerTransportId: string;
27
+ }
28
+
29
+ export interface ResumePauseStreamsParameters<
30
+ TParticipant extends ParticipantLike = Participant,
31
+ TTransport extends TransportLike = Transport,
32
+ TStream = Stream,
33
+ > {
34
+ participants: TParticipant[];
5
35
  dispActiveNames: string[];
6
- remoteScreenStream: Stream[];
7
- consumerTransports: Transport[];
36
+ remoteScreenStream: TStream[];
37
+ consumerTransports: TTransport[];
8
38
  screenId?: string;
9
39
  islevel: string;
10
40
 
11
41
  // mediasfu functions
12
- getUpdatedAllParams: () => ResumePauseStreamsParameters;
42
+ getUpdatedAllParams: () => ResumePauseStreamsParameters<TParticipant, TTransport, TStream>;
13
43
  [key: string]: any;
14
44
  }
15
45
 
16
- export interface ResumePauseStreamsOptions {
17
- parameters: ResumePauseStreamsParameters;
46
+ export interface ResumePauseStreamsOptions<
47
+ TParticipant extends ParticipantLike = Participant,
48
+ TTransport extends TransportLike = Transport,
49
+ TStream = Stream,
50
+ > {
51
+ parameters: ResumePauseStreamsParameters<TParticipant, TTransport, TStream>;
18
52
  }
19
53
 
20
54
  // Export the type definition for the function
21
- export type ResumePauseStreamsType = (options: ResumePauseStreamsOptions) => Promise<void>;
55
+ export type ResumePauseStreamsType = <
56
+ TParticipant extends ParticipantLike = Participant,
57
+ TTransport extends TransportLike = Transport,
58
+ TStream = Stream,
59
+ >(options: ResumePauseStreamsOptions<TParticipant, TTransport, TStream>) => Promise<void>;
22
60
 
23
61
  /**
24
62
  * Resumes or pauses streams based on the provided parameters.
@@ -49,9 +87,13 @@ export type ResumePauseStreamsType = (options: ResumePauseStreamsOptions) => Pro
49
87
  * ```
50
88
  */
51
89
 
52
- export async function resumePauseStreams({
90
+ export async function resumePauseStreams<
91
+ TParticipant extends ParticipantLike = Participant,
92
+ TTransport extends TransportLike = Transport,
93
+ TStream = Stream,
94
+ >({
53
95
  parameters,
54
- }: ResumePauseStreamsOptions): Promise<void> {
96
+ }: ResumePauseStreamsOptions<TParticipant, TTransport, TStream>): Promise<void> {
55
97
  try {
56
98
  // Destructure parameters
57
99
  const { participants, dispActiveNames, consumerTransports, screenId, islevel } = parameters;
@@ -85,6 +127,7 @@ export async function resumePauseStreams({
85
127
  // Get consumer transports with producerId in allVideoIDs
86
128
  const consumerTransportsToResume = consumerTransports.filter(
87
129
  (transport) =>
130
+ transport.producerId &&
88
131
  allVideoIDs.includes(transport.producerId) &&
89
132
  transport.consumer.kind !== "audio"
90
133
  );
@@ -120,6 +120,14 @@ export const joinConsumeRoom = async ({
120
120
 
121
121
  // Receive all piped transports
122
122
  await receiveAllPipedTransports({ nsock: remote_sock, parameters });
123
+
124
+ setTimeout(async () => {
125
+ try {
126
+ await receiveAllPipedTransports({ nsock: remote_sock, parameters });
127
+ } catch (error) {
128
+ console.log('[joinConsumeRoom] Retry receiveAllPipedTransports failed:', error);
129
+ }
130
+ }, 30000);
123
131
  }
124
132
 
125
133
  return data;
@@ -2,6 +2,14 @@ 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
4
  import type { Device } from 'mediasoup-client/lib/types';
5
+
6
+ export interface TranslationMeta {
7
+ speakerId: string;
8
+ speakerName: string;
9
+ language: string;
10
+ originalProducerId?: string;
11
+ isSpeakerControlled?: boolean;
12
+ }
5
13
  export interface NewPipeProducerParameters extends ReorderStreamsParameters, SignalNewConsumerTransportParameters, ConnectRecvTransportParameters {
6
14
 
7
15
  first_round: boolean;
@@ -21,6 +29,15 @@ export interface NewPipeProducerParameters extends ReorderStreamsParameters, Sig
21
29
  connectRecvTransport: ConnectRecvTransportType;
22
30
  reorderStreams: ReorderStreamsType;
23
31
  getUpdatedAllParams: () => NewPipeProducerParameters;
32
+
33
+ startConsumingTranslation?: (producerId: string, speakerId: string, language: string, originalProducerId?: string, nsock?: Socket) => Promise<void>;
34
+ translationSubscriptions?: Map<string, { speakerId: string; language: string }>;
35
+ speakerTranslationStates?: Map<string, { speakerId: string; speakerName: string; inputLanguage: string; outputLanguage: string; originalProducerId: string; enabled: boolean }>;
36
+ listenerTranslationOverrides?: Map<string, { speakerId: string; wantOriginal: boolean; preferredLanguage?: string }>;
37
+ listenerTranslationPreferences?: {
38
+ perSpeaker: Map<string, { speakerId: string; language: string | null; wantOriginal: boolean }>;
39
+ globalLanguage: string | null;
40
+ };
24
41
  [key: string]: any;
25
42
 
26
43
  }
@@ -30,6 +47,8 @@ export interface NewPipeProducerOptions {
30
47
  islevel: string;
31
48
  nsock: Socket;
32
49
  parameters: NewPipeProducerParameters;
50
+ isTranslation?: boolean;
51
+ translationMeta?: TranslationMeta;
33
52
  }
34
53
 
35
54
  // Export the type definition for the function
@@ -96,7 +115,102 @@ export const newPipeProducer = async ({
96
115
  islevel,
97
116
  nsock,
98
117
  parameters,
118
+ isTranslation,
119
+ translationMeta,
99
120
  }: NewPipeProducerOptions): Promise<void> => {
121
+ if (isTranslation && translationMeta) {
122
+ const freshParams = parameters.getUpdatedAllParams ? parameters.getUpdatedAllParams() : parameters;
123
+
124
+ const {
125
+ startConsumingTranslation,
126
+ translationSubscriptions,
127
+ speakerTranslationStates,
128
+ listenerTranslationOverrides,
129
+ listenerTranslationPreferences,
130
+ } = freshParams;
131
+
132
+ const normalizedLang = translationMeta.language?.toLowerCase();
133
+ const isSpeakerControlledFromMeta = translationMeta.isSpeakerControlled === true;
134
+ const speakerState = speakerTranslationStates?.get(translationMeta.speakerId);
135
+ const isSpeakerControlledFromState = speakerState?.enabled
136
+ && speakerState?.outputLanguage?.toLowerCase() === normalizedLang;
137
+ const shouldSkipBecauseWrongLanguage = isSpeakerControlledFromMeta
138
+ && speakerState?.enabled
139
+ && speakerState?.outputLanguage?.toLowerCase() !== normalizedLang;
140
+
141
+ const subscriptionKey = `${translationMeta.speakerId}_${normalizedLang}`;
142
+ const isListenerSubscribed = translationSubscriptions?.has(subscriptionKey)
143
+ || translationSubscriptions?.has(translationMeta.speakerId);
144
+
145
+ let overrideBlocksConsumption = false;
146
+ let shouldConsumeForOverride = false;
147
+ let shouldConsumeForGlobal = false;
148
+
149
+ const perSpeakerPref = listenerTranslationPreferences?.perSpeaker?.get(translationMeta.speakerId);
150
+ const globalPref = listenerTranslationPreferences?.globalLanguage;
151
+ const listenerOverride = listenerTranslationOverrides?.get(translationMeta.speakerId);
152
+
153
+ if (perSpeakerPref) {
154
+ if (perSpeakerPref.wantOriginal) {
155
+ overrideBlocksConsumption = true;
156
+ } else if (perSpeakerPref.language) {
157
+ if (perSpeakerPref.language === normalizedLang) {
158
+ shouldConsumeForOverride = true;
159
+ } else {
160
+ overrideBlocksConsumption = true;
161
+ }
162
+ }
163
+ } else if (globalPref) {
164
+ if (globalPref === normalizedLang) {
165
+ shouldConsumeForGlobal = true;
166
+ } else {
167
+ overrideBlocksConsumption = true;
168
+ }
169
+ } else if (listenerOverride) {
170
+ if (listenerOverride.wantOriginal) {
171
+ overrideBlocksConsumption = true;
172
+ } else if (listenerOverride.preferredLanguage) {
173
+ if (listenerOverride.preferredLanguage.toLowerCase() === normalizedLang) {
174
+ shouldConsumeForOverride = true;
175
+ } else {
176
+ overrideBlocksConsumption = true;
177
+ }
178
+ }
179
+ }
180
+
181
+ const shouldConsumeForSpeakerControlled =
182
+ isSpeakerControlledFromMeta
183
+ && (!speakerState?.enabled || speakerState?.outputLanguage?.toLowerCase() === normalizedLang);
184
+ const hasNoPreference = !perSpeakerPref && !globalPref && !listenerOverride;
185
+ const isListenerInitiated = !isSpeakerControlledFromMeta && !isSpeakerControlledFromState;
186
+ const blockBecauseNotRelevant = hasNoPreference && isListenerInitiated && !isListenerSubscribed;
187
+
188
+ const shouldConsume =
189
+ !overrideBlocksConsumption
190
+ && !shouldSkipBecauseWrongLanguage
191
+ && !blockBecauseNotRelevant
192
+ && (shouldConsumeForOverride
193
+ || shouldConsumeForGlobal
194
+ || shouldConsumeForSpeakerControlled
195
+ || isSpeakerControlledFromState
196
+ || isListenerSubscribed);
197
+
198
+ if (shouldConsume && startConsumingTranslation) {
199
+ try {
200
+ await startConsumingTranslation(
201
+ producerId,
202
+ translationMeta.speakerId,
203
+ translationMeta.language,
204
+ translationMeta.originalProducerId,
205
+ nsock,
206
+ );
207
+ } catch {
208
+ }
209
+ }
210
+
211
+ return;
212
+ }
213
+
100
214
  const {
101
215
  shareScreenStarted,
102
216
  shared,
@@ -63,6 +63,19 @@ export const producerClosed = async ({
63
63
  }: ProducerClosedOptions): Promise<void> => {
64
64
  let { consumerTransports, closeAndResize, screenId, updateConsumerTransports } = parameters;
65
65
 
66
+ const activeTranslationProducerIds = parameters.activeTranslationProducerIds;
67
+ const isTranslationProducer = activeTranslationProducerIds?.has?.(remoteProducerId);
68
+
69
+ if (isTranslationProducer) {
70
+ activeTranslationProducerIds?.delete?.(remoteProducerId);
71
+
72
+ const removeTranslationStream = parameters.removeTranslationStream as ((producerId: string) => void) | undefined;
73
+
74
+ if (removeTranslationStream) {
75
+ removeTranslationStream(remoteProducerId);
76
+ }
77
+ }
78
+
66
79
  // Handle producer closed
67
80
  const producerToClose = consumerTransports.find(
68
81
  (transportData: any) => transportData.producerId === remoteProducerId
@@ -3,11 +3,11 @@
3
3
  import { Socket } from "socket.io-client";
4
4
  import {
5
5
  SleepType, CreateSendTransportType, ConnectSendTransportScreenType, DisconnectSendTransportScreenType, StopShareScreenType, ReorderStreamsType, PrepopulateUserMediaType, RePortType,
6
- ShowAlert, CreateSendTransportParameters, ConnectSendTransportScreenParameters, DisconnectSendTransportScreenParameters, StopShareScreenParameters, ReorderStreamsParameters, PrepopulateUserMediaParameters,
6
+ ShowAlert, CreateSendTransportParameters, ConnectSendTransportScreenParameters, DisconnectSendTransportScreenParameters, StopShareScreenParameters, ReorderStreamsParameters, PrepopulateUserMediaParameters, RePortParameters,
7
7
  EventType
8
8
  } from "../types/types";
9
9
 
10
- export interface StreamSuccessScreenParameters extends CreateSendTransportParameters, ConnectSendTransportScreenParameters, DisconnectSendTransportScreenParameters, StopShareScreenParameters, ReorderStreamsParameters, PrepopulateUserMediaParameters {
10
+ export interface StreamSuccessScreenParameters extends CreateSendTransportParameters, ConnectSendTransportScreenParameters, DisconnectSendTransportScreenParameters, StopShareScreenParameters, ReorderStreamsParameters, PrepopulateUserMediaParameters, RePortParameters {
11
11
  socket: Socket;
12
12
  transportCreated: boolean;
13
13
  localStreamScreen: MediaStream | null;
@@ -30,6 +30,7 @@ export interface StreamSuccessVideoParameters extends CreateSendTransportParamet
30
30
  keepBackground: boolean;
31
31
  appliedBackground: boolean;
32
32
  videoProducer: Producer | null;
33
+ removeSingleVideoEncoding?: boolean;
33
34
 
34
35
  // Update functions
35
36
  updateTransportCreatedVideo: (created: boolean) => void;
@@ -275,6 +276,10 @@ export const streamSuccessVideo = async ({
275
276
  (codec: RtpCodecCapability) => codec.mimeType.toLowerCase() !== "video/vp9" && codec.kind === "video"
276
277
  ) || [];
277
278
 
279
+ if (parameters.removeSingleVideoEncoding && videoParamse.encodings && videoParamse.encodings.length <= 1) {
280
+ delete videoParamse.encodings;
281
+ }
282
+
278
283
  videoParams = {
279
284
  track: localStream.getVideoTracks()[0],
280
285
  ...videoParamse,
@@ -0,0 +1,321 @@
1
+ import { BreakoutParticipant, EventType, Participant, Transport } from '../types/types';
2
+
3
+ export interface TranslationConsumerSwitchParameters {
4
+ consumerTransports: Transport[];
5
+ roomName: string;
6
+ member: string;
7
+ updateConsumerTransports: (transports: Transport[]) => void;
8
+ breakOutRoomStarted?: boolean;
9
+ breakOutRoomEnded?: boolean;
10
+ breakoutRooms?: BreakoutParticipant[][];
11
+ limitedBreakRoom?: BreakoutParticipant[];
12
+ participants?: Participant[];
13
+ ref_participants?: Participant[];
14
+ islevel?: string;
15
+ eventType?: EventType;
16
+ hostNewRoom?: number;
17
+ [key: string]: any;
18
+ }
19
+
20
+ export interface PauseOriginalProducerOptions {
21
+ originalProducerId: string;
22
+ speakerId?: string;
23
+ parameters: TranslationConsumerSwitchParameters;
24
+ }
25
+
26
+ export interface ResumeOriginalProducerOptions {
27
+ originalProducerId: string;
28
+ speakerId?: string;
29
+ parameters: TranslationConsumerSwitchParameters;
30
+ }
31
+
32
+ export type PauseOriginalProducerType = (options: PauseOriginalProducerOptions) => Promise<void>;
33
+ export type ResumeOriginalProducerType = (options: ResumeOriginalProducerOptions) => Promise<void>;
34
+
35
+ export interface StopConsumingTranslationOptions {
36
+ speakerId?: string;
37
+ language: string;
38
+ translationProducerMap: Record<string, Record<string, string>>;
39
+ parameters: TranslationConsumerSwitchParameters;
40
+ }
41
+
42
+ export type StopConsumingTranslationType = (options: StopConsumingTranslationOptions) => Promise<string | null>;
43
+
44
+ export const isSpeakerInMyBreakoutRoom = (
45
+ speakerName: string,
46
+ parameters: TranslationConsumerSwitchParameters
47
+ ): boolean => {
48
+ const {
49
+ breakOutRoomStarted = false,
50
+ breakOutRoomEnded = false,
51
+ limitedBreakRoom = [],
52
+ participants = [],
53
+ islevel = '1',
54
+ eventType = 'conference',
55
+ hostNewRoom = -1,
56
+ breakoutRooms = [],
57
+ member = '',
58
+ } = parameters;
59
+
60
+ if (!breakOutRoomStarted || breakOutRoomEnded) {
61
+ return true;
62
+ }
63
+
64
+ const host = participants.find((p) => p.islevel === '2');
65
+ const speakerIsHost = host?.name === speakerName;
66
+
67
+ if (islevel !== '2') {
68
+ if (eventType === 'webinar' && speakerIsHost) {
69
+ return true;
70
+ }
71
+
72
+ if (eventType === 'conference' && speakerIsHost) {
73
+ const roomMember = breakoutRooms.find((r) =>
74
+ r.find((p) => p.name === member)
75
+ );
76
+ const memberBreakRoom = roomMember ? breakoutRooms.indexOf(roomMember) : -1;
77
+ const inBreakRoom = memberBreakRoom !== -1;
78
+
79
+ if (inBreakRoom) {
80
+ return memberBreakRoom === hostNewRoom;
81
+ }
82
+
83
+ if (hostNewRoom === -1) {
84
+ return true;
85
+ }
86
+
87
+ return hostNewRoom === memberBreakRoom && memberBreakRoom !== -1;
88
+ }
89
+ }
90
+
91
+ return limitedBreakRoom.some((p) => p.name === speakerName);
92
+ };
93
+
94
+ export const pauseOriginalProducer = async ({
95
+ originalProducerId,
96
+ speakerId,
97
+ parameters,
98
+ }: PauseOriginalProducerOptions): Promise<void> => {
99
+ try {
100
+ const { consumerTransports } = parameters;
101
+
102
+ if (speakerId && !isSpeakerInMyBreakoutRoom(speakerId, parameters)) {
103
+ return;
104
+ }
105
+
106
+ const transport = consumerTransports.find(
107
+ (t) => t.producerId === originalProducerId && t.consumer?.kind === 'audio'
108
+ );
109
+
110
+ if (!transport?.consumer) {
111
+ return;
112
+ }
113
+
114
+ if (transport.consumer.track) {
115
+ transport.consumer.track.enabled = false;
116
+ }
117
+
118
+ if (transport.consumer.paused) {
119
+ return;
120
+ }
121
+
122
+ transport.consumer.pause();
123
+
124
+ transport.socket_?.emit(
125
+ 'consumer-pause',
126
+ { serverConsumerId: transport.serverConsumerTransportId },
127
+ async () => {}
128
+ );
129
+ } catch (error) {
130
+ console.error('[TranslationSwitch] Error pausing original producer:', error);
131
+ }
132
+ };
133
+
134
+ export const resumeOriginalProducer = async ({
135
+ originalProducerId,
136
+ speakerId,
137
+ parameters,
138
+ }: ResumeOriginalProducerOptions): Promise<void> => {
139
+ try {
140
+ const { consumerTransports } = parameters;
141
+
142
+ if (speakerId && !isSpeakerInMyBreakoutRoom(speakerId, parameters)) {
143
+ return;
144
+ }
145
+
146
+ const transport = consumerTransports.find(
147
+ (t) => t.producerId === originalProducerId && t.consumer?.kind === 'audio'
148
+ );
149
+
150
+ if (!transport?.consumer) {
151
+ return;
152
+ }
153
+
154
+ if (!transport.consumer.paused) {
155
+ if (transport.consumer.track) {
156
+ transport.consumer.track.enabled = true;
157
+ }
158
+ return;
159
+ }
160
+
161
+ transport.socket_?.emit(
162
+ 'consumer-resume',
163
+ { serverConsumerId: transport.serverConsumerTransportId },
164
+ async ({ resumed }: { resumed: boolean }) => {
165
+ if (resumed) {
166
+ if (transport.consumer.track) {
167
+ transport.consumer.track.enabled = true;
168
+ }
169
+ transport.consumer.resume();
170
+ }
171
+ }
172
+ );
173
+ } catch (error) {
174
+ console.error('[TranslationSwitch] Error resuming original producer:', error);
175
+ }
176
+ };
177
+
178
+ export const isConsumingTranslationForSpeaker = (
179
+ speakerId: string,
180
+ consumerTransports: Transport[],
181
+ translationProducerMap: Map<string, { translationProducerId: string; originalProducerId: string; language: string }>
182
+ ): { consuming: boolean; language?: string; translationProducerId?: string; originalProducerId?: string } => {
183
+ const translationInfo = translationProducerMap.get(speakerId);
184
+
185
+ if (translationInfo) {
186
+ const hasConsumer = consumerTransports.some(
187
+ (t) => t.producerId === translationInfo.translationProducerId
188
+ );
189
+
190
+ if (hasConsumer) {
191
+ return {
192
+ consuming: true,
193
+ language: translationInfo.language,
194
+ translationProducerId: translationInfo.translationProducerId,
195
+ originalProducerId: translationInfo.originalProducerId,
196
+ };
197
+ }
198
+ }
199
+
200
+ return { consuming: false };
201
+ };
202
+
203
+ export const getActiveTranslationConsumers = (
204
+ translationProducerMap: Map<string, { translationProducerId: string; originalProducerId: string; language: string }>,
205
+ consumerTransports: Transport[]
206
+ ): Array<{ speakerId: string; translationProducerId: string; originalProducerId: string; language: string }> => {
207
+ const results: Array<{ speakerId: string; translationProducerId: string; originalProducerId: string; language: string }> = [];
208
+
209
+ translationProducerMap.forEach((info, speakerId) => {
210
+ const hasConsumer = consumerTransports.some(
211
+ (t) => t.producerId === info.translationProducerId
212
+ );
213
+
214
+ if (hasConsumer) {
215
+ results.push({
216
+ speakerId,
217
+ ...info,
218
+ });
219
+ }
220
+ });
221
+
222
+ return results;
223
+ };
224
+
225
+ export const findOriginalProducerForSpeaker = (
226
+ speakerId: string,
227
+ allAudioStreams: Array<{ producerId: string; name?: string; [key: string]: any }>
228
+ ): string | null => {
229
+ const stream = allAudioStreams.find(
230
+ (s) => s.name === speakerId || s.producerId?.includes(speakerId)
231
+ );
232
+ return stream?.producerId || null;
233
+ };
234
+
235
+ export const stopConsumingTranslation = async (options: StopConsumingTranslationOptions): Promise<string | null> => {
236
+ const { language, translationProducerMap, parameters } = options;
237
+ try {
238
+ const { consumerTransports, updateConsumerTransports } = parameters;
239
+
240
+ let originalProducerId: string | null = null;
241
+ let translationProducerId: string | null = null;
242
+
243
+ for (const [origId, langMap] of Object.entries(translationProducerMap)) {
244
+ if (langMap && langMap[language]) {
245
+ translationProducerId = langMap[language];
246
+ originalProducerId = origId;
247
+ break;
248
+ }
249
+ }
250
+
251
+ if (!translationProducerId) {
252
+ return originalProducerId;
253
+ }
254
+
255
+ const transportIndex = consumerTransports.findIndex(
256
+ (t) => t.producerId === translationProducerId
257
+ );
258
+
259
+ if (transportIndex === -1) {
260
+ return originalProducerId;
261
+ }
262
+
263
+ const transport = consumerTransports[transportIndex];
264
+
265
+ if (transport.socket_ && transport.consumer) {
266
+ transport.socket_.emit(
267
+ 'consumer-close',
268
+ { serverConsumerId: transport.serverConsumerTransportId },
269
+ () => {}
270
+ );
271
+ }
272
+
273
+ if (transport.consumer) {
274
+ transport.consumer.close();
275
+ }
276
+
277
+ const updatedTransports = consumerTransports.filter((_, i) => i !== transportIndex);
278
+ updateConsumerTransports(updatedTransports);
279
+
280
+ return originalProducerId;
281
+ } catch (error) {
282
+ console.error('[TranslationSwitch] Error stopping translation consumer:', error);
283
+ return null;
284
+ }
285
+ };
286
+
287
+ export const syncTranslationStateAfterBreakoutChange = async (
288
+ translationProducerMap: Record<string, Record<string, string>>,
289
+ speakerIdByProducerId: Record<string, string>,
290
+ parameters: TranslationConsumerSwitchParameters
291
+ ): Promise<void> => {
292
+ try {
293
+ const { consumerTransports } = parameters;
294
+
295
+ for (const [originalProducerId, langMap] of Object.entries(translationProducerMap)) {
296
+ const speakerId = speakerIdByProducerId[originalProducerId];
297
+ if (!speakerId) continue;
298
+
299
+ const inMyRoom = isSpeakerInMyBreakoutRoom(speakerId, parameters);
300
+ const hasTranslation = Object.keys(langMap).length > 0;
301
+
302
+ const originalConsumer = consumerTransports.find(
303
+ (t) => t.producerId === originalProducerId && t.consumer?.kind === 'audio'
304
+ );
305
+
306
+ if (!originalConsumer) continue;
307
+
308
+ if (inMyRoom && hasTranslation) {
309
+ if (!originalConsumer.consumer.paused) {
310
+ await pauseOriginalProducer({
311
+ originalProducerId,
312
+ speakerId,
313
+ parameters,
314
+ });
315
+ }
316
+ }
317
+ }
318
+ } catch (error) {
319
+ console.error('[TranslationSwitch] Error syncing translation state:', error);
320
+ }
321
+ };