Skip to main content

JavaScript Video Tutorial

In 10 minutes we'll build

A simple web app in vanilla javascript to make a video with audio from a caller to a callee.

Get an API Key

You need a Telnx account to create an API key so you can interact with our Rooms API

  • If you don't have one please: Sign up for a free account
  • Navigate to API Keys section and create an API Key by clicking Create API Key button.
  • Copy your API key

Create a Room

Let's use our Rooms API to create a room.

Request

Note

Don't forget to update YOUR_API_KEY here.

curl -X POST "https://api.telnyx.com/v2/rooms" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-binary '{
"unique_name": "My room",
"max_participants": "10",
"webhook_event_url": "https://example.com",
"enable_recording": "false"
}'

Response

{
"data": {
"active_session_id": null,
"created_at": "2022-07-29T01:25:51.938184Z",
"enable_recording": false,
"id": "599e4d1e-d0aa-40ee-867a-a527f2b2dccd",
"max_participants": 10,
"record_type": "room",
"unique_name": "Jons Room 2",
"updated_at": "2022-07-29T01:25:51.940294Z",
"video_codecs": ["h264", "vp8"],
"webhook_event_failover_url": "",
"webhook_event_url": "https://example.com",
"webhook_timeout_secs": null
}
}

Great, grab the response has an id field, this is the id for your newly created Room.

Generate a client token

You'll need a client access token to join the room.

To create one use our REST API rooms/create endpoint.

NOTE

You'll need to replace ROOM_ID in in the url of the command with your room id, above. As well as YOUR_API_KEY as you have in previous steps.

Request

curl -X POST \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "Authorization: Bearer YOUR_API_KEY" \
--data '{"refresh_token_ttl_secs":3600,"token_ttl_secs":600}' \
https://api.telnyx.com/v2/rooms/ROOM_ID/actions/generate_join_client_token

Response

{
"data": {
"recort_type": "client_token",
"refresh_token": "eyJhb***************************",
"refresh_token_expires_at": "2022-07-29T02:29:07Z",
"token": "eyJhb**********************************",
"token_expires_at": "2022-07-29T01:39:07Z"
}
}

The token field in the response is the client access token you'll use to join the room.

🏁 Checkpoint - run the app

At this point you should have the following:

  • An API Key
  • A room ID
  • A room client access token

Let's run the app in a sandbox

There's a README with instructions inside the sandbox. We can use this sandbox as a template to build out the rest of our application.

Let's write some code

Basic concepts of the video SDK

Here are some basic concepts of the SDK that will help you better understand how it works.

  • A Room represents a real time audio/video/screen share session with other people or participants. It is fundamental to building a video application.

  • A Participant represents a person inside a Room. Each Room has one Local Participant and one or more Remote Participants.

  • A Stream represents the audio/video media streams that are shared by Participants in a Room

    • A Stream is indentified by it's participantId and streamKey
  • A Participant can have one or more Stream's associated with it.

  • A Subscription is used to subscribe to a Stream belonging to a Remote Participant

Room Events

There are a few events trigger on a Room instance that we'll need to finish building this app that makes a video call.

  • connected - triggers when a room instance has connected to the server
  • participant_joined - triggers when a remote participant joins the room
  • stream_published - triggers when a stream has started being published to the room
  • subscription_started - triggers when subscription to remote stream has started

Handling an event looks like this:

room.on("connected", async () => {
...
});

🏁 Checkpoint

It might be helpful to take a look at the full list of Events available on a Room instance.

Connect to the room and get local media

NOTE: We can start implementing our code after the room.initialize block inside the sandbox above.

Let's connect to the room and use the standard WebRTC API to get the audio and video tracks from the device.

// connected to the room as the local participant
telnyxVideoClient.on("connected", async () => {
// use the webrtc api to get media from devices
let intercomStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
console.log("got local stream using getUserMedia...");

// Get audio and video tracks from the MediaStream's
// since the sdk works with MediaStreamTrack
let intercomAudioTrack = intercomStream.getAudioTracks()[0];
let intercomVideoTrack = intercomStream.getVideoTracks()[0];
});

Publish the stream and render it

We want to a add/publish a stream the room which consists of the audio and video track we received from the user's device, above.

We'll give the stream a key of "caller", which is how the stream is identified.

// add/publish a stream with a key "caller" to the room
await telnyxVideoClient.addStream("intercom", {
audio: callerAudioTrack,
video: callerVideoTrack
});
console.log("published local stream to the room...");

Render the stream to the DOM

Now, that we have the caller's stream we want to render it to the DOM.

The entire room.connected block looks like this:

// connected to the room as the local participant
room.on("connected", async () => {
// use the webrtc api to get media from devices
let callerStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
console.log("got local stream using getUserMedia...");

// Get audio and video tracks from the MediaStream's
// since the sdk works with MediaStreamTrack
let callerAudioTrack = callerStream.getAudioTracks()[0];
let callerVideoTrack = callerStream.getVideoTracks()[0];

// add/publish the stream with the key "caller" to the room
await room.addStream("caller", {
audio: callerAudioTrack,
video: callerVideoTrack
});
console.log("published local stream to the room...");

// render the caller stream to the page/DOM
let videoElement = document.getElementById("caller");
videoElement.srcObject = callerStream;
});

Great, we're now publishing the local partipants'/caller's stream, to the room which the callee can subscribe to.

Subscribe to the callee's remote stream

As a caller, how do we know when the callee has joined the room or has started publishing a stream?

That's where the participant_joined and the stream_published events that we went over earlier, come into play.

We need to listen for teh stream_published event like this:

// a stream has been published to the room
room.on(
"stream_published",
async (participantId, streamKey, state) => {
...
}
);

Understanding how subscriptions work

__The stream_published event is triggered for both local and remote streams. __

In our case, we want to know when the callee's remote stream starts publishing so we can subscribe to it. We already know that the caller has published a "caller" stream so we can ignore that event.

Subscribing to a stream

When a stream is published in a room it doesn't mean it's audio and video tracks are accessible, yet. In order to access a remote stream's track we must explicitly subscribe to that stream and wait for the subscription_started event.

// a stream has been published to the room
room.on("stream_published", async (participantId, streamKey, state) => {
// ignore streams that are published by the local participant
// we only care about remote stream from other remote participant
let participant = state.participants.get(participantId);
if (participant.origin === "local") {
return;
}

// the remote stream is identified using the participantId and streamKey
// you need to subscribe to a remote stream in order to access it's `MediaStreamTrack`s
await room.addSubscription(participantId, streamKey, {
audio: true,
video: true
});
});

Render the callee's remote stream

We're almost there! Now, we can listen to the subscription_started event and use args from that handler to get the callee's remote stream and render it to the DOM.

// a subscription to a remote stream has started
room.on("subscription_started", (participantId, streamKey, state) => {
console.log(
`subscription to the: ${participantId} ${streamKey} stream started...`
);

// use a helper method to easily access a remote participants' stream
let remoteStream = room.getParticipantStream(participantId, streamKey);

// create a MediaStream object from the remote stream's track so we can render it
// to a video element
let remoteMediaStream = new MediaStream([
remoteStream.audioTrack,
remoteStream.videoTrack
]);
const calleeVideo = document.getElementById("callee");
calleeVideo.srcObject = remoteMediaStream;
});

Run the app

To run the app:

From the caller app

  • Click the call button
  • You should be prompted by the browser for camera/micrphone access please allow.
  • You should also see video from your device's camera and hear audio from the microphone
  • You should see some console log, if things are working property

From the callee's app

  • Click the call button
  • Notice in the caller's app a stream subscription was logged
  • The callee's video/audio stream is rendered.