@fraku/video 0.0.1 → 0.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.
- package/package.json +2 -3
- package/src/App.tsx +0 -7
- package/src/components/ZoomVideoPlugin/ZoomVideoPlugin.stories.tsx +0 -50
- package/src/components/ZoomVideoPlugin/ZoomVideoPlugin.tsx +0 -253
- package/src/components/ZoomVideoPlugin/components/ButtonsDock/ButtonsDock.tsx +0 -353
- package/src/components/ZoomVideoPlugin/components/ButtonsDock/DockButton.tsx +0 -90
- package/src/components/ZoomVideoPlugin/components/ButtonsDock/MenuItemTemplate.tsx +0 -35
- package/src/components/ZoomVideoPlugin/components/ButtonsDock/index.ts +0 -1
- package/src/components/ZoomVideoPlugin/components/MobileIconButton/MobileIconButton.tsx +0 -30
- package/src/components/ZoomVideoPlugin/components/MobileIconButton/index.ts +0 -1
- package/src/components/ZoomVideoPlugin/components/Overlay/Overlay.tsx +0 -74
- package/src/components/ZoomVideoPlugin/components/Overlay/index.ts +0 -1
- package/src/components/ZoomVideoPlugin/components/ParticipantsList.tsx +0 -52
- package/src/components/ZoomVideoPlugin/components/SettingsOverlay/SettingsContent.tsx +0 -19
- package/src/components/ZoomVideoPlugin/components/SettingsOverlay/SettingsMenu.tsx +0 -30
- package/src/components/ZoomVideoPlugin/components/SettingsOverlay/SettingsOverlay.tsx +0 -52
- package/src/components/ZoomVideoPlugin/components/SettingsOverlay/Tabs/AudioSettings.tsx +0 -191
- package/src/components/ZoomVideoPlugin/components/SettingsOverlay/Tabs/BackgroundSettings.tsx +0 -47
- package/src/components/ZoomVideoPlugin/components/SettingsOverlay/Tabs/DropdownItemTemplate.tsx +0 -20
- package/src/components/ZoomVideoPlugin/components/SettingsOverlay/Tabs/DropdownValueTemplate.tsx +0 -12
- package/src/components/ZoomVideoPlugin/components/SettingsOverlay/Tabs/VideoSettings.tsx +0 -30
- package/src/components/ZoomVideoPlugin/components/SettingsOverlay/context.ts +0 -20
- package/src/components/ZoomVideoPlugin/components/SettingsOverlay/index.ts +0 -1
- package/src/components/ZoomVideoPlugin/components/Video.tsx +0 -35
- package/src/components/ZoomVideoPlugin/constants.ts +0 -4
- package/src/components/ZoomVideoPlugin/context.ts +0 -86
- package/src/components/ZoomVideoPlugin/hooks/useClientMessages.ts +0 -142
- package/src/components/ZoomVideoPlugin/hooks/useDeviceSize.ts +0 -24
- package/src/components/ZoomVideoPlugin/hooks/useStartVideoOptions.ts +0 -14
- package/src/components/ZoomVideoPlugin/hooks/useZoomVideoPlayer.tsx +0 -142
- package/src/components/ZoomVideoPlugin/index.ts +0 -2
- package/src/components/ZoomVideoPlugin/lib/platforms.ts +0 -17
- package/src/components/ZoomVideoPlugin/pages/AfterSession.tsx +0 -14
- package/src/components/ZoomVideoPlugin/pages/MainSession.tsx +0 -53
- package/src/components/ZoomVideoPlugin/pages/PanelistsSession.tsx +0 -97
- package/src/components/ZoomVideoPlugin/pages/PreSessionConfiguration.tsx +0 -154
- package/src/components/ZoomVideoPlugin/types.global.d.ts +0 -15
- package/src/components/ZoomVideoPlugin/types.ts +0 -23
- package/src/global.d.ts +0 -46
- package/src/index.css +0 -4
- package/src/index.ts +0 -4
- package/src/main.tsx +0 -10
- package/src/vite-env.d.ts +0 -12
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { Dropdown } from 'primereact/dropdown'
|
|
2
|
-
import { useZoomVideoContext } from '../../../context'
|
|
3
|
-
import DropdownValueTemplate from './DropdownValueTemplate'
|
|
4
|
-
import DropdownItemTemplate from './DropdownItemTemplate'
|
|
5
|
-
|
|
6
|
-
const VideoSettings = () => {
|
|
7
|
-
const { activeCamera, cameraList, setActiveCamera } = useZoomVideoContext()
|
|
8
|
-
|
|
9
|
-
const cameraOptions = cameraList.map((camera) => ({
|
|
10
|
-
label: camera.label || `Cámara ${camera.deviceId}`,
|
|
11
|
-
value: camera.deviceId
|
|
12
|
-
}))
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<div className="flex flex-col">
|
|
16
|
-
<p className="text-s font-bold mb-8">Cámara</p>
|
|
17
|
-
<Dropdown
|
|
18
|
-
itemTemplate={(option) => <DropdownItemTemplate option={option} isActive={option.value === activeCamera} />}
|
|
19
|
-
className="mb-16"
|
|
20
|
-
onChange={(e) => setActiveCamera(e.value)}
|
|
21
|
-
options={cameraOptions}
|
|
22
|
-
placeholder="Selecciona una cámara"
|
|
23
|
-
value={activeCamera ?? ''}
|
|
24
|
-
valueTemplate={(option) => <DropdownValueTemplate option={option} icon="fa-regular fa-video" />}
|
|
25
|
-
/>
|
|
26
|
-
</div>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export default VideoSettings
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { useContext, createContext } from 'react'
|
|
2
|
-
import { ReactSetter } from '../../types'
|
|
3
|
-
|
|
4
|
-
export enum SettingsTab {
|
|
5
|
-
Audio = 'Audio',
|
|
6
|
-
Video = 'Video',
|
|
7
|
-
Background = 'Background'
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type SettingsOverlayContextProps = {
|
|
11
|
-
selectedSettingsTab: SettingsTab
|
|
12
|
-
setSelectedSettingsTab: ReactSetter<SettingsTab>
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const settingsOverlayContext = createContext<SettingsOverlayContextProps>({
|
|
16
|
-
selectedSettingsTab: SettingsTab.Audio,
|
|
17
|
-
setSelectedSettingsTab: () => {}
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
export const useSettingsOverlayContext = () => useContext(settingsOverlayContext)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from './SettingsOverlay'
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import styled from 'styled-components'
|
|
2
|
-
|
|
3
|
-
type VideoProps = {
|
|
4
|
-
fullRef: React.RefObject<HTMLVideoElement> | null
|
|
5
|
-
}
|
|
6
|
-
const Video = ({ fullRef }: VideoProps) => {
|
|
7
|
-
return (
|
|
8
|
-
<StyledVideoContainer>
|
|
9
|
-
<video-player-container className="video-player-container">
|
|
10
|
-
<video-player ref={fullRef} className="video-player" />
|
|
11
|
-
</video-player-container>
|
|
12
|
-
</StyledVideoContainer>
|
|
13
|
-
)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const StyledVideoContainer = styled.div`
|
|
17
|
-
width: 100%;
|
|
18
|
-
height: auto;
|
|
19
|
-
aspect-ratio: 16/9;
|
|
20
|
-
|
|
21
|
-
& video-player-container {
|
|
22
|
-
margin-left: auto;
|
|
23
|
-
margin-right: auto;
|
|
24
|
-
width: 100%;
|
|
25
|
-
height: auto;
|
|
26
|
-
|
|
27
|
-
video-player {
|
|
28
|
-
width: 100%;
|
|
29
|
-
height: auto;
|
|
30
|
-
aspect-ratio: 16/9;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
`
|
|
34
|
-
|
|
35
|
-
export default Video
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react'
|
|
2
|
-
import ZoomVideo, {
|
|
3
|
-
ActiveSpeaker,
|
|
4
|
-
ConnectionState,
|
|
5
|
-
LocalAudioTrack,
|
|
6
|
-
LocalVideoTrack,
|
|
7
|
-
MediaDevice,
|
|
8
|
-
Participant,
|
|
9
|
-
Stream,
|
|
10
|
-
VideoClient
|
|
11
|
-
} from '@zoom/videosdk'
|
|
12
|
-
import { Breakpoint } from './hooks/useDeviceSize'
|
|
13
|
-
|
|
14
|
-
export const zmClient = ZoomVideo.createClient()
|
|
15
|
-
|
|
16
|
-
export type ZoomVideoContextType = {
|
|
17
|
-
activeAudioOutput: string | undefined
|
|
18
|
-
activeCamera: string | undefined
|
|
19
|
-
activeMicrophone: string | undefined
|
|
20
|
-
activeSpeakers: ActiveSpeaker[]
|
|
21
|
-
activeVideoId: number | null
|
|
22
|
-
audioOutputList: MediaDevice[]
|
|
23
|
-
breakpoint: Breakpoint
|
|
24
|
-
cameraList: MediaDevice[]
|
|
25
|
-
closeParentContainer: () => void
|
|
26
|
-
connectionState: ConnectionState | null
|
|
27
|
-
isBlurred: boolean
|
|
28
|
-
isCamOn: boolean
|
|
29
|
-
isMicOn: boolean
|
|
30
|
-
localAudio: LocalAudioTrack | undefined
|
|
31
|
-
localVideo: LocalVideoTrack | undefined
|
|
32
|
-
mediaStream: typeof Stream | null
|
|
33
|
-
micList: MediaDevice[]
|
|
34
|
-
participants: Participant[]
|
|
35
|
-
setActiveCamera: ReactSetter<string | undefined>
|
|
36
|
-
setIsBlurred: ReactSetter<boolean>
|
|
37
|
-
setIsCamOn: ReactSetter<boolean>
|
|
38
|
-
setIsMicOn: ReactSetter<boolean>
|
|
39
|
-
setLocalAudio: ReactSetter<LocalAudioTrack>
|
|
40
|
-
setLocalVideo: ReactSetter<LocalVideoTrack>
|
|
41
|
-
setMediaStream: ReactSetter<typeof Stream | null>
|
|
42
|
-
setParticipants: ReactSetter<Participant[]>
|
|
43
|
-
stopSession: () => Promise<void>
|
|
44
|
-
switchActiveAudioOutput: (deviceId: string) => Promise<void>
|
|
45
|
-
switchActiveMicrophone: (deviceId: string) => Promise<void>
|
|
46
|
-
zmClient: typeof VideoClient
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const ZoomVideoContext = createContext<ZoomVideoContextType>({
|
|
50
|
-
activeAudioOutput: undefined,
|
|
51
|
-
activeCamera: undefined,
|
|
52
|
-
activeMicrophone: undefined,
|
|
53
|
-
activeSpeakers: [],
|
|
54
|
-
activeVideoId: null,
|
|
55
|
-
audioOutputList: [],
|
|
56
|
-
breakpoint: Breakpoint.Mobile,
|
|
57
|
-
cameraList: [],
|
|
58
|
-
closeParentContainer: () => {},
|
|
59
|
-
connectionState: null,
|
|
60
|
-
isBlurred: false,
|
|
61
|
-
isCamOn: false,
|
|
62
|
-
isMicOn: false,
|
|
63
|
-
localAudio: undefined,
|
|
64
|
-
localVideo: undefined,
|
|
65
|
-
mediaStream: null,
|
|
66
|
-
micList: [],
|
|
67
|
-
participants: [],
|
|
68
|
-
setActiveCamera: () => {},
|
|
69
|
-
setIsBlurred: () => {},
|
|
70
|
-
setIsCamOn: () => {},
|
|
71
|
-
setIsMicOn: () => {},
|
|
72
|
-
setLocalAudio: () => {},
|
|
73
|
-
setLocalVideo: () => {},
|
|
74
|
-
setMediaStream: () => {},
|
|
75
|
-
setParticipants: () => {},
|
|
76
|
-
stopSession: async () => {},
|
|
77
|
-
switchActiveAudioOutput: () => Promise.resolve(),
|
|
78
|
-
switchActiveMicrophone: () => Promise.resolve(),
|
|
79
|
-
zmClient
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
export const useZoomVideoContext = () => {
|
|
83
|
-
const ctx = useContext(ZoomVideoContext)
|
|
84
|
-
if (!ctx) throw new Error('useZoomVideo must be used within ZoomVideoProvider')
|
|
85
|
-
return ctx
|
|
86
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
type ActiveSpeaker,
|
|
4
|
-
CommandChannelMsg,
|
|
5
|
-
ConnectionChangePayload,
|
|
6
|
-
ConnectionState,
|
|
7
|
-
MediaDevice,
|
|
8
|
-
type Participant,
|
|
9
|
-
type Stream,
|
|
10
|
-
VideoActiveState,
|
|
11
|
-
type VideoClient
|
|
12
|
-
} from '@zoom/videosdk'
|
|
13
|
-
import { isAndroidOrIOSBrowser } from '../lib/platforms'
|
|
14
|
-
|
|
15
|
-
type Props = {
|
|
16
|
-
zmClient: typeof VideoClient | null
|
|
17
|
-
mediaStream: typeof Stream | null
|
|
18
|
-
setActiveMicrophone: ReactSetter<string | undefined>
|
|
19
|
-
setActiveAudioOutput: ReactSetter<string | undefined>
|
|
20
|
-
setActiveCamera: ReactSetter<string | undefined>
|
|
21
|
-
setMicList: ReactSetter<MediaDevice[]>
|
|
22
|
-
setAudioOutputList: ReactSetter<MediaDevice[]>
|
|
23
|
-
setCameraList: ReactSetter<MediaDevice[]>
|
|
24
|
-
setConnectionState: ReactSetter<ConnectionState | null>
|
|
25
|
-
setIsCamOn: ReactSetter<boolean>
|
|
26
|
-
setIsMicOn: ReactSetter<boolean>
|
|
27
|
-
setActiveSpeakers: ReactSetter<ActiveSpeaker[]>
|
|
28
|
-
setParticipants: ReactSetter<Participant[]>
|
|
29
|
-
setActiveVideoId: ReactSetter<number | null>
|
|
30
|
-
setHandRaises: ReactSetter<Record<string, boolean>>
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const useClientMessages = ({
|
|
34
|
-
zmClient,
|
|
35
|
-
mediaStream,
|
|
36
|
-
setActiveMicrophone,
|
|
37
|
-
setActiveAudioOutput,
|
|
38
|
-
setActiveCamera,
|
|
39
|
-
setMicList,
|
|
40
|
-
setAudioOutputList,
|
|
41
|
-
setCameraList,
|
|
42
|
-
setConnectionState,
|
|
43
|
-
setIsCamOn,
|
|
44
|
-
setIsMicOn,
|
|
45
|
-
setActiveSpeakers,
|
|
46
|
-
setActiveVideoId,
|
|
47
|
-
setParticipants,
|
|
48
|
-
setHandRaises
|
|
49
|
-
}: Props) => {
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if (!mediaStream || !zmClient) return
|
|
52
|
-
|
|
53
|
-
setActiveVideoId(mediaStream?.getActiveVideoId() ?? null)
|
|
54
|
-
|
|
55
|
-
const onCommandChannelMessage = (payload: CommandChannelMsg) => {
|
|
56
|
-
console.log('onCommandChannelMessage payload: ', payload)
|
|
57
|
-
try {
|
|
58
|
-
const data = JSON.parse(payload?.text)
|
|
59
|
-
if (data.type === 'RAISE_HAND') {
|
|
60
|
-
setHandRaises((prev) => ({ ...prev, [data.userId]: true }))
|
|
61
|
-
} else if (data.type === 'LOWER_HAND') {
|
|
62
|
-
setHandRaises((prev) => ({ ...prev, [data.userId]: false }))
|
|
63
|
-
}
|
|
64
|
-
} catch (e) {
|
|
65
|
-
console.warn('Invalid command message', e)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const onConnectionChange = (payload: ConnectionChangePayload) => {
|
|
70
|
-
setConnectionState(payload.state)
|
|
71
|
-
console.log(`Connection state changed to ${payload.state}, reason: ${payload.reason}`)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const onParticipantChange = () => {
|
|
75
|
-
const allUsers = zmClient.getAllUser()
|
|
76
|
-
setParticipants(allUsers)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const onActiveVideoChange = (payload: { state: VideoActiveState; userId: number }) => {
|
|
80
|
-
const { state, userId } = payload
|
|
81
|
-
if (state === 'Active') {
|
|
82
|
-
setActiveVideoId(userId)
|
|
83
|
-
console.log(`User ${userId} video is active`)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const onActiveSpeakerChange = (payload: ActiveSpeaker[]) => {
|
|
88
|
-
if (Array.isArray(payload)) {
|
|
89
|
-
setActiveSpeakers(payload)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const onDeviceChange = () => {
|
|
94
|
-
if (mediaStream) {
|
|
95
|
-
console.log('onDeviceChange: update device list')
|
|
96
|
-
setMicList(mediaStream.getMicList())
|
|
97
|
-
setAudioOutputList(mediaStream.getSpeakerList())
|
|
98
|
-
if (!isAndroidOrIOSBrowser()) setCameraList(mediaStream.getCameraList())
|
|
99
|
-
setActiveMicrophone(mediaStream.getActiveMicrophone())
|
|
100
|
-
setActiveAudioOutput(mediaStream.getActiveSpeaker())
|
|
101
|
-
setActiveCamera(mediaStream.getActiveCamera())
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/* https://marketplacefront.zoom.us/sdk/custom/web/modules/ZoomVideo.VideoClient.html#on */
|
|
106
|
-
zmClient.on('active-speaker', onActiveSpeakerChange)
|
|
107
|
-
zmClient.on('command-channel-message', onCommandChannelMessage)
|
|
108
|
-
zmClient.on('connection-change', onConnectionChange)
|
|
109
|
-
zmClient.on('device-change', onDeviceChange)
|
|
110
|
-
zmClient.on('user-added', onParticipantChange)
|
|
111
|
-
zmClient.on('user-removed', onParticipantChange)
|
|
112
|
-
zmClient.on('user-updated', onParticipantChange)
|
|
113
|
-
zmClient.on('video-active-change', onActiveVideoChange) // It fires when the active speaker is talking for more than one second, allowing for a smoother user experience than the Audio active-speaker event. Event fires for active speakers who have their video on or off
|
|
114
|
-
|
|
115
|
-
return () => {
|
|
116
|
-
zmClient.off('active-speaker', onActiveSpeakerChange)
|
|
117
|
-
zmClient.off('command-channel-message', onCommandChannelMessage)
|
|
118
|
-
zmClient.off('connection-change', onConnectionChange)
|
|
119
|
-
zmClient.off('device-change', onDeviceChange)
|
|
120
|
-
zmClient.off('user-added', onParticipantChange)
|
|
121
|
-
zmClient.off('user-removed', onParticipantChange)
|
|
122
|
-
zmClient.off('user-updated', onParticipantChange)
|
|
123
|
-
zmClient.off('video-active-change', onActiveVideoChange)
|
|
124
|
-
}
|
|
125
|
-
}, [
|
|
126
|
-
mediaStream,
|
|
127
|
-
setActiveCamera,
|
|
128
|
-
setActiveMicrophone,
|
|
129
|
-
setActiveAudioOutput,
|
|
130
|
-
setActiveSpeakers,
|
|
131
|
-
setActiveVideoId,
|
|
132
|
-
setCameraList,
|
|
133
|
-
setConnectionState,
|
|
134
|
-
setHandRaises,
|
|
135
|
-
setIsCamOn,
|
|
136
|
-
setIsMicOn,
|
|
137
|
-
setMicList,
|
|
138
|
-
setParticipants,
|
|
139
|
-
zmClient,
|
|
140
|
-
setAudioOutputList
|
|
141
|
-
])
|
|
142
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react'
|
|
2
|
-
import { useMeasure } from 'react-use'
|
|
3
|
-
|
|
4
|
-
export enum Breakpoint {
|
|
5
|
-
Mobile,
|
|
6
|
-
Tablet,
|
|
7
|
-
Desktop
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// TODO: Use tailwind screens when created a new project with zoom video
|
|
11
|
-
const MOBILE_MAX = 768
|
|
12
|
-
const TABLET_MAX = 1024
|
|
13
|
-
|
|
14
|
-
export const useContainerSize = () => {
|
|
15
|
-
const [ref, { width }] = useMeasure<HTMLDivElement>()
|
|
16
|
-
|
|
17
|
-
const breakpoint = useMemo(() => {
|
|
18
|
-
if (width < MOBILE_MAX) return Breakpoint.Mobile
|
|
19
|
-
if (width < TABLET_MAX) return Breakpoint.Tablet
|
|
20
|
-
return Breakpoint.Desktop
|
|
21
|
-
}, [width])
|
|
22
|
-
|
|
23
|
-
return { ref, width, breakpoint }
|
|
24
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Stream } from '@zoom/videosdk'
|
|
2
|
-
import { useMemo } from 'react'
|
|
3
|
-
|
|
4
|
-
export const useStartVideoOptions = ({ mediaStream }: { mediaStream: typeof Stream | null }) => {
|
|
5
|
-
return useMemo(
|
|
6
|
-
() => ({
|
|
7
|
-
hd: true,
|
|
8
|
-
fullHd: true,
|
|
9
|
-
ptz: mediaStream?.isBrowserSupportPTZ(),
|
|
10
|
-
originalRatio: true
|
|
11
|
-
}),
|
|
12
|
-
[mediaStream]
|
|
13
|
-
)
|
|
14
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { useEffect, type RefObject } from 'react'
|
|
2
|
-
import { useEffectOnce } from 'react-use'
|
|
3
|
-
import { type VideoClient, type Participant, VideoQuality, VideoPlayer, ConnectionState } from '@zoom/videosdk'
|
|
4
|
-
import { VIDEO_PLACEHOLDER } from './../constants'
|
|
5
|
-
import { useZoomVideoContext } from '../context'
|
|
6
|
-
import { useStartVideoOptions } from './useStartVideoOptions'
|
|
7
|
-
|
|
8
|
-
type UseZoomVideoPlayerProps = {
|
|
9
|
-
fullRef: RefObject<HTMLVideoElement | null>
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/* Create a name tag for the video */
|
|
13
|
-
const createNameTag = ({ name, isMicOff }: { name: string; isMicOff: boolean }): HTMLDivElement => {
|
|
14
|
-
const container = document.createElement('div')
|
|
15
|
-
container.className = 'absolute top-[2px] px-4 py-4 opacity-[0.6] w-full h-fit flex items-center justify-between'
|
|
16
|
-
const nameTag = document.createElement('div')
|
|
17
|
-
container.appendChild(nameTag)
|
|
18
|
-
nameTag.textContent = name
|
|
19
|
-
nameTag.className = 'bg-neutral-40 text-s font-bold text-white px-4 py-4 rounded-m max-w-[60%] truncate'
|
|
20
|
-
|
|
21
|
-
const micOffSpan = document.createElement('i')
|
|
22
|
-
micOffSpan.className = 'fa-regular fa-microphone-slash w-24 h-24 !text-white'
|
|
23
|
-
isMicOff ? micOffSpan.classList.remove('hidden') : micOffSpan.classList.add('hidden')
|
|
24
|
-
container.appendChild(micOffSpan)
|
|
25
|
-
|
|
26
|
-
return container
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/* Determine which user video to show */
|
|
30
|
-
const getRenderTargetUserId = (
|
|
31
|
-
zmClient: typeof VideoClient,
|
|
32
|
-
participants: Participant[],
|
|
33
|
-
activeVideoId: number | null
|
|
34
|
-
): number | null => {
|
|
35
|
-
if (!participants?.length) return null
|
|
36
|
-
const selfId = zmClient?.getCurrentUserInfo()?.userId ?? null
|
|
37
|
-
if (participants.length === 1) return selfId
|
|
38
|
-
if (participants.length === 2) {
|
|
39
|
-
return participants.find((p) => p.userId !== selfId)?.userId ?? selfId
|
|
40
|
-
}
|
|
41
|
-
return activeVideoId ?? selfId
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export const useZoomVideoPlayer = ({ fullRef }: UseZoomVideoPlayerProps) => {
|
|
45
|
-
const {
|
|
46
|
-
activeAudioOutput,
|
|
47
|
-
activeMicrophone,
|
|
48
|
-
activeVideoId,
|
|
49
|
-
connectionState,
|
|
50
|
-
isCamOn,
|
|
51
|
-
isMicOn,
|
|
52
|
-
mediaStream,
|
|
53
|
-
participants,
|
|
54
|
-
zmClient
|
|
55
|
-
} = useZoomVideoContext()
|
|
56
|
-
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
const containerEl = fullRef.current
|
|
59
|
-
if (!mediaStream || !containerEl || !zmClient || connectionState === ConnectionState.Closed) return
|
|
60
|
-
|
|
61
|
-
const userId = getRenderTargetUserId(zmClient, participants, activeVideoId)
|
|
62
|
-
if (!userId) return
|
|
63
|
-
|
|
64
|
-
let videoElement: VideoPlayer | null = null
|
|
65
|
-
let wrapperEl: HTMLDivElement | null = null
|
|
66
|
-
|
|
67
|
-
const attach = async () => {
|
|
68
|
-
const participant = participants.find((p) => p.userId === userId)
|
|
69
|
-
const displayName = participant?.displayName ?? ''
|
|
70
|
-
const nameTag = createNameTag({ name: displayName, isMicOff: participant?.muted || !participant?.audio })
|
|
71
|
-
|
|
72
|
-
// Clear previous content
|
|
73
|
-
containerEl.innerHTML = ''
|
|
74
|
-
|
|
75
|
-
// Create wrapper for video and name tag
|
|
76
|
-
wrapperEl = document.createElement('div')
|
|
77
|
-
wrapperEl.className = 'relative w-full h-full'
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
// Create or reuse the video-player element
|
|
81
|
-
const element = await mediaStream.attachVideo(userId, VideoQuality.Video_720P)
|
|
82
|
-
videoElement = element as VideoPlayer
|
|
83
|
-
if (!videoElement) throw new Error('No video element')
|
|
84
|
-
|
|
85
|
-
// Add video
|
|
86
|
-
wrapperEl.appendChild(videoElement)
|
|
87
|
-
// Add name tag
|
|
88
|
-
wrapperEl.appendChild(nameTag)
|
|
89
|
-
// Append to container
|
|
90
|
-
containerEl.appendChild(wrapperEl)
|
|
91
|
-
} catch (err) {
|
|
92
|
-
console.warn('Failed to attach video, using placeholder:', err)
|
|
93
|
-
containerEl.innerHTML = VIDEO_PLACEHOLDER
|
|
94
|
-
containerEl.appendChild(nameTag)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
attach()
|
|
99
|
-
|
|
100
|
-
return () => {
|
|
101
|
-
if (videoElement && userId) {
|
|
102
|
-
try {
|
|
103
|
-
mediaStream.detachVideo(userId)
|
|
104
|
-
videoElement.remove()
|
|
105
|
-
} catch (err) {
|
|
106
|
-
console.warn('Failed to detach video:', err)
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
if (wrapperEl) wrapperEl.remove()
|
|
110
|
-
}
|
|
111
|
-
}, [activeVideoId, connectionState, fullRef, mediaStream, participants, zmClient])
|
|
112
|
-
|
|
113
|
-
// Manage local camera
|
|
114
|
-
const startVideoOptions = useStartVideoOptions({ mediaStream })
|
|
115
|
-
useEffect(() => {
|
|
116
|
-
if (!mediaStream || connectionState === ConnectionState.Closed) return
|
|
117
|
-
|
|
118
|
-
const toggleCam = async () => {
|
|
119
|
-
try {
|
|
120
|
-
const isCapturing = mediaStream.isCapturingVideo()
|
|
121
|
-
|
|
122
|
-
if (isCamOn && !isCapturing) {
|
|
123
|
-
await mediaStream.startVideo(startVideoOptions)
|
|
124
|
-
} else if (!isCamOn && isCapturing) {
|
|
125
|
-
await mediaStream.stopVideo()
|
|
126
|
-
}
|
|
127
|
-
} catch (err: any) {
|
|
128
|
-
console.warn('Video toggle failed:', err)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
toggleCam()
|
|
133
|
-
}, [mediaStream, isCamOn, startVideoOptions, connectionState])
|
|
134
|
-
|
|
135
|
-
// Initialize microphone on join if mic is on
|
|
136
|
-
useEffectOnce(() => {
|
|
137
|
-
if (!mediaStream || !isMicOn) {
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
|
-
mediaStream.startAudio({ microphoneId: activeMicrophone, speakerId: activeAudioOutput }).catch(console.warn)
|
|
141
|
-
})
|
|
142
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export const isIPad = (): boolean => {
|
|
2
|
-
const { userAgent, maxTouchPoints } = navigator
|
|
3
|
-
const isiPadClassic = /iPad/i.test(userAgent)
|
|
4
|
-
const isiPadOS = /Macintosh/i.test(userAgent) && maxTouchPoints > 1
|
|
5
|
-
return isiPadClassic || isiPadOS
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const isIOSMobile = (): boolean => {
|
|
9
|
-
const { userAgent } = navigator
|
|
10
|
-
return /iPhone|iPad|iPod/i.test(userAgent) || isIPad()
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const isAndroidBrowser = (): boolean => /Android/i.test(navigator.userAgent)
|
|
14
|
-
|
|
15
|
-
export const isAndroidOrIOSBrowser = (): boolean => {
|
|
16
|
-
return isAndroidBrowser() || isIOSMobile()
|
|
17
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Button } from 'primereact/button'
|
|
2
|
-
|
|
3
|
-
const AfterSession = ({ restartSession }: { restartSession: () => void }) => {
|
|
4
|
-
return (
|
|
5
|
-
<div className="flex flex-col gap-24 py-16 justify-center items-center w-full">
|
|
6
|
-
<h2>After Session</h2>
|
|
7
|
-
<Button className="w-fit" onClick={restartSession}>
|
|
8
|
-
Restart Session
|
|
9
|
-
</Button>
|
|
10
|
-
</div>
|
|
11
|
-
)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default AfterSession
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { useRef } from 'react'
|
|
2
|
-
import { useUnmount } from 'react-use'
|
|
3
|
-
import Video from '../components/Video'
|
|
4
|
-
import { Button } from 'primereact/button'
|
|
5
|
-
import { useZoomVideoContext } from '../context'
|
|
6
|
-
import { useZoomVideoPlayer } from '../hooks/useZoomVideoPlayer'
|
|
7
|
-
import ParticipantsList from '../components/ParticipantsList'
|
|
8
|
-
|
|
9
|
-
const MainSession = () => {
|
|
10
|
-
const { activeVideoId, zmClient, mediaStream, stopSession, setIsCamOn, setIsMicOn, participants } =
|
|
11
|
-
useZoomVideoContext()
|
|
12
|
-
|
|
13
|
-
const fullRef = useRef<HTMLVideoElement>(null)
|
|
14
|
-
|
|
15
|
-
useZoomVideoPlayer({ fullRef })
|
|
16
|
-
|
|
17
|
-
useUnmount(() => {
|
|
18
|
-
if (zmClient?.getSessionInfo().isInMeeting) {
|
|
19
|
-
if (zmClient?.getCurrentUserInfo().audio) {
|
|
20
|
-
mediaStream?.stopAudio().catch(console.warn)
|
|
21
|
-
}
|
|
22
|
-
if (zmClient?.getCurrentUserInfo()?.bVideoOn) {
|
|
23
|
-
mediaStream?.stopVideo().catch(console.warn)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
mediaStream?.stopShareScreen().catch(console.warn)
|
|
27
|
-
}
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div className="flex flex-col gap-24 py-16 justify-start w-full">
|
|
32
|
-
<div className="flex flex-col gap-16 items-center justify-center w-full">
|
|
33
|
-
<div className="flex flex-col gap-16 items-center justify-center w-full @sm:w-[640px] @md:w-[680px] mx-auto">
|
|
34
|
-
<Video fullRef={fullRef} />
|
|
35
|
-
|
|
36
|
-
<p>Active video id: {activeVideoId}</p>
|
|
37
|
-
<ParticipantsList
|
|
38
|
-
zmClient={zmClient}
|
|
39
|
-
setIsCamOn={setIsCamOn}
|
|
40
|
-
setIsMicOn={setIsMicOn}
|
|
41
|
-
participants={participants}
|
|
42
|
-
/>
|
|
43
|
-
|
|
44
|
-
<Button className="w-fit" onClick={stopSession}>
|
|
45
|
-
Stop Session
|
|
46
|
-
</Button>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export default MainSession
|