1
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.

Gajus
  • 69,002
  • 70
  • 275
  • 438
  • [PublishedTrack?](https://www.twilio.com/docs/video/api/publishedtrack) – Robert Harvey Jul 24 '20 at 22:22
  • The problem appears to be because [`publishTrack`](https://media.twiliocdn.com/sdk/js/video/releases/2.0.0/docs/LocalParticipant.html#publishTrack__anchor) returns a problem. That explains it. The solution is therefore to implement some sort of mutually exclusive lock. Will share a solution. – Gajus Jul 24 '20 at 22:35

1 Answers1

1

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.

Gajus
  • 69,002
  • 70
  • 275
  • 438