I have an AVQueuePlayer which loads URLs to play audio files and it works well for the most part. However, I have run into a problem where after the player finishes playing a file (with another file in the queue), it will simply stop playing. Normally, the app would be able to use the player's rate, status, and items. In this case, I have gone through with the debugger and everything looks normal.
Everything appears to be playing, except for the player itself. After forcing the player to play, the player will skip to the next track, indicating that the AVPlayerItem it had was not loaded (I can confirm the audio urls are valid).
Does anyone have any ideas how I catch this programatically?
You need to have the delegate method:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
Once this method is finished, start the new audio.
Related
I found myself in a situation where I need to simulate audio playback to trick OS controls and MPNowPlayingInfoCenter into thinking that an audio is being played. This is because I am building a player that plays multiple audio tracks, with pauses in-between creating one, continuous "audio" track. I have already everything setup inside the app itself, and the lock screen controls are working correctly but the only problem I am facing is while the actual audio stops and a pause is being "played", the lock screen info center stops the timer, and it only continues with showing correct time and overall state once another audio track starts playing.
Here is the example of my audio track built from audio files and pause items:
let items: [AudioItem] = [
.audio("part-1.mp3"),
.pause(duration: 5), // value of type: TimeInterval
.audio("part-2.mp3"),
.pause(duration: 3),
... // the list goes on
]
then in my custom player, once AVAudioPlayer finishes its job with current item, I get the next one from the array and play either a .pause with a scheduled Timer or another .audio with AVAudioPlayer.
extension Player: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
playNextItem()
}
}
And here lies the problem, once the AVAudioPlayer stops, the Now Playing info center automatically stops too, even tho I keep feeding it fresh nowPlayingInfo. Then when it hits another .audio item, it resumes correctly and shows current time, etc.
And here lies the question
how do I trick the MPNowPlayingInfoCenter into thinking that audio is being played while I "play" my .pause item?
I realise that it may still not be clear, what I am trying to achieve but I am happy to share more insight if needed. Thanks!
Some solutions I am currently thinking about:
A. Keeping 1s long empty audio track that would play on loop for as long as the pause is needed to play.
B. Creating programatically empty audio track with appropriate lenght and playing it instead of using Timer for keeping track of pause duration/progress and relying completely on AVAudioPlayer for both .audio and .pause items. Not sure this is possible though.
C. Maybe there is a way to tell the MPNowPlayingInfoCenter that the audio keeps playing without the need of using AVAudioPlayer but some API I am not familiar with?
AVAudioPlayer is probably the wrong tool here. You want AVAudioPlayerNode, which is slightly lower-level. Create an AVAudioEngine, and attach an AVAudioPlayerNode. You can then call scheduleFile(_:at:completionHandler:) to play the audio at the times you want.
Much of the Apple documentation on AVAudioEngine appears broken right this moment, but the links hopefully will be available again shortly in the links for Audio Engine Building Blocks. (If it stays down and you have trouble finding docs, leave a comment and I'll hunt down the WWDC videos and other tutorials on using AVAudioEngine. It's not particularly difficult for simple problems.)
If you know in advance how you want to compose these items (and it looks like you may), see also AVMutableComposition, which lets you glue together assets very efficiently, including adding empty segments of silence. See Media Composition and Editing for the various tools in that space.
So I have an app built with a player that plays a video, I have a [player pause] and [player play] in the didBecomeActive and willResignActive methods. Most of the time works fine, but when I open the app, and press the home button and repeat again that process, around the 8th time the video will not play even though I see the play method getting called.
Does anyone have any idea what might be going on?
The app can be in several states that are not foreground. Before playing, check to see if you still have a player, that it still has a player.currentItem, and if it's status is AVPlayerStatusReadyToPlay.
If any of those conditions are not met, then the player and the item must be reinitialized using the code that you used to create it in the first place.
This is a good candidate for a lazy initializer for your player property.
I am using AVPlayer to stream video remotely. Whenever I scrub and use seekToTime it works, and the video plays from the location, however if I do this enough times sequentially then the completion handler of seekToTime never returns with "finished == YES" and is stuck.
Also, when profiling the connections it shows that the buffer never loads again even if seekToTime is called again after that. I can drag the scrubber to the beginning and playback will resume however it is not buffering anymore so it will get stuck in a "buffering" state once the buffer runs out but no buffer will be downloading.
Any ideas what might be wrong?
I had this problem for a long time, but also noticed that even if the seek never completed, I would quickly get a current playback position update (via the block passed to addPeriodicTimeObserverForInterval:queue:usingBlock:) that matched the target seek.
It appears, though, that I was doing some things in the wrong order when initializing the player with an item. I would set an item, and when the player's status became AVPlayerStatusReadyToPlay I would begin the seek, and it would never complete. But if I don't begin the seek until the current player item status is AVPlayerItemStatusReadyToPlay, everything works great.
The problem was with the content distribution network. The streaming url was expiring and then would no longer stream to the player
I have a space shooter game I am making. I want to play a sound every time a user hits the button fire. This is a short .wav file of about half a second in length. Also, when the user dies, I have an explosion .mp3 file to play. Since the fire button can be clicked very rapidly, I need to play sounds quickly. I used some avaudio player code previously where I make a new audioplayer every time I need to fire, but this crashes occasionally if I fire very quickly. Any suggestions on ways to play sound files very quickly? (including some example code in you answer would be great)
if you using MPMediaPlayback you can try prepareToPlay
I have a bit of a strange problem. I have a music app that uses the [MPMusicPlayerController iPodMusicPlayer]. Everything is fine, notifications are fired for track changes and changes in playback state.
I have one screen where the user needs to review one single song, I don't want him to go on to the next song in his queue. Since there is no delegate method for when a track WILL change (only DID change), to prevent the music player from continuing to the next track I use a new [MPMusicPlayerController applicationMusicPlayer], give it iPodMusicPlayer's currently playing song and all is well. No new tracks to continue to, and I'm not touching the original iPodMusicPlayer queue so in theory, when I close this screen and use the iPodMusicPlayer again, all should be perfectly fine.
However, when the user is done on this screen and closes it, iPodMusicPlayer is now suddenly broken, notifications are not called and when I put the app to the background, music stops playing, causing me to believe that iPodMusicPlayer is now actually applicationMusicPlayer.
Okay so my question is basically: I need a way to prevent the music player to continue on to the next track in the queue. Switching to applicationMusicPlayer with one track seems to break stuff, as explained above. What's the best solution?
EDIT: because this might be a bit difficult to understand, I created a small project to show the problem: https://github.com/kevinrenskers/MPMusicPlayerControllerTest. Open the app while music is playing, see that the play button behaves correctly. Now open the popup, close it again and the play button is broken.
I found a solution to my problem: set the repeatMode to MPMusicRepeatModeOne and then catch the MPMusicPlayerControllerNowPlayingItemDidChangeNotification notification. You can stop the playback and you never continue to the next track. Once I'm done with the second screen I reset the repeatMode to the original value.