@tellescope/video-chat 0.0.11 → 0.0.12

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellescope/video-chat",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "",
5
5
  "main": "./lib/cjs/index.js",
6
6
  "module": "./lib/esm/index.js",
@@ -33,13 +33,13 @@
33
33
  "@fontsource/roboto": "^4.5.1",
34
34
  "@mui/icons-material": "^5.0.1",
35
35
  "@mui/material": "^5.0.2",
36
- "@tellescope/constants": "^0.0.11",
37
- "@tellescope/react-components": "^0.0.11",
38
- "@tellescope/sdk": "^0.0.11",
39
- "@tellescope/types-client": "^0.0.11",
40
- "@tellescope/types-models": "^0.0.11",
41
- "@tellescope/types-utilities": "^0.0.11",
42
- "@tellescope/utilities": "^0.0.11",
36
+ "@tellescope/constants": "^0.0.12",
37
+ "@tellescope/react-components": "^0.0.12",
38
+ "@tellescope/sdk": "^0.0.12",
39
+ "@tellescope/types-client": "^0.0.12",
40
+ "@tellescope/types-models": "^0.0.12",
41
+ "@tellescope/types-utilities": "^0.0.12",
42
+ "@tellescope/utilities": "^0.0.12",
43
43
  "@typescript-eslint/eslint-plugin": "^4.33.0",
44
44
  "@typescript-eslint/parser": "^4.33.0",
45
45
  "amazon-chime-sdk-component-library-react": "^2.12.0",
@@ -54,7 +54,7 @@
54
54
  "react": "^17.0.2",
55
55
  "react-dom": "^17.0.2"
56
56
  },
57
- "gitHead": "bfd788eb3dad86bef3ae27c731bdbc399fb7a13e",
57
+ "gitHead": "5d67de4721a3cbc3537a870281f2c657099e07b4",
58
58
  "publishConfig": {
59
59
  "access": "public"
60
60
  }
@@ -0,0 +1,298 @@
1
+ // logic pulled + refactored from
2
+ // https://github.com/aws-samples/amazon-chime-react-native-demo/blob/master/src/containers/Meeting.js
3
+ // which includes the following copyright disclaimer
4
+ /*
5
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
6
+ * SPDX-License-Identifier: MIT-0
7
+ */
8
+
9
+ import React, { useCallback, useContext, useEffect, useState } from "react"
10
+ import { View, Text, StyleSheet } from "react-native"
11
+ import {
12
+ AttendeeInfo,
13
+ MeetingInfo,
14
+ } from '@tellescope/types-models'
15
+ import {
16
+ UserIdentity,
17
+ } from '@tellescope/types-utilities'
18
+ import { useSession } from "@tellescope/react-components/lib/esm/authentication"
19
+ import { Button } from "@tellescope/react-components/lib/esm/mui"
20
+
21
+ import {
22
+ CurrentCallContext,
23
+ JoinVideoCallReturnType,
24
+ StartVideoCallReturnType,
25
+ VideoProps,
26
+ AttendeeDisplayInfo
27
+ } from "./video.js"
28
+
29
+ import {
30
+ getSDKEventEmitter,
31
+ MobileSDKEvent,
32
+ NativeFunction,
33
+ } from "./native/bridge"
34
+ import { RNVideoView } from "./native/RNVideoRenderView"
35
+
36
+ export const WithVideo = ({ children } : VideoProps ) => {
37
+ const [meeting, setMeeting] = useState(undefined as MeetingInfo | undefined)
38
+
39
+ const [inMeeting, setInMeeting] = useState(false)
40
+ const [isLoading, setIsLoading] = useState(false)
41
+ const [videoIsEnabled, setVideoIsEnabled] = useState(false)
42
+ const [videoTiles, setVideoTiles] = useState([] as string[])
43
+ const [screenShareTile, setScreenShareTile] = useState('')
44
+ const [attendees, setAttendees] = useState ([] as AttendeeDisplayInfo[])
45
+
46
+ const toggleVideo = () => NativeFunction.setCameraOn(!videoIsEnabled)
47
+ const emitter = getSDKEventEmitter()
48
+
49
+ useEffect(() => {
50
+ const startSubscription = emitter.addListener(MobileSDKEvent.OnMeetingStart, () => {
51
+ setInMeeting(true)
52
+ setIsLoading(false)
53
+ });
54
+
55
+ const endSubscription = emitter.addListener(MobileSDKEvent.OnMeetingEnd, () => {
56
+ setInMeeting(true)
57
+ setIsLoading(false)
58
+ });
59
+
60
+ const joinSubscription = emitter.addListener(MobileSDKEvent.OnAttendeesJoin, ({ attendeeId, externalUserId }) => {
61
+ setAttendees(as => !as.find(a => a.attendeeId === attendeeId)
62
+ ? [{ attendeeId, externalUserId, muted: false }, ...as]
63
+ : as
64
+ )
65
+ });
66
+
67
+ const leaveSubscription = emitter.addListener(MobileSDKEvent.OnAttendeesLeave, ({ attendeeId }) => {
68
+ setAttendees(as => as.filter(a => a.attendeeId !== attendeeId))
69
+ });
70
+
71
+ const errorSubscription = emitter.addListener(MobileSDKEvent.OnError, (message) => {
72
+ console.error("SDK Error", message);
73
+ });
74
+
75
+ const muteSubscription = emitter.addListener(MobileSDKEvent.OnAttendeesMute, attendeeId => {
76
+ setAttendees(as => as.map(a => a.attendeeId === attendeeId ? { ...a, muted: true } : a))
77
+ });
78
+
79
+ const unmuteSubscription = emitter.addListener(MobileSDKEvent.OnAttendeesUnmute, attendeeId => {
80
+ setAttendees(as => as.map(a => a.attendeeId === attendeeId ? { ...a, muted: false } : a))
81
+ });
82
+
83
+ const addVideoSubscription = emitter.addListener(MobileSDKEvent.OnAddVideoTile, (tileState) => {
84
+ if (tileState.isScreenShare) {
85
+ setScreenShareTile(tileState.tileId)
86
+ return
87
+ }
88
+ setVideoTiles(v => [...v, tileState.titleId])
89
+ setVideoIsEnabled(v => tileState.isLocal ? true : v)
90
+ });
91
+
92
+ const removeVideoSubscription = emitter.addListener(MobileSDKEvent.OnRemoveVideoTile, (tileState) => {
93
+ if (tileState.isScreenShare) {
94
+ setScreenShareTile('')
95
+ return
96
+ }
97
+ setVideoTiles(vs => vs.filter(v => v !== tileState.tileId))
98
+ setVideoIsEnabled(v => tileState.isLocal ? false : v)
99
+ });
100
+
101
+
102
+ return () => {
103
+ startSubscription.remove();
104
+ endSubscription.remove();
105
+ errorSubscription.remove();
106
+ joinSubscription.remove();
107
+ leaveSubscription.remove();
108
+ muteSubscription.remove();
109
+ unmuteSubscription.remove();
110
+ addVideoSubscription.remove();
111
+ removeVideoSubscription.remove();
112
+ }
113
+ }, [emitter])
114
+
115
+ return (
116
+ <CurrentCallContext.Provider value={{ attendees, videoTiles, meeting, shareScreenId: screenShareTile, setMeeting, videoIsEnabled, toggleVideo }}>
117
+ {children}
118
+ </CurrentCallContext.Provider>
119
+ )
120
+ }
121
+
122
+ export const useStartVideoCall = (): StartVideoCallReturnType => {
123
+ const session = useSession()
124
+ const { meeting, setMeeting, videoIsEnabled, toggleVideo } = React.useContext(CurrentCallContext)
125
+
126
+ const [starting, setStarting] = useState(false)
127
+ const [ending, setEnding] = useState(false)
128
+
129
+ const createAndStartMeeting = async (initialAttendees?: UserIdentity[]) => {
130
+ try {
131
+ const { meeting, host } = await session.api.meetings.start_meeting()
132
+
133
+ if (initialAttendees) {
134
+ session.api.meetings.add_attendees_to_meeting({ id: meeting.Meeting.ExternalMeetingId, attendees: initialAttendees })
135
+ }
136
+
137
+ NativeFunction.startMeeting(meeting.Meeting, host.Attendee)
138
+
139
+ setMeeting(meeting.Meeting)
140
+ } catch(err) {
141
+ console.error(err)
142
+ }
143
+ finally {
144
+
145
+ }
146
+ }
147
+
148
+ const addAttendees = useCallback(async (attendees: UserIdentity[]) => {
149
+ if (!meeting) return
150
+ await session.api.meetings.add_attendees_to_meeting({ id: meeting?.ExternalMeetingId, attendees })
151
+ }, [session, meeting])
152
+
153
+ const endMeeting = async () => {
154
+ if (!meeting) return
155
+ setEnding(true)
156
+
157
+ try {
158
+ await session.api.meetings.end_meeting({ id: meeting.ExternalMeetingId })
159
+ } catch(err) { console.error(err) }
160
+
161
+ setEnding(false)
162
+ setMeeting(undefined)
163
+ }
164
+
165
+ return { meeting, videoIsEnabled, starting, ending, toggleVideo, createAndStartMeeting, addAttendees, endMeeting }
166
+ }
167
+
168
+ export const useJoinVideoCall = (): JoinVideoCallReturnType => {
169
+ const { meeting, setMeeting, videoIsEnabled, toggleVideo } = React.useContext(CurrentCallContext)
170
+
171
+ const joinMeeting = async (meetingInfo: { Meeting: MeetingInfo }, attendeeInfo: { Attendee: AttendeeInfo }) => {
172
+ NativeFunction.startMeeting(meetingInfo.Meeting, attendeeInfo.Attendee)
173
+ }
174
+
175
+ return { meeting, videoIsEnabled, toggleVideo, joinMeeting }
176
+ }
177
+
178
+ export const VideoTileGrid = () => {
179
+ const {
180
+ // attendees,
181
+ videoIsEnabled,
182
+ videoTiles,
183
+ shareScreenId
184
+ } = useContext(CurrentCallContext)
185
+
186
+ return (
187
+ <View style={[styles.container, { justifyContent: 'flex-start' }]}>
188
+ <Text style={styles.title}>TITLE</Text>
189
+ <View style={styles.buttonContainer}>
190
+ {/* <MuteButton muted={currentMuted} onPress={() => NativeFunction.setMute(!currentMuted) }/> */}
191
+ {/* <CameraButton disabled={selfVideoEnabled} onPress={() => NativeFunction.setCameraOn(!videoIsEnabled)}/> */}
192
+ <Button disabled={videoIsEnabled} onPress={() => NativeFunction.setCameraOn(!videoIsEnabled)}>
193
+ Toggle Video
194
+ </Button>
195
+ {/* <HangOffButton onPress={() => NativeFunction.stopMeeting()} /> */}
196
+ <Button onPress={() => NativeFunction.stopMeeting()}> Leave Meeting </Button>
197
+ </View>
198
+ <Text style={styles.title}>Video</Text>
199
+ <View style={styles.videoContainer}>
200
+ {
201
+ videoTiles.length > 0 ? videoTiles.map(tileId =>
202
+ <RNVideoView style={styles.video} key={tileId} tileId={tileId} />
203
+ ) : <Text style={styles.subtitle}>No one is sharing video at this moment</Text>
204
+ }
205
+ </View>
206
+ {
207
+ !!shareScreenId &&
208
+ <React.Fragment>
209
+ <Text style={styles.title}>Screen Share</Text>
210
+ <View style={styles.videoContainer}>
211
+ <RNVideoView style={styles.screenShare} key={shareScreenId} tileId={shareScreenId} />
212
+ </View>
213
+ </React.Fragment>
214
+ }
215
+ <Text style={styles.title}>Attendee</Text>
216
+ {/* <FlatList
217
+ style={styles.attendeeList}
218
+ data={attendees}
219
+ renderItem={({ item }) => <AttendeeItem attendeeName={attendeeNameMap[item] ? attendeeNameMap[item] : item} muted={this.state.mutedAttendee.includes(item)}/>}
220
+ keyExtractor={(item) => item}
221
+ /> */}
222
+ </View>
223
+ );
224
+ }
225
+
226
+ const styles = StyleSheet.create({
227
+ container: {
228
+ justifyContent: 'center',
229
+ alignItems: 'center',
230
+ height: '95%',
231
+ backgroundColor: 'white'
232
+ },
233
+ buttonContainer: {
234
+ flexDirection: 'row',
235
+ justifyContent: 'space-between',
236
+ },
237
+ title: {
238
+ fontSize: 30,
239
+ fontWeight: '700'
240
+ },
241
+ subtitle: {
242
+ marginBottom: 25,
243
+ marginTop: 10,
244
+ color: 'grey'
245
+ },
246
+ videoContainer: {
247
+ flexDirection: 'row',
248
+ justifyContent: 'space-around',
249
+ width: '100%',
250
+ // This is an existing React Native issue:
251
+ // When you create a native android component
252
+ // that use GLSurfaceView (We use this internally), the entire screen will
253
+ // black out
254
+ overflow: 'hidden'
255
+ },
256
+ video: {
257
+ width: '45%',
258
+ margin: '1%',
259
+ aspectRatio: 16 / 9,
260
+ },
261
+ screenShare: {
262
+ width: '90%',
263
+ margin: '1%',
264
+ aspectRatio: 16 / 9,
265
+ },
266
+ attendeeList: {
267
+ flex: 1,
268
+ width: '80%'
269
+ },
270
+ attendeeContainer: {
271
+ fontSize: 20,
272
+ margin: 5,
273
+ padding: 5,
274
+ height: 30,
275
+ backgroundColor: '#eee',
276
+ justifyContent: 'space-between',
277
+ flexDirection: 'row',
278
+ },
279
+ attendeeMuteImage: {
280
+ resizeMode: 'contain',
281
+ width: 20,
282
+ height: 20
283
+ },
284
+ inputBox: {
285
+ height: 40,
286
+ borderColor: 'black',
287
+ borderWidth: 1,
288
+ margin: 5,
289
+ width: '50%',
290
+ padding: 10,
291
+ color: 'black'
292
+ },
293
+ meetingButton: {
294
+ resizeMode: 'contain',
295
+ width: 50,
296
+ height: 50
297
+ }
298
+ });
package/src/video.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useState, CSSProperties } from "react"
1
+ import React, { useCallback, useState, CSSProperties, Children } from "react"
2
2
 
3
3
  import {
4
4
  useSession,
@@ -24,8 +24,8 @@ import {
24
24
  import { ThemeProvider } from 'styled-components';
25
25
  import {
26
26
  MeetingProvider,
27
- lightTheme,
28
-
27
+ darkTheme,
28
+ useContentShareState,
29
29
  // LocalVideo,
30
30
  VideoTileGrid,
31
31
  // VideoGrid,
@@ -34,41 +34,61 @@ import {
34
34
  // RemoteVideo,
35
35
  useLocalVideo,
36
36
  useMeetingManager,
37
- // useRosterState,
37
+ useRosterState,
38
+ useRemoteVideoTileState
38
39
  // useRemoteVideoTileState,
39
40
  // useContentShareControls, // screen sharing
40
41
  } from 'amazon-chime-sdk-component-library-react';
41
42
  import { } from "../../../types-client/node_modules/@tellescope/types-models/src"
42
43
 
44
+ export type AttendeeDisplayInfo = { attendeeId: string, externalUserId: string }
45
+
43
46
  export const CurrentCallContext = React.createContext({} as {
44
47
  meeting: MeetingInfo | undefined, setMeeting: (m: MeetingInfo | undefined) => void,
48
+ videoIsEnabled: boolean, toggleVideo: () => Promise<void>,
49
+ attendees: AttendeeDisplayInfo[], shareScreenId: string,
50
+ videoTiles: (number | string)[],
45
51
  })
46
- interface VideoProps {
52
+ export interface VideoProps {
47
53
  children?: React.ReactNode,
48
- theme?: typeof lightTheme,
54
+ theme?: typeof darkTheme,
49
55
  }
50
- export const WithVideo = ({ children, theme=lightTheme }: VideoProps) => {
56
+ const WithContext = ({ children } : { children: React.ReactNode }) => {
51
57
  const [meeting, setMeeting] = useState(undefined as MeetingInfo | undefined)
52
-
58
+ const { toggleVideo, isVideoEnabled: videoIsEnabled } = useLocalVideo();
59
+ const { roster } = useRosterState()
60
+ const { tileId } = useContentShareState()
61
+ const { tiles } = useRemoteVideoTileState()
62
+
63
+ const attendees = [] as AttendeeDisplayInfo[]
64
+ for (const attendeeId in roster) {
65
+ const { externalUserId } = roster[attendeeId]
66
+ attendees.push({ attendeeId, externalUserId: externalUserId as string })
67
+ }
68
+
53
69
  return (
54
- <CurrentCallContext.Provider value={{ meeting, setMeeting }}>
55
- <ThemeProvider theme={theme}>
56
- <MeetingProvider>
70
+ <CurrentCallContext.Provider value={{ attendees, videoTiles: tiles, shareScreenId: tileId?.toString() ?? '', meeting, setMeeting, videoIsEnabled, toggleVideo }}>
57
71
  {children}
58
- </MeetingProvider>
59
- </ThemeProvider>
60
72
  </CurrentCallContext.Provider>
61
73
  )
62
74
  }
75
+ export const WithVideo = ({ children, theme=darkTheme }: VideoProps) => (
76
+ <ThemeProvider theme={theme}>
77
+ <MeetingProvider>
78
+ <WithContext>
79
+ {children}
80
+ </WithContext>
81
+ </MeetingProvider>
82
+ </ThemeProvider>
83
+ )
63
84
 
64
85
  export const useStartVideoCall = () => {
65
86
  const [starting, setStarting] = useState(false)
66
87
  const [ending, setEnding] = useState(false)
67
- const { meeting, setMeeting } = React.useContext(CurrentCallContext)
88
+ const { meeting, setMeeting, toggleVideo, videoIsEnabled } = React.useContext(CurrentCallContext)
68
89
 
69
90
  const session = useSession()
70
91
  const meetingManager = useMeetingManager();
71
- const { toggleVideo, isVideoEnabled } = useLocalVideo();
72
92
 
73
93
  const createAndStartMeeting = async (initialAttendees?: UserIdentity[]) => {
74
94
  setStarting(false)
@@ -109,13 +129,13 @@ export const useStartVideoCall = () => {
109
129
  setMeeting(undefined)
110
130
  }
111
131
 
112
- return { starting, ending, meeting, videoIsEnabled: isVideoEnabled, toggleVideo, createAndStartMeeting, addAttendees, endMeeting }
132
+ return { starting, ending, meeting, videoIsEnabled, toggleVideo, createAndStartMeeting, addAttendees, endMeeting }
113
133
  }
134
+ export type StartVideoCallReturnType = ReturnType<typeof useStartVideoCall>
114
135
 
115
136
  export const useJoinVideoCall = () => {
116
137
  const meetingManager = useMeetingManager();
117
- const { toggleVideo, isVideoEnabled } = useLocalVideo();
118
- const { meeting, setMeeting } = React.useContext(CurrentCallContext)
138
+ const { meeting, setMeeting, toggleVideo, videoIsEnabled } = React.useContext(CurrentCallContext)
119
139
 
120
140
  const joinMeeting = async (meetingInfo: { Meeting: MeetingInfo }, attendeeInfo: { Attendee: AttendeeInfo }) => {
121
141
  await meetingManager.join({ meetingInfo, attendeeInfo }); // Use the join API to create a meeting session
@@ -123,7 +143,8 @@ export const useJoinVideoCall = () => {
123
143
  setMeeting(meetingInfo.Meeting)
124
144
  }
125
145
 
126
- return { meeting, videoIsEnabled: isVideoEnabled, toggleVideo, joinMeeting }
146
+ return { meeting, videoIsEnabled: videoIsEnabled, toggleVideo, joinMeeting }
127
147
  }
148
+ export type JoinVideoCallReturnType = ReturnType<typeof useJoinVideoCall>
128
149
 
129
150
  export { VideoTileGrid }