Loading components/ConferenceScreen.js +2 −30 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import { useEffect, useMemo } from "react"; import useTeamsMeeting from "./hooks/teams-provider/useTeamsMeeting"; import useZoomMeeting from "./hooks/zoom-provider/useZoomMeeting"; // import useWebexMeeting from "./hooks/webex-provider/useWebexMeeting"; import useJitsiMeeting from "./hooks/jitsi-provider/useJitsiMeeting"; import Router from "next/router"; const inter = Inter({ subsets: ["latin"] }); Loading Loading @@ -53,6 +54,7 @@ const useMeetingConnector = { teams: useTeamsMeeting, zoom: useZoomMeeting, // webex: useWebexMeeting, jitsi: useJitsiMeeting, }; export default function ConferenceScreen({ Loading Loading @@ -110,36 +112,6 @@ export default function ConferenceScreen({ > End Meeting </button> <button style={{ margin: "20px", padding: "10px 20px", backgroundColor: "red", cursor: "pointer", border: "none", borderRadius: "10px", }} onClick={() => { stopShare(); }} > Stop Share </button> <button style={{ margin: "20px", padding: "10px 20px", backgroundColor: "red", cursor: "pointer", border: "none", borderRadius: "10px", }} onClick={() => { startShare(); }} > Start Share </button> </div> ); } components/hooks/jitsi-provider/useJitsiMeeting.js 0 → 100644 +330 −0 Original line number Diff line number Diff line /* eslint-disable no-undef */ import { useEffect, useRef, useState } from "react"; function defaultMeetingState(isShare) { return { loading: true, error: false, message: isShare ? "Connecting to Ving Share" : "Connecting to Ving Meet", meetingConnected: false, isSharing: false, connectionStatusCode: 0, remoteHostMeetingEnd: false, }; } // Multistream is enabled by default now and it causes issues during share screen // Manually disabling it // https://github.com/jitsi/lib-jitsi-meet/pull/2116/files const initOptions = {}; const options = { hosts: { domain: "meet.jit.si", muc: "conference.meet.jit.si", focus: "focus.meet.jit.si", }, externalConnectUrl: "https://meet.jit.si/http-pre-bind", enableP2P: true, p2p: { enabled: true, preferH264: true, disableH264: true, useStunTurn: true, }, useStunTurn: true, clientNode: "http://jitsi.org/jitsimeet", }; let connection; let room; let localTracks = []; let selectedWindowId; function setupDisplayMediaStream() { if (!window.vingElectron) { return; } window.JitsiMeetScreenObtainer = { openDesktopPicker(options, onSourceChoose) { const typeIndex = selectedWindowId.indexOf(":"); const type = typeIndex >= 0 ? selectedWindowId.substring(0, typeIndex) : null; onSourceChoose(selectedWindowId, type); }, }; } function onConferenceJoined(setMeetingStatus) { setMeetingStatus((prev) => ({ ...prev, meetingConnected: true, error: false, loading: false, connectionStatusCode: 1, message: "Connected", })); } function transformRemoteParticipants(participants) { if (!participants.length) { return []; } return participants.map((participant) => { return { id: participant._id, participantName: participant._displayName, isHost: false, profileUrl: "", isLocalParticipant: false, isInLobby: false, }; }); } function transformLocalParticipant(room, name) { return { id: room.myUserId(), participantName: name, isHost: room.isModerator(), profileUrl: "", isLocalParticipant: true, isInLobby: false, }; } function onConnectionSuccess(props) { const { setMeetingStatus, roomName, name, setParticipants } = props; try { room = connection.initJitsiConference(roomName, {}); window.room = room; room.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, () => { console.log("jitsiConnectionEvents====> JOINED"); onConferenceJoined(setMeetingStatus); let participants = transformRemoteParticipants(room.getParticipants()); participants.push(transformLocalParticipant(room, name)); setParticipants(participants); }); room.on(JitsiMeetJS.events.conference.USER_JOINED, () => { let participants = transformRemoteParticipants(room.getParticipants()); participants.push(transformLocalParticipant(room, name)); setParticipants(participants); }); room.on(JitsiMeetJS.events.conference.USER_LEFT, () => { let participants = transformRemoteParticipants(room.getParticipants()); participants.push(transformLocalParticipant(room, name)); setParticipants(participants); }); room.setDisplayName(name); room.join(); } catch (e) { console.log("e", e); setMeetingStatus((prev) => ({ ...prev, error: true, message: "Something went wrong while joining room", connectionStatusCode: 0, })); } } function jitsiConnectionEvents(props) { const { setMeetingStatus } = props; console.log("jitsiConnectionEvents====>"); connection.addEventListener( JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, () => { onConnectionSuccess(props); } ); connection.addEventListener( JitsiMeetJS.events.connection.CONNECTION_FAILED, (res) => { console.log("res CONNECTION_FAILED", res); setMeetingStatus((prev) => ({ ...prev, error: true, message: res || "Connection Failed", connectionStatusCode: 0, })); } ); connection.addEventListener( JitsiMeetJS.events.connection.otherError, (res) => { console.log("connection.otherError===>", res); setMeetingStatus((prev) => ({ ...prev, error: true, message: res || "Connection Failed", connectionStatusCode: 0, })); } ); connection.addEventListener( JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, (res) => { console.log("res CONNECTION_DISCONNECTED", res); setMeetingStatus((prev) => ({ ...prev, error: true, message: "Disconnected", connectionStatusCode: 0, })); } ); } async function onLocalTracks(tracks, setMeetingStatus) { try { const audioTracks = tracks.filter((track) => track.getType() === "audio"); const audioTrack = audioTracks[0]; if (audioTrack) { await room.addTrack(audioTrack); } const shareTracks = tracks.filter((track) => track.videoType === "desktop"); const shareTrack = shareTracks[0]; if (shareTrack) { await room.addTrack(shareTrack); } tracks[0].addEventListener( JitsiMeetJS.events.track.LOCAL_TRACK_STOPPED, () => { disposeTracks(setMeetingStatus); } ); // Share Track will be saved in our localTrack variable localTracks.push(tracks[0]); // While Sharing Tab with audio, share track and also tab audio track will come, both the tracks need to be saved // tracks[0] - audio track & tracks[1] - share track if (tracks[1]) { localTracks.push(tracks[1]); } } catch (error) { console.log("onLocalTracks catch error:", error); } } function disposeTracks(setMeetingStatus) { try { localTracks.forEach(async (track) => { if (track.type === "audio") { room.removeTrack(room.getLocalTracks()[0]); track.dispose(); } if (track.videoType === "desktop") { track.dispose(); } }); localTracks = []; setMeetingStatus((prev) => ({ ...prev, isSharing: false })); } catch (error) { console.log("error", error); } } function initAndConnect(props) { const { roomName } = props; JitsiMeetJS.init(initOptions); JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR); //Error LOGS options.serviceUrl = `https://meet.jit.si/http-bind?room=${roomName}`; connection = new JitsiMeetJS.JitsiConnection(null, null, options); jitsiConnectionEvents(props); setTimeout(() => connection.connect(), 5000); } async function startBrowserShare(room, setMeetingStatus) { const shareTracks = await JitsiMeetJS.createLocalTracks({ devices: ["desktop"], }); setMeetingStatus((prev) => ({ ...prev, isSharing: true })); await onLocalTracks(shareTracks, setMeetingStatus); } async function startShare(room, setMeetingStatus) { try { if (window.vingElectron?.getApplicationShareWindows) { await setupDisplayMediaStream(); } await startBrowserShare(room, setMeetingStatus); } catch (err) { setMeetingStatus((prev) => ({ ...prev, isSharing: false, message: "Couldn't share", })); room.getLocalTracks()[1]?.dispose(); } } function useJitsiMeeting( roomName, passcode, name, login, isShare, isStartMeeting, setOpenShareScreen ) { const [meetingStatus, setMeetingStatus] = useState( defaultMeetingState(isShare) ); const [participants, setParticipants] = useState([]); const props = { setMeetingStatus, setParticipants, roomName, name, isShare, }; useEffect(() => { initAndConnect(props); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return { meetingStatus, setMeetingStatus, participants, leaveMeeting: () => { room ?.leave() .then(() => setMeetingStatus((prev) => ({ ...prev, meetingConnected: false })) ) .catch((e) => console.log(e)); }, startShare: (id) => { const isValidId = typeof id === "string"; if (window.vingElectron && !isValidId) { setOpenShareScreen(true); } else { selectedWindowId = id; startShare(room, setMeetingStatus, localTracks, onLocalTracks); } }, stopShare: () => { disposeTracks(setMeetingStatus); }, removeParticipant: (participantId) => room.kickParticipant(participantId), }; } export default useJitsiMeeting; next.config.js +1 −1 Original line number Diff line number Diff line /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, reactStrictMode: false, env: { REACT_APP_HTTP_BASE_URL: process.env.REACT_APP_HTTP_BASE_URL, } Loading pages/_app.js +9 −3 Original line number Diff line number Diff line import '../styles/globals.css' import "../styles/globals.css"; import Script from "next/script"; export default function App({ Component, pageProps }) { return <Component {...pageProps} /> return ( <> <Script src="https://code.jquery.com/jquery-3.5.1.min.js" /> <Script src="https://meet.jit.si/libs/lib-jitsi-meet.min.js" /> <Component {...pageProps} /> </> ); } Loading
components/ConferenceScreen.js +2 −30 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import { useEffect, useMemo } from "react"; import useTeamsMeeting from "./hooks/teams-provider/useTeamsMeeting"; import useZoomMeeting from "./hooks/zoom-provider/useZoomMeeting"; // import useWebexMeeting from "./hooks/webex-provider/useWebexMeeting"; import useJitsiMeeting from "./hooks/jitsi-provider/useJitsiMeeting"; import Router from "next/router"; const inter = Inter({ subsets: ["latin"] }); Loading Loading @@ -53,6 +54,7 @@ const useMeetingConnector = { teams: useTeamsMeeting, zoom: useZoomMeeting, // webex: useWebexMeeting, jitsi: useJitsiMeeting, }; export default function ConferenceScreen({ Loading Loading @@ -110,36 +112,6 @@ export default function ConferenceScreen({ > End Meeting </button> <button style={{ margin: "20px", padding: "10px 20px", backgroundColor: "red", cursor: "pointer", border: "none", borderRadius: "10px", }} onClick={() => { stopShare(); }} > Stop Share </button> <button style={{ margin: "20px", padding: "10px 20px", backgroundColor: "red", cursor: "pointer", border: "none", borderRadius: "10px", }} onClick={() => { startShare(); }} > Start Share </button> </div> ); }
components/hooks/jitsi-provider/useJitsiMeeting.js 0 → 100644 +330 −0 Original line number Diff line number Diff line /* eslint-disable no-undef */ import { useEffect, useRef, useState } from "react"; function defaultMeetingState(isShare) { return { loading: true, error: false, message: isShare ? "Connecting to Ving Share" : "Connecting to Ving Meet", meetingConnected: false, isSharing: false, connectionStatusCode: 0, remoteHostMeetingEnd: false, }; } // Multistream is enabled by default now and it causes issues during share screen // Manually disabling it // https://github.com/jitsi/lib-jitsi-meet/pull/2116/files const initOptions = {}; const options = { hosts: { domain: "meet.jit.si", muc: "conference.meet.jit.si", focus: "focus.meet.jit.si", }, externalConnectUrl: "https://meet.jit.si/http-pre-bind", enableP2P: true, p2p: { enabled: true, preferH264: true, disableH264: true, useStunTurn: true, }, useStunTurn: true, clientNode: "http://jitsi.org/jitsimeet", }; let connection; let room; let localTracks = []; let selectedWindowId; function setupDisplayMediaStream() { if (!window.vingElectron) { return; } window.JitsiMeetScreenObtainer = { openDesktopPicker(options, onSourceChoose) { const typeIndex = selectedWindowId.indexOf(":"); const type = typeIndex >= 0 ? selectedWindowId.substring(0, typeIndex) : null; onSourceChoose(selectedWindowId, type); }, }; } function onConferenceJoined(setMeetingStatus) { setMeetingStatus((prev) => ({ ...prev, meetingConnected: true, error: false, loading: false, connectionStatusCode: 1, message: "Connected", })); } function transformRemoteParticipants(participants) { if (!participants.length) { return []; } return participants.map((participant) => { return { id: participant._id, participantName: participant._displayName, isHost: false, profileUrl: "", isLocalParticipant: false, isInLobby: false, }; }); } function transformLocalParticipant(room, name) { return { id: room.myUserId(), participantName: name, isHost: room.isModerator(), profileUrl: "", isLocalParticipant: true, isInLobby: false, }; } function onConnectionSuccess(props) { const { setMeetingStatus, roomName, name, setParticipants } = props; try { room = connection.initJitsiConference(roomName, {}); window.room = room; room.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, () => { console.log("jitsiConnectionEvents====> JOINED"); onConferenceJoined(setMeetingStatus); let participants = transformRemoteParticipants(room.getParticipants()); participants.push(transformLocalParticipant(room, name)); setParticipants(participants); }); room.on(JitsiMeetJS.events.conference.USER_JOINED, () => { let participants = transformRemoteParticipants(room.getParticipants()); participants.push(transformLocalParticipant(room, name)); setParticipants(participants); }); room.on(JitsiMeetJS.events.conference.USER_LEFT, () => { let participants = transformRemoteParticipants(room.getParticipants()); participants.push(transformLocalParticipant(room, name)); setParticipants(participants); }); room.setDisplayName(name); room.join(); } catch (e) { console.log("e", e); setMeetingStatus((prev) => ({ ...prev, error: true, message: "Something went wrong while joining room", connectionStatusCode: 0, })); } } function jitsiConnectionEvents(props) { const { setMeetingStatus } = props; console.log("jitsiConnectionEvents====>"); connection.addEventListener( JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, () => { onConnectionSuccess(props); } ); connection.addEventListener( JitsiMeetJS.events.connection.CONNECTION_FAILED, (res) => { console.log("res CONNECTION_FAILED", res); setMeetingStatus((prev) => ({ ...prev, error: true, message: res || "Connection Failed", connectionStatusCode: 0, })); } ); connection.addEventListener( JitsiMeetJS.events.connection.otherError, (res) => { console.log("connection.otherError===>", res); setMeetingStatus((prev) => ({ ...prev, error: true, message: res || "Connection Failed", connectionStatusCode: 0, })); } ); connection.addEventListener( JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, (res) => { console.log("res CONNECTION_DISCONNECTED", res); setMeetingStatus((prev) => ({ ...prev, error: true, message: "Disconnected", connectionStatusCode: 0, })); } ); } async function onLocalTracks(tracks, setMeetingStatus) { try { const audioTracks = tracks.filter((track) => track.getType() === "audio"); const audioTrack = audioTracks[0]; if (audioTrack) { await room.addTrack(audioTrack); } const shareTracks = tracks.filter((track) => track.videoType === "desktop"); const shareTrack = shareTracks[0]; if (shareTrack) { await room.addTrack(shareTrack); } tracks[0].addEventListener( JitsiMeetJS.events.track.LOCAL_TRACK_STOPPED, () => { disposeTracks(setMeetingStatus); } ); // Share Track will be saved in our localTrack variable localTracks.push(tracks[0]); // While Sharing Tab with audio, share track and also tab audio track will come, both the tracks need to be saved // tracks[0] - audio track & tracks[1] - share track if (tracks[1]) { localTracks.push(tracks[1]); } } catch (error) { console.log("onLocalTracks catch error:", error); } } function disposeTracks(setMeetingStatus) { try { localTracks.forEach(async (track) => { if (track.type === "audio") { room.removeTrack(room.getLocalTracks()[0]); track.dispose(); } if (track.videoType === "desktop") { track.dispose(); } }); localTracks = []; setMeetingStatus((prev) => ({ ...prev, isSharing: false })); } catch (error) { console.log("error", error); } } function initAndConnect(props) { const { roomName } = props; JitsiMeetJS.init(initOptions); JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR); //Error LOGS options.serviceUrl = `https://meet.jit.si/http-bind?room=${roomName}`; connection = new JitsiMeetJS.JitsiConnection(null, null, options); jitsiConnectionEvents(props); setTimeout(() => connection.connect(), 5000); } async function startBrowserShare(room, setMeetingStatus) { const shareTracks = await JitsiMeetJS.createLocalTracks({ devices: ["desktop"], }); setMeetingStatus((prev) => ({ ...prev, isSharing: true })); await onLocalTracks(shareTracks, setMeetingStatus); } async function startShare(room, setMeetingStatus) { try { if (window.vingElectron?.getApplicationShareWindows) { await setupDisplayMediaStream(); } await startBrowserShare(room, setMeetingStatus); } catch (err) { setMeetingStatus((prev) => ({ ...prev, isSharing: false, message: "Couldn't share", })); room.getLocalTracks()[1]?.dispose(); } } function useJitsiMeeting( roomName, passcode, name, login, isShare, isStartMeeting, setOpenShareScreen ) { const [meetingStatus, setMeetingStatus] = useState( defaultMeetingState(isShare) ); const [participants, setParticipants] = useState([]); const props = { setMeetingStatus, setParticipants, roomName, name, isShare, }; useEffect(() => { initAndConnect(props); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return { meetingStatus, setMeetingStatus, participants, leaveMeeting: () => { room ?.leave() .then(() => setMeetingStatus((prev) => ({ ...prev, meetingConnected: false })) ) .catch((e) => console.log(e)); }, startShare: (id) => { const isValidId = typeof id === "string"; if (window.vingElectron && !isValidId) { setOpenShareScreen(true); } else { selectedWindowId = id; startShare(room, setMeetingStatus, localTracks, onLocalTracks); } }, stopShare: () => { disposeTracks(setMeetingStatus); }, removeParticipant: (participantId) => room.kickParticipant(participantId), }; } export default useJitsiMeeting;
next.config.js +1 −1 Original line number Diff line number Diff line /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, reactStrictMode: false, env: { REACT_APP_HTTP_BASE_URL: process.env.REACT_APP_HTTP_BASE_URL, } Loading
pages/_app.js +9 −3 Original line number Diff line number Diff line import '../styles/globals.css' import "../styles/globals.css"; import Script from "next/script"; export default function App({ Component, pageProps }) { return <Component {...pageProps} /> return ( <> <Script src="https://code.jquery.com/jquery-3.5.1.min.js" /> <Script src="https://meet.jit.si/libs/lib-jitsi-meet.min.js" /> <Component {...pageProps} /> </> ); }