Getting Started

This guide provides you with an overview of the key objects you'll use in the Programmable Video API to build your video application.

Note: If you haven’t already done so, then take a look at the Twilio Video Quickstart Applications. Once you've played with the Quickstart, come back to this guide for more detail on how to add video to your own app.

If you’ve worked with WebRTC in the past, you’ll find that Programmable Video provides a simple wrapper around WebRTC’s lower-level APIs to make it easy to build rich audio and video applications. You can still access lower-level primitives, but that’s not required to get started.

Additionally, Programmable Video provides the missing pieces required to use WebRTC to build sophisticated applications: Global STUN/TURN relays, media services for large-scale group conferences and recording, and signaling infrastructure are all included.

Video API overview

Let’s start with an overview of the Programmable Video API:

  • A Room represents a real-time audio, video, and/or screen-share session, and is the basic building block for a Programmable Video application.
  • Participants represent client applications that are connected to a Room and sharing audio and/or video media with one another.
  • Tracks represent the individual audio and video media streams that are shared within a Room.
  • Following from the above, LocalTracks represent the audio and video captured from the local microphone and camera.

The following code samples illustrate common tasks that you as a developer may wish to perform related to a Room and its Participants.

Generate an Access Token

To execute the code samples below, you can use the Testing Tools page in the Twilio Console to generate an Access Token. An Access Token is a short-lived credential used to authenticate your client-side application to Twilio.

In a production application, your back-end server will need to generate an Access Token for every user in your application. Visit the User Identity and Access Token guide to learn more.

Loading Code Samples...
Language
public void connectToRoom(String roomName) {
  ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken)
    .roomName(roomName)
    .audioTracks(localAudioTracks)
    .videoTracks(localVideoTracks)
    .build();
  room = Video.connect(context, connectOptions, this);
}

private Room.Listener roomListener() {
  return new Room.Listener() {
      @Override
      public void onConnected(Room room) {
        Log.d(TAG,"Connected to " + room.getName());
      }
  }
}
Twilio.Video.connect('$TOKEN', {name:'my-new-room'}).then(function(room) {
  console.log('Successfully joined a Room: ', room);
  room.on('participantConnected', function(participant) {
    console.log('A remote Participant connected: ', participant);
  })
}, function(error) {
    console.error('Unable to connect to Room: ' +  error.message);
});
- (void)createRoom {
    // Create a room 
    TVIConnectOptions *connectOptions = [TVIConnectOptions optionsWithToken:self.accessToken
                                                                      block:^(TVIConnectOptionsBuilder * _Nonnull builder) {
        builder.roomName = @"my-new-room";
    }];
    TVIRoom *room = [TwilioVideo connectWithOptions:connectOptions delegate:self];
}

#pragma mark - TVIRoomDelegate

- (void)didConnectedToRoom:(nonnull TVIRoom *)room {
    NSLog(@"Did connect to Room");
}
@IBAction func createARoom(sender: AnyObject) {
    let connectOptions = TVIConnectOptions.init(token: accessToken) { (builder) in
        builder.roomName = "my-room"
    }
    room = TwilioVideo.connect(with: connectOptions, delegate: self)
}

// MARK: TVIRoomDelegate

func didConnectToRoom(room: TVIRoom) {
    print("Did connect to Room")
}
Create a Room

Connect to a Room

A Room represents a real-time audio, video, and/or screen-share session, and is the basic building block for a Programmable Video application..

Call connect() (connectWithOptions in Objective-C) to connect to a Room from your mobile or web application. Once connected, you can send and receive audio and video streams with other Participants who are connected to the Room.

You must pass the Access Token when connecting to a Room. You may also optionally pass the following:

  • Audio and video options, which when enabled will create and publish audio and video tracks from your local camera and microphone to the Room immediately upon connecting.
  • Local audio or video tracks, to begin sharing pre-created local media with other Participants in the Room upon connecting.
  • A room name, which allows you to dynamically specify the name of the Room you wish to join. (Note: You can also encode the Room name in the Access Token, which will allow the user to connect to only the Room specified in the token.)
  • An ICE transport policy, which allows you to force calls through TURN relay for testing purposes.
  • A log level for debugging.

The name of the Room specifies which Room you wish to join. If a Room by that name does not already exist, it will be created upon connection. If a Room by that name is already active, you'll be connected to the Room and receive notifications from any other Participants also connected to the same Room. Room names must be unique within an account.

You can also create a Room using the Rooms REST API. Look at the REST API Rooms resource docs for more details.

Example: Create a Room called DailyStandup

 curl -XPOST 'https://video.twilio.com/v1/Rooms' \
 -u '{API Key SID}:{API Secret}' \
 -d 'UniqueName=DailyStandup'

Note: If you don’t specify a Type attribute, then Twilio will create a Group room by default.

Default Room Settings

You can also set the room type from the Room Settings page in the Twilio Video Console. Twilio will use the room type set on Room Settings page, when you create a room from the client-side or the REST API.

Note: Twilio will set the Room Type as Group by default on the Room Settings page.

Once a Room is created, Twilio will fire a room-created webhook event, if the StatusCallback URL is set. You can set the StatusCallback URL on the Room Settings page, if you want create a room from the client-side.

If you create a room using the REST API, then you need to provide a StatusCallback URL while creating the room.

 curl -XPOST 'https://video.twilio.com/v1/Rooms' \
 -u '{API Key SID}:{API Secret}' \
 -d 'UniqueName=DailyStandup' \
 -d 'StatusCallback=https://hooks.yoursite.com/room-events' \
 -d 'StatusCallbackMethod=POST' \
 -d 'Type=group'
Loading Code Samples...
Language
public void connectToRoom(String roomName) {
  ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken)
    .roomName(roomName)
    .audioTracks(localAudioTracks)
    .videoTracks(localVideoTracks)
    .build();
  room = Video.connect(context, connectOptions, this);
}

private Room.Listener roomListener() {
  return new Room.Listener() {
    @Override
    public void onConnected(Room room) {
      Log.d(TAG,"Connected to " + room.getName());
    }
  }
}
Twilio.Video.connect('$TOKEN', {name:'existing-room'}).then(function(room) {
  console.log('Successfully joined a Room: ', room);
  room.on('participantConnected', function(participant) {
    console.log('A remote Participant connected: ', participant);
  })
}, function(error) {
    console.error('Unable to connect to Room: ' +  error.message);
});
-(void)joinRoom {
	// Join an existing room 
	TVIConnectOptions *connectOptions = [TVIConnectOptions optionsWithBlock:^(TVIConnectOptionsBuilder * _Nonnull builder) {
		builder.roomName = @"existing-room";
	}];
	TVIRoom *room = [videoClient connectWithOptions:connectOptions delegate:self];
}

#pragma mark - TVIRoomDelegate

- (void)room:(TVIRoom *)room participantDidConnect:(TVIParticipant *)participant {
	NSLog(@"Participant did connect:%@", participant.identity);
}
@IBAction func joinRoom(sender: AnyObject) {
    let connectOptions = TVIConnectOptions.init(block: { (builder) in
        builder.roomName = "existing-room"
    })
    room = TwilioVideo.connect(with: connectOptions, delegate: self)

}

// MARK: TVIRoomDelegate

func didConnectToRoom(room: TVIRoom) {
    print("Did connect to room")
}
Join a Room

Join a Room

If you'd like to join a Room you know already exists, you handle that exactly the same way as creating a room: just pass the Room name to the connect method.

Once in a Room, you'll receive a participantConnected event for each Participant that successfully joins. Querying the participants getter will return any existing Participants who have already joined the Room.

Loading Code Samples...
Language
// Create an audio track
boolean enable = true;
LocalAudioTrack localAudioTrack = LocalAudioTrack.create(context, enable);

// A video track requires an implementation of VideoCapturer
CameraCapturer cameraCapturer = new CameraCapturer(context,
        CameraCapturer.CameraSource.FRONT_CAMERA);

// Create a video track
LocalVideoTrack localVideoTrack = LocalVideoTrack.create(context, enable, cameraCapturer);

// Rendering a local video track requires an implementation of VideoRenderer
// Let's assume we have added a VideoView in our view hierarchy
VideoView videoView = (VideoView) findViewById(R.id.video_view);

// Render a local video track to preview your camera
localVideoTrack.addRenderer(videoView);

// Release the audio track to free native memory resources
localAudioTrack.release();

// Release the video track to free native memory resources
localVideoTrack.release();
const {
  createLocalTracks,
  createLocalAudioTrack,
  createLocalVideoTrack,
} = require('twilio-video');

let localTracks;

// Create default local audio and video tracks
createLocalTracks().then(function(localTracks) {
  console.log('Got default audio and video tracks:', localTracks);
});

// Create default local track of a particular kind
createLocalAudioTrack().then(function(audioTrack) {
  console.log('Got default local audio track:', audioTrack);
});

createLocalVideoTrack().then(function(videoTrack) {
  console.log('Got default local video track:', videoTrack);
});
// Create an audio track
TVILocalAudioTrack *localAudioTrack = [TVILocalAudioTrack track];

// Create a capturer to provide content for the track
TVICameraCapturer *camera = = [[TVICameraCapturer alloc] init];

// Create a video track
TVILocalVideoTrack *localVideoTrack = [TVILocalVideoTrack trackWithCapturer:camera];
// Create an audio track
var localAudioTrack = TVILocalAudioTrack.init()

// Create a Capturer to provide content for the Track
var camera = TVICameraCapturer()

// Create a video track
var localVideoTrack = TVILocalVideoTrack.init(capturer: camera!)
Setup local media

Setup local media when Creating a Room

You can capture local media from your device's microphone, camera or screen-share on different platforms in the following ways:

  • In a JavaScript application, call Twilio's createLocalTracks API to gain access to the user's microphone and camera. Note that some browsers, such as Google Chrome, will only let your application access local media when your site is served from localhost or over HTTPS.
  • In an iOS application, begin capturing audio data by creating a TVILocalAudioTrack, and begin capturing video by creating a TVILocalVideoTrack with an associated TVIVideoCapturer. The iOS Video SDK provides customizable video capturers for both camera and screen capture.
  • In an Android application, begin capturing audio data by creating a LocalAudioTrack, and begin capturing video by adding a LocalVideoTrack with an associated VideoCapturer. The Android Video SDK provides customizable video capturers for both camera and screen capture.
Loading Code Samples...
Language
if (participant.getVideoTracks().size() == 0) {
  CameraCapturer cameraCapturer = new CameraCapturer(this, CameraSource.FRONT_CAMERA);
  LocalVideoTrack localVideoTrack = LocalVideoTrack.create(context, true, cameraCapturer);
}
const { connect, createLocalTracks } = require('twilio-video');

// Option 1
createLocalTracks({
  audio: true,
  video: { width: 640 }
}).then(localTracks => {
  return connect('$TOKEN', {
    name: 'my-room-name',
    tracks: localTracks
  });
}).then(room => {
  console.log('Connected to Room:', room.name);
});

// Option 2
connect('$TOKEN', {
  audio: true,
  name: 'my-room-name',
  video: { width: 640 }
}).then(room => {
  console.log('Connected to Room:', room.name);
});
if (!self.localVideoTrack) {
	self.camera = [[TVICameraCapturer alloc] init];
    self.localVideoTrack = [TVILocalVideoTrack trackWithCapturer:self.camera];
}

if (!self.localAudioTrack) {
    self.localAudioTrack = [TVILocalVideoTrack track];
}

TVIConnectOptions *connectOptions = [TVIConnectOptions optionsWithToken:self.accessToken
                                                                  block:^(TVIConnectOptionsBuilder * _Nonnull builder) {
    builder.roomName = @"my-room";
    builder.audioTracks = @[ self.localAudioTrack ];
    builder.videoTracks = @[ self.localVideoTrack ];
}];

TVIRoom *room = [TwilioVideo connectWithOptions:connectOptions delegate:self];
if (self.localVideoTrack == nil) {
    self.camera = TVICameraCapturer()
    self.localVideoTrack = TVILocalVideoTrack.init(capturer: self.camera!)
}

if (self.localAudioTrack == nil) {
    self.localAudioTrack = TVILocalAudioTrack.init()
}

let connectOptions = TVIConnectOptions.init(token: accessToken) { (builder) in
    builder.roomName = "my-room"
    builder.audioTracks = [ self.localAudioTrack! ]
    builder.videoTracks = [ self.localVideoTrack! ]
}

var room = TwilioVideo.connect(with: connectOptions, delegate: self)
Specify Constraints

Specify Constraints

As the client joins a Room, the client can specify constraints such as the camera source, specific tracks to be added to the room, size of video etc. Take a look at the attached code examples.

Loading Code Samples...
Language
// Connect to room
Room room = Video.connect(context, connectOptions, new Room.Listener() {
    @Override
    public void onConnected(Room room) {}

    @Override
    public void onConnectFailure(Room room, TwilioException e) {}

    @Override
    public void onDisconnected(Room room, TwilioException e) {}

    @Override
    public void onRecordingStarted(Room room) {}

    @Override
    public void onRecordingStopped(Room room) {}

    @Override
    public void onParticipantConnected(Room room, Participant participant) {
        Log.i("Room.Listener", participant.getIdentity() + " has joined the room.");
    }

    @Override
    public void onParticipantDisconnected(Room room, Participant participant) {
        Log.i("Room.Listener", participant.getIdentity() + " has left the room.");
    }
);

// ... Assume we have received the connected callback

// After receiving the connected callback the LocalParticipant becomes available
LocalParticipant localParticipant = room.getLocalParticipant();
Log.i("LocalParticipant ", localParticipant.getIdentity());

// Get a participant from the room (let's assume we have a participant named Alice)
Participant participant = room.getParticipants().get("Alice");
Log.i("HandleParticipants", participant.getIdentity() + " is in the room.");
// Log your Client's LocalParticipant in the Room
const localParticipant = room.localParticipant;
console.log('Connected to the Room as LocalParticipant "%s"', localParticipant.identity);

// Log any Participants already connected to the Room
room.participants.forEach(participant => {
  console.log('Participant "%s" is connected to the Room', participant.identity);
});

// Log new Participants as they connect to the Room
room.once('participantConnected', participant => {
  console.log('Participant "%s" has connected to the Room', participant.identity);
});

// Log Participants as they disconnect from the Room
room.once('participantDisconnected', participant => {
  console.log('Participant "%s" has disconnected from Room', participant.identity);
});
TVIConnectOptions *connectOptions = [TVIConnectOptions optionsWithToken:self.accessToken
                                                                  block:^(TVIConnectOptionsBuilder * _Nonnull builder) {
    builder.roomName = @"my-room";
    builder.audioTracks = @[ self.localAudioTrack ];
    builder.videoTracks = @[ self.localVideoTrack ];
}];

TVIRoom *room = [TwilioVideo connectWithOptions:connectOptions delegate:self];

#pragma mark - TVIRoomDelegate

- (void)didConnectedToRoom:(nonnull TVIRoom *)room {
    // The Local Participant
    TVILocalParticipant *localParticipant = room.localParticipant;
    NSLog(@"Local Participant %@", localParticipant.identity);
    
    // Connected participants
    NSArray *participants = room.participants;
    NSLog(@"Number of connected Participants %ld", [participants count]);
}

- (void)room:(nonnull TVIRoom *)room participantDidConnect:(nonnull TVIParticipant *)participant {
    NSLog(@"Participant %@ has joined Room %@", participant.identity, room.name);
}

- (void)room:(nonnull TVIRoom *)room participantDidDisconnect:(nonnull TVIParticipant *)participant {
    NSLog(@"Participant %@ has left Room %@", participant.identity, room.name);
}
let connectOptions = TVIConnectOptions.init(token: accessToken) { (builder) in
    builder.roomName = "my-room"
    builder.audioTracks = [ self.localAudioTrack! ]
    builder.videoTracks = [ self.localVideoTrack! ]
}

room = TwilioVideo.connect(with: connectOptions, delegate: self)

// MARK: TVIRoomDelegate

func didConnect(to room: TVIRoom) {
    // The Local Participant
    let localParticipant = room.localParticipant;
    print("Local identity \(localParticipant.identity)")
    
    // Connected participants
    let participants = room.participants;
    print("Number of connected Participants \(participants.count)")
}

func room(_ room: TVIRoom, participantDidConnect participant: TVIParticipant) {
    print ("Participant \(participant.identity) has joined Room \(room.name)")
}

func room(_ room: TVIRoom, participantDidDisconnect participant: TVIParticipant) {
    print ("Participant \(participant.identity) has left Room \(room.name)")
}
Handle Participants

Working with Remote Participants

Handle Connected Participants

When you join a Room, Participants may already be present. You can check for existing Participants in the connected event callback by using the participants getter.

Handle Participant Connection Events

When Participants connect to or disconnect from a Room that you're connected to, you'll be notified via an event listener:

Similar to Room Events, Twilio will fire Participant events if the StatusCallback webhook URL is set when the Room is created. These events help your application keep track of the participants who join or leave a Room.

Loading Code Samples...
Language
private Room.Listener roomListener() {
  return new Room.Listener() {

    @Override
    public void onParticipantConnected(Room room, Participant participant) {
      Log.v(TAG, "Participant connected: " + participant.getIdentity());
    }

    @Override
    public void onParticipantDisconnected(Room room, Participant participant) {
      Log.v(TAG, "Participant disconnected: " + participant.getIdentity());
    }
  };
}
room.on('participantConnected', function(participant) {
  console.log('Participant connected: ' + participant.identity);
});

room.on('participantDisconnected', function(participant) {
  console.log('Participant disconnected: ' + participant.identity);
});
#pragma mark - TVIParticipantDelegate

- (void)participant:(TVIParticipant *)participant addedVideoTrack:(TVIVideoTrack *)videoTrack {
    NSLog(@"Participant %@ added a video track",participant.identity);
}

- (void)participant:(TVIParticipant *)participant removedVideoTrack:(TVIVideoTrack *)videoTrack {
    NSLog(@"Participant %@ removed a video track",participant.identity);
}
// MARK: TVIParticipantDelegate

func participant(_ participant: TVIParticipant, addedVideoTrack videoTrack: TVIVideoTrack) {
    NSLog("Participant \(participant.identity) added video track")
}

func participant(_ participant: TVIParticipant, removedVideoTrack videoTrack: TVIVideoTrack) {
    NSLog("Participant \(participant.identity) removed video track")
}
Handle Participant Events

Display a Remote Participant's Video

To see the Video Tracks being sent by remote Participants, we need to render them to the screen:

Loading Code Samples...
Language
// First, we set a Media Listener when a Participant first connects:
private Room.Listener roomListener() {
  return new Room.Listener() {
    @Override
    public void onParticipantConnected(Room room, Participant participant) {
      participant.setListener(participantListener());
    }
  };
}

/* In the Participant listener, we can respond when the Participant adds a Video
Track by rendering it on screen: */
private Participant.Listener participantListener() {
  return new Participant.Listener() {
      @Override
      public void onVideoTrackAdded(Participant participant, VideoTrack videoTrack) {
        primaryVideoView.setMirror(false);
        videoTrack.addRenderer(primaryVideoView);
      }
  };
}
// Attach the Participant's Media to a <div> element.
room.on('participantConnected', function(participant) {
  console.log("Participant '" +  participant.identity  + "' connected");
  
  participant.tracks.forEach(track => {
    document.getElementById('remote-media-div').appendChild(track.attach());
  });
});
#pragma mark - TVIRoomDelegate

// First, we set a Participant Delegate when a Participant first connects
- (void)room:(TVIRoom *)room participantDidConnect:(TVIParticipant *)participant {
    NSLog(@"Participant did connect:%@",participant.identity);
    participant.delegate = self;
}

#pragma mark - TVIParticipantDelegate

/*
 * In the Participant Delegate, we can respond when the Participant adds a Video
 * Track by rendering it on screen
 */
- (void)participant:(TVIParticipant *)participant addedVideoTrack:(TVIVideoTrack *)videoTrack {
    NSLog(@"Participant %@ added a video track",participant.identity);
    
    self.remoteView = [[TVIVideoView alloc] initWithFrame:self.view.bounds delegate:self];
    [videoTrack addRenderer:self.remoteView];

    [self.view addSubview:self.remoteView];
}

#pragma mark - TVIVideoViewDelegate

// Lastly, we can subscribe to important events on the Video View
- (void)videoView:(TVIVideoView *)view videoDimensionsDidChange:(CMVideoDimensions)dimensions {
    NSLog(@"Dimensions changed to: %d x %d", dimensions.width, dimensions.height);
    [self.view setNeedsLayout];
}
// MARK: TVIRoomDelegate

// First, we set a Participant Delegate when a Participant first connects:
func room(_ room: TVIRoom, participantDidConnect participant: TVIParticipant) {
	print("Participant connected: \(participant.identity!)")
	participant.delegate = self
}

// MARK: TVIParticipantDelegate

/* 
 * In the Participant Delegate, we can respond when the Participant adds a Video
 * Track by rendering it on screen.
 */
func participant(_ participant: TVIParticipant, addedVideoTrack videoTrack: TVIVideoTrack) {
	print("Participant \(participant.identity) added video track")
    
	self.remoteView = TVIVideoView.init(frame: self.view.bounds, delegate:self)
	videoTrack.addRenderer(self.remoteView)

    self.view.addSubview(self.remoteView!)
}


// MARK: TVIVideoViewDelegate

// Lastly, we can subscribe to important events on the VideoView
func videoView(_ view: TVIVideoView, videoDimensionsDidChange dimensions: CMVideoDimensions) {
    print("The dimensions of the video track changed to: \(dimensions.width)x\(dimensions.height)")
    self.view.setNeedsLayout()
}
Display Participants Video

Participating in a Room

Display a Camera Preview

Sometimes you need to make sure you're looking fantastic before entering a Room. We get it. Each SDK provides a means to render a local camera preview outside the context of an active Room:

Loading Code Samples...
Language
/* The CameraCapturer is a default video capturer provided by Twilio which can
   capture video from the front or rear-facing device camera */
private CameraCapturer cameraCapturer;

/* A VideoView receives frames from a local or remote video track and renders them
   to an associated view. */
private VideoView primaryVideoView;

// Initialize the camera capturer and start the camera preview
cameraCapturer = new CameraCapturer(this, CameraSource.FRONT_CAMERA);
LocalVideoTrack localVideoTrack = LocalVideoTrack.create(context, true, cameraCapturer);
primaryVideoView.setMirror(true);
localVideoTrack.addRenderer(primaryVideoView);

// Release the local video track to free native memory resources once you are done
localVideoTrack.release();
const { createLocalVideoTrack } = require('twilio-video');

createLocalVideoTrack().then(track => {
  var localMediaContainer = document.getElementById('local-media-ctr');
  localMediaContainer.appendChild(track.attach());
});
// Use TVICameraCapturer to produce video from the device's front camera.
self.camera = [[TVICameraCapturer alloc] init];

// Create a local video track, and render it in the preview view
self.localVideoTrack = [TVILocalVideoTrack trackWithCapturer:self.camera];

if (!self.localVideoTrack) {
    NSLog(@"Failed to add video track");
} else {
    // TVIVideoView is a TVIVideoRenderer and can be added to any TVIVideoTrack.
    self.previewView = [[TVIVideoView alloc] initWithFrame:rect];
    
    // Add renderer to video track for local preview
    [self.localVideoTrack addRenderer:previewView];
    
    self.previewView.mirror = (self.camera.source == TVICameraCaptureSourceFrontCamera);
    
    [self.view addSubview:previewView];
}
// Use TVICameraCapturer to produce video from the device's front camera.
var camera = TVICameraCapturer()

// Create a local video track, and render it in the preview view
var localVideoTrack = TVILocalVideoTrack.init(capturer: camera!)
if (localVideoTrack == nil) {
    logMessage(messageText: "Failed to add video track")
} else {
    // TVIVideoView is a TVIVideoRenderer and can be added to any TVIVideoTrack.
    let previewView = TVIVideoView.init(frame: rect)
    
    // Attach view to video track for local preview
    localVideoTrack!.addRenderer(previewView)
    
    previewView.shouldMirror = (camera!.source == .frontCamera)
    
    self.view.addSubview(previewView)
}
Display a Camera Preview

Disconnect from a Room

You can disconnect from a Room you're currently participating in. Other Participants will receive a participantDisconnected event.

Loading Code Samples...
Language
// To disconnect from a Room, we call:
room.disconnect();

// This results in a call to Room.Listener#onDisconnected
private Room.Listener roomListener() {
  return new Room.Listener() {
    @Override
    public void onDisconnected(Room room, TwilioException e) {
        Log.d(TAG,"Disconnected from " + room.getName());
    } 
  };
}
room.on('disconnected', room => {
  // Detach the local media elements
  room.localParticipant.tracks.forEach(track => {
    var attachedElements = track.detach();
    attachedElements.forEach(element => element.remove());
  });
});

// To disconnect from a Room
room.disconnect();
// To disconnect from a room, we call:
[self.room disconnect];

// This results in a callback to TVIRoomDelegate#room:didDisconnectWithError

#pragma mark - TVIRoomDelegate

- (void)room:(TVIRoom *)room didDisconnectWithError:(NSError *)error {
    NSLog(@"Did disconnect from room");
}
// To disconnect from a Room, we call:
room.disconnect()

// This results in a callback to TVIRoomDelegate#room:didDisconnectWithError

// MARK: TVIRoomDelegate

func room(_ room: TVIRoom, didDisconnectWithError error: Error?) {
    print("Disconnected from room \(room.name)")
}
Disconnect from a Room

Wrapping up

With that, we've given you a whirlwind tour of the Twilio's Programmable Video APIs. To dive deeper, dig into our API documentation here:

Stuck on something? Can't find what you're looking for? Don't hesitate to reach out to us at help@twilio.com and we'll happily give you a hand.

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
public void connectToRoom(String roomName) {
  ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken)
    .roomName(roomName)
    .audioTracks(localAudioTracks)
    .videoTracks(localVideoTracks)
    .build();
  room = Video.connect(context, connectOptions, this);
}

private Room.Listener roomListener() {
  return new Room.Listener() {
      @Override
      public void onConnected(Room room) {
        Log.d(TAG,"Connected to " + room.getName());
      }
  }
}
Twilio.Video.connect('$TOKEN', {name:'my-new-room'}).then(function(room) {
  console.log('Successfully joined a Room: ', room);
  room.on('participantConnected', function(participant) {
    console.log('A remote Participant connected: ', participant);
  })
}, function(error) {
    console.error('Unable to connect to Room: ' +  error.message);
});
- (void)createRoom {
    // Create a room 
    TVIConnectOptions *connectOptions = [TVIConnectOptions optionsWithToken:self.accessToken
                                                                      block:^(TVIConnectOptionsBuilder * _Nonnull builder) {
        builder.roomName = @"my-new-room";
    }];
    TVIRoom *room = [TwilioVideo connectWithOptions:connectOptions delegate:self];
}

#pragma mark - TVIRoomDelegate

- (void)didConnectedToRoom:(nonnull TVIRoom *)room {
    NSLog(@"Did connect to Room");
}
@IBAction func createARoom(sender: AnyObject) {
    let connectOptions = TVIConnectOptions.init(token: accessToken) { (builder) in
        builder.roomName = "my-room"
    }
    room = TwilioVideo.connect(with: connectOptions, delegate: self)
}

// MARK: TVIRoomDelegate

func didConnectToRoom(room: TVIRoom) {
    print("Did connect to Room")
}
public void connectToRoom(String roomName) {
  ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken)
    .roomName(roomName)
    .audioTracks(localAudioTracks)
    .videoTracks(localVideoTracks)
    .build();
  room = Video.connect(context, connectOptions, this);
}

private Room.Listener roomListener() {
  return new Room.Listener() {
    @Override
    public void onConnected(Room room) {
      Log.d(TAG,"Connected to " + room.getName());
    }
  }
}
Twilio.Video.connect('$TOKEN', {name:'existing-room'}).then(function(room) {
  console.log('Successfully joined a Room: ', room);
  room.on('participantConnected', function(participant) {
    console.log('A remote Participant connected: ', participant);
  })
}, function(error) {
    console.error('Unable to connect to Room: ' +  error.message);
});
-(void)joinRoom {
	// Join an existing room 
	TVIConnectOptions *connectOptions = [TVIConnectOptions optionsWithBlock:^(TVIConnectOptionsBuilder * _Nonnull builder) {
		builder.roomName = @"existing-room";
	}];
	TVIRoom *room = [videoClient connectWithOptions:connectOptions delegate:self];
}

#pragma mark - TVIRoomDelegate

- (void)room:(TVIRoom *)room participantDidConnect:(TVIParticipant *)participant {
	NSLog(@"Participant did connect:%@", participant.identity);
}
@IBAction func joinRoom(sender: AnyObject) {
    let connectOptions = TVIConnectOptions.init(block: { (builder) in
        builder.roomName = "existing-room"
    })
    room = TwilioVideo.connect(with: connectOptions, delegate: self)

}

// MARK: TVIRoomDelegate

func didConnectToRoom(room: TVIRoom) {
    print("Did connect to room")
}
// Create an audio track
boolean enable = true;
LocalAudioTrack localAudioTrack = LocalAudioTrack.create(context, enable);

// A video track requires an implementation of VideoCapturer
CameraCapturer cameraCapturer = new CameraCapturer(context,
        CameraCapturer.CameraSource.FRONT_CAMERA);

// Create a video track
LocalVideoTrack localVideoTrack = LocalVideoTrack.create(context, enable, cameraCapturer);

// Rendering a local video track requires an implementation of VideoRenderer
// Let's assume we have added a VideoView in our view hierarchy
VideoView videoView = (VideoView) findViewById(R.id.video_view);

// Render a local video track to preview your camera
localVideoTrack.addRenderer(videoView);

// Release the audio track to free native memory resources
localAudioTrack.release();

// Release the video track to free native memory resources
localVideoTrack.release();
const {
  createLocalTracks,
  createLocalAudioTrack,
  createLocalVideoTrack,
} = require('twilio-video');

let localTracks;

// Create default local audio and video tracks
createLocalTracks().then(function(localTracks) {
  console.log('Got default audio and video tracks:', localTracks);
});

// Create default local track of a particular kind
createLocalAudioTrack().then(function(audioTrack) {
  console.log('Got default local audio track:', audioTrack);
});

createLocalVideoTrack().then(function(videoTrack) {
  console.log('Got default local video track:', videoTrack);
});
// Create an audio track
TVILocalAudioTrack *localAudioTrack = [TVILocalAudioTrack track];

// Create a capturer to provide content for the track
TVICameraCapturer *camera = = [[TVICameraCapturer alloc] init];

// Create a video track
TVILocalVideoTrack *localVideoTrack = [TVILocalVideoTrack trackWithCapturer:camera];
// Create an audio track
var localAudioTrack = TVILocalAudioTrack.init()

// Create a Capturer to provide content for the Track
var camera = TVICameraCapturer()

// Create a video track
var localVideoTrack = TVILocalVideoTrack.init(capturer: camera!)
if (participant.getVideoTracks().size() == 0) {
  CameraCapturer cameraCapturer = new CameraCapturer(this, CameraSource.FRONT_CAMERA);
  LocalVideoTrack localVideoTrack = LocalVideoTrack.create(context, true, cameraCapturer);
}
const { connect, createLocalTracks } = require('twilio-video');

// Option 1
createLocalTracks({
  audio: true,
  video: { width: 640 }
}).then(localTracks => {
  return connect('$TOKEN', {
    name: 'my-room-name',
    tracks: localTracks
  });
}).then(room => {
  console.log('Connected to Room:', room.name);
});

// Option 2
connect('$TOKEN', {
  audio: true,
  name: 'my-room-name',
  video: { width: 640 }
}).then(room => {
  console.log('Connected to Room:', room.name);
});
if (!self.localVideoTrack) {
	self.camera = [[TVICameraCapturer alloc] init];
    self.localVideoTrack = [TVILocalVideoTrack trackWithCapturer:self.camera];
}

if (!self.localAudioTrack) {
    self.localAudioTrack = [TVILocalVideoTrack track];
}

TVIConnectOptions *connectOptions = [TVIConnectOptions optionsWithToken:self.accessToken
                                                                  block:^(TVIConnectOptionsBuilder * _Nonnull builder) {
    builder.roomName = @"my-room";
    builder.audioTracks = @[ self.localAudioTrack ];
    builder.videoTracks = @[ self.localVideoTrack ];
}];

TVIRoom *room = [TwilioVideo connectWithOptions:connectOptions delegate:self];
if (self.localVideoTrack == nil) {
    self.camera = TVICameraCapturer()
    self.localVideoTrack = TVILocalVideoTrack.init(capturer: self.camera!)
}

if (self.localAudioTrack == nil) {
    self.localAudioTrack = TVILocalAudioTrack.init()
}

let connectOptions = TVIConnectOptions.init(token: accessToken) { (builder) in
    builder.roomName = "my-room"
    builder.audioTracks = [ self.localAudioTrack! ]
    builder.videoTracks = [ self.localVideoTrack! ]
}

var room = TwilioVideo.connect(with: connectOptions, delegate: self)
// Connect to room
Room room = Video.connect(context, connectOptions, new Room.Listener() {
    @Override
    public void onConnected(Room room) {}

    @Override
    public void onConnectFailure(Room room, TwilioException e) {}

    @Override
    public void onDisconnected(Room room, TwilioException e) {}

    @Override
    public void onRecordingStarted(Room room) {}

    @Override
    public void onRecordingStopped(Room room) {}

    @Override
    public void onParticipantConnected(Room room, Participant participant) {
        Log.i("Room.Listener", participant.getIdentity() + " has joined the room.");
    }

    @Override
    public void onParticipantDisconnected(Room room, Participant participant) {
        Log.i("Room.Listener", participant.getIdentity() + " has left the room.");
    }
);

// ... Assume we have received the connected callback

// After receiving the connected callback the LocalParticipant becomes available
LocalParticipant localParticipant = room.getLocalParticipant();
Log.i("LocalParticipant ", localParticipant.getIdentity());

// Get a participant from the room (let's assume we have a participant named Alice)
Participant participant = room.getParticipants().get("Alice");
Log.i("HandleParticipants", participant.getIdentity() + " is in the room.");
// Log your Client's LocalParticipant in the Room
const localParticipant = room.localParticipant;
console.log('Connected to the Room as LocalParticipant "%s"', localParticipant.identity);

// Log any Participants already connected to the Room
room.participants.forEach(participant => {
  console.log('Participant "%s" is connected to the Room', participant.identity);
});

// Log new Participants as they connect to the Room
room.once('participantConnected', participant => {
  console.log('Participant "%s" has connected to the Room', participant.identity);
});

// Log Participants as they disconnect from the Room
room.once('participantDisconnected', participant => {
  console.log('Participant "%s" has disconnected from Room', participant.identity);
});
TVIConnectOptions *connectOptions = [TVIConnectOptions optionsWithToken:self.accessToken
                                                                  block:^(TVIConnectOptionsBuilder * _Nonnull builder) {
    builder.roomName = @"my-room";
    builder.audioTracks = @[ self.localAudioTrack ];
    builder.videoTracks = @[ self.localVideoTrack ];
}];

TVIRoom *room = [TwilioVideo connectWithOptions:connectOptions delegate:self];

#pragma mark - TVIRoomDelegate

- (void)didConnectedToRoom:(nonnull TVIRoom *)room {
    // The Local Participant
    TVILocalParticipant *localParticipant = room.localParticipant;
    NSLog(@"Local Participant %@", localParticipant.identity);
    
    // Connected participants
    NSArray *participants = room.participants;
    NSLog(@"Number of connected Participants %ld", [participants count]);
}

- (void)room:(nonnull TVIRoom *)room participantDidConnect:(nonnull TVIParticipant *)participant {
    NSLog(@"Participant %@ has joined Room %@", participant.identity, room.name);
}

- (void)room:(nonnull TVIRoom *)room participantDidDisconnect:(nonnull TVIParticipant *)participant {
    NSLog(@"Participant %@ has left Room %@", participant.identity, room.name);
}
let connectOptions = TVIConnectOptions.init(token: accessToken) { (builder) in
    builder.roomName = "my-room"
    builder.audioTracks = [ self.localAudioTrack! ]
    builder.videoTracks = [ self.localVideoTrack! ]
}

room = TwilioVideo.connect(with: connectOptions, delegate: self)

// MARK: TVIRoomDelegate

func didConnect(to room: TVIRoom) {
    // The Local Participant
    let localParticipant = room.localParticipant;
    print("Local identity \(localParticipant.identity)")
    
    // Connected participants
    let participants = room.participants;
    print("Number of connected Participants \(participants.count)")
}

func room(_ room: TVIRoom, participantDidConnect participant: TVIParticipant) {
    print ("Participant \(participant.identity) has joined Room \(room.name)")
}

func room(_ room: TVIRoom, participantDidDisconnect participant: TVIParticipant) {
    print ("Participant \(participant.identity) has left Room \(room.name)")
}
private Room.Listener roomListener() {
  return new Room.Listener() {

    @Override
    public void onParticipantConnected(Room room, Participant participant) {
      Log.v(TAG, "Participant connected: " + participant.getIdentity());
    }

    @Override
    public void onParticipantDisconnected(Room room, Participant participant) {
      Log.v(TAG, "Participant disconnected: " + participant.getIdentity());
    }
  };
}
room.on('participantConnected', function(participant) {
  console.log('Participant connected: ' + participant.identity);
});

room.on('participantDisconnected', function(participant) {
  console.log('Participant disconnected: ' + participant.identity);
});
#pragma mark - TVIParticipantDelegate

- (void)participant:(TVIParticipant *)participant addedVideoTrack:(TVIVideoTrack *)videoTrack {
    NSLog(@"Participant %@ added a video track",participant.identity);
}

- (void)participant:(TVIParticipant *)participant removedVideoTrack:(TVIVideoTrack *)videoTrack {
    NSLog(@"Participant %@ removed a video track",participant.identity);
}
// MARK: TVIParticipantDelegate

func participant(_ participant: TVIParticipant, addedVideoTrack videoTrack: TVIVideoTrack) {
    NSLog("Participant \(participant.identity) added video track")
}

func participant(_ participant: TVIParticipant, removedVideoTrack videoTrack: TVIVideoTrack) {
    NSLog("Participant \(participant.identity) removed video track")
}
// First, we set a Media Listener when a Participant first connects:
private Room.Listener roomListener() {
  return new Room.Listener() {
    @Override
    public void onParticipantConnected(Room room, Participant participant) {
      participant.setListener(participantListener());
    }
  };
}

/* In the Participant listener, we can respond when the Participant adds a Video
Track by rendering it on screen: */
private Participant.Listener participantListener() {
  return new Participant.Listener() {
      @Override
      public void onVideoTrackAdded(Participant participant, VideoTrack videoTrack) {
        primaryVideoView.setMirror(false);
        videoTrack.addRenderer(primaryVideoView);
      }
  };
}
// Attach the Participant's Media to a <div> element.
room.on('participantConnected', function(participant) {
  console.log("Participant '" +  participant.identity  + "' connected");
  
  participant.tracks.forEach(track => {
    document.getElementById('remote-media-div').appendChild(track.attach());
  });
});
#pragma mark - TVIRoomDelegate

// First, we set a Participant Delegate when a Participant first connects
- (void)room:(TVIRoom *)room participantDidConnect:(TVIParticipant *)participant {
    NSLog(@"Participant did connect:%@",participant.identity);
    participant.delegate = self;
}

#pragma mark - TVIParticipantDelegate

/*
 * In the Participant Delegate, we can respond when the Participant adds a Video
 * Track by rendering it on screen
 */
- (void)participant:(TVIParticipant *)participant addedVideoTrack:(TVIVideoTrack *)videoTrack {
    NSLog(@"Participant %@ added a video track",participant.identity);
    
    self.remoteView = [[TVIVideoView alloc] initWithFrame:self.view.bounds delegate:self];
    [videoTrack addRenderer:self.remoteView];

    [self.view addSubview:self.remoteView];
}

#pragma mark - TVIVideoViewDelegate

// Lastly, we can subscribe to important events on the Video View
- (void)videoView:(TVIVideoView *)view videoDimensionsDidChange:(CMVideoDimensions)dimensions {
    NSLog(@"Dimensions changed to: %d x %d", dimensions.width, dimensions.height);
    [self.view setNeedsLayout];
}
// MARK: TVIRoomDelegate

// First, we set a Participant Delegate when a Participant first connects:
func room(_ room: TVIRoom, participantDidConnect participant: TVIParticipant) {
	print("Participant connected: \(participant.identity!)")
	participant.delegate = self
}

// MARK: TVIParticipantDelegate

/* 
 * In the Participant Delegate, we can respond when the Participant adds a Video
 * Track by rendering it on screen.
 */
func participant(_ participant: TVIParticipant, addedVideoTrack videoTrack: TVIVideoTrack) {
	print("Participant \(participant.identity) added video track")
    
	self.remoteView = TVIVideoView.init(frame: self.view.bounds, delegate:self)
	videoTrack.addRenderer(self.remoteView)

    self.view.addSubview(self.remoteView!)
}


// MARK: TVIVideoViewDelegate

// Lastly, we can subscribe to important events on the VideoView
func videoView(_ view: TVIVideoView, videoDimensionsDidChange dimensions: CMVideoDimensions) {
    print("The dimensions of the video track changed to: \(dimensions.width)x\(dimensions.height)")
    self.view.setNeedsLayout()
}
/* The CameraCapturer is a default video capturer provided by Twilio which can
   capture video from the front or rear-facing device camera */
private CameraCapturer cameraCapturer;

/* A VideoView receives frames from a local or remote video track and renders them
   to an associated view. */
private VideoView primaryVideoView;

// Initialize the camera capturer and start the camera preview
cameraCapturer = new CameraCapturer(this, CameraSource.FRONT_CAMERA);
LocalVideoTrack localVideoTrack = LocalVideoTrack.create(context, true, cameraCapturer);
primaryVideoView.setMirror(true);
localVideoTrack.addRenderer(primaryVideoView);

// Release the local video track to free native memory resources once you are done
localVideoTrack.release();
const { createLocalVideoTrack } = require('twilio-video');

createLocalVideoTrack().then(track => {
  var localMediaContainer = document.getElementById('local-media-ctr');
  localMediaContainer.appendChild(track.attach());
});
// Use TVICameraCapturer to produce video from the device's front camera.
self.camera = [[TVICameraCapturer alloc] init];

// Create a local video track, and render it in the preview view
self.localVideoTrack = [TVILocalVideoTrack trackWithCapturer:self.camera];

if (!self.localVideoTrack) {
    NSLog(@"Failed to add video track");
} else {
    // TVIVideoView is a TVIVideoRenderer and can be added to any TVIVideoTrack.
    self.previewView = [[TVIVideoView alloc] initWithFrame:rect];
    
    // Add renderer to video track for local preview
    [self.localVideoTrack addRenderer:previewView];
    
    self.previewView.mirror = (self.camera.source == TVICameraCaptureSourceFrontCamera);
    
    [self.view addSubview:previewView];
}
// Use TVICameraCapturer to produce video from the device's front camera.
var camera = TVICameraCapturer()

// Create a local video track, and render it in the preview view
var localVideoTrack = TVILocalVideoTrack.init(capturer: camera!)
if (localVideoTrack == nil) {
    logMessage(messageText: "Failed to add video track")
} else {
    // TVIVideoView is a TVIVideoRenderer and can be added to any TVIVideoTrack.
    let previewView = TVIVideoView.init(frame: rect)
    
    // Attach view to video track for local preview
    localVideoTrack!.addRenderer(previewView)
    
    previewView.shouldMirror = (camera!.source == .frontCamera)
    
    self.view.addSubview(previewView)
}
// To disconnect from a Room, we call:
room.disconnect();

// This results in a call to Room.Listener#onDisconnected
private Room.Listener roomListener() {
  return new Room.Listener() {
    @Override
    public void onDisconnected(Room room, TwilioException e) {
        Log.d(TAG,"Disconnected from " + room.getName());
    } 
  };
}
room.on('disconnected', room => {
  // Detach the local media elements
  room.localParticipant.tracks.forEach(track => {
    var attachedElements = track.detach();
    attachedElements.forEach(element => element.remove());
  });
});

// To disconnect from a Room
room.disconnect();
// To disconnect from a room, we call:
[self.room disconnect];

// This results in a callback to TVIRoomDelegate#room:didDisconnectWithError

#pragma mark - TVIRoomDelegate

- (void)room:(TVIRoom *)room didDisconnectWithError:(NSError *)error {
    NSLog(@"Did disconnect from room");
}
// To disconnect from a Room, we call:
room.disconnect()

// This results in a callback to TVIRoomDelegate#room:didDisconnectWithError

// MARK: TVIRoomDelegate

func room(_ room: TVIRoom, didDisconnectWithError error: Error?) {
    print("Disconnected from room \(room.name)")
}