Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion DemoApp/Sources/Components/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,15 @@ final class Router: ObservableObject {
process: processor.process,
release: processor.release
)
videoConfig = .init(noiseCancellationFilter: noiseCancellationFilter)
videoConfig = .init(
renderingOptions: .init(
renderingBackend: AppEnvironment.renderingBackend.rawBackend,
bufferPolicy: AppEnvironment.renderingBufferPolicy.rawPolicy,
maxInFlightFrames: InjectedValues[\.videoRenderingOptions].maxInFlightFrames,
rotationOverride: InjectedValues[\.videoRenderingOptions].rotationOverride
),
noiseCancellationFilter: noiseCancellationFilter
)
#else
videoConfig = .init()
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ struct DemoCallingViewModifier: ViewModifier {
guard !isAnonymous else { return }
callKitAdapter.registerForIncomingCalls()
callKitAdapter.iconTemplateImageData = UIImage(named: "logo")?.pngData()
configureVideoRenderingOptions()
joinCallIfNeeded(with: text.wrappedValue, callType: callType)
}
.onReceive(appState.$activeCall) { call in
Expand All @@ -99,6 +100,7 @@ struct DemoCallingViewModifier: ViewModifier {
await call.updatePublishOptions(
preferredVideoCodec: AppEnvironment.preferredVideoCodec.videoCodec
)

_ = await Task { @MainActor in
viewModel.update(
participantsSortComparators: callType == .livestream
Expand All @@ -113,4 +115,11 @@ struct DemoCallingViewModifier: ViewModifier {
AppState.shared.deeplinkInfo = .empty
}
}

private func configureVideoRenderingOptions() {
InjectedValues[\.videoRenderingOptions] = .init(
renderingBackend: AppEnvironment.renderingBackend.rawBackend,
bufferPolicy: AppEnvironment.renderingBufferPolicy.rawPolicy
)
}
}
120 changes: 120 additions & 0 deletions DemoApp/Sources/Views/Login/Debug/RenderingOptions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//
// Copyright © 2026 Stream.io Inc. All rights reserved.
//

import Foundation
import StreamVideo
import StreamWebRTC
import SwiftUI

extension AppEnvironment {

enum RenderingBackend: Hashable, Debuggable, Sendable, CaseIterable {
case `default`
case sharedMetal

var rawBackend: RTCVideoRenderingBackend {
switch self {
case .default:
return .default
case .sharedMetal:
return .sharedMetal
}
}

var title: String {
switch self {
case .default:
"Default"
case .sharedMetal:
"Shared Metal Pipeline"
}
}
}

nonisolated(unsafe) static var renderingBackend: RenderingBackend = .sharedMetal

enum RenderingBufferPolicy: Hashable, Debuggable, Sendable, CaseIterable {
case none
case wrapOnlyExistingNV12
case copyToNV12
case convertWithPoolToNV12

var rawPolicy: RTCFrameBufferPolicy {
switch self {
case .none:
return .none
case .wrapOnlyExistingNV12:
return .wrapOnlyExistingNV12
case .copyToNV12:
return .copyToNV12
case .convertWithPoolToNV12:
return .convertWithPoolToNV12
}
}

var title: String {
switch self {
case .none:
"None"
case .wrapOnlyExistingNV12:
"Wrap Only"
case .copyToNV12:
"Copy"
case .convertWithPoolToNV12:
"Conver & Copy"
}
}
}

nonisolated(unsafe) static var renderingBufferPolicy: RenderingBufferPolicy = .convertWithPoolToNV12
}

extension DebugMenu {

struct RenderingOptions: View {

@State private var renderingBackend: AppEnvironment.RenderingBackend = AppEnvironment.renderingBackend {
didSet {
AppEnvironment.renderingBackend = renderingBackend
}
}

@State private var renderingBufferPolicy: AppEnvironment.RenderingBufferPolicy = AppEnvironment.renderingBufferPolicy {
didSet {
AppEnvironment.renderingBufferPolicy = renderingBufferPolicy
}
}

var body: some View {
Menu {
renderingBackendView
renderingBufferPolicyView
} label: {
Text("Rendering Options")
}
}

// MARK: - Private Helpers

@ViewBuilder
private var renderingBackendView: some View {
DebugMenuItemView(
label: "Rendering Backend",
availableAfterLogin: false,
items: AppEnvironment.RenderingBackend.allCases,
currentValue: renderingBackend
) { self.renderingBackend = $0 }
}

@ViewBuilder
private var renderingBufferPolicyView: some View {
DebugMenuItemView(
label: "Rendering Buffer Policy",
availableAfterLogin: false,
items: AppEnvironment.RenderingBufferPolicy.allCases,
currentValue: renderingBufferPolicy
) { self.renderingBufferPolicy = $0 }
}
}
}
35 changes: 11 additions & 24 deletions DemoApp/Sources/Views/Login/DebugMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ struct DebugMenu: View {
label: "Moderation Video Policy"
) { self.moderationVideoPolicy = $0 }

RenderingOptions()

makeMenu(
for: [.never, .twoMinutes],
currentValue: disconnectionTimeout,
Expand Down Expand Up @@ -525,34 +527,19 @@ struct DebugMenu: View {
private func makeMenu<Item: Debuggable>(
for items: [Item],
currentValue: Item,
@ViewBuilder additionalItems: () -> some View = { EmptyView() },
@ViewBuilder additionalItems: @escaping () -> some View = { EmptyView() },
label: String,
availableAfterLogin: Bool = true,
updater: @escaping (Item) -> Void
) -> some View {
if !availableAfterLogin, appState.userState == .loggedIn {
EmptyView()
} else {
Menu {
ForEach(items, id: \.self) { item in
Button {
updater(item)
} label: {
Label {
Text(item.title)
} icon: {
currentValue == item
? AnyView(Image(systemName: "checkmark"))
: AnyView(EmptyView())
}
}
}

additionalItems()
} label: {
Text(label)
}
}
DebugMenuItemView(
label: label,
availableAfterLogin: availableAfterLogin,
items: items,
currentValue: currentValue,
additionalItems: additionalItems,
updater: updater
)
}

@ViewBuilder
Expand Down
59 changes: 59 additions & 0 deletions DemoApp/Sources/Views/Login/DebugMenuItemView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// Copyright © 2026 Stream.io Inc. All rights reserved.
//

import Foundation
import SwiftUI

struct DebugMenuItemView<Item: Debuggable, AdditionalItems: View>: View {

private var appState = AppState.shared

var label: String
var availableAfterLogin: Bool = true
var items: [Item]
var currentValue: Item
var additionalItems: () -> AdditionalItems
var updater: (Item) -> Void

init(
label: String,
availableAfterLogin: Bool,
items: [Item],
currentValue: Item,
additionalItems: @escaping () -> AdditionalItems = { EmptyView() },
updater: @escaping (Item) -> Void
) {
self.label = label
self.availableAfterLogin = availableAfterLogin
self.items = items
self.currentValue = currentValue
self.additionalItems = additionalItems
self.updater = updater
}

var body: some View {
if !availableAfterLogin, appState.userState == .loggedIn {
EmptyView()
} else {
Menu {
ForEach(items, id: \.self) { item in
Button {
updater(item)
} label: {
Label {
Text(item.title)
} icon: {
currentValue == item
? AnyView(Image(systemName: "checkmark"))
: AnyView(EmptyView())
}
}
}
additionalItems()
} label: {
Text(label)
}
}
}
}
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/apple/swift-protobuf.git", exact: "1.30.0"),
.package(url: "https://github.com/GetStream/stream-video-swift-webrtc.git", exact: "137.0.54")
.package(url: "https://github.com/GetStream/stream-video-swift-webrtc.git", exact: "137.0.59")
],
targets: [
.target(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ extension SystemEnvironment {
/// A Stream Video version.
public static let version: String = "1.42.0-SNAPSHOT"
/// The WebRTC version.
public static let webRTCVersion: String = "137.0.54"
public static let webRTCVersion: String = "137.0.59"
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ extension AnyPublisher: @retroactive @unchecked Sendable {}
extension Publishers.Filter: @retroactive @unchecked Sendable {}
/// Allows audio buffers to cross concurrency boundaries.
extension AVAudioPCMBuffer: @retroactive @unchecked Sendable {}
extension AVCaptureDevice.Format: @retroactive @unchecked Sendable {}
extension AVCaptureDevice.SystemPressureState: @retroactive @unchecked Sendable {}
#else
extension AnyCancellable: @unchecked Sendable {}
extension AVCaptureDevice: @unchecked Sendable {}
Expand All @@ -59,4 +61,6 @@ extension Publishers.Filter: @unchecked Sendable {}
extension AVAudioPCMBuffer: @unchecked Sendable {}
extension CIContext: @unchecked Sendable {}
extension CIImage: @unchecked Sendable {}
extension AVCaptureDevice.Format: @unchecked Sendable {}
extension AVCaptureDevice.SystemPressureState: @unchecked Sendable {}
#endif
Loading
Loading