I am using the MPMusicPlayerController to play songs from Apple Music. I am periodically (every several seconds) changing the song queue using
setQueue(with: MPMusicPlayerStoreQueueDescriptor(storeIDs: ids))
I don't have any problems during music playback, the song queue is updated as requested, I can skip to next song and everything is fine. However, whenever I pause the player in the middle of the song, wait a bit till a new queue is set, and play again - the current song is lost and next song starts to play!
So imagine the following situation:
I have songs A, B, C, D
I set it as a song queue and call play()
player.nowPlayingItem returns A
I set the song queue to E, F, G, H while playing
I call player.skipToNext() - song E starts playing, as expected
I call player.pause() - song E pauses
I call player.play() - song E continues to play. Everything is fine till now
I call player.pause() again - song E pauses
I set the song queue to I, J, K, L.
I call player.play() - I expect the paused song E to continue playing. Instead, song I starts playing
I also did some log output for the above scenario:
func togglePlayPause() {
if player.playbackState == .playing {
player.pause()
} else {
NSLog("NP before \(player.nowPlayingItem)") // prints E at step 10
player.play()
NSLog("NP after \(player.nowPlayingItem)") // prints nil at step 10
}
}
Strangely, pause/play from lock screen works fine, even if I change the queue in between.
Has anyone experienced similar problem, any hints/workarounds how to fix this?
As a workaround, I used
player.currentPlaybackRate = 1
instead of player.play(). Seems like play() does a bit more than just playing from paused location.
Related
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.
I need to send notifications when the AVPlayer is Play/Paused and Stopped.
For play and pause below is the code
if (self.player.rate > 0.0f) {
NSLog(#" Playing ..")
}
if (self.player.rate == 0.0f) {
NSLog(#" Paused ..")
}
But for stopped also the rate = 0.0 then is there any other property or way to identify the difference between paused and stopped.
For both, paused and stopped the rate = 0.0 and hence need another way for it.
Thanks
There is no stop command for an AVPlayer. So there is no such thing as stopped as distinct from paused. Either the rate is zero (not playing) or it is more than zero (playing).
You can distinguish where the player is within its item (currentTime) so you can tell whether we are at the start, end, or middle; and you can arrange to be notified periodically during play or when the end is reached.
Apart from that, there are no distinctions to be drawn.
So, I am making a game and it is almost done, however, the problem I am having is that when the player comes in contact with the bomb, it just kills the player and does not play any sound effect. It plays the sound effect when collecting items though. The collection for the items are set up the exact same. When I collect an item, it plays the sound effect, when I hit a bomb, it doest play anything. Just silence. Please help!
if firstBody.node?.name == "Player" && secondBody.node?.name == "Bomb" {
if sound == true {
self.run(SKAction.playSoundFileNamed("Bomb.mp3", waitForCompletion: false))
}
firstBody.node?.removeFromParent()
secondBody.node?.removeFromParent()
self.scene?.isPaused = true
playerDied()
}
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.
In Swift, I play a song, it plays at the viewDidLoad. It is 16 seconds long, and it plays all 16 seconds. I want it to repeat forever. I made an NSTimer where every 16 seconds, it plays the song. But, it plays 16 seconds when the app loads, stops for 16 seconds, plays, etc.
The line println("just doing some dandy debugging here.") does print every 16 seconds.
How is this fixed?
CODE:
//these var's are created on the top of the file.
var soundTimer: NSTimer = NSTimer()
var audioPlayer2 = AVAudioPlayer()
var soundTwo = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("sound", ofType: "wav"))
//this stuff is in the viewDidLoad function.
audioPlayer2 = AVAudioPlayer(contentsOfURL: soundTwo, error: nil)
audioPlayer2.prepareToPlay()
audioPlayer2.play()
soundTimer = NSTimer.scheduledTimerWithTimeInterval(16, target: self, selector: Selector("soundTimerPlayed"), userInfo: nil, repeats: true)
func soundTimerPlayed() {
println("just doing some dandy debugging here.")
audioPlayer2.stop()
audioPlayer2.prepareToPlay()
audioPlayer2.play()
}
Just do it the easy, official way, instead. From the documentation of the numberOfLoops property:
Set any negative integer value to loop the sound indefinitely until you call the stop method.
But your actual problem is almost certainly because you're stopping playback a little early:
The stop method does not reset the value of the currentTime property to 0. In other words, if you call stop during playback and then call play, playback resumes at the point where it left off.
What's happening is that you're stopping the sound playback just a little before the actual end of the sample—your timer is set to just too short a time. The first time the timer is triggered, the "play" is playing the tiny amount of quiet or silent sound that's left at the end, and then stopping. The next time, the currentTime has been reset because the sample has successfully reached the end of playback, so on the next timer interval, after another 16 seconds, playback starts successfully from the beginning. And repeat.
As Jack observes in his comment, this is why there's a delegate callback to give you a kick when the playback has actually finished, but I'd still just set numberOfLoops and not bother with the complicated stuff, if you only need the sound to loop indefinitely.
(If you desperately want to repeat on your exact timer event, then just set currentTime to 0 before you play it again.)