From f476e2755ae697b85d90accb32897f5188101e03 Mon Sep 17 00:00:00 2001 From: mayk-zoom <78444092+mayk-zoom@users.noreply.github.com> Date: Wed, 14 Jul 2021 00:31:47 -0700 Subject: [PATCH] Revert "Revert "update 1.1.3"" --- purejs-demo/package.json | 4 +- react-demo/package.json | 6 +- react-demo/src/App.tsx | 3 +- react-demo/src/feature/preview/preview.scss | 2 +- react-demo/src/feature/preview/preview.tsx | 249 +++++++++++------- .../src/feature/video/components/avatar.tsx | 7 +- .../src/feature/video/components/camera.scss | 46 ++++ .../src/feature/video/components/camera.tsx | 61 ++++- .../feature/video/components/microphone.scss | 47 +++- .../feature/video/components/microphone.tsx | 124 +++++++-- .../feature/video/components/video-footer.tsx | 64 ++++- .../src/feature/video/hooks/useRenderVideo.ts | 17 +- .../src/feature/video/video-layout-helper.ts | 9 +- react-demo/src/feature/video/video-types.d.ts | 4 + react-demo/src/feature/video/video.scss | 4 + react-demo/src/index.tsx | 2 + react-demo/src/utils/util.ts | 4 + 17 files changed, 527 insertions(+), 126 deletions(-) diff --git a/purejs-demo/package.json b/purejs-demo/package.json index 1676c5a..06e4e42 100644 --- a/purejs-demo/package.json +++ b/purejs-demo/package.json @@ -1,7 +1,7 @@ { "$schema": "http://json.schemastore.org/coffeelint", "name": "videosdk-demo-purejs", - "version": "1.1.0", + "version": "1.1.3", "description": "Zoom Web Video SDK Pure JS Demo", "main": "index.js", "scripts": { @@ -51,6 +51,6 @@ "webpack-dev-server": "3.11.2" }, "dependencies": { - "@zoom/videosdk": "1.1.0" + "@zoom/videosdk": "1.1.3" } } diff --git a/react-demo/package.json b/react-demo/package.json index b108727..5c69d5e 100644 --- a/react-demo/package.json +++ b/react-demo/package.json @@ -1,12 +1,13 @@ { "name": "react-video-sdk-demo", - "version": "1.1.0", + "version": "1.1.3", "private": true, "dependencies": { "@ant-design/icons": "^4.6.2", "@testing-library/jest-dom": "^5.11.9", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^12.8.3", + "@zoom/videosdk": "^1.1.3", "antd": "^4.14.1", "classnames": "^2.2.6", "crypto-js": "^4.0.0", @@ -18,8 +19,7 @@ "react-dom": "17.0.2", "react-router-dom": "^5.2.0", "react-scripts": "4.0.2", - "web-vitals": "1.1.1", - "@zoom/videosdk": "1.1.0" + "web-vitals": "1.1.1" }, "scripts": { "start": "react-app-rewired start", diff --git a/react-demo/src/App.tsx b/react-demo/src/App.tsx index acbb721..efdf6e2 100644 --- a/react-demo/src/App.tsx +++ b/react-demo/src/App.tsx @@ -87,9 +87,10 @@ function App(props: AppProps) { const [mediaStream, setMediaStream] = useState(null); const [chatClient, setChatClient] = useState(null); const zmClient = useContext(ZoomContext); + useEffect(() => { const init = async () => { - await zmClient.init("en-US", `${window.location.origin}/lib`); + await zmClient.init("en-US", `${window.location.origin}/lib`, 'zoom.us'); try { setLoadingText("Joining the session..."); await zmClient.join(topic, signature, name, password); diff --git a/react-demo/src/feature/preview/preview.scss b/react-demo/src/feature/preview/preview.scss index a1f3b88..531791e 100644 --- a/react-demo/src/feature/preview/preview.scss +++ b/react-demo/src/feature/preview/preview.scss @@ -15,7 +15,7 @@ border: 1px solid rgba(0, 0, 0, 1); } -.video-operations-preview button{ +.video-operations-preview div { background-color: black !important } .join-button { diff --git a/react-demo/src/feature/preview/preview.tsx b/react-demo/src/feature/preview/preview.tsx index 97b1e4d..7810986 100644 --- a/react-demo/src/feature/preview/preview.tsx +++ b/react-demo/src/feature/preview/preview.tsx @@ -1,21 +1,55 @@ import React, { useCallback, useContext, useEffect, useState, useRef } from 'react'; import produce from 'immer'; import ZoomVideo from "@zoom/videosdk"; -import classNames from 'classnames'; import { useMount } from '../../hooks'; import './preview.scss'; import MicrophoneButton from '../video/components/microphone'; import CameraButton from '../video/components/camera'; import { message } from 'antd'; +import { MediaDevice } from '../video/video-types'; +// label: string; +// deviceId: string; let prevMicFeedbackStyle = ''; -let micFeedBackInteval: any = ''; +let micFeedBackInteval: any = ''; + +let localAudio = ZoomVideo.createLocalAudioTrack(); +let localVideo = ZoomVideo.createLocalVideoTrack(); +let allDevices; + +const mountDevices: () => Promise<{ + mics: MediaDevice[]; + speakers: MediaDevice[]; + cameras: MediaDevice[]; +}> = async () => { + allDevices = await ZoomVideo.getDevices(); + const cameraDevices: Array = allDevices.filter(function (device) { + return device.kind === 'videoinput'; + }); + const micDevices: Array = allDevices.filter(function (device) { + return device.kind === 'audioinput'; + }); + const speakerDevices: Array = allDevices.filter(function ( + device, + ) { + return device.kind === 'audiooutput'; + }); + return { + mics: micDevices.map((item) => { + return { label: item.label, deviceId: item.deviceId }; + }), + speakers: speakerDevices.map((item) => { + return { label: item.label, deviceId: item.deviceId }; + }), + cameras: cameraDevices.map((item) => { + return { label: item.label, deviceId: item.deviceId }; + }), + }; +}; -const localAudio = ZoomVideo.createLocalAudioTrack(); -const localVideo = ZoomVideo.createLocalVideoTrack(); const AUDIO_MASK = 1, - MIC_MASK = 2, - VIDEO_MASK = 4; + MIC_MASK = 2, + VIDEO_MASK = 4; let PREVIEW_VIDEO: any; @@ -24,132 +58,171 @@ const updateMicFeedbackStyle = () => { let newMicFeedbackStyle = ''; if (newVolumeIntensity === 0) { - newMicFeedbackStyle = ''; + newMicFeedbackStyle = ''; } else if (newVolumeIntensity <= 0.05) { - newMicFeedbackStyle = 'mic-feedback__very-low'; + newMicFeedbackStyle = 'mic-feedback__very-low'; } else if (newVolumeIntensity <= 0.1) { - newMicFeedbackStyle = 'mic-feedback__low'; + newMicFeedbackStyle = 'mic-feedback__low'; } else if (newVolumeIntensity <= 0.15) { - newMicFeedbackStyle = 'mic-feedback__medium'; + newMicFeedbackStyle = 'mic-feedback__medium'; } else if (newVolumeIntensity <= 0.2) { - newMicFeedbackStyle = 'mic-feedback__high'; + newMicFeedbackStyle = 'mic-feedback__high'; } else if (newVolumeIntensity <= 0.25) { - newMicFeedbackStyle = 'mic-feedback__very-high'; + newMicFeedbackStyle = 'mic-feedback__very-high'; } else { - newMicFeedbackStyle = 'mic-feedback__max'; + newMicFeedbackStyle = 'mic-feedback__max'; } const micIcon: any = document.getElementById('auido-volume-feedback'); if (prevMicFeedbackStyle !== '' && micIcon) { - micIcon.classList.toggle(prevMicFeedbackStyle); + micIcon.classList.toggle(prevMicFeedbackStyle); } if (newMicFeedbackStyle !== '' && micIcon) { - micIcon.classList.toggle(newMicFeedbackStyle); + micIcon.classList.toggle(newMicFeedbackStyle); } console.log(newMicFeedbackStyle, newVolumeIntensity); prevMicFeedbackStyle = newMicFeedbackStyle; }; -const encodePreviewOptions = (isJoinAudio: boolean, isMuteAudio: boolean, isStartVideo: boolean) => { +const encodePreviewOptions = ( + isStartedAudio: boolean, + isMuted: boolean, + isStartedVideo: boolean, +) => { let res = 0; - res = (res | +isStartVideo) << 1; - res = (res | +isMuteAudio) << 1; - res = (res | +isJoinAudio); + res = (res | +isStartedVideo) << 1; + res = (res | +isMuted) << 1; + res = res | +isStartedAudio; return res; -} +}; const decodePreviewOptions = (val: number) => { /* LSB: audio, MSB: video */ - let isJoinAudio = !!((val & AUDIO_MASK) === AUDIO_MASK); - let isMuteAudio = !!((val & MIC_MASK) === MIC_MASK); - let isStartVideo = !!((val & VIDEO_MASK) === VIDEO_MASK); - return {isStartVideo, isMuteAudio, isJoinAudio}; -} + const isStartedAudio = !!((val & AUDIO_MASK) === AUDIO_MASK); + const isMuted = !!((val & MIC_MASK) === MIC_MASK); + const isStartedVideo = !!((val & VIDEO_MASK) === VIDEO_MASK); + return { isStartedVideo, isMuted, isStartedAudio }; +}; const PreviewContainer = () => { + const [isStartedAudio, setIsStartedAudio] = useState(false); + const [isMuted, setIsMuted] = useState(true); + const [isStartedVideo, setIsStartedVideo] = useState(false); + const [micList, setMicList] = useState([]); + const [speakerList, setSpeakerList] = useState([]); + const [cameraList, setCameraList] = useState([]); + const [activeMicrophone, setActiveMicrophone] = useState(''); + const [activeSpeaker, setActiveSpeaker] = useState(''); + const [activeCamera, setActiveCamera] = useState(''); - const [isJoinAudio, setIsJoinAudio] = useState(false); - const [isMuteAudio, setIsMuteAudio] = useState(false); - const [isStartVideo, setIsStartVideo] = useState(false); - const onMicrophoneClick = () => { - if(isJoinAudio) { - if(isMuteAudio) { - localAudio.unmute().then(()=>{ - micFeedBackInteval = setInterval(updateMicFeedbackStyle, 500); - setIsMuteAudio(!isMuteAudio); - }); + const onCameraClick = useCallback(async () => { + if (isStartedVideo) { + await localVideo?.stop(); + setIsStartedVideo(false); + } else { + await localVideo?.start(PREVIEW_VIDEO); + setIsStartedVideo(true); + } + }, [isStartedVideo]); + const onMicrophoneClick = useCallback(async () => { + if (isStartedAudio) { + if (isMuted) { + await localAudio?.unmute(); + micFeedBackInteval = setInterval(updateMicFeedbackStyle, 500); + setIsMuted(false); } else { - localAudio.mute().then(()=>{ - if (micFeedBackInteval) { - clearInterval(micFeedBackInteval); - } - setIsMuteAudio(!isMuteAudio); - }); + await localAudio?.mute(); + if (micFeedBackInteval) { + clearInterval(micFeedBackInteval); + } + setIsMuted(true); } - // localAudio.stop().then(()=>{ - // if (micFeedBackInteval) { - // clearInterval(micFeedBackInteval); - // } - // setIsJoinAudio(!isJoinAudio); - // }); } else { - localAudio.start().then(()=>{ - setIsJoinAudio(!isJoinAudio); - setIsMuteAudio(true); - }); + await localAudio?.start(); + setIsStartedAudio(true); } - - }; - - const toggleVideo = () => { - if(isStartVideo) { - localVideo.stop().then(()=>{ - setIsStartVideo(!isStartVideo); - }); + }, [isStartedAudio, isMuted]); + const onMicrophoneMenuClick = async (key: string) => { + const [type, deviceId] = key.split('|'); + if (type === 'microphone') { + if (deviceId !== activeMicrophone) { + await localAudio.stop(); + setIsMuted(true); + localAudio = ZoomVideo.createLocalAudioTrack(deviceId); + await localAudio.start(); + setActiveMicrophone(deviceId); + } + } else if (type === 'leave audio') { + await localAudio.stop(); + setIsStartedAudio(false); } - else { - localVideo.start(PREVIEW_VIDEO).then(()=>{ - setIsStartVideo(!isStartVideo); - }); + }; + const onSwitchCamera = async (key: string) => { + if (localVideo) { + if (activeCamera !== key) { + await localVideo.stop(); + localVideo = ZoomVideo.createLocalVideoTrack(key); + localVideo.start(PREVIEW_VIDEO); + setActiveCamera(key); + } } - - } + }; useEffect(() => { - const encodeVal = encodePreviewOptions(isJoinAudio, isMuteAudio, isStartVideo); - console.log("preview encode val", encodeVal) - const decodeOption = decodePreviewOptions(encodeVal); - console.log("preview config", decodePreviewOptions(encodeVal)); - message.info(JSON.stringify(decodeOption, null, 2)); - }, [isJoinAudio, isMuteAudio, isStartVideo]); - + const encodeVal = encodePreviewOptions(isStartedAudio, isMuted, isStartedVideo); + console.log('preview encode val', encodeVal); + const decodeOption = decodePreviewOptions(encodeVal); + console.log('preview config', decodePreviewOptions(encodeVal)); + message.info(JSON.stringify(decodeOption, null, 2)); + console.log(micList); + }, [isStartedAudio, isMuted, isStartedVideo]); + useMount(() => { PREVIEW_VIDEO = document.getElementById('js-preview-video'); - ZoomVideo.getDevices().then((res)=>{ - console.log(res); + mountDevices().then((devices) => { + console.log('devicesdevicesdevicesdevices', devices); + setMicList(devices.mics); + setCameraList(devices.cameras); + // setSpeakerList(devices.speakers); }); }); return (
- -

Audio And Video Preview

-
-
- -
- - -
-
+ +

Audio And Video Preview

+
+
+ +
+ + +
+
); }; diff --git a/react-demo/src/feature/video/components/avatar.tsx b/react-demo/src/feature/video/components/avatar.tsx index cf2d61a..65e2bb5 100644 --- a/react-demo/src/feature/video/components/avatar.tsx +++ b/react-demo/src/feature/video/components/avatar.tsx @@ -5,15 +5,16 @@ import './avatar.scss'; import { Participant } from '../../../index-types'; interface AvatarProps { participant: Participant; - style: { [key: string]: string }; + style?: { [key: string]: string }; isActive: boolean; + className?: string; } const Avatar = (props: AvatarProps) => { - const { participant, style, isActive } = props; + const { participant, style, isActive, className } = props; const { displayName, audio, muted, bVideoOn } = participant; return (
{(bVideoOn || (audio === 'computer' && muted)) && ( diff --git a/react-demo/src/feature/video/components/camera.scss b/react-demo/src/feature/video/components/camera.scss index 21f698c..d5a8bf2 100644 --- a/react-demo/src/feature/video/components/camera.scss +++ b/react-demo/src/feature/video/components/camera.scss @@ -10,4 +10,50 @@ > *{ font-size: 28px !important; } +} +.camera-dropdown-button { + border-radius: 20px; + border: 1px solid #fff; + width: 70px; + display: flex; + justify-content: center; + margin-left: 30px; + > .ant-btn { + font-size: 30px; + color: #fff; + display: flex; + height: 60px !important; + align-items: center; + border-width: 0 !important; + padding: 0; + margin-right: 5px; + &:hover { + color: #40a9ff; + } + } + > .ant-dropdown-trigger { + width: 20px !important; + margin-right: 0; + } +} +.camera-menu { + border: 1px solid #747487; + background: rgba(0, 0, 0, 0.9) !important; + .ant-dropdown-menu-item { + padding: 10px 30px; + color: #ccc; + position: relative; + .anticon-check { + position: absolute; + left: 10px; + top: 15px; + } + &:hover { + color: #40a9ff; + } + } + .ant-dropdown-menu-item-group-title { + color: #fff; + font-weight: 700; + } } \ No newline at end of file diff --git a/react-demo/src/feature/video/components/camera.tsx b/react-demo/src/feature/video/components/camera.tsx index c9f9399..29a3725 100644 --- a/react-demo/src/feature/video/components/camera.tsx +++ b/react-demo/src/feature/video/components/camera.tsx @@ -1,27 +1,78 @@ import React from 'react'; -import { Button, Tooltip } from 'antd'; -import { VideoCameraAddOutlined, VideoCameraOutlined } from '@ant-design/icons'; +import { Button, Tooltip, Menu, Dropdown } from 'antd'; +import { + CheckOutlined, + UpOutlined, + VideoCameraAddOutlined, + VideoCameraOutlined, +} from '@ant-design/icons'; import classNames from 'classnames'; import './camera.scss'; +import { MediaDevice } from '../video-types'; interface CameraButtonProps { isStartedVideo: boolean; onCameraClick: () => void; + onSwitchCamera: (deviceId: string) => void; className?: string; + cameraList?: MediaDevice[]; + activeCamera?: string; } const CameraButton = (props: CameraButtonProps) => { - const { isStartedVideo, onCameraClick, className } = props; + const { + isStartedVideo, + className, + cameraList, + activeCamera, + onCameraClick, + onSwitchCamera, + } = props; + const onMenuItemClick = (payload: { key: any }) => { + onSwitchCamera(payload.key); + }; + const menu = cameraList && cameraList.length > 0 && ( + + + {cameraList.map((item) => ( + } + > + {item.label} + + ))} + + + ); return ( +
+ {isStartedVideo && menu ? ( + } + placement="topRight" + > + + + ) : (
); }; export default CameraButton; diff --git a/react-demo/src/feature/video/components/microphone.scss b/react-demo/src/feature/video/components/microphone.scss index 46afb77..47bc4f7 100644 --- a/react-demo/src/feature/video/components/microphone.scss +++ b/react-demo/src/feature/video/components/microphone.scss @@ -9,4 +9,49 @@ > *{ font-size: 28px !important; } -} \ No newline at end of file +} +.microphone-dropdown-button { + border-radius: 20px; + border: 1px solid #fff; + width: 70px; + display: flex; + justify-content: center; + > .ant-btn { + font-size: 30px; + color: #fff; + display: flex; + height: 60px !important; + align-items: center; + border-width: 0 !important; + padding: 0; + margin-right: 5px; + &:hover { + color: #40a9ff; + } + } + > .ant-dropdown-trigger { + width: 20px !important; + margin-right: 0; + } +} +.microphone-menu { + border: 1px solid #747487; + background: rgba(0, 0, 0, 0.9) !important; + .ant-dropdown-menu-item { + padding: 10px 30px; + color: #ccc; + position: relative; + .anticon-check { + position: absolute; + left: 10px; + top: 15px; + } + &:hover { + color: #40a9ff; + } + } + .ant-dropdown-menu-item-group-title { + color: #fff; + font-weight: 700; + } +} diff --git a/react-demo/src/feature/video/components/microphone.tsx b/react-demo/src/feature/video/components/microphone.tsx index 5784772..f5a52cd 100644 --- a/react-demo/src/feature/video/components/microphone.tsx +++ b/react-demo/src/feature/video/components/microphone.tsx @@ -1,40 +1,132 @@ /* eslint-disable no-nested-ternary */ import React from 'react'; -import { Button, Tooltip } from 'antd'; +import { Menu, Tooltip, Dropdown, Button } from 'antd'; import classNames from 'classnames'; -import { AudioOutlined, AudioMutedOutlined } from '@ant-design/icons'; +import { + AudioOutlined, + AudioMutedOutlined, + CheckOutlined, + UpOutlined, +} from '@ant-design/icons'; import { IconFont } from '../../../component/icon-font'; import './microphone.scss'; +import { MediaDevice } from '../video-types'; +const { Button: DropdownButton } = Dropdown; interface MicrophoneButtonProps { isStartedAudio: boolean; isMuted: boolean; onMicrophoneClick: () => void; + onMicrophoneMenuClick: (key: string) => void; className?: string; + microphoneList?: MediaDevice[]; + speakerList?: MediaDevice[]; + activeMicrophone?: string; + activeSpeaker?: string; } const MicrophoneButton = (props: MicrophoneButtonProps) => { - const { isStartedAudio, isMuted, onMicrophoneClick, className } = props; + const { + isStartedAudio, + isMuted, + className, + microphoneList, + speakerList, + activeMicrophone, + activeSpeaker, + onMicrophoneClick, + onMicrophoneMenuClick, + } = props; const tooltipText = isStartedAudio ? (isMuted ? 'unmute' : 'mute') : 'start audio'; + const menu = []; + if (microphoneList && microphoneList.length) { + menu.push({ + group: 'microphone', + title: 'Select a Microphone', + items: microphoneList.map((i) => ({ + label: i.label, + value: i.deviceId, + checked: activeMicrophone === i.deviceId, + })), + }); + } + if (speakerList && speakerList.length) { + menu.push({ + group: 'speaker', + title: 'Select a speaker', + items: speakerList.map((i) => ({ + label: i.label, + value: i.deviceId, + checked: activeSpeaker === i.deviceId, + })), + }); + } + menu.push({ + items: [ + { + label: 'Leave Audio', + value: 'leave audio', + }, + ], + }); + const onMenuItemClick = (payload: { key: any }) => { + onMicrophoneMenuClick(payload.key); + }; + const overlayMenu = ( + + {menu.map((e) => { + if (e.group) { + const mItem = e.items.map((m) => ( + } + > + {m.label} + + )); + return ( + + + {mItem} + + + + ); + } + // initialData.products.map(product => product.id) + // (initialData.products as Array).map(product => product.id) + return (e.items as Array<{ value: string; label: string }>).map((m: any) => ( + {m?.label} + )); + })} + + ); return ( +
+ {isStartedAudio ? ( + } + placement="topRight" + > + {isMuted ? : } + + ) : (
); }; diff --git a/react-demo/src/feature/video/components/video-footer.tsx b/react-demo/src/feature/video/components/video-footer.tsx index 9b44841..49d6746 100644 --- a/react-demo/src/feature/video/components/video-footer.tsx +++ b/react-demo/src/feature/video/components/video-footer.tsx @@ -13,12 +13,14 @@ import MicrophoneButton from './microphone'; import { ScreenShareButton, ScreenShareLockButton } from './screen-share'; import ZoomMediaContext from '../../../context/media-context'; import { useUnmount } from '../../../hooks'; +import { MediaDevice } from '../video-types'; import './video-footer.scss'; interface VideoFooterProps { className?: string; shareRef?: MutableRefObject; sharing?: boolean; } +const isAudioEnable = typeof AudioWorklet === 'function'; const VideoFooter = (props: VideoFooterProps) => { const { className, shareRef, sharing } = props; const [isStartedAudio, setIsStartedAudio] = useState(false); @@ -27,6 +29,12 @@ const VideoFooter = (props: VideoFooterProps) => { const [isLockedScreenShare, setIsLockedScreenShare] = useState(false); const [isMuted, setIsMuted] = useState(true); + const [activeMicrophone, setActiveMicrophone] = useState(''); + const [activeSpeaker, setActiveSpeaker] = useState(''); + const [activeCamera, setActiveCamera] = useState(''); + const [micList, setMicList] = useState([]); + const [speakerList, setSpeakerList] = useState([]); + const [cameraList, setCameraList] = useState([]); const { mediaStream } = useContext(ZoomMediaContext); const zmClient = useContext(ZoomContext); const onCameraClick = useCallback(async () => { @@ -52,6 +60,33 @@ const VideoFooter = (props: VideoFooterProps) => { setIsStartedAudio(true); } }, [mediaStream, isStartedAudio, isMuted]); + const onMicrophoneMenuClick = async (key: string) => { + if (mediaStream) { + const [type, deviceId] = key.split('|'); + if (type === 'microphone') { + if (deviceId !== activeMicrophone) { + await mediaStream.switchMicrophone(deviceId); + setActiveMicrophone(mediaStream.getActiveMicrophone()); + } + } else if (type === 'speaker') { + if (deviceId !== activeSpeaker) { + await mediaStream.switchSpeaker(deviceId); + setActiveSpeaker(mediaStream.getActiveSpeaker()); + } + } else if (type === 'leave audio') { + await mediaStream.stopAudio(); + setIsStartedAudio(false); + } + } + }; + const onSwitchCamera = async (key: string) => { + if (mediaStream) { + if (activeCamera !== key) { + await mediaStream.switchCamera(key); + setActiveCamera(mediaStream.getActiveCamera()); + } + } + }; const onHostAudioMuted = useCallback((payload) => { const { action, source, type } = payload; if (action === 'join' && type === 'computer') { @@ -83,14 +118,26 @@ const VideoFooter = (props: VideoFooterProps) => { console.log('passively stop reason:', reason); setIsStartedScreenShare(false); }, []); + const onDeviceChange = useCallback(() => { + if (mediaStream) { + setMicList(mediaStream.getMicList()); + setSpeakerList(mediaStream.getSpeakerList()); + setCameraList(mediaStream.getCameraList()); + setActiveMicrophone(mediaStream.getActiveMicrophone()); + setActiveSpeaker(mediaStream.getActiveSpeaker()); + setActiveCamera(mediaStream.getActiveCamera()); + } + }, [mediaStream]); useEffect(() => { zmClient.on('current-audio-change', onHostAudioMuted); zmClient.on('passively-stop-share', onPassivelyStopShare); + zmClient.on('device-change', onDeviceChange); return () => { zmClient.off('current-audio-change', onHostAudioMuted); zmClient.off('passively-stop-share', onPassivelyStopShare); + zmClient.off('device-change', onDeviceChange); }; - }, [zmClient, onHostAudioMuted, onPassivelyStopShare]); + }, [zmClient, onHostAudioMuted, onPassivelyStopShare, onDeviceChange]); useUnmount(() => { if (isStartedAudio) { mediaStream?.stopAudio(); @@ -104,12 +151,25 @@ const VideoFooter = (props: VideoFooterProps) => { }); return (
+ {isAudioEnable && ( + )} + - {sharing && ( user.userId === userId); const cellDimension = layout[index]; if (cellDimension) { - const { width, height, x, y } = cellDimension; + const { width, height, x, y, quality } = cellDimension; + if ( + previousLayout && + previousLayout[index] && + previousLayout[index].quality !== quality + ) { + mediaStream?.renderVideo( + videoRef.current as HTMLCanvasElement, + userId, + width, + height, + x, + y, + quality, + ); + } mediaStream?.adjustRenderedVideoPosition( videoRef.current as HTMLCanvasElement, userId, diff --git a/react-demo/src/feature/video/video-layout-helper.ts b/react-demo/src/feature/video/video-layout-helper.ts index d1ef77a..d210403 100644 --- a/react-demo/src/feature/video/video-layout-helper.ts +++ b/react-demo/src/feature/video/video-layout-helper.ts @@ -110,10 +110,13 @@ export function getVideoLayout( const verticalMargin = (rootHeight - cellBoxHeight * row) / 2 + cellOffset; const cellDimensions = []; const lastRowColumns = column - ((column * row) % actualCount); - const lastRowMargin = - (rootWidth - cellBoxWidth * lastRowColumns) / 2 + cellOffset; + const lastRowMargin = (rootWidth - cellBoxWidth * lastRowColumns) / 2 + cellOffset; let quality = VideoQuality.Video_90P; - if (actualCount <= 4 && cellHeight >= 270) { + + if (actualCount <= 4 && cellBoxHeight >= 510) { + // GROUP HD + quality = VideoQuality.Video_720P; + } else if (actualCount <= 4 && cellHeight >= 270) { quality = VideoQuality.Video_360P; } else if (actualCount > 4 && cellHeight >= 180) { quality = VideoQuality.Video_180P; diff --git a/react-demo/src/feature/video/video-types.d.ts b/react-demo/src/feature/video/video-types.d.ts index f3465ac..c6da58c 100644 --- a/react-demo/src/feature/video/video-types.d.ts +++ b/react-demo/src/feature/video/video-types.d.ts @@ -20,3 +20,7 @@ export interface CellLayout { y: number; quality: number; } +export interface MediaDevice { + label: string; + deviceId: string; +} diff --git a/react-demo/src/feature/video/video.scss b/react-demo/src/feature/video/video.scss index 3631558..d7f8ad0 100644 --- a/react-demo/src/feature/video/video.scss +++ b/react-demo/src/feature/video/video.scss @@ -67,4 +67,8 @@ .avatar-wrap-dragging{ z-index: 10; } + .single-view-avatar { + top:0; + left: 0; + } } \ No newline at end of file diff --git a/react-demo/src/index.tsx b/react-demo/src/index.tsx index 6ac663e..6508aab 100644 --- a/react-demo/src/index.tsx +++ b/react-demo/src/index.tsx @@ -24,6 +24,8 @@ if (!meetingArgs.signature && meetingArgs.sdkSecret && meetingArgs.topic) { meetingArgs.sdkSecret, meetingArgs.topic, meetingArgs.password, + 'jack4', + 'jack' ); } console.log('meetingArgs', meetingArgs); diff --git a/react-demo/src/utils/util.ts b/react-demo/src/utils/util.ts index 65734ef..8b6e2b1 100644 --- a/react-demo/src/utils/util.ts +++ b/react-demo/src/utils/util.ts @@ -5,6 +5,8 @@ export function generateVideoToken( sdkSecret: string, topic: string, passWord = '', + userIdentity = '', + sessionKey = '' ) { let signature = ''; try { @@ -20,6 +22,8 @@ export function generateVideoToken( exp, tpc: topic, pwd: passWord, + user_identity: userIdentity, + session_key: sessionKey // topic }; // Sign JWT, password=616161