Swift AVAudioPlayer Restart from Beginning - ios

I have an AVAudioPlayer stored in a property called "player". When the user clicks a button, it triggers this code:
#IBAction func restartPressed(sender: AnyObject) {
player.play()
}
The problem is happening when the user clicks the button twice in a row very quickly.
If the sound from the first click is still playing, it seems like the second call is ignored.
Instead, I'd like to either:
a) restart the player from the beginning when the button is clicked a second time; or
b) have two "instances" of the sound playing at the same time.
How would I go about achieving either or both of these options?

Answering to "either" part (rather than "both") of these options: (a) how to stop and play the sound from beginning:
#IBAction func restartPressed(sender: AnyObject) {
if player.playing {
player.pause()
}
player.currentTime = 0
player.play()
}
Regarding (b): from the Language Ref for AVAudioPlayer:
Using an audio player you can:
...
Play multiple sounds simultaneously, one sound per audio player, with precise synchronization.
So you'd need two separate players to simultaneously (off-sync) play two separate sounds, even if both players use the same sound. I'd stick with (a).

dfri's answer updated for Swift 4
#IBAction func restartPressed(sender: UIButton) {
if negativePlayer.isPlaying
{
negativePlayer.pause()
}
negativePlayer.currentTime = 0
negativePlayer.play()
}

player.seek(to: .zero)
player.play()

Related

How to stop currently playing audio automatically when another play button in different UITableView cells are pressed? - Swift

I am a swift beginner currently working on my first iOS app.
I have a TableViewController which has several cells and each cell contains a play button. When a few play buttons are pressed, a few different audios are played at the same time, but I would like to stop a currently playing audio when another playing play button is pressed.
Here are the sample codes I created.
Please give me an advice. Thank you so much!
The problem is that every single cell has its own independent player. You want just one player, so that you can stop it and start a new sound. For example, make the one player a property of the view controller. Every cell can see it but there is just one.
create an extension for AVPlayer because AVPlayer does not own condition for playing. extension given below.
extension AVPlayer {
var isPlaying: Bool {
return rate != 0 && error == nil
}
}
use it while tap on play like this.
if player?.isPlaying == true {
player?.pause()
player?.seek(to: CMTime(seconds: 0, preferredTimescale: 60))
}
//set up you audio file to player
player?.play()

When I tap the "next" button it pauses the music instead of automatically going to the next song

Hi I recently implemented a play/pause button which works perfectly using MPMusicPlayerController I have now added a "next song" button. But when I click this "next song" button it pauses the music and when I tap the "play" button then it plays the next song. I was wondering if there was a way where as I click the "next song" button it would automatically play the next song instead of pausing it. Heres my code for the 2 buttons:
var musicPlayer = MPMusicPlayerController.systemMusicPlayer
#IBAction func nextMusicTapped(_ sender: UIButton) {
musicPlayer.skipToNextItem()
musicPlayer.prepareToPlay()
musicPlayer.play()
}
#IBAction func playPauseMusicTapped(_ sender: UIButton) {
let state = musicPlayer.playbackState.rawValue
if (state == 1) { //playing
musicPlayer.pause()
musicPlayPauseButton.setImage(UIImage(systemName: "play.fill"), for: .normal)
musicNextButton.isHidden = true
} else if (state == 2) { //paused
musicPlayer.prepareToPlay()
musicPlayer.play()
musicPlayPauseButton.setImage(UIImage(systemName: "pause.fill"), for: .normal)
musicNextButton.isHidden = false
}
}
First off, according to Apple's documentation, skipToNextItem() does the following:
Starts playback of the next media item in the playback queue; or, if the music player is not playing, designates the next media item as the next to be played.
So in other words, if the music player is already playing a song, you shouldn't have to tell it to play the next item, it should automatically start playing. So you wouldn't need the following lines:
musicPlayer.prepareToPlay()
musicPlayer.play()
This also leads me to believe that somehow your music player is being paused prior to that skipToNextItem() being called. Try putting print(musicPlayer.playbackState.rawValue) before line 4. If it prints 2, then your music player seems to pause by clicking the "next song" button, so that would mean something weird is going on with your IBAction connections. I've done this before where I accidentally call two different IBAction methods with the same button (due to an error in setting up the outlets in the first place). In your particular case, you could be accidentally calling both the playPauseMusicTapped (which pauses the music), and the nextMusicTapped() method to switch the songs, but that's just a random thought. Let me know what that playback state before line 4 is, regardless. That would be good to know.

How to get animation to work at exact points during playback of a music file?

Question:
In Swift code, apart from using an NSTimer, how can I get animations
to start at exact points during playback of a music file played using AVFoundation?
Background
I have a method that plays a music file using AVFoundation (below). I also have UIView animations that I want to start at exact points during the music file being played.
One way I could achieve this is using an NSTimer, but that has the potential to get out of sync or not be exact enough.
Is there a method that I can tap into AVFoundation accessing the music file's time elapsed (time counter), so when certain points during the music playback arrive, animations start?
Is there an event / notification that AVFoundation triggers that gives a constant stream of time elapsed since the music file has started playing?
For example
At 0:52.50 (52 seconds and 1/2), call startAnimation1(), at 1:20.75 (1 minute, 20 seconds and 3/4), call startAnimation2(), and so on?
switch musicPlayingTimeElapsed {
case 0:52.50:
startAnimation1()
case 1:20.75:
startAnimation2()
default:
()
}
Playing music using AVFoundation
import AVFoundation
var myMusic : AVAudioPlayer?
func playMusic() {
if let musicFile = self.setupAudioPlayerWithFile("fileName", type:"mp3") {
self.myMusic = musicFile
}
myMusic?.play()
}
func setupAudioPlayerWithFile(file:NSString, type:NSString) -> AVAudioPlayer? {
let path = NSBundle.mainBundle().pathForResource(file as String, ofType: type as String)
let url = NSURL.fileURLWithPath(path!)
var audioPlayer:AVAudioPlayer?
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: url)
} catch {
print("AVAudioPlayer not available")
}
return audioPlayer
}
If you use AVPlayer instead of AVAudioPlayer, you can use the (TBH slightly awkward) addBoundaryTimeObserverForTimes method:
let times = [
NSValue(CMTime:CMTimeMake(...)),
NSValue(CMTime:CMTimeMake(...)),
NSValue(CMTime:CMTimeMake(...)),
// etc
];
var observer: AnyObject? = nil // instance variable
self.observer = self.player.addBoundaryTimeObserverForTimes(times, queue: nil) {
switch self.player.currentTime() {
case 0:52.50:
startAnimation1()
case 1:20.75:
startAnimation2()
default:
break
}
}
// call this to stop observer
self.player.removeTimeObserver(self.observer)
The way I solve this is to divide the music up into separate segments beforehand. I then use one of two approaches:
I play the segments one at a time, each in its own audio player. The audio player's delegate is notified when a segment finishes, and so starting the next segment — along with accompanying action — is up to me.
Alternatively, I queue up all the segments onto an AVQueuePlayer. I then use KVO on the queue player's currentItem. Thus, I am notified exactly when we move to a new segment.
You might try using Key Value Observing to observe the duration property of your sound as it plays. When the duration reaches your time thresholds you'd trigger each animation. You'd need to make the time thresholds match times >= the trigger time, since you will likely not get a perfect match with your desired time.
I don't know how well that would work however. First, I'm not sure if the sound player's duration is KVO-compliant.
Next, KVO is somewhat resource-intensive, and if your KVO listener gets called thousands of times a second it might bog things down. It would at least be worth a try.

AVAudioPlayer audio not playing when its set and played from different methods

I have an app with different button press sounds. I'm trying to use two functions to set the next button press sound and then play it. The idea is to load the audio file for when the next button is pressed, and then finally play it through a different function when a button is pressed. Using prepareToPlay(), this should make playback faster.
The problem is that when I press a button, I hear 'silence'. I say 'silence' because my speakers actually receive some signal, but its too week to be heard.
Here's my code:
func loadNextButtonPressSound() {
let randomSoundIndex = Int(arc4random_uniform(UInt32(pressSounds.count)))
pressSoundPlayer = try! AVAudioPlayer(contentsOfURL: pressSounds[randomSoundIndex])
pressSoundPlayer.prepareToPlay()
pressSoundPlayer.delegate = self
}
func playButtonPressSound() { // called when a button is pressed
if(StoredSettings.instance.getEnableSoundsSetting()) {
pressSoundPlayer.play()
loadNextButtonPressSound()
}
}
If I instead merge the codes to form this:
internal func playButterPressSound() {
if(StoredSettings.instance.getEnableSoundsSetting()) {
let randomSoundIndex = Int(arc4random_uniform(UInt32(pressSounds.count)))
pressSoundPlayer = try! AVAudioPlayer(contentsOfURL: pressSounds[randomSoundIndex])
pressSoundPlayer.play()
}
}
It starts working (i.e. I hear button presses), but it defeats the purpose of using prepareToPlay() to preload the next sound.
Any suggestions on how to make the first code above work ?

Keep AVAudioPlayer sound in the memory

I use AVAudioPlayer to play a click sound if the user taps on a button.
Because there is a delay between the tap and the sound, I play the sound once in viewDidAppear with volume = 0
I found that if the user taps on the button within a time period the sound plays immediately, but after a certain time there is a delay between the tap and the sound in this case also.
It seems like in the first case the sound comes from cache of the initial play, and in the second case the app has to load the sound again.
Therefore now I play the sound every 2 seconds with volume = 0 and when the user actually taps on the button the sound comes right away.
My question is there a better approach for this?
My goal would be to keep the sound in cache within the whole lifetime of the app.
Thank you,
To avoid audio lag, use the .prepareToPlay() method of AVAudioPlayer.
Apple's Documentation on Prepare To Play
Calling this method preloads buffers and acquires the audio hardware
needed for playback, which minimizes the lag between calling the
play() method and the start of sound output.
If player is declared as an AVAudioPlayer then player.prepareToPlay() can be called to avoid the audio lag. Example code:
struct AudioPlayerManager {
var player: AVAudioPlayer? = AVAudioPlayer()
mutating func setupPlayer(soundName: String, soundType: SoundType) {
if let soundURL = Bundle.main.url(forResource: soundName, withExtension: soundType.rawValue) {
do {
player = try AVAudioPlayer(contentsOf: soundURL)
player?.prepareToPlay()
}
catch {
print(error.localizedDescription)
}
} else {
print("Sound file was missing, name is misspelled or wrong case.")
}
}
Then play() can be called with minimal lag:
player?.play()
If you save the pointer to AVAudioPlayer then your sound remains in memory and no other lag will occur.
First delay is caused by sound loading, so your 1st playback in viewDidAppear is right.

Resources