philnash/twilio-video-react-hooks

Upgrade to twilio-video 2.x

Closed this issue · 6 comments

Hi @philnash . Thank you for putting together the great blog post: https://www.twilio.com/blog/video-chat-react-hooks

I find the Twilio live video API hard to follow and have not found a resource that documents all the various event names that are fired. Your declarative treatment of Rooms and Participants was a godsend. However, when I tried to re-use your code against twilio-video 2.x dependency, the code did not work. I believe it has to do with how streams are now publications you can subscribe to instead of direct streams. I believe your components rely on working directly with streams. If possible, could you update the repo or create a branch that works against twilio-video 2.x? Or give some guidance on a good source for documentation of Twilio 2.x API?

Hi @tedhopps, you're right about the differences, you need to subscribe to tracks being published and then subscribe to get the track subscription. It's one more step that this repo doesn't do yet.

I will try to get around to updating this repo and the blog post soon.

I second that @philnash. Thank you for the excellent blog post and repo. An update to 2.x would be amazing!

This is how I approached it. Notice that I do not keep both Audio and Video track state as the original blogpost. I instead check the track types when attaching to the DOM.

import React, {CSSProperties, useEffect, useRef, useState} from 'react';
import {
  Participant as TwilioParticipant,
  RemoteTrack,
  RemoteTrackPublication,
} from 'twilio-video';

interface Props {
  participant: TwilioParticipant;
  style?: CSSProperties;
}

// https://www.twilio.com/blog/video-chat-react-hooks
const Participant = (props: Props) => {
  const {participant, style} = props;
  const [tracks, setTracks] = useState<RemoteTrack[]>([]);

  const videoRef = useRef<HTMLVideoElement>(null);
  const audioRef = useRef<HTMLAudioElement>(null);

  useEffect(() => {
    const trackSubscribed = (track: RemoteTrack) => {
      setTracks(tracks => [...tracks, track]);
    };

    const trackUnsubscribed = (track: RemoteTrack) => {
      setTracks(tracks => tracks.filter(v => v !== track));
    };

    const trackPublished = (publication: RemoteTrackPublication) => {
      publication.on('subscribed', trackSubscribed);
      publication.on('unsubscribed', trackUnsubscribed);
      if (publication.isSubscribed && publication.track !== null) {
        trackSubscribed(publication.track);
      }
    };

    const trackUnpublished = (publication: RemoteTrackPublication) => {
      if (publication.track) {
        trackUnsubscribed(publication.track);
      }
    };

    participant.on('trackPublished', trackPublished);
    participant.on('trackUnpublished', trackUnpublished);
    participant.tracks.forEach(trackPublication =>
      trackPublished(trackPublication as RemoteTrackPublication),
    );

    return () => {
      setTracks([]);
      participant.removeAllListeners();
    };
  }, [participant]);

  useEffect(() => {
    tracks.forEach(track => {
      if (track.kind === 'video' && videoRef.current !== null) {
        track.attach(videoRef.current as HTMLVideoElement);
      } else if (track.kind === 'audio' && audioRef.current !== null) {
        track.attach(audioRef.current as HTMLAudioElement);
      }
    });
    return () => {
      tracks.forEach((track: any) => track.detach());
    };
  }, [tracks]);

  return (
    <div style={{...style, position: 'relative'}}>
      <video ref={videoRef} autoPlay={true} />
      <audio ref={audioRef} autoPlay={true} />
    </div>
  );
};

export default Participant;

Hey everyone,

I have updated the repo to support twilio-video 2.2.0 now. After updating the dependencies, this was the commit that fixed the video display.

Or in the comment here:

   const videoRef = useRef();
   const audioRef = useRef();

+  const trackpubsToTracks = trackMap => Array.from(trackMap.values())
+    .map(publication => publication.track)
+    .filter(track => track !== null);

   useEffect(() => {
-    console.log(participant);
-    setVideoTracks(Array.from(participant.videoTracks.values()));
-    setAudioTracks(Array.from(participant.audioTracks.values()));
+    setVideoTracks(trackpubsToTracks(participant.videoTracks));
+    setAudioTracks(trackpubsToTracks(participant.audioTracks));

     const trackSubscribed = track => {
       if (track.kind === 'video') {

The issue between versions was that participant.videoTracks.values() previously returned tracks and now returns track publications. A track publication may or may not have a track available, which is where we listen to the subscription event. Since this project was built with a later version of twilio-video version 1 we could already listen for track subscription events, which is why that wasn't changed. Instead, we just need to make sure that we are only putting tracks that exist into the state, thus the trackpubsToTracks function that filters out any that don't have a track.

I'll update the blog post for this soon, but it's not likely to change too much given how small this update turned out to be.

I've updated the blog post too. There wasn't a lot to change thankfully.

One final thing, here are some links for keeping up with the docs and products:

I'm going to close this issue now, but do let me know if you have other questions.

Thank you for your work @philnash.