@@ -9,16 +9,20 @@ class MediaDevices {
99 /// Subscribers for `onDeviceChange` callback of these `MediaDevices`.
1010 private var onDeviceChange : [ ( ) -> Void ] = [ ]
1111
12+ /// Set of all existing `RTCPeerConnection`s.
13+ private var activePeers : Set < Int > = [ ]
14+
15+ /// Set of all existing local audio tracks.
16+ private var activeAudioTracks : Set < String > = [ ]
17+
18+ /// Indicator of whether `AVAudioSession` is currently "captured".
19+ private var isAudioSessionActive : Bool = false
20+
1221 /// Initializes new `MediaDevices` with the provided `State`.
1322 ///
1423 /// Subscribes on `AVAudioSession.routeChangeNotification` notifications for
1524 /// `onDeviceChange` callback firing.
1625 init ( state: State ) {
17- try ? AVAudioSession . sharedInstance ( ) . setCategory (
18- AVAudioSession . Category. playAndRecord,
19- options: AVAudioSession . CategoryOptions. allowBluetooth
20- )
21- try ? AVAudioSession . sharedInstance ( ) . setActive ( true )
2226 self . state = state
2327 NotificationCenter . default. addObserver (
2428 forName: AVAudioSession . routeChangeNotification, object: nil ,
@@ -31,6 +35,77 @@ class MediaDevices {
3135 )
3236 }
3337
38+ /// Called when a new `RTCPeerConnection` is created.
39+ ///
40+ /// Captures the `AVAudioSession` (if its not captured already).
41+ func peerAdded( _ id: Int ) {
42+ assert ( Thread . isMainThread)
43+
44+ self . activePeers. insert ( id)
45+ self . updateAudioSession ( )
46+ }
47+
48+ /// Called when a `RTCPeerConnection` is disposed.
49+ ///
50+ /// Releases the `AVAudioSession` if it's the last `RTCPeerConnection` and
51+ /// there are no active local audio tracks.
52+ func peerRemoved( _ id: Int ) {
53+ assert ( Thread . isMainThread)
54+
55+ if self . activePeers. remove ( id) != nil {
56+ self . updateAudioSession ( )
57+ }
58+ }
59+
60+ /// Called when a new local audio track is created.
61+ ///
62+ /// Captures the `AVAudioSession` (if its not captured already).
63+ func audioTrackAdded( _ id: String ) {
64+ assert ( Thread . isMainThread)
65+
66+ self . activeAudioTracks. insert ( id)
67+ self . updateAudioSession ( )
68+ }
69+
70+ /// Called when a local audio track is disposed.
71+ ////
72+ /// Releases the `AVAudioSession` if it's the last local audio track and there
73+ /// are no `RTCPeerConnection`s.
74+ func audioTrackRemoved( _ id: String ) {
75+ assert ( Thread . isMainThread)
76+
77+ if self . activeAudioTracks. remove ( id) != nil {
78+ self . updateAudioSession ( )
79+ }
80+ }
81+
82+ /// Captures the `AVAudioSession` if there is at least one local audio track
83+ /// or `RTCPeerConnection`, or releases otherwise.
84+ ///
85+ /// No-op if the `AVAudioSession` is in the desired state already.
86+ private func updateAudioSession( ) {
87+ assert ( Thread . isMainThread)
88+
89+ let shouldBeActive = !self . activePeers. isEmpty || !self . activeAudioTracks
90+ . isEmpty
91+ if shouldBeActive, !self . isAudioSessionActive {
92+ try ? AVAudioSession . sharedInstance ( ) . setCategory (
93+ AVAudioSession . Category. playAndRecord,
94+ options: AVAudioSession . CategoryOptions. allowBluetooth
95+ )
96+ try ? AVAudioSession . sharedInstance ( ) . setActive ( true )
97+ self . isAudioSessionActive = true
98+ } else {
99+ if self . isAudioSessionActive {
100+ try ? AVAudioSession . sharedInstance ( ) . setActive (
101+ false ,
102+ options: . notifyOthersOnDeactivation
103+ )
104+ self . isAudioSessionActive = false
105+ }
106+ }
107+ }
108+
34109 /// Switches current input device to the iPhone's microphone.
35110 func setBuiltInMicAsInput( ) {
36111 if let routes = AVAudioSession . sharedInstance ( ) . availableInputs {
@@ -159,7 +234,12 @@ class MediaDevices {
159234 withTrackId: LocalTrackIdGenerator . shared. nextId ( )
160235 )
161236 let audioSource = AudioMediaTrackSourceProxy ( track: track)
162- return audioSource. newTrack ( )
237+ let trackProxy = audioSource. newTrack ( )
238+ self . audioTrackAdded ( trackProxy. id ( ) )
239+ trackProxy. onStopped ( cb: { [ weak self] in
240+ self ? . audioTrackRemoved ( trackProxy. id ( ) )
241+ } )
242+ return trackProxy
163243 }
164244
165245 /// Creates a video `MediaStreamTrackProxy` for the provided
0 commit comments