didEnterRegion - audio not playing - Swift - swift

I am updating an existing app to Swift. The issue I am having is that the main purpose of the app is to play a sound file when the app gets to a specific location. Everything works in the simulator, but it does not play the sound on the device. If I add the background mode for audio and airplay, it works flawlessly. Apple has rejected this as it doesn't use the audio all the time, just when it enters a region. I did not have to have audio and airplay background mode enabled for iOS7.
I think it has something to do with the audioplayer being dealocated before it has a chance to be used. I cannot figure out how to get it set properly. There is this same question on StackOverflow for Objective C, but I cannot find anything similar for swift (and to me, this is what the answer to that question suggests and the way I had it set for iOS7).
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, CLLocationManagerDelegate, AVSpeechSynthesizerDelegate, AVAudioPlayerDelegate {
var myManager: CLLocationManager!
var audioPlayer = AVAudioPlayer()
var mySpeechSynthesizer = AVSpeechSynthesizer()
func playSound()
{
audioPlayer.prepareToPlay()
audioPlayer.play()
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer!, successfully flag: Bool)
{
speak()
}
func speak()
{
var myString = "This is the phrase I want to speak"
var mySpeechUtterance:AVSpeechUtterance = AVSpeechUtterance(string:myString)
mySpeechUtterance.rate = 0.12
mySpeechUtterance.voice = AVSpeechSynthesisVoice(language: "en-US")
mySpeechSynthesizer .speakUtterance(mySpeechUtterance)
}
func locationManager(manager: CLLocationManager!, didEnterRegion region: CLRegion!)
{
playSound()
scheduleAllLocations()
}
override func viewDidLoad()
{
super.viewDidLoad()
myManager = CLLocationManager()
myManager.delegate = self
myManager.desiredAccuracy = kCLLocationAccuracyBest
myManager.requestAlwaysAuthorization()
var alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("SpeakMinder", ofType: "mp3")!)
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound, error: &error)
audioPlayer.delegate = self
mySpeechSynthesizer.delegate = self
}
This is the complete code. It works flawlessly if I have audio/airplay enabled in the background. Doesn't play a sound or speak if I have the audio/airplay disabled in the background. (Apple has rejected the app for having audio/airplay enabled)

From the Technical Team
If you are attempting to use AVSpeechSynthesizer from the background you should have the background audio key enabled. AVSpeech simply uses the same audio plumbing as playing any sound.
Regarding it working in iOS7 without having audio in the background enabled
If this key was not required, you were most likely getting lucky due to a bug that we have since corrected.
And an app was approved today with the audio background enabled.
If you are rejected due to having audio enabled in the background for the Speech Synthesizer.
Our own Location Based sample “Breadcrumbs" has the following enabled in the plist. You may point to this sample in your appeal.

Related

Using an AVPlayer returns a "non-Multipath connection" error

I'm using AVKit to play a youtube URL.
I have this code inside a button action:
#IBAction func trailerButtonAction(_ sender: Any) {
guard let youtubeUrl = youtubeURL else { return }
let player = AVPlayer(url: youtubeUrl)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
present(playerViewController, animated: true) {
player.play()
}
}
The URL is valid, but when I press the button, the video doesn't stop loading and I'm getting this message on Debug area:
nw_endpoint_flow_copy_multipath_subflow_counts Called on non-Multipath connection
Edit:
I found that AVPlayer doesn't support youtube URL
I would say this log isn't necessarily relevant. I was getting this error when trying to playback on the simulator but it wasn't happening on a real device.
One workaround would be to use a 12.4.x simulator as it does not exhibit this issue. Only the 13.x simulators are showing this error. It happens to repeatedly that it slows down Simulator to a craw until all requested tracks have been buffered.
To combat this while testing, I am either not turning on AVPlayer or I am only buffering a short track.
To cut down on the number of errors try initing your AVPlayer like so:
var avPlayer : AVPlayer = AVPlayer()
This may cut down the errors by 30%.

How to embed and stream a video from the web within an Xcode macOS Cocoa Application?

I am trying to get my macOS Cocoa Application Xcode project to play some video when I feed it a specified URL.
To simplify the question lets just use an empty Xcode project.
I want to achieve the same level of control as I am able to get using AVPlayerViewController() in my iOS Single View Application. Within that app I am currently using the solution offered here.
However, only the second example works in my macOS Cocoa Application when adapted like so:
import Cocoa
import AVFoundation
import AVKit
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(url: videoURL!)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.view.layer?.addSublayer(playerLayer)
player.play()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
This option however has 2 downsides:
It creates a repetitive error message in the console:
2017-06-27 18:22:06.833441+0200 testVideoOnmacOS[24456:1216773] initWithSessionInfo: XPC connection interrupted
2017-06-27 18:22:06.834830+0200 testVideoOnmacOS[24456:1216773] startConfigurationWithCompletionHandler: Failed to get remote object proxy: Error Domain=NSCocoaErrorDomain Code=4097 "connection to service named com.apple.rtcreportingd" UserInfo={NSDebugDescription=connection to service named com.apple.rtcreportingd}
This AVPlayer solution does not offer the same control as the AVPlayerViewController() solution.
So I attempted to get the following AVPlayerViewController() solution to work. But it fails:
import Cocoa
import AVFoundation
import AVKit
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(url: videoURL!)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
The error I am getting is:
Use of unresolved identifier 'AVPlayerViewController' - Did you mean
'AVPlayerViewControlsStyle'?
I have tried to work around this in many different ways. It will be confusing if I spell them all out. My prominent problem seems to be that the vast majority of online examples are iOS based.
Thus, the main question:
How would I be able to successfully use AVPlayerViewController within my Xcode macOS Cocoa Application?
Yes, got it working!
Thnx to ninjaproger's comment.
This seems to work in order to get a video from the web playing / streaming on a macOS Cocoa Application in Xcode.
First, create a AVKitPlayerView on your storyboard and link it as an IBOutlet to your NSViewController.
Then, simply apply this code:
import Cocoa
import AVFoundation
import AVKit
class ViewController: NSViewController {
#IBOutlet var videoView: AVPlayerView!
override func viewDidLoad() {
super.viewDidLoad()
let videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(url: videoURL!)
let playerViewController = videoView
playerViewController?.player = player
playerViewController?.player!.play()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
Update august 2021
Ok, it's been a while since this answer has been up and I am no longer invested in it. But it seems others still end up here and the solution needs a bit more work to work properly at times.
With thanks to #SouthernYankee65:
What is needed sometimes is to go to the project file and on the Signing & Capabilities tab check Outgoing Connection (Client). Then in the info.plist add App Transport Security Settings dictionary property, then add Allows Arbitrary Loads boolean property and set it to YES.
The video now plays. However, some errors in the console might occur.

Can play a sound effect on apple watch simulator now?

I want to play a sound when user press on the button on WKInterfaceController. Here is how I did in my project:
- Add a AVFoundation frame work to my watchkit app.
- Import AVFoundation on my WKInterfaceController
- Create 2 variable for audio session and player:
var audioSession:AVAudioSession!
var player:AVAudioPlayer!
- make 2 function for configure an audio session and configure Audio Player:
func configureAudioSession() {
self.audioSession = AVAudioSession.sharedInstance()
var categoryError:NSError?
var activeError:NSError?
// set category cho audio session
self.audioSession.setCategory(AVAudioSessionCategoryPlayback, error: &categoryError)
println("error: \(categoryError)")
// set active cho audio session
var success = self.audioSession.setActive(true, error: &activeError)
if !success {
println("error making audio session active :\(activeError)")
}
}
func configureAudioPlayer() {
// Lay song path
var songPath = NSBundle.mainBundle().pathForResource("Open Source - Sending My Signal", ofType: "mp3")
// Chuyen thanh URL
println("songpath: \(songPath)")
var songURL = NSURL(fileURLWithPath: songPath!)
println("songURL: \(songURL)")
//
var songError:NSError?
// Tao audioplayer
self.player = AVAudioPlayer(contentsOfURL: songURL!, error: &songError)
println("songerror:\(songError)")
self.player.numberOfLoops = 0
}
After that i finish my button press function like this:
#IBAction func startGameButtonPressed() {
self.configureAudioSession()
self.configureAudioPlayer()
self.player.play()
}
Every thing's working fine , I can saw the southPath although my button is working but I cannot hear the sound. I still use these steps on IOS app and it's working fine. May be we can't play a sound effect on Watchkit at this time? If we can, please help me to do that.
No. It is not possible to play sounds with WatchKit on the Apple Watch.
- Apple's WatchKit Evangelist
It is not currently possible to play sounds using the latest build of WatchKit.
I would suggest submitting a feature request.
YEs its possible to play a sound file in apple watch application.You need to add the sound file separately in apple watch application extension and the call the AVAudioPlayer to play the sound file.
e.g:
let path = NSBundle.mainBundle().pathForResource("coinFlip", ofType:"caf")
let fileURL = NSURL(fileURLWithPath: path!)
player = AVAudioPlayer(contentsOfURL: fileURL, error: nil)
player.prepareToPlay()
player.delegate = self
player.play()

Best / Proper way to play a short sound

I'm trying to play a short sound in my Swift app.
The sound I want to play :
is very short (less than five seconds) ;
is one of the system sound (ex: sms received) ;
will most likely be played multiple time during the life cycle of my application ;
can be fired two time in less than the sound length time (i.e. fire the first one and a second one is fired before the first finish playing) ;
In addition to this, I would like to :
hear the sound only if the application is active and the "silent mode" is off ;
make the phone vibrate (regardless if silent mode is on or off).
So far, here is what I have :
import AVFoundation
class SoundPlayer {
// Singleton
class var sharedInstance : SoundPlayer {
struct Static {
static let instance : SoundPlayer = SoundPlayer()
}
return Static.instance
}
// Properties
var error:NSError?
var audioPlayer = AVAudioPlayer()
let soundURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("sound", ofType: "wav")!) // Working
//let soundURL = NSURL(string:"/System/Library/Audio/UISounds/sms-received1.caf") // Working
//let soundURL = NSURL(fileURLWithPath: NSBundle(identifier: "com.apple.UIKit")!.pathForResource("sms-received1", ofType: "caf")!) // Not working (crash at runtime)
init() {
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
audioPlayer = AVAudioPlayer(contentsOfURL: soundURL, error: &error)
if (error != nil) {
println("[SoundPlayer] There was an error: \(error)")
}
}
func playSound() {
if (audioPlayer.playing) {
audioPlayer.stop()
}
audioPlayer.play()
}
}
What I understand there are several ways to play a sound.
The two most common ways seems to be : AVAudioPlayer (like I did) and AudioToolbox.
From there, I'm asking myself three questions :
Which of those two is the most adapted (best / proper) for my situation ? (AVAudioPlayer is working correctly at the moment)
Are we allowed to play a system sound ? (Very small gain in size if in don't add my own sound to the project)
If so, is NSURL(string:"/System/Library/Audio/UISounds/sms-received1.caf") a good way to retrieve the sound ? Any better / proper solution ?
Is there a way to vibrate the phone at the same time the sound is fired ?

AVFoundation playing audio in background/on lock screen in Swift

I'm trying to find a way with AVFoundation to play audio when the user is on the lock screen or if they lock the app in the middle of using my app
class ViewController: UIViewController, AVAudioPlayerDelegate {
var avPlayer: AVAudioPlayer?
var error: NSError?
... other stuff ...
func playChime(fileName: String) -> Void {
let fileURL: NSURL! = NSBundle.mainBundle().URLForResource(fileName, withExtension: "wav")
self.avPlayer = AVAudioPlayer(contentsOfURL: fileURL, error: nil)
self.avPlayer?.play()
}
... other stuff ...
}
What needs to be added to this to ensure sounds play when the user is on the lock screen as well?
To continue playing audio in the background when the device is locked or on the home screen (on Xcode 6.3):
Add to your ViewController:
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
Then in your Info.plist, add a new entry for UIBackgroundModes and set it to audio. Xcode will automatically fill it out as 'Require background modes' and 'App plays audio or streams audio/video using Airplay'