Create dynamic meetings using track subscriptions

Use the Daily.co API to selectively engage with other participants in a call.

Intro

At Daily.co we’re focused on making the experience of building video applications as developer-friendly as possible. That means creating easy to understand abstractions on top of underlying technologies such as WebRTC. That said, sometimes it’s helpful to allow more granular control of that underlying tech. Specifically, we want to make it easier for you to control various media tracks and who can see them at any given time. 

Enter track subscriptions! Any participant can now “subscribe” to the tracks (microphone, camera, screenshare) of any other participant. This is useful for any case where it isn’t necessary for all participants to see or hear each other. It also saves on CPU cost since the browser only needs to decode tracks required by the client UI. Some potential applications include: 

  • Randomized breakout sessions (without the need for another room) 
  • Randomized audience samples for presenters 
  • Introducing yourself to a group when you join

Let’s consider the suggested use case where the last person to join can introduce themselves. 

Please join() the meeting, and say “Hello World” 

We’ll use our pre-built UI, leveraging the `participant-joined` event, `is_owner` property, and `setSubscribedTracks`

The logic will be the following: 

  • All participants will see (and hear) `owners`
  • All participants will see (and hear) the latest person who joined so they can say hello
  • All participants will see (and hear) no one else in the call  

Getting Started

To get started make sure you have: 

  • Node.js > 10 installed 
  • A copy of our latest daily-demos repo
  • A daily.co room to use for testing [0]
  • A meeting token with `is_owner` set to `true` [1] 

From the root directory of `daily-demos` run the following commands to start your webserver: 

`cd static-demos`

`npm run start`

Navigate to `static-demos/track-subs-demo/` and open `index.html` in the editor of your choice. At the top of the `createRoom()` function you should see `INSERT_ROOM` and  `INSERT_TOKEN`. Replace those with the meeting room url and token mentioned above and save your changes. 

Now if you open http://localhost:3001/static-demos/track-subs-demo/index.html in a browser you should see a working demo. One thing to note here is that clicking `create a room` does not actually create a room, it instead uses the room url that you added. We did this to ensure we had a way for everyone to enter the same room while using the additional controls in the demo. 

> Note: We recommend testing this out in incognito windows (or different simultaneous browsers) to make sure the meeting owner token is correctly detected (if you happen to be logged into your Daily account, it may consider you an owner without the token depending on your room settings). Also, since not all track subscriptions features are available in 1:1 calls, please test with three or more participants.

Getting back on track

If you’ve ever looked at our `basics-demo` this should all feel familiar. We’ve added a few new elements to demonstrate how to make use of track subscriptions. First off we’re using the new `daily-js` constructor property `subscribeToTracksAutomatically` and setting it to `false`. This tells `daily-js` to start the call by subscribing to no other tracks which means you won’t immediately see or hear anyone. We’ll need to explicitly subscribe to tracks for them to be visible/audible in the meeting. 

The easiest way to see this in action is to get a few participants in the meeting and try the `subscribe` and `unsubscribe` buttons. They use the new `setSubscribeToTracksAutomatically()` instance method. This allows you to toggle your track subscriptions for all participants while in a call. While this all or nothing approach has its uses, it may be more practical to selectively subscribe to a participant’s tracks. 

You’ll notice in the callback for the `joined-meeting` event we’re calling `subscribeToOwnerTracks()`. Let’s have a look at that function and see what’s going on. 


     -- CODE language-js --
      function subscribeToOwnerTracks() {
        const participants = callFrame.participants(); 
        let idsToBeSubscribed = [];  
        // add non local owners to to-be-subscribed list
        for (let id in participants) {
         let p = participants[id]
          if (p.owner === true && p.local === false) {
            idsToBeSubscribed.push(participants[id].session_id);
          }
        }

        // subscribe to list 
       idsToBeSubscribed.forEach((id) => {
         callFrame.updateParticipant(
           id,
           {
              setSubscribedTracks: true
           }
         );
       })
     }


Here we’re getting a list of participants by calling the `participants()` instance method. We’re then iterating over that list and storing the IDs of (non-local) owners in the `idsToBeSubscribed` array. We are then iterating over this array and calling `updateParticipant` for each of the IDs and setting their `setSubscribedTracks` property to `true`. This tells `daily-js` to subscribe to all of this participant’s tracks. This is equivalent to: 

`setSubscribedTracks : { audio: true, video: true, screenVideo: true}`

This allows you to selectively subscribe to a participant’s individual media tracks.

So we’ve covered how we’re subscribing to owner tracks. Finally, let’s take a look at how we’re subscribing to guest tracks as they join and then subsequently unsubscribing. 

You say hello, I say goodbye

We start by adding a custom callback for the `participant-joined` event called, you guessed it, `participantJoined`.
   

`callFrame.on('participant-joined', participantJoined)`


    -- CODE language-js --
    function participantJoined(e) {
     // subscribe to latest participant
     callFrame.updateParticipant(
       e.participant.session_id,
        {
           setSubscribedTracks: true
         }
       );

       // unsubscribe from latest after 30 seconds if they aren't an owner
       if (!e.participant.owner) {
         setTimeout( () => {
           callFrame.updateParticipant(
             e.participant.session_id,
             {
               setSubscribedTracks: false
             }
           );
           }, 30000)
       }
       updateParticipantInfoDisplay(e);
     }

Just like before, we’re calling `updateParticipant`. In this case, it’s with the ID of the participant that just joined the call. Then, if this participant is not an owner, we call `updateParticipant` again after 30 seconds (via `setTimeout`) to unsubscribe, by setting `setSubscribedTracks` to `false`.

Summary

So with just a few new lines of code, we’ve added the ability to dynamically control who can see each other in a call. In this case, we’ve created a relatively easy way to make more engaging and dynamic large meetings. 

Next steps

Here are a just a few thoughts on how to enhance what we’ve gone through today: 

  • Build a fully custom UI using callObject mode 
  • Allow an owner to control other participants’ subscriptions when someone needs to speak (audience questions perhaps!) 
  • Create dynamic virtual breakout rooms 

In future posts we’ll explore features like pagination and different host/guest views. Stay tuned!

For now, see our beta docs for further reading. 

-------

[0] Try creating one using our new dashboard! 

[1] See our article on room access control to learn more

Recent posts