Convert Audio call speaker to mic - swift

i'm developing an app in which i'm making a video call using twilio. Now when i make a a video call it runs properly. But the voice of both parties is on speaker, i want to give user flexibility that it can either speak using speaker or mic, how can i do this using swift? I have searched for it, it shows me this function in this function speaker get enable and disable but don't convert to mic. How i can convert speaker voice to mic or mic to speaker?
#IBAction func speakerBtnTapped(_ sender: Any) {
if (self.localAudioTrack != nil) {
//MARK:- Usage for Enable
self.setAudioOutputSpeaker(enabled: true)
}
else
{
//MARK:- Usage for Disable
self.setAudioOutputSpeaker(enabled: false)
}
}
//MARK:- Manual Speaker Enagle and Disable
func setAudioOutputSpeaker(enabled: Bool)
{
let session = AVAudioSession.sharedInstance()
var _: Error?
try? session.setCategory(AVAudioSessionCategoryPlayAndRecord)
try? session.setMode(AVAudioSessionModeVoiceChat)
if enabled {
try? session.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker)
} else {
try? session.overrideOutputAudioPort(AVAudioSessionPortOverride.none)
}
try? session.setActive(true)
}

If I understand you correctly then you would like to provide a feature, where user can select the way for input of voice, either Speaker or Microphone then, that functionalities is not available in existing iOS Technologies.
There is no method to overrideIutputAudioPort, only you can overrideOutputAudioPort and send the incoming voice to Speaker or Microphone based on your choice.
For now, if overrideOutputAudioPort is speaker then input is speaker and vice versa
You can also refer the most popular apps, like WhatsApp, Google Duo, there doesn't exists such options.
Hope it helps

Swift 5 - use below code snippet
// Change the audio route after connecting to a Room.
func moveToMic() {
audioDevice.block = {
DefaultAudioDevice.DefaultAVAudioSessionConfigurationBlock()
do {
try AVAudioSession.sharedInstance().setMode(.voiceChat)
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
} catch {
print(error)
}
}
audioDevice.block();
}

Related

How to update MPNowPlayingInfoCenter/MPRemoteCommandCenter play/pause controls with AVAudioPlayerNode?

I am making a simple music player app that just plays audio using AVAudioEngine. When I pause the AVAudioPlayerNode, it does not update the play/pause control of the MPNowPlayingInfoCenter/MPRemoteCommandCenter. How do I update it?
I dont want to pause the entire AVAudioEngine and then resume to update the MPNowPlayingInfoCenter/MPRemoteCommandCenter play/pause control since it lags up my UI and there's a delay when playing back audio.
Does anyone have any idea or solution regarding this topic? The documentation is absolutely atrocious and no one knows a goddamn thing about AVAudioEngine. Anyone?
func pause() {
playerNode.pause()
audioEngine.pause()
displayLink?.isPaused = true
DispatchQueue.main.async {
self.musicPlayerControlsManager.isPlaying = false
}
}
func playPlayerNode() {
if !audioEngine.attachedNodes.isEmpty && audioFile != nil {
DispatchQueue.main.async {
self.musicPlayerControlsManager.isPlaying = true
self.displayLink?.isPaused = false
}
do {
try audioEngine.start()
playerNode.play()
} catch {
print(error.localizedDescription)
}
}
}

setting sharedInstance of audiosessions category options not working

I'm building an App where I want to play music from the local library AND use the AVQueuePlayer, to play a list of tracks, where once in a while there's a break between the track. The music works totally fine, now, Since I want everything to work in the background, my only option when playing the AVQueuePlayer and want a break, is to play a silent AVPlayerItem (an empty audiofile.) I want the Music to play normally when silent AVPlayerItems are playing, I achieved that by setting AVAudioSession category to .playback and with options: .mixWithOthers, and when a regular track (not silent) is played by the AVQueuePlayer, I want the music to be dimmed a little.
I've tried changing the the audio session like this: AVAudioSession.sharedInstance().setCategory(.playback, options: [.mixWithOthers, .duckOthers])
but it doesn't change anything. When I check if it's changed like this: if AVAudioSession.sharedInstance().categoryOptions == .mixWithOthers {
print("Music mixing with silence")
}
the session category options seem to have changed, but it doesn't affect the audio in the end.
#objc func playerDidFinishPlaying() {
print("Player finished!")
guard let queuePlayer = queuePlayer else { return }
if let index = queuePlayer.items().lastIndex(where: { (playerItem) -> Bool in
return playerItem == queuePlayer.currentItem
}) {
if let nextItem = queuePlayer.items()[index + 1].asset as? AVURLAsset {
if nextItem.url.absoluteString != oneSecondSilenceUrl?.absoluteString {
try? AVAudioSession.sharedInstance().setCategory(.playback, options: [.mixWithOthers, .duckOthers])
//When narrator is speaking
} else {
try? AVAudioSession.sharedInstance().setCategory(.playback, options: .mixWithOthers)
AVAudioSession.sharedInstance()
//When there's silence and only music should be playing
}
}
}
if AVAudioSession.sharedInstance().categoryOptions == .mixWithOthers {
print("Music mixing with silence")
}
try? AVAudioSession.sharedInstance().setActive(true)
}
enter code here
This method runs whenever the the AVQueuePlayer has finished playing one of its items. I use it to check and see if the NEXT item is a silent track or not. If its a silent track, I want .mixWithOthers, If its a track that is NOT silent and the track actually plays audio, I want category options: .duckOthers :)
Any help, response or answer is very much appreciated! :)
Switching to main thread seemed to fix the problem. Weird.
DispatchQueue.main.async {
if AVAudioSession.sharedInstance().categoryOptions != .duckOthers {
try? AVAudioSession.sharedInstance().setActive(false)
try? AVAudioSession.sharedInstance().setCategory(.playback, options: [ .duckOthers])
}
}

How to play sounds, like the ringing beep, while using Callkit?

I am trying to implement VoIP using webRTC and Callkit. The audio works perfectly fine during the call, but I would like to play sounds to the user while the user is initiating a call (an outgoing call).
When the user initiates a call and waits for the recipient to answer, i would like to play the waiting beep sound (long beeps). I can manage to play the sound without using Callkit but when I inform Callkit about an outgoing call it somehow cancels the audio. My assumption is it does this because it IOS silences audio when a call is beging made.
So my question is, how can i playback an mp3 file when Callkit is active. Or is this waiting sound somehow integrated in Callkit or WebRTC?
I messed around with different categories for the audiosession but no luck so far. See below a summary of my current code.
public var audioPlayer: AVAudioPlayer?
private init() {
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: Bundle.main.path(forResource: "dialring", ofType: "mp3")!))
audioPlayer!.prepareToPlay()
audioPlayer!.numberOfLoops = -1 //loop
} catch {
print(error.localizedDescription)
}
}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
configureAudioSession()
audioPlayer?.play()
}
func configureAudioSession() {
print("Configuring audio session")
let session = AVAudioSession.sharedInstance()
do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, options: [.mixWithOthers])
try session.setMode(AVAudioSession.Mode.voiceChat)
} catch (let error) {
print("Error while configuring audio session: \(error)")
}
}
If anyone could point me in the right direction I would be grateful.
EDIT: I have background mode for audio enabled.
Threw around the structure a bit and now it works. I am not exactly sure what changed thought. If people face the same issue.
- Make sure you keep a strong reference to the audioplayer.
- Make sure the mode is either in .playback or .playAndRecord

Play sound on WatchOS

I'm trying to make an app for Apple Watch using Xcode. It's a very simple app that plays a sound whenever a button is played but I can't seem to find the way to play.
The audio file is in my WatchKit extension and I have tried to play it through a WKAudioFilePlayer object but I don't know if I am doing it the right way.
override func awake(withContext context: Any?) {
super.awake(withContext: context)
kickPath = Bundle.main.path(forResource:"Kick", ofType: "mp3")!
kickUrl = URL(fileURLWithPath: kickPath!)
kickAsset = WKAudioFileAsset(url: kickUrl!)
kickItem = WKAudioFilePlayerItem(asset: kickAsset!)
kick = WKAudioFilePlayer(playerItem: kickItem!)
}
#IBAction func kickButton() {
switch kick.status {
case .readyToPlay:
kick.play()
print("sound")
case .failed:
print("failed")
case .unknown:
print("unknown")
}
}
The audio doesn't play but I know it's getting on the right switch case because it prints "sound".
The thing that worked for me was using AVFoundation with an AVAudioPlayer for the sound instead of the WKAudioFilePlayer I was using. It sounds through the Watch's speaker and through headphones too if you have a bluetooth set connected.

xcode swift 3 lock screen remote control not working?

i am trying to catch play/stop/next/prev user action from lock screen when player is active and playing , for some how its not working .
inside class MusicPlayerViewController: BaseViewController
override func viewDidLoad() {
super.viewDidLoad()
do {
UIApplication.shared.beginReceivingRemoteControlEvents()
print("bb> Receiving remote control events\n")
} catch {
print("bb> Audio Session error.\n")
}
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.nextTrackCommand.isEnabled = true
commandCenter.nextTrackCommand.addTarget(self, action: #selector(MusicPlayerViewController.nextTrackCommandSelector))
}
func nextTrackCommandSelector()
{
print("omg")
}
in the log i can see only
bb> Receiving remote control events
also inside AppDelegate.swift has
override func remoteControlReceived(with event: UIEvent?) {
print("remote::")
guard let event = event else {
print("no event\n")
return
}
guard event.type == UIEventType.remoteControl else {
print("received other event type\n")
return
}
switch event.subtype {
case UIEventSubtype.remoteControlPlay:
print("received remote play\n")
case UIEventSubtype.remoteControlPause:
print("received remote pause\n")
case UIEventSubtype.remoteControlTogglePlayPause:
print("received toggle\n")
case UIEventSubtype.remoteControlNextTrack:
print("clicked next \n")
case UIEventSubtype.remoteControlPreviousTrack:
print("clicked Prev \n")
default:
print("received \(event.subtype) which we did not process\n")
}
}
and capabilities
what did i miss ?
A few things:
You're using both the delegate style and MPRemoteCommandCenter style of remote event handling. Pick one, rather than both to see if they're conflicting. Apple recommends the MPRemoteCommandCenter style, but if you're supporting older iOS versions, you may need to stick with the delegate style.
If you do elect to use the delegate style, my recollection is that you must also become the first responder in order to begin receiving remote control events.
Regardless of which style of event handling you choose, you must play audio in your app to let the system know to route events to you. The lock screen (or audio control center) should have your app listed in the "Now Playing" area.
i found the solution
for swift 3 i have to add this
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: [])
try! AVAudioSession.sharedInstance().setActive(true)