mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
ios: audio device picker
This commit is contained in:
@@ -179,7 +179,7 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
|
||||
if playback {
|
||||
if AVAudioSession.sharedInstance().category != .playback {
|
||||
logger.log("AudioSession: playback")
|
||||
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: .duckOthers)
|
||||
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: [.duckOthers, .allowBluetooth, .allowAirPlay, .allowBluetoothA2DP])
|
||||
}
|
||||
} else {
|
||||
if AVAudioSession.sharedInstance().category != .soloAmbient {
|
||||
|
||||
@@ -111,7 +111,7 @@ struct ActiveCallView: View {
|
||||
call.localCapabilities = capabilities
|
||||
}
|
||||
if call.supportsVideo {
|
||||
try? AVAudioSession.sharedInstance().setCategory(.playback, options: .defaultToSpeaker)
|
||||
try? AVAudioSession.sharedInstance().setCategory(.playback, options: [.allowBluetooth, .allowAirPlay, .allowBluetoothA2DP])
|
||||
}
|
||||
CallSoundsPlayer.shared.startConnectingCallSound()
|
||||
activeCallWaitDeliveryReceipt()
|
||||
@@ -236,6 +236,7 @@ struct ActiveCallOverlay: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@ObservedObject var call: Call
|
||||
var client: WebRTCClient
|
||||
let callAudioDeviceManager = CallAudioDeviceManager()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
@@ -251,7 +252,13 @@ struct ActiveCallOverlay: View {
|
||||
HStack {
|
||||
toggleAudioButton()
|
||||
Spacer()
|
||||
Color.clear.frame(width: 40, height: 40)
|
||||
if AVAudioSession.sharedInstance().availableInputs?.allSatisfy({ $0.portType == .builtInMic }) == true {
|
||||
Color.clear.frame(width: 40, height: 40)
|
||||
} else {
|
||||
AudioDevicePicker()
|
||||
.scaleEffect(1.8)
|
||||
.frame(maxWidth: 40, maxHeight: 40)
|
||||
}
|
||||
Spacer()
|
||||
endCallButton()
|
||||
Spacer()
|
||||
@@ -292,14 +299,28 @@ struct ActiveCallOverlay: View {
|
||||
toggleAudioButton()
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
endCallButton()
|
||||
toggleSpeakerButton()
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
// Check if the only input is microphone. And in this case show toggle button, If there are more inputs, it probably means something like bluetooth headphones are available and in this case show iOS button for choosing different output. There is no way to get available outputs, only inputs
|
||||
if AVAudioSession.sharedInstance().availableInputs?.allSatisfy({ $0.portType == .builtInMic }) == true {
|
||||
toggleSpeakerButton()
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
} else {
|
||||
AudioDevicePicker()
|
||||
.scaleEffect(1.8)
|
||||
.frame(maxWidth: 50, maxHeight: 40)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 60)
|
||||
.padding(.horizontal, 48)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.onAppear {
|
||||
callAudioDeviceManager.start()
|
||||
}
|
||||
.onDisappear {
|
||||
callAudioDeviceManager.stop()
|
||||
}
|
||||
}
|
||||
|
||||
private func audioCallInfoView(_ call: Call) -> some View {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// MPVolumeView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Stanislav on 24.04.2024.
|
||||
// Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import MediaPlayer
|
||||
|
||||
struct AudioDevicePicker: UIViewRepresentable {
|
||||
func makeUIView(context: Context) -> some UIView {
|
||||
let v = MPVolumeView(frame: .zero)
|
||||
v.showsVolumeSlider = false
|
||||
return v
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UIViewType, context: Context) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// CallAudioDeviceManager.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Avently on 23.04.2024.
|
||||
// Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
import AVKit
|
||||
|
||||
class CallAudioDeviceManager {
|
||||
let audioSession: AVAudioSession
|
||||
let nc = NotificationCenter.default
|
||||
|
||||
@State var devices: [AVAudioSessionPortDescription]
|
||||
@State var currentDevice: AVAudioSessionPortDescription? = nil
|
||||
@State var availableInputs: [AVAudioSessionPortDescription] = []
|
||||
|
||||
|
||||
init(_ audioSession: AVAudioSession? = nil) {
|
||||
self.audioSession = audioSession ?? AVAudioSession.sharedInstance()
|
||||
self.devices = self.audioSession.currentRoute.outputs
|
||||
self.availableInputs = self.audioSession.availableInputs ?? []
|
||||
}
|
||||
|
||||
@objc func audioCallback(notification: Notification) {
|
||||
logger.debug("LALAL CALLED")
|
||||
guard let userInfo = notification.userInfo,
|
||||
let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
|
||||
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
|
||||
return
|
||||
}
|
||||
|
||||
switch reason {
|
||||
case .newDeviceAvailable:
|
||||
devices = audioSession.currentRoute.outputs
|
||||
case .oldDeviceUnavailable:
|
||||
devices = audioSession.currentRoute.outputs
|
||||
case .override:
|
||||
currentDevice = audioSession.currentRoute.outputs.first
|
||||
default: ()
|
||||
}
|
||||
self.availableInputs = audioSession.availableInputs ?? []
|
||||
logger.debug("LALAL inputs \(String(describing: self.availableInputs))")
|
||||
}
|
||||
|
||||
func start() {
|
||||
logger.debug("LALAL START")
|
||||
nc.addObserver(self, selector: #selector(audioCallback), name: AVAudioSession.routeChangeNotification, object: nil)
|
||||
}
|
||||
|
||||
func stop() {
|
||||
logger.debug("LALAL STOP")
|
||||
nc.removeObserver(self, name: AVAudioSession.routeChangeNotification, object: nil)
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
|
||||
RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
|
||||
RTCAudioSession.sharedInstance().isAudioEnabled = true
|
||||
do {
|
||||
try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: .mixWithOthers)
|
||||
try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.mixWithOthers, .allowBluetooth, .allowAirPlay, .allowBluetoothA2DP])
|
||||
logger.debug("audioSession category set")
|
||||
try audioSession.setActive(true)
|
||||
logger.debug("audioSession activated")
|
||||
|
||||
@@ -605,10 +605,12 @@ extension WebRTCClient {
|
||||
self.rtcAudioSession.unlockForConfiguration()
|
||||
}
|
||||
do {
|
||||
try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue)
|
||||
try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue, with: [.allowBluetooth, .allowAirPlay, .allowBluetoothA2DP])
|
||||
try self.rtcAudioSession.setMode(AVAudioSession.Mode.voiceChat.rawValue)
|
||||
try self.rtcAudioSession.overrideOutputAudioPort(enabled ? .speaker : .none)
|
||||
try self.rtcAudioSession.setActive(true)
|
||||
logger.debug("LALAL AUDIO in \(self.rtcAudioSession.currentRoute.inputs.description)")
|
||||
logger.debug("LALAL AUDIO out \(self.rtcAudioSession.currentRoute.outputs.description)")
|
||||
logger.debug("WebRTCClient: configuring session with speaker enabled \(enabled) success")
|
||||
} catch let error {
|
||||
logger.debug("Error configuring AVAudioSession: \(error)")
|
||||
|
||||
@@ -188,6 +188,8 @@
|
||||
8C69FE7D2B8C7D2700267E38 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */; };
|
||||
8C7D949A2B88952700B7B9E1 /* MigrateToDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7D94992B88952700B7B9E1 /* MigrateToDevice.swift */; };
|
||||
8C7DF3202B7CDB0A00C886D0 /* MigrateFromDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7DF31F2B7CDB0A00C886D0 /* MigrateFromDevice.swift */; };
|
||||
8C81482C2BD91CD4002CBEC3 /* AudioDevicePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C81482B2BD91CD4002CBEC3 /* AudioDevicePicker.swift */; };
|
||||
8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */; };
|
||||
8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; };
|
||||
D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; };
|
||||
D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A9087294BD7A70047C86D /* NativeTextEditor.swift */; };
|
||||
@@ -483,6 +485,8 @@
|
||||
8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
|
||||
8C7D94992B88952700B7B9E1 /* MigrateToDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToDevice.swift; sourceTree = "<group>"; };
|
||||
8C7DF31F2B7CDB0A00C886D0 /* MigrateFromDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateFromDevice.swift; sourceTree = "<group>"; };
|
||||
8C81482B2BD91CD4002CBEC3 /* AudioDevicePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioDevicePicker.swift; sourceTree = "<group>"; };
|
||||
8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallAudioDeviceManager.swift; sourceTree = "<group>"; };
|
||||
8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = "<group>"; };
|
||||
D72A9087294BD7A70047C86D /* NativeTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeTextEditor.swift; sourceTree = "<group>"; };
|
||||
D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
@@ -549,6 +553,8 @@
|
||||
5C55A922283CEDE600C4E99E /* SoundPlayer.swift */,
|
||||
18415323A4082FC92887F906 /* WebRTCClient.swift */,
|
||||
18415B08031E8FB0F7FC27F9 /* CallViewRenderers.swift */,
|
||||
8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */,
|
||||
8C81482B2BD91CD4002CBEC3 /* AudioDevicePicker.swift */,
|
||||
);
|
||||
path = Call;
|
||||
sourceTree = "<group>";
|
||||
@@ -1237,6 +1243,7 @@
|
||||
5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */,
|
||||
5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */,
|
||||
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */,
|
||||
8C81482C2BD91CD4002CBEC3 /* AudioDevicePicker.swift in Sources */,
|
||||
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */,
|
||||
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */,
|
||||
5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */,
|
||||
@@ -1266,6 +1273,7 @@
|
||||
18415B0585EB5A9A0A7CA8CD /* PressedButtonStyle.swift in Sources */,
|
||||
1841560FD1CD447955474C1D /* UserProfilesView.swift in Sources */,
|
||||
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */,
|
||||
8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */,
|
||||
18415C6C56DBCEC2CBBD2F11 /* WebRTCClient.swift in Sources */,
|
||||
184152CEF68D2336FC2EBCB0 /* CallViewRenderers.swift in Sources */,
|
||||
5CB634AD29E46CF70066AD6B /* LocalAuthView.swift in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user