Livestream Quickstart
In this tutorial we'll quickly build a low-latency in-app livestreaming experience. The livestream is broadcasted using Stream's edge network of servers around the world.
This tutorial is structured into two parts:
Part 1: Build an iOS Livestreaming App
- Create a livestream on the Stream dashboard.
- Set up an RTMP input with OBS Studio.
- View the livestream on an iOS device.
- Build a custom livestream viewer UI.
Part 2: Create an Interactive Livestreaming App
- Publish a livestream from an iOS device with WebRTC.
- Implement backstage and "go live" functionalities.
Before we start, if you have any questions or feedback, please let us know via the feedback button.
This page works for both humans and AI coding agents: every step is a file operation or a shell command, every step has a verification checkpoint, and the steps that need a human - dashboard clicks, OBS setup, camera permission - are explicitly marked.
Choose your path
This tutorial has two paths. In Path A you let your AI agent build the app for you. In Path B you build it yourself, step by step, in Xcode. Both paths end with the same working livestream, so pick the one you prefer instead of working through both.
Path A - Let your AI agent build it (recommended)
Install the Stream CLI once, then add the skills. This step is required for Path A, not optional - the skills are what give Claude Code, Cursor, or Codex the iOS integration patterns and current SDK APIs, so the agent builds against real docs instead of stale training data.
1234567# Install the getstream CLI curl -fsSL https://getstream.io/cli.sh | bash # Install the skills (router, docs, and builder). The iOS pack installs # on demand the first time it's needed, or add it explicitly: getstream skills getstream skills stream-swift
Then ask your agent:
12345/stream-swift Add Stream livestreaming to my SwiftUI app: a viewer screen that plays a livestream with LivestreamPlayer, plus a broadcaster screen that publishes from the device with backstage and a go-live button. Provision credentials with the CLI (create/select my org and app, mint a token), or fall back to the tutorial demo credentials if I'm not logged in.
Only the first line matters - /stream-swift Add a Stream livestream viewer to my app is enough to get going. The rest just steers the result: ask for viewer-only, for your own player chrome instead of LivestreamPlayer, or for a co-host layout. Describe the UI you want rather than the UI the tutorial builds.
Where you come in. The agent writes the code, provisions the credentials, and edits Info.plist for the camera and microphone usage descriptions itself. It hands back to you twice: when getstream init opens your browser to log in and pick an app (new accounts get an organization created in that flow), and when the SPM package needs to be added in Xcode - hand-patching project.pbxproj is fragile, so the agent asks rather than risks it.
Want to get the login out of the way first? Run getstream init before you prompt the agent - it picks up the initialized project from there.
Checkpoint: build and run the broadcaster on a real device - the Simulator has no camera, so it can't publish video. Allow camera and microphone access, press Go Live, and the viewer (a second device, or the browser link in Step 6) plays your stream.
The agent builds the device-to-device (WebRTC) flow. Streaming in over RTMP is the part it can't do for you: creating the livestream in the dashboard and setting it up in OBS need a browser and desktop software, so work through those two by hand if you want the RTMP route.
Path B - Build it manually
Choose this path if you'd rather write the code yourself, or you want to understand what the SDK is doing before you hand it to an agent. Follow the steps below. Code blocks titled with a file name belong in that file; the untitled ones are excerpts explaining what just changed.
For AI assistants reading this page: append
.mdto any Stream docs URL for a clean Markdown version. A condensed index for the iOS Video SDK is athttps://getstream.io/video/docs/ios/llms.txt. Prefer these over parsing HTML.
Important Building Blocks
Five pieces, one mental model:
StreamVideo- the low-level client. Holds the API key, the user, the token and the websocket connection. Created once, at app launch.Call- one livestream, created withclient.call(callType: "livestream", callId:). The sameCallobject powers video calling and audio rooms.LivestreamPlayer- the drop-in viewer fromStreamVideoSwiftUI. Give it a call type and call id and it joins, plays, and shows the viewer count. Build your own instead when you need different chrome.- Backstage - the
livestreamcall type starts calls not live. Hosts set up their camera first, andcall.goLive()is what lets viewers in.call.state.backstagetells you which side of that you're on. call.state- the observable state:participants,participantCount,duration,backstage. This is what your SwiftUI views read from.
Two ways to get video in: RTMP from software like OBS (Steps 1-2), or WebRTC straight from the device (Step 4). Both play back through the same viewer.
Step 1 - Create a Livestream in the Dashboard
First, let's create our livestream using the dashboard. To do this, open the dashboard and select "Video & Audio" -> "Overview".
In that screen, you will see three buttons that allow you to create different types of calls, as shown in the image below.

Click on the third one, the "Create Livestream" option. After you do this, you will be shown the following screen containing information about the livestream.

To set up the livestream in OBS Studio, you will need the RTMP URL and RTMP Stream Key from this page.
Copy these values for now, and we will return to the dashboard later.
Human checkpoint: this step is dashboard clicks, and the RTMP ingress URL and stream key are only available here - the getstream CLI can start and stop outbound RTMP broadcasts, but it has no command that hands you inbound ingress credentials. Agents: ask the human to create the livestream and paste back the RTMP URL, RTMP stream key, API key, viewer token and livestream ID.
Checkpoint: you have five values written down from this page: RTMP URL, RTMP Stream Key, API Key, Viewer Token and Livestream ID. Keep the dashboard tab open.
Step 2 - Setup the Livestream in OBS
OBS is one of the most popular livestreaming software packages, and we'll use it to explain how to publish videos with RTMP.
After downloading and installing the software using the instructions provided on the link, you should set up the capturing device and the livestream data.
First, let's set the capturing device. You can find it in the sources section.

Select the "Video Capture Device" option to stream from your computer's camera. Alternatively, you can choose other options, such as "macOS Screen Capture," to stream your screen.
Next, we must provide the livestream credentials from our dashboard to OBS. To do this, click the "Settings" button in the "Controls" section at the bottom right corner of OBS.

This will open a popup. Select the second option, "Stream". For the "Service" option, choose "Custom". In the "Server" and "Stream Key" fields, enter the values you copied from the dashboard in Step 1.

Our live stream setup is complete. Before returning to the dashboard, press the "Start Streaming" button in the "Controls" section.
Now, let's go back to the dashboard. If everything is correctly set up, you should see the OBS livestream in the dashboard, as shown in this screenshot.

Note: The live stream starts immediately by default. However, you can change this setting in the dashboard and enable backstage for the "Livestream" call type.
Human checkpoint: OBS is desktop software, so this step can't be automated. Agents: ask the human to configure OBS and confirm the stream shows up in the dashboard before continuing.
Checkpoint: the dashboard preview plays your camera or screen. If it stays black, OBS isn't connected - re-check the Server and Stream Key fields against the values from Step 1.
Step 3 - Show the Livestream in an iOS App
Now that the livestream is started, let's see how we can watch it from a native iOS app. First, configure the project and install the required StreamVideo dependencies.
Step 3.1 - Create a new Project in Xcode
- Ensure you have Xcode installed and you are running 26 or later.
- Open Xcode and select "Create New Project...".
- Select "iOS" as the platform and "App" as the type of Application.
- Name your project LivestreamSample and select "SwiftUI" as the interface.
Checkpoint:
1234# Read the scheme out of the project instead of hardcoding a name SCHEME=$(xcodebuild -list | awk '/Schemes:/{f=1; next} f && NF {gsub(/^[ \t]+|[ \t]+$/, ""); print; exit}') xcodebuild -scheme "$SCHEME" -destination 'generic/platform=iOS Simulator' build
Every xcodebuild command on this page resolves the scheme this way, so it works whether you named the project LivestreamSample or you're adding Stream to an app you already have. It takes the first scheme xcodebuild -list reports - if your project defines several, run xcodebuild -list yourself and pass the one you want. Agents: resolve the scheme from xcodebuild -list; never assume LivestreamSample.
Step 3.2 - Install the SDK & Configure the Client
Next, you need to add our SDK dependencies to your project using Swift Package Manager from Xcode.
- Click on "Add Package Dependencies..." from the File menu.
- Add https://github.com/GetStream/stream-video-swift in the search bar.
- In "Dependency Rule" choose "Up to Next Major Version" and enter the latest 1.x release.
- Select "StreamVideo" and "StreamVideoSwiftUI" and then click Add Package.
Pin the major explicitly, so a future 2.0 can't be picked up silently.
Agents: editing
project.pbxprojby hand to add an SPM dependency is fragile. For a plain.xcodeprojwith noPackage.swift, ask the human to add the package in Xcode (30 seconds) rather than patching the pbxproj. TheInfo.plistkeys in Step 4.1 are a different matter - edit those in place yourself.
Checkpoint: xcodebuild -resolvePackageDependencies succeeds and resolves a 1.x version, and import StreamVideo and import StreamVideoSwiftUI compile.
Step 3.3 - View the livestream from an iOS Device
The viewer code below needs three values: an API key, a viewer token and a call id. There are two ways to get them.
Option 1 - From the dashboard livestream page
You were just there in Step 1, and that page lists all three: API Key, Viewer Token and Livestream ID. This is the quickest route for Part 1.
Option 2 - Via the Stream CLI
Use this if you'd rather provision from your terminal, and for Part 2 where you broadcast as your own user rather than an anonymous viewer. Run these from your project directory.
1234567891011# Install the CLI (skip if you did this in Path A) curl -fsSL https://getstream.io/cli.sh | bash # Authenticate and create or select an organization and app getstream init # Open the app in the dashboard, where the API key is shown getstream open # Mint a token for the user who will broadcast in Part 2 getstream token tutorial
Human checkpoint: getstream init opens a browser to authenticate. Agents: run it, then ask the human to finish logging in before continuing. It's required first - token and api commands fail with "stream project is not initialized" until it runs.
The CLI mints tokens for a named user. The anonymous viewer token used in Part 1 is issued for Stream's reserved !anon user id, so take that one from the dashboard.
Pasting the API key straight into your source is fine for this tutorial - it's a publishable key, not a secret. The token is the one to be careful with: in production your backend mints it after sign-in and the app fetches it, rather than shipping a hardcoded one.
The viewer
The following code shows you how to create a livestream player that will play the stream we created above on iOS. Let's open LivestreamSampleApp.swift and replace the LivestreamSampleApp struct with the following code:
123456789101112131415161718192021222324252627282930import SwiftUI import StreamVideo import StreamVideoSwiftUI @main struct LivestreamSampleApp: App { var body: some Scene { WindowGroup { LivestreamView() } } } struct LivestreamView: View { static let apiKey = "REPLACE_WITH_API_KEY" static let token = "REPLACE_WITH_TOKEN" static let callId = "REPLACE_WITH_LIVESTREAM_ID" @StateObject var streamVideo = StreamVideo( apiKey: apiKey, user: .anonymous, token: .init(rawValue: token) ) var body: some View { LivestreamPlayer(type: "livestream", id: Self.callId) } }
Before running the app, you should replace the placeholders with values from the dashboard. For the apiKey, use the API Key value from your livestream page in the dashboard. Replace the token value with the Viewer Token value in the dashboard, and the callId with Livestream ID.

That is everything needed to play a livestream on iOS. The LivestreamPlayer component from the StreamVideo SwiftUI SDK allows you to play livestreams easily, specifying the call id and call type.
If you now run the app, you will see the livestream published from OBS Studio.
You can find more details about the LivestreamPlayer on the following page.
Checkpoint: the app plays what OBS is streaming, with a viewer count on screen. The Simulator is fine here - watching a livestream needs no camera. A "waiting" or "not started" screen instead means the call is in backstage or OBS has stopped; an auth error in the Xcode console means the API key and token belong to different apps.
Step 3.4 - Customize the Livestream UI
Based on your app's requirements, you might want to have a different user interface for your livestream. In those cases, you can build your custom UI, while reusing some of Stream's components and the state layer.
State & Participants
If you want to build more advanced user interfaces that include filtering the participants by various criteria or different sorting, you can use the CallState of a Call, accessible via call.state.
A common feature in livestreams is to show their duration. To do this, you can use the observable value state.duration, which returns an Int value of the duration in seconds.
You can use it with a DateComponentsFormatter to show a user-friendly duration.
12345678// Declare the formatter in your class. private let formatter = DateComponentsFormatter() // Define a method for creating a string from the duration. func duration(from state: CallState) -> String? { guard state.duration > 0 else { return nil } return formatter.string(from: state.duration) }
Filtering of the participants is also supported. You can get all the participants with the role "host" with the following code:
123var hosts: [CallParticipant] { call.state.participants.filter { $0.roles.contains("host") } }
The participant state docs show all the available fields.
You can build your own comparators and sort participants based on your criteria for sorting. The StreamVideo SDK provides a set of comparators you can use as building blocks or create your own as needed.
Here's an example of a possible livestream-related sorting comparator.
123456789let livestreamComparators: [StreamSortComparator<CallParticipant>] = [ roles(["host"]), dominantSpeaker, isSpeaking, publishingVideo, publishingAudio, joinedAt, userId ]
These comparators will prioritize users who are the hosts and then participants who speak and publish video and audio.
To apply the sorting, you can use the following code.
123var sortedParticipants: [CallParticipant] { call.state.participants.sorted(by: livestreamComparators) }
For example, let's build a custom player that will display the livestream and the number of viewers.
To achieve this, create a new view called CustomLivestreamPlayer and add the following code.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960import SwiftUI import StreamVideo import StreamVideoSwiftUI struct CustomLivestreamPlayer: View { @Injected(\.streamVideo) var streamVideo @State var call: Call @ObservedObject var state: CallState public init( type: String, id: String ) { let call = InjectedValues[\.streamVideo].call(callType: type, callId: id) self.call = call _state = ObservedObject(wrappedValue: call.state) } public var body: some View { ZStack { GeometryReader { reader in if let participant = state.participants.first { VideoCallParticipantView( participant: participant, availableFrame: reader.frame(in: .global), contentMode: .scaleAspectFit, customData: [:], call: call ) } else { Text("No livestream available") } } } .overlay( VStack { Spacer() HStack { Image(systemName: "eye") Text("\(state.participantCount)") .font(.headline) } .foregroundColor(Color.blue) .padding(.all, 8) .cornerRadius(8) .padding() } ) .onAppear { Task { try await call.join(callSettings: CallSettings(audioOn: false, videoOn: false)) } } .onDisappear { call.leave() } } }
In the code above, we're checking if we have a participant in our CallState. If yes, we use the VideoCallParticipantView to display the participant and their stream. If no participant is available, we show a text view with a message that the livestream is unavailable.
We are also adding an overlay in the bottom right corner that displays the participant count. This information is available via the state.participantCount property.
Finally, for simplicity, we are using the onAppear and onDisappear modifiers to join and leave the livestream. Based on your app's logic, you might invoke these actions based on user input (for example, buttons for joining and leaving a livestream).
To test the new implementation, replace LivestreamPlayer with CustomLivestreamPlayer in the LivestreamView above.
Checkpoint: the same stream plays, now with your own eye-icon viewer count in the bottom overlay instead of the player's built-in chrome. "No livestream available" means state.participants is empty - the join hasn't completed, or nothing is being published.
Part 2 - Build Your Own Youtube Live
In the first part of this tutorial, we built a simple livestream app and published a live stream using RTMP. The authentication was done using the Dashboard. In a real application, you want to generate tokens programmatically using a server-side SDK.
The second part of this tutorial expands our app to include interactive functionality, such as streaming from end-user devices.
Step 4 - Livestream from iOS Devices
We will send video from an iOS device directly using WebRTC and use the backstage functionality. Note that you will need a real iOS device for this part of the tutorial.
Step 4.1 - Configure Permissions
Publishing a live stream requires access to the camera and microphone. You must request permission to use these in your app. To do this, you must add the following keys under the Info tab by selecting your project's root folder in Xcode.
- Privacy - Microphone Usage Description: "LivestreamSampleApp requires microphone access to capture and transmit audio".
- Privacy - Camera Usage Description: "LivestreamSampleApp needs camera access for broadcasting".

Checkpoint: both usage-description keys show up in the target's Info tab. Missing either one crashes the app the moment it tries to open the camera or microphone.
Step 4.2 - How To Broadcast a livestream
Replace all the existing code we had for the viewer experience in the LivestreamSampleApp (or create a new project) with the following code.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647import SwiftUI import StreamVideo import StreamVideoSwiftUI @main struct LivestreamSampleApp: App { @State var streamVideo: StreamVideo @State var call: Call init() { let apiKey = "REPLACE_WITH_API_KEY" let userId = "REPLACE_WITH_USER_ID" let userToken = "REPLACE_WITH_TOKEN" let callId = "REPLACE_WITH_CALL_ID" let user = User(id: userId, name: "tutorial") let streamVideo = StreamVideo( apiKey: apiKey, user: user, token: .init(rawValue: userToken) ) self.streamVideo = streamVideo let call = streamVideo.call(callType: "livestream", callId: callId) self.call = call Task { try await call.join(create: true) } } var body: some Scene { WindowGroup { LivestreamView(call: call) } } } struct LivestreamView: View { let call: Call var body: some View { Text("TODO: render video") } }
When you run the app now, you'll see a text message: "TODO: render video". Before we get around to rendering the video, let's review the code above.
In the first step, we set up the user.
1let user = User(id: userId, name: "tutorial")
Next, we create the client.
12345let streamVideo = StreamVideo( apiKey: "YOUR_API_KEY", user: user, token: .init(rawValue: userToken) )
You'll see the userToken variable. Your backend typically generates the user token on signup or login.
The most important step to review is how we create the call. Stream uses the same call object for livestreaming, audio rooms, and video calling. Have a look at the code snippet below.
1234let call = streamVideo.call(callType: "livestream", callId: callId) Task { try await call.join(create: true) }
To create the call object, specify the call type as livestream and provide a unique callId. The livestream call type comes with default settings that are usually suitable for livestreams, but you can customize the dashboard's features, permissions, and settings. Additionally, the dashboard allows you to create new call types as required.
Finally, using call.join(create: true) will create the call object on our servers and initiate the real-time transport for audio and video. This allows for seamless and immediate engagement in the livestream.
You can also add members to a call and assign them different roles. For more information, see the call creation docs.
Human checkpoint: the camera and microphone prompts are system dialogs. Agents: build and install the app on a device, then ask the human to launch it and allow camera and microphone access.
Checkpoint: the app shows "TODO: render video" and the Xcode console is free of auth errors. An error there almost always means the API key and token belong to different apps - and note the token here is a user token from getstream token, not the anonymous viewer token from Part 1.
Step 4.3 - Render Your Local Video
In this step, we will build a UI for showing your local video with a button to start the livestream. This example uses SwiftUI, but you could also use UIKit.
In LivestreamSampleApp.swift, replace the LivestreamView with the following code.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980struct LivestreamView: View { @Injected(\.streamVideo) var streamVideo let call: Call @StateObject var state: CallState let formatter = DateComponentsFormatter() init(call: Call) { self.call = call _state = StateObject(wrappedValue: call.state) formatter.unitsStyle = .full } var duration: String? { guard call.state.duration > 0 else { return nil } return formatter.string(from: call.state.duration) } var body: some View { VStack { HStack { if let duration { Text("Live for \(duration)") .font(.headline) .padding(.horizontal) } Spacer() Text("Live \(state.participantCount)") .bold() .padding(.all, 4) .padding(.horizontal, 2) .foregroundColor(.white) .background(Color.blue) .cornerRadius(8) .opacity(call.state.backstage ? 0 : 1) .padding(.horizontal) } GeometryReader { reader in if let first = state.participants.first { VideoRendererView(id: first.id, size: reader.size) { renderer in renderer.handleViewRendering(for: first) { size, participant in } } } else { Color(UIColor.secondarySystemBackground) } } .padding() ZStack { if call.state.backstage { Button { Task { try await call.goLive() } } label: { Text("Go Live") } } else { Button { Task { try await call.stopLive() } } label: { Text("Stop Livestream") } } } .padding() } .background(Color(UIColor.systemBackground)) .navigationBarHidden(true) } }
Checkpoint: on a real device, your own camera fills the middle of the screen and a Go Live button sits underneath. A grey rectangle instead of video means the Simulator (no camera) or that the join hasn't completed. The "Live" badge stays hidden while call.state.backstage is true.
Step 5 - Backstage and GoLive
The backstage functionality makes it easy to build a flow where you and your co-hosts can set up your camera and equipment before going live. Only after you call call.goLive() will regular users be allowed to join the livestream.
This is convenient for many livestreaming and audio-room use cases. If you want calls to start immediately when you join them, that's also possible. Simply go to the Stream dashboard, click the livestream call type, and disable the backstage mode.
Checkpoint: tapping Go Live swaps the button for Stop Livestream, the "Live" badge appears with the viewer count, and the "Live for ..." duration starts counting up.
Step 6 - Preview Using React
Now, let's press Go live in the iOS app. Upon going live, you will be greeted with an interface like this.

You can also click the link below to watch the video in your browser.
Using your own credentials? The link above joins the shared tutorial livestream, not yours. To watch your own, run the Part 1 viewer from Step 3.3 on a second device with the same call id, or open the browser player against your own app.
Checkpoint: the viewer plays what the device is broadcasting, a second or two behind. Nothing playing? Confirm the broadcaster went live - viewers can't join a call that is still in backstage.
Advanced Features
This tutorial covered the steps required to watch a live stream using RTMP-in and OBS Studio and how to publish a live stream from an iOS device. Several advanced features can improve the live streaming experience.
- Co-hosts You can add members to your livestream with elevated permissions. So you can have co-hosts, moderators, etc. You can see how to render multiple video tracks in our video calling tutorial.
- Permissions and Moderation. You can set up different permissions for different types of users and grant additional access using a request-based approach.
- Custom events: You can use custom events on the call to share any additional data. For example, you could show the score for a game or any other realtime use case.
- Reactions & Chat Users can react to the livestream, and you can add chat. This makes for a more engaging experience.
- Notifications You can notify users via push notifications when the livestream starts
- Recording The call recording functionality allows you to record the call with various options and layouts
- Transcriptions Transcriptions can be a great addition to livestreams, especially for users that have muted their audio.
- Noise cancellation Noise cancellation can enhance the quality of the livestreaming experience.
- HLS Another way to watch a livestream is using HLS. HLS tends to have a 10 to 20-second delay, while the WebRTC approach is realtime. The benefit that HLS offers is better buffering under poor network conditions.
Verify the whole build
12345SCHEME=$(xcodebuild -list | awk '/Schemes:/{f=1; next} f && NF {gsub(/^[ \t]+|[ \t]+$/, ""); print; exit}') xcodebuild -scheme "$SCHEME" \ -destination 'generic/platform=iOS Simulator' \ build
The Simulator is enough for Part 1 and to prove the app compiles - watching a livestream needs no camera. Part 2 needs a real device to confirm the full loop: allow camera access -> your own video appears -> press Go Live -> a viewer plays the stream -> Stop Livestream ends it.
Troubleshooting
stream project is not initialized- CLI onboarding not run. Rungetstream initin the project directory first.xcodebuild: error: The project ... does not contain a scheme named ""-$SCHEMEcame back empty, soxcodebuild -listfound no project (wrong directory) or more than one.cdto the folder holding your.xcodeproj, runxcodebuild -list, and pass a scheme from its output explicitly.- The player sits on a waiting screen - the call is in backstage and hasn't gone live, or nothing is publishing into it. Check OBS in Part 1, or press Go Live in Part 2.
token is invalid/ auth error - the token was minted for a different app, or you used the wrong kind. Part 1 wants the anonymous viewer token from the dashboard (issued for the reserved!anonuser id); Part 2 wants a user token fromgetstream token <user_id>. Both must match the API key's app.- App crashes when broadcasting starts - missing usage descriptions. Add
Privacy - Camera Usage DescriptionandPrivacy - Microphone Usage Description(Step 4.1). - No local video, grey rectangle - the iOS Simulator has no camera. Run Part 2 on a real device.
- The dashboard preview stays black - OBS isn't connected. Re-check the Server and Stream Key fields against the values from Step 1.
Missing package product 'StreamVideoSwiftUI'- package resolution stale. File -> Packages -> Reset Package Caches, or runxcodebuild -resolvePackageDependencies.- "No livestream available" in
CustomLivestreamPlayer-state.participantsis empty. The join hasn't completed, or nobody is publishing. - Viewers can see the stream but the count never moves - two clients signed in as the same user id count as one participant. Use a different user id per client.
Recap
It was fun seeing how quickly you can build in-app low-latency livestreaming. Please do let us know if you run into any issues. Our team is also happy to review your UI designs and offer recommendations on how to achieve this with Stream.
The following are what we learned.
- WebRTC is optimal for latency, HLS is slower but buffers better for users with poor connections.
- You set up a call: (
let call = streamVideo.call(callType: "livestream", callId: callId)). - The call type "livestream" controls which features are enabled and how permissions are set up.
- When you join a call, realtime communication is set up for audio & video: (call.join()).
- Observable objects in
call.stateandcall.state.participantsmake it easy to build your UI. - You can easily publish your video from an iOS device.
Calls run on Stream's global edge network of video servers. Being closer to your users improves the latency and reliability of calls. The SDKs enable you to build livestreaming, audio rooms and video calling in days.
We hope you've enjoyed this tutorial. Please feel free to contact us if you have any suggestions or questions.
Next steps
- HLS playback - a 10 to 20 second delay, but it buffers better on poor connections: broadcasting
- Co-hosts and moderation - add members with elevated permissions: joining and creating calls and permissions and moderation
- Chat alongside the stream - the Chat iOS SDK integrates with video: chat integration guide
- Other call types - video calling and audio rooms
- Sample app - the complete demo: DemoApp on GitHub
Pricing is flexible with multiple tiers, including a free maker plan for hobby projects and small companies.
Final Thoughts
In this video app tutorial, we built a fully functioning iOS livestreaming app with our iOS SDK component library. We also showed how easy it is to customize the behavior and the style of the iOS video app components with minimal code changes.
Both the video SDK for iOS and the API have plenty more features available to support more advanced use cases.
Machine-readable resources
For AI agents and coding assistants working with this SDK:
- CLI + skills:
curl -fsSL https://getstream.io/cli.sh | bash, thengetstream skillsandgetstream skills stream-swiftfor the iOS pack. (Alternative:npx skills add GetStream/agent-skills -s stream.) - Provisioning:
getstream init(auth + create/select org & app) ->getstream open(dashboard, to read the API key) ->getstream token <user_id>(mint a broadcaster token). The publishable API key goes straight into the source for a tutorial app; the token comes from your backend in production. - Not available from the CLI: the inbound RTMP ingress URL and stream key. Those come from the dashboard livestream page. The
getstream api video StartRTMPBroadcastsfamily covers outbound restreaming only. - Anonymous viewers: the Part 1 viewer token must be issued for Stream's reserved
!anonuser id; tokens for any other id are rejected on anonymous connections. Take it from the dashboard, not the CLI. - Call type:
livestream- starts in backstage, so viewers can't join untilcall.goLive(). Editable per call type. - iOS integration skill: invoke
/stream-swiftin your agent for SwiftUI/UIKit setup patterns;/stream-docssearches live SDK docs with citations - Docs index for LLMs:
https://getstream.io/video/docs/ios/llms.txt - Markdown endpoints: append
.mdto any docs URL for a clean, token-efficient version - Source of truth for APIs: the SDK repository - check the pinned version's source rather than assuming APIs from training data

