0

I am trying to limit the users that should be allowed to use the camera in a room (max of 9), so I'd like to disable the camera buttons for all the users once the limit has been reached. But I am currently blocked in determining the current number of users that has their camera on. I have searched the Twilio API about the rooms and participants API but can't find the answers in there.

Is there an easy way that I can use to determine the number of users that has their camera on in a single room? Once I'm able to determine that, I can use it to enable/disable the camera buttons of other users. I'm using JavaScript SDK and my app is based on https://github.com/twilio/twilio-video-app-react.

morcen
  • 368
  • 2
  • 14

2 Answers2

1

Twilio developer evangelist here.

Different devices and different operating systems will have different abilities with regard to request the same device simultaneously.

It sounds like you want to determine who has their cameras on, and then you can add those up. getUserMedia throws a NotReadableError when the browser tries to access the webcam but it's already in use (this isn’t consistent across all browsers / OS's.)

You can handle that error using the new-ish promise-based getUserMedia(), seeing when the webcam is already in use.

// both the video and audio track set to false to immediately trigger TypeError
var constraints = {
    video: false,
    audio: false
}
navigator.mediaDevices.getUserMedia(constraints).then(function success(stream) {
    /* do stuff */
}).catch(function(err) {
    //log to console first 
    console.log(err); /* handle the error */
    if (err.name == "NotReadableError" || err.name == "TrackStartError") {
        //webcam or mic are already in use 
    } else {
        //other errors 
    }
});

Alternatively, it seems like you can check if readystate is set to live--more on that here:

navigator.getUserMedia = (navigator.getUserMedia ||
  navigator.webkitGetUserMedia ||
  navigator.mozGetUserMedia ||
  navigator.msGetUserMedia);

if (navigator.getUserMedia) {
  navigator.getUserMedia({
      audio: true,
      video: true
    },
    function(stream) {
      // returns true if any tracks have active state of true
      var result = stream.getVideoTracks().some(function(track) {
        return track.enabled && track.readyState === 'live';
      });

      if (result) {
        alert('Your webcam is busy!');
      } else {
        alert('Not busy');
      }
    },
    function(e) {
      alert("Error: " + e.name);
    });
}

Similarly it looks like you can use video.onplaying to detect when the video is on--more on that here.

To stop the track, you can do something like

stream.getTracks().forEach(track => track.stop())

Let me know if this helps!

lizziepika
  • 3,231
  • 1
  • 14
  • 23
  • thanks @lizziepika! But this solution seems to be only applicable to the local participant. Can you confirm if that's the case? – morcen Dec 02 '20 at 04:58
0

Since I wasn't able to find a solution using javascript/react, I have decided to solve it from the backend which is using an old Laravel version(4.2). I created a route that all participants will call whenever someone joins in the room or turns on/off their camera:

// routes.php
Route::get('room/{name}/participants/all', function ($name) {
    $twilioConfig = \Config::get('app.twilio.video');
    $twilioApiKey = $twilioConfig['apiKey'];
    $twilioApiSecret = $twilioConfig['apiSecret'];

    $client = new \Twilio\Rest\Client($twilioApiKey, $twilioApiSecret);
    $participants = $client->video->rooms($name)
            ->participants->read(["status" => "connected"]);

    $allParticipants = [];
    $count = count($participants);
    foreach ($participants as $participant) {
        $publishedTracks = $client->video->rooms($name)
            ->participants($participant->sid)
            ->publishedTracks->read();

        $videoOn = false;
        foreach ($publishedTracks as $publishedTrack) {
            if ('video' === $publishedTrack->kind && $publishedTrack->enabled) {
                $videoOn = true;
                break;
            }
        }
        
        $format = 'H:i:s';
        $allParticipants[$participant->sid] = [
            'identity' => $participant->identity,
            'created' => $participant->dateCreated->format($format),
            'updated' => $participant->dateUpdated->format($format),
            'started' => $participant->startTime->format($format),
            'videoOn' => $videoOn,
            'order' => $count--,
        ];
    }

    return \Illuminate\Support\Facades\Response::json($allParticipants);
});

The order is set to be in descending order because that's how the response I get from the Twilio's participants list per room (last joining participant first), which for me doesn't matter because it can be easily parsed in the frontend side.

Basically:

  • get the list of all participants in the room
  • for every participant, get all tracks it is publishing
    • get the status of the video track in particular
  • return the full list of participants to the react app

Response looks like this:

[
  {
    "identity": "morcen1@example.com",
    "created": "17:05:17",
    "updated": "17:07:11",
    "started": "17:05:17",
    "videoOn": true,
    "order": 4
  },
  {
    "identity": "morcen2@example.com",
    "created": "17:05:16",
    "updated": "17:07:08",
    "started": "17:05:16",
    "videoOn": true,
    "order": 3
  },
  {
    "identity": "morcen3@example.com",
    "created": "17:05:15",
    "updated": "17:07:04",
    "started": "17:05:15",
    "videoOn": true,
    "order": 2
  },
  {
    "identity": "morcen4@example.com",
    "created": "16:46:32",
    "updated": "17:07:14",
    "started": "16:46:32",
    "videoOn": true,
    "order": 1
  }
]

This script can also be useful for determining the status of audio track.

morcen
  • 368
  • 2
  • 14