agora-appbuilder-core 4.0.26 → 4.0.27

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.
@@ -149,4 +149,5 @@ export interface IconsInterface {
149
149
  'play-circle': string;
150
150
  'copy-link': string;
151
151
  'recording-status': string;
152
+ fullscreen: string;
152
153
  }
@@ -77,17 +77,21 @@ const GridVideo: LayoutComponent = ({renderData}) => {
77
77
  disabled={renderData.length === 1}
78
78
  onPress={() => {
79
79
  //if (!(ridx === 0 && cidx === 0)) {
80
- const currentUid = renderData[ridx * dims.c + cidx];
81
- if (
82
- currentUid !== pinnedUid &&
83
- currentUid !== secondaryPinnedUid
84
- ) {
85
- dispatch({
86
- type: 'ActiveSpeaker',
87
- value: [renderData[ridx * dims.c + cidx]],
88
- });
89
- }
80
+ //const currentUid = renderData[ridx * dims.c + cidx];
81
+ // if (
82
+ // currentUid !== pinnedUid &&
83
+ // currentUid !== secondaryPinnedUid
84
+ // ) {
85
+ // dispatch({
86
+ // type: 'ActiveSpeaker',
87
+ // value: [renderData[ridx * dims.c + cidx]],
88
+ // });
89
+ // }
90
90
  //}
91
+ dispatch({
92
+ type: 'UserPin',
93
+ value: [renderData[ridx * dims.c + cidx]],
94
+ });
91
95
  setPinnedLayout();
92
96
  }}
93
97
  style={{
@@ -20,7 +20,7 @@ import {
20
20
  Text,
21
21
  } from 'react-native';
22
22
  import {layoutProps} from '../../theme.json';
23
- import {useContent} from 'customization-api';
23
+ import {useContent, useLocalUserInfo} from 'customization-api';
24
24
  import RenderComponent from '../pages/video-call/RenderComponent';
25
25
  import IconButton from '../atoms/IconButton';
26
26
  import hexadecimalTransparency from '../utils/hexadecimalTransparency';
@@ -36,9 +36,12 @@ import {
36
36
  moreBtnRemoveFromLarge,
37
37
  videoRoomGoToActiveSpeakerText,
38
38
  } from '../language/default-labels/videoCallScreenLabels';
39
+ import {useFullScreen} from '..//utils/useFullScreen';
39
40
  const {topPinned} = layoutProps;
40
41
 
41
42
  const PinnedVideo = ({renderData}) => {
43
+ const {screenUid} = useLocalUserInfo();
44
+ const {requestFullscreen} = useFullScreen();
42
45
  const removeFromLargeText = useString(moreBtnRemoveFromLarge)();
43
46
  const goToASText = useString(videoRoomGoToActiveSpeakerText)();
44
47
  const [isOnTop, setIsOnTop] = useState(true);
@@ -52,6 +55,7 @@ const PinnedVideo = ({renderData}) => {
52
55
  const {dispatch} = useContext(DispatchContext);
53
56
  const {videoTileInViewPortState} = useVideoCall();
54
57
  const {getWhiteboardUid = () => 0} = useWhiteboard();
58
+ const {defaultContent, customContent} = useContent();
55
59
  useEffect(() => {
56
60
  if (activeSpeaker && !videoTileInViewPortState[activeSpeaker] && isOnTop) {
57
61
  dispatch({
@@ -80,6 +84,14 @@ const PinnedVideo = ({renderData}) => {
80
84
  !isOnTop && setIsOnTop(true);
81
85
  }
82
86
  };
87
+
88
+ const isWhiteboard = customContent && customContent[pinnedUid || maxUid];
89
+ const isRemoteScreenshare =
90
+ (pinnedUid || maxUid) != screenUid &&
91
+ defaultContent &&
92
+ defaultContent[pinnedUid || maxUid]?.video &&
93
+ defaultContent[pinnedUid || maxUid]?.type === 'screenshare';
94
+
83
95
  return (
84
96
  <View
85
97
  style={{
@@ -248,7 +260,7 @@ const PinnedVideo = ({renderData}) => {
248
260
  : style.flex8
249
261
  }>
250
262
  <View style={style.flex1} key={'maxVideo' + (pinnedUid || maxUid)}>
251
- {isSidePinnedlayout && (
263
+ {isSidePinnedlayout && (isWhiteboard || isRemoteScreenshare) ? (
252
264
  <IconButton
253
265
  containerStyle={{
254
266
  position: 'absolute',
@@ -257,7 +269,16 @@ const PinnedVideo = ({renderData}) => {
257
269
  zIndex: 999,
258
270
  elevation: 999,
259
271
  }}
260
- onPress={() => setCollapse(!collapse)}
272
+ onPress={() => {
273
+ if (
274
+ defaultContent &&
275
+ defaultContent[pinnedUid || maxUid]?.video
276
+ ) {
277
+ requestFullscreen(pinnedUid || maxUid);
278
+ } else if (customContent[pinnedUid || maxUid]) {
279
+ setCollapse(!collapse);
280
+ }
281
+ }}
261
282
  iconProps={{
262
283
  iconContainerStyle: {
263
284
  padding: 8,
@@ -265,13 +286,19 @@ const PinnedVideo = ({renderData}) => {
265
286
  $config.CARD_LAYER_5_COLOR +
266
287
  hexadecimalTransparency['10%'],
267
288
  },
268
- name: collapse ? 'collapse' : 'expand',
289
+ name: isRemoteScreenshare
290
+ ? 'fullscreen'
291
+ : isWhiteboard && collapse
292
+ ? 'collapse'
293
+ : 'expand',
269
294
  tintColor: $config.SECONDARY_ACTION_COLOR,
270
295
  iconSize: 24,
271
296
  }}
272
297
  />
298
+ ) : (
299
+ <></>
273
300
  )}
274
- {pinnedUid && pinnedUid !== getWhiteboardUid() ? (
301
+ {/* {pinnedUid && pinnedUid !== getWhiteboardUid() ? (
275
302
  <IconButton
276
303
  containerStyle={{
277
304
  paddingHorizontal: 8,
@@ -310,7 +337,7 @@ const PinnedVideo = ({renderData}) => {
310
337
  />
311
338
  ) : (
312
339
  <></>
313
- )}
340
+ )} */}
314
341
  {/** Render the maximized view */}
315
342
  <RenderComponent uid={pinnedUid || maxUid} isMax={true} />
316
343
  </View>
@@ -39,7 +39,7 @@ import {useScreenshare} from '../../subComponents/screenshare/useScreenshare';
39
39
  import {useFocus} from '../../utils/useFocus';
40
40
  import Toast from '../../../react-native-toast-message';
41
41
  import RemoteMutePopup from '../../subComponents/RemoteMutePopup';
42
- import {calculatePosition, trimText} from '../../utils/common';
42
+ import {calculatePosition, isMobileUA, trimText} from '../../utils/common';
43
43
  import {useVideoCall} from '../useVideoCall';
44
44
  import {customEvents} from 'customization-api';
45
45
  import {useDisableChat} from '../disable-chat/useDisableChat';
@@ -63,6 +63,7 @@ import {
63
63
  moreBtnViewWhiteboard,
64
64
  userRemovedFromTheRoomToastHeading,
65
65
  } from '../../language/default-labels/videoCallScreenLabels';
66
+ import {isAndroid, isIOS} from '../../utils/common';
66
67
 
67
68
  interface UserActionMenuOptionsOptionsProps {
68
69
  user: ContentInterface;
@@ -154,32 +155,33 @@ export default function UserActionMenuOptionsOptions(
154
155
  )
155
156
  ) {
156
157
  if (enablePinForMe) {
157
- //if (pinnedUid !== user.uid) {
158
- items.push({
159
- //disabled: activeUids?.filter(i => !customContent[i])?.length === 1,
160
- disabled: activeUids?.length === 1,
161
- icon: pinnedUid ? 'unpin-outlined' : 'pin-outlined',
162
- onHoverIcon: pinnedUid ? 'unpin-outlined' : 'pin-filled',
163
- iconColor: $config.SECONDARY_ACTION_COLOR,
164
- textColor: $config.SECONDARY_ACTION_COLOR,
165
- title: pinnedUid
166
- ? user.uid === pinnedUid
167
- ? removeFromLargeLabel
158
+ if (pinnedUid !== user.uid) {
159
+ items.push({
160
+ //disabled: activeUids?.filter(i => !customContent[i])?.length === 1,
161
+ disabled: activeUids?.length === 1,
162
+ icon: pinnedUid ? 'unpin-outlined' : 'pin-outlined',
163
+ onHoverIcon: pinnedUid ? 'unpin-outlined' : 'pin-filled',
164
+ iconColor: $config.SECONDARY_ACTION_COLOR,
165
+ textColor: $config.SECONDARY_ACTION_COLOR,
166
+ title: pinnedUid
167
+ ? user.uid === pinnedUid
168
+ ? removeFromLargeLabel
169
+ : user.uid === getWhiteboardUid()
170
+ ? viewWhiteboardLabel
171
+ : viewInLargeLabel
168
172
  : user.uid === getWhiteboardUid()
169
173
  ? viewWhiteboardLabel
170
- : viewInLargeLabel
171
- : user.uid === getWhiteboardUid()
172
- ? viewWhiteboardLabel
173
- : viewInLargeLabel,
174
- callback: () => {
175
- setActionMenuVisible(false);
176
- dispatch({
177
- type: 'UserPin',
178
- value: [pinnedUid && user.uid === pinnedUid ? 0 : user.uid],
179
- });
180
- setLayout(getPinnedLayoutName());
181
- },
182
- });
174
+ : viewInLargeLabel,
175
+ callback: () => {
176
+ setActionMenuVisible(false);
177
+ dispatch({
178
+ type: 'UserPin',
179
+ value: [pinnedUid && user.uid === pinnedUid ? 0 : user.uid],
180
+ });
181
+ setLayout(getPinnedLayoutName());
182
+ },
183
+ });
184
+ }
183
185
  if (currentLayout === DefaultLayouts[1].name) {
184
186
  items.push({
185
187
  // disabled:
@@ -207,7 +209,6 @@ export default function UserActionMenuOptionsOptions(
207
209
  },
208
210
  });
209
211
  }
210
- //}
211
212
  }
212
213
  }
213
214
 
@@ -80,7 +80,8 @@ type LogType = {
80
80
  | 'RECORDING'
81
81
  | 'STORE'
82
82
  | 'GET_MEETING_PHRASE'
83
- | 'MUTE_PSTN';
83
+ | 'MUTE_PSTN'
84
+ | 'FULL_SCREEN';
84
85
  [LogSource.NetworkRest]:
85
86
  | 'idp_login'
86
87
  | 'token_login'
@@ -473,12 +473,8 @@ const ActionSheetContent = props => {
473
473
  ([_, v]) => !isHidden(v?.hide) && v?.component,
474
474
  );
475
475
 
476
- console.log('debugging displayItems', displayItems);
477
-
478
476
  const displayItemsOrdered = CustomToolbarSorting(displayItems);
479
477
 
480
- console.log('debugging displayItemsOrdered', displayItemsOrdered);
481
-
482
478
  if (displayCustomBottomSheetContent) {
483
479
  return <View>{customBottomSheetContent}</View>;
484
480
  }
@@ -42,7 +42,12 @@ import WhiteboardView from '../../components/whiteboard/WhiteboardView';
42
42
  const VideoCallMobileView = props => {
43
43
  const {native = true} = props;
44
44
  const {customContent} = useContent();
45
- const {isScreenShareOnFullView, screenShareData} = useScreenContext();
45
+ const {
46
+ isScreenShareOnFullView,
47
+ screenShareData,
48
+ setScreenShareData,
49
+ setScreenShareOnFullView,
50
+ } = useScreenContext();
46
51
  const {getWhiteboardUid, isWhiteboardOnFullScreen} = useWhiteboard();
47
52
 
48
53
  const {RtcEngineUnsafe} = useContext(RtcContext);
@@ -66,6 +71,25 @@ const VideoCallMobileView = props => {
66
71
  const isVBAvaialble =
67
72
  $config.ENABLE_VIRTUAL_BACKGROUND && !$config.AUDIO_ROOM && isVBActive;
68
73
 
74
+ useEffect(() => {
75
+ if (
76
+ isScreenShareOnFullView &&
77
+ defaultContent &&
78
+ !defaultContent[maxScreenShareUid]?.video
79
+ ) {
80
+ setScreenShareOnFullView(false);
81
+ setScreenShareData(prevState => {
82
+ return {
83
+ ...prevState,
84
+ [maxScreenShareUid]: {
85
+ ...prevState[maxScreenShareUid],
86
+ isExpanded: !prevState[maxScreenShareUid]?.isExpanded,
87
+ },
88
+ };
89
+ });
90
+ }
91
+ }, [defaultContent, maxScreenShareUid, isScreenShareOnFullView]);
92
+
69
93
  useEffect(() => {
70
94
  // console.log(`Video State ${local.video} in Mode ${appStateVisible}`);
71
95
  //native screenshare use local uid to publish the screenshare stream
@@ -38,6 +38,7 @@ import {
38
38
  moreBtnViewWhiteboard,
39
39
  } from '../../language/default-labels/videoCallScreenLabels';
40
40
  import {LogSource, logger} from '../../logger/AppBuilderLogger';
41
+ import {useFullScreen} from '../../utils/useFullScreen';
41
42
  export interface VideoRendererProps {
42
43
  user: ContentInterface;
43
44
  isMax?: boolean;
@@ -49,6 +50,7 @@ const VideoRenderer: React.FC<VideoRendererProps> = ({
49
50
  CustomChild,
50
51
  }) => {
51
52
  const {height, width} = useWindowDimensions();
53
+ const {requestFullscreen} = useFullScreen();
52
54
  const {dispatch} = useContext(DispatchContext);
53
55
  const {RtcEngineUnsafe} = useRtc();
54
56
  const {pinnedUid, secondaryPinnedUid} = useContent();
@@ -141,6 +143,19 @@ const VideoRenderer: React.FC<VideoRendererProps> = ({
141
143
  const viewinlargeLabel = useString(moreBtnViewInLarge)();
142
144
  const viewwhiteboardlabel = useString(moreBtnViewWhiteboard)();
143
145
 
146
+ const setScreenShareFullScreen = () => {
147
+ setScreenShareOnFullView(!screenShareData[user.uid]?.isExpanded);
148
+ setScreenShareData(prevState => {
149
+ return {
150
+ ...prevState,
151
+ [user.uid]: {
152
+ ...prevState[user.uid],
153
+ isExpanded: !prevState[user.uid]?.isExpanded,
154
+ },
155
+ };
156
+ });
157
+ };
158
+
144
159
  return (
145
160
  <>
146
161
  <UserActionMenuOptionsOptions
@@ -231,7 +246,8 @@ const VideoRenderer: React.FC<VideoRendererProps> = ({
231
246
  pinnedUid &&
232
247
  pinnedUid == user.uid &&
233
248
  !isScreenShareOnFullView
234
- ? 160 + (isAndroid() ? 15 : 0)
249
+ ? //160 + (isAndroid() ? 15 : 0)
250
+ 8
235
251
  : 8,
236
252
  zIndex: 999,
237
253
  elevation: 999,
@@ -241,18 +257,13 @@ const VideoRenderer: React.FC<VideoRendererProps> = ({
241
257
  if (user?.type == 'whiteboard') {
242
258
  setWhiteboardOnFullScreen(!isWhiteboardOnFullScreen);
243
259
  } else {
244
- setScreenShareOnFullView(
245
- !screenShareData[user.uid]?.isExpanded,
246
- );
247
- setScreenShareData(prevState => {
248
- return {
249
- ...prevState,
250
- [user.uid]: {
251
- ...prevState[user.uid],
252
- isExpanded: !prevState[user.uid]?.isExpanded,
253
- },
254
- };
255
- });
260
+ if (isMobileUA() && !(isAndroid() || isIOS())) {
261
+ requestFullscreen(user.uid).catch(() => {
262
+ setScreenShareFullScreen();
263
+ });
264
+ } else {
265
+ setScreenShareFullScreen();
266
+ }
256
267
  }
257
268
  }}
258
269
  iconProps={{
@@ -0,0 +1,161 @@
1
+ import {useEffect, useRef} from 'react';
2
+ import {useContent, useLocalUserInfo} from 'customization-api';
3
+ import {UidType} from '../../agora-rn-uikit';
4
+ import {isWebInternal} from '../utils/common';
5
+ import {logger, LogSource} from '../logger/AppBuilderLogger';
6
+
7
+ export const useFullScreen = () => {
8
+ const {uid: localUid, screenUid: localScreenUid} = useLocalUserInfo();
9
+ const {defaultContent} = useContent();
10
+ const defaultContentRef = useRef(defaultContent);
11
+
12
+ useEffect(() => {
13
+ defaultContentRef.current = defaultContent;
14
+ }, [defaultContent]);
15
+
16
+ const onFullScreenChange = () => {
17
+ setTimeout(() => {
18
+ try {
19
+ if (
20
+ document.fullscreenElement &&
21
+ screen?.orientation?.type?.startsWith('portrait')
22
+ ) {
23
+ //@ts-ignore
24
+ screen?.orientation?.lock &&
25
+ //@ts-ignore
26
+ screen?.orientation?.lock('landscape')?.catch(e => {
27
+ console.error('debugging error on lock', e);
28
+ });
29
+ }
30
+ } catch (error) {
31
+ console.error('debugging error on onFullScreenChange', error);
32
+ }
33
+ }, 500);
34
+ };
35
+
36
+ useEffect(() => {
37
+ try {
38
+ document?.addEventListener('fullscreenchange', onFullScreenChange);
39
+ } catch (error) {}
40
+
41
+ return () => {
42
+ document?.removeEventListener('fullscreenchange', onFullScreenChange);
43
+ };
44
+ }, []);
45
+
46
+ const requestFullscreen = async (uid: UidType) => {
47
+ if (!document?.fullscreenEnabled) {
48
+ console.error('Full screen API - Not supported');
49
+ return Promise.reject(false);
50
+ }
51
+
52
+ if (!isWebInternal()) {
53
+ console.error('Full screen API - only available web platform');
54
+ return Promise.reject(false);
55
+ }
56
+
57
+ if (!uid) {
58
+ console.error("Full screen API - Uid can't be empty");
59
+ return Promise.reject(false);
60
+ }
61
+
62
+ let isVideoEnabled = defaultContentRef?.current[uid]?.video;
63
+
64
+ if (!isVideoEnabled) {
65
+ console.error(
66
+ `Full screen API - please enable video for uid - ${uid} before calling full screen api`,
67
+ );
68
+ return Promise.reject(false);
69
+ }
70
+
71
+ let fullScreenUid = uid === localUid ? 0 : uid === localScreenUid ? 1 : uid;
72
+
73
+ try {
74
+ /**
75
+ * Agora assign user uid in the video tag container element
76
+ * 0 - local user
77
+ * 1 - local screen
78
+ * actual user uid - for rest of remote stream
79
+ *
80
+ * agora assign uid to parent div of video element
81
+ * so that's reason we are access children element to get the video element
82
+ *
83
+ * we can requestFullScreen for parent div
84
+ * but it won't have auto rotate as soon fullscreen enabled and timer/exit full screen buttons
85
+ *
86
+ * Sample structure
87
+ * <div id="0" >
88
+ * <div>
89
+ * <video />
90
+ * </div>
91
+ * </div>
92
+ * */
93
+ const videoTag = document.getElementById(fullScreenUid?.toString())
94
+ ?.children[0]?.children[0];
95
+
96
+ if (!document?.fullscreenElement && videoTag?.requestFullscreen) {
97
+ videoTag
98
+ ?.requestFullscreen()
99
+ .then(() => {
100
+ logger.log(
101
+ LogSource.Internals,
102
+ 'FULL_SCREEN',
103
+ 'requestFullscreen success',
104
+ );
105
+ return Promise.resolve(true);
106
+ })
107
+ .catch(error => {
108
+ logger.error(
109
+ LogSource.Internals,
110
+ 'FULL_SCREEN',
111
+ 'requestFullscreen - error',
112
+ error,
113
+ );
114
+ return Promise.reject(false);
115
+ });
116
+ } else {
117
+ logger.error(
118
+ LogSource.Internals,
119
+ 'FULL_SCREEN',
120
+ 'requestFullscreen - error',
121
+ );
122
+ return Promise.reject(false);
123
+ }
124
+ } catch (error) {
125
+ logger.log(
126
+ LogSource.Internals,
127
+ 'FULL_SCREEN',
128
+ 'requestFullscreen error',
129
+ error,
130
+ );
131
+ return Promise.reject(false);
132
+ }
133
+ };
134
+
135
+ const exitFullScreen = () => {
136
+ try {
137
+ if (document?.fullscreenElement) {
138
+ document?.exitFullscreen();
139
+ logger.log(
140
+ LogSource.Internals,
141
+ 'FULL_SCREEN',
142
+ 'exitFullscreen success',
143
+ );
144
+ return true;
145
+ } else {
146
+ console.error('Full screen AI - there is no fullscreen element');
147
+ return false;
148
+ }
149
+ } catch (error) {
150
+ logger.error(
151
+ LogSource.Internals,
152
+ 'FULL_SCREEN',
153
+ 'exitFullscreen error',
154
+ error,
155
+ );
156
+ return false;
157
+ }
158
+ };
159
+
160
+ return {requestFullscreen, exitFullScreen};
161
+ };
@@ -1,20 +1,19 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <link rel="preconnect" href="https://fonts.googleapis.com">
7
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
7
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
8
8
  <!-- moved to template/src/assets/font-styles.css as it was not available for sdks-->
9
9
  <!-- <link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap" rel="stylesheet"> -->
10
10
 
11
- <base href="/">
11
+ <base href="/" />
12
12
  <title><%= htmlWebpackPlugin.options.title %></title>
13
-
14
- </head>
15
- <body>
13
+ </head>
14
+ <body>
16
15
  <style>
17
- /* .video-container > *{
16
+ /* .video-container > *{
18
17
  background-color: #000 !important;
19
18
 
20
19
  }
@@ -24,36 +23,40 @@
24
23
  .video-container{
25
24
  background-color: #000;
26
25
  } */
27
- /* *:active {
26
+ /* *:active {
28
27
  outline: none;
29
28
  } */
30
- /* Webkit (Chrome, Safari) */
31
- ::-webkit-scrollbar {
32
- width: 4px;
33
- }
34
-
35
- ::-webkit-scrollbar-track {
36
- background-color: transparent;
37
- }
38
-
39
- ::-webkit-scrollbar-thumb {
40
- background-color: #777;
41
- border-radius: 2px;
42
- }
43
-
44
- /* Firefox */
45
- html {
46
- scrollbar-width: thin;
47
- scrollbar-color: transparent #777;
48
- }
49
-
29
+ /* Webkit (Chrome, Safari) */
30
+ ::-webkit-scrollbar {
31
+ width: 4px;
32
+ }
33
+
34
+ ::-webkit-scrollbar-track {
35
+ background-color: transparent;
36
+ }
37
+
38
+ ::-webkit-scrollbar-thumb {
39
+ background-color: #777;
40
+ border-radius: 2px;
41
+ }
42
+
43
+ /** to hide screenshare fullscreen play/pause button */
44
+ video::-webkit-media-controls-play-button {
45
+ display: none !important;
46
+ }
47
+
48
+ /* Firefox */
49
+ html {
50
+ scrollbar-width: thin;
51
+ scrollbar-color: transparent #777;
52
+ }
50
53
  </style>
51
- <div id="react-app" style="
52
- height: 100vh;
53
- max-height: -webkit-fill-available;
54
- display: flex;
55
- ">
56
-
57
- </div>
58
- </body>
59
- </html>
54
+ <div
55
+ id="react-app"
56
+ style="
57
+ height: 100vh;
58
+ max-height: -webkit-fill-available;
59
+ display: flex;
60
+ "></div>
61
+ </body>
62
+ </html>