Display Video - iOS
In this guide, we will take a look at how to render the participant's video on the screen.
For simplicity purposes, we will render a remote participant and a local participant in our room view.
1. Create VideoView
We will create a storyboard for our video view as shown below.
Use the above view hierarchy for faster implementation.
RTCMTLVideoView comes from VideoSDKRTC. In the storyboard, set the Class of each video view to RTCMTLVideoView and its Module to VideoSDKRTC in the Identity inspector.
Below is the code for defining the variables for the UIViews that we create in the storyboard
- Swift
import VideoSDKRTC
class RoomViewController: UIViewController, UNUserNotificationCenterDelegate {
//remote participant view
@IBOutlet weak var participantViewsContainer: UIView!
@IBOutlet weak var remoteParticipantVideoContainer: RTCMTLVideoView!
//local participant view
@IBOutlet weak var localParticipantViewContainer: UIView!
@IBOutlet weak var localParticipantViewVideoContainer: RTCMTLVideoView!
// ...
}
2. Setup Event Listeners
When a room is created or joined by a participant, we add the event listeners that will notify us when participant's video needs to be rendered.
Adding event listeners will enable us to listen to the onStreamEnabled and onStreamDisabled events, using which we can update the view and start rendering the video.
- Swift
//adding event listener when local peer joins the room
func createAndJoinRoom() {
do {
// create the room
room = try VideoSDK.createRoom(
roomId: "abcd-xyzw-efgh",
token: "<Your-Token>",
participantName: "John Doe",
micEnabled: true,
camEnabled: true
)
} catch {
print("Failed to create the room: \(error)")
return
}
// add listener after creating the room
room?.addEventListener(self)
// join
Task {
do {
try await room?.join()
} catch {
print("Failed to join the room: \(error)")
}
}
}
//adding event listener when remote peer joins the room
extension RoomViewController: RoomEventListener {
/// Room joined
func onRoomJoined() {
// handle local participant on start
guard let localParticipant = self.room?.localParticipant else { return }
// add event listener
localParticipant.addEventListener(self)
}
func onParticipantJoined(_ participant: Participant) {
// add event listener
participant.addEventListener(self)
}
}
After adding event listeners, we can use the onStreamEnabled() and onStreamDisabled() of the ParticipantEventListener protocol for rendering purposes.
3. Render Video
-
When an audio/video stream is enabled by local or remote participant, the
onStreamEnabledevent is triggered. Similarly when an audio/video stream is disabled by the local or remote participant, theonStreamDisabledevent is triggered. -
We can render the video using the stream that we get from
onStreamEnabledas shown below.
We will not render audio separately. The SDK plays the audio of the remote participants for you.
- Swift
extension RoomViewController: ParticipantEventListener {
func onStreamEnabled(_ stream: RTCStream, forParticipant participant: Participant) {
//call updateView function
updateView(participant: participant, forStream: stream, enabled: true)
}
func onStreamDisabled(_ stream: RTCStream, forParticipant participant: Participant) {
//call updateView function
updateView(participant: participant, forStream: stream, enabled: false)
}
}
- Swift
extension RoomViewController {
//update view for stream kind video
func updateView(participant: Participant, forStream stream: RTCStream, enabled: Bool) { // true
switch stream.kind {
case .video:
if let videotrack = stream.track as? RTCVideoTrack {
if enabled {
showVideoView(participant: participant, stream: videotrack) // show video
} else {
hideVideoView(participant: participant, stream: videotrack) // hide video
}
}
default:
break
}
}
//show video view
func showVideoView(participant: Participant, stream: RTCVideoTrack){
DispatchQueue.main.async {
UIView.animate(withDuration: 0.5){
//rendering if more than one participants
if self.participants.count > 1 {
if let currentVideoTrack = self.participants.first(where: { $0.isLocal })?.streams.first(where: {$1.kind == .video})?.value.track as? RTCVideoTrack {
currentVideoTrack.remove(self.remoteParticipantVideoContainer)
currentVideoTrack.remove(self.localParticipantViewVideoContainer)
}
//add stream to the respective video view
stream.add(participant.isLocal ? self.localParticipantViewVideoContainer : self.remoteParticipantVideoContainer)
//show the respective video view
if participant.isLocal {
self.localParticipantViewVideoContainer.isHidden = false
} else {
self.remoteParticipantVideoContainer.isHidden = false
}
//shift local video view to its container
if let localParticipantVideoStream = self.participants.first(where: { $0.isLocal })?.streams.first(where: {$1.kind == .video})?.value.track as? RTCVideoTrack {
localParticipantVideoStream.add(self.localParticipantViewVideoContainer)
self.localParticipantViewVideoContainer.isHidden = false
self.localParticipantViewContainer.isHidden = false
}
} else {
//rendering if more than one participants
stream.add(self.remoteParticipantVideoContainer)
self.remoteParticipantVideoContainer.isHidden = false
}
}
}
}
//hide video view
func hideVideoView(participant: Participant, stream: RTCVideoTrack){
UIView.animate(withDuration: 0.5){
//adjust view when there are more than one participants
if self.participants.count > 1 {
//remove stream from the respective view
stream.remove(participant.isLocal ? self.localParticipantViewVideoContainer : self.remoteParticipantVideoContainer)
//hide the respective view
if participant.isLocal {
self.localParticipantViewVideoContainer.videoContentMode = .scaleAspectFill
self.localParticipantViewVideoContainer.isHidden = true
} else {
self.remoteParticipantVideoContainer.isHidden = true
}
} else {
//render video in remote container when there is only one participant
stream.remove(self.remoteParticipantVideoContainer)
self.remoteParticipantVideoContainer.isHidden = true
}
}
}
}

The above code is a simplified example of how you can render or remove a video stream. Use it as a reference to render your own video inside an RTCMTLVideoView.
If you don't use a storyboard, call createRenderView() on the stream in onStreamEnabled. It returns an RTCMTLVideoView that already shows the stream's video, or nil for an audio stream. Add it to your view hierarchy, and remove it from its superview in onStreamDisabled.
To mirror your own front-camera view, call setMirror(true) on its RTCMTLVideoView. It is @MainActor, so call it on the main thread. Any transform you set on the view is kept.
Additionally, you can show Participant's initials when their video is off using the participant.displayName. Display the initials when the onStreamDisabled event is triggered as the user switches off their camera, by placing another view that would contain the initials over the respective container.
4. Display Audio Status
- Unlike Video, we do not need to explicitly render audio. Therefore, you can use the
onStreamEnabledevents for the audio kind to show the mute/unmute mic icon.
Below is the sample on how it can be done.
Example
- Swift
extension RoomViewController: ParticipantEventListener {
func onStreamEnabled(_ stream: RTCStream, forParticipant participant: Participant) {
//call updateView function
updateView(participant: participant, forStream: stream, enabled: true)
}
func onStreamDisabled(_ stream: RTCStream, forParticipant participant: Participant) {
//call updateView function
updateView(participant: participant, forStream: stream, enabled: false)
}
}
- Swift
extension RoomViewController {
//update view for stream kind audio
func updateView(participant: Participant, forStream stream: RTCStream, enabled: Bool) { // true
switch stream.kind {
case .audio:
updateMic(participant: participant, enabled)
default:
break
}
}
//update mic according to the status.
func updateMic(participant: Participant, _ enabled: Bool) {
if !participant.isLocal {
viewRemoteMicContainer.isHidden = enabled // set mic icon accordingly
} else if participants.count == 1 {
participantViewsContainer.bringSubviewToFront(viewRemoteMicContainer)
viewRemoteMicContainer.isHidden = enabled // set mic icon accordingly
}
}
}
API Reference
The API references for all the methods and events utilized in this guide are provided below.
Got a Question? Ask us on discord

