Skip to content

Commit 61824b8

Browse files
authored
Implement lazy AVAudioSession activation on iOS (#268)
1 parent 730ba5f commit 61824b8

File tree

7 files changed

+121
-20
lines changed

7 files changed

+121
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ All user visible changes to this project will be documented in this file. This p
2929

3030
- Upgraded [libwebrtc] to [142.0.7444.175] version. ([#256], [#254], [#248], [#260], [#264], [todo])
3131
- Removed camera permission request in `enumerateDevices()` on Android. ([#258])
32+
- `AVAudioSession` is now captured only when there is at least one `PeerConnection` or local audio track, and released when there are none. ([#268])
3233

3334
### Fixed
3435

@@ -51,6 +52,7 @@ All user visible changes to this project will be documented in this file. This p
5152
[#263]: https://github.com/instrumentisto/medea-flutter-webrtc/pull/263
5253
[#264]: https://github.com/instrumentisto/medea-flutter-webrtc/pull/264
5354
[#265]: https://github.com/instrumentisto/medea-flutter-webrtc/pull/265
55+
[#268]: https://github.com/instrumentisto/medea-flutter-webrtc/pull/268
5456
[todo]: https://github.com/instrumentisto/medea-flutter-webrtc/commit/todo
5557
[142.0.7444.175]: https://github.com/instrumentisto/libwebrtc-bin/releases/tag/142.0.7444.175
5658

example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ EXTERNAL SOURCES:
2727
SPEC CHECKSUMS:
2828
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
2929
instrumentisto-libwebrtc-bin: 06422ba9bf8b5ec14f25c1dda10109e4021decec
30-
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
31-
medea_flutter_webrtc: b3379af53010e6d4bd45023ba64d59864117c2fc
30+
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
31+
medea_flutter_webrtc: 90f34d3480bf89ebe1699c3091795b6a0ac1c2ae
3232

3333
PODFILE CHECKSUM: 96cbd66feeeb9e49d88210ea2b661316b312dda7
3434

ios/Classes/MedeaFlutterWebrtcPlugin.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ public class MedeaFlutterWebrtcPlugin: NSObject, FlutterPlugin {
2020
self.state = State()
2121
self.messenger = messenger
2222
self.textures = textures
23+
let mediaDevices = MediaDevices(state: self.state)
2324
self.peerConnectionFactory = PeerConnectionFactoryController(
24-
messenger: self.messenger, state: self.state
25+
messenger: self.messenger, state: self.state, mediaDevices: mediaDevices
2526
)
2627
self.mediaDevices = MediaDevicesController(
27-
messenger: self.messenger, mediaDevices: MediaDevices(state: self.state)
28+
messenger: self.messenger, mediaDevices: mediaDevices
2829
)
2930
self.videoRendererFactory = VideoRendererFactoryController(
3031
messenger: self.messenger, registry: self.textures

ios/Classes/MediaDevices.swift

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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

ios/Classes/controller/PeerConnectionFactoryController.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,22 @@ class PeerConnectionFactoryController {
1212
private var channel: FlutterMethodChannel
1313

1414
/// Initializes a new `PeerConnectionFactoryController` and
15-
/// `PeerConnectionFactoryProxy` based on the provided `State`.
16-
init(messenger: FlutterBinaryMessenger, state: State) {
15+
/// `PeerConnectionFactoryProxy` based on the provided `State` and
16+
/// `MediaDevices`.
17+
init(
18+
messenger: FlutterBinaryMessenger,
19+
state: State,
20+
mediaDevices: MediaDevices
21+
) {
1722
let channelName = ChannelNameGenerator.name(
1823
name: "PeerConnectionFactory",
1924
id: 0
2025
)
2126
self.messenger = messenger
22-
self.peerFactory = PeerConnectionFactoryProxy(state: state)
27+
self.peerFactory = PeerConnectionFactoryProxy(
28+
state: state,
29+
mediaDevices: mediaDevices
30+
)
2331
self.channel = FlutterMethodChannel(
2432
name: channelName,
2533
binaryMessenger: messenger

ios/Classes/proxy/PeerConnectionFactoryProxy.swift

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,14 @@ class PeerConnectionFactoryProxy {
4848
/// Underlying native factory object of this factory.
4949
private var factory: RTCPeerConnectionFactory
5050

51+
/// Instance of a `MediaDevices` manager.
52+
private var mediaDevices: MediaDevices
53+
5154
/// Initializes a new `PeerConnectionFactoryProxy` based on the provided
52-
/// `State`.
53-
init(state: State) {
55+
/// `State` and `MediaDevices`.
56+
init(state: State, mediaDevices: MediaDevices) {
5457
self.factory = state.getPeerFactory()
58+
self.mediaDevices = mediaDevices
5559
}
5660

5761
/// Returns sender capabilities of this factory.
@@ -86,18 +90,20 @@ class PeerConnectionFactoryProxy {
8690
delegate: peerObserver
8791
)
8892
let peerProxy = PeerConnectionProxy(id: id, peer: peer!)
93+
self.mediaDevices.peerAdded(id)
94+
peerProxy.onDispose = { [weak self] in
95+
guard let self = self else { return }
96+
97+
self.peerObservers.removeValue(forKey: id)
98+
self.mediaDevices.peerRemoved(id)
99+
}
89100
peerObserver.setPeer(peer: peerProxy)
90101

91102
self.peerObservers[id] = peerObserver
92103

93104
return peerProxy
94105
}
95106

96-
/// Removes the specified `PeerObserver` from the `peerObservers`.
97-
private func remotePeerObserver(id: Int) {
98-
self.peerObservers.removeValue(forKey: id)
99-
}
100-
101107
/// Generates the next track ID.
102108
private func nextId() -> Int {
103109
self.lastPeerConnectionId += 1

ios/Classes/proxy/PeerConnectionProxy.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ class PeerConnectionProxy {
2828
/// Last unique ID of `RtpTransceiverProxy`s.
2929
private var lastTransceiverId: Int = 0
3030

31+
/// Callback for notifying about `PeerConnectionProxy` disposal.
32+
var onDispose: (() -> Void)?
33+
3134
/// Initializes a new `PeerConnectionProxy` with the provided `peer` and `id`.
3235
init(id: Int, peer: RTCPeerConnection) {
3336
self.peer = peer
@@ -289,5 +292,6 @@ class PeerConnectionProxy {
289292
receiver.notifyRemoved()
290293
}
291294
self.receivers = [:]
295+
self.onDispose?()
292296
}
293297
}

0 commit comments

Comments
 (0)