@tellescope/video-chat 0.0.37 → 0.0.41

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.37",
3
+ "version": "0.0.41",
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.37",
37
- "@tellescope/react-components": "^0.0.37",
38
- "@tellescope/sdk": "^0.0.37",
39
- "@tellescope/types-client": "^0.0.37",
40
- "@tellescope/types-models": "^0.0.37",
41
- "@tellescope/types-utilities": "^0.0.37",
42
- "@tellescope/utilities": "^0.0.37",
36
+ "@tellescope/constants": "^0.0.41",
37
+ "@tellescope/react-components": "^0.0.41",
38
+ "@tellescope/sdk": "^0.0.41",
39
+ "@tellescope/types-client": "^0.0.41",
40
+ "@tellescope/types-models": "^0.0.41",
41
+ "@tellescope/types-utilities": "^0.0.41",
42
+ "@tellescope/utilities": "^0.0.41",
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": "9d2f8b2693292c5882c9920de84176d9cbb87711",
57
+ "gitHead": "5eece62ed79893238758bd54df460360b013c2d3",
58
58
  "publishConfig": {
59
59
  "access": "public"
60
60
  }
package/src/controls.tsx CHANGED
@@ -16,6 +16,7 @@ import { LabeledIconButton } from "@tellescope/react-components/lib/esm/controls
16
16
  import { Flex } from "@tellescope/react-components/lib/esm/layout"
17
17
  import { CurrentCallContext } from "./video_shared"
18
18
  import { useStartVideoCall } from "./video"
19
+ import { useJoinVideoCall } from "."
19
20
 
20
21
  const DEFAULT_BUTTON_SIZE = 30
21
22
  interface ButtonProps {
@@ -42,20 +43,34 @@ export const MicrophoneToggle = ({ size=DEFAULT_BUTTON_SIZE }: ButtonProps) => {
42
43
  }
43
44
 
44
45
  // ends meeting if host, otherwise leaves meeting
45
- export const EndMeeting = ({ size=DEFAULT_BUTTON_SIZE }: ButtonProps) => {
46
+ export const EndMeeting = ({ size=DEFAULT_BUTTON_SIZE, onLeave }: ButtonProps & LeaveMeetingProps) => {
46
47
  const { endMeeting } = useStartVideoCall()
47
48
 
48
49
  return (
49
- <LabeledIconButton size={size} Icon={CallEndIcon} onClick={endMeeting} label="End Meeting"/>
50
+ <LabeledIconButton size={size} Icon={CallEndIcon} label="End Meeting"
51
+ onClick={() => {
52
+ endMeeting()
53
+ onLeave?.()
54
+ }}
55
+ />
50
56
  )
51
57
  }
52
58
 
53
59
  interface LeaveMeetingProps {
54
60
  onLeave?: () => void,
55
61
  }
56
- export const LeaveMeeting = ({ onLeave, size=DEFAULT_BUTTON_SIZE } : LeaveMeetingProps & ButtonProps) => (
57
- <LabeledIconButton size={size} Icon={CallEndIcon} onClick={onLeave} label="Leave Meeting"/>
58
- )
62
+ export const LeaveMeeting = ({ onLeave, size=DEFAULT_BUTTON_SIZE } : LeaveMeetingProps & ButtonProps) => {
63
+ const { leaveMeeting } = useJoinVideoCall()
64
+
65
+ return (
66
+ <LabeledIconButton size={size} Icon={CallEndIcon} label="Leave Meeting"
67
+ onClick={() => {
68
+ onLeave?.()
69
+ leaveMeeting()
70
+ }}
71
+ />
72
+ )
73
+ }
59
74
 
60
75
  interface ControlbarProps {
61
76
  spacing?: number,
@@ -76,7 +91,7 @@ export const ControlBar = ({ onLeave, style, spacing=15, size } : ControlbarProp
76
91
  <MicrophoneToggle size={size}/>
77
92
  </Flex>
78
93
  <Flex style={itemStyle}>
79
- {isHost ? <EndMeeting size={size}/> : <LeaveMeeting size={size} onLeave={onLeave}/>}
94
+ {isHost ? <EndMeeting size={size} onLeave={onLeave}/> : <LeaveMeeting size={size} onLeave={onLeave}/>}
80
95
  </Flex>
81
96
  </Paper>
82
97
  </Flex>
@@ -15,7 +15,7 @@ import {
15
15
  import {
16
16
  UserIdentity,
17
17
  } from '@tellescope/types-utilities'
18
- import { useSession } from "@tellescope/react-components/lib/esm/authentication"
18
+ import { useResolvedSession, useSession } from "@tellescope/react-components/lib/esm/authentication"
19
19
  import { Flex } from "@tellescope/react-components/lib/esm/layout"
20
20
  import {
21
21
  Button,
@@ -190,8 +190,9 @@ export const useStartVideoCall = (): StartVideoCallReturnType => {
190
190
  const [ending, setEnding] = useState(false)
191
191
 
192
192
  const createAndStartMeeting = async (initialAttendees?: UserIdentity[]) => {
193
+ setStarting(true)
193
194
  try {
194
- const { meeting, host } = await session.api.meetings.start_meeting()
195
+ const { id, meeting, host } = await session.api.meetings.start_meeting()
195
196
 
196
197
  if (initialAttendees) {
197
198
  session.api.meetings.add_attendees_to_meeting({ id: meeting.Meeting.ExternalMeetingId, attendees: initialAttendees })
@@ -201,11 +202,13 @@ export const useStartVideoCall = (): StartVideoCallReturnType => {
201
202
 
202
203
  setMeeting(meeting.Meeting)
203
204
  setIsHost(true)
205
+
206
+ return id
204
207
  } catch(err) {
205
208
  console.error(err)
206
- }
207
- finally {
208
-
209
+ throw err
210
+ } finally {
211
+ setStarting(false)
209
212
  }
210
213
  }
211
214
 
@@ -239,13 +242,24 @@ export const useStartVideoCall = (): StartVideoCallReturnType => {
239
242
  }
240
243
 
241
244
  export const useJoinVideoCall = (): JoinVideoCallReturnType => {
245
+ const session = useResolvedSession()
242
246
  const { meeting, setMeeting, videoIsEnabled, toggleVideo } = React.useContext(CurrentCallContext)
243
247
 
244
- const joinMeeting = async (meetingInfo: { Meeting: MeetingInfo }, attendeeInfo: { Attendee: AttendeeInfo }) => {
248
+ const joinMeeting = async (meetingInfo: string | { Meeting: MeetingInfo }, attendeeInfo: { Attendee: AttendeeInfo }) => {
249
+ if (typeof meetingInfo == 'string') {
250
+ const meetings = await session.api.meetings.my_meetings()
251
+ const meeting = meetings.find(m => m.id === meetingInfo)
252
+ meetingInfo = meeting?.meetingInfo as { Meeting: MeetingInfo }
253
+ attendeeInfo = meeting?.attendees.find?.(a => a.id === session.userInfo.id)?.info as { Attendee: AttendeeInfo }
254
+ }
255
+ if (!meetingInfo || typeof meetingInfo === 'string' || !attendeeInfo) return
256
+
245
257
  NativeFunction.startMeeting(meetingInfo.Meeting, attendeeInfo.Attendee)
246
258
  }
247
259
 
248
- return { meeting, videoIsEnabled, toggleVideo, joinMeeting }
260
+ const leaveMeeting = () => setMeeting(undefined)
261
+
262
+ return { meeting, videoIsEnabled, toggleVideo, joinMeeting, leaveMeeting }
249
263
  }
250
264
  export const VideoTileGrid = () => {
251
265
  const {
@@ -359,4 +373,8 @@ const styles = StyleSheet.create({
359
373
  width: 50,
360
374
  height: 50
361
375
  }
362
- });
376
+ });
377
+
378
+ export const LocalPreview = () => {
379
+ throw new Error("Unimplemented")
380
+ }
package/src/video.tsx CHANGED
@@ -1,6 +1,7 @@
1
- import React, { useCallback, useState, CSSProperties, Children } from "react"
1
+ import React, { useCallback, useState, CSSProperties, Children, useEffect } from "react"
2
2
 
3
3
  import {
4
+ useResolvedSession,
4
5
  useSession,
5
6
  } from "@tellescope/react-components/lib/esm/authentication"
6
7
 
@@ -30,8 +31,9 @@ import {
30
31
  useMeetingManager,
31
32
  useRosterState,
32
33
  useRemoteVideoTileState,
33
- VideoTile,
34
34
  useToggleLocalMute,
35
+ useMeetingStatus,
36
+ MeetingStatus,
35
37
  // useRemoteVideoTileState,
36
38
  // useContentShareControls, // screen sharing
37
39
  } from 'amazon-chime-sdk-component-library-react';
@@ -42,7 +44,12 @@ import {
42
44
  AttendeeDisplayInfo,
43
45
  VideoProps,
44
46
  VideoViewProps,
47
+ JoinVideoCallReturnType,
48
+ StartVideoCallReturnType,
49
+ JoinVideoCallProps,
45
50
  } from "./video_shared"
51
+ import { ConsoleLogger, DefaultDeviceController, Logger, LogLevel } from "amazon-chime-sdk-js";
52
+ import { Styled } from "@tellescope/react-components";
46
53
 
47
54
  const WithContext = ({ children } : { children: React.ReactNode }) => {
48
55
  const [meeting, setMeeting] = useState(undefined as MeetingInfo | undefined)
@@ -87,7 +94,7 @@ export const WithVideo = ({ children }: VideoProps) => (
87
94
  </ThemeProvider>
88
95
  )
89
96
 
90
- export const useStartVideoCall = () => {
97
+ export const useStartVideoCall = (): StartVideoCallReturnType => {
91
98
  const [starting, setStarting] = useState(false)
92
99
  const [ending, setEnding] = useState(false)
93
100
  const { meeting, setMeeting, toggleVideo, videoIsEnabled, setIsHost } = React.useContext(CurrentCallContext)
@@ -96,10 +103,9 @@ export const useStartVideoCall = () => {
96
103
  const meetingManager = useMeetingManager();
97
104
 
98
105
  const createAndStartMeeting = async (initialAttendees?: UserIdentity[]) => {
99
- setStarting(false)
100
-
106
+ setStarting(true)
101
107
  try {
102
- const { meeting, host } = await session.api.meetings.start_meeting()
108
+ const { id, meeting, host } = await session.api.meetings.start_meeting()
103
109
 
104
110
  await meetingManager.join({ meetingInfo: meeting, attendeeInfo: host.info }); // Use the join API to create a meeting session
105
111
  await meetingManager.start(); // At this point you can let users setup their devices, or start the session immediately
@@ -110,8 +116,10 @@ export const useStartVideoCall = () => {
110
116
 
111
117
  setMeeting(meeting.Meeting)
112
118
  setIsHost(true)
119
+ return id
113
120
  } catch(err) {
114
121
  console.error(err)
122
+ throw err
115
123
  }
116
124
  finally {
117
125
  setStarting(false)
@@ -137,21 +145,55 @@ export const useStartVideoCall = () => {
137
145
 
138
146
  return { starting, ending, meeting, videoIsEnabled, toggleVideo, createAndStartMeeting, addAttendees, endMeeting }
139
147
  }
140
- export type StartVideoCallReturnType = ReturnType<typeof useStartVideoCall>
141
148
 
142
- export const useJoinVideoCall = () => {
149
+ export const useJoinVideoCall = (props?: JoinVideoCallProps): JoinVideoCallReturnType => {
150
+ const { onCallEnd } = props ?? {}
151
+ const session = useResolvedSession()
143
152
  const meetingManager = useMeetingManager();
144
153
  const { meeting, setMeeting, toggleVideo, videoIsEnabled } = React.useContext(CurrentCallContext)
154
+ const status = useMeetingStatus()
155
+
156
+ // meetingInfo may be meetingId as string
157
+ const joinMeeting = async (meetingInfo: string | { Meeting: MeetingInfo }, attendeeInfo: { Attendee: AttendeeInfo }) => {
158
+ if (typeof meetingInfo == 'string') {
159
+ const meetings = await session.api.meetings.my_meetings()
160
+ const meeting = meetings.find(m => m.id === meetingInfo)
161
+ meetingInfo = meeting?.meetingInfo as { Meeting: MeetingInfo }
162
+ attendeeInfo = meeting?.attendees.find?.(a => a.id === session.userInfo.id)?.info as { Attendee: AttendeeInfo }
163
+ }
164
+ if (!meetingInfo || typeof meetingInfo === 'string' || !attendeeInfo) return
145
165
 
146
- const joinMeeting = async (meetingInfo: { Meeting: MeetingInfo }, attendeeInfo: { Attendee: AttendeeInfo }) => {
147
166
  await meetingManager.join({ meetingInfo, attendeeInfo }); // Use the join API to create a meeting session
148
167
  await meetingManager.start(); // At this point you can let users setup their devices, or start the session immediately
149
168
  setMeeting(meetingInfo.Meeting)
150
169
  }
151
170
 
152
- return { meeting, videoIsEnabled: videoIsEnabled, toggleVideo, joinMeeting }
171
+ const leaveMeeting = () => {
172
+ if (meetingManager.audioVideo) {
173
+ meetingManager.audioVideo.chooseVideoInputDevice(null);
174
+
175
+ // Stop local video tile (stops sharing the video tile in the meeting)
176
+ meetingManager.audioVideo.stopLocalVideoTile();
177
+
178
+ // Stop a video preview that was previously started (before session starts)
179
+ // meetingManager.audioVideo.stopVideoPreviewForVideoInput(previewVideoElement);
180
+
181
+ // Stop the meeting session (audio and video)
182
+ meetingManager.audioVideo && meetingManager.audioVideo.stop()
183
+
184
+ }
185
+ }
186
+
187
+ useEffect(() => {
188
+ if (!meeting) return
189
+ if (status === MeetingStatus.Ended) {
190
+ leaveMeeting()
191
+ onCallEnd?.()
192
+ }
193
+ }, [meeting, status, leaveMeeting])
194
+
195
+ return { meeting, videoIsEnabled: videoIsEnabled, toggleVideo, joinMeeting, leaveMeeting }
153
196
  }
154
- export type JoinVideoCallReturnType = ReturnType<typeof useJoinVideoCall>
155
197
 
156
198
  export const SelfView = ({ style }: VideoViewProps) => <div style={style}><LocalVideo/></div>
157
199
 
@@ -165,4 +207,65 @@ export const useRemoteViews = (props={} as VideoViewProps) => {
165
207
  }
166
208
 
167
209
 
210
+ class SwappableLogger implements Logger {
211
+ constructor(public inner: Logger) {}
212
+ debug(debugFunction: string | (() => string)): void {
213
+ this.inner.debug(debugFunction);
214
+ }
215
+
216
+ info(msg: string): void {
217
+ this.inner.info(msg);
218
+ }
219
+
220
+ error(msg: string): void {
221
+ this.inner.error(msg);
222
+ }
223
+
224
+ warn(msg: string): void {
225
+ this.inner.warn(msg);
226
+ }
227
+
228
+ setLogLevel(level: LogLevel): void {
229
+ this.inner.setLogLevel(level);
230
+ }
231
+
232
+ getLogLevel(): LogLevel {
233
+ return this.inner.getLogLevel();
234
+ }
235
+ }
236
+
237
+ const PREVIEW_ELEMENT_ID = 'video-preview'
238
+ const defaultPreviewStyle = {
239
+ width: 400,
240
+ minHeight: 200,
241
+ height: 'auto',
242
+ maxHeight: 600,
243
+ backgroundColor: '#f0f0f0'
244
+ }
245
+ export const LocalPreview = ({ style=defaultPreviewStyle }: Styled) => {
246
+ useEffect(() => {
247
+ const videoElement = document.getElementById(PREVIEW_ELEMENT_ID) as HTMLVideoElement;
248
+ if (!videoElement) return
249
+
250
+ const deviceController = new DefaultDeviceController(new SwappableLogger(new ConsoleLogger('SDK', LogLevel.WARN)), { enableWebAudio: true });
251
+
252
+ //List the video device list
253
+ deviceController.listVideoInputDevices()
254
+ .then(deviceList => {
255
+ //Choose video device
256
+ deviceController.chooseVideoInputDevice(deviceList[0].deviceId)
257
+ .then(() => {
258
+ //Start video preview
259
+ deviceController.startVideoPreviewForVideoInput(videoElement);
260
+ }).catch(console.error)
261
+ }).catch(console.error)
262
+
263
+ // disconnect camera
264
+ return () => {
265
+ deviceController.destroy()
266
+ }
267
+ }, [])
268
+
269
+ return <video id={PREVIEW_ELEMENT_ID} style={style}/>
270
+ }
168
271
  export { VideoTileGrid }
@@ -29,11 +29,15 @@ export interface VideoViewProps {
29
29
  style?: CSSProperties,
30
30
  }
31
31
 
32
+ export interface JoinVideoCallProps {
33
+ onCallEnd?: () => void;
34
+ }
32
35
  export interface JoinVideoCallReturnType {
33
36
  meeting: CallContext['meeting'],
34
37
  videoIsEnabled: CallContext['videoIsEnabled'],
35
38
  toggleVideo: CallContext['toggleVideo'],
36
- joinMeeting: (meetingInfo: { Meeting: MeetingInfo }, attendeeInfo: { Attendee: AttendeeInfo }) => Promise<void>,
39
+ leaveMeeting: () => void,
40
+ joinMeeting: (meetingInfo: { Meeting: MeetingInfo } | string, attendeeInfo: { Attendee: AttendeeInfo }) => Promise<void>,
37
41
  }
38
42
 
39
43
  export interface StartVideoCallReturnType {
@@ -42,7 +46,7 @@ export interface StartVideoCallReturnType {
42
46
  meeting: CallContext['meeting'],
43
47
  videoIsEnabled: CallContext['videoIsEnabled'],
44
48
  toggleVideo: CallContext['toggleVideo'],
45
- createAndStartMeeting: (initialAttendees?: UserIdentity[]) => Promise<void>,
49
+ createAndStartMeeting: (initialAttendees?: UserIdentity[]) => Promise<string>,
46
50
  addAttendees: (attendees: UserIdentity[]) => Promise<void>,
47
51
  endMeeting: () => Promise<void>,
48
52
  }