-
Notifications
You must be signed in to change notification settings - Fork 11
Full Agora UIKit Guide
Agora UIKit Makes Video Streaming Easier Than Ever
When setting up your own streaming application with Agora, a few technical steps might slow you down. Now with Agora UIKit for iOS and macOS you can create an application as easily as placing a UIView, NSView or SwiftUI View.
- Prerequisites
- Setup
- Adding Video Streaming
- Default Functionality
- Basic Customising with AgoraSettings
- Advanced Customisation of AgoraVideoViewer
- An Agora developer account (see How To Get Started with Agora)
- Xcode 11.0 or later
- iOS device with minimum iOS 13.0, or macOS minimum 10.14
- A basic understanding of iOS development
- CocoaPods
Agora iOS macOS UIKit supports both Swift PM and CocoaPods dependency managers.
Add the URL of this repository to your Xcode 11+ Project.
Go to File > Swift Packages > Add Package Dependency, and paste in this link:
https://github.com/AgoraIO-Community/iOS-UIKit
If you are using the developer preview, add 4.0.0-preview
in the version box there, otherwise use a version from 1.0.0
up to 2.0.0
.
If you have issues installing the Swift Package: In Xcode's File menu, select 'Swift Packages' and then 'Reset Package Caches'.
Create an iOS or macOS project in Xcode, and then install the CocoaPod AgoraUIKit_iOS or AgoraUIKit_macOS. Your Podfile should look like this:
target 'Agora-UIKit-Project' do
# Uncomment the next line if you want to install for iOS
# pod 'AgoraUIKit_iOS', '~> 1.3'
# Uncomment the next line if you want to install for macOS
# pod 'AgoraUIKit_macOS', '~> 1.3'
end
The latest AgoraUIKit release at the time of writing this post is v1.3.2
Run pod init
, and open the .xcworkspace file to get started.
Adding an AgoraVideoViewer to your ViewController should look like this:
import UIKit
import AgoraUIKit
class ViewController: UIViewController {
var agoraView: AgoraVideoViewer!
override func viewDidLoad() {
super.viewDidLoad()
self.agoraView = AgoraVideoViewer(
connectionData: AgoraConnectionData(
appId: <#Agora App ID#>,
appToken: <#Agora Token or nil#>
)
)
// frame the view
self.view.addSubview(agoraView)
agoraView.frame = self.view.bounds
agoraView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Set the layout style (default .grid)
agoraView.style = .grid // or .floating
// join the channel "test"
agoraView.join(channel: "test", as: .broadcaster)
}
}
Grid layout on the left, floating layout on the right
If you opt to make a SwiftUI app, adding an AgoraViewer to your view looks like this:
import SwiftUI
import AgoraUIKit
struct ContentView: View {
@State private var connectedToChannel = false
var agview: AgoraViewer {
let agoraView = AgoraViewer(
connectionData: AgoraConnectionData(
appId: <#Agora App ID#>,
appToken: <#Agora Token or nil#>
),
style: .floating
)
agoraView.join(channel: "test", with: nil, as: .broadcaster)
}
var body: some View {
agview
}
}
For the SwiftUI app, you may need to handle microphone and camera permission pop-ups manually. See the SwiftUI example project included with the Agora UIKit repository for an example.
The SwiftUI app makes a very similar view to that found in the previous example.
The macOS app setup is very similar to the first example found above. A minimal example app looks like this:
import Cocoa
import AgoraUIKit
class ViewController: NSViewController {
var agoraView: AgoraVideoViewer!
override func viewDidLoad() {
super.viewDidLoad()
self.agoraView = AgoraVideoViewer(
connectionData: AgoraConnectionData(
appId: "cff7258e55c24d0f99bbf93d10367d5f",
appToken: nil
),
style: .floating,
)
// Method provided with the library to fill
// a UIView or NSView with the AgoraVideoViewer
agoraView.fills(view: self.view)
// join the channel "test"
agoraView.join(channel: "test", as: .broadcaster)
// Optionally the application window to a specific size
self.view.setFrameSize(NSSize(width: 1440, height: 790))
}
}
The layouts are also the same, with a choice of grid or floating:
Agora UIKit grid layout
Agora UIKit floating layout
By default, Agora UIKit includes the following functionality before any customising:
- Automatically layout all video streams
- Displaying the active speaker in the larger display in the floating layout
- Allowing you to pin any stream to the larger display in the floating layout
- Buttons for disabling camera or microphone, beautify filter, share screen (macOS only) and switch cameras (iOS only).
- Icon for signalling the local or remote streamer’s microphone is disabled
- Automatically subscribing to high- or low-quality video streams, depending on the size of views
- Multiple layouts, including floating, grid, collection (which displays only the scrolling collection of streams), and custom (example of custom below)
A few basic and more advanced features in this library make it customisable for your needs. And there’s a growing list of things to customise using AgoraSettings. The entire class definition can be found here.
The position of the collection view of all the streamers when using floating layout and the collection of buttons (such as mute microphone and beautify) can be moved around with floatPosition
and buttonPosition
, respectively. You have the choice of top, right, bottom and left for this enumeration. floatPosition
defaults to the top, and buttonPosition
defaults to the bottom.
There aren’t many elements where you can choose the color in this library yet--only the mute icon flag, which defaults to blue. It can be changed like this:
var agSettings = AgoraSettings()
agSettings.colors.micFlag = .red
By default, all the buttons are added (with the flip camera button available only on iOS and share screen available only on macOS). The set of buttons is created using the parameter enabledButtons. enabledButtons is an OptionSet, a type that is explained by NSHipster here.
If you want to show only the button to enable and disable the camera, you could set enabledButtons like this:
var agSettings = AgoraSettings()
agSettings.enabledButtons = .cameraButton
Or if you want to show the camera button, microphone, and share screen (macOS only):
var agSettings = AgoraSettings()
agSettings.enabledButtons = [
.cameraButton, .micButton, .screenShareButton
]
Then apply these settings using the AgoraVideoViewer initialiser:
let agoraView = AgoraVideoViewer(
connectionData: AgoraConnectionData(
appId: "cff7258e55c24d0f99bbf93d10367d5f",
appToken: nil
),
agoraSettings: agSettings
)
In the Advanced Customisation section below you see how to add your own custom button.
You can find a few parameters in AgoraSettings to change the way the video stream looks.
AgoraSettings.videoRenderMode lets you choose the AgoraVideoRenderMode for all the videos rendered on your local device, with a choice between fill, fit, and hidden. See Agora’s full documentation on AgoraVideoRenderMode [here](https://docs.agora.io/en/Video/API Reference/oc/Constants/AgoraVideoRenderMode.html).
AgoraSettings.videoConfiguration lets you add your own AgoraVideoEncoderConfiguration, with options of size, frame rate, bitrate, orientation and more. See Agora’s full documentation for AgoraVideoEncoderConfiguration [here](https://docs.agora.io/en/Video/API Reference/oc/Classes/AgoraVideoEncoderConfiguration.html).
You can also customise settings for dual-stream mode in several ways. By default, a session created with AgoraVideoViewer enables dual-stream mode and adopts a default low-bitrate parameter identical to the one shown in the below example. This can be customised by setting AgoraSettings.lowBitRateStream, like this:
var agSettings = AgoraSettings()
agSettings.lowBitrateParam = """
{ "che.video.lowBitRateStreamParameter": {
"width":160,"height":120,"frameRate":5,"bitRate":45
}}
"""
To better understand this setting, the following post talks in depth about dual-stream mode and the low-bitrate stream parameter:
With AgoraVideoViewers, all videos in the floating view collection are subscribed to the low-bitrate stream until they move to the main view or the layout changes to grid mode. The grid mode may also subscribe to the low-bitrate stream if the number of videos in the grid passes the threshold value, found in AgoraSettings.gridThresholdHighBitrate.
This value can be set to any integer. The default value is 4. This means that if 5 videos are shown in the grid view, then they are all subscribed to the low-bitrate feed.
Subscribing to the low-bitrate mode is important for a couple of reasons: It reduces the bandwidth used by all users subscribed to the feed, and it can help avoid using more minutes on your Agora account by reaching a higher streaming band.
The final parameter currently in this library is AgoraSettings.tokenURL. This property is an optional string that can be used to automatically fetch a new access token for the channel if the provided token has expired or is due to expire soon.
The functionality for requesting a new token expects the token to follow the URL scheme in the Golang token server found on GitHub: AgoraIO-Community/agora-token-service.
To set up this property, you can add to following code, replacing the localhost address with the URL path to your own token server:
var agSettings = AgoraSettings()
agSettings.tokenURL = "http://localhost:8080"
This feature can be triggered when calling the join
method on your AgoraVideoViewer, like this:
agoraView.join(
channel: "test", as: .broadcaster, fetchToken: true
)
Any attempt to fetch a new token only will only be triggered if a tokenURL is provided in the AgoraSettings for this AgoraVideoViewer object.
You may want to add your own buttons to the enabledButtons. You can use the delegate method extraButtons for that. To use extraButtons, you must set a delegate to the AgoraVideoViewer. Thereis a parameter for this in the AgoraVideoViewer initialiser.
An example implementation of extraButtons might look like this:
func extraButtons() -> [UIButton] {
let button = UIButton()
button.setImage(UIImage(
systemName: "bolt.fill",
withConfiguration: UIImage.SymbolConfiguration(scale: .large)
), for: .normal)
return [button]
}
This code adds a button with the SF Symbol bolt.fill
. It is added to the end of the button collection, adopting the same style as the other buttons in the collection.
You can add a bit of functionality to the button in the exact same way you would any other UIButton, by using addTarget:
extension ViewController: AgoraVideoViewerDelegate {
func extraButtons() -> [UIButton] {
let button = UIButton()
button.setImage(UIImage(
systemName: "bolt.fill",
withConfiguration: UIImage.SymbolConfiguration(scale: .large)
), for: .normal)
button.addTarget(
self,
action: #selector(self.clickedBolt),
for: .touchUpInside
)
return [button]
}
@objc func clickedBolt(sender: UIButton) {
print("zap!")
sender.isSelected.toggle()
sender.backgroundColor = sender.isSelected ?
.systemYellow : .systemGray
}
}
Since the buttons are being added to a set of existing buttons,I advise not to try to customise these buttons too much, to avoid the risk of the design falling apart. If you wish to add completely custom buttons, the best way is to add them on top of the AgoraVideoViewer object and set the enabledButtons setting to an empty set.
A choice between just two options may not satisfy all use cases, so you may want to create your own layout using the custom style option:
agoraView.style = .custom(
customFunction: myLayout(viewer:videoViews:count:)
)
func myLayout(
viewer: AgoraVideoViewer,
videoViews: EnumeratedSequence<[UInt: AgoraSingleVideoView]>,
count: Int
) {
// custom layout
}
The layout method can do anything you like, including adding all the AgoraSingleVideoView objects into a separate list, to be shown in your own CollectionView. Here is an example of how the body of myLayout can be used to create something like the grid style:
{% gist https://gist.github.com/maxxfrazer/410cf77b12178fd0ebf04bbe63e1bd50 %}
As well as displaying all the video views together, you may want to place and organise them independently, this is possible with AgoraSingleVideoView.
This is the same way that AgoraVideoViewer displays the boxes, but if you were to create the views yourself for a remote user, you can do so like this:
func rtcEngine(
_ engine: AgoraRtcEngineKit,
didJoinChannel channel: String,
withUid uid: UInt, elapsed: Int
) {
let remoteVideoView = AgoraSingleVideoView(
uid: uid, micColor: .systemBlue
)
engine.setupRemoteVideo(remoteVideoView.canvas)
// position the remoteVideoView in your scene...
}
I recommend keeping a way to reference each AgoraSingleVideoView by userID, in form of a dictionary for example: [UInt: AgoraSingleVideoView]
. That way you can utilise all the features of the view, such as a signal that the user microphone has been muted, or camera turned off.
Here's an example of those features, wich userVideoLookup
as a dictionary of userID to AgoraSingleVideoView:
extension ViewController: {
func rtcEngine(
_ engine: AgoraRtcEngineKit,
remoteVideoStateChangedOfUid uid: UInt,
state: AgoraVideoRemoteState,
reason: AgoraVideoRemoteStateReason,
elapsed: Int
) {
// Hide/Show the video feed
switch state {
case .decoding:
self.userVideoLookup[uid]?.videoMuted = false
case .stopped:
self.userVideoLookup[uid]?.videoMuted = true
default: break
}
}
func rtcEngine(
_ engine: AgoraRtcEngineKit,
remoteAudioStateChangedOfUid uid: UInt,
state: AgoraAudioRemoteState,
reason: AgoraAudioRemoteStateReason,
elapsed: Int
) {
// Hide/Show the muted microphone flag
if state == .stopped || state == .starting {
self.userVideoLookup[uid]?.audioMuted = state == .stopped
}
}
}
The documentation for AgoraSingleVideoView can be found here.
If there are features you think would be good to add to Agora UIKit for iOS or macOS that many users would benefit from, feel free to fork the repository and add a pull request. Or open an issue on the repository with the feature request.
The plan is to grow this library and have similar offerings across all supported platforms. There are already similar libraries for ReactNative and Android, so be sure to check those out.
For more information about building applications using Agora.io SDKs, take a look at the Agora Video Call Quickstart Guide and [Agora API Reference](https://docs.agora.io/en/Video/API Reference/oc/docs/headers/Agora-Objective-C-API-Overview.html?utm_source=medium&utm_medium=blog&utm_campaign=real-time-messaging-video-dynamic-channels).
I also invite you to join the Agoira.io Developer Slack community.