Overview
It all comes down to this. All the setup so far has been done so that we can show live streaming video in our beautiful apps.
-
First, you need to get all the peers in the room. You can use the
selectPeersselector for this. -
Next, you'll need to have a reference to a video element. You must add the attributes as present below.
<video autoplay muted playsinline />
- You can then iterate over all the peers, and call the
attachVideofunction in hmsActions to render the video.
hmsActions.attachVideo(videoTrack.id, videoElement);
The videos will now be rendered & start playing on your screen. 🥳
In case you want to render local peer and remote peers separately, you can use the selectors - selectLocalPeer and selectRemotePeers.
Detach video to conserve bandwidth and cleanup elements
You can call detachVideo on the hmsActions to unsubscribe a track and not fetch it's data. This can be done
for example when a video goes out of view. This should also be called when the component is going to be unmounted
for proper cleanups of the video elements.
hmsActions.detachVideo(videoTrack.id, videoElement);
When to attach/detach videos
Starting from this release, sdk handles the subsequent attach/detach automatically for a given track.
- attach needs to be called once per track id. Repeat calls for the same track id are ignored, and the sdk takes care of mute/unmute, plugins, camera/device change and going in and out of view on its own. None of these change the peer's track id.
- detach needs to be called only when the element is removed from the dom while the track is still available. For the majority of cases, calling detach won't be needed.
When a session is moved to another media server, every peer republishes and peer.videoTrack becomes a new track id. The sdk's auto handling is scoped to one track id, so it cannot carry your element across to the replacement track.
If you attach once to peer.videoTrack and never subscribe to changes, the tile freezes the moment that happens and stays frozen for the rest of the call. Subscribe by peer id instead, so you always receive that peer's current track, and re-attach when the id changes.
Server migration needs no action from your app or your users, so it is the case that gets missed. The only other way peer.videoTrack changes is a real unpublish and republish of that peer's video — for example a role change to a role that cannot publish video, and then back to one that can.
Use the selectVideoTrackByPeerID selector with hmsActions.attachVideo and hmsActions.detachVideo:
// assuming you have a peer object and a video element // complete code snippet below let attachedTrackId = null; hmsStore.subscribe((track) => { const nextTrackId = track?.id ?? null; if (nextTrackId === attachedTrackId) return; // same track, e.g. mute/unmute - nothing to do if (attachedTrackId) hmsActions.detachVideo(attachedTrackId, videoElement); if (nextTrackId) hmsActions.attachVideo(nextTrackId, videoElement); attachedTrackId = nextTrackId; }, selectVideoTrackByPeerID(peer.id));
If you are on React, the useVideo hook from react-sdk already does exactly this — pass it peer.videoTrack and it re-attaches for you.
Example Snippet
import { hmsActions } from './hms'; const peersContainer = document.getElementById('peers-container'); // store peer IDs already rendered to avoid re-render on mute/unmute const renderedPeerIDs = new Set(); // render a single peer video tile function renderPeer(peer) { const peerTileDiv = document.createElement('div'); // you can either get an existing video element or create a new one. const videoElement = document.createElement('video'); const peerTileName = document.createElement('div'); videoElement.autoplay = true; videoElement.muted = true; videoElement.playsinline = true; peerTileName.textContent = peer.name; // Subscribe by peer id, not track id: a peer's track id changes when their session // moves to another media server. Re-attach whenever it changes. let attachedTrackId = null; hmsStore.subscribe((track) => { const nextTrackId = track?.id ?? null; if (nextTrackId === attachedTrackId) return; // same track, e.g. mute/unmute if (attachedTrackId) hmsActions.detachVideo(attachedTrackId, videoElement); if (nextTrackId) hmsActions.attachVideo(nextTrackId, videoElement); attachedTrackId = nextTrackId; }, selectVideoTrackByPeerID(peer.id)); peerTileDiv.append(videoElement); peerTileDiv.append(peerTileName); // Tile creation stays keyed on peer id so the element is not rebuilt on every change renderedPeerIDs.add(peer.id); return peerTileDiv; } // display a tile for each peer in the peer list function renderPeers(peers) { peersContainer = document.getElementById('peers-container'); peers.forEach((peer) => { if (!renderedPeerIDs.has(peer.id) && peer.videoTrack) { console.log( `rendering video for peer - ${peer.name}, roleName - ${peer.roleName}, isLocal- ${peer.isLocal}` ); peersContainer.append(renderPeer(peer)); } }); } // subscribe to the peers, so render is called whenever there is a change like peer join and leave hmsStore.subscribe(renderPeers, selectPeers);
Mirror video
To mirror local peer's video, apply the following CSS style to the video element.
/** enable mirror with -1 */ transform: scaleX(-1);
The 100ms sample app already does this by default. To turn it off, you can override this styling in code or disable the "Mirror Local Tile" from layout settings.



