WIP: bluetooth shows audio route button instead of speakerphone
// FREEBIE TODO NEED -[ ] icon in route picker -[ ] commit cleanup NICE -[ ] present action sheet automatically when making outgoing bluetooth call -[ ] left align icons -[ ] audio is paused when switching between video mode (maybe existing behavior, not sure) -[ ] Copy: iPhone/iPad/iPod instead of "iPhone Microphone" DONE -[x] remove "receiver" from options while in video mode -[x] show available audio routes -[x] select available audio routes -[x] notification if availabe inputs change so we can update call screen mid call with available BT route -[x] include speakerphone in choices -[x] Enabled button shows active speakerphone. Should still show bluetooth picker. -[x] toggle back and forth between audio devices -[x] hide audio route button in video mode if no BT available -[x] Fixed: When on speakerphone - switching to video mode goes back to bluetooth. -[x] Fixed: When switching to video w/ bluetooth device connected there is no audio picker. -[x] respect speakerphone/BT selection when in or toggling to/from video -[x] do not hide audio route button when in video mode and bluetooth connected -[x] Show which is currently selected audio route -[x] switching to speakerphone no longer works -[x] switching *back* to bluetooth no longer works -[x] add proper bluetooth button for audio calls -[x] add proper bluetooth button for video calls
This commit is contained in:
parent
109cb6cdb6
commit
9bd68ed490
21
Signal/Images.xcassets/ic_speaker_bluetooth_inactive_audio_mode.imageset/Contents.json
vendored
Normal file
21
Signal/Images.xcassets/ic_speaker_bluetooth_inactive_audio_mode.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_speaker_bluetooth_inactive_audio_mode.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
21
Signal/Images.xcassets/ic_speaker_bluetooth_inactive_video_mode.imageset/Contents.json
vendored
Normal file
21
Signal/Images.xcassets/ic_speaker_bluetooth_inactive_video_mode.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_speaker_bluetooth_inactive_video_mode.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
|
@ -7,6 +7,11 @@ import Foundation
|
|||
/**
|
||||
* Strings re-used in multiple places should be added here.
|
||||
*/
|
||||
|
||||
@objc class CommonStrings: NSObject {
|
||||
static let dismissActionText = NSLocalizedString("DISMISS_BUTTON_TEXT", comment: "Short text to dismiss current modal / actionsheet / screen")
|
||||
}
|
||||
|
||||
@objc class CallStrings: NSObject {
|
||||
|
||||
static let callStatusFormat = NSLocalizedString("CALL_STATUS_FORMAT", comment: "embeds {{Call Status}} in call screen label. For ongoing calls, {{Call Status}} is a seconds timer like 01:23, otherwise {{Call Status}} is a short text like 'Ringing', 'Busy', or 'Failed Call'")
|
||||
|
|
|
@ -41,7 +41,8 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
|
|||
var ongoingCallView: UIView!
|
||||
|
||||
var hangUpButton: UIButton!
|
||||
var speakerPhoneButton: UIButton!
|
||||
var audioRouteButton: UIButton!
|
||||
var soundRouteButton: UIButton!
|
||||
var audioModeMuteButton: UIButton!
|
||||
var audioModeVideoButton: UIButton!
|
||||
var videoModeMuteButton: UIButton!
|
||||
|
@ -86,11 +87,70 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
|
|||
var settingsNagView: UIView!
|
||||
var settingsNagDescriptionLabel: UILabel!
|
||||
|
||||
// MARK: Audio Routing
|
||||
|
||||
// var hasAlternateAudioRoutes = false {
|
||||
// didSet {
|
||||
// if oldValue != hasAlternateAudioRoutes {
|
||||
// updateCallUI(callState: call.state)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// TODO use "audioSource" terminalogy rather than input/output/route
|
||||
var hasAlternateAudioRoutes: Bool {
|
||||
Logger.info("\(TAG) available audio routes count: \(allAvailableAudioRoutes.count)")
|
||||
// internal mic and speakerphone will be the first two, any more than one indicates e.g. an attached bluetooth device.
|
||||
// TODO is this sufficient? Are their devices w/ bluetooth but no external speaker? e.g. ipod?
|
||||
return allAvailableAudioRoutes.count > 2
|
||||
}
|
||||
|
||||
var allAvailableAudioRoutes: Set<AudioSource>
|
||||
|
||||
var availableAudioRoutes: Set<AudioSource> {
|
||||
if call.hasLocalVideo {
|
||||
let forVideo = allAvailableAudioRoutes.filter { audioSource in
|
||||
if audioSource.isBuiltInSpeaker {
|
||||
return true
|
||||
} else {
|
||||
guard let portDescription = audioSource.portDescription else {
|
||||
owsFail("Only built in speaker should be lacking a port description.")
|
||||
return false
|
||||
}
|
||||
return portDescription.portType != AVAudioSessionPortBuiltInMic
|
||||
}
|
||||
}
|
||||
return Set(forVideo)
|
||||
} else {
|
||||
return allAvailableAudioRoutes
|
||||
}
|
||||
}
|
||||
|
||||
var audioSource: AudioSource? {
|
||||
didSet {
|
||||
if audioSource != oldValue {
|
||||
if let audioSource = audioSource {
|
||||
if audioSource.isBuiltInSpeaker {
|
||||
// TODO seems like CVC knows too much about AudioSource.
|
||||
// Maybe these conditionals belong in the callUIAdapter? Or audioService?
|
||||
// self.callUIAdapter.audioService.setPreferredInput(audioSource: audioSource)
|
||||
|
||||
self.callUIAdapter.setIsSpeakerphoneEnabled(call: self.call, isEnabled: true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.callUIAdapter.setIsSpeakerphoneEnabled(call: self.call, isEnabled: false)
|
||||
self.callUIAdapter.audioService.setPreferredInput(call: self.call, audioSource: audioSource)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
contactsManager = Environment.getCurrent().contactsManager
|
||||
callUIAdapter = Environment.getCurrent().callUIAdapter
|
||||
allAvailableAudioRoutes = Set(callUIAdapter.audioService.availableInputs)
|
||||
super.init(coder: aDecoder)
|
||||
observeNotifications()
|
||||
}
|
||||
|
@ -98,6 +158,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
|
|||
required init() {
|
||||
contactsManager = Environment.getCurrent().contactsManager
|
||||
callUIAdapter = Environment.getCurrent().callUIAdapter
|
||||
allAvailableAudioRoutes = Set(callUIAdapter.audioService.availableInputs)
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
observeNotifications()
|
||||
}
|
||||
|
@ -107,6 +168,11 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
|
|||
selector:#selector(didBecomeActive),
|
||||
name:NSNotification.Name.UIApplicationDidBecomeActive,
|
||||
object:nil)
|
||||
|
||||
NotificationCenter.default.addObserver(forName: CallAudioServiceSessionChanged, object: nil, queue: nil) { _ in
|
||||
self.didChangeAudioSession()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
@ -157,7 +223,7 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
|
|||
// Subscribe for future call updates
|
||||
call.addObserverAndSyncState(observer: self)
|
||||
|
||||
Environment.getCurrent().callService.addObserverAndSyncState(observer:self)
|
||||
Environment.getCurrent().callService.addObserverAndSyncState(observer: self)
|
||||
}
|
||||
|
||||
// MARK: - Create Views
|
||||
|
@ -288,8 +354,8 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
|
|||
|
||||
// textMessageButton = createButton(imageName:"message-active-wide",
|
||||
// action:#selector(didPressTextMessage))
|
||||
speakerPhoneButton = createButton(imageName:"audio-call-speaker-inactive",
|
||||
action:#selector(didPressSpeakerphone))
|
||||
audioRouteButton = createButton(imageName:"audio-call-speaker-inactive",
|
||||
action:#selector(didPressAudioRoute))
|
||||
hangUpButton = createButton(imageName:"hangup-active-wide",
|
||||
action:#selector(didPressHangup))
|
||||
audioModeMuteButton = createButton(imageName:"audio-call-mute-inactive",
|
||||
|
@ -305,12 +371,67 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
|
|||
setButtonSelectedImage(button: videoModeMuteButton, imageName: "video-mute-selected")
|
||||
setButtonSelectedImage(button: audioModeVideoButton, imageName: "audio-call-video-active")
|
||||
setButtonSelectedImage(button: videoModeVideoButton, imageName: "video-video-selected")
|
||||
setButtonSelectedImage(button: speakerPhoneButton, imageName: "audio-call-speaker-active")
|
||||
// setButtonSelectedImage(button: audioRouteButton, imageName: "audio-call-speaker-active")
|
||||
|
||||
ongoingCallView = createContainerForCallControls(controlGroups : [
|
||||
[audioModeMuteButton, speakerPhoneButton, audioModeVideoButton ],
|
||||
[audioModeMuteButton, audioRouteButton, audioModeVideoButton ],
|
||||
[videoModeMuteButton, hangUpButton, videoModeVideoButton ]
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
func didChangeAudioSession() {
|
||||
AssertIsOnMainThread()
|
||||
// TODO unnecessary?
|
||||
let availableInputs = callUIAdapter.audioService.availableInputs
|
||||
self.allAvailableAudioRoutes.formUnion(availableInputs)
|
||||
}
|
||||
|
||||
func presentAudioRoutePicker() {
|
||||
Logger.info("\(TAG) in \(#function)")
|
||||
AssertIsOnMainThread()
|
||||
|
||||
let actionSheetController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
|
||||
let dismissAction = UIAlertAction(title: CommonStrings.dismissActionText, style: .cancel, handler: nil)
|
||||
actionSheetController.addAction(dismissAction)
|
||||
|
||||
let currentAudioSource = callUIAdapter.audioService.currentAudioSource(call: self.call)
|
||||
for audioSource in self.availableAudioRoutes {
|
||||
// TODO add image
|
||||
let routeAudioAction = UIAlertAction(title: audioSource.localizedName, style: .default) { _ in
|
||||
// Disable any speakerphone
|
||||
// TODO will this update the UI appropriately?
|
||||
self.audioSource = audioSource
|
||||
}
|
||||
|
||||
// HACK private API to create checkmark for active audio source.
|
||||
routeAudioAction.setValue(currentAudioSource == audioSource, forKey: "checked")
|
||||
|
||||
// HACK private API to add image to actionsheet
|
||||
routeAudioAction.setValue(audioSource.image, forKey: "image")
|
||||
|
||||
actionSheetController.addAction(routeAudioAction)
|
||||
}
|
||||
|
||||
// if let builtInMicrophoneSource = self.callUIAdapter.audioService.builtInMicrophoneSource {
|
||||
// Speakerphone is handled separately from the other audio routes as it doesn't appear as an "input"
|
||||
// let speakerphoneAction = UIAlertAction(title:
|
||||
// style: .default) { _ in
|
||||
// self.updateAudioOutput(audioSource: builtInMicrophoneSource)
|
||||
//
|
||||
// }
|
||||
// actionSheetController.addAction(speakerphoneAction)
|
||||
// } else {
|
||||
// owsFail("unable to find built in microphone source")
|
||||
// }
|
||||
|
||||
self.present(actionSheetController, animated: true)
|
||||
}
|
||||
|
||||
func updateAudioOutput(audioSource: AudioSource) {
|
||||
Logger.info("\(TAG) in \(#function) with audioSource: \(audioSource)")
|
||||
// This seems like overreach. audioservice as property on CVC?
|
||||
|
||||
}
|
||||
|
||||
func setButtonSelectedImage(button: UIButton, imageName: String) {
|
||||
|
@ -653,7 +774,6 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
|
|||
videoModeMuteButton.isSelected = call.isMuted
|
||||
audioModeVideoButton.isSelected = call.hasLocalVideo
|
||||
videoModeVideoButton.isSelected = call.hasLocalVideo
|
||||
speakerPhoneButton.isSelected = call.isSpeakerphoneEnabled
|
||||
|
||||
// Show Incoming vs. Ongoing call controls
|
||||
let isRinging = callState == .localRinging
|
||||
|
@ -668,7 +788,8 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
|
|||
|
||||
// Rework control state if local video is available.
|
||||
let hasLocalVideo = !localVideoView.isHidden
|
||||
for subview in [speakerPhoneButton, audioModeMuteButton, audioModeVideoButton] {
|
||||
|
||||
for subview in [audioModeMuteButton, audioModeVideoButton] {
|
||||
subview?.isHidden = hasLocalVideo
|
||||
}
|
||||
for subview in [videoModeMuteButton, videoModeVideoButton] {
|
||||
|
@ -685,6 +806,35 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
|
|||
callStatusLabel.isHidden = false
|
||||
}
|
||||
|
||||
// Handle audio source picking interface (blue tooth)
|
||||
if self.hasAlternateAudioRoutes {
|
||||
// TODO proper image
|
||||
Logger.info("\(TAG) in \(#function) setting alternate audio route image")
|
||||
|
||||
// With bluetooth, button does not stay selected. Pressing it pops an actionsheet
|
||||
// and the button should immediately "unselect".
|
||||
audioRouteButton.isSelected = false
|
||||
|
||||
if hasLocalVideo {
|
||||
audioRouteButton.setImage(#imageLiteral(resourceName: "ic_speaker_bluetooth_inactive_video_mode"), for: .normal)
|
||||
audioRouteButton.setImage(#imageLiteral(resourceName: "ic_speaker_bluetooth_inactive_video_mode"), for: .selected)
|
||||
} else {
|
||||
audioRouteButton.setImage(#imageLiteral(resourceName: "ic_speaker_bluetooth_inactive_audio_mode"), for: .normal)
|
||||
audioRouteButton.setImage(#imageLiteral(resourceName: "ic_speaker_bluetooth_inactive_audio_mode"), for: .selected)
|
||||
}
|
||||
audioRouteButton.isHidden = false
|
||||
} else {
|
||||
// No bluetooth audio detected
|
||||
|
||||
audioRouteButton.isSelected = call.isSpeakerphoneEnabled
|
||||
audioRouteButton.setImage(#imageLiteral(resourceName: "audio-call-speaker-inactive"), for: .normal)
|
||||
audioRouteButton.setImage(#imageLiteral(resourceName: "audio-call-speaker-active"), for: .selected)
|
||||
|
||||
// If there's no bluetooth, we always use speakerphone, so no need for
|
||||
// a button, giving more screen back for the video.
|
||||
audioRouteButton.isHidden = hasLocalVideo
|
||||
}
|
||||
|
||||
// Dismiss Handling
|
||||
switch callState {
|
||||
case .remoteHangup, .remoteBusy, .localFailure:
|
||||
|
@ -742,6 +892,16 @@ class CallViewController: UIViewController, CallObserver, CallServiceObserver, R
|
|||
}
|
||||
}
|
||||
|
||||
func didPressAudioRoute(sender button: UIButton) {
|
||||
Logger.info("\(TAG) called \(#function)")
|
||||
|
||||
if self.hasAlternateAudioRoutes {
|
||||
presentAudioRoutePicker()
|
||||
} else {
|
||||
didPressSpeakerphone(sender: button)
|
||||
}
|
||||
}
|
||||
|
||||
func didPressSpeakerphone(sender button: UIButton) {
|
||||
Logger.info("\(TAG) called \(#function)")
|
||||
button.isSelected = !button.isSelected
|
||||
|
|
|
@ -5,6 +5,77 @@
|
|||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
public let CallAudioServiceSessionChanged = Notification.Name("CallAudioServiceSessionChanged")
|
||||
|
||||
struct AudioSource: Hashable {
|
||||
|
||||
// let name: String
|
||||
let image: UIImage
|
||||
let localizedName: String
|
||||
let portDescription: AVAudioSessionPortDescription?
|
||||
let isBuiltInSpeaker: Bool
|
||||
|
||||
// init(name: String, image: UIImage, isCurrentRoute: Bool) {
|
||||
//
|
||||
// }
|
||||
//
|
||||
|
||||
init(localizedName: String, image: UIImage, isBuiltInSpeaker: Bool, portDescription: AVAudioSessionPortDescription? = nil) {
|
||||
self.localizedName = localizedName
|
||||
self.image = image
|
||||
self.isBuiltInSpeaker = isBuiltInSpeaker
|
||||
self.portDescription = portDescription
|
||||
}
|
||||
|
||||
init(portDescription: AVAudioSessionPortDescription) {
|
||||
self.init(localizedName: portDescription.portName,
|
||||
image:#imageLiteral(resourceName: "button_phone_white"), // TODO
|
||||
isBuiltInSpeaker: false,
|
||||
portDescription: portDescription)
|
||||
}
|
||||
|
||||
// Speakerphone is handled separately from the other audio routes as it doesn't appear as an "input"
|
||||
static var builtInSpeaker: AudioSource {
|
||||
return self.init(localizedName: NSLocalizedString("AUDIO_ROUTE_BUILT_IN_SPEAKER", comment: "action sheet button title to enable built in speaker during a call"),
|
||||
image: #imageLiteral(resourceName: "button_phone_white"), //TODO
|
||||
isBuiltInSpeaker: true)
|
||||
}
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
static func ==(lhs: AudioSource, rhs: AudioSource) -> Bool {
|
||||
// Simply comparing the `portDescription` vs the `portDescription.uid`
|
||||
// caused multiple instances of the built in mic to turn up in a set.
|
||||
if lhs.isBuiltInSpeaker && rhs.isBuiltInSpeaker {
|
||||
return true
|
||||
}
|
||||
|
||||
if lhs.isBuiltInSpeaker || rhs.isBuiltInSpeaker {
|
||||
return false
|
||||
}
|
||||
|
||||
guard let lhsPortDescription = lhs.portDescription else {
|
||||
owsFail("only the built in speaker should lack a port description")
|
||||
return false
|
||||
}
|
||||
|
||||
guard let rhsPortDescription = rhs.portDescription else {
|
||||
owsFail("only the built in speaker should lack a port description")
|
||||
return false
|
||||
}
|
||||
|
||||
return lhsPortDescription.uid == rhsPortDescription.uid
|
||||
}
|
||||
|
||||
var hashValue: Int {
|
||||
guard let portDescription = self.portDescription else {
|
||||
assert(self.isBuiltInSpeaker)
|
||||
return "Built In Speaker".hashValue
|
||||
}
|
||||
return portDescription.uid.hash
|
||||
}
|
||||
}
|
||||
|
||||
@objc class CallAudioService: NSObject, CallObserver {
|
||||
|
||||
private let TAG = "[CallAudioService]"
|
||||
|
@ -98,14 +169,17 @@ import AVFoundation
|
|||
setAudioSession(category: AVAudioSessionCategorySoloAmbient,
|
||||
mode: AVAudioSessionModeDefault)
|
||||
} else if call.hasLocalVideo {
|
||||
// Auto-enable speakerphone when local video is enabled.
|
||||
// Don't allow bluetooth for local video if speakerphone has been explicitly chosen by the user.
|
||||
let options: AVAudioSessionCategoryOptions = call.isSpeakerphoneEnabled ? [.defaultToSpeaker] : [.defaultToSpeaker, .allowBluetooth]
|
||||
|
||||
setAudioSession(category: AVAudioSessionCategoryPlayAndRecord,
|
||||
mode: AVAudioSessionModeVideoChat,
|
||||
options: [.defaultToSpeaker, .allowBluetooth])
|
||||
options: options)
|
||||
} else if call.isSpeakerphoneEnabled {
|
||||
// Ensure no bluetooth if user has specified speakerphone
|
||||
setAudioSession(category: AVAudioSessionCategoryPlayAndRecord,
|
||||
mode: AVAudioSessionModeVoiceChat,
|
||||
options: [.defaultToSpeaker, .allowBluetooth])
|
||||
options: [.defaultToSpeaker])
|
||||
} else {
|
||||
setAudioSession(category: AVAudioSessionCategoryPlayAndRecord,
|
||||
mode: AVAudioSessionModeVoiceChat,
|
||||
|
@ -308,11 +382,102 @@ import AVFoundation
|
|||
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
|
||||
}
|
||||
|
||||
// MARK - AudioSession MGMT
|
||||
// TODO move this to CallAudioSession?
|
||||
|
||||
var hasAlternateAudioRoutes: Bool {
|
||||
// let session = AVAudioSession.sharedInstance()
|
||||
|
||||
// PROBLEM: doesn't list bluetooth when speakerphone is enabled.
|
||||
// guard let availableInputs = session.availableInputs else {
|
||||
// // I'm not sure when this would happen.
|
||||
// owsFail("No available inputs or inputs not ready")
|
||||
// return false
|
||||
// }
|
||||
|
||||
//
|
||||
// let availableInputs = session.currentRoute.inputs
|
||||
|
||||
Logger.info("\(TAG) in \(#function) availableInputs: \(availableInputs)")
|
||||
for input in self.availableInputs {
|
||||
if input.portDescription?.portType == AVAudioSessionPortBluetoothHFP {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Note this method is sensitive to the current audio session configuration.
|
||||
// Specifically if you call it while speakerphone is enabled you won't see
|
||||
// any connected bluetooth routes.
|
||||
var availableInputs: [AudioSource] {
|
||||
let session = AVAudioSession.sharedInstance()
|
||||
// guard let availableOutputs = session.outputDataSources else {
|
||||
|
||||
// Maybe... shows the bluetooth AND the receiver (but not speaker)
|
||||
// PROBLEM: doesn't list bluetooth when speakerphone is enabled.
|
||||
guard let availableInputs = session.availableInputs else {
|
||||
// I'm not sure when this would happen.
|
||||
owsFail("No available inputs or inputs not ready")
|
||||
return [AudioSource.builtInSpeaker]
|
||||
}
|
||||
|
||||
// PROBLEM: doesn't list iphone internal
|
||||
// PROBLEM: doesn't list bluetooth until toggling speakerphone on/off
|
||||
// let availableInputs = session.currentRoute.inputs
|
||||
// let availableInputs = session.currentRoute.outputs
|
||||
|
||||
// NOPE. only shows the single active one. (e.g. blue tooth XOR receive)
|
||||
// let availableOutputs = session.currentRoute.outputs
|
||||
|
||||
Logger.info("\(TAG) in \(#function) availableInputs: \(availableInputs)")
|
||||
return [AudioSource.builtInSpeaker] + availableInputs.map { portDescription in
|
||||
// TODO get proper image
|
||||
// TODO set isCurrentRoute correctly
|
||||
// return AudioSource(name: output.dataSourceName, image:#imageLiteral(resourceName: "button_phone_white"), isCurrentRoute: false)
|
||||
// return AudioSource(name: output.portName, image:#imageLiteral(resourceName: "button_phone_white"), isCurrentRoute: false)
|
||||
|
||||
return AudioSource(portDescription: portDescription)
|
||||
}
|
||||
}
|
||||
|
||||
// var builtInMicrophoneSource: AudioSource? {
|
||||
// availableInputs.first { source -> Bool in
|
||||
// if source.uid =
|
||||
// }
|
||||
// }
|
||||
|
||||
func currentAudioSource(call: SignalCall) -> AudioSource? {
|
||||
if call.isSpeakerphoneEnabled {
|
||||
return AudioSource.builtInSpeaker
|
||||
} else {
|
||||
let session = AVAudioSession.sharedInstance()
|
||||
guard let portDescription = session.currentRoute.inputs.first else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return AudioSource(portDescription: portDescription)
|
||||
}
|
||||
}
|
||||
|
||||
public func setPreferredInput(call: SignalCall, audioSource: AudioSource?) {
|
||||
let session = AVAudioSession.sharedInstance()
|
||||
do {
|
||||
Logger.debug("\(TAG) in \(#function) audioSource: \(String(describing: audioSource))")
|
||||
try session.setPreferredInput(audioSource?.portDescription)
|
||||
} catch {
|
||||
owsFail("\(TAG) failed with error: \(error)")
|
||||
}
|
||||
self.ensureProperAudioSession(call: call)
|
||||
}
|
||||
|
||||
private func setAudioSession(category: String,
|
||||
mode: String? = nil,
|
||||
options: AVAudioSessionCategoryOptions = AVAudioSessionCategoryOptions(rawValue: 0)) {
|
||||
|
||||
let session = AVAudioSession.sharedInstance()
|
||||
var audioSessionChanged = false
|
||||
do {
|
||||
if #available(iOS 10.0, *), let mode = mode {
|
||||
let oldCategory = session.category
|
||||
|
@ -323,6 +488,8 @@ import AVFoundation
|
|||
return
|
||||
}
|
||||
|
||||
audioSessionChanged = true
|
||||
|
||||
if oldCategory != category {
|
||||
Logger.debug("\(self.TAG) audio session changed category: \(oldCategory) -> \(category) ")
|
||||
}
|
||||
|
@ -342,6 +509,8 @@ import AVFoundation
|
|||
return
|
||||
}
|
||||
|
||||
audioSessionChanged = true
|
||||
|
||||
if oldCategory != category {
|
||||
Logger.debug("\(self.TAG) audio session changed category: \(oldCategory) -> \(category) ")
|
||||
}
|
||||
|
@ -355,5 +524,10 @@ import AVFoundation
|
|||
let message = "\(self.TAG) in \(#function) failed to set category: \(category) mode: \(String(describing: mode)), options: \(options) with error: \(error)"
|
||||
owsFail(message)
|
||||
}
|
||||
|
||||
if audioSessionChanged {
|
||||
Logger.info("\(TAG) in \(#function)")
|
||||
NotificationCenter.default.post(name: CallAudioServiceSessionChanged, object: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ extension CallUIAdaptee {
|
|||
let TAG = "[CallUIAdapter]"
|
||||
private let adaptee: CallUIAdaptee
|
||||
private let contactsManager: OWSContactsManager
|
||||
private let audioService: CallAudioService
|
||||
internal let audioService: CallAudioService
|
||||
|
||||
required init(callService: CallService, contactsManager: OWSContactsManager, notificationsAdapter: CallNotificationsAdapter) {
|
||||
AssertIsOnMainThread()
|
||||
|
|
|
@ -139,6 +139,9 @@
|
|||
/* Short text label for a voice message attachment, used for thread preview and on lockscreen */
|
||||
"ATTACHMENT_TYPE_VOICE_MESSAGE" = "Voice Message";
|
||||
|
||||
/* action sheet button title to enable built in speaker during a call */
|
||||
"AUDIO_ROUTE_BUILT_IN_SPEAKER" = "Built in Speaker";
|
||||
|
||||
/* An explanation of the consequences of blocking another user. */
|
||||
"BLOCK_BEHAVIOR_EXPLANATION" = "Blocked users will not be able to call you or send you messages.";
|
||||
|
||||
|
@ -376,7 +379,8 @@
|
|||
/* Accessibility label for disappearing messages */
|
||||
"DISAPPEARING_MESSAGES_LABEL" = "Disappearing messages settings";
|
||||
|
||||
/* Generic short text for button to dismiss a dialog */
|
||||
/* Generic short text for button to dismiss a dialog
|
||||
Short text to dismiss current modal / actionsheet / screen */
|
||||
"DISMISS_BUTTON_TEXT" = "Dismiss";
|
||||
|
||||
/* Section title for the 'domain fronting country' view. */
|
||||
|
|
Loading…
Reference in New Issue