mediasfu-shared 1.0.1 → 1.0.2

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 (125) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +103 -222
  3. package/dist/index.cjs +7500 -2163
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +4203 -273
  6. package/dist/index.js +7521 -2184
  7. package/dist/index.js.map +1 -1
  8. package/package.json +85 -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/connectRecvTransport.ts +42 -1
  15. package/src/consumers/consumerResume.ts +2 -2
  16. package/src/consumers/dispStreams.ts +83 -37
  17. package/src/consumers/frameworkConsumerContract.ts +6 -0
  18. package/src/consumers/generatePageContent.ts +24 -10
  19. package/src/consumers/getPipedProducersAlt.ts +112 -16
  20. package/src/consumers/gridLayout/addVideosGrid.engine.ts +42 -0
  21. package/src/consumers/gridLayout/prepopulateUserMedia.engine.ts +444 -0
  22. package/src/consumers/mixStreams.ts +45 -14
  23. package/src/consumers/onScreenChanges.ts +25 -10
  24. package/src/consumers/prepopulateUserMedia.ts +3 -2
  25. package/src/consumers/processConsumerTransports.ts +68 -23
  26. package/src/consumers/processConsumerTransportsAudio.ts +53 -16
  27. package/src/consumers/reUpdateInter.ts +61 -21
  28. package/src/consumers/readjust.ts +30 -14
  29. package/src/consumers/reorderStreams.ts +76 -42
  30. package/src/consumers/resumePauseAudioStreams.ts +66 -17
  31. package/src/consumers/resumePauseStreams.ts +53 -10
  32. package/src/consumers/socketReceiveMethods/joinConsumeRoom.ts +8 -0
  33. package/src/consumers/socketReceiveMethods/newPipeProducer.ts +114 -0
  34. package/src/consumers/socketReceiveMethods/producerClosed.ts +13 -0
  35. package/src/consumers/streamSuccessScreen.ts +2 -2
  36. package/src/consumers/streamSuccessVideo.ts +5 -0
  37. package/src/consumers/translationConsumerSwitch.ts +299 -0
  38. package/src/index.ts +85 -1
  39. package/src/methods/coHostMethods/modifyCoHostSettings.ts +9 -9
  40. package/src/methods/displaySettings/modifyDisplaySettings.ts +5 -0
  41. package/src/methods/index.ts +66 -0
  42. package/src/methods/message/sendMessage.ts +12 -29
  43. package/src/methods/panelists/focusPanelists.ts +83 -0
  44. package/src/methods/panelists/index.ts +3 -0
  45. package/src/methods/panelists/launchPanelists.ts +13 -0
  46. package/src/methods/panelists/updatePanelists.ts +135 -0
  47. package/src/methods/permissions/index.ts +3 -0
  48. package/src/methods/permissions/launchPermissions.ts +13 -0
  49. package/src/methods/permissions/updateParticipantPermission.ts +127 -0
  50. package/src/methods/permissions/updatePermissionConfig.ts +52 -0
  51. package/src/methods/polls/pollUpdated.ts +88 -0
  52. package/src/methods/recording/confirmRecording.ts +15 -12
  53. package/src/methods/recording/recordResumeTimer.ts +2 -2
  54. package/src/methods/recording/recordStartTimer.ts +2 -2
  55. package/src/methods/recording/timeLeftRecording.ts +25 -0
  56. package/src/methods/requests/hostRequestResponse.ts +153 -0
  57. package/src/methods/settings/modifySettings.ts +17 -17
  58. package/src/methods/socketReceive/allMembers.ts +450 -0
  59. package/src/methods/socketReceive/allMembersRest.ts +480 -0
  60. package/src/methods/socketReceive/allWaitingRoomMembers.ts +35 -0
  61. package/src/methods/socketReceive/banParticipant.ts +73 -0
  62. package/src/methods/socketReceive/controlMediaHost.ts +280 -0
  63. package/src/methods/socketReceive/disconnect.ts +40 -0
  64. package/src/methods/socketReceive/disconnectUserSelf.ts +56 -0
  65. package/src/methods/socketReceive/getDomains.ts +112 -0
  66. package/src/methods/socketReceive/meetingEnded.ts +49 -0
  67. package/src/methods/socketReceive/meetingStillThere.ts +26 -0
  68. package/src/methods/socketReceive/panelistReceiveMethods.ts +195 -0
  69. package/src/methods/socketReceive/participantRequested.ts +48 -0
  70. package/src/methods/socketReceive/permissionReceiveMethods.ts +59 -0
  71. package/src/methods/socketReceive/personJoined.ts +35 -0
  72. package/src/methods/socketReceive/producerMediaClosed.ts +223 -0
  73. package/src/methods/socketReceive/producerMediaPaused.ts +267 -0
  74. package/src/methods/socketReceive/producerMediaResumed.ts +157 -0
  75. package/src/methods/socketReceive/reInitiateRecording.ts +53 -0
  76. package/src/methods/socketReceive/receiveMessage.ts +117 -0
  77. package/src/methods/socketReceive/recordingNotice.ts +286 -0
  78. package/src/methods/socketReceive/roomRecordParams.ts +122 -0
  79. package/src/methods/socketReceive/screenProducerId.ts +61 -0
  80. package/src/methods/socketReceive/startRecords.ts +46 -0
  81. package/src/methods/socketReceive/stoppedRecording.ts +44 -0
  82. package/src/methods/socketReceive/translationReceiveMethods.ts +581 -0
  83. package/src/methods/socketReceive/updateConsumingDomains.ts +128 -0
  84. package/src/methods/socketReceive/updateMediaSettings.ts +45 -0
  85. package/src/methods/socketReceive/updatedCoHost.ts +75 -0
  86. package/src/methods/socketReceive/userWaiting.ts +45 -0
  87. package/src/methods/stream/clickAudio.ts +380 -0
  88. package/src/methods/stream/clickChat.ts +36 -0
  89. package/src/methods/stream/clickScreenShare.ts +173 -0
  90. package/src/methods/stream/clickVideo.ts +22 -5
  91. package/src/methods/stream/index.ts +1 -0
  92. package/src/methods/utils/SoundPlayer.ts +31 -0
  93. package/src/methods/utils/checkLimitsAndMakeRequest.ts +156 -2
  94. package/src/methods/utils/createResponseJoinRoom.ts +47 -0
  95. package/src/methods/utils/createRoomOnMediaSFU.ts +160 -0
  96. package/src/methods/utils/formatNumber.ts +42 -0
  97. package/src/methods/utils/generateRandomMessages.ts +70 -0
  98. package/src/methods/utils/generateRandomParticipants.ts +100 -0
  99. package/src/methods/utils/generateRandomPolls.ts +43 -0
  100. package/src/methods/utils/generateRandomRequestList.ts +51 -0
  101. package/src/methods/utils/generateRandomWaitingRoomList.ts +17 -0
  102. package/src/methods/utils/getModalPosition.ts +23 -0
  103. package/src/methods/utils/getOverlayPosition.ts +37 -0
  104. package/src/methods/utils/initialValuesState.ts +405 -0
  105. package/src/methods/utils/joinRoomOnMediaSFU.ts +124 -0
  106. package/src/methods/utils/liveSubtitle.ts +107 -0
  107. package/src/methods/utils/meetingTimeRemaining.ts +33 -0
  108. package/src/methods/utils/meetingTimer/startMeetingProgressTimer.ts +72 -0
  109. package/src/methods/utils/producer/aParams.ts +10 -0
  110. package/src/methods/utils/producer/hParams.ts +26 -0
  111. package/src/methods/utils/producer/screenParams.ts +13 -0
  112. package/src/methods/utils/producer/vParams.ts +26 -0
  113. package/src/methods/utils/producer/videoCaptureConstraints.ts +65 -0
  114. package/src/methods/utils/resolveMediaSFURoomApi.ts +16 -0
  115. package/src/methods/utils/sleep.ts +24 -0
  116. package/src/methods/utils/translationLanguages.ts +308 -0
  117. package/src/methods/utils/webrtc.ts +44 -0
  118. package/src/methods/welcome/handleWelcomeRequest.ts +11 -2
  119. package/src/methods/welcome/index.ts +5 -1
  120. package/src/methods/whiteboard/captureCanvasStream.ts +128 -0
  121. package/src/producers/producerEmits/joinConRoom.ts +2 -2
  122. package/src/producers/producerEmits/joinLocalRoom.ts +240 -0
  123. package/src/producers/producerEmits/joinRoom.ts +129 -0
  124. package/src/types/shared-base-types.ts +14 -3
  125. package/src/types/types.ts +255 -0
@@ -1,11 +1,37 @@
1
1
  import { Socket } from "socket.io-client";
2
2
  import { SignalNewConsumerTransportParameters, SignalNewConsumerTransportType } from '../types/types';
3
3
 
4
- export interface GetPipedProducersAltParameters extends SignalNewConsumerTransportParameters {
4
+ interface TranslationMeta {
5
+ speakerId: string;
6
+ speakerName: string;
7
+ language: string;
8
+ originalProducerId?: string;
9
+ isSpeakerControlled?: boolean;
10
+ }
11
+
12
+ interface ProducerInfo {
13
+ id: string;
14
+ translationMeta?: TranslationMeta | null;
15
+ }
16
+
17
+ export interface GetPipedProducersAltParameters extends Omit<SignalNewConsumerTransportParameters, 'getUpdatedAllParams'> {
5
18
  member: string;
6
19
 
20
+ listenerTranslationPreferences?: {
21
+ perSpeaker: Map<string, { speakerId: string; language: string | null; wantOriginal: boolean }>;
22
+ globalLanguage: string | null;
23
+ };
24
+
7
25
  // mediasfu functions
8
26
  signalNewConsumerTransport: SignalNewConsumerTransportType;
27
+ startConsumingTranslation?: (
28
+ producerId: string,
29
+ speakerId: string,
30
+ language: string,
31
+ originalProducerId?: string,
32
+ nsock?: Socket
33
+ ) => Promise<void>;
34
+ getUpdatedAllParams?: () => GetPipedProducersAltParameters;
9
35
  [key: string]: any;
10
36
  }
11
37
 
@@ -19,6 +45,41 @@ export interface GetPipedProducersAltOptions {
19
45
  // Export the type definition for the function
20
46
  export type GetPipedProducersAltType = (options: GetPipedProducersAltOptions) => Promise<void>;
21
47
 
48
+ const shouldConsumeTranslationProducer = (
49
+ translationMeta: TranslationMeta,
50
+ listenerTranslationPreferences?: {
51
+ perSpeaker: Map<string, { speakerId: string; language: string | null; wantOriginal: boolean }>;
52
+ globalLanguage: string | null;
53
+ }
54
+ ): boolean => {
55
+ const normalizedLang = translationMeta.language?.toLowerCase();
56
+ const speakerId = translationMeta.speakerId;
57
+ const isSpeakerControlled = translationMeta.isSpeakerControlled === true;
58
+
59
+ if (listenerTranslationPreferences) {
60
+ const perSpeakerPref = listenerTranslationPreferences.perSpeaker?.get(speakerId);
61
+ if (perSpeakerPref) {
62
+ if (perSpeakerPref.wantOriginal) {
63
+ return false;
64
+ }
65
+ if (perSpeakerPref.language) {
66
+ return perSpeakerPref.language.toLowerCase() === normalizedLang;
67
+ }
68
+ }
69
+
70
+ const globalPref = listenerTranslationPreferences.globalLanguage;
71
+ if (globalPref) {
72
+ return globalPref.toLowerCase() === normalizedLang;
73
+ }
74
+ }
75
+
76
+ if (isSpeakerControlled) {
77
+ return true;
78
+ }
79
+
80
+ return false;
81
+ };
82
+
22
83
  /**
23
84
  * Retrieves piped producers and signals new consumer transport for each retrieved producer.
24
85
  *
@@ -63,8 +124,14 @@ export const getPipedProducersAlt = async ({
63
124
  parameters,
64
125
  }: GetPipedProducersAltOptions): Promise<void> => {
65
126
  try {
66
- // Destructure parameters
67
- const { member, signalNewConsumerTransport } = parameters;
127
+ const freshParams = parameters.getUpdatedAllParams ? parameters.getUpdatedAllParams() : parameters;
128
+
129
+ const {
130
+ member,
131
+ signalNewConsumerTransport,
132
+ startConsumingTranslation,
133
+ listenerTranslationPreferences,
134
+ } = freshParams;
68
135
 
69
136
  const emitName = community ? "getProducersAlt" : "getProducersPipedAlt";
70
137
 
@@ -72,20 +139,49 @@ export const getPipedProducersAlt = async ({
72
139
  await nsock.emit(
73
140
  emitName,
74
141
  { islevel, member },
75
- async (producerIds: string[]) => {
142
+ async (producers: (string | ProducerInfo)[]) => {
76
143
  // Check if producers are retrieved
77
- if (producerIds.length > 0) {
78
- // Signal new consumer transport for each retrieved producer
79
- await Promise.all(
80
- producerIds.map((id) =>
81
- signalNewConsumerTransport({
82
- nsock,
83
- remoteProducerId: id,
84
- islevel,
85
- parameters,
86
- })
87
- )
88
- );
144
+ if (producers.length > 0) {
145
+ for (const producer of producers) {
146
+ let producerId: string;
147
+ let translationMeta: TranslationMeta | null = null;
148
+
149
+ if (typeof producer === 'string') {
150
+ producerId = producer;
151
+ } else {
152
+ producerId = producer.id;
153
+ translationMeta = producer.translationMeta || null;
154
+ }
155
+
156
+ if (translationMeta) {
157
+ const shouldConsume = shouldConsumeTranslationProducer(
158
+ translationMeta,
159
+ listenerTranslationPreferences,
160
+ );
161
+
162
+ if (!shouldConsume) {
163
+ continue;
164
+ }
165
+
166
+ if (startConsumingTranslation) {
167
+ await startConsumingTranslation(
168
+ producerId,
169
+ translationMeta.speakerId,
170
+ translationMeta.language,
171
+ translationMeta.originalProducerId,
172
+ nsock,
173
+ );
174
+ continue;
175
+ }
176
+ }
177
+
178
+ await signalNewConsumerTransport({
179
+ nsock,
180
+ remoteProducerId: producerId,
181
+ islevel,
182
+ parameters: freshParams as unknown as SignalNewConsumerTransportParameters,
183
+ });
184
+ }
89
185
  }
90
186
  }
91
187
  );
@@ -0,0 +1,42 @@
1
+ export interface GridPlanEntry<T> {
2
+ stream: T;
3
+ index: number;
4
+ }
5
+
6
+ export interface BuildAddVideosGridPlanOptions<T> {
7
+ mainGridStreams: T[];
8
+ altGridStreams: T[];
9
+ numToAdd?: number;
10
+ }
11
+
12
+ export interface AddVideosGridPlan<T> {
13
+ mainEntries: GridPlanEntry<T>[];
14
+ altEntries: GridPlanEntry<T>[];
15
+ }
16
+
17
+ /**
18
+ * Pure planning engine for addVideosGrid.
19
+ * Produces deterministic main/alt stream iteration plans without rendering concerns.
20
+ */
21
+ export function buildAddVideosGridPlan<T>({
22
+ mainGridStreams,
23
+ altGridStreams,
24
+ numToAdd,
25
+ }: BuildAddVideosGridPlanOptions<T>): AddVideosGridPlan<T> {
26
+ const safeMain = Array.isArray(mainGridStreams) ? mainGridStreams : [];
27
+ const safeAlt = Array.isArray(altGridStreams) ? altGridStreams : [];
28
+
29
+ const bounded = Math.max(0, Math.min(numToAdd ?? safeMain.length, safeMain.length));
30
+
31
+ const mainEntries = safeMain.slice(0, bounded).map((stream, index) => ({
32
+ stream,
33
+ index,
34
+ }));
35
+
36
+ const altEntries = safeAlt.map((stream, index) => ({
37
+ stream,
38
+ index,
39
+ }));
40
+
41
+ return { mainEntries, altEntries };
42
+ }
@@ -0,0 +1,444 @@
1
+ export interface PrepopulateParticipantLike {
2
+ name?: string;
3
+ islevel?: string;
4
+ ScreenID?: string;
5
+ ScreenOn?: boolean;
6
+ [key: string]: any;
7
+ }
8
+
9
+ export interface PrepopulateStreamLike {
10
+ producerId?: string;
11
+ stream?: any;
12
+ [key: string]: any;
13
+ }
14
+
15
+ export interface BuildPrepopulateUserMediaPlanOptions<
16
+ P extends PrepopulateParticipantLike,
17
+ S extends PrepopulateStreamLike,
18
+ > {
19
+ participants: P[];
20
+ allVideoStreams: S[];
21
+ member: string;
22
+ islevel: string;
23
+ shared: boolean;
24
+ shareScreenStarted: boolean;
25
+ eventType: string;
26
+ screenId?: string;
27
+ whiteboardStarted: boolean;
28
+ whiteboardEnded: boolean;
29
+ remoteScreenStream: S[];
30
+ localStreamScreen: any;
31
+ checkOrientation: () => string;
32
+ isWideScreen: boolean;
33
+ forceFullDisplay: boolean;
34
+ includeWhiteboardAsScreenFlow?: boolean;
35
+ }
36
+
37
+ export interface PrepopulateUserMediaPlan<P extends PrepopulateParticipantLike> {
38
+ screenFlowActive: boolean;
39
+ shouldReturnEarly: boolean;
40
+ shouldUpdateAdminOnMainScreen: boolean;
41
+ screenForceFullDisplay: boolean;
42
+ host: P | null;
43
+ hostStream: any;
44
+ adminOnMainScreen: boolean;
45
+ mainScreenPerson: string;
46
+ }
47
+
48
+ export interface ResolveMainHostRenderModeOptions {
49
+ islevel: string;
50
+ localUIMode: boolean;
51
+ videoAlreadyOn: boolean;
52
+ audioAlreadyOn: boolean;
53
+ hostVideoOn: boolean;
54
+ hostMuted?: boolean;
55
+ }
56
+
57
+ export type MainHostRenderMode = 'adminVideo' | 'audio' | 'mini' | 'video';
58
+
59
+ export interface ResolveHostVideoStreamOptions<TStreamLike = PrepopulateStreamLike> {
60
+ islevel: string;
61
+ keepBackground: boolean;
62
+ virtualStream: any;
63
+ localStreamVideo: any;
64
+ oldAllStreams: TStreamLike[];
65
+ hostVideoID?: string;
66
+ }
67
+
68
+ export interface BuildMainScreenStateOptions {
69
+ filled: boolean;
70
+ adminOnMainScreen: boolean;
71
+ mainScreenPerson: string;
72
+ }
73
+
74
+ export interface MainScreenState {
75
+ filled: boolean;
76
+ adminOnMainScreen: boolean;
77
+ mainScreenPerson: string;
78
+ }
79
+
80
+ export interface BuildMainHostCardPlanOptions<TStreamLike = PrepopulateStreamLike> {
81
+ islevel: string;
82
+ localUIMode: boolean;
83
+ videoAlreadyOn: boolean;
84
+ audioAlreadyOn: boolean;
85
+ hostVideoOn: boolean;
86
+ hostMuted?: boolean;
87
+ hostIsAdmin: boolean;
88
+ hostName: string;
89
+ hostVideoID?: string;
90
+ fallbackName: string;
91
+ member: string;
92
+ keepBackground: boolean;
93
+ virtualStream: any;
94
+ localStreamVideo: any;
95
+ oldAllStreams: TStreamLike[];
96
+ }
97
+
98
+ export interface MainHostCardPlan {
99
+ kind: 'video' | 'audio' | 'mini';
100
+ key: string;
101
+ name: string;
102
+ initials?: string;
103
+ remoteProducerId?: string;
104
+ videoStream?: any;
105
+ doMirror?: boolean;
106
+ state: MainScreenState;
107
+ }
108
+
109
+ export interface BuildScreenShareHostCardPlanOptions {
110
+ hostName: string;
111
+ hostScreenID?: string;
112
+ hostIsAdmin: boolean;
113
+ shared: boolean;
114
+ hostStream: any;
115
+ screenForceFullDisplay: boolean;
116
+ annotateScreenStream: boolean;
117
+ }
118
+
119
+ export interface ScreenShareHostCardPlan {
120
+ key: string;
121
+ name: string;
122
+ remoteProducerId: string;
123
+ videoStream: any;
124
+ forceFullDisplay: boolean;
125
+ doMirror: false;
126
+ state: MainScreenState;
127
+ }
128
+
129
+ /**
130
+ * Pure helper for resolving which non-screen-share host card mode should render.
131
+ */
132
+ export function resolveMainHostRenderMode({
133
+ islevel,
134
+ localUIMode,
135
+ videoAlreadyOn,
136
+ audioAlreadyOn,
137
+ hostVideoOn,
138
+ hostMuted,
139
+ }: ResolveMainHostRenderModeOptions): MainHostRenderMode {
140
+ const hostVideoOffPath =
141
+ (islevel !== '2' && !hostVideoOn)
142
+ || (islevel === '2' && (!hostVideoOn || !videoAlreadyOn))
143
+ || localUIMode === true;
144
+
145
+ if (!hostVideoOffPath) {
146
+ return 'video';
147
+ }
148
+
149
+ if (islevel === '2' && videoAlreadyOn) {
150
+ return 'adminVideo';
151
+ }
152
+
153
+ const audOn =
154
+ (islevel === '2' && audioAlreadyOn)
155
+ || (islevel !== '2' && hostMuted === false);
156
+
157
+ return audOn ? 'audio' : 'mini';
158
+ }
159
+
160
+ /**
161
+ * Resolves the correct host video stream source for non-screen-share rendering.
162
+ */
163
+ export function resolveHostVideoStream<TStreamLike extends PrepopulateStreamLike = PrepopulateStreamLike>({
164
+ islevel,
165
+ keepBackground,
166
+ virtualStream,
167
+ localStreamVideo,
168
+ oldAllStreams,
169
+ hostVideoID,
170
+ }: ResolveHostVideoStreamOptions<TStreamLike>): any {
171
+ if (islevel === '2') {
172
+ return keepBackground && virtualStream ? virtualStream : localStreamVideo;
173
+ }
174
+
175
+ const safeOldStreams = Array.isArray(oldAllStreams) ? oldAllStreams : [];
176
+ const matched = safeOldStreams.find((stream) => stream.producerId === hostVideoID);
177
+ return matched?.stream ?? null;
178
+ }
179
+
180
+ /**
181
+ * Pure helper for normalizing main-screen state updates.
182
+ */
183
+ export function buildMainScreenState({
184
+ filled,
185
+ adminOnMainScreen,
186
+ mainScreenPerson,
187
+ }: BuildMainScreenStateOptions): MainScreenState {
188
+ return {
189
+ filled,
190
+ adminOnMainScreen,
191
+ mainScreenPerson,
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Pure helper for resolving the non-screen-share host card to render on the main screen.
197
+ */
198
+ export function buildMainHostCardPlan<TStreamLike extends PrepopulateStreamLike = PrepopulateStreamLike>({
199
+ islevel,
200
+ localUIMode,
201
+ videoAlreadyOn,
202
+ audioAlreadyOn,
203
+ hostVideoOn,
204
+ hostMuted,
205
+ hostIsAdmin,
206
+ hostName,
207
+ hostVideoID,
208
+ fallbackName,
209
+ member,
210
+ keepBackground,
211
+ virtualStream,
212
+ localStreamVideo,
213
+ oldAllStreams,
214
+ }: BuildMainHostCardPlanOptions<TStreamLike>): MainHostCardPlan {
215
+ const mode = resolveMainHostRenderMode({
216
+ islevel,
217
+ localUIMode,
218
+ videoAlreadyOn,
219
+ audioAlreadyOn,
220
+ hostVideoOn,
221
+ hostMuted,
222
+ });
223
+
224
+ if (mode === 'adminVideo') {
225
+ return {
226
+ kind: 'video',
227
+ key: hostVideoID || hostName || 'host-video',
228
+ name: hostName,
229
+ remoteProducerId: hostVideoID || '',
230
+ videoStream: keepBackground && virtualStream ? virtualStream : localStreamVideo,
231
+ doMirror: true,
232
+ state: buildMainScreenState({
233
+ filled: true,
234
+ adminOnMainScreen: true,
235
+ mainScreenPerson: hostName,
236
+ }),
237
+ };
238
+ }
239
+
240
+ if (mode === 'audio') {
241
+ return {
242
+ kind: 'audio',
243
+ key: hostName || fallbackName,
244
+ name: hostName,
245
+ state: buildMainScreenState({
246
+ filled: true,
247
+ adminOnMainScreen: islevel === '2',
248
+ mainScreenPerson: hostName,
249
+ }),
250
+ };
251
+ }
252
+
253
+ if (mode === 'mini') {
254
+ return {
255
+ kind: 'mini',
256
+ key: fallbackName,
257
+ name: hostName,
258
+ initials: fallbackName,
259
+ state: buildMainScreenState({
260
+ filled: false,
261
+ adminOnMainScreen: islevel === '2',
262
+ mainScreenPerson: hostName,
263
+ }),
264
+ };
265
+ }
266
+
267
+ const hostStream = resolveHostVideoStream({
268
+ islevel,
269
+ keepBackground,
270
+ virtualStream,
271
+ localStreamVideo,
272
+ oldAllStreams,
273
+ hostVideoID,
274
+ });
275
+
276
+ if (!hostStream) {
277
+ return {
278
+ kind: 'mini',
279
+ key: fallbackName,
280
+ name: hostName,
281
+ initials: fallbackName,
282
+ state: buildMainScreenState({
283
+ filled: false,
284
+ adminOnMainScreen: islevel === '2',
285
+ mainScreenPerson: hostName,
286
+ }),
287
+ };
288
+ }
289
+
290
+ return {
291
+ kind: 'video',
292
+ key: hostVideoID || hostName || 'host-video',
293
+ name: hostName,
294
+ remoteProducerId: hostVideoID || '',
295
+ videoStream: hostStream,
296
+ doMirror: member === hostName,
297
+ state: buildMainScreenState({
298
+ filled: true,
299
+ adminOnMainScreen: hostIsAdmin,
300
+ mainScreenPerson: hostName,
301
+ }),
302
+ };
303
+ }
304
+
305
+ /**
306
+ * Pure helper for resolving the screen-share host card to render on the main screen.
307
+ */
308
+ export function buildScreenShareHostCardPlan({
309
+ hostName,
310
+ hostScreenID,
311
+ hostIsAdmin,
312
+ shared,
313
+ hostStream,
314
+ screenForceFullDisplay,
315
+ annotateScreenStream,
316
+ }: BuildScreenShareHostCardPlanOptions): ScreenShareHostCardPlan {
317
+ return {
318
+ key: hostScreenID || hostName || 'host-screen',
319
+ name: hostName,
320
+ remoteProducerId: hostScreenID || '',
321
+ videoStream: shared ? hostStream : hostStream?.stream ?? null,
322
+ forceFullDisplay: annotateScreenStream && shared ? false : screenForceFullDisplay,
323
+ doMirror: false,
324
+ state: buildMainScreenState({
325
+ filled: true,
326
+ adminOnMainScreen: hostIsAdmin,
327
+ mainScreenPerson: hostName,
328
+ }),
329
+ };
330
+ }
331
+
332
+ /**
333
+ * Pure planning engine for prepopulateUserMedia host/screen-flow resolution.
334
+ * Returns deterministic state decisions without rendering concerns.
335
+ */
336
+ export function buildPrepopulateUserMediaPlan<
337
+ P extends PrepopulateParticipantLike,
338
+ S extends PrepopulateStreamLike,
339
+ >({
340
+ participants,
341
+ allVideoStreams,
342
+ member,
343
+ shared,
344
+ shareScreenStarted,
345
+ eventType,
346
+ screenId,
347
+ whiteboardStarted,
348
+ whiteboardEnded,
349
+ remoteScreenStream,
350
+ localStreamScreen,
351
+ checkOrientation,
352
+ isWideScreen,
353
+ forceFullDisplay,
354
+ includeWhiteboardAsScreenFlow = false,
355
+ }: BuildPrepopulateUserMediaPlanOptions<P, S>): PrepopulateUserMediaPlan<P> {
356
+ const safeParticipants = Array.isArray(participants) ? participants : [];
357
+ const safeVideoStreams = Array.isArray(allVideoStreams) ? allVideoStreams : [];
358
+ const safeRemoteScreenStreams = Array.isArray(remoteScreenStream) ? remoteScreenStream : [];
359
+
360
+ const whiteboardActive = whiteboardStarted && !whiteboardEnded;
361
+ const screenFlowActive =
362
+ shareScreenStarted || shared || (includeWhiteboardAsScreenFlow && whiteboardActive);
363
+
364
+ let screenForceFullDisplay = forceFullDisplay;
365
+ const orientation = checkOrientation();
366
+ if ((orientation === 'portrait' || !isWideScreen) && (shareScreenStarted || shared)) {
367
+ screenForceFullDisplay = false;
368
+ }
369
+
370
+ if (!screenFlowActive) {
371
+ if (eventType === 'conference') {
372
+ return {
373
+ screenFlowActive,
374
+ shouldReturnEarly: true,
375
+ shouldUpdateAdminOnMainScreen: false,
376
+ screenForceFullDisplay,
377
+ host: null,
378
+ hostStream: null,
379
+ adminOnMainScreen: false,
380
+ mainScreenPerson: '',
381
+ };
382
+ }
383
+
384
+ const host = safeParticipants.find((participant) => participant.islevel === '2') ?? null;
385
+
386
+ return {
387
+ screenFlowActive,
388
+ shouldReturnEarly: false,
389
+ shouldUpdateAdminOnMainScreen: false,
390
+ screenForceFullDisplay,
391
+ host,
392
+ hostStream: null,
393
+ adminOnMainScreen: false,
394
+ mainScreenPerson: host?.name ?? '',
395
+ };
396
+ }
397
+
398
+ let host: P | null = null;
399
+ let hostStream: any = null;
400
+
401
+ if (shared) {
402
+ host = { name: member, audioID: '', videoID: '' } as unknown as P;
403
+ hostStream = localStreamScreen;
404
+ } else {
405
+ host =
406
+ safeParticipants.find(
407
+ (participant) => participant.ScreenID === screenId && participant.ScreenOn === true,
408
+ ) ?? null;
409
+
410
+ if (whiteboardActive) {
411
+ host = {
412
+ name: 'WhiteboardActive',
413
+ islevel: '2',
414
+ audioID: '',
415
+ videoID: '',
416
+ } as unknown as P;
417
+ hostStream = { producerId: 'WhiteboardActive' };
418
+ }
419
+
420
+ if (host === null) {
421
+ host = safeParticipants.find((participant) => participant.ScreenOn === true) ?? null;
422
+ }
423
+
424
+ if (host && !String(host.name ?? '').includes('WhiteboardActive')) {
425
+ if (safeRemoteScreenStreams.length === 0) {
426
+ hostStream =
427
+ safeVideoStreams.find((stream) => stream.producerId === host?.ScreenID) ?? null;
428
+ } else {
429
+ hostStream = safeRemoteScreenStreams[0];
430
+ }
431
+ }
432
+ }
433
+
434
+ return {
435
+ screenFlowActive,
436
+ shouldReturnEarly: false,
437
+ shouldUpdateAdminOnMainScreen: true,
438
+ screenForceFullDisplay,
439
+ host,
440
+ hostStream,
441
+ adminOnMainScreen: (host && host.islevel === '2') ?? false,
442
+ mainScreenPerson: host?.name ?? '',
443
+ };
444
+ }