diff --git a/src/page/Meeting/index.tsx b/src/page/Meeting/index.tsx index 3986060..8c070bc 100644 --- a/src/page/Meeting/index.tsx +++ b/src/page/Meeting/index.tsx @@ -174,6 +174,7 @@ const Meeting: React.FC = () => { } }); const [isVideoFullScreen, setIsVideoFullScreen] = useState(false) + const [observer, setObserver] = useState() let userInfo = JSON.parse(storage.getItem('user') as string) const msgTips = '您不是管理员或发言人,无法开启此功能!' useEffect(() => { @@ -583,6 +584,33 @@ const Meeting: React.FC = () => { return () => clearTimeout(timer); }, [isClicked]); + useEffect(() => { + const elements = document.querySelectorAll('.intersectionObserver-view'); + if (elements.length && currentVideoId) { + elements.forEach(element => { + observer?.unobserve(element); + }); + const observerObject = new IntersectionObserver(async (entries: IntersectionObserverEntry[], _observer: IntersectionObserver) => { + entries.forEach(async (entry) => { + if (entry.target.id !== user.uid) { + await agora.muteRemoteVideoStreamEx(Number(entry.target.id), !entry.isIntersecting) + } + }); + await agora.muteRemoteVideoStreamEx(Number(currentVideoId), false) + }, { threshold: 0, root: document.getElementById('videoView') }); + setObserver(observerObject) + elements.forEach(element => { + observerObject.observe(element); + }); + } + return () => { + elements.forEach(element => { + observer?.unobserve(element); + }); + observer?.disconnect(); + } + }, [roomUserList, currentVideoId]); + // 声网初始化 const agoraInit = async () => { await agora.init(true) @@ -989,53 +1017,76 @@ const Meeting: React.FC = () => { } }) break; - case '录制': - const setting = await JSON.parse(storage.getItem('setting') as string) - try { - await fs.access(setting.recordingFilesPath, fs.constants.F_OK) - footerListTemplate[itemIndex][rowIndex].title = '录制中' - footerListTemplate[itemIndex][rowIndex].active = true - setFooterList(footerListTemplate) - window.electron.getSources().then((sources: any) => { - const screenId = sources[0].id; - navigator.mediaDevices.getUserMedia({ - audio: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: screenId, - } - } as any, - video: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: screenId, - } - } as any - }).then(async (steam) => { - try { - const audioTracks = await navigator.mediaDevices - .getUserMedia({ audio: true, video: false }) - .then(audioStream => audioStream.getAudioTracks()[0]); - steam.addTrack(audioTracks); - } catch (error) { - + case '录制': + const setting = await JSON.parse(storage.getItem('setting') as string); + try { + await fs.access(setting.recordingFilesPath, fs.constants.F_OK); + footerListTemplate[itemIndex][rowIndex].title = '录制中'; + footerListTemplate[itemIndex][rowIndex].active = true; + setFooterList(footerListTemplate); + + window.electron.getSources().then(async (sources: any) => { + const screenId = sources[0].id; + + const stream = await navigator.mediaDevices.getUserMedia({ + audio: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: screenId, } - setMediaStream(steam) - setRecorder(new MediaRecorder(steam)) - }) - }) - } catch (error: any) { - if (error.code === 'ENOENT') { - message.error({ - content:
文件夹不存在 { - stupWizardRef.current.changeModal(3) - }}>前往设置
- }) - return - } else { - message.error(error) - } + }, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: screenId, + } + } + }); + + // 获取所有音频输入设备 + const devices = await navigator.mediaDevices.enumerateDevices(); + const audioInputDevices = devices.filter(device => device.kind === 'audioinput' && + device.deviceId !== 'default' && + device.deviceId !== 'communications' ); + + // 使用Web Audio API来捕获系统声音和麦克风声音,将它们合并到同一个MediaStream中。 + const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + const systemSoundSource = audioCtx.createMediaStreamSource(stream); + const systemSoundDestination = audioCtx.createMediaStreamDestination(); + systemSoundSource.connect(systemSoundDestination); + + // 录制所有音频输入设备 + audioInputDevices.forEach( async device=>{ + const micStream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: device.deviceId } }}); + const micSoundSource = audioCtx.createMediaStreamSource(micStream); + micSoundSource.connect(systemSoundDestination); + }) + + // 合并音频流与视频流 + const combinedSource = new MediaStream([...stream.getVideoTracks(), ...systemSoundDestination.stream.getAudioTracks()]); + + // 开始录制 + const recorder = new MediaRecorder(combinedSource, { + mimeType: 'video/webm;codecs=vp9,opus', + videoBitsPerSecond: 1.5e6, + }); + + setMediaStream(combinedSource); + setRecorder(recorder); + }); + } catch (error: any) { + if (error.code === 'ENOENT') { + message.error({ + content:
文件夹不存在 { + stupWizardRef.current.changeModal(3); + }}>前往设置
+ }); + return; + } else { + message.error(error); } + } + break; case '录制中': footerListTemplate[itemIndex][rowIndex].title = '录制' @@ -1510,7 +1561,7 @@ const Meeting: React.FC = () => { {roomUserList.map((item: any, index: number) => { return (index <= 19 && item.isRoom && item.isAdmin ?
{ if (String(isShare) === item.screenShareId) { @@ -2289,4 +2340,4 @@ const networkIcon = (network: number) => { } } -export default Meeting \ No newline at end of file +export default Meeting