Stop crashing when AVAudioPlayer URL is nil? - swift

I'm still a beginner in coding but I'm working on a very basic music player to get an idea of how Swift works.
////Functions
//Play chosen file function
func playChosenFile() {
//Set up the music file URL.
let musicFilePathURL = MenuBarModel.mainMenu.URL
if musicFilePathURL != nil {
//Initialize audioPlayer with safety.
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: musicFilePathURL!)
}
catch {
}
}
else if musicFilePathURL == nil {
//Some code that will make the player do nothing.
}
}
//Play function
func play() {
playChosenFile()
audioPlayer.play()
}
So musicFilePathURL gets initialized with a URL if I choose an audio file using NSOpenPanel().
If I do select a file using NSOpenPanel(), then musicFilePathURL has the location of the selected file and is then passed to AVAudioPlayer. Once I press the Play button, the function "play()" executes and then it executes playChosenFile() and audioPlayer.play().
The music player plays the song if I do this before pressing the play button.
If I press the play button before selecting any file to play, then the program crashes because musicFilePathURL is nil.
I'm confused on how to make the program not crash if there is no file selected before pressing the play button. As an example, if you open VLC, if you press the Play button, it prompts to pick a file, but if you decide to press cancel, the player does nothing.
How can I make my program do nothing if I press the play button with no audio file picked at startup?

You've got the right idea with the try/catch statements for the Audio Player, but you could probably use another one in the play() function.
That way when the audioPlayer attempts to play a null object, an exception will be caught and you can process it handle it on your own rather than having the app crash.
Perhaps something like this? I would try it, but unfortunately I left my Mac at home.
func play(){
do {
playChosenFile()
audioPlayer.play()
} catch {
print "Error Occurred (Did the user specify a file?)
}
}

Related

Carplay Resume Music

I am creating a CarPlay music app, everything works fine except when I get a call in between my music is playing. Carplay pauses music in between but when I end phone call its not resuming back.
Please help if anyone has faced same issue
CarPlay is not actually playing or pausing the music, it is still your app on the user's device that is doing all the work and CarPlay is just presenting an interface to interact with your app which is what you do in your CarPlaySceneDelegate
Therefore, the handling of how your app resumes after the call is not part of CarPlay.
You can try the following in your player management module:
1. Listen for audio interruption notifications
NotificationCenter.default
.addObserver(self,
selector: #selector(handleInterruption(_:)),
name: AVAudioSession.interruptionNotification,
object: nil)
2. Check the status of the interruption and handle accordingly
#objc
private func handleInterruption(_ notification: Notification)
{
// Check if we have valid information from the notification
// with regards to the interruption
guard let info = notification.userInfo,
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue)
else
{
return
}
// We check if the interruption has begun
if type == .began
{
// Pause the AVPlayer in that case
player?.pause()
}
else if type == .ended
{
// We check if we have instructions on how to handle the interruptions
guard let optionsValue = info[AVAudioSessionInterruptionOptionKey] as? UInt else
{
return
}
// Convert the optionsValue into an AVAudioSession.InterruptionOptions
// which can be tested
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
// Check if the playback can resume after the interruption
if options.contains(.shouldResume)
{
// Request AVPlayer to resume the playback
player?.play()
}
}
}
Give this a try and see if it helps your situation
Update based on OP comments
The audio not re-starting is not related to CarPlay, if you app resumes audio after a call, this will be carried over to CarPlay.
With that being said, if you have not already, add this code when your player initializes:
UIApplication.shared.beginReceivingRemoteControlEvents()
Maybe my code does not work for your exact scenario but I believe you will need to use the AVAudioSession.interruptionNotification which is what get's called when a phone call, siri or similar things interrupt your audio.
When this interruption has ended, you need to restart your audio.
I can't show you a CarPlay example, but here is an example of the above code in action when Siri interrupts the audio, the lock screen shows the status of the audio being paused, when Siri is dismissed, the app resumes the player as you can see.
So I found solution: - I was missing one line of code,
"try AVAudioSession.sharedInstance() .setCategory(AVAudioSession.Category.playback)" this is must for maintaining audio session

Check if Screen is recording swift

I am trying to check if the screen is recording before allowing a following action, I initially tried to use ReplayKit to automatically record, but this isn't a viable solution because it doesn't allow for recording outside the app, so basically what I want to do is check if the user has began screen recording using the IOS Control Centre recorder, before allowing them to execute another piece of code.
Something like:
func handleScreen() {
var isRecording: Bool = false
if ScreenIsRecording { //(this is what i don't know how to check)
isRecording = true
}
if isRecording == true {
// execute this code.
}
}
I am open to other solutions of being able to execute screen recording, but it has to be able to record all screens not just the in-app screen.
Thanks
UIScreen includes the UIScreen.isCaptured property which you should be able to reference to determine if the screen is likely being recorded. However this will also return true if the device is being AirPlayed or otherwise broadcast:
A value of YES indicates the system is actively recording, mirroring,
or using AirPlay to stream the contents of the screen.
if UIScreen.mainScreen().isCaptured {
isRecording = true
}
I just tested this on iOS 16, and even though the documentation says that isCaptured should return true for AirPlay. It does not when I tested it!
Observe changes to isCaptured with NotificationCenter:
NotificationCenter.default.addObserver(self, selector: #selector(screenCaptureDidChange),
name: UIScreen.capturedDidChangeNotification,
object: nil)
Handle the notification:
#objc func screenCaptureDidChange() {
debugPrint("screenCaptureDidChange.. isCapturing: \(UIScreen.main.isCaptured)")
if !UIScreen.main.isCaptured {
//TODO: They stopped capturing..
} else {
//TODO: They started capturing..
debugPrint("screenCaptureDidChange - is recording screen")
}
}
This notification does NOT get fired when you start AirPlay, and if you start screen recording while AirPlaying, when you stop recording the notification will get fired but UIScreen.main.isCaptured is false even though AirPlay is still active.

SpriteKit overlaying audio file

I'm trying to set a background jingle for my game which is supposed to start at the Home scene and then loop continuously even while on the other scenes.
This is what my class looks like:
class AudioPlayer: AVAudioPlayer {
var audioPlayer: AVAudioPlayer? = AVAudioPlayer()
let ost = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("bkg", ofType: "mp3")!)
func playOst() {
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: ost)
}
catch {
audioPlayer = nil
}
audioPlayer?.prepareToPlay()
audioPlayer!.play()
audioPlayer!.numberOfLoops = -1
}
func stopOst() {
audioPlayer!.stop()
}
since I put the play method in the home scene, it will start playing a new track every time I load it, overlaying one track on another making it result in a big mess. How can I prevent it?
Make your audio player class a singleton, or let AppDelegate look after it. This will also allow you to control the music from any scene/screen in your app at any time.
Whenever your home screen loads check to see if music is already playing, if not, call playOst, otherwise do nothing.

Audio Jump Forward Button

I've got the following code tied to a 'Jump Forward' button:
#IBAction func jumpForwardPressed(sender: AnyObject) {
var currentTime = audioPlayer.currentTime
var playForwardSkip = NSTimeInterval()
audioPlayer.stop()
audioPlayer.prepareToPlay()
playForwardSkip = 3.0 // skip forward 3 seconds
var skipF = currentTime + playForwardSkip
println("currentTime:\(currentTime) and playForwardSkip:\(playForwardSkip) and skipF:\(skipF)")
audioPlayer.playAtTime(skipF)
When button is pressed, audio stops, console reads out correct 'skipF' to skip to but audio does not play at that new point in time. It does not play at all.
Any pointers as to what might be wrong with my code?
You have misread the docs. playAtTime: does not seek to a later time in a sound file. It delays the start of playing the sound file (from the start).
To play from the middle of a sound file, set the player's currentTime and start playing.

Playing the same sound at the same time? (swift, spritekit)

Im trying to play a pop sound whenever a ball is touched but if the ball is touched again, before the pop sound ends, the sound will not play. I want the pop sound to cut out and start playing again if the ball is touched over and over again. The code i have now is below...
let popSound = NSBundle.mainBundle().URLForResource("pop",withExtension: "wav")
let Pop = AVAudioPlayer(contentsOfURL: popSound, error: nil)
////this is in the touches begin function/////
if touchedNode==ball{
Pop.play()
}
//////////////////////////////////
I fixed it with the help of this post. AVAudioPlayer stop a sound and play it from the beginning
I needed to set the Pop.currentTime to = 0
if touchedNode==ball{
Pop.currentTime=0
Pop.play()
}