jitsi / jitsi-meet-react-sdk

React SDK for Jitsi Meet
Apache License 2.0
65 stars 36 forks source link

How to use jibri to a recording meeting with JitsiMeeting component. #34

Closed ItaloCobains closed 11 months ago

ItaloCobains commented 11 months ago

I'm new to using jitsi and would like help integrating a way to record my meetings, I did some research and found a service called jibri but I don't know how to use and configure it. I accept any suggestion

my stupid code.

'use client'

import { JitsiMeeting } from '@jitsi/react-sdk'
import { ReactElement, ReactNode, useRef, useState } from 'react'

interface Payload {
  muted?: boolean
  isOpen?: boolean
  unreadCount?: number
  participant?: Participant
}

interface Participant {
  id: string
  name: string
}

export default function Page() {
  const apiRef = useRef<any>(null)
  const [logItems, updateLog] = useState<string[]>([])
  const [showNew, toggleShowNew] = useState<boolean>(false)
  const [knockingParticipants, updateKnockingParticipants] = useState<Participant[]>([])
  const [recording, setRecording] = useState(null)

  const handleRecordingAvailable = (newRecording: any) => {
    setRecording(newRecording)
  }

  const printEventOutput = (payload: Payload) => {
    updateLog((items) => [...items, JSON.stringify(payload)])
  }

  const handleAudioStatusChange = (payload: Payload, feature: string) => {
    if (payload.muted) {
      updateLog((items) => [...items, `${feature} off`])
    } else {
      updateLog((items) => [...items, `${feature} on`])
    }
  }

  const handleChatUpdates = (payload: Payload) => {
    if (payload.isOpen || !payload.unreadCount) {
      return
    }
    apiRef.current.executeCommand('toggleChat')
    updateLog((items) => [...items, `you have ${payload.unreadCount} unread messages`])
  }

  const handleKnockingParticipant = (payload: Payload) => {
    updateLog((items) => [...items, JSON.stringify(payload)])
    if (payload.participant) {
      updateKnockingParticipants((participants) => [...participants, payload.participant] as any)
    }
  }

  const resolveKnockingParticipants = (condition: (participant: Participant) => boolean) => {
    knockingParticipants.forEach((participant) => {
      apiRef.current.executeCommand('answerKnockingParticipant', participant.id, condition(participant))
      updateKnockingParticipants((participants) => participants.filter((item) => item.id === participant.id))
    })
  }

  const handleJitsiIFrameRef1 = (iframeRef: HTMLIFrameElement) => {
    iframeRef.style.border = '10px solid #3d3d3d'
    iframeRef.style.background = '#3d3d3d'
    iframeRef.style.height = '400px'
    iframeRef.style.marginBottom = '20px'
  }

  const handleJitsiIFrameRef2 = (iframeRef: HTMLIFrameElement) => {
    iframeRef.style.marginTop = '10px'
    iframeRef.style.border = '10px dashed #df486f'
    iframeRef.style.padding = '5px'
    iframeRef.style.height = '400px'
  }

  const handleJaaSIFrameRef = (iframeRef: HTMLIFrameElement) => {
    iframeRef.style.border = '10px solid #3d3d3d'
    iframeRef.style.background = '#3d3d3d'
    iframeRef.style.height = '400px'
    iframeRef.style.marginBottom = '20px'
  }

  const handleApiReady = (apiObj: any) => {
    apiRef.current = apiObj
    apiRef.current.on('knockingParticipant', handleKnockingParticipant)
    apiRef.current.on('audioMuteStatusChanged', (payload) => handleAudioStatusChange(payload, 'audio'))
    apiRef.current.on('videoMuteStatusChanged', (payload) => handleAudioStatusChange(payload, 'video'))
    apiRef.current.on('raiseHandUpdated', printEventOutput)
    apiRef.current.on('titleViewChanged', printEventOutput)
    apiRef.current.on('chatUpdated', handleChatUpdates)
    apiRef.current.on('knockingParticipant', handleKnockingParticipant)
    apiRef.current.executeCommand('startRecording', { mode: 'file' })
  }

  const handleReadyToClose = () => {
    alert('Ready to close...')
  }

  const generateRoomName = () => `JitsiMeetRoomNo${Math.random() * 100}-${Date.now()}`

  const renderNewInstance = (): ReactNode => {
    if (!showNew) {
      return null
    }

    return <JitsiMeeting roomName={generateRoomName()} getIFrameRef={handleJitsiIFrameRef2 as any} />
  }

  const renderButtons = (): ReactElement => {
    return (
      <div style={{ margin: '15px 0' }}>
        <div
          style={{
            display: 'flex',
            justifyContent: 'center',
          }}
        >
          <button
            type="text"
            title="Click to execute toggle raise hand command"
            style={{
              border: 0,
              borderRadius: '6px',
              fontSize: '14px',
              background: '#f8ae1a',
              color: '#040404',
              padding: '12px 46px',
              margin: '2px 2px',
            }}
            onClick={() => {
              console.log('toggleRaiseHand')
              apiRef.current.executeCommand('toggleRaiseHand')
            }}
          >
            Raise hand
          </button>
          <button
            type="text"
            title="Click to approve/reject knocking participant"
            style={{
              border: 0,
              borderRadius: '6px',
              fontSize: '14px',
              background: '#0056E0',
              color: 'white',
              padding: '12px 46px',
              margin: '2px 2px',
            }}
            onClick={() => {
              console.log('resolveKnockingParticipants')
              resolveKnockingParticipants(({ name }) => !name.includes('test'))
            }}
          >
            Resolve lobby
          </button>
          <button
            type="text"
            title="Click to execute subject command"
            style={{
              border: 0,
              borderRadius: '6px',
              fontSize: '14px',
              background: '#df486f',
              color: 'white',
              padding: '12px 46px',
              margin: '2px 2px',
            }}
            onClick={() => {
              console.log('subject')
              apiRef.current.executeCommand('subject', 'New Subject')
            }}
          >
            Change subject
          </button>
          <button
            type="text"
            title="Click to create a new JitsiMeeting instance"
            style={{
              border: 0,
              borderRadius: '6px',
              fontSize: '14px',
              background: '#3D3D3D',
              color: 'white',
              padding: '12px 46px',
              margin: '2px 2px',
            }}
            onClick={() => toggleShowNew(!showNew)}
          >
            Toggle new instance
          </button>
        </div>
      </div>
    )
  }

  const renderLog = () =>
    logItems.map((item, index) => (
      <div
        style={{
          fontFamily: 'monospace',
          padding: '5px',
        }}
        key={index}
      >
        {item}
      </div>
    ))

  const renderSpinner = () => (
    <div
      style={{
        fontFamily: 'sans-serif',
        textAlign: 'center',
      }}
    >
      Loading..
    </div>
  )

  return (
    <>
      <h1
        style={{
          fontFamily: 'sans-serif',
          textAlign: 'center',
        }}
      >
        Phd GRAVAS
      </h1>
      <JitsiMeeting
        roomName={generateRoomName()}
        spinner={renderSpinner}
        configOverwrite={{
          subject: 'lalalala',
          hideConferenceSubject: false,
        }}
        onApiReady={(externalApi: any) => handleApiReady(externalApi)}
        onReadyToClose={handleReadyToClose}
        getIFrameRef={handleJitsiIFrameRef1 as any}
      />
      {renderButtons()}
      {renderNewInstance()}
      {renderLog()}
    </>
  )
}
mihhu commented 11 months ago

Hi, you would need to configure jibri if you were to use your own instance. In your snippet, you're using JitsiMeeting with the default domain, that is meet.jit.si. Now it depends on what you need: if you want cloud recordings, you need to self host or use JaaS, if you're ok with dropbox or local recordings, you can tweak the configOverwrite prop to enable that.

Edit: meet.jit.si works in such integrations only for demo purposes (5min meetings cap), so you would eventually need to decide whether you want to host the service yourself, or purchase JaaS. I think there are a couple of free servers out there, but I can't be sure of their availability.

ItaloCobains commented 11 months ago

I would like to do everything locally. So I have to host both the jitsi and jibri services on your server? How do I do this? Is there an existing tutorial? I am using a computer on Azure to host my services. Thank you for your support.

mihhu commented 11 months ago

I see, please check out this repo: https://github.com/jitsi/jibri#jibri

There are a couple of community-made video tutorials, but they might be outdated.