From 334bb2a53684da0fc9012fa0d565fd36703f6e66 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:39:35 +0900 Subject: [PATCH 1/8] Implement --- .../UI/Participant/ParticipantView.swift | 7 +- .../UI/Visualizer/Visualizer.swift | 94 +++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift diff --git a/Sources/LiveKitComponents/UI/Participant/ParticipantView.swift b/Sources/LiveKitComponents/UI/Participant/ParticipantView.swift index bc8b7c7c..b73d36f7 100644 --- a/Sources/LiveKitComponents/UI/Participant/ParticipantView.swift +++ b/Sources/LiveKitComponents/UI/Participant/ParticipantView.swift @@ -31,11 +31,16 @@ public struct ParticipantView: View { GeometryReader { geometry in ZStack(alignment: .topLeading) { let cameraReference = TrackReference(participant: _participant, source: .camera) + let microphoneReference = TrackReference(participant: _participant, source: .microphone) if cameraReference.isResolvable { VideoTrackView(trackReference: cameraReference) } else { - _ui.videoDisabledView(geometry: geometry) + if microphoneReference.isResolvable { + BarAudioVisualizer(trackReference: microphoneReference) + } else { + _ui.videoDisabledView(geometry: geometry) + } } if _showInformation { diff --git a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift new file mode 100644 index 00000000..3616bd8d --- /dev/null +++ b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift @@ -0,0 +1,94 @@ +/* + * Copyright 2024 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import AVFoundation +import LiveKit +import SwiftUI + +class AudioProcessor: ObservableObject, AudioRenderer { + // Published property to update the view + private let _trackReference: TrackReference + + @Published var data: [Float] = [] + var centeredBandCount: Int = 10 + + private let processor: AudioVisualizeProcessor + + init(trackReference: TrackReference, bandCount: Int) { + processor = AudioVisualizeProcessor(bandsCount: bandCount) + _trackReference = trackReference + + if let track = _trackReference.resolve()?.track as? AudioTrack { + track.add(audioRenderer: self) + } + } + + deinit { + // TODO: Remove + } + + func render(pcmBuffer: AVAudioPCMBuffer) { + processor.add(pcmBuffer: pcmBuffer) + let processedData = processor.bands ?? [] + DispatchQueue.main.async { [weak self] in + self?.data = processedData + } + } +} + +struct BarAudioVisualizer: View { + public let barCount: Int + public let barColor: Color + public let barCornerRadius: CGFloat + public let barSpacing: CGFloat + + public let trackReference: TrackReference + + @ObservedObject private var _observableAudioProcessor: AudioProcessor + + init(trackReference: TrackReference, + barColor: Color = .white, + barCount: Int = 5, + cornerRadius: CGFloat = 15, + barSpacing: CGFloat = 10) + { + self.trackReference = trackReference + self.barColor = barColor + self.barCount = barCount + barCornerRadius = cornerRadius + self.barSpacing = barSpacing + + _observableAudioProcessor = AudioProcessor(trackReference: trackReference, + bandCount: barCount) + } + + var body: some View { + HStack(alignment: .center, spacing: barSpacing) { + ForEach(0 ..< _observableAudioProcessor.data.count, id: \.self) { index in + GeometryReader { geometry in + VStack { + Spacer() + RoundedRectangle(cornerRadius: barCornerRadius) + .fill(barColor.opacity(Double(_observableAudioProcessor.data[index]))) // Use normalized magnitude for opacity + .frame(height: CGFloat(_observableAudioProcessor.data[index]) * geometry.size.height) // Magnitude determines height + Spacer() + } + } + } + } + .padding() + } +} From 9052e3da7a25d425a39a1094e7b56aeec83db1c1 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:53:23 +0900 Subject: [PATCH 2/8] centered --- .../UI/Visualizer/Visualizer.swift | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift index 3616bd8d..b2cff56e 100644 --- a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift +++ b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift @@ -19,16 +19,15 @@ import LiveKit import SwiftUI class AudioProcessor: ObservableObject, AudioRenderer { - // Published property to update the view private let _trackReference: TrackReference + // Normalized to 0.0-1.0 range. @Published var data: [Float] = [] - var centeredBandCount: Int = 10 private let processor: AudioVisualizeProcessor - init(trackReference: TrackReference, bandCount: Int) { - processor = AudioVisualizeProcessor(bandsCount: bandCount) + init(trackReference: TrackReference, bandCount: Int, isCentered: Bool) { + processor = AudioVisualizeProcessor(bandsCount: bandCount, isCentered: isCentered) _trackReference = trackReference if let track = _trackReference.resolve()?.track as? AudioTrack { @@ -54,6 +53,7 @@ struct BarAudioVisualizer: View { public let barColor: Color public let barCornerRadius: CGFloat public let barSpacing: CGFloat + public let isCentered: Bool public let trackReference: TrackReference @@ -62,17 +62,20 @@ struct BarAudioVisualizer: View { init(trackReference: TrackReference, barColor: Color = .white, barCount: Int = 5, - cornerRadius: CGFloat = 15, - barSpacing: CGFloat = 10) + barCornerRadius: CGFloat = 15, + barSpacing: CGFloat = 10, + isCentered: Bool = true) { self.trackReference = trackReference self.barColor = barColor self.barCount = barCount - barCornerRadius = cornerRadius + self.barCornerRadius = barCornerRadius self.barSpacing = barSpacing + self.isCentered = isCentered _observableAudioProcessor = AudioProcessor(trackReference: trackReference, - bandCount: barCount) + bandCount: barCount, + isCentered: isCentered) } var body: some View { From 8f166127a104fdc2b3ecc5166f8385dd61c26f30 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 29 Sep 2024 18:45:14 +0900 Subject: [PATCH 3/8] barSpacingFactor --- .../LiveKitComponents/UI/Visualizer/Visualizer.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift index b2cff56e..62259fdc 100644 --- a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift +++ b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift @@ -52,7 +52,7 @@ struct BarAudioVisualizer: View { public let barCount: Int public let barColor: Color public let barCornerRadius: CGFloat - public let barSpacing: CGFloat + public let barSpacingFactor: CGFloat public let isCentered: Bool public let trackReference: TrackReference @@ -63,14 +63,14 @@ struct BarAudioVisualizer: View { barColor: Color = .white, barCount: Int = 5, barCornerRadius: CGFloat = 15, - barSpacing: CGFloat = 10, + barSpacingFactor: CGFloat = 0.015, isCentered: Bool = true) { self.trackReference = trackReference self.barColor = barColor self.barCount = barCount self.barCornerRadius = barCornerRadius - self.barSpacing = barSpacing + self.barSpacingFactor = barSpacingFactor self.isCentered = isCentered _observableAudioProcessor = AudioProcessor(trackReference: trackReference, @@ -79,9 +79,9 @@ struct BarAudioVisualizer: View { } var body: some View { - HStack(alignment: .center, spacing: barSpacing) { + GeometryReader { geometry in + HStack(alignment: .center, spacing: geometry.size.width * barSpacingFactor) { ForEach(0 ..< _observableAudioProcessor.data.count, id: \.self) { index in - GeometryReader { geometry in VStack { Spacer() RoundedRectangle(cornerRadius: barCornerRadius) From 134376a043bb03bfe702b2a254751fb33c147ab2 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:21:45 +0900 Subject: [PATCH 4/8] Optimize --- .../UI/Participant/ParticipantView.swift | 4 +- .../UI/Visualizer/Visualizer.swift | 39 +++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/Sources/LiveKitComponents/UI/Participant/ParticipantView.swift b/Sources/LiveKitComponents/UI/Participant/ParticipantView.swift index b73d36f7..2fc77aae 100644 --- a/Sources/LiveKitComponents/UI/Participant/ParticipantView.swift +++ b/Sources/LiveKitComponents/UI/Participant/ParticipantView.swift @@ -33,10 +33,10 @@ public struct ParticipantView: View { let cameraReference = TrackReference(participant: _participant, source: .camera) let microphoneReference = TrackReference(participant: _participant, source: .microphone) - if cameraReference.isResolvable { + if let cameraTrack = cameraReference.resolve(), !cameraTrack.isMuted { VideoTrackView(trackReference: cameraReference) } else { - if microphoneReference.isResolvable { + if let microphoneTrack = microphoneReference.resolve(), !microphoneTrack.isMuted { BarAudioVisualizer(trackReference: microphoneReference) } else { _ui.videoDisabledView(geometry: geometry) diff --git a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift index 62259fdc..5eeb61b3 100644 --- a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift +++ b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift @@ -19,29 +19,25 @@ import LiveKit import SwiftUI class AudioProcessor: ObservableObject, AudioRenderer { - private let _trackReference: TrackReference - + private weak var _track: AudioTrack? // Normalized to 0.0-1.0 range. @Published var data: [Float] = [] - private let processor: AudioVisualizeProcessor - - init(trackReference: TrackReference, bandCount: Int, isCentered: Bool) { - processor = AudioVisualizeProcessor(bandsCount: bandCount, isCentered: isCentered) - _trackReference = trackReference + private let _processor: AudioVisualizeProcessor - if let track = _trackReference.resolve()?.track as? AudioTrack { - track.add(audioRenderer: self) - } + init(track: AudioTrack?, bandCount: Int, isCentered: Bool) { + _processor = AudioVisualizeProcessor(bandsCount: bandCount, isCentered: isCentered) + _track = track + _track?.add(audioRenderer: self) } deinit { - // TODO: Remove + _track?.remove(audioRenderer: self) } func render(pcmBuffer: AVAudioPCMBuffer) { - processor.add(pcmBuffer: pcmBuffer) - let processedData = processor.bands ?? [] + _processor.add(pcmBuffer: pcmBuffer) + let processedData = _processor.bands ?? [] DispatchQueue.main.async { [weak self] in self?.data = processedData } @@ -57,11 +53,11 @@ struct BarAudioVisualizer: View { public let trackReference: TrackReference - @ObservedObject private var _observableAudioProcessor: AudioProcessor + @StateObject private var audioProcessor: AudioProcessor init(trackReference: TrackReference, barColor: Color = .white, - barCount: Int = 5, + barCount: Int = 7, barCornerRadius: CGFloat = 15, barSpacingFactor: CGFloat = 0.015, isCentered: Bool = true) @@ -73,20 +69,21 @@ struct BarAudioVisualizer: View { self.barSpacingFactor = barSpacingFactor self.isCentered = isCentered - _observableAudioProcessor = AudioProcessor(trackReference: trackReference, - bandCount: barCount, - isCentered: isCentered) + let track = trackReference.resolve()?.track as? AudioTrack + _audioProcessor = StateObject(wrappedValue: AudioProcessor(track: track, + bandCount: barCount, + isCentered: isCentered)) } var body: some View { GeometryReader { geometry in HStack(alignment: .center, spacing: geometry.size.width * barSpacingFactor) { - ForEach(0 ..< _observableAudioProcessor.data.count, id: \.self) { index in + ForEach(0 ..< audioProcessor.data.count, id: \.self) { index in VStack { Spacer() RoundedRectangle(cornerRadius: barCornerRadius) - .fill(barColor.opacity(Double(_observableAudioProcessor.data[index]))) // Use normalized magnitude for opacity - .frame(height: CGFloat(_observableAudioProcessor.data[index]) * geometry.size.height) // Magnitude determines height + .fill(barColor.opacity(Double(audioProcessor.data[index]))) + .frame(height: CGFloat(audioProcessor.data[index]) * geometry.size.height) Spacer() } } From ddbda58c4fa77065e41a73b7d20afb579ade1708 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:59:22 +0900 Subject: [PATCH 5/8] adjustments --- .../UI/Participant/ParticipantView.swift | 4 +-- .../UI/Visualizer/Visualizer.swift | 25 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Sources/LiveKitComponents/UI/Participant/ParticipantView.swift b/Sources/LiveKitComponents/UI/Participant/ParticipantView.swift index 2fc77aae..bde8be32 100644 --- a/Sources/LiveKitComponents/UI/Participant/ParticipantView.swift +++ b/Sources/LiveKitComponents/UI/Participant/ParticipantView.swift @@ -36,8 +36,8 @@ public struct ParticipantView: View { if let cameraTrack = cameraReference.resolve(), !cameraTrack.isMuted { VideoTrackView(trackReference: cameraReference) } else { - if let microphoneTrack = microphoneReference.resolve(), !microphoneTrack.isMuted { - BarAudioVisualizer(trackReference: microphoneReference) + if let microphoneTrack = microphoneReference.resolve(), !microphoneTrack.isMuted, let audioTrack = microphoneTrack.track as? AudioTrack { + BarAudioVisualizer(audioTrack: audioTrack) } else { _ui.videoDisabledView(geometry: geometry) } diff --git a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift index 5eeb61b3..a69c582e 100644 --- a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift +++ b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift @@ -21,7 +21,7 @@ import SwiftUI class AudioProcessor: ObservableObject, AudioRenderer { private weak var _track: AudioTrack? // Normalized to 0.0-1.0 range. - @Published var data: [Float] = [] + @Published var bands: [Float] = [] private let _processor: AudioVisualizeProcessor @@ -36,10 +36,10 @@ class AudioProcessor: ObservableObject, AudioRenderer { } func render(pcmBuffer: AVAudioPCMBuffer) { - _processor.add(pcmBuffer: pcmBuffer) - let processedData = _processor.bands ?? [] + let bands = _processor.process(pcmBuffer: pcmBuffer) + guard let bands else { return } DispatchQueue.main.async { [weak self] in - self?.data = processedData + self?.bands = bands } } } @@ -51,26 +51,25 @@ struct BarAudioVisualizer: View { public let barSpacingFactor: CGFloat public let isCentered: Bool - public let trackReference: TrackReference + public let audioTrack: AudioTrack @StateObject private var audioProcessor: AudioProcessor - init(trackReference: TrackReference, + init(audioTrack: AudioTrack, barColor: Color = .white, barCount: Int = 7, - barCornerRadius: CGFloat = 15, + barCornerRadius: CGFloat = 100, barSpacingFactor: CGFloat = 0.015, isCentered: Bool = true) { - self.trackReference = trackReference + self.audioTrack = audioTrack self.barColor = barColor self.barCount = barCount self.barCornerRadius = barCornerRadius self.barSpacingFactor = barSpacingFactor self.isCentered = isCentered - let track = trackReference.resolve()?.track as? AudioTrack - _audioProcessor = StateObject(wrappedValue: AudioProcessor(track: track, + _audioProcessor = StateObject(wrappedValue: AudioProcessor(track: audioTrack, bandCount: barCount, isCentered: isCentered)) } @@ -78,12 +77,12 @@ struct BarAudioVisualizer: View { var body: some View { GeometryReader { geometry in HStack(alignment: .center, spacing: geometry.size.width * barSpacingFactor) { - ForEach(0 ..< audioProcessor.data.count, id: \.self) { index in + ForEach(0 ..< audioProcessor.bands.count, id: \.self) { index in VStack { Spacer() RoundedRectangle(cornerRadius: barCornerRadius) - .fill(barColor.opacity(Double(audioProcessor.data[index]))) - .frame(height: CGFloat(audioProcessor.data[index]) * geometry.size.height) + .fill(barColor.opacity(Double(audioProcessor.bands[index]))) + .frame(height: CGFloat(audioProcessor.bands[index]) * geometry.size.height) Spacer() } } From 8b35e3d9bbbae75bd9733a194d38e64bddf5edcd Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:03:20 +0900 Subject: [PATCH 6/8] Move code from sdk --- .../UI/Visualizer/Visualizer.swift | 72 +++++++++++++++++-- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift index a69c582e..10dba970 100644 --- a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift +++ b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift @@ -20,13 +20,24 @@ import SwiftUI class AudioProcessor: ObservableObject, AudioRenderer { private weak var _track: AudioTrack? + private let isCentered: Bool + public let smoothingFactor: Float + // Normalized to 0.0-1.0 range. - @Published var bands: [Float] = [] + @Published var bands: [Float] private let _processor: AudioVisualizeProcessor - init(track: AudioTrack?, bandCount: Int, isCentered: Bool) { - _processor = AudioVisualizeProcessor(bandsCount: bandCount, isCentered: isCentered) + init(track: AudioTrack?, + bandCount: Int, + isCentered: Bool = true, + smoothingFactor: Float = 0.3) + { + self.isCentered = isCentered + self.smoothingFactor = smoothingFactor + bands = Array(repeating: 0.0, count: bandCount) + + _processor = AudioVisualizeProcessor(bandsCount: bandCount) _track = track _track?.add(audioRenderer: self) } @@ -36,11 +47,60 @@ class AudioProcessor: ObservableObject, AudioRenderer { } func render(pcmBuffer: AVAudioPCMBuffer) { - let bands = _processor.process(pcmBuffer: pcmBuffer) - guard let bands else { return } + let newBands = _processor.process(pcmBuffer: pcmBuffer) + guard var newBands else { return } + + // If centering is enabled, rearrange the normalized bands + if isCentered { + newBands.sort(by: >) + newBands = centerBands(newBands) + } + DispatchQueue.main.async { [weak self] in - self?.bands = bands + guard let self else { return } + + self.bands = zip(self.bands, newBands).map { old, new in + self._smoothTransition(from: old, to: new, factor: self.smoothingFactor) + } + } + } + + // MARK: - Private + + /// Centers the sorted bands by placing higher values in the middle. + @inline(__always) private func centerBands(_ sortedBands: [Float]) -> [Float] { + var centeredBands = [Float](repeating: 0, count: sortedBands.count) + var leftIndex = sortedBands.count / 2 + var rightIndex = leftIndex + + for (index, value) in sortedBands.enumerated() { + if index % 2 == 0 { + // Place value to the right + centeredBands[rightIndex] = value + rightIndex += 1 + } else { + // Place value to the left + leftIndex -= 1 + centeredBands[leftIndex] = value + } } + + return centeredBands + } + + /// Applies an easing function to smooth the transition. + @inline(__always) private func _smoothTransition(from oldValue: Float, to newValue: Float, factor: Float) -> Float { + // Calculate the delta change between the old and new value + let delta = newValue - oldValue + // Apply an ease-in-out cubic easing curve + let easedFactor = _easeInOutCubic(t: factor) + // Calculate and return the smoothed value + return oldValue + delta * easedFactor + } + + /// Easing function: ease-in-out cubic + @inline(__always) private func _easeInOutCubic(t: Float) -> Float { + t < 0.5 ? 4 * t * t * t : 1 - pow(-2 * t + 2, 3) / 2 } } From e8372fecce5d1ea6ca524a5788f5f5d9263f7002 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 13 Nov 2024 01:34:47 +0900 Subject: [PATCH 7/8] Docs --- .../UI/Visualizer/Visualizer.swift | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift index 10dba970..5c55b7aa 100644 --- a/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift +++ b/Sources/LiveKitComponents/UI/Visualizer/Visualizer.swift @@ -104,6 +104,36 @@ class AudioProcessor: ObservableObject, AudioRenderer { } } +/// A SwiftUI view that visualizes audio levels as a series of vertical bars, +/// responding to real-time audio data processed from an audio track. +/// +/// `BarAudioVisualizer` displays bars whose heights and opacities dynamically +/// reflect the magnitude of audio frequencies in real time, creating an +/// interactive, visual representation of the audio track's spectrum. This +/// visualizer can be customized in terms of bar count, color, corner radius, +/// spacing, and whether the bars are centered based on frequency magnitude. +/// +/// Usage: +/// ``` +/// let audioTrack: AudioTrack = ... +/// BarAudioVisualizer(audioTrack: audioTrack) +/// ``` +/// +/// - Parameters: +/// - audioTrack: The `AudioTrack` providing audio data to be visualized. +/// - barColor: The color used to fill each bar, defaulting to white. +/// - barCount: The number of bars displayed, defaulting to 7. +/// - barCornerRadius: The corner radius applied to each bar, giving a +/// rounded appearance. Defaults to 100. +/// - barSpacingFactor: Determines the spacing between bars as a factor +/// of view width. Defaults to 0.015. +/// - isCentered: A Boolean indicating whether higher-decibel bars +/// should be centered. Defaults to `true`. +/// +/// Example: +/// ``` +/// BarAudioVisualizer(audioTrack: audioTrack, barColor: .blue, barCount: 10) +/// ``` struct BarAudioVisualizer: View { public let barCount: Int public let barColor: Color From 5c96db9a16481c15439aab57938abc798b238f21 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 13 Nov 2024 02:00:30 +0900 Subject: [PATCH 8/8] Require 2.0.17 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 7edfdf14..b722d4a5 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/livekit/client-sdk-swift.git", from: "2.0.14"), + .package(url: "https://github.com/livekit/client-sdk-swift.git", from: "2.0.17"), .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"), ], targets: [