diff --git a/app/.env.local.sample b/app/.env.local.sample index c0c15538..edb7dd0e 100644 --- a/app/.env.local.sample +++ b/app/.env.local.sample @@ -44,3 +44,5 @@ NEXT_PUBLIC_ROCKET_CHAT_HOST=requiredInProd NEXT_PUBLIC_ROCKET_CHAT_CONF_RID=required # id of the greenroom NEXT_PUBLIC_ROCKET_CHAT_GREENROOM_RID=required +NEXT_PUBLIC_ROCKET_CHAT_GREENROOM_RTMP="rtmp://bkk.contribute.live-video.net/app/{stream_key}" + diff --git a/app/components/clientsideonly/jitsibroadcaster.js b/app/components/clientsideonly/jitsibroadcaster.js index f87c8b06..f9a80f08 100644 --- a/app/components/clientsideonly/jitsibroadcaster.js +++ b/app/components/clientsideonly/jitsibroadcaster.js @@ -1,269 +1,412 @@ - -import dynamic from 'next/dynamic' -import React, { useRef, useState } from 'react' - - -const JitsiMeeting = dynamic( () => import('@jitsi/web-sdk').then((mod) => mod.JitsiMeeting) , {ssr: false } ) - - -const Jitsibroadcaster = () => { - const apiRef = useRef(); - const apiRefNew = useRef(); - const [ logItems, updateLog ] = useState([]); - const [ showNew, toggleShowNew ] = useState(false); - const [ knockingParticipants, updateKnockingParticipants ] = useState([]); - - const printEventOutput = payload => { - updateLog(items => [ ...items, JSON.stringify(payload) ]); - }; - - const handleAudioStatusChange = (payload, feature) => { - if (payload.muted) { - updateLog(items => [ ...items, `${feature} off` ]) - } else { - updateLog(items => [ ...items, `${feature} on` ]) - } - }; - - const handleChatUpdates = (payload, ref) => { - if (payload.isOpen || !payload.unreadCount) { - return; - } - ref.current.executeCommand('toggleChat'); - updateLog(items => [ ...items, `you have ${payload.unreadCount} unread messages` ]) - }; - - const handleKnockingParticipant = payload => { - updateLog(items => [ ...items, JSON.stringify(payload) ]); - updateKnockingParticipants(participants => [ ...participants, payload?.participant ]) - }; - - const resolveKnockingParticipants = (ref, condition) => { - knockingParticipants.forEach(participant => { - ref.current.executeCommand('answerKnockingParticipant', participant?.id, condition(participant)); - updateKnockingParticipants(participants => participants.filter(item => item.id === participant.id)); +import dynamic from "next/dynamic"; +import React, { useEffect, useRef, useState } from "react"; +import { Button, ButtonGroup } from "react-bootstrap"; +import { BiMicrophone, BiMicrophoneOff } from "react-icons/bi"; + +const JitsiMeeting = dynamic( + () => import("@jitsi/react-sdk").then((mod) => mod.JitsiMeeting), + { ssr: false } +); + +const rtmp = process.env.NEXT_PUBLIC_ROCKET_CHAT_GREENROOM_RTMP; + +const Jitsibroadcaster = ({room, disName, rtmpSrc}) => { + const apiRef = useRef(); + const [logItems, updateLog] = useState([]); + const [knockingParticipants, updateKnockingParticipants] = useState([]); + const [mute, setMute] = useState(true); + const [name, setName] = useState(null) + const dataArr = [{speaker: "A", hour: "10"}, {speaker: "B", hour: "20"}, {speaker: "C", hour: "30"}, {speaker: "D", hour: "40"}, {speaker: "Z", hour: "50"}] + + const handleDisplayName = async (hr) => { + const tar = dataArr.find(o => o.hour === hr) + if (!tar || tar.speaker == name) { + return + } + setName(tar.speaker) + await apiRef.current.executeCommand("displayName", tar.speaker) + } + + useEffect(() => { + setInterval(() => { + const tada = new Date() + handleDisplayName(tada.getHours().toString()) + }, 900000); + }, []) + + const printEventOutput = (payload) => { + updateLog((items) => [...items, JSON.stringify(payload)]); + }; + + const handleAudioStatusChange = (payload, feature) => { + if (payload.muted) { + updateLog((items) => [...items, `${feature} off`]); + } else { + updateLog((items) => [...items, `${feature} on`]); + } + }; + + const handleChatUpdates = (payload, ref) => { + if (payload.isOpen || !payload.unreadCount) { + return; + } + ref.current.executeCommand("toggleChat"); + updateLog((items) => [ + ...items, + `you have ${payload.unreadCount} unread messages`, + ]); + }; + + const handleKnockingParticipant = (payload) => { + updateLog((items) => [...items, JSON.stringify(payload)]); + updateKnockingParticipants((participants) => [ + ...participants, + payload?.participant, + ]); + }; + + const resolveKnockingParticipants = (ref, condition) => { + knockingParticipants.forEach((participant) => { + ref.current.executeCommand( + "answerKnockingParticipant", + participant?.id, + condition(participant) + ); + updateKnockingParticipants((participants) => + participants.filter((item) => item.id === participant.id) + ); + }); + }; + + const handleJitsiIFrameRef1 = (iframeRef) => { + iframeRef.style.border = "10px solid cadetblue"; + iframeRef.style.background = "cadetblue"; + iframeRef.style.height = "720px"; + iframeRef.style.overflow = "auto"; + iframeRef.style.resize = "both"; + }; + + const showDevices = async (ref) => { + const videoInputs = []; + // get all available video input + const devices = await ref.current.getAvailableDevices(); + + for (const [key, value] of Object.entries(devices)) { + if (key == "videoInput") { + value.forEach((vid) => { + videoInputs.push(vid.label); }); - }; - - const handleJitsiIFrameRef1 = iframeRef => { - iframeRef.style.border = '10px solid cadetblue'; - iframeRef.style.background = 'cadetblue'; - iframeRef.style.height = '720px'; - }; - - const handleJitsiIFrameRef2 = iframeRef => { - iframeRef.style.marginTop = '10px'; - iframeRef.style.border = '10px dashed tomato'; - iframeRef.style.padding = '5px'; - iframeRef.style.height = '400px'; - }; - - - const showDevices = async (ref) => { - const videoInputs = []; - let currentDevice = ""; - // get all available video input - const devices = await ref.current.getAvailableDevices(); - - for (const [key, value] of Object.entries(devices)) { - if ( key == 'videoInput') { - value.forEach( (vid) => { - videoInputs.push( vid.label ); - }); - - } + } + } + // log for debug + updateLog((items) => [...items, JSON.stringify(videoInputs)]); + + let nextDevice = ""; + let devs = await ref.current.getCurrentDevices(); + + for (const [key, value] of Object.entries(devs)) { + if (key == "videoInput") { + updateLog((items) => [...items, "found " + JSON.stringify(value)]); + let devLabel = value.label; + let idx = 0; + videoInputs.forEach((vid) => { + if (devLabel == vid) { + let cur = idx + 1; + if (cur >= videoInputs.length) { + nextDevice = videoInputs[0]; + } else { + nextDevice = videoInputs[cur]; + updateLog((items) => [...items, "next is " + nextDevice]); } - // log for debug - updateLog(items => [ ...items, JSON.stringify(videoInputs) ]) - - let nextDevice = ""; - let devs = await ref.current.getCurrentDevices(); - - for (const [key, value] of Object.entries(devs)) { - if ( key == 'videoInput') { - updateLog(items => [ ...items, "found " + JSON.stringify(value) ]) - let devLabel = value.label; - let idx = 0; - videoInputs.forEach( (vid) => { - if (devLabel == vid) { - let cur = idx + 1; - if (cur >= videoInputs.length) { - nextDevice = videoInputs[0]; - } else { - nextDevice = videoInputs[cur]; - updateLog(items => [ ...items, "next is " + nextDevice ]) - } - - } - idx++; - }); - - - } - - - } - updateLog(items => [ ...items, "switching to " + nextDevice ]) - await ref.current.setVideoInputDevice(nextDevice); - }; - - const handleApiReady = async (apiObj, ref) => { - ref.current = apiObj; - await ref.current.addEventListeners({ - // Listening to events from the external API - audioMuteStatusChanged: payload => handleAudioStatusChange(payload, 'audio'), - videoMuteStatusChanged: payload => handleAudioStatusChange(payload, 'video'), - raiseHandUpdated: printEventOutput, - tileViewChanged: printEventOutput, - chatUpdated: payload => handleChatUpdates(payload, ref), - knockingParticipant: handleKnockingParticipant + } + idx++; }); - - - await ref.current.executeCommand('toggleFilmStrip'); - - } ; - - // Multiple instances demo - const showUsers = async (ref, which) => { - - const pinfo = await ref.current.getParticipantsInfo(); - updateLog(items => [ ...items, "participantes " + JSON.stringify(pinfo) ]) - await ref.current.executeCommand('setTileView', false); - await ref.current.setLargeVideoParticipant( pinfo[which].participantId); - }; - - const makeTile = (ref) => { - - ref.current.executeCommand('setTileView', true); - }; - const renderButtons = () => ( -
-
- - - - -
-
- ); - - const renderLog = () => logItems.map( - (item, index) => ( -
{item}
- ) - ); - - const renderSpinner = () => ( -
Loading..
- ); - - - return ( - <> -

- handleApiReady(externalApi, apiRef)} - getIFrameRef={handleJitsiIFrameRef1} - configOverwrite={{ - startWithAudioMuted: true, - disableModeratorIndicator: true, - startScreenSharing: false, - enableEmailInStats: false, - toolbarButtons: [], - enableWelcomePage: false, - prejoinPageEnabled: false, - startWithVideoMuted: false, - liveStreamingEnabled: true, - disableSelfView: false, - disableSelfViewSettings: true, - disableShortcuts: true, - disable1On1Mode: true, - p2p: { - enabled: false - } - }} - interfaceConfigOverwrite={{ - DISABLE_JOIN_LEAVE_NOTIFICATIONS: true, - FILM_STRIP_MAX_HEIGHT: 0, - TILE_VIEW_MAX_COLUMNS: 0, - VIDEO_QUALITY_LABEL_DISABLED: true - }} - userInfo={{ - displayName: 'Sing' - }} - - /> - {renderButtons()} - {renderLog()} - - ); + } + } + updateLog((items) => [...items, "switching to " + nextDevice]); + + await ref.current.setVideoInputDevice(nextDevice); + }; + + const showAudioOutDevices = async (ref) => { + const audioOutputs = []; + // get all available audio output + const devices = await ref.current.getAvailableDevices(); + + for (const [key, value] of Object.entries(devices)) { + if (key == "audioOutput") { + value.forEach((vid) => { + audioOutputs.push(vid.label); + }); + } + } + // log for debug + updateLog((items) => [...items, JSON.stringify(audioOutputs)]); + + let nextDevice = ""; + let devs = await ref.current.getCurrentDevices(); + + for (const [key, value] of Object.entries(devs)) { + if (key == "audioOutput") { + updateLog((items) => [...items, "found " + JSON.stringify(value)]); + let devLabel = value.label; + let idx = 0; + audioOutputs.forEach((vid) => { + if (devLabel == vid) { + let cur = idx + 1; + if (cur >= audioOutputs.length) { + nextDevice = audioOutputs[0]; + } else { + nextDevice = audioOutputs[cur]; + updateLog((items) => [...items, "next is " + nextDevice]); + } + } + idx++; + }); + } + } + updateLog((items) => [...items, "switching to " + nextDevice]); + + await ref.current.setAudioOutputDevice(nextDevice); + }; + + const showAudioDevice = async (ref) => { + const audioInputs = []; + // get all available audio input + const devices = await ref.current.getAvailableDevices(); + + for (const [key, value] of Object.entries(devices)) { + if (key == "audioInput") { + value.forEach((vid) => { + audioInputs.push(vid.label); + }); + } + } + // log for debug + updateLog((items) => [...items, JSON.stringify(audioInputs)]); + + let nextDevice = ""; + let devs = await ref.current.getCurrentDevices(); + + for (const [key, value] of Object.entries(devs)) { + if (key == "audioInput") { + updateLog((items) => [...items, "found " + JSON.stringify(value)]); + let devLabel = value.label; + let idx = 0; + audioInputs.forEach((vid) => { + if (devLabel == vid) { + let cur = idx + 1; + if (cur >= audioInputs.length) { + nextDevice = audioInputs[0]; + } else { + nextDevice = audioInputs[cur]; + updateLog((items) => [...items, "next is " + nextDevice]); + } + } + idx++; + }); + } + } + updateLog((items) => [...items, "switching to " + nextDevice]); + await ref.current.setAudioInputDevice(nextDevice); + }; + + const handleApiReady = async (apiObj, ref) => { + ref.current = apiObj; + await ref.current.addEventListeners({ + // Listening to events from the external API + audioMuteStatusChanged: (payload) => + handleAudioStatusChange(payload, "audio"), + videoMuteStatusChanged: (payload) => + handleAudioStatusChange(payload, "video"), + raiseHandUpdated: printEventOutput, + tileViewChanged: printEventOutput, + chatUpdated: (payload) => handleChatUpdates(payload, ref), + knockingParticipant: handleKnockingParticipant, + }); + + await ref.current.executeCommand("toggleFilmStrip"); + }; + + // Multiple instances demo + const showUsers = async (ref, which) => { + const pinfo = await ref.current.getParticipantsInfo(); + updateLog((items) => [...items, "participantes " + JSON.stringify(pinfo)]); + await ref.current.executeCommand("setTileView", false); + await ref.current.setLargeVideoParticipant(pinfo[which].participantId); + }; + + const makeTile = (ref) => { + ref.current.executeCommand("setTileView", true); + }; + + const renderStream = (key) => ( +
+
+ + + + +
+
+ ); + + const renderButtons = () => ( +
+
+ + + + + + + + + + + + + + +
+
+ ); + + const renderLog = () => + logItems.map((item, index) => ( +
+ {item} +
+ )); + + const renderSpinner = () => ( +
+ Loading.. +
+ ); + + return ( + <> +

+ {rtmp ? renderStream(rtmp) : rtmpSrc && renderStream(rtmpSrc)} + handleApiReady(externalApi, apiRef)} + getIFrameRef={handleJitsiIFrameRef1} + configOverwrite={{ + startWithAudioMuted: true, + disableModeratorIndicator: true, + startScreenSharing: false, + enableEmailInStats: false, + toolbarButtons: [], + enableWelcomePage: false, + prejoinPageEnabled: false, + startWithVideoMuted: false, + liveStreamingEnabled: true, + disableSelfView: false, + disableSelfViewSettings: true, + disableShortcuts: true, + disable1On1Mode: true, + p2p: { + enabled: false, + }, + }} + interfaceConfigOverwrite={{ + DISABLE_JOIN_LEAVE_NOTIFICATIONS: true, + FILM_STRIP_MAX_HEIGHT: 0, + TILE_VIEW_MAX_COLUMNS: 0, + VIDEO_QUALITY_LABEL_DISABLED: true, + }} + userInfo={{ + displayName: disName, + }} + /> + {renderButtons()} +
+ {renderLog()} +
+ + ); }; - export default Jitsibroadcaster; diff --git a/app/package.json b/app/package.json index 0457adee..5334e52c 100644 --- a/app/package.json +++ b/app/package.json @@ -11,6 +11,7 @@ "lint": "next lint" }, "dependencies": { + "@jitsi/react-sdk": "^1.0.0", "@jitsi/web-sdk": "^0.2.0", "@rocket.chat/fuselage": "^0.31.3", "@rocket.chat/fuselage-hooks": "^0.31.3", diff --git a/app/pages/virtualconf/greenroom/index.js b/app/pages/virtualconf/greenroom/index.js index 28dcd1a1..63e6a098 100644 --- a/app/pages/virtualconf/greenroom/index.js +++ b/app/pages/virtualconf/greenroom/index.js @@ -31,7 +31,7 @@ const Greenroom = () => { - + diff --git a/docs/components/jitsi/README.md b/docs/components/jitsi/README.md new file mode 100644 index 00000000..d7342ed2 --- /dev/null +++ b/docs/components/jitsi/README.md @@ -0,0 +1,64 @@ +# Jitsi Component + +The Jitsi component exports an component `Jitsi`, which could be used to render a Jitsi meet frame. + +## Components + +### _Jitsi_ + +This component renders an Jitsi meet window if called on any page. The props required for the components are, + +| Prop Name | Description | Type | +| ------------- |------------------------- | -----| +| password | The password for entry to the meeting | string | +| subject | The subject of the meeting | string | +| + + +#### **Usage** + +```JSX +import Head from "next/head"; +import { Stack } from "react-bootstrap"; +import dynamic from "next/dynamic"; + +const Jitsi = dynamic( + () => import('../components/jitsi/jitsi'), + { ssr: false } +) + +function JitsiDemo() { + return ( +
+ + Jitsi + + + + + +

Preview of Jitsi Component

+ +
+
+ ); +} + +export default JitsiDemo; + +``` +A demo page is shown with preview forms in the [Screenshots](#screenshots) section. +### Setup form question data in CMS + +No CMS setup is required as of now. + +--- + +### Screenshots + +
+ jitsi meet window +
A screenshot of Jitsi meet window rendered using "Jitsi" component
+
+ +### :arrow_left: Explore More Components \ No newline at end of file diff --git a/docs/components/jitsi/jitsiDemo.png b/docs/components/jitsi/jitsiDemo.png new file mode 100644 index 00000000..e1bc3cde Binary files /dev/null and b/docs/components/jitsi/jitsiDemo.png differ