Gajus
Gajus

Reputation: 73908

How to check if a track is already published?

for (const localTrack of localTracks) {
  if (localTrack.kind === 'video') {
    localParticipant.publishTrack(localTrack, {
      priority: 'low',
    });
  } else {
    localParticipant.publishTrack(localTrack, {
      priority: 'standard',
    });
  }
}

I am currently getting an error:

TwilioError: Track name is duplicated

This is because this method is called multiple times (each time a new permission is approved) with the list of all approved tracks.

How do I check if a particular track has been already published?

One would expect that we can inspect the localParticipant object, e.g.

console.log(
  '>>>',
  localParticipant.tracks.size,
  localParticipant.audioTracks.size,
  localParticipant.videoTracks.size
);

but the above produces >>> 0 0 0 and then is followed by "Track name is duplicated" error. So there is some race-condition error.

Upvotes: 1

Views: 334

Answers (1)

Gajus
Gajus

Reputation: 73908

This was indeed a race condition, and to understand how we got there, we need the full code example:

useEffect(() => {
  if (!localParticipant) {
    return;
  }

  for (const localTrack of localTracks) {
    if (localTrack.kind === 'video') {
      localParticipant.publishTrack(localTrack, {
        priority: 'low',
      });
    } else {
      localParticipant.publishTrack(localTrack, {
        priority: 'standard',
      });
    }
  }

  return () => {
    localParticipant.audioTracks.forEach((publication) => {
      publication.unpublish();
    });

    localParticipant.videoTracks.forEach((publication) => {
      publication.unpublish();
    });
  };
}, [localParticipant, localTracks]);

What is happening here is that every time localParticipant or localTracks change, we do two things:

  1. We clean-up by unsetting any existing audio/ video tracks
  2. We bind new tracks

Somehow the clean up logic causes the localParticipant.publishTrack method to go into an error state ("Track name is duplicated") publishTrack is invoked just after unpublish.

The fix is to simply move unpublish logic into a separate hook that does not depend on localTracks.

useEffect(() => {
  if (!localParticipant) {
    return;
  }

  return () => {
    localParticipant.audioTracks.forEach((publication) => {
      publication.unpublish();
    });

    localParticipant.videoTracks.forEach((publication) => {
      publication.unpublish();
    });
  };
}, [localParticipant]);

useEffect(() => {
  if (!localParticipant) {
    return;
  }

  for (const localTrack of localTracks) {
    if (localTrack.kind === 'video') {
      localParticipant.publishTrack(localTrack, {
        priority: 'low',
      });
    } else {
      localParticipant.publishTrack(localTrack, {
        priority: 'standard',
      });
    }
  }
}, [localParticipant, localTracks]);

Note that you need to do this in addition for handling events. The unmount clean-up strategy is used here primarily to enable react hot reloading.

Upvotes: 1

Related Questions