@100mslive/roomkit-react 0.1.8 → 0.1.9-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. package/dist/{HLSView-DDGPZHA2.js → HLSView-U53QN3AC.js} +3 -3
  2. package/dist/Modal/Dialog.d.ts +402 -1706
  3. package/dist/Prebuilt/App.d.ts +5 -0
  4. package/dist/Prebuilt/AppContext.d.ts +1 -0
  5. package/dist/Prebuilt/AppStateContext.d.ts +16 -0
  6. package/dist/Prebuilt/components/ConferenceScreen.d.ts +2 -0
  7. package/dist/Prebuilt/components/Footer/PollsToggle.d.ts +2 -0
  8. package/dist/Prebuilt/components/LeaveScreen.d.ts +2 -0
  9. package/dist/Prebuilt/components/MwebLandscapePrompt.d.ts +2 -0
  10. package/dist/Prebuilt/components/Notifications/AutoplayBlockedModal.d.ts +2 -0
  11. package/dist/Prebuilt/components/Notifications/HLSFailureModal.d.ts +2 -0
  12. package/dist/Prebuilt/components/Notifications/InitErrorModal.d.ts +2 -0
  13. package/dist/Prebuilt/components/Notifications/Notifications.d.ts +2 -0
  14. package/dist/Prebuilt/components/Notifications/PeerNotifications.d.ts +1 -0
  15. package/dist/Prebuilt/components/Notifications/PermissionErrorModal.d.ts +2 -0
  16. package/dist/Prebuilt/components/Notifications/ReconnectNotifications.d.ts +2 -0
  17. package/dist/Prebuilt/components/Notifications/TrackBulkUnmuteModal.d.ts +2 -0
  18. package/dist/Prebuilt/components/Notifications/TrackNotifications.d.ts +1 -0
  19. package/dist/Prebuilt/components/Notifications/TrackUnmuteModal.d.ts +2 -0
  20. package/dist/Prebuilt/components/Polls/Polls.d.ts +2 -0
  21. package/dist/Prebuilt/components/Preview/PreviewJoin.d.ts +1 -2
  22. package/dist/Prebuilt/components/Preview/PreviewScreen.d.ts +2 -0
  23. package/dist/Prebuilt/components/hooks/useRedirectToLeave.d.ts +1 -1
  24. package/dist/{VirtualBackground-UVZJVOA2.js → VirtualBackground-PMLQPJB6.js} +3 -5
  25. package/dist/{VirtualBackground-UVZJVOA2.js.map → VirtualBackground-PMLQPJB6.js.map} +1 -1
  26. package/dist/chunk-ANQRGVIX.js +14441 -0
  27. package/dist/chunk-ANQRGVIX.js.map +7 -0
  28. package/dist/{chunk-6SQTFOK6.js → chunk-XQ2NRKIW.js} +66 -3
  29. package/dist/{chunk-6SQTFOK6.js.map → chunk-XQ2NRKIW.js.map} +4 -4
  30. package/dist/context/DialogContext.d.ts +6 -0
  31. package/dist/hooks/useDialogContainerSelector.d.ts +1 -0
  32. package/dist/index.cjs.js +10944 -9974
  33. package/dist/index.cjs.js.map +4 -4
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.js +6 -2
  36. package/dist/meta.cjs.json +3871 -3188
  37. package/dist/meta.esbuild.json +4303 -3728
  38. package/dist/utils/animations.d.ts +11 -0
  39. package/package.json +6 -7
  40. package/src/Modal/Dialog.tsx +31 -3
  41. package/src/Prebuilt/App.tsx +46 -99
  42. package/src/Prebuilt/AppContext.tsx +4 -0
  43. package/src/Prebuilt/AppStateContext.tsx +71 -0
  44. package/src/Prebuilt/common/constants.js +35 -0
  45. package/src/Prebuilt/common/utils.js +47 -0
  46. package/src/Prebuilt/components/AppData/AppData.jsx +5 -0
  47. package/src/Prebuilt/components/AppData/useSidepane.js +23 -1
  48. package/src/Prebuilt/components/AppData/useUISettings.js +48 -4
  49. package/src/Prebuilt/components/{conference.jsx → ConferenceScreen.tsx} +30 -43
  50. package/src/Prebuilt/components/Footer/Footer.tsx +5 -0
  51. package/src/Prebuilt/components/Footer/PaginatedParticipants.tsx +63 -32
  52. package/src/Prebuilt/components/Footer/ParticipantList.jsx +2 -1
  53. package/src/Prebuilt/components/Footer/PollsToggle.tsx +22 -0
  54. package/src/Prebuilt/components/Footer/RoleAccordion.tsx +2 -2
  55. package/src/Prebuilt/components/Header/StreamActions.tsx +5 -3
  56. package/src/Prebuilt/components/Leave/DesktopLeaveRoom.tsx +4 -5
  57. package/src/Prebuilt/components/Leave/LeaveRoom.tsx +0 -4
  58. package/src/Prebuilt/components/{PostLeave.jsx → LeaveScreen.tsx} +6 -13
  59. package/src/Prebuilt/components/MoreSettings/ChangeNameModal.jsx +2 -3
  60. package/src/Prebuilt/components/MoreSettings/EmbedUrl.jsx +2 -3
  61. package/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.tsx +18 -1
  62. package/src/Prebuilt/components/{MwebLandscapePrompt.jsx → MwebLandscapePrompt.tsx} +10 -11
  63. package/src/Prebuilt/components/Notifications/{AutoplayBlockedModal.jsx → AutoplayBlockedModal.tsx} +2 -1
  64. package/src/Prebuilt/components/Notifications/{HLSFailureModal.jsx → HLSFailureModal.tsx} +10 -8
  65. package/src/Prebuilt/components/Notifications/{InitErrorModal.jsx → InitErrorModal.tsx} +5 -2
  66. package/src/Prebuilt/components/Notifications/{Notifications.jsx → Notifications.tsx} +41 -27
  67. package/src/Prebuilt/components/Notifications/{PeerNotifications.jsx → PeerNotifications.tsx} +3 -0
  68. package/src/Prebuilt/components/Notifications/{PermissionErrorModal.jsx → PermissionErrorModal.tsx} +6 -4
  69. package/src/Prebuilt/components/Notifications/{ReconnectNotifications.jsx → ReconnectNotifications.tsx} +11 -6
  70. package/src/Prebuilt/components/Notifications/{TrackBulkUnmuteModal.jsx → TrackBulkUnmuteModal.tsx} +9 -3
  71. package/src/Prebuilt/components/Notifications/{TrackUnmuteModal.jsx → TrackUnmuteModal.tsx} +9 -3
  72. package/src/Prebuilt/components/Notifications/index.tsx +1 -0
  73. package/src/Prebuilt/components/Polls/CreatePollQuiz/PollsQuizMenu.jsx +229 -0
  74. package/src/Prebuilt/components/Polls/CreatePollQuiz/Timer.jsx +71 -0
  75. package/src/Prebuilt/components/Polls/CreateQuestions/CreateQuestions.jsx +132 -0
  76. package/src/Prebuilt/components/Polls/CreateQuestions/DeleteQuestionModal.jsx +66 -0
  77. package/src/Prebuilt/components/Polls/CreateQuestions/QuestionForm.jsx +251 -0
  78. package/src/Prebuilt/components/Polls/CreateQuestions/SavedQuestion.jsx +57 -0
  79. package/src/Prebuilt/components/Polls/Polls.tsx +28 -0
  80. package/src/Prebuilt/components/Polls/Voting/PollResultSummary.jsx +125 -0
  81. package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +249 -0
  82. package/src/Prebuilt/components/Polls/Voting/StandardVoting.jsx +40 -0
  83. package/src/Prebuilt/components/Polls/Voting/TimedVoting.jsx +36 -0
  84. package/src/Prebuilt/components/Polls/Voting/Voting.jsx +99 -0
  85. package/src/Prebuilt/components/Polls/common/MultipleChoiceOptions.jsx +101 -0
  86. package/src/Prebuilt/components/Polls/common/OptionInputWithDelete.jsx +25 -0
  87. package/src/Prebuilt/components/Polls/common/SingleChoiceOptions.jsx +125 -0
  88. package/src/Prebuilt/components/Polls/common/StatusIndicator.jsx +47 -0
  89. package/src/Prebuilt/components/Polls/common/VoteCount.jsx +28 -0
  90. package/src/Prebuilt/components/Polls/common/VoteProgress.jsx +17 -0
  91. package/src/Prebuilt/components/Polls/common/VoterList.jsx +22 -0
  92. package/src/Prebuilt/components/Polls/common/Votes.jsx +72 -0
  93. package/src/Prebuilt/components/Preview/PreviewForm.tsx +3 -2
  94. package/src/Prebuilt/components/Preview/PreviewJoin.tsx +32 -27
  95. package/src/Prebuilt/components/Preview/{PreviewContainer.tsx → PreviewScreen.tsx} +2 -19
  96. package/src/Prebuilt/components/RaiseHand.jsx +1 -1
  97. package/src/Prebuilt/components/RoleChangeModal.jsx +2 -3
  98. package/src/Prebuilt/components/RoleChangeRequest/RequestPrompt.tsx +2 -3
  99. package/src/Prebuilt/components/Settings/SettingsModal.jsx +2 -3
  100. package/src/Prebuilt/components/Settings/StartRecording.jsx +15 -4
  101. package/src/Prebuilt/components/SidePaneTabs.tsx +1 -1
  102. package/src/Prebuilt/components/StatsForNerds.jsx +2 -3
  103. package/src/Prebuilt/components/Streaming/Common.jsx +31 -21
  104. package/src/Prebuilt/components/VideoLayouts/ScreenshareLayout.tsx +8 -9
  105. package/src/Prebuilt/components/VideoTile.jsx +37 -33
  106. package/src/Prebuilt/components/hooks/useAutoStartStreaming.tsx +3 -3
  107. package/src/Prebuilt/components/hooks/useRedirectToLeave.tsx +9 -17
  108. package/src/Prebuilt/components/pdfAnnotator/pdfFileOptions.jsx +2 -3
  109. package/src/Prebuilt/components/pdfAnnotator/submitPdf.jsx +1 -1
  110. package/src/Prebuilt/components/pdfAnnotator/uploadedFile.jsx +2 -3
  111. package/src/Prebuilt/layouts/EmbedView.jsx +47 -60
  112. package/src/Prebuilt/layouts/PDFView.jsx +49 -99
  113. package/src/Prebuilt/layouts/SidePane.tsx +8 -4
  114. package/src/Prebuilt/layouts/VideoStreamingSection.tsx +2 -2
  115. package/src/Prebuilt/primitives/DialogContent.jsx +4 -5
  116. package/src/context/DialogContext.tsx +13 -0
  117. package/src/hooks/useDialogContainerSelector.tsx +7 -0
  118. package/src/index.ts +1 -0
  119. package/src/utils/animations.ts +6 -0
  120. package/dist/Prebuilt/components/Notifications/HeadlessEndRoomListener.d.ts +0 -2
  121. package/dist/Prebuilt/components/PrebuiltDialogPortal.d.ts +0 -4
  122. package/dist/Prebuilt/components/PrebuiltTileElements.d.ts +0 -2198
  123. package/dist/Prebuilt/components/Preview/PreviewContainer.d.ts +0 -3
  124. package/dist/chunk-HUMNPIYI.js +0 -70
  125. package/dist/chunk-HUMNPIYI.js.map +0 -7
  126. package/dist/chunk-PRM33R4R.js +0 -7160
  127. package/dist/chunk-PRM33R4R.js.map +0 -7
  128. package/dist/conference-N7S47TDK.js +0 -6602
  129. package/dist/conference-N7S47TDK.js.map +0 -7
  130. package/src/Prebuilt/components/GoLiveButton.jsx +0 -42
  131. package/src/Prebuilt/components/Notifications/HeadlessEndRoomListener.tsx +0 -23
  132. package/src/Prebuilt/components/PrebuiltDialogPortal.tsx +0 -6
  133. package/src/Prebuilt/components/PrebuiltTileElements.tsx +0 -5
  134. package/src/Prebuilt/components/Streaming/HLSStreaming.jsx +0 -220
  135. package/src/Prebuilt/components/Streaming/RTMPStreaming.jsx +0 -334
  136. package/src/Prebuilt/components/Streaming/StreamingLanding.jsx +0 -76
  137. /package/dist/{HLSView-DDGPZHA2.js.map → HLSView-U53QN3AC.js.map} +0 -0
  138. /package/{src/Prebuilt/components/Notifications/index.jsx → dist/Prebuilt/components/Notifications/index.d.ts} +0 -0
  139. /package/src/Prebuilt/components/Notifications/{TrackNotifications.jsx → TrackNotifications.tsx} +0 -0
@@ -1,42 +0,0 @@
1
- import React from 'react';
2
- import { useRecordingStreaming } from '@100mslive/react-sdk';
3
- import { GoLiveIcon } from '@100mslive/react-icons';
4
- import { Button } from '../../Button';
5
- import { Tooltip } from '../../Tooltip';
6
- import { useIsSidepaneTypeOpen, useSidepaneToggle } from './AppData/useSidepane';
7
- import { useIsHLSStartedFromUI, useIsRTMPStartedFromUI } from './AppData/useUISettings';
8
- import { SIDE_PANE_OPTIONS } from './../common/constants';
9
-
10
- const GoLiveButton = () => {
11
- const isStreamingSidepaneOpen = useIsSidepaneTypeOpen(SIDE_PANE_OPTIONS.STREAMING);
12
- const toggleStreaming = useSidepaneToggle(SIDE_PANE_OPTIONS.STREAMING);
13
- const { isStreamingOn, isBrowserRecordingOn } = useRecordingStreaming();
14
- const isHLSStartedFromUI = useIsHLSStartedFromUI();
15
- const isRTMPStartedFromUI = useIsRTMPStartedFromUI();
16
- let tooltipText = 'Start streaming';
17
- if (isHLSStartedFromUI || isRTMPStartedFromUI) {
18
- if (isHLSStartedFromUI) {
19
- tooltipText = 'HLS start in progress';
20
- }
21
- if (isRTMPStartedFromUI) {
22
- tooltipText = 'RTMP start in progress';
23
- }
24
- }
25
- return (
26
- <Tooltip title={tooltipText}>
27
- <Button
28
- data-testid="go_live"
29
- variant={isStreamingSidepaneOpen ? 'standard' : 'primary'}
30
- onClick={toggleStreaming}
31
- icon
32
- loading={isRTMPStartedFromUI || isHLSStartedFromUI}
33
- disabled={isBrowserRecordingOn && !isStreamingOn}
34
- >
35
- <GoLiveIcon />
36
- Go Live
37
- </Button>
38
- </Tooltip>
39
- );
40
- };
41
-
42
- export default GoLiveButton;
@@ -1,23 +0,0 @@
1
- import React, { useEffect } from 'react';
2
- import { HMSNotificationTypes, useHMSNotifications } from '@100mslive/react-sdk';
3
- // @ts-ignore
4
- import { useIsNotificationDisabled } from '../AppData/useUISettings';
5
- import { useRedirectToLeave } from '../hooks/useRedirectToLeave';
6
-
7
- export function HeadlessEndRoomListener() {
8
- const notification = useHMSNotifications();
9
- const isNotificationDisabled = useIsNotificationDisabled();
10
- const { redirectToLeave } = useRedirectToLeave();
11
-
12
- useEffect(() => {
13
- if (!notification || !isNotificationDisabled) {
14
- return;
15
- }
16
- if ([HMSNotificationTypes.ROOM_ENDED, HMSNotificationTypes.REMOVED_FROM_ROOM].includes(notification.type)) {
17
- redirectToLeave(1000);
18
- }
19
- // eslint-disable-next-line react-hooks/exhaustive-deps
20
- }, [notification]);
21
-
22
- return <></>;
23
- }
@@ -1,6 +0,0 @@
1
- import React, { ReactNode } from 'react';
2
- import { Dialog } from '../../Modal';
3
-
4
- export const PrebuiltDialogPortal = ({ children }: { children: ReactNode }) => (
5
- <Dialog.Portal container={document.getElementById('prebuilt-container')}>{children}</Dialog.Portal>
6
- );
@@ -1,5 +0,0 @@
1
- import { styled } from '../../Theme';
2
- import { StyledVideoTile } from '../../VideoTile';
3
-
4
- export const PrebuiltAudioIndicator = styled(StyledVideoTile.AudioIndicator, { height: '$12', width: '$12' });
5
- export const PrebuiltAttributeBox = styled(StyledVideoTile.AttributeBox, { height: '$12', width: '$12' });
@@ -1,220 +0,0 @@
1
- import React, { Fragment, useCallback, useEffect, useState } from 'react';
2
- import { selectRoomID, useHMSActions, useHMSStore, useRecordingStreaming } from '@100mslive/react-sdk';
3
- import {
4
- EndStreamIcon,
5
- EyeOpenIcon,
6
- GoLiveIcon,
7
- InfoIcon,
8
- LinkIcon,
9
- PeopleIcon,
10
- SupportIcon,
11
- WrenchIcon,
12
- } from '@100mslive/react-icons';
13
- import { Box, Button, Flex, Loading, Text } from '../../../';
14
- import { Container, ContentBody, ContentHeader, ErrorText, RecordStream } from './Common';
15
- import { useSetAppDataByKey } from '../AppData/useUISettings';
16
- import { useFilteredRoles } from '../../common/hooks';
17
- import { APP_DATA } from '../../common/constants';
18
-
19
- const getCardData = (roleName, roomId) => {
20
- let data = {};
21
- const formattedRoleName = roleName[0].toUpperCase() + roleName.slice(1);
22
-
23
- switch (roleName) {
24
- case 'broadcaster': {
25
- data = {
26
- title: formattedRoleName,
27
- content: 'Broadcasters can livestream audio or video, manage stream appearance and control the room via HLS.',
28
- icon: <SupportIcon />,
29
- };
30
- break;
31
- }
32
- case 'hls-viewer': {
33
- data = {
34
- title: 'HLS Viewer',
35
- content:
36
- 'Viewers can view and send chat messages, but need to be made broadcasters to participate with audio or video.',
37
- icon: <EyeOpenIcon />,
38
- };
39
- break;
40
- }
41
- default:
42
- data = {
43
- title: formattedRoleName,
44
- content: `${formattedRoleName} is customised with specific permissions, which will determine how it interacts with this room.`,
45
- icon: <WrenchIcon />,
46
- order: 1,
47
- };
48
- }
49
- data['link'] = `/${roomId}/${roleName}`;
50
- return data;
51
- };
52
-
53
- const Card = ({ title, icon, link, content, isHLSRunning, order = 0 }) => {
54
- const [copied, setCopied] = useState(false);
55
- return isHLSRunning ? (
56
- <Box
57
- key={title}
58
- css={{
59
- backgroundColor: '$surface_bright',
60
- padding: '$10',
61
- order,
62
- borderRadius: '$2',
63
- }}
64
- >
65
- <Flex align="center" gap="2" css={{ color: '$primary_bright' }}>
66
- {icon}
67
- <Text variant="h6" css={{ fontWeight: '$semiBold' }}>
68
- {title}
69
- </Text>
70
- </Flex>
71
- <Text variant="sm" css={{ color: '$on_surface_medium', mt: '$6' }}>
72
- {content}
73
- </Text>
74
- <Button
75
- variant="standard"
76
- onClick={() => {
77
- navigator.clipboard.writeText(`${window.location.origin}${link}`);
78
- setCopied(true);
79
- setTimeout(() => setCopied(false), 2000);
80
- }}
81
- css={{ w: '100%', r: '$1', mt: '$10', fontWeight: '$semiBold' }}
82
- icon
83
- >
84
- {copied ? (
85
- <>Link copied!</>
86
- ) : (
87
- <>
88
- <LinkIcon style={{ color: 'inherit' }} />
89
- Copy Invite Link
90
- </>
91
- )}
92
- </Button>
93
- </Box>
94
- ) : null;
95
- };
96
-
97
- export const HLSStreaming = ({ onBack }) => {
98
- const roleNames = useFilteredRoles();
99
- const roomId = useHMSStore(selectRoomID);
100
- const cards = roleNames.map(roleName => getCardData(roleName, roomId));
101
-
102
- const { isHLSRunning } = useRecordingStreaming();
103
- const [showLinks, setShowLinks] = useState(false);
104
- return !showLinks ? (
105
- <Container rounded>
106
- <ContentHeader title="Start Streaming" content="HLS" onBack={onBack} />
107
- <ContentBody title="HLS Streaming" Icon={GoLiveIcon} removeVerticalPadding>
108
- Stream directly from the browser using any device with multiple hosts and real-time messaging, all within this
109
- platform.
110
- </ContentBody>
111
- {isHLSRunning ? <EndHLS setShowLinks={setShowLinks} /> : <StartHLS />}
112
- </Container>
113
- ) : (
114
- <Container rounded>
115
- <ContentHeader title="Invite People" content="Start the conversation" onBack={() => setShowLinks(false)} />
116
-
117
- <Flex direction="column" css={{ gap: '$10', p: '$0 $10', overflowY: 'auto', mb: '$10' }}>
118
- {cards.map(card => (
119
- <Card key={card.title} {...card} isHLSRunning={isHLSRunning} />
120
- ))}
121
- </Flex>
122
- </Container>
123
- );
124
- };
125
-
126
- const StartHLS = () => {
127
- const [record, setRecord] = useState(false);
128
- const [error, setError] = useState(false);
129
- const hmsActions = useHMSActions();
130
- const [isHLSStarted, setHLSStarted] = useSetAppDataByKey(APP_DATA.hlsStarted);
131
- const startHLS = useCallback(
132
- async variants => {
133
- try {
134
- if (isHLSStarted) {
135
- return;
136
- }
137
- setHLSStarted(true);
138
- setError('');
139
- await hmsActions.startHLSStreaming({
140
- variants,
141
- recording: { hlsVod: record, singleFilePerLayer: record },
142
- });
143
- } catch (error) {
144
- setHLSStarted(false);
145
- setError(error.message);
146
- }
147
- },
148
- [hmsActions, record, isHLSStarted, setHLSStarted],
149
- );
150
-
151
- return (
152
- <Fragment>
153
- <RecordStream record={record} setRecord={setRecord} testId="hls-recording" />
154
- <Box css={{ p: '$4 $10' }}>
155
- <ErrorText error={error} />
156
- <Button
157
- data-testid="start_hls"
158
- css={{ w: '100%', r: '$0' }}
159
- icon
160
- onClick={() => startHLS()}
161
- disabled={isHLSStarted}
162
- >
163
- {isHLSStarted ? <Loading size={24} color="currentColor" /> : <GoLiveIcon />}
164
- {isHLSStarted ? 'Starting stream...' : 'Go Live'}
165
- </Button>
166
- </Box>
167
- <Flex align="center" css={{ p: '$4 $10' }}>
168
- <Text>
169
- <InfoIcon width={16} height={16} />
170
- </Text>
171
- <Text variant="tiny" color="$on_surface_medium" css={{ mx: '$8' }}>
172
- You cannot start recording once the stream starts, you will have to stop the stream to enable recording.
173
- </Text>
174
- </Flex>
175
- </Fragment>
176
- );
177
- };
178
-
179
- const EndHLS = ({ setShowLinks }) => {
180
- const hmsActions = useHMSActions();
181
-
182
- const [inProgress, setInProgress] = useState(false);
183
- const [error, setError] = useState('');
184
- const { isHLSRunning } = useRecordingStreaming();
185
-
186
- useEffect(() => {
187
- if (inProgress && !isHLSRunning) {
188
- setInProgress(false);
189
- }
190
- }, [inProgress, isHLSRunning]);
191
-
192
- return (
193
- <Box css={{ p: '$4 $10' }}>
194
- <ErrorText error={error} />
195
- <Button
196
- data-testid="stop_hls"
197
- variant="danger"
198
- css={{ w: '100%', r: '$0', mt: '$8' }}
199
- icon
200
- loading={inProgress}
201
- disabled={inProgress}
202
- onClick={async () => {
203
- try {
204
- setInProgress(true);
205
- await hmsActions.stopHLSStreaming();
206
- } catch (error) {
207
- setError(error.message);
208
- setInProgress(false);
209
- }
210
- }}
211
- >
212
- <EndStreamIcon />
213
- End Stream
214
- </Button>
215
- <Button icon css={{ w: '100%', r: '$0', mt: '$8' }} onClick={() => setShowLinks(true)}>
216
- <PeopleIcon /> Invite People
217
- </Button>
218
- </Box>
219
- );
220
- };
@@ -1,334 +0,0 @@
1
- import React, { useEffect, useRef, useState } from 'react';
2
- import { useHMSActions, useRecordingStreaming } from '@100mslive/react-sdk';
3
- import { AddCircleIcon, EndStreamIcon, GoLiveIcon, PencilIcon, SettingsIcon, TrashIcon } from '@100mslive/react-icons';
4
- import { Accordion, Box, Button, Flex, Input, Label, Loading, Text } from '../../../';
5
- import { Container, ContentBody, ContentHeader, ErrorText, RecordStream } from './Common';
6
- import { ResolutionInput } from './ResolutionInput';
7
- import { useSetAppDataByKey } from '../AppData/useUISettings';
8
- import { UserPreferencesKeys, useUserPreferences } from '../hooks/useUserPreferences';
9
- import { APP_DATA, RTMP_RECORD_DEFAULT_RESOLUTION } from '../../common/constants';
10
-
11
- export const RTMPStreaming = ({ onBack }) => {
12
- const { isRTMPRunning } = useRecordingStreaming();
13
-
14
- return (
15
- <Container>
16
- <ContentHeader title="Start Streaming" content="Choose a destination" onBack={onBack} />
17
- <ContentBody Icon={SettingsIcon} title="RTMP">
18
- Live Stream your call to Twitch, YouTube, Facebook and any app which supports RTMP, all at the same time
19
- </ContentBody>
20
- {!isRTMPRunning ? <StartRTMP /> : <EndRTMP />}
21
- </Container>
22
- );
23
- };
24
-
25
- const StartRTMP = () => {
26
- const [rtmpPreference = [], setRTMPPreference] = useUserPreferences(UserPreferencesKeys.RTMP_URLS);
27
- const [rtmpStreams, setRTMPStreams] = useState(
28
- rtmpPreference.length > 0
29
- ? rtmpPreference
30
- : [
31
- {
32
- name: 'Stream',
33
- id: Date.now(),
34
- rtmpURL: '',
35
- streamKey: '',
36
- },
37
- ],
38
- );
39
- const hmsActions = useHMSActions();
40
- const [error, setError] = useState(false);
41
- const [record, setRecord] = useState(false);
42
- const [resolution, setResolution] = useState(RTMP_RECORD_DEFAULT_RESOLUTION);
43
- const [isRTMPStarted, setRTMPStarted] = useSetAppDataByKey(APP_DATA.rtmpStarted);
44
- const hasRTMPURL = rtmpStreams.some(value => value.rtmpURL && value.streamKey);
45
-
46
- return (
47
- <Box
48
- css={{ overflowY: 'auto' }}
49
- as="form"
50
- onSubmit={e => {
51
- e.preventDefault();
52
- }}
53
- >
54
- {rtmpStreams.length > 0 && (
55
- <Box css={{ px: '$10' }}>
56
- <Accordion.Root type="single" collapsible defaultValue={rtmpStreams[0].id}>
57
- {rtmpStreams.map((rtmp, index) => {
58
- return (
59
- <Accordion.Item
60
- value={rtmp.id}
61
- key={rtmp.id}
62
- css={{
63
- border: '2px solid $surface_bright !important',
64
- r: '$1',
65
- my: '$4',
66
- }}
67
- >
68
- <AccordionHeader rtmp={rtmp} setRTMPStreams={setRTMPStreams} />
69
- <Accordion.Content css={{ px: '$8', py: 0 }}>
70
- <RTMPForm {...rtmp} setRTMPStreams={setRTMPStreams} testId={`${index}_rtmp`} />
71
- </Accordion.Content>
72
- </Accordion.Item>
73
- );
74
- })}
75
- </Accordion.Root>
76
- </Box>
77
- )}
78
- <ResolutionInput
79
- testId="rtmp_resolution"
80
- onResolutionChange={setResolution}
81
- css={{
82
- flexDirection: 'column',
83
- alignItems: 'start',
84
- px: '$10',
85
- my: '$8',
86
- }}
87
- />
88
- <RecordStream record={record} setRecord={setRecord} testId="rtmp_recording" />
89
- <Box css={{ p: '$8 $10', '@lg': { display: 'flex', gap: '$4' } }}>
90
- {rtmpStreams.length < 3 && (
91
- <Button
92
- data-testid="add_stream"
93
- variant="standard"
94
- outlined
95
- icon
96
- css={{ my: '$4', w: '100%' }}
97
- onClick={() => {
98
- setRTMPStreams(streams => [
99
- ...streams,
100
- {
101
- name: 'Stream',
102
- id: Date.now(),
103
- rtmpURL: '',
104
- streamKey: '',
105
- },
106
- ]);
107
- }}
108
- >
109
- <AddCircleIcon /> Add Stream
110
- </Button>
111
- )}
112
-
113
- <Button
114
- data-testid="start_rtmp"
115
- variant="primary"
116
- icon
117
- type="submit"
118
- css={{ w: '100%', my: '$4' }}
119
- disabled={isRTMPStarted || (rtmpStreams.length === 0 && !record)}
120
- onClick={async () => {
121
- try {
122
- const hasInvalidData = rtmpStreams.find(
123
- value => (value.rtmpURL && !value.streamKey) || (value.streamKey && !value.rtmpURL),
124
- );
125
- if (hasInvalidData || (rtmpStreams.length > 0 && !hasRTMPURL)) {
126
- return;
127
- }
128
- setError('');
129
- setRTMPStarted(true);
130
- const urls = hasRTMPURL ? rtmpStreams.map(value => `${value.rtmpURL}/${value.streamKey}`) : [];
131
- await hmsActions.startRTMPOrRecording({
132
- rtmpURLs: urls,
133
- resolution: getResolution(resolution),
134
- record: record,
135
- });
136
- setRTMPPreference(rtmpStreams);
137
- } catch (error) {
138
- console.error(error);
139
- setError(error.message);
140
- setRTMPStarted(false);
141
- }
142
- }}
143
- >
144
- {isRTMPStarted ? <Loading size={24} color="currentColor" /> : <GoLiveIcon />}
145
- {isRTMPStarted ? 'Starting stream...' : 'Go Live'}
146
- </Button>
147
- <ErrorText error={error} />
148
- </Box>
149
- </Box>
150
- );
151
- };
152
-
153
- const EndRTMP = () => {
154
- const hmsActions = useHMSActions();
155
- const [inProgress, setInProgress] = useState(false);
156
- const [error, setError] = useState('');
157
- const { isRTMPRunning } = useRecordingStreaming();
158
-
159
- useEffect(() => {
160
- if (inProgress && !isRTMPRunning) {
161
- setInProgress(false);
162
- }
163
- }, [inProgress, isRTMPRunning]);
164
-
165
- return (
166
- <Box css={{ p: '$4 $10' }}>
167
- <ErrorText error={error} />
168
- <Button
169
- data-testid="stop_rtmp"
170
- variant="danger"
171
- css={{ w: '100%', r: '$0', my: '$8' }}
172
- icon
173
- loading={inProgress}
174
- disabled={inProgress}
175
- onClick={async () => {
176
- try {
177
- setInProgress(true);
178
- await hmsActions.stopRTMPAndRecording();
179
- } catch (error) {
180
- setError(error.message);
181
- setInProgress(false);
182
- }
183
- }}
184
- >
185
- <EndStreamIcon />
186
- End Stream
187
- </Button>
188
- </Box>
189
- );
190
- };
191
-
192
- const ActionIcon = ({ icon: Icon, onClick }) => {
193
- return (
194
- <Text as="span" css={{ mx: '$2', cursor: 'pointer' }} onClick={onClick}>
195
- <Icon width={16} height={16} />
196
- </Text>
197
- );
198
- };
199
-
200
- const FormLabel = ({ id, children }) => {
201
- return (
202
- <Label htmlFor={id} css={{ color: '$on_surface_high', my: '$4', fontSize: '$sm' }}>
203
- {children}
204
- </Label>
205
- );
206
- };
207
-
208
- const RTMPForm = ({ rtmpURL, id, streamKey, setRTMPStreams, testId }) => {
209
- const formRef = useRef(null);
210
- return (
211
- <Flex id={id} direction="column" css={{ mb: '$8', px: '$8' }} ref={formRef}>
212
- <FormLabel id="rtmpURL">
213
- RTMP URL
214
- <Asterik />
215
- </FormLabel>
216
- <Input
217
- data-testid={`${testId}_url`}
218
- placeholder="Enter RTMP URL"
219
- id="rtmpURL"
220
- name="rtmpURL"
221
- value={rtmpURL}
222
- onChange={e => {
223
- setRTMPStreams(streams =>
224
- updateStream({
225
- streams,
226
- id,
227
- value: e.target.value,
228
- key: e.target.name,
229
- }),
230
- );
231
- }}
232
- required
233
- />
234
- <FormLabel id="streamKey">
235
- Stream Key
236
- <Asterik />
237
- </FormLabel>
238
- <Input
239
- placeholder="Enter Stream Key"
240
- id="streamKey"
241
- name="streamKey"
242
- value={streamKey}
243
- data-testid={`${testId}_key`}
244
- onChange={e => {
245
- setRTMPStreams(streams =>
246
- updateStream({
247
- streams,
248
- id,
249
- value: e.target.value,
250
- key: e.target.name,
251
- }),
252
- );
253
- }}
254
- required
255
- />
256
- </Flex>
257
- );
258
- };
259
-
260
- const Asterik = () => {
261
- return (
262
- <Text variant="sm" as="span" css={{ color: '$alert_error_default', mx: '$2' }}>
263
- *
264
- </Text>
265
- );
266
- };
267
- const AccordionHeader = ({ rtmp, setRTMPStreams }) => {
268
- const [edit, setEdit] = useState(false);
269
- return (
270
- <Accordion.Header css={{ px: '$8' }}>
271
- {edit ? (
272
- <Input
273
- defaultValue={rtmp.name}
274
- autoFocus
275
- onBlur={e => {
276
- const value = e.currentTarget.value.trim();
277
- if (value) {
278
- setRTMPStreams(streams =>
279
- streams.map(stream => {
280
- if (stream.id === rtmp.id) {
281
- stream.name = value;
282
- }
283
- return stream;
284
- }),
285
- );
286
- setEdit(false);
287
- }
288
- }}
289
- />
290
- ) : (
291
- <Text css={{ flex: '1 1 0' }}>{rtmp.name}</Text>
292
- )}
293
- <Flex css={{ mx: '$4', gap: '$2' }}>
294
- <ActionIcon
295
- onClick={e => {
296
- e.stopPropagation();
297
- setEdit(true);
298
- }}
299
- icon={PencilIcon}
300
- />
301
- <ActionIcon
302
- onClick={() => {
303
- setRTMPStreams(streams => streams.filter(stream => stream.id !== rtmp.id));
304
- }}
305
- icon={TrashIcon}
306
- />
307
- </Flex>
308
- </Accordion.Header>
309
- );
310
- };
311
-
312
- const updateStream = ({ streams, id, key, value }) =>
313
- streams.map(stream => {
314
- if (stream.id === id) {
315
- return {
316
- ...stream,
317
- [key]: value,
318
- };
319
- }
320
- return stream;
321
- });
322
-
323
- export function getResolution(recordingResolution) {
324
- const resolution = {};
325
- if (recordingResolution.width) {
326
- resolution.width = recordingResolution.width;
327
- }
328
- if (recordingResolution.height) {
329
- resolution.height = recordingResolution.height;
330
- }
331
- if (Object.keys(resolution).length > 0) {
332
- return resolution;
333
- }
334
- }