Source: media/VideoRoom.js

import React, { useState } from "react";
import Twilio from "twilio-video";
import { emptyObject, processOptions, useVars } from "../../_core";
import { useMemo } from "react";
import { useCallback } from "react";
import { useEffect } from "react";
import { VideoRoomParticipant } from "./VideoRoomParticipant";

/**
 * Componente que renderiza una pizarra donde el usuario puede dibujar libremente.
 * En la documentacion se pueden consultar las opciones que acepta options: https://github.com/embiem/react-canvas-draw
 * 
 * @name Reactor.Components.Media.VideoRoom
 * Example
 * 
 * 
<video_room
  access_token="abcd1234"
  options='{
    "classes": {
      "classes_root": "w-full max-w-full h-screen relative bg-gray-800",
      "classes_participant": "h-full w-full max-w-full max-h-full bg-gray-800 flex justify-center rounded overflow-auto relative",
      "classes_video_off": "h-full w-full bg-gray-800 text-white absolute flex justify-center items-center",
      "classes_mic_off": "absolute flex top-1 left-1 text-white inline-flex items-center justify-center w-8 h-8 bg-gray-800 rounded-full",
      "classes_video": "h-full max-w-full max-h-full",
      "classes_local_participant": "absolute h-32 flex justify-center bottom-5 right-5 shadow-2xl",
      "classes_local_videoOff": "h-full w-full bg-gray-800 text-white absolute flex justify-center items-center",
      "classes_localMicOff": "absolute flex top-1 left-1 text-white inline-flex items-center justify-center w-8 h-8 bg-gray-800 rounded-full",
      "classes_localVideo": "h-full max-w-full max-h-full",
      "classes_controls": "absolute h-32 bottom-0 right-0 w-full flex justify-center items-center gap-4",
      "classes_toggleMic": "",
      "classes_toggleMicDisabled": "",
      "classes_toggleVideo": "",
      "classes_toggleVideoDisabled": "",
      "classes_disconnect": ""
    },

  }'
  on_disconnected="Pagina"
  on_local_disconneted="Pagina"
>Conectando...</video_room>
*/

const contents = {
  disconnect: <svg data-v-041ad48a='' version='1.1' viewBox='0 0 24 24' ><path pid='0' d='M0 0h24v24H0V0z' fill='none'></path><path pid='1' d='M18.59 10.52c1.05.51 2.04 1.15 2.96 1.91l-1.07 1.07c-.58-.47-1.21-.89-1.88-1.27v-1.71m-13.2 0v1.7c-.65.37-1.28.79-1.87 1.27l-1.07-1.07c.91-.75 1.9-1.38 2.94-1.9M12 7C7.46 7 3.34 8.78.29 11.67c-.18.18-.29.43-.29.71s.11.53.29.7l2.48 2.48c.18.18.43.29.71.29.27 0 .52-.1.7-.28.79-.73 1.68-1.36 2.66-1.85.33-.16.56-.51.56-.9v-3.1C8.85 9.25 10.4 9 12 9s3.15.25 4.59.73v3.1c0 .4.23.74.56.9.98.49 1.88 1.11 2.67 1.85.18.17.43.28.7.28.28 0 .53-.11.71-.29l2.48-2.48c.18-.18.29-.43.29-.71s-.11-.53-.29-.71A16.971 16.971 0 0012 7z'></path></svg>,
  toggleMicDisabled: <svg data-v-041ad48a='' version='1.1' viewBox='0 0 24 24' ><g fill='none'><path pid='0' d='M0 0h24v24H0z'></path><path pid='1' d='M0 0h24v24H0z'></path><path pid='2' d='M0 0h24v24H0z'></path></g><path pid='3' d='M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z'></path><path pid='4' d='M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z'></path></svg>,
  toggleMic: <svg data-v-041ad48a='' version='1.1' viewBox='0 0 24 24' ><path pid='0' d='M0 0h24v24H0V0z' fill='none'></path><path pid='1' d='M10.8 4.9c0-.66.54-1.2 1.2-1.2s1.2.54 1.2 1.2l-.01 3.91L15 10.6V5c0-1.66-1.34-3-3-3-1.54 0-2.79 1.16-2.96 2.65l1.76 1.76V4.9zM19 11h-1.7c0 .58-.1 1.13-.27 1.64l1.27 1.27c.44-.88.7-1.87.7-2.91zM4.41 2.86L3 4.27l6 6V11c0 1.66 1.34 3 3 3 .23 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28a7.13 7.13 0 002.55-.9l4.2 4.2 1.41-1.41L4.41 2.86z'></path></svg>,
  toggleVideoDisabled: <svg data-v-041ad48a='' version='1.1' viewBox='0 0 24 24' ><path pid='0' d='M0 0h24v24H0V0z' fill='none'></path><path pid='1' d='M15 8v8H5V8h10m1-2H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4V7c0-.55-.45-1-1-1z'></path></svg>,
  toggleVideo: <svg data-v-041ad48a='' version='1.1' viewBox='0 0 24 24' ><path pid='0' d='M0 0h24v24H0V0z' fill='none'></path><path pid='1' d='M9.56 8l-2-2-4.15-4.14L2 3.27 4.73 6H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.21 0 .39-.08.55-.18L19.73 21l1.41-1.41-8.86-8.86L9.56 8zM5 16V8h1.73l8 8H5zm10-8v2.61l6 6V6.5l-4 4V7c0-.55-.45-1-1-1h-5.61l2 2H15z'></path></svg>,
  micOff: <svg data-v-041ad48a='' version='1.1' viewBox='0 0 24 24' ><path pid='0' d='M0 0h24v24H0V0z' fill='none'></path><path pid='1' d='M10.8 4.9c0-.66.54-1.2 1.2-1.2s1.2.54 1.2 1.2l-.01 3.91L15 10.6V5c0-1.66-1.34-3-3-3-1.54 0-2.79 1.16-2.96 2.65l1.76 1.76V4.9zM19 11h-1.7c0 .58-.1 1.13-.27 1.64l1.27 1.27c.44-.88.7-1.87.7-2.91zM4.41 2.86L3 4.27l6 6V11c0 1.66 1.34 3 3 3 .23 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28a7.13 7.13 0 002.55-.9l4.2 4.2 1.41-1.41L4.41 2.86z'></path></svg>,
  videoOff: <svg data-v-041ad48a='' version='1.1' viewBox='0 0 24 24' ><path pid='0' d='M0 0h24v24H0V0z' fill='none'></path><path pid='1' d='M9.56 8l-2-2-4.15-4.14L2 3.27 4.73 6H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.21 0 .39-.08.55-.18L19.73 21l1.41-1.41-8.86-8.86L9.56 8zM5 16V8h1.73l8 8H5zm10-8v2.61l6 6V6.5l-4 4V7c0-.55-.45-1-1-1h-5.61l2 2H15z'></path></svg>,
}


const VideoRoom = ({access_token, options = "{}", children, context}) => {


  const [varDisconnected, setvarDisconnected] = useVars({variable: "disconnected", context, storage: false, defaultValue: false});
  const [varServerDisconnected, setVarServerDisconnected] = useVars({variable: "serverDisconnected", context, storage: false, defaultValue: false});
  const [varRoom, setVarRoom] = useVars({variable: "room", context, storage: false, defaultValue: emptyObject});
  
  const [room, setRoom] = useState(null);
  const [isMicDisabled, setIsMicDisabled] = useState(false);
  const [isVideoDisabled, setIsVideoDisabled] = useState(false);
  const [participants, setParticipants] = useState([]);
  const [status, setStatus] = useState("connecting");
  const optionsObj = useMemo(() => processOptions(options), [options]);
  const participantDisconnected = useCallback(
    (participant) => {
      setParticipants((prevParticipants) =>
        prevParticipants.filter((p) => p !== participant)
      );
    },
    [room]
  );

  useEffect(()=>{
    setVarRoom({state: room?.state, sid: room?.sid});
  }, [room?.state, room?.id]);

  useEffect(() => {
    const participantConnected = (participant) => {
      setParticipants((prevParticipants) => {
        if (!prevParticipants.some((part) => part.sid === participant.sid)) {
          return [...prevParticipants, participant];
        } else {
          return [prevParticipants];
        }
      });
    };

    const disconnected = () => {
      if(!!context){
        setVarServerDisconnected(true);
      }
    }

    Twilio.connect(access_token, {
      video: true,
      audio: true
    }).then((room) => {
      setRoom(room);
      room.on("participantConnected", participantConnected);
      room.on("participantDisconnected", participantDisconnected);
      room.on("disconnected", disconnected);
      room.participants.forEach(participantConnected);
      setStatus("connected");
    }).catch(reason => {
      console.log(reason);
    });

    return () => {
      setRoom((currentRoom) => {
        if (currentRoom && currentRoom.localParticipant.state === "connected") {
          currentRoom.localParticipant.tracks
            .forEach(trackPublication => {
                trackPublication.track.stop();
            });
          currentRoom.disconnect();
          return null;
        } else {
          return currentRoom;
        }
      });
    };
  }, []); //TODO ADD HERE

  const toggleMic = useCallback(() => {
    if (isMicDisabled) {
      room.localParticipant.audioTracks.forEach(({ track }) => {
        track.enable();
      });
    } else {
      room.localParticipant.audioTracks.forEach(({ track }) => {
        track.disable();
      });
    }

    setIsMicDisabled(!isMicDisabled);
  }, [isMicDisabled, room]);

  const toggleVideo = useCallback(() => {
    if (isVideoDisabled) {
      room.localParticipant.videoTracks.forEach(({ track }) => {
        track.enable();
      });
    } else {
      room.localParticipant.videoTracks.forEach(({ track }) => {
        track.disable();
      });
    }
    setIsVideoDisabled(!isVideoDisabled);
  }, [isVideoDisabled, room]);

  const handleDisconnect = useCallback(() => {
    if(!!context){
      setvarDisconnected(true);
    }
    room.disconnect();
  }, [room]);

  if (status === "connecting") return children;

  return (
    <div className={optionsObj?.classes?.root}>
      {participants.map((participant) => (
        <VideoRoomParticipant   
          participantClass={optionsObj?.classes?.participant} 
          participantIconsClass={optionsObj?.classes?.participantIcons}
          videoDisabledClass={optionsObj?.classes?.videoOff} 
          audioDisabledClass={optionsObj?.classes?.micOff}
          videoDisabledContent={contents.videoOff} 
          audioDisabledContent={contents.micOff}
          videoClass={optionsObj?.classes?.video}
          key={participant.identity} 
          participant={participant} 
        />
      ))}
      <VideoRoomParticipant
        participantClass={optionsObj?.classes?.localParticipant} 
        videoClass={optionsObj?.classes?.localVideo}
        participant={room.localParticipant}
        isMicDisabled={isMicDisabled}
        isVideoDisabled={isVideoDisabled}
        local={true}
      />
      <div className={optionsObj?.classes?.controls}>
        <div className={isMicDisabled ? optionsObj?.classes?.toggleMicDisabled : optionsObj?.classes?.toggleMic} onClick={toggleMic}>{isMicDisabled ? contents.toggleMic : contents.toggleMicDisabled}</div>
        <div className={optionsObj?.classes?.disconnect} onClick={handleDisconnect} >{contents.disconnect}</div>
        <div className={isVideoDisabled ? optionsObj?.classes?.toggleVideoDisabled : optionsObj?.classes?.toggleVideo} onClick={toggleVideo} >{isVideoDisabled ? contents.toggleVideo : contents.toggleVideoDisabled}</div>
      </div>
    </div>
  )
}

export default VideoRoom;