How to use CoreBluetooth framework from XCUITest - core-bluetooth

I'm having a project where I have to write automation for Apple Watch & iPhone app. I'm forced to use XCUITest with Swift because Appium is not yet supporting WatchOS as a testing target.
In my tests, at some points, I have to communicate with an external device via BLE. I wanted to use the CoreBluetooth framework, but I got no luck with that. I have a working CoreBT code that I've used to create a simple BLE chat app, but for some reason, that same code is not working when called from the UI test.
The main problem is that I'm not able to get CBCentralManager in a powered state:
[CoreBluetooth] API MISUSE: <CBCentralManager: 0x28175ce00> can only accept this command while in the powered on state
Later I checked the BT authorization state inside the test with cbCentralManager.authorization == .notDetermined, and it confirmed that authorization is not confirmed. That might be a problem why CBCentralManager is not powering up, but I'm not sure how to resolve it. The app I'm testing is able to use BT without any issues (has all permissions).
Is it even possible to use CoreBluetooth from UITests??
This is the test class code:
import CoreBluetooth
import XCTest
let peripheralName = "MY-BLE-DEVICE-NAME"
let service_First = CBUUID(string: "0000ffe0-0000-1000-8000-00805f9b34fb")
let readCharacteristic = CBUUID(string: "0000ffe1-0000-1000-8000-00805f9b34fb")
let writeCharacteristic = CBUUID(string: "0000ffe1-0000-1000-8000-00805f9b34fb")
var ble_characteristic: CBCharacteristic?
var ble_perip: CBPeripheral?
class myBLERemoteUITests : XCTestCase {
var cbCentralManager : CBCentralManager!
func wait(timeout: TimeInterval){
let exp = expectation(description: "Wait for X seconds")
let result = XCTWaiter.wait(for: [exp], timeout: timeout)
print("LOG - Timer started for " + String(timeout) + " seconds...")
if result == XCTWaiter.Result.timedOut {
print("LOG - Timer stopped after " + String(timeout) + " seconds...")
}else {
XCTFail("Delay interrupted")
}
}
func test_someBLEtest() throws {
let app = XCUIApplication()
app.launch()
// MARK: Some UI automation steps
app.buttons["Get Started"].tap()
let tablesQuery2 = app.tables
let shoeImage = tablesQuery2.cells["ItemA"].images["item"]
shoeImage.tap()
let shoe2 = tablesQuery2.cells["ItemB"].images["item"]
shoe2.tap()
wait(timeout: 10)
app.buttons["Finish"].tap()
// MARK: Part where I need to communicate with BLE device
// Initialize CBCentralManager
cbCentralManager = CBCentralManager.init(delegate: self, queue: nil) // --> getting API MISUSE mentioned above
wait(timeout: 10)
cbCentralManager = CBCentralManager.init(delegate: self, queue: nil) // --> getting API MISUSE mentioned above
wait(timeout: 10)
cbCentralManager = CBCentralManager.init() // --> getting [CoreBluetooth] XPC connection invalid
// MARK: I check CBCentral Manager Authorization state
if (cbCentralManager.authorization == .allowedAlways){
print("allowed")
}else if (cbCentralManager.authorization == .notDetermined){
print("not determined") // --> I get that authorization is not determined
}
}
}
// MARK: Over here I have all needed CoreBluetooth callback functions:
extension myBLERemoteUITests : CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let services = peripheral.services {
//discover characteristics of services
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let charac = service.characteristics {
for characteristic in charac {
peripheral.setNotifyValue(true, for: characteristic)
if characteristic.uuid == writeCharacteristic {
ble_characteristic = characteristic
}
for newChar: CBCharacteristic in service.characteristics!{
peripheral.readValue(for: newChar)
}
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid == readCharacteristic {
print("Read Value : \(characteristic)")
}
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid == writeCharacteristic {
print("WRITE VALUE : \(characteristic)")
}
}
}
extension myBLERemoteUITests : CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
central.scanForPeripherals(withServices: nil, options: nil)
print("Scanning...")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
guard peripheral.name != nil else {return}
if peripheral.name! == peripheralName {
print("BLE Device Found!")
//stopScan
cbCentralManager.stopScan()
//connect
cbCentralManager.connect(peripheral, options: nil)
ble_perip = peripheral
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Connected : \(peripheral.name ?? "No Name")")
peripheral.discoverServices([service_First])
//discover all service
//peripheral.discoverServices(nil)
peripheral.delegate = self
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("Disconnected : \(peripheral.name ?? "No Name")")
cbCentralManager.scanForPeripherals(withServices: nil, options: nil)
}
}

Related

Q: CoreBluetooth peripheral:didConnect called twice

I am using MacOS 10.14.6 and I see peripheral:didConnect being called twice for the same device every time I call _centralManager?.connect once. Does anyone know why? It seems like I get 2 calls to didConnect depending on what device I am connecting to. Some devices get called once and others get called twice. The devices I am connecting to are different models so all the advertising and GATT database data are different. This is my code:
func Init(){
let centralQueue: DispatchQueue = DispatchQueue(label: "centralMsgQueue", attributes: .concurrent)
let opts = [CBCentralManagerOptionShowPowerAlertKey: false]
_centralManager = CBCentralManager(delegate: self, queue: centralQueue, options: opts)
}
func Open(id: Int) -> Int {
let i = GetDevice(id: id)
if (i != -1)
{
let uuid = UUID(uuidString:_deviceTable[i].uuid!);
let data = _centralManager!.retrievePeripherals(withIdentifiers: [uuid!]) as [CBPeripheral]
if data.count > 0 {
_deviceTable[i]._periph = data[0]
_deviceTable[i]._periph?.delegate = self
_deviceTable[i].conn_status = CONN_STATUS_PENDING
_centralManager?.connect(_deviceTable[i]._periph!, options: nil)
}
}
else
{
return -1
}
return 0
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
let i = GetDevicePeriph(per: peripheral)
if (i != -1) {
if (_deviceTable[i].conn_status == CONN_STATUS_PENDING)
{
_deviceTable[i].conn_status = CONN_STATUS_DIDCONN
peripheral.discoverServices(nil)
}
}
else {
_centralManager?.cancelPeripheralConnection(peripheral)
}
} // END func centralManager(... didConnect peripheral
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
let peripheralID = peripheral.identifier.uuidString
let deviceName = (advertisementData as NSDictionary).object(forKey: CBAdvertisementDataLocalNameKey) as? String
if (deviceName == "Test"){
_centralManager?.stopScan()
let dev = device(_periph: nil, uuid: peripheralID, conn_status: CONN_STATUS_DISCOVERED)
_deviceTable.append(dev)
}
}
func Discover() {
self._centralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey:_scanAllowDuplicates])
}
func Test() {
Init()
Discover()
Thread.sleep(forTimeInterval: 5.0)
if (_deviceTable.count > 0) {
Open(0)}
}

I'm writing a basic command line (UDP) server/listener in swift using Xcode. #GCDAsyncUdpSocket doesnt call any of my delegates

This is my very first program in Swift. I'm writing a basic command line (UDP) server/listener in swift using Xcode. I'm able to send data i.e. a string with characters 'testing' via the "sendData" call (verified through wireshark). However I can't seem to invoke any of the delegate callbacks. For "sendData", I've checked the implementation within #GCDAsyncUdpSocket's main file and I see that none of the delegates are called unless send we see a particular error (resolve error) in which case the "didNOTSendDataWithTag" is called.
But if "beginReceiving" is called, it does NOT invoke "didReceiveData" callback. I cant seem to find figure out why. Also "beginReceiving is suppose to be recursive (Calls itself forever I assume) according to its implementation. But my program exits quickly without any errors. Any help will be really appreciated.
import Cocoa
import CocoaAsyncSocket
class udpListener : GCDAsyncUdpSocketDelegate {
var udpSock : GCDAsyncUdpSocket?
let listenPort : UInt16 = 14000
let data = "testing".dataUsingEncoding(NSUTF8StringEncoding)
let toAddress = "127.0.0.1"
let connectPort : UInt16 = 14001
init () {
udpSock = GCDAsyncUdpSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
do {
try udpSock!.bindToPort(listenPort, interface: "lo0") // Swift automatically translates Objective-C methods that produce
// errors into methods that throw an error according to Swift’s native error handling functionality.
}
catch _ as NSError {
print("Issue with binding to Port")
return }
do {
try udpSock!.beginReceiving()
}
catch _ as NSError {
print("Issue with receciving data")
return }
}
func sendData() {
udpSock!.sendData(data, toHost: toAddress, port: connectPort, withTimeout: -1, tag: 0)
}
}
// Delegate CallBacks
#objc func udpSocket(sock: GCDAsyncUdpSocket!, didReceiveData data: NSData!, fromAddress address: NSData!, withFilterContext filterContext: AnyObject!) {
let str = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(str)
}
#objc func udpSocket(sock: GCDAsyncUdpSocket!, didSendDataWithTag tag: Int) {
print("didSendDataWithTag")
}
#objc func udpSocket(sock: GCDAsyncUdpSocket!, didNotSendDataWithTag tag: Int, dueToError error: NSError!) {
print("didNOTSendDataWithTag")
}
}
I'm instantiating an instance of the class from the main swift file and calling its methods in this order. Is this correct or am I missing something here
import Foundation
var dnsListener = udpListener()
dnsListener.sendData()
Here's the screenshot of the wireshark results
The main thread does not have any loop to keep it from exiting, so the execution is finished before the message is sent. If a loop or delay is added to main(), the message will be sent.
The other problem with this code is that the delegates are set to run on the main queue. The main queue is busy running the loop or delay, and can not execute the delegate code. By creating a new delegate queue, the code in the delegate functions will run.
The following code works in Swift 3.
Main code:
import Foundation
var dnsListener = UdpListener()
for i in 1...3 {
print("Sending data \(i)")
dnsListener.sendData()
sleep(3)
}
print("Execution finished")
UdpListener code:
import Foundation
import CocoaAsyncSocket
class UdpListener : NSObject, GCDAsyncUdpSocketDelegate {
var udpSock: GCDAsyncUdpSocket?
let listenPort: UInt16 = 14000
let data = "testing".data(using: String.Encoding.utf8)
let toAddress = "127.0.0.1"
let connectPort: UInt16 = 14001
override init() {
super.init()
let utilityQueue = DispatchQueue(label: "com.stackoverflow.UdpListener.utilityQueue", qos: .utility)
udpSock = GCDAsyncUdpSocket(delegate: self, delegateQueue: utilityQueue)
do {
try udpSock!.bind(toPort: listenPort, interface: "lo0")
} catch {
print("Issue with binding to Port")
return
}
do {
try udpSock!.beginReceiving()
} catch {
print("Issue with receciving data")
return
}
}
func sendData() {
udpSock!.send(data!, toHost: toAddress, port: connectPort, withTimeout: -1, tag: 0)
}
// Delegate CallBacks
func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) {
let str = String(data: data, encoding: String.Encoding.utf8)
if let str = str {
print(str)
} else {
print("Could not decode received data")
}
}
func udpSocket(_ sock: GCDAsyncUdpSocket, didSendDataWithTag tag: Int) {
print("didSendDataWithTag")
}
func udpSocket(_ sock: GCDAsyncUdpSocket, didNotSendDataWithTag tag: Int, dueToError error: Error?) {
print("didNOTSendDataWithTag")
}
}

Xcode swift SKScene in app purchase

I'm trying to create in app purchases on an SKScene in my game. The scene is called coinShop. Basically, an IAP that gives the user 300 coins. When I tried copying code, it said SKScene can not conform to the delegates I tried adding. If anybody knows how do in app purchases with a scene instead of a viewcontroller, I would love to know. Here is all the code relating to in app purchases
in the didMoveToView(), i have
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
requestProductInfo()
productIDs.append("com.ClearRockTechnologies.alien-anarchy.300Coins")
and then i have the following functions
func requestProductInfo() {
if SKPaymentQueue.canMakePayments() {
let productIdentifiers = NSSet(array: productIDs)
let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers as Set<NSObject> )
productRequest.delegate = self
productRequest.start()
}
else {
print("Cannot perform In App Purchases.")
}
}
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
if response.products.count != 0 {
for product in response.products {
productsArray.append(product)
}
}
else {
print("There are no products.")
}
if response.invalidProductIdentifiers.count != 0 {
print(response.invalidProductIdentifiers.description)
}
}
func showActions() {
let payment = SKPayment(product: self.productsArray[0] as SKProduct)
SKPaymentQueue.defaultQueue().addPayment(payment)
//self.transactionInProgress = true
}
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
for transaction in transactions as! [SKPaymentTransaction] {
switch transaction.transactionState {
case SKPaymentTransactionState.Purchased:
print("Transaction completed successfully.")
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
coins+=300
NSUserDefaults.standardUserDefaults().setInteger(coins, forKey: "coins")
case SKPaymentTransactionState.Failed:
print("Transaction Failed");
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
default:
print(transaction.transactionState.rawValue)
}
}
}
and here are my two errors,
Type 'coinShop' does not conform to protocol 'SKPaymentTransactionObserver'
'NSSet' is not implicitly convertible to 'Set'; did you mean to use 'as' to explicitly convert?
Error 1 has no suggestion, and error 2 tells me to add as "Set< NSObject >" to this line
let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers as Set<NSObject> )
but I clearly already have that. Any help would be appreciated.

Heart rate monitor only app for watchOS 2 records extra calories and shows exercise

Basically I am working on a sleep monitoring application that monitors heart rate as well. So, I don't want to start any workout activity but I think that's the way apple works!
Here's the heart rate only code I am using:
#IBOutlet private weak var label: WKInterfaceLabel!
#IBOutlet private weak var deviceLabel : WKInterfaceLabel!
#IBOutlet private weak var heart: WKInterfaceImage!
#IBOutlet private weak var startStopButton : WKInterfaceButton!
let healthStore = HKHealthStore()
//State of the app - is the workout activated
var workoutActive = false
// define the activity type and location
var workoutSession : HKWorkoutSession?
let heartRateUnit = HKUnit(fromString: "count/min")
var anchor = HKQueryAnchor(fromValue: Int(HKAnchoredObjectQueryNoAnchor))
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
}
override func willActivate() {
super.willActivate()
guard HKHealthStore.isHealthDataAvailable() == true else {
label.setText("not available")
return
}
guard let quantityType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate) else {
displayNotAllowed()
return
}
let dataTypes = Set(arrayLiteral: quantityType)
healthStore.requestAuthorizationToShareTypes(nil, readTypes: dataTypes) { (success, error) -> Void in
if success == false {
self.displayNotAllowed()
}
}
}
func displayNotAllowed() {
label.setText("not allowed")
}
func workoutSession(workoutSession: HKWorkoutSession, didChangeToState toState: HKWorkoutSessionState, fromState: HKWorkoutSessionState, date: NSDate) {
switch toState {
case .Running:
workoutDidStart(date)
case .Ended:
workoutDidEnd(date)
default:
print("Unexpected state \(toState)")
}
}
func workoutSession(workoutSession: HKWorkoutSession, didFailWithError error: NSError) {
// Do nothing for now
NSLog("Workout error: \(error.userInfo)")
}
func workoutDidStart(date : NSDate) {
if let query = createHeartRateStreamingQuery(date) {
healthStore.executeQuery(query)
} else {
label.setText("cannot start")
}
}
func workoutDidEnd(date : NSDate) {
if let query = createHeartRateStreamingQuery(date) {
healthStore.stopQuery(query)
label.setText("---")
} else {
label.setText("cannot stop")
}
}
// MARK: - Actions
#IBAction func startBtnTapped() {
if (self.workoutActive) {
//finish the current workout
self.workoutActive = false
self.startStopButton.setTitle("Start")
if let workout = self.workoutSession {
healthStore.endWorkoutSession(workout)
}
} else {
//start a new workout
self.workoutActive = true
self.startStopButton.setTitle("Stop")
startWorkout()
}
}
func startWorkout() {
self.workoutSession = HKWorkoutSession(activityType: HKWorkoutActivityType.CrossTraining, locationType: HKWorkoutSessionLocationType.Indoor)
self.workoutSession?.delegate = self
healthStore.startWorkoutSession(self.workoutSession!)
}
func createHeartRateStreamingQuery(workoutStartDate: NSDate) -> HKQuery? {
// adding predicate will not work
// let predicate = HKQuery.predicateForSamplesWithStartDate(workoutStartDate, endDate: nil, options: HKQueryOptions.None)
guard let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate) else { return nil }
let heartRateQuery = HKAnchoredObjectQuery(type: quantityType, predicate: nil, anchor: anchor, limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
guard let newAnchor = newAnchor else {return}
self.anchor = newAnchor
self.updateHeartRate(sampleObjects)
}
heartRateQuery.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
self.anchor = newAnchor!
self.updateHeartRate(samples)
}
return heartRateQuery
}
func updateHeartRate(samples: [HKSample]?) {
guard let heartRateSamples = samples as? [HKQuantitySample] else {return}
dispatch_async(dispatch_get_main_queue()) {
guard let sample = heartRateSamples.first else{return}
let value = sample.quantity.doubleValueForUnit(self.heartRateUnit)
self.label.setText(String(UInt16(value)))
// retrieve source from sample
let name = sample.sourceRevision.source.name
self.updateDeviceName(name)
self.animateHeart()
}
}
func updateDeviceName(deviceName: String) {
deviceLabel.setText(deviceName)
}
func animateHeart() {
self.animateWithDuration(0.5) {
self.heart.setWidth(60)
self.heart.setHeight(90)
}
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * double_t(NSEC_PER_SEC)))
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_after(when, queue) {
dispatch_async(dispatch_get_main_queue(), {
self.animateWithDuration(0.5, animations: {
self.heart.setWidth(50)
self.heart.setHeight(80)
})
})
}
} }
To summarize, the unexpected observations are:
1. The time I monitor the heart rate contributes to the green ring in the activity app.
2. Unexpected high amount of calories are being recorded i.e. when the person is on bed or asleep!
Can you please help with the correct code that helps me to monitor and display a person's heart beat at regular interval during his sleep without contributing to the green ring or contributing extra cals. ?
Thanks a lot in advance!
Starting a workout and running the heart rate monitor will drain the apple watch's battery after about 6 hours (if it has a full charge), so having it run continuously while sleeping is probably not realistic at this time.
From what I can tell, starting a workout using workoutSession does 2 things for your app. It keeps your app in the foreground, and it starts taking heart rate sample every few seconds. Have you considered not starting it? Your health kit queries will still work as is and the heart rate monitor still records the users heart rate every 15 minutes or so. The main thing you loose is keeping your app in the foreground, and I am wondering if you need to do that (since the user will be asleep).
To retrieve the last heart rate sample from healthkit:
func getLatestHeartRate() {
let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)!
let sortDescriptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate, ascending: false)
let sampleQuery = HKSampleQuery(sampleType: quantityType, predicate: nil, limit: 1, sortDescriptors: [sortDescriptor])
{ (sampleQuery, results, error ) -> Void in
}
self.healthStore.executeQuery(sampleQuery)
}

Force audio file playback through iPhone loud speaker using Swift

I have an App that records and then plays back an audio file. Currently the audio playback is playing through the earpiece speaker. Could someone tell me how Swift would handle coding this to force the audio out the loud speaker instead?
Below is a one of the instances I'm using to play the audio file:
#IBAction func playAudioVader(sender: UIButton) {
playAudioWithVariablePitch(-1000)
}
func playAudioWithVariablePitch(pitch: Float){
audioPlayer.stop()
audioEngine.stop()
audioEngine.reset()
var audioPlayerNode = AVAudioPlayerNode()
audioEngine.attachNode(audioPlayerNode)
var changePitchEffect = AVAudioUnitTimePitch()
changePitchEffect.pitch = pitch
audioEngine.attachNode(changePitchEffect)
audioEngine.connect(audioPlayerNode, to: changePitchEffect, format: nil)
audioEngine.connect(changePitchEffect, to: audioEngine.outputNode, format: nil)
audioPlayerNode.scheduleFile(audioFile, atTime: nil, completionHandler: nil)
audioEngine.startAndReturnError(nil)
audioPlayerNode.play()
}
override func viewDidLoad() {
super.viewDidLoad()
audioPlayer = AVAudioPlayer(contentsOfURL:receivedAudio.filePathURL, error: nil)
audioPlayer.enableRate = true
audioEngine = AVAudioEngine()
audioFile = AVAudioFile(forReading:receivedAudio.filePathURL, error: nil)
}
EDIT July 2017: Refer to Husam's answer for the Swift 2.0 solution.
As of Swift 1.2, you use overrideOutputAudioPort and AVAudioSessionPortOverride. It can be implemented by doing something like this:
if !session.overrideOutputAudioPort(AVAudioSessionPortOverride.Speaker, error:&error) {
println("could not set output to speaker")
if let e = error {
println(e.localizedDescription)
}
}
I'm working on an app that uses this now, and I have a function called setSessionPlayandRecord, which looks like:
func setSessionPlayAndRecord() {
let session:AVAudioSession = AVAudioSession.sharedInstance()
var error: NSError?
if !session.setCategory(AVAudioSessionCategoryPlayAndRecord, error:&error) {
println("could not set session category")
if let e = error {
println(e.localizedDescription)
}
}
if !session.overrideOutputAudioPort(AVAudioSessionPortOverride.Speaker, error:&error) {
println("could not set output to speaker")
if let e = error {
println(e.localizedDescription)
}
}
if !session.setActive(true, error: &error) {
println("could not make session active")
if let e = error {
println(e.localizedDescription)
}
}
}
Swift 2.0 Code
func setSessionPlayerOn()
{
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ {
}
do {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSessionPortOverride.Speaker)
} catch _ {
}
}
func setSessionPlayerOff()
{
do {
try AVAudioSession.sharedInstance().setActive(false)
} catch _ {
}
}

Resources