I've created a simple audio player in swift 5 that implements a AVRoutePicker view. The routepicker is displayed without a problem, and I can change between headphones, phone speaker and phone call speakers.
The problem I have is that once I select my airplay speaker, the audio from my app doesn't play through the selected Airplay speaker and instead plays out of the phone speaker even though the routepicker has a tick next to the AirPlay device.
I can successfully connected to the AirPlay speaker and can play audio through youtube or spotify so I know that this should be possible.
I'm hoping that I've made a simple error in my code - I don't get any errors so it's a bit hard to debug.
My code:
import UIKit
import AVFoundation //audio-video foundation
import AVKit
let rect = CGRect(x: 10, y: 10, width: 100, height: 100)
let myView = UIView(frame: rect)
class ViewController: UIViewController, AVAudioRecorderDelegate, UITableViewDelegate, UITableViewDataSource {
var recordingSession: AVAudioSession!
var audioRecorder: AVAudioRecorder!
var audioPlayer: AVAudioPlayer!
var numberOfRecords = 0
@IBOutlet weak var TestButton1: UIButton!
@IBOutlet weak var MyTableView: UITableView!
@IBOutlet weak var buttonLabel: UIButton!
@IBAction func record(_ sender: Any)
{
//Check if we have an active recorder
if audioRecorder == nil
{
numberOfRecords += 1
let filename = getDirectory().appendingPathComponent("\(numberOfRecords).m4a")
let settings = [AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 12000, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue]
//Start rec
do
{
audioRecorder = try AVAudioRecorder(url: filename, settings: settings)
audioRecorder.delegate = self
audioRecorder.record()
buttonLabel.setTitle("Stop Recording", for: .normal)
}
catch
{
displayAlert(title:"Ups!", message: "Recording failed")
}
}
else
{
//stopping rec
audioRecorder.stop()
audioRecorder = nil
UserDefaults.standard.set(numberOfRecords, forKey: "myNumber")
MyTableView.reloadData()
buttonLabel.setTitle("Start Recording", for: .normal)
}
}
@IBAction func RoutePicker(_ sender: Any) {
let routePickerView = AVRoutePickerView()
self.view.addSubview(routePickerView)
if let routePickerButton = routePickerView.subviews.first(where: { $0 is UIButton }) as? UIButton {
routePickerButton.sendActions(for: .touchUpInside)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let routePickerView = AVRoutePickerView()
self.view.addSubview(routePickerView)
if let routePickerButton = routePickerView.subviews.first(where: { $0 is UIButton }) as? UIButton {
routePickerButton.sendActions(for: .touchUpInside)
}
//Setting up session
recordingSession = AVAudioSession.sharedInstance()
if let number:Int = UserDefaults.standard.object(forKey: "muNumber") as? Int
{
numberOfRecords = number
}
if (recordingSession.responds(to: #selector(AVAudioSession.requestRecordPermission(_:)))) {
AVAudioSession.sharedInstance().requestRecordPermission({(granted: Bool)-> Void in
if granted {
print("granted")
do {
try self.recordingSession.setCategory(AVAudioSession.Category.playAndRecord)
try self.recordingSession.setActive(true)
}
catch {
print("Couldn't set Audio session category")
}
} else{
print("not granted")
}
})
}
}
//Function that gets path to directory
func getDirectory() -> URL
{
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentDirectory = paths[0]
return documentDirectory
}
//Function that displays an alert
func displayAlert(title:String, message:String)
{
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "dismiss", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
//SETTING UP TABLE VIEW//
func tableView (_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfRecords
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = String(indexPath.row + 1)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let path = getDirectory().appendingPathComponent("\(indexPath.row + 1).m4a")
do
{
// try recordingSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
audioPlayer = try AVAudioPlayer(contentsOf: path)
audioPlayer.play()
}
catch
{
}
}
}
I think the line try recordingSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
is key here as when this is uncommented, we cannot switch between the phone speaker or headphones.
I'm wondering if there is an equivalent for Airplay speakers.
Xcode 12.5 Device IOS: 13.1.2 MacOS: Big Sur 11.0.1 Swift: 4