mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-04-17 11:48:27 +00:00
Option ui/only_allow_earpiece_during_call to enforce the use or earpiecee only during call
This commit is contained in:
parent
b82156d2f2
commit
fed6394cd5
23 changed files with 277 additions and 107 deletions
|
|
@ -276,6 +276,15 @@ class CorePreferences: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
var onlyAllowEarpieceDuringCall: Bool {
|
||||
get {
|
||||
config.getBool(section: "ui", key: "only_allow_earpiece_during_call", defaultValue: false)
|
||||
}
|
||||
set {
|
||||
config.setBool(section: "ui", key: "only_allow_earpiece_during_call", value: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
var printLogsInLogcat: Bool {
|
||||
get {
|
||||
config.getBool(section: "app", key: "debug", defaultValue: true)
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
|
||||
"notification_earpiece_enforcement_message" = "Si us plau, utilitzeu només l'auricular. Les altres sortides d'àudio estan desactivades.";
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@
|
|||
"new_conversation_title" = "Nová konverzace";
|
||||
"next" = "Další";
|
||||
"notification_missed_call_title" = "Zmeškaný hovor";
|
||||
"notification_earpiece_enforcement_message" = "Používejte pouze sluchátko. Ostatní zvukové výstupy jsou zakázány.";
|
||||
"operation_in_progress_overlay" = "Operace probíhá, prosím počkejte";
|
||||
"or" = "nebo";
|
||||
"settings_advanced_allow_outgoing_early_media_title" = "Přenášet zvuk při odchozím hovoru (early media)";
|
||||
|
|
|
|||
|
|
@ -301,6 +301,7 @@
|
|||
"new_conversation_title" = "Neuer Chat";
|
||||
"next" = "Weiter";
|
||||
"notification_missed_call_title" = "Verpasster Anruf";
|
||||
"notification_earpiece_enforcement_message" = "Bitte verwenden Sie nur den Hörer. Andere Audioausgaben sind deaktiviert.";
|
||||
"operation_in_progress_overlay" = "Vorgang wird ausgeführt, bitte warten";
|
||||
"or" = "oder";
|
||||
"password" = "Passwort";
|
||||
|
|
|
|||
|
|
@ -464,6 +464,7 @@
|
|||
"notification_chat_message_reaction_received" = "%@ reacted by %@ to: %@";
|
||||
"notification_chat_message_received_title" = "Message received";
|
||||
"notification_missed_call_title" = "Missed call";
|
||||
"notification_earpiece_enforcement_message" = "Please use the earpiece only. Other audio outputs are disabled.";
|
||||
"operation_in_progress_overlay" = "Operation in progress, please wait";
|
||||
"or" = "or";
|
||||
"password" = "Password";
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@
|
|||
"message_delivery_info_error_title" = "Error";
|
||||
"next" = "Siguiente";
|
||||
"notification_missed_call_title" = "Llamada perdida";
|
||||
"notification_earpiece_enforcement_message" = "Utilice únicamente el auricular. Las demás salidas de audio están desactivadas.";
|
||||
"or" = "o";
|
||||
"password" = "Clave";
|
||||
"phone_number" = "Número de teléfono";
|
||||
|
|
|
|||
|
|
@ -14,3 +14,4 @@
|
|||
"sip_address_domain" = "Domeinua";
|
||||
"start" = "Hasi";
|
||||
"username" = "Erabiltzaile izena";
|
||||
"notification_earpiece_enforcement_message" = "Mesedez, erabili entzungailua soilik. Beste audio-irteerak desgaituta daude.";
|
||||
|
|
|
|||
|
|
@ -27,5 +27,6 @@
|
|||
"8" = "8";
|
||||
"9" = "9";
|
||||
"account_settings_avpf_title" = "AVPF";
|
||||
"notification_earpiece_enforcement_message" = "Käytä vain kuuloketta. Muut äänilähdöt on poistettu käytöstä.";
|
||||
"ZRTP" = "ZRTP";
|
||||
"[https://sip.linphone.org](https://sip.linphone.org)" = "[https://sip.linphone.org](https://sip.linphone.org)";
|
||||
|
|
|
|||
|
|
@ -463,6 +463,7 @@
|
|||
"notification_chat_message_reaction_received" = "%@ a réagi par %@ à : %@";
|
||||
"notification_chat_message_received_title" = "Message reçu";
|
||||
"notification_missed_call_title" = "Appel manqué";
|
||||
"notification_earpiece_enforcement_message" = "Veuillez utiliser uniquement l'écouteur. Les autres sorties audio sont désactivées.";
|
||||
"operation_in_progress_overlay" = "Opération en cours, merci de patienter...";
|
||||
"or" = "ou";
|
||||
"password" = "Mot de passe";
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
"message_delivery_info_error_title" = "Hiba";
|
||||
"next" = "Következő";
|
||||
"notification_missed_call_title" = "Nem fogadott hívás";
|
||||
"notification_earpiece_enforcement_message" = "Kérjük, csak a fülhallgatót használja. A többi hangkimenet le van tiltva.";
|
||||
"or" = "vagy";
|
||||
"password" = "Jelszó";
|
||||
"phone_number" = "Telefonszám";
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
|
||||
"notification_earpiece_enforcement_message" = "Ве молиме користете само слушалка. Другите аудио излези се оневозможени.";
|
||||
|
|
|
|||
|
|
@ -291,6 +291,7 @@
|
|||
"new_conversation_title" = "Nieuw gesprek";
|
||||
"next" = "Volgende";
|
||||
"notification_missed_call_title" = "Gemiste oproep";
|
||||
"notification_earpiece_enforcement_message" = "Gebruik alleen de oortelefoon. Andere audio-uitgangen zijn uitgeschakeld.";
|
||||
"operation_in_progress_overlay" = "Bezig met bewerking, even geduld alstublieft";
|
||||
"or" = "of";
|
||||
"password" = "Wachtwoord";
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
"menu_reply_to_chat_message" = "Odpowiedz";
|
||||
"next" = "Dalej";
|
||||
"notification_missed_call_title" = "Nieodebrane połączenie";
|
||||
"notification_earpiece_enforcement_message" = "Proszę używać tylko słuchawki. Inne wyjścia audio są wyłączone.";
|
||||
"or" = "lub";
|
||||
"password" = "Hasło";
|
||||
"phone_number" = "Numer telefonu";
|
||||
|
|
|
|||
|
|
@ -310,6 +310,7 @@
|
|||
"new_conversation_title" = "Nova conversa";
|
||||
"next" = "Próximo";
|
||||
"notification_missed_call_title" = "Chamada perdida";
|
||||
"notification_earpiece_enforcement_message" = "Use apenas o auricular. As outras saídas de áudio estão desativadas.";
|
||||
"operation_in_progress_overlay" = "Operação em andamento, por favor, aguarde";
|
||||
"recordings_title" = "Gravações";
|
||||
"settings_advanced_accept_early_media_title" = "Aceitar mídia antecipada";
|
||||
|
|
|
|||
|
|
@ -6,3 +6,4 @@
|
|||
"%lld" = "%lld";
|
||||
"[https://sip.linphone.org](https://sip.linphone.org)" = "[https://sip.linphone.org](https://sip.linphone.org)";
|
||||
": %@" = ": %@";
|
||||
"notification_earpiece_enforcement_message" = "Use apenas o auricular. As outras saídas de áudio estão desativadas.";
|
||||
|
|
|
|||
|
|
@ -292,6 +292,7 @@
|
|||
"new_conversation_title" = "Новая беседа";
|
||||
"next" = "Следующий";
|
||||
"notification_missed_call_title" = "Пропущенный звонок";
|
||||
"notification_earpiece_enforcement_message" = "Пожалуйста, используйте только динамик телефона. Другие аудиовыходы отключены.";
|
||||
"operation_in_progress_overlay" = "Операция выполняется, пожалуйста, подождите";
|
||||
"or" = "или";
|
||||
"password" = "Пароль";
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@
|
|||
"menu_reply_to_chat_message" = "Odpovedať";
|
||||
"next" = "Ďalej";
|
||||
"notification_missed_call_title" = "Zmeškaný hovor";
|
||||
"notification_earpiece_enforcement_message" = "Používajte iba slúchadlo. Ostatné zvukové výstupy sú zakázané.";
|
||||
"or" = "alebo";
|
||||
"password" = "Heslo";
|
||||
"phone_number" = "Telefónne číslo";
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@
|
|||
"new_conversation_title" = "Нова розмова";
|
||||
"next" = "Наступний";
|
||||
"notification_missed_call_title" = "Пропущений виклик";
|
||||
"notification_earpiece_enforcement_message" = "Будь ласка, використовуйте лише навушник. Інші аудіовиходи вимкнено.";
|
||||
"operation_in_progress_overlay" = "Операція триває, будь ласка, зачекайте";
|
||||
"password" = "Пароль";
|
||||
"phone_number" = "Номер телефону";
|
||||
|
|
|
|||
|
|
@ -287,6 +287,7 @@
|
|||
"new_conversation_title" = "新聊天";
|
||||
"next" = "下一个";
|
||||
"notification_missed_call_title" = "未接来电";
|
||||
"notification_earpiece_enforcement_message" = "请仅使用听筒。其他音频输出已被禁用。";
|
||||
"operation_in_progress_overlay" = "操作正在进行中,请稍候";
|
||||
"or" = "或";
|
||||
"password" = "密码";
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class TelecomManager: ObservableObject {
|
|||
Log.info("Can not start a call with null address!")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if TelecomManager.callKitEnabled(core: core) {// && !nextCallIsTransfer != true {
|
||||
let uuid = UUID()
|
||||
let name = addr?.asStringUriOnly() ?? "Unknown"
|
||||
|
|
@ -383,6 +383,13 @@ class TelecomManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
static func isAudioRouteAllowedForCall() -> Bool {
|
||||
guard AppServices.corePreferences.onlyAllowEarpieceDuringCall else { return true }
|
||||
let output = AVAudioSession.sharedInstance().currentRoute.outputs.first
|
||||
Log.info("Current audio route output is \(output?.portType.rawValue ?? "Unknown")")
|
||||
return output?.portType == .builtInReceiver
|
||||
}
|
||||
|
||||
static func callKitEnabled(core: Core) -> Bool {
|
||||
#if !targetEnvironment(simulator)
|
||||
return core.callkitEnabled
|
||||
|
|
@ -650,8 +657,12 @@ class TelecomManager: ObservableObject {
|
|||
do {
|
||||
try core.setVideodevice(newValue: "AV Capture: com.apple.avfoundation.avcapturedevice.built-in_video:1")
|
||||
} catch _ {
|
||||
|
||||
|
||||
}
|
||||
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ["linphone-earpiece-enforcement"])
|
||||
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["linphone-earpiece-enforcement"])
|
||||
|
||||
withAnimation {
|
||||
self.outgoingCallStarted = false
|
||||
self.callInProgress = false
|
||||
|
|
@ -726,6 +737,10 @@ class TelecomManager: ObservableObject {
|
|||
}
|
||||
case .Released:
|
||||
TelecomManager.setAppData(sCall: call, appData: nil)
|
||||
if core.callsNb == 0 {
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ["linphone-earpiece-enforcement"])
|
||||
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["linphone-earpiece-enforcement"])
|
||||
}
|
||||
case .Referred:
|
||||
referedFromCall = call.callLog?.callId
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -2004,38 +2004,46 @@ struct CallView: View {
|
|||
.background(callViewModel.micMutted ? Color.redDanger500 : Color.gray500)
|
||||
.cornerRadius(40)
|
||||
|
||||
Button {
|
||||
if AVAudioSession.sharedInstance().availableInputs != nil
|
||||
&& !AVAudioSession.sharedInstance().availableInputs!.filter({ $0.portType.rawValue.contains("Bluetooth") }).isEmpty {
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
audioRouteSheet = true
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue == "Speaker" }).isEmpty ? .speaker : .none)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} label: {
|
||||
HStack {
|
||||
Image(imageAudioRoute)
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
.onAppear(perform: getAudioRouteImage)
|
||||
.onReceive(pub) { _ in
|
||||
self.getAudioRouteImage()
|
||||
if !callViewModel.hasAudioRouteRestriction {
|
||||
Button {
|
||||
if AVAudioSession.sharedInstance().availableInputs != nil
|
||||
&& !AVAudioSession.sharedInstance().availableInputs!.filter({ $0.portType.rawValue.contains("Bluetooth") }).isEmpty {
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
audioRouteSheet = true
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue == "Speaker" }).isEmpty ? .speaker : .none)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} label: {
|
||||
HStack {
|
||||
Image(imageAudioRoute)
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle(buttonSize: buttonSize))
|
||||
.frame(width: buttonSize, height: buttonSize)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle(buttonSize: buttonSize))
|
||||
.frame(width: buttonSize, height: buttonSize)
|
||||
.background(Color.gray500)
|
||||
.cornerRadius(40)
|
||||
Color.clear
|
||||
.frame(width: 0, height: 0)
|
||||
.onAppear {
|
||||
getAudioRouteImage()
|
||||
callViewModel.enforceEarpieceIfNeeded()
|
||||
}
|
||||
.onReceive(pub) { _ in
|
||||
self.getAudioRouteImage()
|
||||
callViewModel.enforceEarpieceIfNeeded()
|
||||
}
|
||||
}
|
||||
.frame(height: geo.size.height * 0.15)
|
||||
.padding(.horizontal, 20)
|
||||
|
|
|
|||
|
|
@ -70,72 +70,74 @@ struct AudioRouteBottomSheet: View {
|
|||
})
|
||||
.frame(maxHeight: .infinity)
|
||||
|
||||
Button(action: {
|
||||
optionsAudioRoute = 2
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}, label: {
|
||||
HStack {
|
||||
Image(optionsAudioRoute == 2 ? "radio-button-fill" : "radio-button")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
|
||||
Text("call_audio_device_type_speaker")
|
||||
.default_text_style_white(styleSize: 15)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("speaker-high")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
})
|
||||
.frame(maxHeight: .infinity)
|
||||
|
||||
Button(action: {
|
||||
optionsAudioRoute = 3
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
|
||||
try AVAudioSession.sharedInstance().setPreferredInput(
|
||||
AVAudioSession.sharedInstance().availableInputs?.filter({ $0.portType.rawValue.contains("Bluetooth") }).first)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}, label: {
|
||||
HStack {
|
||||
Image(optionsAudioRoute == 3 ? "radio-button-fill" : "radio-button")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
|
||||
Text(String(format: String(localized: "call_audio_device_type_bluetooth"),
|
||||
AVAudioSession.sharedInstance().currentRoute.outputs.first?.portName ?? ""))
|
||||
.default_text_style_white(styleSize: 15)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("bluetooth")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
})
|
||||
.frame(maxHeight: .infinity)
|
||||
if !AppServices.corePreferences.onlyAllowEarpieceDuringCall {
|
||||
Button(action: {
|
||||
optionsAudioRoute = 2
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}, label: {
|
||||
HStack {
|
||||
Image(optionsAudioRoute == 2 ? "radio-button-fill" : "radio-button")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
|
||||
Text("call_audio_device_type_speaker")
|
||||
.default_text_style_white(styleSize: 15)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("speaker-high")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
})
|
||||
.frame(maxHeight: .infinity)
|
||||
|
||||
Button(action: {
|
||||
optionsAudioRoute = 3
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
|
||||
try AVAudioSession.sharedInstance().setPreferredInput(
|
||||
AVAudioSession.sharedInstance().availableInputs?.filter({ $0.portType.rawValue.contains("Bluetooth") }).first)
|
||||
} catch _ {
|
||||
|
||||
}
|
||||
}, label: {
|
||||
HStack {
|
||||
Image(optionsAudioRoute == 3 ? "radio-button-fill" : "radio-button")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
|
||||
Text(String(format: String(localized: "call_audio_device_type_bluetooth"),
|
||||
AVAudioSession.sharedInstance().currentRoute.outputs.first?.portName ?? ""))
|
||||
.default_text_style_white(styleSize: 15)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("bluetooth")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
.padding(.all, 10)
|
||||
}
|
||||
})
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.background(Color.gray600)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import linphonesw
|
|||
import AVFAudio
|
||||
import Combine
|
||||
import SwiftUI
|
||||
import UserNotifications
|
||||
|
||||
// swiftlint:disable line_length
|
||||
// swiftlint:disable type_body_length
|
||||
|
|
@ -89,25 +90,142 @@ class CallViewModel: ObservableObject {
|
|||
@Published var letters4: String = "DD"
|
||||
|
||||
@Published var operationInProgress: Bool = false
|
||||
|
||||
@Published var hasAudioRouteRestriction: Bool = false
|
||||
@Published var audioMutedByEarpieceEnforcement: Bool = false
|
||||
|
||||
private var mCoreDelegate: CoreDelegate?
|
||||
private var chatRoomDelegate: ChatRoomDelegate?
|
||||
|
||||
|
||||
private static let earpieceNotificationIdentifier = "linphone-earpiece-enforcement"
|
||||
private var isEnforcingEarpiece: Bool = false
|
||||
private var routeChangeObserver: Any?
|
||||
|
||||
init() {
|
||||
// Not needed since call audio configuration (AVAudioSession) is handled by the SDK
|
||||
/*
|
||||
do {
|
||||
try configureAudio(.call)
|
||||
} catch {
|
||||
print("Audio session error: \(error)")
|
||||
}
|
||||
*/
|
||||
hasAudioRouteRestriction = AppServices.corePreferences.onlyAllowEarpieceDuringCall
|
||||
|
||||
NotificationCenter.default.addObserver(forName: Notification.Name("CallViewModelReset"), object: nil, queue: nil) { notification in
|
||||
self.resetCallView()
|
||||
}
|
||||
|
||||
if hasAudioRouteRestriction {
|
||||
routeChangeObserver = NotificationCenter.default.addObserver(
|
||||
forName: AVAudioSession.routeChangeNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
self?.enforceEarpieceIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let observer = routeChangeObserver {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
|
||||
func isRouteAllowed() -> Bool {
|
||||
guard AppServices.corePreferences.onlyAllowEarpieceDuringCall else { return true }
|
||||
let output = AVAudioSession.sharedInstance().currentRoute.outputs.first
|
||||
return output?.portType == .builtInReceiver
|
||||
}
|
||||
|
||||
func enforceEarpieceIfNeeded() {
|
||||
guard hasAudioRouteRestriction else { return }
|
||||
|
||||
if isRouteAllowed() {
|
||||
if audioMutedByEarpieceEnforcement {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
if let call = self.currentCall {
|
||||
call.microphoneMuted = false
|
||||
core.micEnabled = true
|
||||
let micMuttedTmp = call.microphoneMuted || !core.micEnabled
|
||||
DispatchQueue.main.async {
|
||||
self.micMutted = micMuttedTmp
|
||||
self.audioMutedByEarpieceEnforcement = false
|
||||
}
|
||||
Log.info("\(CallViewModel.TAG) Earpiece restored, unmuting audio")
|
||||
}
|
||||
}
|
||||
cancelEarpieceNotification()
|
||||
}
|
||||
} else {
|
||||
guard !isEnforcingEarpiece else { return }
|
||||
isEnforcingEarpiece = true
|
||||
|
||||
Log.info("\(CallViewModel.TAG) Disallowed audio route detected, muting and forcing earpiece")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.micMutted = true
|
||||
self.audioMutedByEarpieceEnforcement = true
|
||||
}
|
||||
|
||||
self.forceEarpiece()
|
||||
self.postEarpieceEnforcementNotification()
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
if !self.isRouteAllowed() {
|
||||
self.forceEarpiece()
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
self.isEnforcingEarpiece = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func forceEarpiece() {
|
||||
coreContext.doOnCoreQueue { core in
|
||||
if let call = self.currentCall {
|
||||
call.microphoneMuted = true
|
||||
AudioRouteUtils.routeAudioToEarpiece(core: core, call: call)
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
let session = AVAudioSession.sharedInstance()
|
||||
try session.overrideOutputAudioPort(.none)
|
||||
let receiver = session.availableInputs?.first(where: { $0.portType.rawValue.contains("Receiver") })
|
||||
?? session.availableInputs?.first
|
||||
try session.setPreferredInput(receiver)
|
||||
} catch {
|
||||
Log.error("\(CallViewModel.TAG) Failed to override audio port to earpiece: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func postEarpieceEnforcementNotification() {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = "Linphone"
|
||||
content.body = String(localized: "notification_earpiece_enforcement_message")
|
||||
content.sound = .default
|
||||
|
||||
let request = UNNotificationRequest(
|
||||
identifier: CallViewModel.earpieceNotificationIdentifier,
|
||||
content: content,
|
||||
trigger: nil
|
||||
)
|
||||
|
||||
UNUserNotificationCenter.current().add(request) { error in
|
||||
if let error = error {
|
||||
Log.error("\(CallViewModel.TAG) Failed to post earpiece notification: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func cancelEarpieceNotification() {
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(
|
||||
withIdentifiers: [CallViewModel.earpieceNotificationIdentifier]
|
||||
)
|
||||
UNUserNotificationCenter.current().removePendingNotificationRequests(
|
||||
withIdentifiers: [CallViewModel.earpieceNotificationIdentifier]
|
||||
)
|
||||
}
|
||||
|
||||
func resetCallView() {
|
||||
cancelEarpieceNotification()
|
||||
audioMutedByEarpieceEnforcement = false
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.displayName = ""
|
||||
self.avatarModel = nil
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue