Thread1:EXC_BAD_ACCESS(code=1, address = 0X48) when button is pressed - ios

var buttonTapPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global(qos: .background).async {
do{
let correctSoundUrl: URL = URL(fileURLWithPath: Bundle.main.path(forResource: "buttonTap", ofType: "wav")!)
self.buttonTapPlayer = try AVAudioPlayer(contentsOf: correctSoundUrl)
self.buttonTapPlayer.prepareToPlay()
}
catch{}
}
}
#IBAction func buttonPressed(_ sender: UIButton) {
self.buttonTapPlayer.play()
performSegue(withIdentifier: "showDetail", sender: sender)
}
I have the above code in my swift project. My project crashes the very first time I run the project in simulator at self.buttonTapPlayer.play()
with the following error:
Thread1:EXC_BAD_ACCESS(code=1, address = 0X48)
However, when I re-run it, everything is fine. How do I resolve this?

You can't do it. You've build your buttonTapPlayer in a background thread and you try to call it from the main thread (to IBAction method). The compiler crashed to play() because your player is not yet ready.
To avoid this, you could build your player inside:
DispatchQueue.main.async {
// build your stuff to the main thread
}
or simply remove DispatchQueue.global(qos: .background).async {}

Related

xcode - stop audio files from playing over each other

So I am new to xcode and programming in general. I am creating a simple soundboard and I am running into an issue. What I want to do is stop the current audio from playing when a different audio button is selected. Then have the stopped audio reset itself and be ready to start playing from the beginning when pressed again. I had added a stop() to my code and it would stop() the audio from playing but when I would try to restart it, I discovered that the audio just paused, and continued from the where it stopped.
Can anyone provide me with the code set that I need to fix this, and if not can anyone point me in the direction of a good tutorial that can lead me down the right path. I looked at the apple Audio documentation and unfortunately I got lost.
My code is as follows.
var SoundPlayer1:AVAudioPlayer = AVAudioPlayer()
var SoundPlayer2:AVAudioPlayer = AVAudioPlayer()
var SoundPlayer3:AVAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
let FileLocation1 = Bundle.main.path(forResource: "Audio1", ofType: "mp3")
let FileLocation2 = Bundle.main.path(forResource: "Audio2", ofType: "mp3")
let FileLocation3 = Bundle.main.path(forResource: "Audio3", ofType: "mp3")
do {
SoundPlayer1 = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: FileLocation1!))
SoundPlayer2 = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: FileLocation2!))
SoundPlayer3 = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: FileLocation3!))
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
try AVAudioSession.sharedInstance().setActive(true)
}
catch {
print(error)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func catButton(_ sender: AnyObject) {
SoundPlayer1.play()
}
#IBAction func pigButton(_ sender: AnyObject) {
SoundPlayer2.play()
}
#IBAction func pigButton(_ sender: AnyObject) {
SoundPlayer3.play()
}
This is just a bit of Swift logic you can carry out to check if any other of the players are playing and then stop them!
// Make sure all audio playback times are reset
func resetAllAudioTime(){
SoundPlayer1.currentTime = 0.0;
SoundPlayer2.currentTime = 0.0;
SoundPlayer3.currentTime = 0.0;
}
IBAction func catButton(_ sender: AnyObject) {
// Check SoundPlayer2 and stop if playing
if(SoundPlayer2.playing){
SoundPlayer2.pause();
}
// Check SoundPlayer3 and stop if playing
if(SoundPlayer3.playing){
SoundPlayer3.pause();
}
// Reset all playback times to start of file
resetAllAudioTime();
// Play audio file
SoundPlayer1.play()
}
#IBAction func pigButton(_ sender: AnyObject) {
// Check SoundPlayer1 and stop if playing
if(SoundPlayer1.playing){
SoundPlayer1.pause();
}
// Check SoundPlayer3 and stop if playing
if(SoundPlayer3.playing){
SoundPlayer3.pause();
}
// Reset all playback times to start of file
resetAllAudioTime();
// Play audio file
SoundPlayer2.play()
}
#IBAction func pigButton(_ sender: AnyObject) {
// Check SoundPlayer1 and stop if playing
if(SoundPlayer1.playing){
SoundPlayer1.pause();
}
// Check SoundPlayer2 and stop if playing
if(SoundPlayer2.playing){
SoundPlayer2.pause();
}
// Reset all playback times to start of file
resetAllAudioTime();
// Play audio file
SoundPlayer3.play()
}
Note: This code is untested but should work. See the AVAudioPlayer docs for any further help.
You will see that rather than calling the AVAudioPlayer.stop() method, I have used the AVAudioPlayer.pause() and then reset the playback time to the start. If you read the documentation it states calling the AVAudioPlayer.stop() method:
func stop()
Stops playback and undoes the setup needed for playback.
Therefore, calling pause and resetting the time doesn't keep undoing the setup needed for playback (should be more efficient!).
I hope this helps! :-)
WoodyDev, thank you for the info it was clean and easy to understand. I was able to fix my issue with two functions.
func stopAll() {
SoundPlayer1.stop()
SoundPlayer2.stop()
}
func resetAudioTime() {
SoundPlayer1.currentTime = 0.0;
SoundPlayer2.currentTime = 0.0;
}
#IBAction func catButton(_ sender: AnyObject) {
stopAll();<br/>
resetAudioTime();<br/>
SoundPlayer1.play()
}

How do I perform segue AFTER the sound has stopped playing

I have made a custom LaunchScreen in the Main.Storyboard to make it seem that a sound is actually coming from the LaunchScreen. The sound works fine and the segue to the next view controller as well. The only problem is that the segue happens before the sound has stopped playing. I would like the sound to complete before making the segue. Logically, it should work since the performSegue is directly after the .play(). But it seems that the two happens simultaneously. Here's my code:
super.viewDidLoad()
//PLAY SOUND CLIP//
let musicFile = Bundle.main.path(forResource: "fanfare", ofType: ".mp3")
do {
try musicSound = AVAudioPlayer(contentsOf: URL (fileURLWithPath: musicFile!))
} catch { print("ERROR PLAYING MUSIC")}
musicSound.play() //WORKS
//
DispatchQueue.main.async() {
self.performSegue(withIdentifier: "myLaunchSegue", sender: self) //WORKS
}
I have tried to add:
perform(Selector(("showNavController")), with: self, afterDelay: 3)
where "showNavController" simply is the segue:
func showNavController() {
performSegue(withIdentifier: "myLaunchSegue", sender: nil)
}
but the program crashes with the error "uncaught exception.... ....unrecognized selector sent to instance"
I have also tried to add a boolean to keep the program from progressing until the sound has played, but didn't get it to work. Any ideas?
//////////////////////////
Update:
Trying Russels answer, but have a few questions. Setting AVAudioPlayer as delegate, does that mean setting it next to the class like this:
class MyLaunchViewController: UIViewController, AVAudioPlayer { ...
Also, how do I call the function audioPlayerDidFinishPlaying? Like so:
audioPlayerDidFinishPlaying(musicSound, successfully: true)
I'll post the whole code block. Makes it easier to understand...
import UIKit
import AVFoundation //FOR SOUND
class MyLaunchViewController: UIViewController, AVAudioPlayer {
var musicSound: AVAudioPlayer = AVAudioPlayer() //FOR SOUND
override func viewDidLoad() {
super.viewDidLoad()
//PLAY SOUND CLIP//
let musicFile = Bundle.main.path(forResource: "fanfare", ofType: ".mp3")
do {
try musicSound = AVAudioPlayer(contentsOf: URL (fileURLWithPath: musicFile!))
} catch { print("ERROR PLAYING MUSIC")}
musicSound.play() //WORKS
//
audioPlayerDidFinishPlaying(musicSound, successfully: true)
}
optional func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
DispatchQueue.main.async() {
self.performSegue(withIdentifier: "myLaunchSegue", sender: self)
}
}
I get an error when writing AVAudioPlayer in the class (perhaps I misunderstood what I was supposed to do?). It says I have multiple inheritances. Also, it doesn't want me to set the new function as optional, as its only for protocol members. Finally, If I correct the errors and run the program, the next segue runs before the sound has finished playing... :( sad panda.
You need to make your LaunchScreen an AVAudioPlayerDelegate, and then use the audioPlayerDidFinishPlaying callback. Here's all you need in the first controller
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate
{
var musicSound: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
//PLAY SOUND CLIP//
let musicFile = Bundle.main.path(forResource: "sound", ofType: ".wav")
do {
try musicSound = AVAudioPlayer(contentsOf: URL (fileURLWithPath: musicFile!))
} catch { print("ERROR PLAYING MUSIC")}
musicSound?.delegate = self
musicSound!.play() //WORKS
print("Next line after play")
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
DispatchQueue.main.async() {
print("audioPlayerDidFinishPlaying")
self.performSegue(withIdentifier: "myLaunchSegue", sender: self)
}
}
}
You can get more details here https://developer.apple.com/documentation/avfoundation/avaudioplayerdelegate/1389160-audioplayerdidfinishplaying

Button in Today Widget Xcode

New here to Xcode and Swift. I am working on an IOT app that enables me to switch a light on at a distance through a button on my today extension.
So I activated app groups and I am using UserDefaults to share info between app and widget. My logic being when button is pressed on the widget it returns a bool true. And if it returns true, I activate for now what is a LED on my ESP8266.
However, this doesn't seem to work, when I click the button nothing happens. What am I doing wrong? Any help or hints would be great! Thank you.
In viewcontroller.swift
override func viewDidLoad() {
defaults?.synchronize()
let Distribute = defaults?.bool(forKey: "Distribute")
if Distribute == true {
let requestURL = NSURL(string: led1on_URL)
// Instantiate an NSURLRequest object using the
// requestURL NSURL
let request = NSURLRequest(url: requestURL! as URL)
// Use the webview to send the request to the
// request NSURLRequest
View1.loadRequest(request as URLRequest)
super.viewDidLoad()
#IBAction func LED_control(_ sender: UIButton) {
}
}
In todayviewcontroller I have:
#IBAction func ButtonSnd(_ sender: Any) {
defaults?.set(true, forKey: "Distribute") //Bool Data Type
//Pass anything with this line
//defaults?.set("\(aNumber)", forKey: "userKey")
defaults?.synchronize()
print("Buttonpressed")
}
}

AVAudioPlayer doesn't stop while playing

I'm working on developing an app that plays narration by playing sentence-by-sentence sound file one after another.
With below code, it played as expected. However, after adding "Stop" button to stop what's playing, I found that "Stop" button didn't stop the sound.
I tested the "Stop" button before pressing "Play" button, which worked no problem (message was printed). However, after pressing "Play" and while NarrationPlayer is playing, "Stop" button didn't work (no message was printed).
Any idea what's wrong?
import UIKit
import AVFoundation
class ViewController: UIViewController,AVAudioPlayerDelegate {
var NarrationPlayer:AVAudioPlayer = AVAudioPlayer()
var soundlist: [String] = []
var counter = 0
}
func playSound(_ soundfile: String) {
let NarPath = Bundle.main.path(forResource: soundfile, ofType:"mp3")!
let NarUrl = URL(fileURLWithPath: NarPath)
do {
NarrationPlayer = try AVAudioPlayer(contentsOf: NarUrl)
NarrationPlayer.delegate = self
} catch{
print(error)
}
NarrationPlayer.play()
}
#IBAction func play(_ sender: Any) {
soundlist.append("a")
soundlist.append("b")
soundlist.append("c")
playSound("first")
while counter < soundlist.count{
if NarrationPlayer.isPlaying == true{
}
else{
playSound(soundlist[counter])
counter += 1
}
}
}
#IBAction func StopPlay(_ sender: Any) {
print("stop button worked")
}
The problem you're running into is that this line here:
while counter < soundlist.count{
is hogging the main thread, keeping any click on your "Stop Playing" button from actually firing.
You've set a delegate method though, and one of the very handy things you can do here is increment your counter and play your next sound file each time a sound file finishes up.
Something like this:
func playSound(_ soundfile: String) {
let NarPath = Bundle.main.path(forResource: soundfile, ofType:"mp3")!
let NarUrl = URL(fileURLWithPath: NarPath)
do {
NarrationPlayer = try AVAudioPlayer(contentsOf: NarUrl)
NarrationPlayer.delegate = self
} catch{
print(error)
}
NarrationPlayer.play()
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
{
playSound(self.soundlist[counter])
counter += 1
}
#IBAction func play(_ sender: Any) {
playSound("first")
soundlist.append("a")
soundlist.append("b")
soundlist.append("c")
}
One last piece of advice:
Change the name NarrationPlayer to narrationPlayer. Variables in Swift, like in Objective-C, should start with lowercase (also known as lowerCamelCase).

How To Make UISlider match Audio progress

I am creating a simple music app, and I was wondering how I can make a UiSlider to follow the progress of a audio file. Here's my project so far:
Code:
import UIKit
import AVFoundation
class SongDetailViewController: UITableViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "Song Name", ofType: "mp3")!))
audioPlayer.prepareToPlay()
var audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
}
}
catch {
print(error)
}
}
// Buttons
// Dismiss
#IBAction func dismiss(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
// Play
#IBAction func play(_ sender: Any) {
audioPlayer.stop()
audioPlayer.play()
}
// Pause
#IBAction func pause(_ sender: Any) {
audioPlayer.pause()
}
// Restart
#IBAction func restart(_ sender: Any) {
audioPlayer.currentTime = 0
}
}
I'm wanting to create the uislider similar to the Apple Music app where it follows the audio file's progress and whenever the user slides the ball thing (lol) it goes to that time of the song. If you could give me some code to complete this, that would be amazing!
Please keep in mind that I am fairly new to coding and am still learning swift, so keep it simple :-) Thanks again!
If you switch to using an AVPlayer, you can add a periodicTimeObserver to your AVPlayer. In the example below you'll get a callback every 1/30 second…
let player = AVPlayer(url: Bundle.main.url(forResource: "Song Name", withExtension: "mp3")!)
player.addPeriodicTimeObserver(forInterval: CMTimeMake(1, 30), queue: .main) { time in
let fraction = CMTimeGetSeconds(time) / CMTimeGetSeconds(player.currentItem!.duration)
self.slider.value = fraction
}
Where you create an audioPlayer in your code, replace with the code above.
Using AVAudioPlayer you could create a periodic timer that fires several times a second (up to 60 times/second - any more would be a waste) and updates your slider based on your audio player's currentTime property. To sync the update with screen refresh you could use a CADisplayLink timer.
Edit:
This part of my answer doesn't work:
It should also be possible to set up a Key Value Observer on your
AVAudioPlayers currentTime property so that each time the value
changes your observer fires. (I haven't tried this, but it should
work.)

Resources