@fraku/video 0.0.1

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.
Files changed (44) hide show
  1. package/README.md +2 -0
  2. package/package.json +75 -0
  3. package/src/App.tsx +7 -0
  4. package/src/components/ZoomVideoPlugin/ZoomVideoPlugin.stories.tsx +50 -0
  5. package/src/components/ZoomVideoPlugin/ZoomVideoPlugin.tsx +253 -0
  6. package/src/components/ZoomVideoPlugin/components/ButtonsDock/ButtonsDock.tsx +353 -0
  7. package/src/components/ZoomVideoPlugin/components/ButtonsDock/DockButton.tsx +90 -0
  8. package/src/components/ZoomVideoPlugin/components/ButtonsDock/MenuItemTemplate.tsx +35 -0
  9. package/src/components/ZoomVideoPlugin/components/ButtonsDock/index.ts +1 -0
  10. package/src/components/ZoomVideoPlugin/components/MobileIconButton/MobileIconButton.tsx +30 -0
  11. package/src/components/ZoomVideoPlugin/components/MobileIconButton/index.ts +1 -0
  12. package/src/components/ZoomVideoPlugin/components/Overlay/Overlay.tsx +74 -0
  13. package/src/components/ZoomVideoPlugin/components/Overlay/index.ts +1 -0
  14. package/src/components/ZoomVideoPlugin/components/ParticipantsList.tsx +52 -0
  15. package/src/components/ZoomVideoPlugin/components/SettingsOverlay/SettingsContent.tsx +19 -0
  16. package/src/components/ZoomVideoPlugin/components/SettingsOverlay/SettingsMenu.tsx +30 -0
  17. package/src/components/ZoomVideoPlugin/components/SettingsOverlay/SettingsOverlay.tsx +52 -0
  18. package/src/components/ZoomVideoPlugin/components/SettingsOverlay/Tabs/AudioSettings.tsx +191 -0
  19. package/src/components/ZoomVideoPlugin/components/SettingsOverlay/Tabs/BackgroundSettings.tsx +47 -0
  20. package/src/components/ZoomVideoPlugin/components/SettingsOverlay/Tabs/DropdownItemTemplate.tsx +20 -0
  21. package/src/components/ZoomVideoPlugin/components/SettingsOverlay/Tabs/DropdownValueTemplate.tsx +12 -0
  22. package/src/components/ZoomVideoPlugin/components/SettingsOverlay/Tabs/VideoSettings.tsx +30 -0
  23. package/src/components/ZoomVideoPlugin/components/SettingsOverlay/context.ts +20 -0
  24. package/src/components/ZoomVideoPlugin/components/SettingsOverlay/index.ts +1 -0
  25. package/src/components/ZoomVideoPlugin/components/Video.tsx +35 -0
  26. package/src/components/ZoomVideoPlugin/constants.ts +4 -0
  27. package/src/components/ZoomVideoPlugin/context.ts +86 -0
  28. package/src/components/ZoomVideoPlugin/hooks/useClientMessages.ts +142 -0
  29. package/src/components/ZoomVideoPlugin/hooks/useDeviceSize.ts +24 -0
  30. package/src/components/ZoomVideoPlugin/hooks/useStartVideoOptions.ts +14 -0
  31. package/src/components/ZoomVideoPlugin/hooks/useZoomVideoPlayer.tsx +142 -0
  32. package/src/components/ZoomVideoPlugin/index.ts +2 -0
  33. package/src/components/ZoomVideoPlugin/lib/platforms.ts +17 -0
  34. package/src/components/ZoomVideoPlugin/pages/AfterSession.tsx +14 -0
  35. package/src/components/ZoomVideoPlugin/pages/MainSession.tsx +53 -0
  36. package/src/components/ZoomVideoPlugin/pages/PanelistsSession.tsx +97 -0
  37. package/src/components/ZoomVideoPlugin/pages/PreSessionConfiguration.tsx +154 -0
  38. package/src/components/ZoomVideoPlugin/types.global.d.ts +15 -0
  39. package/src/components/ZoomVideoPlugin/types.ts +23 -0
  40. package/src/global.d.ts +46 -0
  41. package/src/index.css +4 -0
  42. package/src/index.ts +4 -0
  43. package/src/main.tsx +10 -0
  44. package/src/vite-env.d.ts +12 -0
@@ -0,0 +1,14 @@
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
+ }
@@ -0,0 +1,142 @@
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
+ }
@@ -0,0 +1,2 @@
1
+ export { default } from './ZoomVideoPlugin'
2
+ export type { ZoomVideoPluginProps } from './types'
@@ -0,0 +1,17 @@
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
+ }
@@ -0,0 +1,14 @@
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
@@ -0,0 +1,53 @@
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
@@ -0,0 +1,97 @@
1
+ import { useCallback, useRef } from 'react'
2
+ import { Button } from 'primereact/button'
3
+ import { useUnmount } from 'react-use'
4
+ import Video from '../components/Video'
5
+ import { useZoomVideoContext } from '../context'
6
+ import { useZoomVideoPlayer } from '../hooks/useZoomVideoPlayer'
7
+ import ParticipantsList from '../components/ParticipantsList'
8
+ import ButtonsDock from '../components/ButtonsDock'
9
+ import { SessionStep } from '../types'
10
+
11
+ const PanelistsSession = ({ initMainSession }: { initMainSession: () => void }) => {
12
+ const {
13
+ activeMicrophone,
14
+ activeAudioOutput,
15
+ activeVideoId,
16
+ mediaStream,
17
+ stopSession,
18
+ setIsCamOn,
19
+ setIsMicOn,
20
+ participants,
21
+ switchActiveMicrophone,
22
+ setActiveCamera,
23
+ switchActiveAudioOutput,
24
+ zmClient
25
+ } = useZoomVideoContext()
26
+
27
+ const fullRef = useRef<HTMLVideoElement>(null)
28
+
29
+ useZoomVideoPlayer({ fullRef })
30
+
31
+ const switchActiveCamera = useCallback(
32
+ (deviceId: string) => {
33
+ setActiveCamera(deviceId)
34
+ mediaStream?.switchCamera(deviceId)
35
+ },
36
+ [mediaStream, setActiveCamera]
37
+ )
38
+
39
+ const toggleMicrophone = useCallback(async () => {
40
+ if (!mediaStream || !zmClient) return
41
+ if (zmClient.getCurrentUserInfo().audio) {
42
+ await mediaStream.stopAudio().catch(console.warn)
43
+ setIsMicOn(false)
44
+ } else {
45
+ await mediaStream.startAudio({ microphoneId: activeMicrophone, speakerId: activeAudioOutput }).catch(console.warn)
46
+ setIsMicOn(true)
47
+ }
48
+ }, [activeMicrophone, activeAudioOutput, mediaStream, setIsMicOn, zmClient])
49
+
50
+ useUnmount(() => {
51
+ if (zmClient?.getSessionInfo().isInMeeting) {
52
+ if (zmClient?.getCurrentUserInfo().audio) {
53
+ mediaStream?.stopAudio().catch(console.warn)
54
+ }
55
+ if (zmClient?.getCurrentUserInfo()?.bVideoOn) {
56
+ mediaStream?.stopVideo().catch(console.warn)
57
+ }
58
+
59
+ mediaStream?.stopShareScreen().catch(console.warn)
60
+ }
61
+ })
62
+
63
+ return (
64
+ <div className="flex flex-col gap-24 py-16 justify-start w-full">
65
+ <div className="flex flex-col @sm:flex-row gap-8 p-16 items-center justify-between bg-warning-90 w-full">
66
+ <p>Estás en una conferencia de prueba, abre sesión para invitar a todos el listado de asistentes.</p>
67
+ <Button onClick={initMainSession} className="whitespace-pre-line">
68
+ Abrir videoconferencia
69
+ </Button>
70
+ </div>
71
+
72
+ <div className="flex flex-col gap-16 items-center justify-center w-full @sm:w-[640px] @md:w-[680px] mx-auto">
73
+ <Video fullRef={fullRef} />
74
+
75
+ <ButtonsDock
76
+ exit={stopSession}
77
+ sessionStep={SessionStep.OnlyPanelistsSession}
78
+ setIsCamOn={() => setIsCamOn((prev) => !prev)}
79
+ setIsMicOn={toggleMicrophone}
80
+ setActiveMicrophone={switchActiveMicrophone}
81
+ setActiveCamera={switchActiveCamera}
82
+ setActiveAudioOutput={switchActiveAudioOutput}
83
+ />
84
+
85
+ <p>Active video id: {activeVideoId}</p>
86
+ <ParticipantsList
87
+ zmClient={zmClient}
88
+ setIsCamOn={setIsCamOn}
89
+ setIsMicOn={setIsMicOn}
90
+ participants={participants}
91
+ />
92
+ </div>
93
+ </div>
94
+ )
95
+ }
96
+
97
+ export default PanelistsSession
@@ -0,0 +1,154 @@
1
+ import { useRef, useState, useCallback } from 'react'
2
+ import ZoomVideo from '@zoom/videosdk'
3
+ import { Button } from 'primereact/button'
4
+ import { useEffectOnce, useUnmount } from 'react-use'
5
+ import { useZoomVideoContext } from '../context'
6
+ import Video from '../components/Video'
7
+ import ButtonsDock from '../components/ButtonsDock'
8
+ import { VIDEO_PLACEHOLDER } from '../constants'
9
+ import { SessionStep } from '../types'
10
+
11
+ const PreSessionConfiguration = ({ joinSession }: { joinSession: () => Promise<void> }) => {
12
+ const {
13
+ closeParentContainer,
14
+ localAudio,
15
+ localVideo,
16
+ setIsCamOn,
17
+ setIsMicOn,
18
+ setActiveCamera,
19
+ switchActiveAudioOutput,
20
+ switchActiveMicrophone,
21
+ setLocalVideo
22
+ } = useZoomVideoContext()
23
+ const [isVideoStarted, setIsVideoStarted] = useState(false)
24
+ const [isAudioStarted, setIsAudioStarted] = useState(false)
25
+ const videoRef = useRef<HTMLVideoElement | null>(null)
26
+
27
+ const stopLocalVideo = useCallback(async () => {
28
+ const containerEl = videoRef.current
29
+ if (!containerEl || !localVideo) return
30
+ try {
31
+ await localVideo.stop()
32
+ setIsVideoStarted(false)
33
+ containerEl.innerHTML = VIDEO_PLACEHOLDER
34
+ setIsCamOn(false)
35
+ } catch (error) {
36
+ console.error('Error stopping local video:', error)
37
+ await localVideo.start(containerEl)
38
+ setIsVideoStarted(true)
39
+ }
40
+ }, [localVideo, setIsCamOn])
41
+
42
+ const stopLocalAudio = useCallback(async () => {
43
+ if (!localAudio) return
44
+ try {
45
+ await localAudio.stop()
46
+ setIsAudioStarted(false)
47
+ setIsMicOn(false)
48
+ } catch (error) {
49
+ console.error('Error stopping local audio:', error)
50
+ await localAudio.start()
51
+ }
52
+ }, [localAudio, setIsMicOn])
53
+
54
+ const toggleLocalVideo = useCallback(async () => {
55
+ if (!localVideo) return
56
+ if (isVideoStarted) {
57
+ await stopLocalVideo()
58
+ } else if (videoRef.current) {
59
+ try {
60
+ await localVideo.start(videoRef.current)
61
+ setIsVideoStarted(true)
62
+ setIsCamOn(true)
63
+ } catch (error) {
64
+ console.error('Error starting local video:', error)
65
+ }
66
+ }
67
+ }, [localVideo, isVideoStarted, stopLocalVideo, setIsCamOn])
68
+
69
+ const toggleAudioMuteStatus = useCallback(async () => {
70
+ if (!localAudio) return
71
+ if (isAudioStarted) {
72
+ await stopLocalAudio()
73
+ } else {
74
+ try {
75
+ await localAudio.start()
76
+ setIsAudioStarted(true)
77
+ setIsMicOn(true)
78
+ } catch (error) {
79
+ console.error('Error starting local audio:', error)
80
+ }
81
+ }
82
+ }, [isAudioStarted, localAudio, stopLocalAudio, setIsMicOn])
83
+
84
+ const switchActiveCamera = async (deviceId: string) => {
85
+ if (!localVideo) return
86
+ setActiveCamera(deviceId)
87
+ if (!isVideoStarted) return
88
+ await localVideo.stop()
89
+ setLocalVideo(ZoomVideo.createLocalVideoTrack(deviceId))
90
+ if (!videoRef.current) return
91
+ await localVideo.start(videoRef.current)
92
+ }
93
+
94
+ const exit = useCallback(async () => {
95
+ if (isVideoStarted) await stopLocalVideo()
96
+ if (isAudioStarted) await stopLocalAudio()
97
+ closeParentContainer()
98
+ }, [isVideoStarted, isAudioStarted, stopLocalVideo, stopLocalAudio, closeParentContainer])
99
+
100
+ useEffectOnce(() => {
101
+ const startLocalMedia = async () => {
102
+ if (!videoRef.current || !localVideo || !localAudio) return
103
+ try {
104
+ await localVideo.start(videoRef.current).catch(console.error)
105
+ await localAudio.start().catch(console.error)
106
+ setIsVideoStarted(true)
107
+ setIsAudioStarted(true)
108
+ setIsCamOn(true)
109
+ setIsMicOn(true)
110
+ } catch (error) {
111
+ console.error('Error starting local media:', error)
112
+ }
113
+ }
114
+
115
+ startLocalMedia()
116
+ })
117
+
118
+ useUnmount(async () => {
119
+ if (isAudioStarted) {
120
+ await stopLocalAudio().catch(console.error)
121
+ }
122
+ if (isVideoStarted) {
123
+ await stopLocalVideo().catch(console.error)
124
+ }
125
+ })
126
+
127
+ return (
128
+ <div className="flex flex-col gap-24 py-16 items-center justify-between w-full">
129
+ <section className="flex flex-col gap-8 py-16 items-center text-center">
130
+ <h3>Acceso a la videoconferencia</h3>
131
+ <p>Comprueba que todo funciona correctamente para una mejor experiencia en la videollamada.</p>
132
+ </section>
133
+
134
+ <div className="flex flex-col gap-16 items-center justify-center w-full sm:w-[640px] md:w-[680px] mx-auto">
135
+ <Video fullRef={videoRef} />
136
+ <ButtonsDock
137
+ exit={exit}
138
+ sessionStep={SessionStep.LocalSettingsConfiguration}
139
+ setIsCamOn={toggleLocalVideo}
140
+ setIsMicOn={toggleAudioMuteStatus}
141
+ setActiveMicrophone={switchActiveMicrophone}
142
+ setActiveCamera={switchActiveCamera}
143
+ setActiveAudioOutput={switchActiveAudioOutput}
144
+ />
145
+ </div>
146
+
147
+ <section className="flex flex-col gap-8">
148
+ <Button onClick={joinSession} label="Acceder a la videoconferencia" />
149
+ </section>
150
+ </div>
151
+ )
152
+ }
153
+
154
+ export default PreSessionConfiguration
@@ -0,0 +1,15 @@
1
+ // Global type declarations for Zoom Video SDK custom elements
2
+ declare global {
3
+ interface Window {
4
+ webEndpoint?: string
5
+ }
6
+
7
+ namespace JSX {
8
+ interface IntrinsicElements {
9
+ 'video-player-container': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>
10
+ 'video-player': React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>
11
+ }
12
+ }
13
+ }
14
+
15
+ export {}
@@ -0,0 +1,23 @@
1
+ import React from 'react'
2
+
3
+ export enum SessionStep {
4
+ LocalSettingsConfiguration,
5
+ OnlyPanelistsSession,
6
+ MainSession,
7
+ AfterSession
8
+ }
9
+
10
+ export type Credentials = {
11
+ sessionName: string
12
+ signature: string
13
+ userName: string
14
+ sessionPasscode: string
15
+ }
16
+
17
+ export type ReactSetter<T> = React.Dispatch<React.SetStateAction<T>>
18
+
19
+ export type ZoomVideoPluginProps = {
20
+ credentials: Credentials | null
21
+ closeParentContainer: () => void
22
+ setIsCloseButtonVisible: ReactSetter<boolean>
23
+ }
@@ -0,0 +1,46 @@
1
+ type PromiseType<T extends (..._: any) => any> = Awaited<ReturnType<T>>
2
+
3
+ type DeepPartial<T> = {
4
+ [P in keyof T]?: DeepPartial<T[P]>
5
+ }
6
+
7
+ type ReactSetter<T> = React.Dispatch<React.SetStateAction<T>>
8
+
9
+ interface AwsWafCaptcha {
10
+ renderCaptcha: (
11
+ container: HTMLElement,
12
+ options: {
13
+ apiKey: string
14
+ onSuccess: (wafToken: string) => void
15
+ onLoad?: () => void
16
+ onError?: () => void
17
+ onPuzzleTimeout?: () => void
18
+ onPuzzleIncorrect?: () => void
19
+ onPuzzleCorrect?: () => void
20
+ defaultLocale?: string
21
+ disableLanguageSelector?: boolean
22
+ dynamicWidth?: boolean
23
+ skipTitle?: boolean
24
+ }
25
+ ) => void
26
+ }
27
+
28
+ interface AwsWafIntegration {
29
+ getToken: () => Promise<string>
30
+ hasToken: () => boolean
31
+ forceRefreshToken: () => Promise<void>
32
+ }
33
+
34
+ interface Window {
35
+ dataLayer: Record<string, unknown>[]
36
+ AwsWafCaptcha: AwsWafCaptcha
37
+ AwsWafIntegration: AwsWafIntegration
38
+ awsWafCookieDomainList: string[]
39
+
40
+ documentPictureInPicture: {
41
+ requestWindow: (options: { width: number; height: number }) => Promise<Window>
42
+ addEventListener: (type: 'enter' | 'leave', listener: (event: any) => void) => void
43
+ window: Window | null
44
+ }
45
+ webEndpoint: string | undefined
46
+ }
package/src/index.css ADDED
@@ -0,0 +1,4 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ import './index.css'
2
+
3
+ export { default as ZoomVideoPlugin } from './components/ZoomVideoPlugin'
4
+ export type { ZoomVideoPluginProps } from './components/ZoomVideoPlugin'
package/src/main.tsx ADDED
@@ -0,0 +1,10 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App'
4
+ import './index.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ )
@@ -0,0 +1,12 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_ZOOM_SESSION_NAME?: string
5
+ readonly VITE_ZOOM_SIGNATURE?: string
6
+ readonly VITE_ZOOM_USER_NAME?: string
7
+ readonly VITE_ZOOM_SESSION_PASSCODE?: string
8
+ }
9
+
10
+ interface ImportMeta {
11
+ readonly env: ImportMetaEnv
12
+ }