This tutorial enables you to quickly get started in your development efforts to create an iOS app with real-time video calls, voice calls, and interactive broadcasting. With this sample app you can:
- Start and end audio/visual communication between two users.
- Join a communication channel.
- Mute and unmute audio.
- Enable and disable video.
- Switch between the front and rear cameras.
- Xcode
- An Agora.io Developer Account
This section shows you how to prepare, build, and run the sample application.
In order to build and run the sample application, you must specify an App ID:
- Create a developer account at agora.io. Once you finish the sign up process, you will be redirected to the Dashboard.
- Navigate in the Dashboard tree on the left to Projects > Project List.
- Locate the Default Project and copy the value for App ID.
-
Clone this repository.
-
Open the project file Agora iOS Tutorial.xcodeproj in Xcode.
-
In the Project Navigator, navigate to Agora iOS Tutorial > Agora iOS Tutorial.
-
Open VideoCallViewController.swift.
-
Locate the following line and replace <#Your App Id#> with the App ID in the dashboard.
let AppID: String = <#Your App Id#>
-
Build and run the project. This will display the application on your iOS device or emulator.
This workflow was used to develop the 1-to-1 sample application:
For details about the APIs used to develop this sample, see the Agora.io Documentation.
To set up the project:
- Import the Agora SDK
- Set Permissions
- Create Visual Assets
- Design the User Interface
- Prepare the Video Call View Controller
- Prepare the Channel View Controller
The sample's Xcode project is a Single View Application.
To integrate the SDK into the project, create a pod file in the same location as the Xcode project file:
pod init
Modify the new pod file to include the AgoraRtcEngine_iOS
pod:
target 'AgoraVideoQuickstart' do
use_frameworks!
pod 'AgoraRtcEngine_iOS'
end
Install the pod file:
pod install
In the Info.plist
file, set the privacy settings for the camera and microphone to Video Chat to allow the app to access them:
Add the following icon assets for the user interface to the Assets.xcassets folder:
Asset | Description |
---|---|
hangUpButton |
An image of a red telephone for a hang up button. |
localVideoMutedBg |
The background image for a video mute button. |
muteButton |
A picture of a microphone to mute audio. |
muteButtonSelected |
A picture of a microphone with a cross through it to indicate that the audio is muted. |
switchCameraButton |
A picture of a camera and rotational arrows to switch between the two cameras. |
switchCameraButtonSelected |
A highlighted picture of a camera and rotational arrows to indicate that the rear camera is in use. |
videoMuteButton |
A picture of a camera to mute video. |
videoMuteButtonSelected |
A picture of a camera highlighted to indicate that video is muted. |
videoMuteButtonIndicator |
A picture of a camera crossed out to indicate the camera is off. |
The sample was developed using the model-view-controller pattern. The Main.storyboard file defines the user interface and makes use of two view controllers: VideoCallViewController.swift and SetChannelViewController.swift.
VideoCallViewController.swift defines a view that handles a video call. Note that its file VideoCallViewController.swift was renamed from the default file name ViewController.swift to reflect the purpose of the view.
These are the main aspects of the Video Call View Controller on the storyboard:
Component | Description |
---|---|
View |
A view that handles the main video feed. This view contains other views. |
remoteVideo |
A view displaying the remote, incoming video feed (for example, the video the user will see). |
remoteVideoMutedIndicator |
A view displaying an icon indicating that remote video is muted. |
localVideo |
A smaller view at the top right corner showing the local video feed. |
localVideoMutedBg |
A gray background to indicate that local video is muted when the user pauses the video feed. |
localVideoMutedIndicator |
An icon that is overlaid and centered over the localVideoMutedBg view to indicate that local video is muted. |
controlButtons |
A view that encapsulates four buttons: Pause Video, Audio Mute, Switch Camera, and Hang Up. Each button uses the assets described above. |
SetChannelViewController.swift defines a view that handles channel selection.
The main aspects of the Channel View Controller on the storyboard are shown here:
- A segue (
exitCall
) fromVideoCallViewController
toSetChannelViewController
ends the video call once the user has pressed the Hang Up button. - The text field is where the user inputs a channel name.
- The button starts the video call.
To configure 1-to-1 communication resources:
- Create an Agora Instance
- Configure the Video Mode
- Join a Channel
- Set up Local Video
- Set up Video Call View Controller
The code samples in this section are in VideoCallViewController.Swift.
AgoraRtcEngineKit
is the interface of the Agora API that provides communication functionality. Once imported, create a singleton instance by invoking sharedEngine during initialization, passing the application ID and a reference to self
as the delegate. The Agora API uses delegates to inform the application about Agora engine runtime events such as joining or leaving a channel and the addition of new participants.
In the sample project, a helper method called initializeAgoraEngine()
contains this logic and is invoked by viewDidLoad()
:
import UIKit
import AgoraRtcEngineKit
var agoraKit: AgoraRtcEngineKit!
let AppID: String = "Your-App-ID"
var channel:String? //Stores the user's desired channel name from (covered later in the tutorial)
func initializeAgoraEngine() {
agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: AppID, delegate: self)
}
override func viewDidLoad() {
super.viewDidLoad(true);
initializeAgoraEngine();
...
}
The next step is to enable video mode, configure the video encoding profile, and specify if the width and height can change when switching from portrait to landscape:
func setupVideo() {
agoraKit.enableVideo() // Enables video mode.
agoraKit.setVideoProfile(._VideoProfile_360P, swapWidthAndHeight: false) // Default video profile is 360P
}
override func viewDidLoad() {
super.viewDidLoad();
initializeAgoraEngine();
setupVideo();
...
}
In the sample, a helper method called setupVideo()
contains this logic and is invoked by viewDidLoad()
. It starts by enabling video with enableVideo(). The video encoding profile is then set to 360p and the swapWidthAndHeight
parameter is set to false
via setVideoProfile(). Each profile includes a set of parameters such as resolution, frame rate, and bitrate. If a device's camera does not support the specified resolution, the SDK automatically chooses a suitable camera resolution. However, the encoder resolution still uses the profile specified by setVideoProfile()
.
Since this configuration takes place before entering a channel, the end user will initially begin in video mode rather than audio mode. If video mode were to be enabled enabled during a call, the app will switch from audio to video mode.
A helper method called joinChannel()
invokes agoraKit.joinChannel()
enables a user to join a specific channel:
func joinChannel() {
agoraKit.joinChannel(byKey: nil, channelName: "demoChannel1", info:nil, uid:0) {[weak self] (sid, uid, elapsed) -> Void in
if let weakSelf = self {
weakSelf.agoraKit.setEnableSpeakerphone(true)
UIApplication.shared.isIdleTimerDisabled = true
}
}
}
The channelName
parameter receives the name of the channel to join, and the value of 0 for the uid
parameter allows Agora to chose a random ID for the channel ID.
The call using agoraKit enables the speakerphone when using Agora, and UIApplication.shared.isIdleTimerDisabled
disables the application's idle timer to prevent the application from idling while the app is running.
Note: Users in the same channel can talk to each other, but users with different app IDs cannot call each other even if they join the same channel.
In the sample, the helper method joinChannel()
is invoked by viewDidLoad()
:
super.viewDidLoad();
initializeAgoraEngine();
setupVideo();
joinChannel();
...
}
The logic for the local video feed is contained within a helper method called setupLocalVideo()
, which is invoked by viewDidLoad()
:
func setupLocalVideo() {
let videoCanvas = AgoraRtcVideoCanvas()
videoCanvas.uid = 0
videoCanvas.view = localVideo
videoCanvas.renderMode = .render_Fit
agoraKit.setupLocalVideo(videoCanvas)
}
override func viewDidLoad() {
super.viewDidLoad(true);
initializeAgoraEngine();
setupVideo();
setupLocalVideo();
}
setupLocalVideo()
creates an AgoraRtcVideoCanvas
object for the video stream and initializes the following properties:
- uid: A value of 0 allows Agora to chose a random ID for the stream feed.
- view: Set to the
localVideo
view from the storyboard. - renderMode: Set to
render_Fit
to ensure the video is resized proportionally to fit the display window.
The call to setupLocalVideo() passes the AgoraRtcVideoCanvas
object that was just created.
The VideoCallViewController
class extends AgoraRtcEngineDelegate
:
func rtcEngine(_ engine: AgoraRtcEngineKit!, firstRemoteVideoDecodedOfUid uid:UInt, size:CGSize, elapsed:Int) {
if (remoteVideo.isHidden) {
remoteVideo.isHidden = false
}
let videoCanvas = AgoraRtcVideoCanvas()
videoCanvas.uid = uid
videoCanvas.view = remoteVideo
videoCanvas.renderMode = .render_Adaptive
agoraKit.setupRemoteVideo(videoCanvas)
}
func rtcEngine(_ engine: AgoraRtcEngineKit!, didOfflineOfUid uid:UInt, reason:AgoraRtcUserOfflineReason) {
self.remoteVideo.isHidden = true
}
func rtcEngine(_ engine: AgoraRtcEngineKit!, didVideoMuted muted:Bool, byUid:UInt) {
remoteVideo.isHidden = muted
remoteVideoMutedIndicator.isHidden = !muted
}
The rtcEngine(engine: AgoraRtcEngineKit, firstRemoteVideoDecodedOfUid uid: UInt, size: CGSize, elapsed: Int)
delegate method is invoked once connected with another user and the first remote video frame is received and decoded. This method performs the following actions:
- Checks if the
remoteVideo
view is hidden and unhides it if it is hidden. - Initializes the
AgoraRtcVideoCanvas
object. - Sets the
uid
property to 0 to allow Agora to choose a random UID for the stream feed. - Sets the
view
property to theremoteVideo
view from the storyboard. - Sets
renderMode
torender_Fit
to ensure the video is resized proportionally to fit the display window. - Invokes
agoraKit.setupRemoteVideo(videoCanvas)
passing in theAgoraRtcVideoCanvas
object that was just created.
The rtcEngine(_engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraRtcUserOfflineReason)
delegate is invoked when another user leaves the channel. This method sets the remoteVideo
view to be hidden when a user leaves the call.
The rtcEngine(engine: AgoraRtcEngineKit, didVideoMuted muted: UInt, byUid: UInt)
is invoked when a remote user pauses their stream. This method toggles the remote video user inteface elements.
Implement the following communication features:
- Channel Selection
- Hang Up and End the Call
- Mute Audio and Video
- Toggle Cameras
- Hide Video Views
- Hide Buttons
The Channel View Controller allows the user to specify the channel they wish to join through a textbox for the channel name and a button to start the call. The text field was added as an outlet and the button was added as an action. The logic to handle this in the sample is implemented in SetChannelViewController.swift
:
@IBOutlet weak var channelName: UITextField!
@IBAction func startCall(_ sender: UIButton) {
if !(channelName.text?.isEmpty) {
self.performSegue(withIdentifier: "startCall", sender: self)
} else {
print("Enter Channel Name")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let viewController = segue.destination as? VideoCallViewController {
viewController.channel = channelName.text!
}
}
The startCall()
method performs the following actions:
- If the text field contains a value, the segue
startCall
navigates the user to theVideoChatViewController
within theIBAction
for the button. - If the text field does not contain a value, the method prompts the user to enter some text for the channel name.
The prepare(for segue:)
method is invoked to obtain the name of the channel and pass it to the VideoChatViewController
.
Video Call View Controller contains a helper function called leaveChannel()
with the logic to leave the current video call (channel). This is invoked by the IBAction
for the Hang-Up button:
@IBAction func didClickHangUpButton(_ sender: UIButton) {
leaveChannel()
}
func leaveChannel() {
agoraKit.leaveChannel(nil)
hideControlButtons()
UIApplication.shared.isIdleTimerDisabled = false
remoteVideo.removeFromSuperview()
localVideo.removeFromSuperview()
agoraKit = nil
}
func hideControlButtons() {
controlButtons.isHidden = true
}
The leaveChannel()
method:
- Invokes
agoraKit.leaveChannel()
to leave the channel - Invokes the helper method
hideControlButtons()
to hide thecontrolButtons
view containing the bottom buttons. - Re-enables the application's idle timer.
- Removes both the local and remote video views.
- Sets
agoraKit
tonil
to remove the reference to theAgoraRtcEngineKit
object.
To allow the user to mute audio, the IBAction
for the mute button invokes agoraKit.muteLocalAudioStream()
:
@IBAction func didClickMuteButton(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
agoraKit.muteLocalAudioStream(sender.isSelected)
resetHideButtonsTimer()
}
Once audio is muted, the helper method resetHideButtonsTimer()
is invoked to cancel any view requests and to ensure the control buttons are hidden:
func resetHideButtonsTimer() {
VideoCallViewController.cancelPreviousPerformRequests(withTarget: self)
perform(#selector(hideControlButtons), with:nil, afterDelay:3)
}
To allow the user to mute local video (i.e. to prevent video of the current user from being broadcast to other users), the IBAction
for the video button invokes muteLocalVideoStream():
@IBAction func didClickVideoMuteButton(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
agoraKit.muteLocalVideoStream(sender.isSelected)
localVideo.isHidden = sender.isSelected
localVideoMutedBg.isHidden = !sender.isSelected
localVideoMutedIndicator.isHidden = !sender.isSelected
resetHideButtonsTimer()
}
Once muted, the views related to the local view are hidden and the helper method resetHideButtonsTimer()
is invoked to cancel any perform requests and to ensure the control buttons are hidden.
To enable the user to choose between the front and rear cameras, an IBAction
for the camera switch button invokes switchCamera() to add the camera switch functionality:
@IBAction func didClickSwitchCameraButton(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
agoraKit.switchCamera()
resetHideButtonsTimer()
}
To hide all the image views that are meant to appear when either the remote or local video feeds are paused, the sample defines the hideVideoMuted()' helper method. This method is invoked from
viewDidLoad()` to ensure the videos are hidden on startup:
func hideVideoMuted() {
remoteVideoMutedIndicator.isHidden = true
localVideoMutedBg.isHidden = true
localVideoMutedIndicator.isHidden = true
}
For a refined user experience, the sample hides the view containing the buttons after three seconds, to make the user interface appear cleaner. The sample uses a helper method called setupButtons()
which calls the hideControlButtons()
function after three seconds, and is invoked by viewDidLoad()
:
func setupButtons() {
perform(#selector(hideControlButtons), with:nil, afterDelay:3)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(VideoChatViewController.viewTapped))
view.addGestureRecognizer(tapGestureRecognizer)
view.isUserInteractionEnabled = true
}
The sample uses a tap gesture recognizer (of type: UITabGestureRecognizer
) as part of the view which performs the action of calling the viewTapped()
function:
func viewTapped() {
if (controlButtons.isHidden) {
controlButtons.isHidden = false;
perform(#selector(hideControlButtons), with:nil, afterDelay:3)
}
}
If you have any questions, please feel free to reach out via e-mail or Twitter.