1414 * limitations under the License.
1515 */
1616
17+ import AVFAudio
1718import Combine
1819import Foundation
1920
@@ -26,7 +27,15 @@ internal import LiveKitWebRTC
2627@objc
2728public class LocalAudioTrack : Track , LocalTrack , AudioTrack , @unchecked Sendable {
2829 /// ``AudioCaptureOptions`` used to create this track.
29- let captureOptions : AudioCaptureOptions
30+ public let captureOptions : AudioCaptureOptions
31+
32+ // MARK: - Internal
33+
34+ struct FrameWatcherState {
35+ var frameWatcher : AudioFrameWatcher ?
36+ }
37+
38+ let _frameWatcherState = StateSync ( FrameWatcherState ( ) )
3039
3140 init ( name: String ,
3241 source: Track . Source ,
@@ -43,6 +52,10 @@ public class LocalAudioTrack: Track, LocalTrack, AudioTrack, @unchecked Sendable
4352 reportStatistics: reportStatistics)
4453 }
4554
55+ deinit {
56+ cleanUpFrameWatcher ( )
57+ }
58+
4659 public static func createTrack( name: String = Track . microphoneName,
4760 options: AudioCaptureOptions ? = nil ,
4861 reportStatistics: Bool = false ) -> LocalAudioTrack
@@ -93,6 +106,10 @@ public class LocalAudioTrack: Track, LocalTrack, AudioTrack, @unchecked Sendable
93106 throw error
94107 }
95108 }
109+
110+ override func stopCapture( ) async throws {
111+ cleanUpFrameWatcher ( )
112+ }
96113}
97114
98115public extension LocalAudioTrack {
@@ -109,3 +126,49 @@ public extension LocalAudioTrack {
109126 AudioManager . shared. remove ( localAudioRenderer: audioRenderer)
110127 }
111128}
129+
130+ // MARK: - Internal frame waiting
131+
132+ extension LocalAudioTrack {
133+ final class AudioFrameWatcher : AudioRenderer , Loggable {
134+ private let completer = AsyncCompleter < Void > ( label: " Frame watcher " , defaultTimeout: 5 )
135+
136+ func wait( ) async throws {
137+ try await completer. wait ( )
138+ }
139+
140+ func reset( ) {
141+ completer. reset ( )
142+ }
143+
144+ // MARK: - AudioRenderer
145+
146+ func render( pcmBuffer _: AVAudioPCMBuffer ) {
147+ completer. resume ( returning: ( ) )
148+ }
149+ }
150+
151+ func startWaitingForFrames( ) async throws {
152+ let frameWatcher = _frameWatcherState. mutate {
153+ $0. frameWatcher? . reset ( )
154+ let watcher = AudioFrameWatcher ( )
155+ add ( audioRenderer: watcher)
156+ $0. frameWatcher = watcher
157+ return watcher
158+ }
159+
160+ try await frameWatcher. wait ( )
161+ // Detach after wait is complete
162+ cleanUpFrameWatcher ( )
163+ }
164+
165+ func cleanUpFrameWatcher( ) {
166+ _frameWatcherState. mutate {
167+ if let watcher = $0. frameWatcher {
168+ watcher. reset ( )
169+ remove ( audioRenderer: watcher)
170+ $0. frameWatcher = nil
171+ }
172+ }
173+ }
174+ }
0 commit comments