Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ All user visible changes to this project will be documented in this file. This p

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

### Fixed

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

Expand Down
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
instrumentisto-libwebrtc-bin: 06422ba9bf8b5ec14f25c1dda10109e4021decec
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
medea_flutter_webrtc: b3379af53010e6d4bd45023ba64d59864117c2fc
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
medea_flutter_webrtc: 90f34d3480bf89ebe1699c3091795b6a0ac1c2ae

PODFILE CHECKSUM: 96cbd66feeeb9e49d88210ea2b661316b312dda7

Expand Down
5 changes: 3 additions & 2 deletions ios/Classes/MedeaFlutterWebrtcPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ public class MedeaFlutterWebrtcPlugin: NSObject, FlutterPlugin {
self.state = State()
self.messenger = messenger
self.textures = textures
let mediaDevices = MediaDevices(state: self.state)
self.peerConnectionFactory = PeerConnectionFactoryController(
messenger: self.messenger, state: self.state
messenger: self.messenger, state: self.state, mediaDevices: mediaDevices
)
self.mediaDevices = MediaDevicesController(
messenger: self.messenger, mediaDevices: MediaDevices(state: self.state)
messenger: self.messenger, mediaDevices: mediaDevices
)
self.videoRendererFactory = VideoRendererFactoryController(
messenger: self.messenger, registry: self.textures
Expand Down
92 changes: 86 additions & 6 deletions ios/Classes/MediaDevices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,20 @@ class MediaDevices {
/// Subscribers for `onDeviceChange` callback of these `MediaDevices`.
private var onDeviceChange: [() -> Void] = []

/// Set of all existing `RTCPeerConnection`s.
private var activePeers: Set<Int> = []

/// Set of all existing local audio tracks.
private var activeAudioTracks: Set<String> = []

/// Indicator of whether `AVAudioSession` is currently "captured".
private var isAudioSessionActive: Bool = false

/// Initializes new `MediaDevices` with the provided `State`.
///
/// Subscribes on `AVAudioSession.routeChangeNotification` notifications for
/// `onDeviceChange` callback firing.
init(state: State) {
try? AVAudioSession.sharedInstance().setCategory(
AVAudioSession.Category.playAndRecord,
options: AVAudioSession.CategoryOptions.allowBluetooth
)
try? AVAudioSession.sharedInstance().setActive(true)
self.state = state
NotificationCenter.default.addObserver(
forName: AVAudioSession.routeChangeNotification, object: nil,
Expand All @@ -31,6 +35,77 @@ class MediaDevices {
)
}

/// Called when a new `RTCPeerConnection` is created.
///
/// Captures the `AVAudioSession` (if its not captured already).
func peerAdded(_ id: Int) {
assert(Thread.isMainThread)

self.activePeers.insert(id)
self.updateAudioSession()
}

/// Called when a `RTCPeerConnection` is disposed.
///
/// Releases the `AVAudioSession` if it's the last `RTCPeerConnection` and
/// there are no active local audio tracks.
func peerRemoved(_ id: Int) {
assert(Thread.isMainThread)

if self.activePeers.remove(id) != nil {
self.updateAudioSession()
}
}

/// Called when a new local audio track is created.
///
/// Captures the `AVAudioSession` (if its not captured already).
func audioTrackAdded(_ id: String) {
assert(Thread.isMainThread)

self.activeAudioTracks.insert(id)
self.updateAudioSession()
}

/// Called when a local audio track is disposed.
////
/// Releases the `AVAudioSession` if it's the last local audio track and there
/// are no `RTCPeerConnection`s.
func audioTrackRemoved(_ id: String) {
assert(Thread.isMainThread)

if self.activeAudioTracks.remove(id) != nil {
self.updateAudioSession()
}
}

/// Captures the `AVAudioSession` if there is at least one local audio track
/// or `RTCPeerConnection`, or releases otherwise.
///
/// No-op if the `AVAudioSession` is in the desired state already.
private func updateAudioSession() {
assert(Thread.isMainThread)

let shouldBeActive = !self.activePeers.isEmpty || !self.activeAudioTracks
.isEmpty
if shouldBeActive, !self.isAudioSessionActive {
try? AVAudioSession.sharedInstance().setCategory(
AVAudioSession.Category.playAndRecord,
options: AVAudioSession.CategoryOptions.allowBluetooth
)
try? AVAudioSession.sharedInstance().setActive(true)
self.isAudioSessionActive = true
} else {
if self.isAudioSessionActive {
try? AVAudioSession.sharedInstance().setActive(
false,
options: .notifyOthersOnDeactivation
)
self.isAudioSessionActive = false
}
}
}

/// Switches current input device to the iPhone's microphone.
func setBuiltInMicAsInput() {
if let routes = AVAudioSession.sharedInstance().availableInputs {
Expand Down Expand Up @@ -159,7 +234,12 @@ class MediaDevices {
withTrackId: LocalTrackIdGenerator.shared.nextId()
)
let audioSource = AudioMediaTrackSourceProxy(track: track)
return audioSource.newTrack()
let trackProxy = audioSource.newTrack()
self.audioTrackAdded(trackProxy.id())
trackProxy.onStopped(cb: { [weak self] in
self?.audioTrackRemoved(trackProxy.id())
})
return trackProxy
}

/// Creates a video `MediaStreamTrackProxy` for the provided
Expand Down
14 changes: 11 additions & 3 deletions ios/Classes/controller/PeerConnectionFactoryController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,22 @@ class PeerConnectionFactoryController {
private var channel: FlutterMethodChannel

/// Initializes a new `PeerConnectionFactoryController` and
/// `PeerConnectionFactoryProxy` based on the provided `State`.
init(messenger: FlutterBinaryMessenger, state: State) {
/// `PeerConnectionFactoryProxy` based on the provided `State` and
/// `MediaDevices`.
init(
messenger: FlutterBinaryMessenger,
state: State,
mediaDevices: MediaDevices
) {
let channelName = ChannelNameGenerator.name(
name: "PeerConnectionFactory",
id: 0
)
self.messenger = messenger
self.peerFactory = PeerConnectionFactoryProxy(state: state)
self.peerFactory = PeerConnectionFactoryProxy(
state: state,
mediaDevices: mediaDevices
)
self.channel = FlutterMethodChannel(
name: channelName,
binaryMessenger: messenger
Expand Down
20 changes: 13 additions & 7 deletions ios/Classes/proxy/PeerConnectionFactoryProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ class PeerConnectionFactoryProxy {
/// Underlying native factory object of this factory.
private var factory: RTCPeerConnectionFactory

/// Instance of a `MediaDevices` manager.
private var mediaDevices: MediaDevices

/// Initializes a new `PeerConnectionFactoryProxy` based on the provided
/// `State`.
init(state: State) {
/// `State` and `MediaDevices`.
init(state: State, mediaDevices: MediaDevices) {
self.factory = state.getPeerFactory()
self.mediaDevices = mediaDevices
}

/// Returns sender capabilities of this factory.
Expand Down Expand Up @@ -86,18 +90,20 @@ class PeerConnectionFactoryProxy {
delegate: peerObserver
)
let peerProxy = PeerConnectionProxy(id: id, peer: peer!)
self.mediaDevices.peerAdded(id)
peerProxy.onDispose = { [weak self] in
guard let self = self else { return }

self.peerObservers.removeValue(forKey: id)
self.mediaDevices.peerRemoved(id)
}
peerObserver.setPeer(peer: peerProxy)

self.peerObservers[id] = peerObserver

return peerProxy
}

/// Removes the specified `PeerObserver` from the `peerObservers`.
private func remotePeerObserver(id: Int) {
self.peerObservers.removeValue(forKey: id)
}

/// Generates the next track ID.
private func nextId() -> Int {
self.lastPeerConnectionId += 1
Expand Down
4 changes: 4 additions & 0 deletions ios/Classes/proxy/PeerConnectionProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class PeerConnectionProxy {
/// Last unique ID of `RtpTransceiverProxy`s.
private var lastTransceiverId: Int = 0

/// Callback for notifying about `PeerConnectionProxy` disposal.
var onDispose: (() -> Void)?

/// Initializes a new `PeerConnectionProxy` with the provided `peer` and `id`.
init(id: Int, peer: RTCPeerConnection) {
self.peer = peer
Expand Down Expand Up @@ -289,5 +292,6 @@ class PeerConnectionProxy {
receiver.notifyRemoved()
}
self.receivers = [:]
self.onDispose?()
}
}
Loading