When i call AudioOutputUnitStop, I hear an audible pop/click. Is there a way to prevent this? One solution I had in mind was to set a flag as soon as the user pauses. This flag would cause the audio to keep running but it would fade out. Then it would reset the playhead to 0.1s before where the audio was paused. Then, when the user hits play it would fade in in that tenth of a second. Is there an easier way?
EDIT:
I ended up implementing what I suggested and it works fine though I changed the delay to 0.02s to make it less noticeable. I actually don't call AudioOutputUnitStop at all anymore but I just fill the buffer with 0's when the user hits pause. There's less lag this way when starting to play again.
Related
So here is the story.
I have a timer and timer.setEventHandler renders animation (something like adding 10 circles in the UI every 125ms) when I tap a button. The animation runs smoothly and so far so good.
Now I want to add sound (the sound plays 10mm like a beep but from a mp3 file) every time when the circle is added. I tried 2 approaches:
AudioServicesPlaySystemSound
The Animation is smooth, but the sound is not played evenly. At least the sound playing doesn't lag the UI whatsoever.
Use AVAudioPlay
I prepared the AVAudioPlay instance in a singleton class and every time the animation logic calls play() in a DispatchedQueue to make sure the sound is played in a separate thread. The first time when the button is pressed, the animation is lagged. However, when I press the button again shortly (within 2 seconds) when the animation is done, the animation and sound will be played perfectly. However, if I wait for longer time like 10 seconds since the last sound was played, the animation will be lagged and sound is kind of bursting out.
I am wondering if iOS (I am running iOS 10 on iPad Air 2) actually put AVAudioPlay instances on sleep when it is not active. Unless I press the button within the short time (2 seconds) since the last sound was played, the AVAudioPlay thread will be active all the time.
Any idea?
I figured it out.
Use a timer to play sound at 0 volumn every 1 second forever to keep the audio session alive. It sounds stupid but it works like a charm!
Hey I have a couple of AVAudioPlayers containing one sound each. If I press the same button a couple of times, it should repeat the sound from the beginning. If I press another button afterwards, the running sound shall be stopped in order to "make room" for the new one.
The code I am using for that:
-(void) plays:(int)p{ // p is the index of the sound being triggered
if([players[p] isPlaying])
{ // setting the time back to 0 makes
players[p].currentTime = 0.0; // the player automatically play again
}
else
{
[players[p] play]; // if not playing, start playing
}
if(last!=p)
{ // if the last sound is different from the current
[players[last] stop]; // stop the last one
players[last].currentTime = 0.0;} // put its position back to 0
last=p; // set the 'last' variable
}
However, hitting the same button again ends up in a little delay (maybe 20ms) in which no sound is heard. This is the time, the AVAudioPlayer seems to need to "rewind" the track in order to play it again. One Idea to get around this would be to create multiple objects of AVAudioPlayer for each sound but that'd make some awful code! Any ideas on how to make this process quicker?
Thanks, Alex
EDIT: playing 2 different sounds works perfectly fine, I can't hear any delay in between them as I prepareToPlay all the sounds beforehand.
I know how to eliminate the 20ms gap, but first consider if you want to.
Imagine if you jumped immediately from the mid-point of the sound file to the beginning with no gap at all. Better yet, download Audacity and hear how it sounds. Because of the discontinuity, you are going to get an unpleasant crackling or pop sound. Perhaps that 1 fiftieth of a second of silence actually sounds better than immediately restarting.
If you want an uninterrupted audio stream, you're going to have to put away the easy AVAudioPlayer interface and build an AUGraph. Of course, this means learning a complex audio interface and learning all about audio formats. And then you have to figure out what data you're going to stuff into your audio stream.
How would you make your loop sounds nice? You might try fading out at the touch point, and then fading back in. Or you could search for the zero crossings at the beginning and end of your loop (zero crossings are the place where the value of your sound wave is 0. They happen all the time in a mono output, but might be harder to find if your output is stereo.) In the end will this sound nicer than the 20 ms of silence? Get out Audacity and experiment with looping before you enter the long road of learning the AUGraph interace.
To the same song, how about stopping it, then prepareToPlay and play?
I know that you can easily set the volume property of the music player, but I want to do it smoothly like Google Maps does when they use the voiceover for navigation instructions.
I was wondering what the best way to do this is.
Thanks!
I would try using a repeating NSTimer. Every time the timer fires you lower the volume a bit. When it reaches the target value you invalidate the timer.
Other ways of getting a repeated event (so that you can do something in stages gradually over time) are DISPATCH_SOURCE_TYPE_TIMER and CADisplayLink. But I think a timer is probably the simplest way to get started.
If you have a pre-existing sound that you're playing, a completely different solution is to apply a fadeout to it before you start playing it (and then just play it all at the same volume, because the sound itself fades out, do you see). AVFoundation gives you the tools to do that (e.g. setVolumeRampFromStartVolume:toEndVolume:timeRange:).
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.
I think we all run into this as a user as well. For example, when I'm playing a Youtube video, and the connection is slow, it will not play until enough content has been buffered. But sometimes even after the content arrived it won't resume playing.
Most of the times when this happens I just click once somewhere on the player bar and it will resume.
Now, I think I've run into this situation while programming with HTML5 Audio elements and Youtube APIs. Most of the times they work well, but when the connection is spotty, it will just stop and never resume even after content has arrived. Is there a way to get around this problem? Thank you!
HTML5 Media Elements trigger a lot of useful events that might help you solve this problem. I would consider starting a timer when the waiting event fires (which indicates buffering) then periodically attempt to resume playing the track until canplay fires.
You could also call load instead of play then wait for canplaythrough fires (which indicates that media can play continuously without needing to pause for buffering).
Of course it's possible that the YouTube API prevents these events from propagating to the containing page, but it's worth a try.