philnash / twilio-video-react-hooks

A video chat application built with Twilio Video and React Hooks
MIT License
110 stars 61 forks source link

Problem black screen when reconnecting #47

Closed Yateux closed 3 years ago

Yateux commented 3 years ago

Hello all !

I'am using this stack for my project, and i have some random error when a user is reconnecting on group room.

In fact, i have 3 users :

When Tianna is reloading her page, we have a black square but for other users this black square does not appear. So i don't know where is the problem ? Maybe the cause of the problem is the track ?

You can see more with the following image.

https://ibb.co/SKjY1HD

Regards.

philnash commented 3 years ago

Hi @Chateux, when users refresh the page, they should be disconnected from the room using this code which should mean they trigger participantDisconnected events in the browsers of the other participants.

Is there something up with that code in your application?

Yateux commented 3 years ago

True I have implemented this piece of code, but it does not works....

philnash commented 3 years ago

Are you able to investigate whether it is being triggered properly? I haven't experienced an issue with it myself, so I need you to tell me as much as you can about this if I am to help.

Yateux commented 3 years ago

Hello,

I have implemented the solution like this :

The file is Lobby.jsx :

  useEffect(() => {
    const participantConnected = participant => {
      setLoggin(false);
      setParticipants(prevParticipants => [...prevParticipants, participant]);
    };

    const participantDisconnected = participant => {
      setLoggin(true);
      setParticipants(prevParticipants =>
        prevParticipants.filter(p => p !== participant)
      );
    };
    setLoading(true);

    const VideoConnectCallbackSingle = {
      name: roomName,
      audio: true,
      video: isSmallScreen
        ? { height: 480, frameRate: 24, width: 640 }
        : { height: 720, frameRate: 24, width: 1280 },
      networkQuality: { local: 2, remote: 1 }
    };

    const VideoConnectCallbackGroups = {
      name: roomName,
      audio: true,
      debug: true,
      video: isSmallScreen
        ? { height: 480, frameRate: 24, width: 640 }
        : { height: 720, frameRate: 24, width: 1280 },
      bandwidthProfile: {
        video: {
          mode: "presentation",
          renderDimensions: {
            high: { height: 1080, width: 1920 },
            standard: { height: 720, width: 1280 },
            low: { height: 176, width: 144 }
          },
          maxTracks: isSmallScreen ? 5 : 10,
          maxSubscriptionBitrate: isSmallScreen ? 2500000 : 0
        }
      },
      maxAudioBitrate: 16000,
      preferredVideoCodecs: [{ codec: "VP8", simulcast: true }],
      networkQuality: { local: 1, remote: 1 }
    };

    Video.connect(
      token,
      appointment.appointment.type === "masterclass"
        ? VideoConnectCallbackGroups
        : VideoConnectCallbackSingle
    ).then(room => {
      setRoom(room);
      room.on("participantConnected", participantConnected);
      room.on("participantDisconnected", participantDisconnected);
      room.participants.forEach(participantConnected);
      setLoading(false);
      return () => {
        room.off("participantConnected", participantConnected);
        room.off("participantDisconnected", participantDisconnected);
      };
    });

    return () => {
      setRoom(currentRoom => {
        if (currentRoom && currentRoom.localParticipant.state === "connected") {
          currentRoom.localParticipant.tracks.forEach(trackPublication => {
            trackPublication.track.stop();
          });
          currentRoom.disconnect();
          return null;
        }
        return currentRoom;
      });
    };

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [token, roomName]);

  const displayCamera = () => {
    room.localParticipant.videoTracks.forEach(track => {
      if (track.track.isEnabled) {
        track.track.disable();
      } else {
        track.track.enable();
      }
      setCameraChecked(track.track.isEnabled);
    });
  };

  const displayAudio = () => {
    room.localParticipant.audioTracks.forEach(track => {
      if (track.track.isEnabled) {
        track.track.disable();
      } else {
        track.track.enable();
      }
      setAudioChecked(track.track.isEnabled);
    });
  };

  const zoomOnParticipant = participant => {
    if (room.localParticipant === participant) {
      setIsZoomParticipant(room.localParticipant);
    } else {
      setIsZoomParticipant(participant);
    }
  };
  const clickRoomParticipant = () => {
    setIsZoomParticipant(null);
  };

  // eslint-disable-next-line react-hooks/exhaustive-deps
  const handleLogout = useCallback(() => {
    setRoom(prevRoom => {
      if (prevRoom) {
        prevRoom.localParticipant.tracks.forEach(trackPub => {
          trackPub.track.stop();
        });
        prevRoom.disconnect();
      }
      return null;
    });
    handleLogoutFinal();
  });

  // eslint-disable-next-line consistent-return
  useEffect(() => {
    if (room) {
      const tidyUp = event => {
        if (event.persisted) {
          return;
        }
        if (room) {
          room.disconnect();
          handleLogout();
        }
      };
      window.addEventListener("pagehide", tidyUp);
      window.addEventListener("beforeunload", tidyUp);
      return () => {
        window.removeEventListener("pagehide", tidyUp);
        window.removeEventListener("beforeunload", tidyUp);
      };
    }
  }, [room, handleLogout]);

isSmallScreen is a variable that contains the width of the page (For mobile) setLogin is a useState to check if a user is connected VideoConnectCallbackGroups is a variable contains group VideoConnectCallbackSingle is a viable that contains pee-to-peer room setIsZoomParticipant is for a user that i would to pin

Then, the code I put in place is inspired by this: https://github.com/philnash/twilio-video-react-hooks/tree/917b232264a7d457f969f43ea7c8a0857db04219

Regards.

philnash commented 3 years ago

Are you able to log whether tidyUp is getting called when your participant refreshes the page?

Also, why do you call setLoggin(false) for each participant that connects and setLoggin(true) for each participant that disconnects?

Yateux commented 3 years ago

I will try to send you the log.

Because we have a waiting room, and if a new user is connecting it redirect to twilio.

philnash commented 3 years ago

I guess my question about the setLoggin function is that it happens for every new participant that joins the room and for every participant that leaves the call. Is it doing something that affects removing the participant when they disconnect? Because that could cause the black screen.

Yateux commented 3 years ago

In fact, in the hook lobby, I have a waiting page that watches if a person has logged in or not.

If a person has arrived, the cameras will be displayed.

Like that :

if (
    login !== true &&
    room &&
    room.participants.size === 0
  ) {
    return (
      <StyledLobbyWait>
        <StyledWaitContainer>
          <Title type="title2Bold">Please wait your participant.</Title>
     )
}

and then i have a return return who contain participant

<Participant

                          key={room.localParticipant.sid}
                          participant={room.localParticipant}
                        />
Yateux commented 3 years ago

Here the logs :

EVENT TIDY UP beforeunload

bubbles: false ​ cancelBubble: false ​ cancelable: true ​ composed: false ​ currentTarget: null ​ defaultPrevented: false ​ eventPhase: 0 ​ explicitOriginalTarget: HTMLDocument ​ isTrusted: true ​ originalTarget: HTMLDocument ​ returnValue: "" ​ srcElement: HTMLDocument ​ target: HTMLDocument ​ timeStamp: 36204 ​ type: "beforeunload" ​ <get isTrusted()>: function isTrusted() ​

: BeforeUnloadEventPrototype { returnValue: Getter & Setter, … } Lobby.jsx:247 ROOM IF 3 : {…} ​ _events: Object { participantConnected: participantConnected(), participantDisconnected: participantDisconnected() } ​ _eventsCount: 2 ​ _instanceId: 1 ​ _log: Object { … } ​ _maxListeners: undefined ​ _options: Object { wsServer: "wss://global.vss.twilio.com/signaling", automaticSubscription: true, dominantSpeaker: false, … } ​ _participants: Map { PAde699680ea74467dae1e3312e5997d3c → {…}, PAa83c92dc3ecddb887baf0faea5f3611f → {…} } ​ _signaling: Object { _events: {…}, _eventsCount: 6, name: "e15ae334-5c01-498c-a20d-93c057416a5a", … } ​ dominantSpeaker: ​ isRecording: ​ localParticipant: Object { audioTracks: Map(0), dataTracks: Map(0), identity: Getter, … } ​ mediaRegion: "de1" ​ name: "e15ae334-5c01-498c-a20d-93c057416a5a" ​ participants: Map { PAde699680ea74467dae1e3312e5997d3c → {…}, PAa83c92dc3ecddb887baf0faea5f3611f → {…} } ​ sid: "RM729ce63356e1c0ab07d3e917e5bf8a49" ​ state: ​ : function get() ​ : function get() ​ : function get() ​ : Object { … }
philnash commented 3 years ago

I am going to need a bit more context for where all those logs came from. Which participant are these for?

Yateux commented 3 years ago

I found the solution, thank you very much! In fact, it was the cameras that were badly disconnected that were displayed, you just had to add a condition.

philnash commented 3 years ago

Oh, ok! Is that something I should add back into this repo, or was it part of your implementation?

Yateux commented 3 years ago

It's on my side haha 👍