In this tutorial, we'll build an iOS app for livestream events where attendees and speakers can interact in a virtual conference room using Stream Chat, to deliver fully featured chat components, and Dolby.io, to provide quality live-streamed audio and video for large audiences. By the end, we'll have an application similar to the screenshots below. As a bonus, it's compatible with both light and dark mode.
This post is still useful, but out of date. Stream now offers a Live Video Streaming API!
If you get lost during this tutorial, you can check the completed project in this GitHub repo. Let's get started with our live event app development!
What is Stream Chat?
Build real-time chat in less time. Rapidly ship in-app messaging with our highly reliable chat infrastructure. Drive in-app conversion, engagement, and retention with the Stream Chat messaging platform API & SDKs.
What is Dolby.io's Client SDK?
Dolby Interactivity APIs provide a platform for unified communications and collaboration. In-flow communications refers to the combination of voice, video, and messaging integrated into your application in a way that is cohesive for your end-users. This is in contrast to out-of-app communications where users must stop using your application and instead turn to third-party tools.
Requirements
Set up project
Create the Xcode project
First, we open Xcode and create a Single View App project.
And make sure to select 'Storyboard' for the User Interface.
Install dependencies
To install the Stream Chat and Dolby.io's Client SDK dependencies, we'll use CocoaPods. If you prefer Carthage, both frameworks support it as well.
In the folder where you saved the project, run pod init
and add StreamChat
and VoxeetUXKit
to the Podfile
. It should look similar to this:
# Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'OnlineEventApp' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for OnlineEventApp pod 'StreamChat', '~> 2.2' pod 'VoxeetUXKit', '~> 1.3' end
After you do that, run pod install
, wait a bit for it to finish, and open the project via the .xcworkspace
that CocoaPods created.
Configure the Stream Chat dashboard
Sign up for Chat at GetStream.io, create the application, and make sure to select development instead of production.
To make things simple for now, let's disable both auth checks and permission checks. Make sure to hit save. When your app is in production, you should keep these enabled.
You can see the documentation about authentication here and permissions here.
Now, save your Stream credentials, as we'll need them to power the chat in the app. Since we disabled auth and permissions, we'll only really need the key for now. Still, in production, you'll use the secret in your backend to implement proper authentication to issue user tokens for Stream Chat, so users can interact with your app securely.
As you can see, I've blacked out my keys. You should make sure to keep your credentials safe.
Configure the Dolby.io dashboard
Configuring the Dolby.io dashboard is simpler. Just create an account there, and it should already set up an initial application for you.
Now, save your credentials, as we'll need them to power the audio and video streaming in the app. As with the Stream credentials, you use these for development. In production, you'll need to set up proper authentication. It's described in detail here.
Configure Stream Chat and Dolby.io's SDKs
The first step with code is to configure the Stream and Dolby SDK with the credentials from the dashboards. Open the AppDelegate.swift
file and modify it, so it looks similar to this:
import UIKit import StreamChatClient import VoxeetSDK import VoxeetUXKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { ... func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. Client.configureShared(.init(apiKey: "7[redacted]2", logOptions: .info)) VoxeetSDK.shared.initialize(consumerKey: "ZTB[redacted]mt0aA==", consumerSecret: "ND[redacted]ZH[redacted]c[redacted]=") VoxeetUXKit.shared.initialize() return true } ... }
That code initializes the Dolby.io and Stream Chat SDKs with credentials you got in the previous two steps.
Create the Join Screen
Let's start building the "Join" screen. This screen consists of two UIButton
instances. One to join as an attendee, and the other to participate as the speaker. That is an oversimplification to make this tutorial short and get to the chat, audio, and video features faster. In your complete app, you'll need proper registration, database, and all that. For this tutorial, the screen will look similar to the screenshot below.
Go to the storyboard, select the default view controller, and click Editor > Embed In > Navigation Controller
. That will place it under a navigation controller, which we'll use to navigate to the conference room screen.
Make sure to rename ViewController
to JoinViewController
, so you don't get confused later. You can do this easily by right-clicking on ViewController
in ViewController.swift
and selecting refactor
.
To make things simple, let's leave the storyboard like this and use only code from now on. To set up the two buttons, we need the following code in JoinViewController.swift
:
import UIKit class JoinViewController: UIViewController { let attendeeButton = UIButton() let speakerButton = UIButton() override func viewDidLoad() { super.viewDidLoad() title = "Join" setupViews() setupConstraints() setupHandlers() } }
That code sets up the views, the constraints, and the handlers we need. Let's start by extending JoinViewController
to define setupViews
:
import Foundation extension JoinViewController { func setupViews() { setupAttendeeButton() setupSpeakerButton() } func setupAttendeeButton() { attendeeButton.translatesAutoresizingMaskIntoConstraints = false attendeeButton.setTitleColor(.systemBlue, for: .normal) attendeeButton.setTitle("Attendee 🙋♂️", for: .normal) attendeeButton.titleLabel?.font = .systemFont(ofSize: 32) view.addSubview(attendeeButton) } func setupSpeakerButton() { speakerButton.translatesAutoresizingMaskIntoConstraints = false speakerButton.setTitleColor(.systemBlue, for: .normal) speakerButton.setTitle("Speaker 👩💻", for: .normal) speakerButton.titleLabel?.font = .systemFont(ofSize: 32) view.addSubview(speakerButton) } }
That code will create the buttons and add them to the controller's view. Next, we need to define constraints between the three. Let's do this by extending JoinViewController
to define setupConstraints
:
extension JoinViewController { func setupConstraints() { view.addConstraints([ attendeeButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), attendeeButton.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor, constant: -100), speakerButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), speakerButton.centerYAnchor.constraint(equalTo: attendeeButton.centerYAnchor, constant: 100) ]) } }
That code will ensure the attendeeButton
stays in the center of the screen and the speakerButton
below it. Now we need to set up the handler for when the user presses the buttons. Let's do this again by extending the controller to define setupHandlers
:
import StreamChat extension JoinViewController { func setupHandlers() { attendeeButton.addTarget(self, action: #selector(handleAttendeeButtonPress), for: .touchUpInside) speakerButton.addTarget(self, action: #selector(handleSpeakerButtonPress), for: .touchUpInside) } @objc func handleAttendeeButtonPress() { let roomVC = RoomViewController() roomVC.setupAttendee() navigationController?.pushViewController(roomVC, animated: true) } @objc func handleSpeakerButtonPress() { let roomVC = RoomViewController() roomVC.setupSpeaker() navigationController?.pushViewController(roomVC, animated: true) } }
That code will make it so, when the user presses the button, a RoomViewController
is created and set up for the speaker or an attendee, depending on which button the user pressed. We'll create RoomViewController
in the next step.
Create the Conference Room Screen
Now, let's create the screen where the the attendees and speaker will talk via chat and where they may begin a video call to watch or transmit the event stream. We'll start by defining RoomViewController. It will look similar to the screenshots below.
The first step is to create a RoomViewController.swift file and paste the code below.
import StreamChat import StreamChatClient class RoomViewController: ChatViewController { lazy var channel = Client.shared.channel( type: .messaging, id: "conference_room_id", extraData: ChannelExtraData(name: "Conference Room", imageURL: nil) ) let attendee = User(id: .random()) let speaker = User( id: "speaker", extraData: UserExtraData( name: "Speaker", avatarURL: URL(string: "https://raw.githubusercontent.com/GetStream/online-event-app-ios/main/meta/speaker.png") ) ) override func viewDidLoad() { super.viewDidLoad() setupViews() setupHandlers() } }
That code defines a subclass of ChatViewController, which provides most of the chat behavior and UI we need. It also defines the attendee and speaker User
objects and a Channel
object. These objects will be used to interact with the Stream API. Additionally, we're generating a random id for the attendee using the String extension below.
import Foundation extension String { static func random(length: Int = 10) -> String { let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" return String((0..<length).map{ _ in letters.randomElement()! }) } }
On viewDidLoad
, we also call setupViews
and setupHandlers
to set up the views and handlers needed. We'll define those functions next.
But, let's first define the setupAttendee
function that sets the current Stream Chat user as an attendee, and the setupSpeaker
function that sets it as the speaker.
import StreamChatClient extension RoomViewController { func setupAttendee() { Client.shared.set(user: attendee, token: .development) self.presenter = .init(channel: channel) } func setupSpeaker() { Client.shared.set(user: speaker, token: .development) self.presenter = .init(channel: channel) } }
Now we define setupViews
to set up the views we need.
import UIKit extension RoomViewController { func setupViews() { setupCallButton() } func setupCallButton() { let button = UIBarButtonItem() button.image = UIImage(systemName: "phone") navigationItem.rightBarButtonItem = button } }
Those functions will display a button which starts a call. For it to work, we'll need to define setupHandlers
as well.
import Foundation extension RoomViewController { func setupHandlers() { setupCallButtonHandler() } func setupCallButtonHandler() { navigationItem.rightBarButtonItem?.target = self navigationItem.rightBarButtonItem?.action = #selector(callButtonPressed) } @objc func callButtonPressed() { startCall() } }
Those functions set callButtonPressed
as the function to be called when the call button is pressed, which in turn calls startCall
, which we define next.
import VoxeetSDK import VoxeetUXKit extension RoomViewController { func startCall() { let options = VTConferenceOptions() options.alias = "conference_room_id" VoxeetSDK.shared.conference.create(options: options, success: { conf in VoxeetSDK.shared.conference.join(conference: conf) }, fail: { error in print(error) }) } }
Finally, that function uses the Dolby.io SDK to start a conference call.
Configure usage descriptions
If you run the app now, you'll be able to chat, but pressing the call button will cause the application to crash. That happens because we need to configure the usage descriptions for microphone and video in the Info.plist
file. To do this, just open Info.plist
and set the NSMicrophoneUsageDescription
and NSCameraUsageDescription
keys as pictured below.
Finally, we open the app in two devices, and, from the chat, we can start a call.
Live Event App Project Complete
Congratulations! You've built the basis of a functioning Online Event app with Stream Chat and Dolby.io. I encourage you to browse through Stream Chat's docs, Dolby.io's docs, and experiment with the project you just built. And check out our UX best practices for livesteam chat. Good luck on your Live Event app development!
If you also need a web version, there's a separate tutorial on how to build a live stream chat with Wowza and Stream.