Skip to main content

Session Timers

In some cases, you want to be able to limit the duration of a call. StreamVideo supports this use-case by allowing you to specify the duration of a call on creation. Additionally, it provides you a way to extend the duration during the call, if needed.

Low-level client capabilities

First, let's see how we can create a call that has limited duration.

let call = streamVideo.call(callType: "default", callId: "callId")
try await call.create(members: [], maxDuration: 300)

This code will create a call, which will have a duration of 300 seconds (5 minutes), as soon as the session is started (a participant joined the call).

You can check the start date of a call with the following code:

let startedAt = call.state.session?.startedAt

When the maxDuration of a call is specified, the call session also provides the timerEndsAt value, which provides the date when the call will end. When a call is ended, all the participants are removed from the call.

let timerEndsAt = call.state.session?.timerEndsAt

Extending the call duration

You can also extend the duration of a call, both before or during the call. To do that, you should use the call.update method:

let newDuration = (call.state.settings?.limits.maxDurationSeconds ?? 0) + Int(extendDuration)
try await call.update(settingsOverride: .init(limits: .init(maxDurationSeconds: newDuration)))

When the call duration is extended, the timerEndsAt will be updated to reflect that change.

Example implementation

Let's see how we can put these methods together in a sample session timer implementation.

In this cookbook, we will show a popup that will notify the user that a call will end soon. It will also allow the creator of the call to extend its duration.

Prerequisite for following along is a working StreamVideo integration and the ability to establish calls. To help with that, check our tutorials and getting started docs.

Session Timer example

Let's create a new Swift file, and call it SessionTimer. We will put the following contents in it:

@MainActor class SessionTimer: ObservableObject {

@Published var showTimerAlert: Bool = false {
didSet {
if showTimerAlert, let timerEndsAt {
sessionEndCountdown?.invalidate()
secondsUntilEnd = timerEndsAt.timeIntervalSinceNow
sessionEndCountdown = Timer.scheduledTimer(
withTimeInterval: 1.0,
repeats: true,
block: { [weak self] _ in
guard let self else { return }
Task { @MainActor in
if self.secondsUntilEnd <= 0 {
self.sessionEndCountdown?.invalidate()
self.sessionEndCountdown = nil
self.secondsUntilEnd = 0
self.showTimerAlert = false
return
}
self.secondsUntilEnd -= 1
}
}
)
} else if !showTimerAlert {
sessionEndCountdown?.invalidate()
secondsUntilEnd = 0
}
}
}

@Published var secondsUntilEnd: TimeInterval = 0

private var call: Call?
private var cancellables = Set<AnyCancellable>()
private var timerEndsAt: Date? {
didSet {
setupTimerIfNeeded()
}
}

private var timer: Timer?
private var sessionEndCountdown: Timer?

private let alertInterval: TimeInterval

private var extendDuration: TimeInterval

private let changeMaxDurationPermission = Permission(
rawValue: OwnCapability.changeMaxDuration.rawValue
)

let extensionTime: TimeInterval

var showExtendCallDurationButton: Bool {
call?.state.ownCapabilities.contains(.changeMaxDuration) == true
}

@MainActor init(
call: Call?,
alertInterval: TimeInterval,
extendDuration: TimeInterval = 120
) {
self.call = call
self.alertInterval = alertInterval
self.extendDuration = extendDuration
extensionTime = extendDuration
timerEndsAt = call?.state.session?.timerEndsAt
setupTimerIfNeeded()
subscribeForSessionUpdates()
}

func extendCallDuration() {
guard let call else { return }
Task {
do {
let newDuration = (call.state.settings?.limits.maxDurationSeconds ?? 0) + Int(extendDuration)
extendDuration += extendDuration
log.debug("Extending call duration to \(newDuration) seconds")
try await call.update(settingsOverride: .init(limits: .init(maxDurationSeconds: newDuration)))
showTimerAlert = false
} catch {
log.error("Error extending call duration \(error.localizedDescription)")
}
}
}

// MARK: - private

private func subscribeForSessionUpdates() {
call?.state.$session.sink { [weak self] response in
guard let self else { return }
if response?.timerEndsAt != self.timerEndsAt {
self.timerEndsAt = response?.timerEndsAt
}
}
.store(in: &cancellables)
}

private func setupTimerIfNeeded() {
timer?.invalidate()
timer = nil
showTimerAlert = false
if let timerEndsAt {
let alertDate = timerEndsAt.addingTimeInterval(-alertInterval)
let timerInterval = alertDate.timeIntervalSinceNow
if timerInterval < 0 {
showTimerAlert = true
return
}
log.debug("Starting a timer in \(timerInterval) seconds")
timer = Timer.scheduledTimer(
withTimeInterval: timerInterval,
repeats: false,
block: { [weak self] _ in
guard let self else { return }
log.debug("Showing timer alert")
Task { @MainActor in
self.showTimerAlert = true
}
}
)
}
}

deinit {
timer?.invalidate()
sessionEndCountdown?.invalidate()
}
}

The session timer will be used in our SwiftUI view shown during a call. It will provide information when the session timer popup should be shown, as well as a countdown timer.

The showTimerAlert published variable will be set to true whenever the popup should be shown. This value is set whenever the timerEndsAt variable is updated. We listen to this value in the subscribeForSessionUpdates method above.

When the popup is shown, we start a timer called sessionEndCountdown, which will count down the seconds until the call is ended. We will use this value in the UI layer to inform the user.

We also created a method called extendCallDuration, which will allows us the extend the duration of the call, which would be an option provided to the user.

Next, let's declare this timer in our view shown during a duration of the call:

@StateObject var sessionTimer: SessionTimer

init(
// other params ommited
call: Call?
) {
_sessionTimer = .init(wrappedValue: .init(call: call, alertInterval: 60))
}

Our SessionTimer is created with a Call object, and an alertInterval. The alert interval in seconds tells us when the popup for session end should appear. The 60 seconds value means that the popup will be shown a minute before the session expires. Depending on your app's use-case, you can set a bigger value.

We can set the popup view as an overlay to your existing call view:

YourExistingCallView()
.overlay(
sessionTimer.showTimerAlert ? DemoSessionTimerView(sessionTimer: sessionTimer) : nil
)

View implementation

Next, let's see a sample implementation of the DemoSessionTimerView:

struct DemoSessionTimerView: View {

@Injected(\.colors) var colors
@Injected(\.fonts) var fonts
public var formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second]
formatter.zeroFormattingBehavior = .pad
return formatter
}()

@ObservedObject var sessionTimer: SessionTimer

var body: some View {
VStack {
HStack {
if let duration = formatter.string(from: sessionTimer.secondsUntilEnd) {
Text("Call will end in \(duration)")
.font(fonts.body.monospacedDigit())
.minimumScaleFactor(0.2)
.lineLimit(1)
} else {
Text("Call will end soon")
}
Divider()
if sessionTimer.showExtendCallDurationButton {
Button(action: {
sessionTimer.extendCallDuration()
}, label: {
Text("Extend for \(Int(sessionTimer.extensionTime / 60)) min")
.bold()
})
}
}
.foregroundColor(Color(colors.callDurationColor))
.padding(.horizontal)
.padding(.vertical, 4)
.background(Color(colors.participantBackground))
.clipShape(Capsule())
.frame(height: 60)
.padding(.top, 80)

Spacer()
}
}
}

In the implementation, we are formatting the secondsUntilEnd value from the sessionTimer, in order to inform the user about the session end.

Additionally, we expose a button for extending the call duration. The visibility of this button is controlled by the showExtendCallDurationButton from the sessionTimer, which checks if the user has the changeMaxDuration capability:

var showExtendCallDurationButton: Bool {
call?.state.ownCapabilities.contains(.changeMaxDuration) == true
}

With that, you can have a working implementation of a session timer.

Did you find this page helpful?