feat: introduce a comprehensive LiveKit-based voice and video chat system with state management and screen sharing capabilities.
All checks were successful
Build and Release / build-and-release (push) Successful in 10m6s
All checks were successful
Build and Release / build-and-release (push) Successful in 10m6s
This commit is contained in:
118
Frontend/Electron/src/utils/streamUtils.jsx
Normal file
118
Frontend/Electron/src/utils/streamUtils.jsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { RoomEvent } from 'livekit-client';
|
||||
|
||||
export const VideoRenderer = ({ track, style }) => {
|
||||
const videoRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = videoRef.current;
|
||||
if (el && track) {
|
||||
track.attach(el);
|
||||
return () => {
|
||||
track.detach(el);
|
||||
};
|
||||
}
|
||||
}, [track]);
|
||||
|
||||
return (
|
||||
<video
|
||||
ref={videoRef}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain', backgroundColor: 'black', ...style }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export function findTrackPubs(participant) {
|
||||
let cameraPub = null;
|
||||
let screenSharePub = null;
|
||||
let screenShareAudioPub = null;
|
||||
|
||||
const trackMap = participant.tracks || participant.trackPublications;
|
||||
if (trackMap) {
|
||||
for (const pub of trackMap.values()) {
|
||||
const source = pub.source ? pub.source.toString().toLowerCase() : '';
|
||||
const name = pub.trackName ? pub.trackName.toLowerCase() : '';
|
||||
|
||||
if (source === 'screen_share_audio' || name === 'screen_share_audio') {
|
||||
screenShareAudioPub = pub;
|
||||
} else if (source === 'screenshare' || source === 'screen_share' || name.includes('screen')) {
|
||||
screenSharePub = pub;
|
||||
} else if (source === 'camera' || name.includes('camera')) {
|
||||
cameraPub = pub;
|
||||
} else if (pub.kind === 'video' && !screenSharePub) {
|
||||
screenSharePub = pub;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for older SDKs
|
||||
if ((!screenSharePub && !cameraPub) && participant.getTracks) {
|
||||
try {
|
||||
for (const pub of participant.getTracks()) {
|
||||
const source = pub.source ? pub.source.toString().toLowerCase() : '';
|
||||
const name = pub.trackName ? pub.trackName.toLowerCase() : '';
|
||||
if (source === 'screen_share_audio' || name === 'screen_share_audio') {
|
||||
screenShareAudioPub = pub;
|
||||
} else if (source === 'screenshare' || source === 'screen_share' || name.includes('screen')) {
|
||||
screenSharePub = pub;
|
||||
} else if (source === 'camera' || name.includes('camera')) {
|
||||
cameraPub = pub;
|
||||
} else if (pub.kind === 'video' && !screenSharePub) {
|
||||
screenSharePub = pub;
|
||||
}
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
return { cameraPub, screenSharePub, screenShareAudioPub };
|
||||
}
|
||||
|
||||
export function useParticipantTrack(participant, source) {
|
||||
const [track, setTrack] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!participant) { setTrack(null); return; }
|
||||
|
||||
const resolve = () => {
|
||||
const { cameraPub, screenSharePub } = findTrackPubs(participant);
|
||||
const pub = source === 'camera' ? cameraPub : screenSharePub;
|
||||
|
||||
if (pub && !pub.isSubscribed && source === 'camera') {
|
||||
pub.setSubscribed(true);
|
||||
}
|
||||
|
||||
setTrack(pub?.track || null);
|
||||
};
|
||||
|
||||
resolve();
|
||||
const interval = setInterval(resolve, 1000);
|
||||
const timeout = setTimeout(() => clearInterval(interval), 10000);
|
||||
|
||||
const onPub = () => resolve();
|
||||
const onUnpub = () => resolve();
|
||||
|
||||
participant.on(RoomEvent.TrackPublished, onPub);
|
||||
participant.on(RoomEvent.TrackUnpublished, onUnpub);
|
||||
participant.on(RoomEvent.TrackSubscribed, onPub);
|
||||
participant.on(RoomEvent.TrackUnsubscribed, onUnpub);
|
||||
participant.on(RoomEvent.TrackMuted, onPub);
|
||||
participant.on(RoomEvent.TrackUnmuted, onPub);
|
||||
participant.on('localTrackPublished', onPub);
|
||||
participant.on('localTrackUnpublished', onUnpub);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
clearTimeout(timeout);
|
||||
participant.off(RoomEvent.TrackPublished, onPub);
|
||||
participant.off(RoomEvent.TrackUnpublished, onUnpub);
|
||||
participant.off(RoomEvent.TrackSubscribed, onPub);
|
||||
participant.off(RoomEvent.TrackUnsubscribed, onUnpub);
|
||||
participant.off(RoomEvent.TrackMuted, onPub);
|
||||
participant.off(RoomEvent.TrackUnmuted, onPub);
|
||||
participant.off('localTrackPublished', onPub);
|
||||
participant.off('localTrackUnpublished', onUnpub);
|
||||
};
|
||||
}, [participant, source]);
|
||||
|
||||
return track;
|
||||
}
|
||||
Reference in New Issue
Block a user