> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Video llms-full.txt

> Complete machine-readable documentation content for Video (Other APIs) for AI agents and LLMs

# Telnyx Other APIs: Video — Full Documentation

> Complete page content for Video (Other APIs section) of the Telnyx developer docs ([https://developers.telnyx.com](https://developers.telnyx.com)).
> This file: [https://developers.telnyx.com/docs/development/llms/other-apis-video-llms-full-txt.md](https://developers.telnyx.com/docs/development/llms/other-apis-video-llms-full-txt.md) · Root index: [https://developers.telnyx.com/llms.txt](https://developers.telnyx.com/llms.txt)

### Getting Started

> Source: [https://developers.telnyx.com/docs/video/get-started.md](https://developers.telnyx.com/docs/video/get-started.md)

Telnyx Video Rooms are enabled using the Rooms API. You can create as many rooms as needed using this API.
To access a Telnyx Video Room, a Client Join Token will first need to be
generated in JWT form. You can use our HTTPS API and authenticate using the
API Key associated with your Mission Control account under API Keys. Find out
more about authenticating with API V2 here.
Adding Telnyx Rooms functionality to your app can be done using our JS SDK
(mobile SDKs coming soon). To gain access to a live Video Rooms sample app,
reach out to our sales team today!
Check out the full API reference today!

## Glossary

Room
Resource representing a virtual place where multiple endpoints using one
of Telnyx’s Programmable Video SDKs can connect.
Room Session
Resource representing a moment where multiple Room Participants were
communicating within a given Room.
Room Participant
Resource representing an endpoint using one of Telnyx’s Programmable Video
SDKs to connect to a given Room.
JWT
JSON Web Token. A standard method for representing claims.
Client Join Token (JWT)
A JWT token which contains grants allowing in the Room usecase to join a
Room.
Refresh Token (JWT)
A JWT token which permits to obtain a new Client Token with same grants.
API Key
Secret API Key generated via Portal and used to authenticate Telnyx API
calls.
Video SDK
A library used to provide Video features to your application using Telnyx
Video platform.
Configuration and usage
Telnyx Video is enabled using Video Rooms. A Video Room represents
communications session among multiple endpoints using one of Telnyx’s
Programmable Video SDKs. Connected users (Participants) can share video and
audio Tracks with the Room, and receive video and audio Tracks from other
Participants in the Room. You can as many Rooms as you want. For example you
could create a long lived Room such as "Daily Standup", or Rooms that you
would delete after it's been used like "1-1 with X". To create a Video Room
you can use the REST API V2 documented\{" "}
here. A Video Room can only be joined
if the client owns a Client Join Token, you can create it using the
REST API V2 documented\{" "}
here. The Client Join
Token is short lived and you will be able to refresh it using the\{" "}
Refresh Token provided with it when you request for a Client Join
Token. Once you have a Video Room and an Client Join Token for it, you can
then use our Video SDK on your client side to connect your client to the Room
you just created.

## Concepts

### Architecture

`Video Rooms` is a platform that enables developers to add audio and video capability to Web, Android, and iOS applications.

The platform consists of REST APIs, Client SDKs, and our mission control portal that makes it really easily to capture, stream, record, and render live audio and video.

A video application built with Video Rooms has to parts:

* **Client:** Our client side Javascript, iOS, and Android SDKs used to interact with a `Room` instance
* **Server:** Our REST APIs and portal to create/manage room and session, configuring recording, or leverage our `Participants API` to moderator participants in a `Room`.

### Terms

Understanding the basic concepts of the video SDK will help you understand how it works. These concepts apply in general across all of our platforms.

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

* `Room State` tracks the state of the room as it changes making it extremely easy to understand what's happened to a `Room`.
  * `For example`: Room State could change due to a Local Participant has started publishing a stream or because a Remote Participant left.
    A Stream represents the audio/video media streams that are shared by Participants in a Room

* 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 `Subscription` is used to subscribe to a `Stream` belonging to a `Remote Participant`

### Dive a bit deeper

Dive a little bit deeper into our `Video Rooms` platform to get a better understand of what its capable of, and what you can buid. Learn more about our Client SDKs and Server APIs.

### Client SDKs

Our [Javascript SDK API reference](/docs/video/javascript-sdk) which details the API of our SDK including behaviors of the `Room` class and the `Events` that triggers and how they function.

* [Rooms Class API Reference](/docs/video/javascript-sdk/room-events#room)
* [Room Events Reference](/docs/video/javascript-sdk/room-events#room-events)

### Server APIs

* [Rooms](/api-reference/rooms/view-a-list-of-rooms) - manage Rooms
* [Client Access Tokens](/api-reference/rooms-client-tokens/create-client-token-to-join-a-room#create-client-token-to-join-a-room) - manage client access tokens needed to interact with a `Room`
* [Sessions](/api-reference/room-sessions/view-a-list-of-room-sessions) - manage room sessions, end a session, and mute/unmute/kick all participants in a given session.
* [Participants](/api-reference/room-participants/view-a-list-of-room-participants) - search for participants based on a number of filters like `session id`
* [Recordings](/api-reference/room-recordings/view-a-list-of-room-recordings) - manage recordings, including bulk delete.
* [Compositions](/api-reference/room-compositions/view-a-list-of-room-compositions) - create and manage compositions.

***

## SDKs

### Getting Started

> Source: [https://developers.telnyx.com/docs/video/javascript-sdk.md](https://developers.telnyx.com/docs/video/javascript-sdk.md)

The Telnyx Video Client SDK provides all the functionality you need to join and interact with a video room from a browser.

![npm](https://img.shields.io/npm/v/@telnyx/video.svg?color=lightgrey\&style=flat)

## Adding Telnyx to your JavaScript client application

Include the `@telnyx/video` npm module as a dependency:

`npm install @telnyx/video --save`

After pasting the above content, Kindly check and remove any new line added

Then, import `@telnyx/video` in your application code.

```javascript theme={null}
// main.js
import { Room, createLocalParticipant } from '@telnyx/video';
```

After pasting the above content, Kindly check and remove any new line added

Now you are ready to connect to a video room that you created. In order to connect to a video room you will require a client token that has the necessary grants to join the room.

```javascript theme={null}
const room = new Room(roomId, {
  clientToken: '<CLIENT_TOKEN_FOR_THE_ROOM>',
  localParticipant: createLocalParticipant({
    context: JSON.stringify({ name: 'Bob The Builder', id: 1 }), // send data that you can associate with this participant (for e.g., userId for this participant in your DB)
  }),
});

const stateCallback = (state) => {
  // the state object is immutable and can be easily integrated with most modern UI libraries like React, Vue etc.
};

room.on('state_changed', stateCallback);

room.connect().then(() => {
  console.log('You are connected to the room!');
});
```

After pasting the above content, Kindly check and remove any new line added

## Understanding the state of the video room

The `state_changed` event callback contains the state of the SDK at that point in time. This is an immutable object that you can use in most modern UI libraries like React and Vue.

The Typescript definition of the State is helpful in understanding the structure of this object.

```javascript theme={null}
type Status =
  | 'initialized'
  | 'connecting'
  | 'connected'
  | 'disconnecting'
  | 'disconnected';

interface State {
  status: Status;
  localParticipantId: Participant['id'];
  participants: {
    [id: string]: Participant;
  };
  streams: {
    [id: string]: Stream;
  };
}
```

After pasting the above content, Kindly check and remove any new line added

Everytime the state of the SDK changes the `state_changed` callback is invoked with a new immutable state that represents the current state of the SDK. Since most modern UI libraries are able to compare the two immutable states and render only the components that changed it makes it easier to integrate the SDK with them rather than depending on multiple event callbacks.

For e.g., based on how we initialized it in the above code example the initial state of the SDK would result in an object give below. Note that the ID of the local participant is unique and are generated based on UUID v4 standard when you create the local participant.

```javascript theme={null}
{
  status: 'initialized',
  localParticipantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
  participants: {
    "8c3bacb5-2e90-4379-8ee8-d5446213fee9": {
      id: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
      context: "{\"name\":\"Bob The Builder\",\"id\":1}",
      streams: {},
    },
  },
  streams: {},
};
```

After pasting the above content, Kindly check and remove any new line added

At this point there are no streams being published by the local participant. We will come to that shortly. The participant object follows the TypeScript interface given below.

```javascript theme={null}
interface Participant {
  id: string;
  context?: string;
  streams: {
    [key: string]: Stream['id'];
  };
}
```

After pasting the above content, Kindly check and remove any new line added

When we called the `room.connect()` method in the example code the state of the SDK will change to the following ...

```javascript theme={null}
{
  status: 'connecting',
  localParticipantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
  participants: {
    "8c3bacb5-2e90-4379-8ee8-d5446213fee9": {
      id: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
      context: "{\"name\":\"Bob The Builder\",\"id\":1}",
      streams: {},
    },
  },
  streams: {},
}
```

After pasting the above content, Kindly check and remove any new line added

This new state will be available to your application via the `state_changed` callback. Since state is immutable the only difference between the initial state and the new state is the `status` property. If you are using a modern UI library like React you can easily rerender the components to show that the application is connecting to the room.

When successfully connected the state changes to ...

```javascript theme={null}
{
  status: "connected",
  localParticipantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
  participants: {
    "8c3bacb5-2e90-4379-8ee8-d5446213fee9": {
      id: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
      context: "{\"name\":\"Bob The Builder\",\"id\":1}",
      streams: {},
    },
  },
  streams: {},
}
```

After pasting the above content, Kindly check and remove any new line added

Now we are ready to start publishing streams on behalf of the local participant.

## Publishing your local camera and mic stream

In order to publish a stream you need to define the constraints of the media. The simplest form of the constraints is ...

```javascript theme={null}
const constraints = { audio: true, video: true };
```

After pasting the above content, Kindly check and remove any new line added

With these constraints the SDK will try to obtain both audio and video from the local participant. In order to publish the stream as the local participant you have to use the `publish` method available to you via the room object.

```javascript theme={null}
room.publish('self', {
  constraints: { audio: true, video: true },
});
```

After pasting the above content, Kindly check and remove any new line added

The first argument to `publish` is a string that acts as the key that you can use to refer to this specific stream. You can use any valid string for this as long as you are consistent in your application. For e.g. here we're using 'self' for the camera/mic stream but you could use 'presentation' as the key for when you publish video of the screen. We will get to how you can publish your screen in a short while.

When you make the request to publish a stream, the browser will ask for the necessary permissions required to access the camera and mic. Once the permissions are acquired the SDK will configure and publish the stream to the room you are connected to.

At this point you will receive the new state to the `state_changed` event callback with the newly created stream.

```javascript theme={null}
{
  status: "connected",
  localParticipantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
  participants: {
    "8c3bacb5-2e90-4379-8ee8-d5446213fee9": {
      id: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
      context: "{\"name\":\"Bob The Builder\",\"id\":1}",
      streams: {
        self: "a87dd242-de78-4c19-a09c-23b336c9f25e",
      },
    },
  },
  streams: {
    "a87dd242-de78-4c19-a09c-23b336c9f25e": {
      id: "a87dd242-de78-4c19-a09c-23b336c9f25e",
      key: "self",

      // the constraits that was provided by you
      constraints: {
        audio: true,
        video: true,
      },
      bitrate: 256000, // also configurable when you publish a stream

      audioActive: false, // whether the audio track is being published
      videoActive: false, // whether the video track is being published
      source: MediaStream, // an instance of MediaStream that can be used to render video/audio
      audioTrack: undefined, // this will be an instance of MediaStreamTrack when the track is available
      videoTrack: undefined, // this will be an instance of MediaStreamTrack when the track is available

      isSpeaking: false, // whether the audio level of the track is high enough to consider the participant who owns this stream is speaking or not
      isRemote: false, // whether the stream originates from a remote source or not

      isPublishing: true, // whether the stream is being published
      isConfiguring: false, // whether the SDK is currently negotiating the WebRTC connection

      participantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
    },
  },
}
```

After pasting the above content, Kindly check and remove any new line added

The stream object is the most complex object in the state of the SDK. However you don't have to worry about all of these properties at the moment.

As you can see the `isPublishing` property of the stream is `true` now. This means that the stream is being published, which involves creating the SDP (Session Description Protocol) and establishing the WebRTC connection. You can't publish another stream of the same key ("self" in our case) until this stream is published or removed (by unpublishing).

Once the SDK acquires the media tracks from the camera and mic of the local participant a new state will be returned using the `state_changed` callback. This state will contain the audio and video track from the local media devices.

The streams object at this point will look like this

```javascript theme={null}
  streams: {
    "a87dd242-de78-4c19-a09c-23b336c9f25e": {
      id: "a87dd242-de78-4c19-a09c-23b336c9f25e",
      key: "self",

      // the constraits that was provided by you
      constraints: {
        audio: true,
        video: true,
      },
      bitrate: 256000,

      audioActive: true,
      videoActive: true,
      source: MediaStream,
      audioTrack: MediaStreamTrack,
      videoTrack: MediaStreamTrack,

      isSpeaking: false,
      isRemote: false,

      isPublishing: true,
      isConfiguring: true,

      participantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
    },
  },
```

After pasting the above content, Kindly check and remove any new line added

As you can see at this point you can use the `source` or the media tracks individually from `audioTrack` and `videoTrack` to show the media in your UI.

You can also see that the `audioActive` and `videoActive` flags are now true indicating the stream is publishing audio and video respectively.

Another property that was updated in the new state is the `isConfiguring` flag. You can ignore this in most use-cases as this indicates if the WebRTC connection is being negotiated.

Once the WebRTC connection is negotiated and the stream is successfully published the stream state would look like this

```javascript theme={null}
  streams: {
    "a87dd242-de78-4c19-a09c-23b336c9f25e": {
      id: "a87dd242-de78-4c19-a09c-23b336c9f25e",
      key: "self",

      // the constraints provided by you
      constraints: {
        audio: true,
        video: true,
      },
      bitrate: 256000,

      audioActive: true,
      videoActive: true,
      source: MediaStream,
      audioTrack: MediaStreamTrack,
      videoTrack: MediaStreamTrack,

      isSpeaking: false,
      isRemote: false,

      isPublishing: false,
      isConfiguring: false,

      participantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
    },
  },
```

After pasting the above content, Kindly check and remove any new line added

At this point the stream will be available to be subscribed by other clients connected to the same room.

## Knowing when a new participant joins the room

A video room can have multiple participants and you can get a list of all the participants connected to the room at the time by using the `participants` property in the state.

If the participant is not publishing any streams the `streams` property of that participant will be an empty object.

Let's imagine that another participant joins the same room from a different browser session with the following context

```javascript theme={null}
const room = new Room(roomId, {
  clientToken: '<CLIENT_TOKEN_FOR_THE_ROOM>',
  localParticipant: createLocalParticipant({
    context: JSON.stringify({ name: 'Oswald', id: 2 }), // send data that you can associate with this participant (for e.g., userId for this participant in your DB)
  }),
});
```

After pasting the above content, Kindly check and remove any new line added

Since the room already contains a participant who is publishing a stream the state once this session is connected would look something like ...

```javascript theme={null}
{
  status: "connected",
  localParticipantId: "d42926f5-fe6c-48d5-b24b-7444048fa68e",
  participants: {
    "8c3bacb5-2e90-4379-8ee8-d5446213fee9": {
      id: "8c3bacb5-2e90-4379-8ee8-d5446213fee9",
      context: "{\"name\":\"Bob The Builder\",\"id\":1}",
      streams: {
        self: "a87dd242-de78-4c19-a09c-23b336c9f25e",
      },
    },
    "d42926f5-fe6c-48d5-b24b-7444048fa68e": {
      id: "d42926f5-fe6c-48d5-b24b-7444048fa68e",
      context: "{\"name\":\"Oswald\",\"id\":2}",
      streams: {},
    },
  },
  streams: {
    "a87dd242-de78-4c19-a09c-23b336c9f25e": {
      id: "a87dd242-de78-4c19-a09c-23b336c9f25e",
      key: "self",

      audioActive: true, // whether audio is being published by the remote stream
      videoActive: true, // whether video is bein published by the remote stream
      source: MediaStream,
      audioTrack: undefined, // will be an instance of MediaStreamTrack once subscribed
      videoTrack: undefined, // will be an instance of MediaStreamTrack once subscribed
      videoCodec: "vp8", // the codec used by the remote video
      audioCodec: "opus", // the codec used by the remote audio

      isRemote: true,
      isSpeaking: false, // whether the remote participant is speaking or not

      isConfiguring: false, // whether the WebRTC connection is being negotiated or not

      subscription: {
        status: "unsubscribed", // will change to 'subscribed' once the the client subscribes to this stream
      },

      participantId: "8c3bacb5-2e90-4379-8ee8-d5446213fee9", // the ID of the participant this stream belong to
    },
  },
}
```

After pasting the above content, Kindly check and remove any new line added

As you can see there is already a participant in the `participants` object that maps to the remote particpant from the previous browser session. This remote participant also has a stream with the key "self".

The stream object for "self" has the structure very similar to the one we saw before but has a few new properties.

The ones that are particularly interesting are `subscription`, `videoCodec`, and `audioCodec`. The properties for video and audio codec can be used to decide if the browser has the capabilities to decode the video and audio tracks.

Then there is `subscription.status`, which is `unpublished` at the moment.

## Subscribing to a remote stream

At this point we are ready to subscribe to the remote stream published by the participant. In order to do that we use the `subscribe` method from the room object.

```javascript theme={null}
room.subscribe('a87dd242-de78-4c19-a09c-23b336c9f25e');
```

After pasting the above content, Kindly check and remove any new line added

This will start the WebRTC negotiation to receive the remote stream. Once successful the `subscription.status` will change to `subscribed`.

You can use the `source` property, which is an instance of MediaStream to render the media on the browser.

If the remote participant unpublishes this stream the SDK will automatically unsubscibe from the stream and the stream will disappear from the state.

***

### Room and Events

> Source: [https://developers.telnyx.com/docs/video/javascript-sdk/room-events.md](https://developers.telnyx.com/docs/video/javascript-sdk/room-events.md)

Telnyx's video JavaScript SDK lets you manage room events and join/leave notifications.

We've made it much easier to add video capability to your web application using our Javascript SDK.

The `Room` object and the `Events` that occur in a `Room` is the API you will use to build your video application. We've extensively documented the interface for the `Room` and `Events` objects, below.

## Room

```javascript theme={null}
export type Room = Immutable<{
  /// The unique identifier of the Room instance
  id: string;

  /// Event listener/handler for various events that are triggered in the room.
  /// See the Events interface for a list.
  on<E extends keyof Events>(event: E, callback: Events[E]): Unsubscribe;

  /// Connects a Room instance to the server
  /// The state of the room, specifically the status of the room, will change while it's connecting,
  /// if you can listen to these changes with the state_changed event.
  /// Once the room is connected its local partipant will available in State.
  connect: () => Promise<void>;

  /// Disconnect a Room instance from the server
  /// The status of the state of the room will change while it's disconnecting.
  disconnect: () => Promise<void>;

  /// Updates the client token
  /// The client token is a short lived access token that expires after a time to live in seconds,
  /// which needs to be refreshed using a refresh token.
  /// If you are farmilar with how JWT's work you'll understand, head here for [more details](https://jwt.io)
  /// This was set when the client token was created - the default TTL is 600 seconds or 10 minutes.
  ///
  /// Client tokens need to refreshed before they expire otherise the Room instance will automatically
  /// disconnect.
  /// It's up to the developer to keep the client token fresh.
  /// To create a client token please use the `generate_join_client_token` endpoint
  /// To refresh a token please use `refresh_client_token` endpoint
  updateClientToken: (clientToken: string) => Promise<void>;

  /// Gets the state of the Room
  /// What does the state of the room look like?
  /*
  type Status =
    | 'initialized'
    | 'connecting'
    | 'connected'
    | 'disconnecting'
    | 'disconnected';

    interface State {
      status: Status;
      localParticipantId: Participant['id'];
      participants: {
        [id: string]: Participant;
      };
      streams: {
        [id: string]: Stream;
      };
    }
  */

  /// State is important because there can be lot of things changing in a room at any given moment, especially for really large rooms.
  /// Participants leaving the room, new participants joining, other starting/stopping their video, a moderator kicking
  /// a participant out.
  /// It would be a lot to keep track of manually, which is why we have state.
  /// Don't worry we've abstracted all the accounting, inside our SDK, and provided you with helper methods
  /// like this one, getParticipant(), and getLocalStreams() to make access simple.
  getState: () => State;

  /// Participants and Streams
  /// There's a two entities that are important to understand
  /// and as you can see from the State interface above they are big
  /// part of the State of the Room.
  ///
  /// Stream
  /// A stream represents media stream published by Participant in a Room.
  /// It's identified by it's participantId and streamKey and typically has a audio track or a video
  /// track, or both.

  /// Participants
  /// There are two type of participants: local and remote.
  /// Remote Participant - other participants in the room who are not the local participant.
  /// A local partipant subscribes to streams published by remote participants.
  ///
  /// Local Participant - is the person on a mobile device or desktop that joins the room to publish a stream.
  /// **NOTE**: There can only be one local participant in a room at a given time.

  /// Gets the local participant for a given Room instance
  /// You can use this method to find more details about the local participant in the Room
  ///
  /// What does a Participant look like?
  /*
    export type Participant = Immutable<{
      id: string;
      context?: string;
      streams: {
        [key: string]: Stream['id'];
      };
      canReceiveMessages: boolean;
      origin: 'local' | 'remote' | 'telephony_engine';
    }>;
  */
  getLocalParticipant: () => Participant;

  /// Get the streams associated to the local participant
  getLocalStreams: () => { [key: Stream['key']]: Stream };

  /// Add a stream to a Room
  /// key - is the unique identifier for the stream based on its realted participant.
  /// tracks.audio - the audio track
  /// tracks.video - the video track
  /// Once the promise is completed you can assume that the stream has started publishing in the room.
  addStream: (
    key: Stream['key'],
    tracks?: {
      audio?: MediaStreamTrack;
      video?:
        | MediaStreamTrack
        | { track?: MediaStreamTrack; options?: { enableSimulcast?: boolean } };
    }
  ) => Promise<void>;

  /// Update an existing stream in a Room
  /// key - identifier of the stream
  /// tracks.audio - the audio track
  /// tracks.video - the video track
  ///
  /// Why would you update a Stream?
  /// If you wanted to mute the audio of a particular stream you would update
  /// it and set the track to null
  updateStream: (
    key: Stream['key'],
    tracks?: {
      audio?: MediaStreamTrack;
      video?:
        | MediaStreamTrack
        | { track?: MediaStreamTrack; options?: { enableSimulcast?: boolean } };
    }
  ) => Promise<void>;

  /// Remove a stream
  /// key - identifier of the stream
  /// Removing a stream removes or unpublishes it from the room.
  removeStream: (key: string) => Promise<void>;

  /// Subscriptions
  /// A subscription is used to subscribed to a stream published by a Remote Participant

  /// Subscribe to a stream belonging to a remote participant
  /// participantId - the id of the remote participant
  /// key - the stream key of the stream
  /// config.audio - flag to indicate if you'd like to subscribe to the remote streams' audio
  // config.video - flag to indicate if you'd like to subscribe to the remote streams' video
  /// When the promise is fullfilled a remote stream is added to state.streams
  /// which then can be used to render the audio or video of that stream.
  /// You can access remote participant streams more easily using the getParticipantsStreams
  // and getParticipantStreams helper methods below.
  ///
  /// A developer is not required to subscribe to every remote stream being published in the room instead
  /// they can choose. This is very useful in situations conserving bandwidth and cpu resources is a priority,
  /// For example:
  /// A developer building a vidoe conferencing application and wants to support 100+ participants may only want subscribe
  /// to 15-20 participants at a time display each set in a seperate page, thus significantly saving bandwith and resources.
  addSubscription: (
    participantId: Participant['id'],
    key: Stream['key'],
    config: { audio: boolean; video: boolean }
  ) => Promise<void>;

  /// Update an existing subscription
  /// participantId - the id of the remote participant
  /// key - the stream key of the stream
  /// config.audio - flag to indicate if you'd like to subscribe to the remote streams' audio
  // config.video - flag to indicate if you'd like to subscribe to the remote streams' video
  /// When a remote participant toggles audio or video on a stream that you're subscribed to
  /// you'll need to update that subscription.
  updateSubscription: (
    participantId: Participant['id'],
    key: Stream['key'],
    config: { audio: boolean; video: boolean }
  ) => Promise<void>;

  /// Remove or stop subscribing to a remote participant's stream
  removeSubscription: (
    participantId: Participant['id'],
    key: Stream['key']
  ) => Promise<void>;

  /// Helper method to easily access all streams for a given remote participant
  getParticipantStreams: (
    participantId: Participant['id']
  ) => Map<Stream['key'], Stream>;

  /// Helper method to easily access a remote participant stream
  getParticipantStream: (
    participantId: Participant['id'],
    key: Stream['key']
  ) => Stream | undefined;

  /// Provides statistics for a local or remote stream
  /// It presents the same data you'd get from doing:
  /// RTCRtpSender.getStats() - https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender/getStats
  /// RTCRtpReceiver.getStats() - https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpReceiver/getStats
  /// using the standard WebRTC APIs but in a much more readable format.
  getWebRTCStatsForStream: (
    participantId: Participant['id'],
    key: Stream['key']
  ) => Promise<WebRTCStats>;

  /// Send a message to one, more than one, or all participants in the room
  /// message - a message type, current only 'text' is supported
  /// receipients? - an array of participants to send the message, if
  /// null the message is broadcasted to everyone in the room.
  sendMessage: (
    message: Message,
    recipients?: Array<Participant['id']>
  ) => Promise<void>;

  enableNetworkMetricsReport: (
    participantIds: Array<Participant['id']>,
    options?: { includeStreams?: boolean }
  ) => Promise<void>;

  disableNetworkMetricsReport: (
    participantIds?: Array<Participant['id']>
  ) => Promise<void>;
}>;
```

## Room events

```javascript theme={null}
export interface Events {
  /// Triggered when the state of the room changes
  /// For more
  /// TODO
  state_changed: (state: State) => void;

  /// Triggers on a Room instance when it connects to the server
  connected: (state: State) => void;

  /// Triggers on a Room instance when it disconnects from the server
  disconnected: (state: State) => void;

  /// Triggered when a remote participant joins the room
  /// Only triggered for remote participants the local participant does not
  /// need to be notified that they've joined the room since they've initiated
  /// that action themselves by connecting to the Room
  participant_joined: (participant: Participant['id'], state: State) => void;

  /// Triggered when a  participant is leaving the room because they were kicked
  /// due to a moderator event
  /// Unlike the joined and left events the local participant can be kicked from the room
  participant_leaving: (
    participant: Participant['id'],
    reason: 'kicked' | null,
    state: State
  ) => void;

  /// Triggered when a remote participant leaves the room
  participant_left: (participantId: Participant['id'], state: State) => void;

  /// Triggered after successfully adding or publishing a stream to the room
  /// This event is triggered for local and remote streams.
  /// A local stream is one that's published by the local participant in the Room
  /// A remote stream is one that's published by a remote participant in the Room
  stream_published: (
    participantId: Participant['id'],
    key: Stream['key'],
    state: State
  ) => void;

  /// Triggered after successfully unregistering a stream
  /// This event is triggered for both local and remote streams.
  /// It triggered when the local or remote participant remove or stops publishing a stream.
  /// When a remote participant leaves a room it will trigger for any of their related streams.
  stream_unpublished: (
    participantId: Participant['id'],
    key: Stream['key'],
    state: State
  ) => void;

  /// Triggered when a local stream or a remote stream track has been enabled.
  /// Notifies consumers about remote stream tracks being enabled.
  /// For example: when audio is unmuted or video has started on a remote stream.
  track_enabled: (
    participantId: Participant['id'],
    key: Stream['key'],
    kind: 'audio' | 'video',
    state: State
  ) => void;

  /// The oposite of track_enabled
  /// Triggers when a local stream or a remote stream track has been disabled.
  /// Notifies consumers about remote stream tracks being disabled.
  /// For example: when audio is muted or video has stopped on a remote stream.
  track_disabled: (
    participantId: Participant['id'],
    key: Stream['key'],
    kind: 'audio' | 'video',
    state: State
  ) => void;

  /// Triggered  when a track is censored due to a moderator event
  /// Like the kick moderator event in participant_leaving, both local and remote streams can be
  /// censored.
  ///
  /// Since a Stream consists of an audio and video track, either can be censored by a moderator
  /// If the audio or video track on given stream is null, effectively nothing happens.
  track_censored: (
    participantId: Participant['id'],
    key: Stream['key'],
    kind: 'audio' | 'video',
    state: State
  ) => void;

  /// Triggered when a  track is uncensored due to a moderator event
  /// Opposite of track_censored
  /// Naturally, a track must be censored to order be uncensored.
  track_uncensored: (
    participantId: Participant['id'],
    key: Stream['key'],
    kind: 'audio' | 'video',
    state: State
  ) => void;

  /// Triggered when there is audio activity from a particular stream or participant talking in the Room
  /// When there's audio activity when a participant speaking this event will trigger with the participant id that's talking and the stream key will be null
  audio_activity: (
    participantId: Participant['id'],
    key: Stream['key'] | null,
    state: State
  ) => void;

  /// Triggered a subscription to a remote stream is started
  subscription_started: (
    participantId: Participant['id'],
    key: Stream['key'],
    state: State
  ) => void;

  /// Triggered when the subscription is reconfigured using the updateSubscription method
  subscription_reconfigured: (
    participantId: Participant['id'],
    key: Stream['key'],
    state: State
  ) => void;

  /// Triggered when subscription is removed or ended for a remote stream
  /// A subscription can be ended by calling `removeSubscription(ParticipantId,StreamKey)`
  /// This is also triggered when a remote participant leaves the room.
  subscription_ended: (
    participantId: Participant['id'],
    key: Stream['key'],
    state: State
  ) => void;

  /// onMessageReceived
  /// Triggered when a new message is recieved either by one or more participants or broadcasted to all participants
  /// in the room.
  /// participantId - sender of the message
  /// recipients - an array of participants the message was sent to. when null the message was broadcasted to the all participants in the room.
  message_received: (
    participantId: Participant['id'], // the participant that sent the message
    message: Message,
    recipients: Array<Participant['id']> | null,
    state: State
  ) => void;

  network_metrics_report: (networkMetrics: NetworkMetrics) => void;
}
```

***

### Tutorial

> Source: [https://developers.telnyx.com/docs/video/javascript-sdk/javascript-video-tutorial.md](https://developers.telnyx.com/docs/video/javascript-sdk/javascript-video-tutorial.md)

## 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**

*Don't forget to update `YOUR_API_KEY` here.*

```bash theme={null}
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**

```json theme={null}
{
  "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.](/api-reference/rooms/create-a-room#create-a-room)

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**

```bash theme={null}
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**

```json theme={null}
{
  "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:

```javascript theme={null}
room.on("connected", async () => {
  ...
});
```

## 🏁 Checkpoint

> It might be helpful to take a look at the [full list of `Events` available on a `Room` instance.](/docs/video/javascript-sdk/room-events)

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

```javascript theme={null}
// 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.

```javascript theme={null}
// 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:

```javascript theme={null}
// 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](#lets-write-some-code), come into play.

We need to listen for teh `stream_published` event like this:

```javascript theme={null}
// 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.

```javascript theme={null}
// 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.

```javascript theme={null}
// 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:

* Open the sandbox, the browser here represents the caller's app
* Open the app in another tab, this represents the callee's application which you'll need to interact with.

**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
  * If you see errors your [client token has expired and you need to regenerate it.](#generate-a-client-token)

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

***

### Android SDK

> Source: [https://developers.telnyx.com/docs/video/android-client-sdk.md](https://developers.telnyx.com/docs/video/android-client-sdk.md)

The Telnyx Video Android SDK is simple to use and makes it easy to get started with video calling. With this SDK, you can easily add video calling to your app with just a few lines of code.

It provides all the functionality you need to join and interact with a Telnyx Room from an Android application.

#### A link to the repo can be found here:

Android SDK

## Project structure

* SDK project: sdk module, containing all Telnyx SDK components as well as tests.
* Demo application: app module, containing a sample demo application utilizing the sdk module.

## Adding the SDK to your Android client application

Add Jitpack.io as a repository within your root level build file:

```
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
```

Add the dependency within the app level build file:

```
dependencies {
    implementation 'com.github.team-telnyx:telnyx-video-android:<tag>'
}
```

Tag should be replaced with the release version.

Then, import the TelnyxVideo SDK into your application code at the top of the class:

```
import com.telnyx.video.sdk.*
```

The '\*' symbol will import the whole SDK which will then be available for use within that class.

Remember to add and handle INTERNET, RECORD\_AUDIO and ACCESS\_NETWORK\_STATE permissions in order to properly use the SDK.

```
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
```

## Before connecting to a Room

## Get an API Key

You'll need an API key which is associated with your Mission Control Portal account under **API Keys**. You can learn how to do that [here](/docs/development/api-fundamentals/create-api-keys).

An API key is your credential to access our API. It allows you to:

* to authenicate to the Rest API
* to manage your access tokens

## Create a Room to join (if it doesn't exist)

In order to join a room you must create it, if it doesn't already exist. See our [Room Rest API](/api-reference/rooms/view-a-list-of-rooms) to create one.

There's also additional resources on other endpoints available to perform basic operations on a `Room`.

## Generate an a client token to join a room

In order to join a room you must have a client token for that `Room`. The `client token` is short lived and you will be able to refresh it using the `refresh token` provided with it when you  request a `client token`.

Please see the [docs here](/api-reference/rooms-client-tokens/create-client-token-to-join-a-room#create-client-token-to-join-a-room) to learn how to create a `client token`.

Now you are ready to connect to a video room that you previously created using the REST API.

## Connect to Room

To connect, you'll need to provide a **participantName** that will identify your user in that room.

You'll also need to provide an instance of *ExternalData* that will contain a *username* of type String and an Integer *id*. You will also provide your Android application's context in **context**

```
room = Room(
    context = context,
    roomId = UUID.fromString(roomId),
    roomToken = tokenInfo.token,
    externalData = ExternalData(id = 1234, username = "Android Participant")
    enableMessages = false
)
...

...
room.connect()

```

## Publish video/audio stream

To publish as video or audio stream, we will need an instance of *PublishConfigHelper* with the *application context*, camera *direction*, *streamKey* (unique for each stream published), and *streamId* (unique for each stream published)

```java theme={null}
//AUDIO
publishConfigHelper =
        PublishConfigHelper(
            context = requireContext(),
            direction = CameraDirection.FRONT,
            streamKey = SELF_STREAM_KEY // a key to identify this stream i.e: "self"
            streamId = SELF_STREAM_ID // RANDOM id to this stream i.e: "qlkj323kj423"
        )
    publishConfigHelper.createAudioTrack(true, // isTrackEnabled?
                                        AUDIO_TRACK_KEY // i.e: "myMic", "002"
                                        )
...

...
room.addStream(publishConfigHelper) // This stream is new. addStream() is called.
```

New streams can be created via the PublishConfigHelper class for both audio and video. They can be created together or added later independently.

```java theme={null}
//VIDEO

//NOTE: in this case, video is published in the same stream as above
publishConfigHelper.setSurfaceView(selfSurfaceRenderer) //Provide SurfaceRenderer

publishConfigHelper.createVideoTrack(
            CapturerConstraints.WIDTH.value,// i.e: 1280
            CapturerConstraints.HEIGHT.value,// i.e: 720
            CapturerConstraints.FPS.value, //i.e: 30 (fps)
            true, // isTrackEnabled?
            VIDEO_TRACK_KEY //i.e: i.e "cameraFeed", "001"
        )
...

...
room.updateStream(publishConfigHelper) // Stream already created, therefore updateStream is called.
```

Since stream is already created, it is only necessary to add the video track to PublishConfigHelper, and "update" the stream.

## Remove video/audio track

To remove a video or audio track, publishConfigHelper has to be modified in order to remove the unwanted track

````java theme={null}
//Considering publishConfigHelper is the same instance as above

publishConfigHelper?.let {
            it.stopCapture()   //In case of video, we "stop the capture", update the stream, and release the surface
            roomsViewModel.updateStream(it)
            selfSurface?.let { surface -> it.releaseSurfaceView(surface) }
        }
...

```java
publishConfigHelper?.let &#123;
            it.disposeAudio()    //In case of audio, we "dispose" audio and update the stream.
            roomsViewModel.updateStream(it)
        &#125;
````

## Remove stream

By removing the stream, we will remove all tracks added to it.

```java theme={null}
room.removeStream(SELF_STREAM_KEY) // a key to identify this stream i.e: "self"
```

## Video/Audio Observables

The Telnyx Video SDK for android works with mutable live data, and all the information you need to build your UI is provided through **observables** that will contain the most up to date information of the state of the room, such as **current participant list**, **talking events**, **stream information**, **participants added**, **participants leaving**, etc

## State Observable

```java theme={null}
    room.getStateObservable()
    // MutableLiveData
```

This observable will provide the current state of a room at any given moment.
We will receive a State object that will contain:

```java theme={null}
data class State(
    val action: String,
    val status: String,
    val participants: List,
    val streams: HashMap,
    val publishers: List,
    val subscriptions: List
)
```

**action** -> \`\`val action: String\`
is the cause of the latest change of State

```java theme={null}
enum class StateAction(val action: String) &#123;
    INITIALIZING_ROOM("initializing room"),
    STATUS_CHANGED("status changed"),
    ADD_PARTICIPANT("add participant"),
    REMOVE_PARTICIPANT("remove participant"),
    ADD_STREAM("add stream"),
    REMOVE_STREAM("remove stream"),
    ADD_SUBSCRIPTION("add subscription"),
    UPDATE_SUBSCRIPTION("update subscription"),
    REMOVE_SUBSCRIPTION("remove subscription"),
    PUBLISH("publish"),
    UNPUBLISH("unpublish"),
    UPDATE_PUBLISHED_STREAM("update published stream"),
    AUDIO_ACTIVITY("audio activity")
&#125;
```

**status** -> `val status: String`
is the status of the Room session, when that action happened

<a id="status-header" />

```java theme={null}
enum class Status(val status: String) &#123;
    INITIALIZED("initialized"),
    CONNECTING("connecting"),
    CONNECTED("connected"),
    DISCONNECTING("disconnecting"),
    DISCONNECTED("disconnected")
&#125;
```

**participants** -> `val participants: List<Participant>` <a id="participant-header" />
is the list of participants present in a room. A Participant is a UI representation in variables, for an attendee to the room session.

<a id="participant-header" />

```java theme={null}
data class Participant(
    var id: Long,
    val participantId: String,
    var externalUsername: String? = null,
    val isSelf: Boolean,
    var streams: MutableList = mutableListOf(),
    var isTalking: String?,
    var isAudioCensored: Boolean? = false,
    var audioBridgeId: Long? = null,
    var canReceiveMessages: Boolean = false
) : Serializable

data class ParticipantStream(
    val publishingId: Long? = null,
    var streamKey: String? = null,
    var audioEnabled: StreamStatus = StreamStatus.UNKNOWN,
    var videoEnabled: StreamStatus = StreamStatus.UNKNOWN,
    var audioTrack: AudioTrack? = null,
    var videoTrack: VideoTrack? = null
)

enum class StreamStatus(val request: String) &#123;
    UNKNOWN("unknown"),
    ENABLED("enabled"),
    DISABLED("disabled")
&#125;

```

**streams** -> `val streams: HashMap<Long, Stream>`
this hash map, contains a track of the currently available streams and the **id** of the **publisher** streaming them.
A stream, will contain tracks for video, audio or both.

```java theme={null}
data class Stream(
    val id: String,
    val key: String,
    val participantId: String,
    val origin: String,
    var isAudioEnabled: Boolean? = null,
    var isVideoEnabled: Boolean? = null,
    var isAudioCensored: Boolean? = null,
    var isVideoCensored: Boolean? = null
)
```

**publishers** -> `val publishers: List<Publisher>`

This will track all publishers in the room. A Publisher is an instance of an attendee or participant sharing some stream content in the room.

<Callout type="info">
  A single Participant, sharing multiple streams can be also linked to multiple Publisher ids.
</Callout>

```java theme={null}
data class Publisher(
    val audio_codec: String?,
    val video_codec: String?,
    val display: String, // see DisplayParameters.kt
    val id: Long,
    val talking: Boolean,
    val audio_moderated: Boolean? // aka Censored
)

data class DisplayParameters(
    val participantId: String,
    val telephonyEngineParticipant: Boolean? = null,
    val external: String? = null,
    val stream: StreamData? = null,
    val canReceiveMessages: Boolean? = null
)
```

**subscriptions** -> `val subscriptions: List<Subscription>`

Each time a publisher starts streaming information, we will have the option of subscribe/unsubscribe to/from it. This list will track all the subscriptions.

```java theme={null}
data class Subscription(
    var publisherId: Long,
    var status: SubscriptionStatus
)

enum class SubscriptionStatus(val status: String) &#123;
    NEVER_REQUESTED("never_requested"),
    PENDING("pending"),
    STARTED("started"),
    PAUSED("paused")
&#125;

```

## Event observables

Some observables will have a MutableLiveData of Event. Event is a wrapper of LiveData, and provide the means to ensure we only handle an observable once, no matter how many times we're set to observe it.
If the contents have already been handled, we won't get that content again, unless we *peekContent()* instead.

```java theme={null}
/**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
open class Event(private val content: T) &#123;

    var hasBeenHandled = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandled(): T? &#123;
        return if (hasBeenHandled) &#123;
            null
        &#125; else &#123;
            hasBeenHandled = true
            content
        &#125;
    &#125;

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peekContent(): T = content
&#125;
```

## Participants Observable

```java theme={null}
room.getParticipantsObservable()
// MutableLiveData>
```

This mutable list will be received as soon as we join a Room session, and contains a list of the participants already present in the room, including yourself. The SDK will keep this list updated but won't post the changes, so this observer won't be fired again. See [Participant](#participant-header)

By receiving this list we can initialize a recycler adapter in order to show participants:

```java theme={null}
roomsViewModel.getParticipants().observe(viewLifecycleOwner) &#123; participants ->
  participants.let &#123; participantList ->
    participantAdapter.setData(participantList)
    if (participantList.size > 0)&#123;
      selfParticipantId = participantList[0].participantId
      selfParticipantHandleId = participantList[0].id
    &#125;
  &#125;
&#125;
```

## Joined Room Observable

```java theme={null}
    room.getJoinedRoomObservable()
    // MutableLiveData>
```

This mutable will fire an event as soon as we have connected to a room, and we have retrieved an *initial* list of [Participants](#participant-header)

It is useful when we want to update UI as soon as we have joined the room sucessfully.

<Callout type="info">
  This will be different to Status-CONNECTED that will be issued when we have successfully joined our plugins to handle session audio. See [Status](#status-header).
</Callout>

## Joined Participant Observable

```java theme={null}
    room.getJoinedParticipant()
    //MutableLiveData>
```

We receive the participant that has joined the room after client has already joined.
We can add the this reference to the list used in our adapter as:

```java theme={null}
roomsViewModel.getJoinedParticipant().observe(viewLifecycleOwner) &#123; participantJoined ->
  participantJoined?.let &#123; joinedParticipantEvent ->
    joinedParticipantEvent.getContentIfNotHandled()?.let &#123;
      participantsAdapter.addParticipant(it)
    &#125;
  &#125;
&#125;
```

## Leaving participant id Observable

```java theme={null}
    room.getLeavingParticipantId()
    //MutableLiveData>
```

We receive the publisherId that has leaved the room, and a reason for its exit ("Left" or "Kicked").

```java theme={null}
roomsViewModel.getLeavingParticipantId()
    .observe(viewLifecycleOwner) &#123; participantLeavingId ->
        participantLeavingId?.let &#123; (id, reason) ->
            participantAdapter.removeParticipant(id)
            if (id == selfParticipantHandleId && reason == "kicked") &#123;
                //It's ourselves, remove from the room.
                goBack(wasKicked = true)
                Toast.makeText(requireContext(), "You were kicked!", Toast.LENGTH_LONG).show()
            &#125;
        &#125;
    &#125;
&#125;
```

## Connected to room Observable

```java theme={null}
    room.getConnectionStatus()
    //LiveData
```

Receive true when we have opened a webSocket and connected to the room

```java theme={null}
roomsViewModel.connectedToRoomObservable().observe(this.viewLifecycleOwner) &#123;
    it?.let &#123; isConnected ->
        if (isConnected) &#123;
            buttonCreateRoom.isEnabled = true
        &#125;
    &#125;
&#125;
```

## Participant stream changed Observable

```java theme={null}
    room.getParticipantStreamChanged()
    //MutableLiveData>
```

We will receive here an event with the participant that has recently changed its video and/or audio stream status. Mutable list of participants will also be updated with this change, but here the specific individual is received.

## Stream status

```java theme={null}
enum class StreamStatus(val request: String) &#123;
    UNKNOWN("unknown"),
    ENABLED("enabled"),
    DISABLED("disabled")
&#125;
```

These status apply for audio, video and shared screen:

***UNKNOWN*** : initial status. We don't have information on whether this participant is sharing audio/video/screen

***ENABLED*** : the participant is sharing audio/video/screen and we can subscribe to a stream for it like:

***DISABLED*** : the participant is not sharing audio/video/screen. If we were subscribed to that participant audio/video/screen we can unsubscribe from it.

## Subscribe to a video stream

In order to subscribe to a **video stream**, there are 3 actions that needs to be performed:

```java theme={null}
StreamStatus.ENABLED -> &#123;
    // This notifies the WebRTC connection we're ready to receive stream information
    participantTileListener.subscribeTileToStream(model.participantId, "self")

    itemView.participant_tile_surface.visibility = View.VISIBLE
    itemView.participant_tile_place_holder.visibility = View.GONE

    // This ensures surfaces are initialized in an EglContext provided inside WebRTC connection
    participantTileListener.notifyTileSurfaceId(
        itemView.participant_tile_surface,
        model.participantId,
        "self"
    )

    model.streams.find &#123; it.streamKey == "self" &#125;?.videoTrack?.let &#123;
        if (viewHolderMap[holder] != it) &#123;  // NOTE: keep a map of surfaces to release

            // Updates only if previous register differs from what we need
            viewHolderMap[holder]?.removeSink(holder.itemView.participant_tile_surface)
            holder.itemView.participant_tile_surface.release()
            viewHolderMap[holder] = it

            it.addSink(itemView.participant_tile_surface)
            it.setEnabled(true)
        &#125;
    &#125;
&#125;
```

1 Provide the SDK with the same instance of *SurfaceViewRenderer* you want to *init*:

```java theme={null}
    room.setParticipantSurface(
        participantId: String,
        surface: SurfaceViewRenderer,
        streamKey: String //Stream key to indentify the webrtc connection to init this surface
    )
```

This will use the proper **WebRTC Connection** to provide an *EglContext* for the surface to be *init*

2 Use method *addSink()* to add a *SurfaceViewRenderer* instance to the videoTrack provided for a [Participant](#participant-header), and set that track to enable *videoTrack?.setEnabled(true)*

3 Subscribe to the stream a participant is providing

```java theme={null}

    participantTileListener.subscribeTileToStream(model.participantId, "self")

    // Eventually calls:

    room.addSubscription(
        participantId: String,
        streamKey: String,
        streamConfig: StreamConfig,
    )
```

*participantId* is the participant id that uniquely identifies a single participant in the room

*streamKey* i.e: "SharingSubscriptions" "CameraSubscriptions"

*streamConfig* whether we want to subscribe to audio/video or both. By default we will attempt both.

## Remove subscription to a \*video stream

To remove a subscription we need to issue

```java theme={null}
    room.removeSubscription(participantId: String, streamKey: String)
```

A good practice when handling surfaces is to make sure you remove this surface properly from the rendering context before eliminating or removing the surface from the UI:

```
    // Here, order is important
    videoTrack.removeSink(surfaceViewRenderer)
    surfaceViewRenderer.release()

```

## Participant Talking Observable

```java theme={null}
    room.getParticipantTalking()
    MutableLiveData>
```

We will receive the participant that has updated its talking status, and the stream key. This information is also modified in the [Participants list](#participants-observable)

```java theme={null}
data class Participant(
...
    var isTalking: String?, // Can either be "talking" or "stopped-talking"
...
) : Serializable
```

## Stats

We provide the method *getWebRTCStatsForStream()* to retrieve WebRTC stats
This request brings stats one time only, so if you want to keep receiving stats, you will need to use some sort of runnable or coroutine to recursively request for them such as:

```java theme={null}
mStatsjob = CoroutineScope(Dispatchers.Default).launch &#123;
        while (isActive) &#123;
            room.getWebRTCStatsForStream(participantId, streamKey, callback)
            delay(2000)
    &#125;
```

We have to provide *participantId*, *streamKey* that is the key that identifies the stream we need the stats from, and *callback* that is an **RTCStatsCollectorCallback** we provide to the WebRTC peer connection in order to retrieve the stats.

In Kotlin, method call will look like this

```java theme={null}

room.getWebRTCStatsForStream(participantId, streamKey) &#123; stats ->
    ...
    ...
&#125;
```

We can later parse the information retrieved to obtain the specific information we go after.
In our sample app, you will see the models we use for audio and video, both local and remote

## Remote Video stats

WEBRTC's *RTCStatsReport* can be parsed and mapped to RemoteVideoStreamStats

```java theme={null}
data class RemoteVideoStreamStats(
    val bytesReceived: Int,
    val frameHeight: Int,
    val frameWidth: Int,
    val framesDecoded: Int,
    val framesDropped: Int,
    val framesPerSecond: Double,
    val framesReceived: Int,
    val packetsLost: Int,
    val packetsReceived: Int,
    val totalInterFrameDelay: Double
) : StreamStats()
```

```java theme={null}
stats.statsMap.values.filter &#123; it.type == "inbound-rtp" &#125;
    .findLast &#123; it.toString().contains("mediaType: \"video\"") &#125;
    ?.let &#123; rtcVideoStats ->
        val videoStreamStats =
            gson.fromJson(
                rtcVideoStats.toString(),
                RemoteVideoStreamStats::class.java
            )
        videoStreamStats?.let &#123;
            Timber.tag("RoomFragment")
                .d("ParticipantID: $participantId video STATS: $it")
            // Proceed to use stats
        &#125;
    &#125;
```

## Local Video stats

WEBRTC's *RTCStatsReport* can be parsed and mapped to LocalVideoStreamStats

```java theme={null}
data class LocalVideoStreamStats(
    val bytesSent: Int,
    val codecId: String,
    val frameHeight: Int,
    val frameWidth: Int,
    val framesEncoded: Int,
    val framesPerSecond: Double,
    val framesSent: Int,
    val headerBytesSent: Int,
    val nackCount: Int,
    val packetsSent: Int,
) : StreamStats()
```

```java theme={null}
stats.statsMap.values.filter &#123; it.type == "outbound-rtp" &#125;
    .findLast &#123; it.toString().contains("mediaType: \"video\"") &#125;
    ?.let &#123; rtcVideoStats ->
        val videoStreamStats =
            gson.fromJson(
                rtcVideoStats.toString(),
                LocalVideoStreamStats::class.java
            )
        videoStreamStats?.let &#123;
            Timber.tag("RoomFragment")
                .d("SelfParticipant video STATS: $it")
            // Proceed to use stats
        &#125;
    &#125;
```

## Remote audio stats

WEBRTC's *RTCStatsReport* can be parsed and mapped to RemoteAudioStreamStats

```java theme={null}
data class RemoteAudioStreamStats(
    val audioLevel: Double,
    val bytesReceived: Int,
    val codecId: String,
    val headerBytesReceived: Int,
    val jitter: Double,
    val packetsLost: Int,
    val packetsReceived: Int,
    val totalAudioEnergy: Double,
    val totalSamplesDuration: Double,
    val totalSamplesReceived: Int,
) : StreamStats()
```

```java theme={null}
stats.statsMap.values.filter &#123; it.type == "inbound-rtp" &#125;
    .findLast &#123; it.toString().contains("mediaType: \"audio\"") &#125;
    ?.let &#123; rtcStats ->
        val audioStreamStats =
            gson.fromJson(
                rtcStats.toString(),
                RemoteAudioStreamStats::class.java
            )
        audioStreamStats?.let &#123;
            // Proceed to use stats
        &#125;
    &#125;
```

## Local audio stats

WEBRTC's *RTCStatsReport* can be parsed and mapped to LocalAudioStreamStats

```java theme={null}
data class LocalAudioStreamStats(
    val bytesSent: Int,
    val codecId: String,
    val headerBytesSent: Int,
    val packetsSent: Int,
    val retransmittedBytesSent: Int,
    val retransmittedPacketsSent: Int,
) : StreamStats()
```

```java theme={null}
stats.statsMap.values.filter &#123; it.type == "outbound-rtp" &#125;
    .findLast &#123; it.toString().contains("mediaType: \"audio\"") &#125;
    ?.let &#123; rtcStats ->
        val audioStreamStats =
            gson.fromJson(
                rtcStats.toString(),
                LocalAudioStreamStats::class.java
            )
        audioStreamStats?.let &#123;
            // Proceed to use stats
        &#125;
    &#125;
```

***

### iOS SDK

> Source: [https://developers.telnyx.com/docs/video/ios-client-sdk.md](https://developers.telnyx.com/docs/video/ios-client-sdk.md)

The Telnyx Video iOS SDK provides the functionality you need to join and interact with a video room from an iOS application.

#### A link to the repo can be found here:

iOS SDK

## If you prefer to jump right in

Have a look at our demo app: Telnyx Meet.

## An overview of the Video API

These are important concepts to understand.

* 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`.

* `Room State` tracks the state of the room as it changes making it extremely easy to understand what's happened to a `Room`.

  * `Room State` could change due to a `Local Participant` has started publishing a stream or because a `Remote Participant`left.

* 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`

## API of a Room

This should give you high level overview of the Room API and it's functionality.

```
    func connect(statusChanged: @escaping (_ status: RoomStatus) -> Void)

    func disconnect(completion: @escaping () -> Void)

    func updateClientToken(clientToken: String, completion: () -> Void)

    func addStream(key: StreamKey, audio: RTCAudioTrack?, video: RTCVideoTrack?, completion:
    @escaping OnSuccess, onFailed: @escaping OnFailed)

    func updateStream(key: StreamKey, audio: RTCAudioTrack?, video: RTCVideoTrack?, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func removeStream(key: StreamKey, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func addSubscription(participantId: ParticipantId, key: StreamKey, audio: Bool, video: Bool, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func pauseSubscription(participantId: ParticipantId, key: StreamKey, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func resumeSubscription(participantId: ParticipantId, key: StreamKey, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func updateSubscription(participantId: ParticipantId, key: StreamKey, audio: Bool, video: Bool, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func removeSubscription(participantId: ParticipantId, key: StreamKey, completion: @escaping OnSuccess, onFailed: @escaping OnFailed)

    func getWebRTCStatsForStream(participantId: ParticipantId, streamKey: StreamKey, completion: @escaping (_ stats: [String: [String: Any]]) -> Void)

    /// Helpers methods
    func getState() -> State
    func getLocalParticipant() throws -> Participant
    func getLocalStreams() throws -> [StreamKey: Stream]
    func getParticipantStream(participantId: ParticipantId, key: StreamKey) -> Stream?
    func getParticipantStreams(participantId: ParticipantId) throws -> [StreamKey: Stream]
```

After pasting the above content, Kindly check and remove any new line added

## Events that are triggered in a Room

Here's a list of events that will fire as you make API calls.

```
  /// Triggered each time the state is updated.
  var onStateChanged: ((_ state: State) -> Void)?

  /// Triggered when connected to a room.
  var onConnected: (() -> Void)?

  /// Triggered when disconnects from room / leaves room.
  var onDisconnected: (() -> Void)?

  /// Triggered when a remote participant joins the room.
  var onParticipantJoined: ((_ participantId: ParticipantId, _ participant: Participant) -> Void)?

  /// Triggered when a remote participant leaves the room.
  var onParticipantLeft: ((_ participantId: ParticipantId) -> Void)?

  /// Triggered after successfully registering a stream.
  var onStreamPublished: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// Triggered after successfully unregistering a stream.
  var onStreamUnpublished: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// Triggered when a local stream or a remote stream track has been enabled.
  /// Notifies consumers about remote stream tracks being enabled. For example: when audio is unmuted or video has started on a remote stream.
  var onTrackEnabled: ((_ participantId: ParticipantId, _ streamKey: StreamKey, _ kind: String) -> Void)?

  /// The oposite of` onTrackEnabled`. Triggers when a local stream or a remote stream track has been disabled.
  /// Notifies consumers about remote stream tracks being disabled. For example: when audio is muted or video has stopped on a remote stream.
  var onTrackDisabled: ((_ participantId: ParticipantId, _ streamKey: StreamKey, _ kind: String) -> Void)?

  /// Triggered when subscribed to a remote participant's stream.
  var onSubscriptionStarted: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// Triggered when an ongoing subscription is paused.
  var onSubscriptionPaused: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// Triggered when a paused subsription is resumed.
  var onSubscriptionResumed: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// Triggered when the subscription is reconfigured.
  var onSubscriptionReconfigured: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// Triggered when subscription is ended for a remote participant's stream.
  /// The subscription can be ended by calling `removeSubscription(ParticipantId,StreamKey)` or when the remote participant leaves.
  var onSubscriptionEnded: ((_ participantId: ParticipantId, _ streamKey: StreamKey) -> Void)?

  /// onError
  /// Triggered when there's an error processing incoming events from the server.
  var onError: ((_ error: SdkError) -> Void)?
```

After pasting the above content, Kindly check and remove any new line added

## Understanding the state of the Room

Everything in the SDK centers around the `Room` object. When the state of the `Room` changes (e.g. a new participant joins or a remote participant starts publishing a stream) the `onStateChanged` event is triggered.

The event is invoked with a `state` parameter which contains the current of the state of the `Room`.

```
 /// Triggered each time the state is updated.
 var onStateChanged: ((_ state: State) -> Void)?
```

After pasting the above content, Kindly check and remove any new line added

## Before getting started

## Install the SDK

Currently, the Telnyx iOS Video SDK can be installed using CocoaPods. For instructions on that check out our releases repo for iOS

## Get an API Key

You'll need an API key which is associated with your Mission Control Portal account under **API Keys**. You can learn how to do that [here](/docs/development/api-fundamentals/create-api-keys).

An API key is your credential to access our API. It allows you to:

* to authenicate to the Rest API
* to manage your access tokens

## Create a Room to join (if it doesn't exist)

In order to join a room you must create it, if it doesn't already exist. See our [Room Rest API](/api-reference/rooms-client-tokens/create-client-token-to-join-a-room#create-client-token-to-join-a-room) to create one.

There's also additional resources on other endpoints available to perform basic operations on a `Room`.

## Generate an a client token to join a room

In order to join a room you must have a client token for that `Room`. The `client token` is short lived and you will be able to refresh it using the `refresh token` provided with it when you  request a `client token`.

Please see the [docs here](/api-reference/rooms-client-tokens/create-client-token-to-join-a-room#create-client-token-to-join-a-room) to learn how to create a `client token`.

## Code Examples

Enough already let's get to the code. Here are some code examples to get your wet feet on how to start building something with the iOS video SDK.

## Participating in a Room

## Connect to a room

First, you'll need to create a `Room` instance and then connect to it. Once you're connected to a room, you can start sharing audio/video streams with other participant in the rooms.

**Important Note:**
This simply creates an instance of a Room in code it does not use the Rooms Rest API to create a room, mentioned above in "Create a Room to join"\*

```
// Create an instance of a Room

Room.createRoom(
            id: "92f83cf907b6426197ca6ccc83f3cba3",
            clientToken: accessToken,
            context: ["userid": 12345, "username": "jane doe"])
&#123; room in
  // Once a room is created we can connect to it
  room.connect &#123; status in

  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

Once the room is connected you've joined the room as it's local participant. You can see this more clearly by, after connecting, get the local participant.

**Important Note:**
`Room` only has one `Local Participant` but can have multiple `Remote Participant`s.

```
Room.createRoom(
            id: "92f83cf907b6426197ca6ccc83f3cba3",
            clientToken: accessToken,
            context: ["username": "jane doe"])
&#123; room in
    room.connect &#123; status in
        let localParticipant = room.getLocalParticipant()
    &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## What is Room context?

Context is any details you want to include about the `LocalParticipant` of the `Room`.

For instance, let's say you want to use `context` to identify a `Participant` with fields from an external system. You could pass a `userId` and `username` as context, like the code snippet above.

These details will be available to all `RemoteParticipant`s in the Room, when they are notified about your presence in the `Room`.

## Working with local media

Publishing audio and/or video from your camera or microphone works by using `MediaDevices`. `MediaDevices` is a helper class that we provide for you to make it easy to grab local media from your device.

```
let stream = MediaDevices.shared().getUserMedia(audio:true, video:true)

// If you want to run your app in a simulator provide a video file name to MediaDevices and it wil be used as the source for the cameraTrack. The video needs to be added to your Main.bundle, for things to work properly.

let cameraTrack = stream.videoTracks.first
let microphoneTrack = stream.audioTracks.first
```

After pasting the above content, Kindly check and remove any new line added

## Setting the quality of the local video

You can set the camera resolution and fps using `MediaDevices`.

```
#if !targetEnvironment(simulator)
guard let camera = RTCCameraVideoCapturer.captureDevices().first(where: &#123;
    $0.position == MediaDevices.shared().cameraPosition &#125;) else &#123;
        return
    &#125;
// Choose a suitable resolution/capture format
guard let captureFormat = RTCCameraVideoCapturer.supportedFormats(for: camera).sorted &#123; (f1, f2) -> Bool in
    let width1 = CMVideoFormatDescriptionGetDimensions(f1.formatDescription).width
    let width2 = CMVideoFormatDescriptionGetDimensions(f2.formatDescription).width
    return width1 &lt; width2
&#125;.first else &#123;
    return
&#125;
// Choose a suitable fps
let fps = captureFormat.videoSupportedFrameRateRanges.sorted &#123; return $0.maxFrameRate &lt; $1.maxFrameRate &#125;.first!
// Set the resolution and fps to `Mediadevices`.
MediaDevices.shared().set(format: captureFormat, fps: Int(fps.maxFrameRate))
#endif
```

After pasting the above content, Kindly check and remove any new line added

**Note: If you choose highest resolution and fps, the local video stream will lag if you have poor internet / bandwith**

## Publishing a stream

Once you have tracks from say, a local media device like video from a camera and/our audio from your micrphone you can use those to create a `Stream` and publish it in the `Room`.

```
room.connect &#123; status in
  let cameraTrack: RTCVideoTrack
  let microphoneTrack: RTCAudioTrack

  // The onStreamPublished will trigger once the stream has started publishing in the room
  room.onStreamPublished = &#123;
          participantId, streamKey in

  &#125;

  room.addStream(
      key: "camera/mic",
      audio: microphoneTrack,
      video: cameraTrack)&#123;

  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## Unpublishing a stream

If you can longer want to continue publishing a stream you can `unpublish` it.
Naturally the stream you want to unpublish must be added already.

```
room.connect &#123; status in
  // onStreamUnpublished will trigger once the stream has been unpublished
  room.onStreamUnpublished = &#123;
      participantId, streamKey in

  &#125;

  room.removeStream(key: "camera/mic") &#123;

  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## Working with Remote Participants and Streams

## A remote participant who joins or leaves the room

When a remote participant joins a a room you will be notified with the `Room.onParticipantJoined` event. And similiarly with `Room.onParticipantLeaving` when a remote participant leaves.

You can use these events to keep track of participants in the room.

```
room.connect &#123;
  room.onParticipantJoined = &#123;
    participantId in
    // This event will trigger when a remote participant joins the room
  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## Remote participants already in the room

When you connect to a `Room` there may already be remote participants in the `Room`. To understand who is in the `Room` after you connect use the `onParticipantJoined` event.

```
room.connect &#123;
  room.onParticipantJoined = &#123;
    participantId in
    // The event triggers for remote participants who are already in the room, just like it does for a new remote participant that joins the room.
  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## Display a remote participant's media

In order to understand how to display a remote participants' media let's review on subscriptions work in the API.

It might be helpful to review the Video API overview to get a better understanding of how a `Room` is modeled, especially `Stream` and `Subscription`.

**Major 🔑 about Subscriptions**
In order to display media from a remote stream you need to subscribe to it.

It's important to understand that your `Room` doesn't automatically subscribe to a remote stream being published. It's your **choice to decide whether to subscribe to a given stream.**

## Subscribing to a stream

So let's say the app your building has two users - let's them call them Alice and Bob.

First, Alice joins the `Room` and starts publishing a stream with audio from her microhone and video from her camera like this:
*NOTE: The app on Alice's device runs the following code...*

```
room.connect&#123;
  status in
  // Let's assume that we have the tracks for Alice's camera and microphone already

  // Alice starts publishing a stream in the room
  room.addStream(
      key: "self",
      audio: microphoneTrack,
      video: cameraTrack)&#123;
  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

Bob wants to get Alice's stream so he can display it. In order to do so he needs to subscribe to Alice's stream.
*NOTE: The app on Bob's device runs the following code...*

```
room.connect &#123; status in
  // onStreamPublished event is triggered notifying him that Alice's stream is being published
  room.onStreamPublished = &#123;
          participantId, streamKey in

          // Bob subscribes to Alice's stream
          room.addSubscription(
            participantId: participantId,
            key: streamKey,
            audio: true,
            video: true
          )

  &#125;

  // onSubscriptionStarted triggers when the subscription to Alice's stream has started
  room.onSubscritionStarted = &#123;
    participantId, streamKey in
    // Bob needs to fetch the stream so he can display it
    let aliceStream = room.getParticipantStream(participantId: participantId, key: streamKey)

    // Alice's stream has a key of 'self' which has the audio track from her microphone and a video from her device's camera. Bob can use these tracks and display them as Alice in his app.
    let aliceCameraTrack = aliceStream.videoTrack
    let aliceMicrophoneTrack = aliceStream.audioTrack
  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## Handling remote streams that are already publishing in the Room

After you connect to a Room there may be remote participants already in the `Room` who are publishing streams in the Room.

To deal with that use the `onStreamPublished` event:

```
room.connect &#123; status in
  // After connecting to a room the onStreamPublished event will trigger for remote stream
  // that are already being published in the room
  // The onStreamPublished event will trigger
  room.onStreamPublished = &#123;
          participantId, streamKey in

  &#125;
&#125;
```

After pasting the above content, Kindly check and remove any new line added

## Disconnecting from a Room

To disconect from a room do:

```
room.disconnect &#123;
  // after the room disconnect that it's status is .disconnected
&#125;
```

After pasting the above content, Kindly check and remove any new line added

When you disconnect from a `Room` all `Remote Participant`'s will be notified that you've left the `Room` because the `Room.onParticipantLeft` event will fire on their `Room` instance.

***

## API Reference (Video)

### Room Compositions

* [View a list of room compositions.](https://developers.telnyx.com/api-reference/room-compositions/view-a-list-of-room-compositions.md): Returns a paginated list of room compositions. Filter compositions by creation date, room session, or processing status.
* [Create a room composition.](https://developers.telnyx.com/api-reference/room-compositions/create-a-room-composition.md): Asynchronously create a room composition.
* [View a room composition.](https://developers.telnyx.com/api-reference/room-compositions/view-a-room-composition.md): Returns the composition identified by `room_composition_id`, including its room and session, processing status, media details, video layout, lifecycle timestam…
* [Delete a room composition.](https://developers.telnyx.com/api-reference/room-compositions/delete-a-room-composition.md): Synchronously delete a room composition.

### Room Participants

* [View a list of room participants.](https://developers.telnyx.com/api-reference/room-participants/view-a-list-of-room-participants.md): Returns a paginated list of room participants across sessions. Filter participants by session, join, update, or leave date and by participant context.
* [View a room participant.](https://developers.telnyx.com/api-reference/room-participants/view-a-room-participant.md): Returns the participant identified by `room_participant_id`, including its session, context, and join, update, and leave timestamps.

### Room Recordings

* [View a list of room recordings.](https://developers.telnyx.com/api-reference/room-recordings/view-a-list-of-room-recordings.md): Returns a paginated list of room recordings. Filter recordings by room, session, participant, recording type, status, duration, or start and end dates.
* [Delete several room recordings in a bulk.](https://developers.telnyx.com/api-reference/room-recordings/delete-several-room-recordings-in-a-bulk.md): Deletes the room recordings that match the supplied filters and returns the number of recordings affected. Filters support room, session, participant, recordin…
* [View a room recording.](https://developers.telnyx.com/api-reference/room-recordings/view-a-room-recording.md): Returns the recording identified by `room_recording_id`, including its room, session, participant, status, media details, lifecycle timestamps, and download UR…
* [Delete a room recording.](https://developers.telnyx.com/api-reference/room-recordings/delete-a-room-recording.md): Synchronously delete a Room Recording.

### Room Sessions

* [View a list of room sessions.](https://developers.telnyx.com/api-reference/room-sessions/view-a-list-of-room-sessions.md): Returns a paginated list of room sessions across the account. Filter sessions by room, creation, update, or end date and active status, and use \`include\_partic…
* [View a room session.](https://developers.telnyx.com/api-reference/room-sessions/view-a-room-session.md): Returns the room session identified by `room_session_id`, including its room, active status, and lifecycle timestamps. Use `include_participants` to include it…
* [End a room session.](https://developers.telnyx.com/api-reference/room-sessions/end-a-room-session.md): Note: this will also kick all participants currently present in the room
* [Kick participants from a room session.](https://developers.telnyx.com/api-reference/room-sessions/kick-participants-from-a-room-session.md): Removes the selected participants from the specified room session. Apply the action to a list of participant IDs or to `all`, with optional participant IDs exc…
* [Mute participants in room session.](https://developers.telnyx.com/api-reference/room-sessions/mute-participants-in-room-session.md): Mutes the selected participants in the specified room session. Apply the action to a list of participant IDs or to `all`, with optional participant IDs exclude…
* [Unmute participants in room session.](https://developers.telnyx.com/api-reference/room-sessions/unmute-participants-in-room-session.md): Unmutes the selected participants in the specified room session. Apply the action to a list of participant IDs or to `all`, with optional participant IDs exclu…
* [View a list of room participants.](https://developers.telnyx.com/api-reference/room-sessions/view-a-list-of-room-participants.md): Returns a paginated list of participants for the specified room session. Filter participants by join, update, or leave date and by participant context.

### Rooms

* [View a list of rooms.](https://developers.telnyx.com/api-reference/rooms/view-a-list-of-rooms.md): Returns a paginated list of rooms. Filter the results by creation or update date and unique name, and use `include_sessions` to include each room’s sessions.
* [Create a room.](https://developers.telnyx.com/api-reference/rooms/create-a-room.md): Synchronously create a Room.
* [View a room.](https://developers.telnyx.com/api-reference/rooms/view-a-room.md): Returns the room identified by `room_id`, including its participant limit, recording and webhook configuration, and active session identifier. Use \`include\_ses…
* [Update a room.](https://developers.telnyx.com/api-reference/rooms/update-a-room.md): Synchronously update a Room.
* [Delete a room.](https://developers.telnyx.com/api-reference/rooms/delete-a-room.md): Synchronously delete a Room. Participants from that room will be kicked out, they won't be able to join that room anymore, and you won't be charged anymore for…
* [View a list of room sessions.](https://developers.telnyx.com/api-reference/rooms/view-a-list-of-room-sessions.md): Returns a paginated list of sessions for the specified room. Filter sessions by creation, update, or end date and active status, and use `include_participants`…

### Rooms Client Tokens

* [Create Client Token to join a room.](https://developers.telnyx.com/api-reference/rooms-client-tokens/create-client-token-to-join-a-room.md): Synchronously create an Client Token to join a Room. Client Token is necessary to join a Telnyx Room. Client Token will expire after `token_ttl_secs`, a Refres…
* [Refresh Client Token to join a room.](https://developers.telnyx.com/api-reference/rooms-client-tokens/refresh-client-token-to-join-a-room.md): Synchronously refresh an Client Token to join a Room. Client Token is necessary to join a Telnyx Room. Client Token will expire after `token_ttl_secs`.
