Recording
Call's recording features
In some cases, you want to be able to record a meeting and share the recording with the participants later on. The StreamVideo SDK has support for this use-case.
Recording requires the appropriate permissions to be configured for your call type in the Stream Dashboard. Users must have the start-record-call and stop-record-call capabilities to control recording.
In order to support this feature, you will need to use the Call's recording features, available after you join a call.
Recording state
The recording state of the call is available via the CallViewModel's recordingState published property. It's an enum, which has the following values:
noRecording- default value, there's no recording on the call.requested- recording was requested by the current user.recording- recording is in progress.
If you are not using our CallViewModel, you can also listen to this state via the Call's property recordingState.
Start a recording
To start a recording, you need to call the startRecording method of the call:
func startRecording() {
Task {
try await call.startRecording()
}
}This will change the current recording state of the call to requested. Since it takes several seconds before the recording is started, it's best to handle this state by presenting a progress indicator to provide a better user experience.
After the recording is started, the recordingState changes to recording.
Stop a recording
To stop a recording, you need to call the stopRecording method of the Call:
func stopRecording() {
Task {
try await call.stopRecording()
}
}This will change the current recording state of the call to noRecording.
Recording events
You can listen to the recording events and show visual indications to the users based on these events, by subscribing to the async stream of the recordingEvents:
func subscribeToRecordingEvents() {
Task {
for await event in call.subscribe() {
switch event {
case .typeCallRecordingStartedEvent(let recordingStartedEvent):
log.debug("received an event \(recordingStartedEvent)")
/* handle recording event */
case .typeCallRecordingStoppedEvent(let recordingStoppedEvent):
log.debug("received an event \(recordingStoppedEvent)")
/* handle recording event */
default:
break
}
}
}
}Search recordings
You can search for recordings in a video call, using the Call's listRecordings method:
func loadRecordings() {
Task {
self.recordings = try await call.listRecordings()
}
}This will return a list of recordings, that contains information about the filename, URL, as well as the start and end time. You can use the URL to present the recording in a player. Here's an example in SwiftUI:
import SwiftUI
import StreamVideo
import AVKit
struct PlayerView: View {
let recording: CallRecording
var body: some View {
Group {
if let url = URL(string: recording.url) {
VideoPlayer(player: AVPlayer(url: url))
} else {
Text("Video can't be loaded")
}
}
}
}Delete a recording
Recordings are stored per call session, so deleting one requires both the id of the session it belongs to and the recording's filename. The filename comes from the CallRecording objects returned by listRecordings, while the session id of the ongoing session is available on the call state:
@MainActor
func deleteRecording(_ recording: CallRecording) {
guard let callSessionId = call.state.session?.id else { return }
Task {
try await call.deleteRecording(
callSessionId: callSessionId,
filename: recording.filename
)
}
}Deleting a recording requires the DeleteRecording permission. Only recordings stored on Stream's side (the default) are removed, and an error is thrown if the recording doesn't exist. See the recording management API for more details.
Recording Storage
Recordings are stored according to your Stream dashboard configuration. By default, recordings are stored on Stream's servers and are available for download via the URL provided in the CallRecording object.
You can also configure external storage (such as S3) in your dashboard settings for more control over where recordings are stored.
Recording Settings
When starting a recording, you can optionally specify custom settings. The recording configuration is typically set at the call type level in your dashboard, but can be customized per call if needed.
Recording properties available in CallRecording:
filename- The name of the recording fileurl- The URL to download/stream the recordingstartTime- When the recording startedendTime- When the recording ended
Frame recording
Frame recording is a lightweight alternative to a full call recording. Instead of producing a video file, the backend periodically captures a still frame for every published video track and delivers it as an event. It's a good fit for moderation and post-call analysis, since it avoids the storage cost of a full recording. You can read more about the feature in the frame recording API docs.
Users need the StartFrameRecording and StopFrameRecording permissions to control it.
Start and stop frame recording
Use the Call's startFrameRecording and stopFrameRecording methods:
func startFrameRecording() {
Task {
try await call.startFrameRecording()
}
}
func stopFrameRecording() {
Task {
try await call.stopFrameRecording()
}
}Optionally, you can specify an external storage location for the captured frames:
try await call.startFrameRecording(recordingExternalStorage: "my-storage")The capture interval, the image quality and the mode (for example, starting frame recording automatically for every new call) are configured on the call type, either in your dashboard or with the server-side API, rather than per call.
Frame recording state
The CallState exposes a frameRecordingStatus property that tracks whether frame recording is active for the call you have joined. It's nil until the call state is loaded, true while frame recording runs, and false after it stops or fails:
call.state.$frameRecordingStatus
.sink { status in
showFrameRecordingIndicator = status == true
}
.store(in: &cancellables)Frame recording events
The captured frames are delivered as CallFrameRecordingFrameReadyEvents, one per published video track. Each event contains the URL of the captured image, the time it was captured, and the users visible in it:
func subscribeToFrameRecordingEvents() {
Task {
for await event in call.subscribe() {
switch event {
case .typeCallFrameRecordingFrameReadyEvent(let frameReadyEvent):
log.debug("frame captured at \(frameReadyEvent.capturedAt): \(frameReadyEvent.url)")
/* handle the captured frame */
case .typeCallFrameRecordingStartedEvent(let startedEvent):
log.debug("received an event \(startedEvent)")
/* frame recording started */
case .typeCallFrameRecordingStoppedEvent(let stoppedEvent):
log.debug("received an event \(stoppedEvent)")
/* frame recording stopped */
case .typeCallFrameRecordingFailedEvent(let failedEvent):
log.debug("received an event \(failedEvent)")
/* frame recording failed */
default:
break
}
}
}
}