I have MPMoviePlayer that load movie from stream. I implemented timeout on 15 seconds with timers. But is there maybe some other better way to implement timeout without timer?
Register for the MPMoviePlayerLoadStateDidChangeNotification. Within its handler, check the current loadstate and mask out the MPMovieLoadStateStalled.
- (void)MPMoviePlayerLoadStateDidChange:(NSNotification *)notification
{
//is the player stalled, hence possibly starving?
if ((movieController_.loadState & MPMovieLoadStateStalled) == MPMovieLoadStateStalled)
{ //yes->do something
NSLog(#"hey there, I am starving to death here");
}
}
You may want to register a timer within the upper if-clause - e.g. 10seconds. Once that baby runs out of time without further state-changes, do something to terminate / skip the video playback.
I'm not sure but i think it's possible to use performSelector as timer?
[self performSelector:#selector(checkTimeout:) withObject:theMovie afterDelay:15];
and then check for the movie state.
Related
OSStatus err = AudioQueueNewOutput(&audioDescription, AudioPlayerAQOutputCallback, ( void* )self, nil, nil, 0, &audioQueue);
if( err != noErr )
NSLog(#"Couldn't open AudioFile.");
err = AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, isRunningProc, self);
if( err != noErr )
NSLog(#"Couldn't register for playback state changes.");
this callback function only be called once after AudioQueueStart(audioQueue, NULL);
what ever i call AudioQueuePause(audioQueue);
or audio reach to end.
static void isRunningProc(void * inUserData,
AudioQueueRef inAQ,
AudioQueuePropertyID inID)
what i have missed?
I did a short test on this:
It does indeed seem like the callback it is not called either for pause or for start, when you are resuming a pause.
But this is not something you cannot solve. You started the song somehow. This will trigger the property listener. Equally if the song stops. Or you stop it. You may have to trigger the property listener yourself somehow using something like this in your play routine:
if (bytesRead == 0) {
//This will trigger the property listener
AudioQueueStop(inAQ, false);
}
else {
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
As AudioQueue is concerned, as long as you keep feeding it with audio buffers to play, it is still playing. (I also tested not feeding any buffers at all, which did not trigger a stop, so you have to call stop explicitly to trigger the property listener.)
This means you already know whether your song is playing or not. To pause or un-pause is requested by clicking your button. If the song is not playing, don't do anything. If the song is playing, call AudioQueuePause and set a flag that you have paused your music. Remember to check the error code. (See (1) below). If the flag says that you have paused the music, call AudioQueueStart, and clear the flag indicating if you have paused or not. Again check the error code.
(1) Why check the error code?
First, although unlikely, an error may occur because it is a blue moon.
However, my concern is with multiple threads. AudioQueue obviously runs on a separate thread than your GUI. That means if you test a flag whether music is playing or not, this state cannot fully be trusted because it might have changed since you tested the status. Another thread might have snuck in between your test and your action based on that test.
Say you check that the song is already playing. (It is.)
Then you ask the song to pause, but the song is really stopped since it reached the end in the meantime before you got to ask the song to pause.
Then you ask to pause the song. But it is already stopped.
What happens then? I don't really know. It might not even be a problem in this situation, but things like this is worth considering. It needs testing, or at least a consulting with the documentation.
How about another scenario? What if the song is stopped and you ask to start it again. I would think that is a worse scenario, but it might not be a problem. Again consider those cases and check the documentation, or even test yourself.
I need to play 2 sounds with 2 AVAudioPlayer objects at the same exact time... so I found this example on Apple AVAudioPlayer Class Reference (https://developer.apple.com/library/mac/#documentation/AVFoundation/Reference/AVAudioPlayerClassReference/Reference/Reference.html):
- (void) startSynchronizedPlayback {
NSTimeInterval shortStartDelay = 0.01; // seconds
NSTimeInterval now = player.deviceCurrentTime;
[player playAtTime: now + shortStartDelay];
[secondPlayer playAtTime: now + shortStartDelay];
// Here, update state and user interface for each player, as appropriate
}
What I don't understand is: why also the secondPlayer has the shorStartDelay?
Shouldn't it be without? I thought the first Player needed a 0.1 sec delay as it is called before the second Player... but in this code the 2 players have the delay...
Anyone can explain me if that is right and why?
Thanks a lot
Massy
If you only use the play method ([firstPlayer play];), firstPlayer will start before the second one as it will receive the call before.
If you set no delay ([firstPlayer playAtTime:now];), the firstPlayer will also start before de second one because firstPlayer will check the time at which it is supposed to start, and will see that it's already passed. Thus, it will have the same behaviour as when you use only the play method.
The delay is here to ensure that the two players start at the same time. It is supposed to be long enough to ensure that the two players receive the call before the 'now+delay' time has passed.
I don't know if I'm clear (English is not my native langage). I can try to be more clear if you have questions
Yeah what he said ^ The play at time will schedule both players to start at that time (sometime in the future).
To make it obvious, you can set "shortStartDelay" to 2 seconds and you will see there will be a two second pause before both items start playing.
Another tip to keep in mind here are that when you play/pause AVAudioPlayer they dont actually STOP at exactly the same time. So when you want to resume, you should also sync the audio tracks.
Swift example:
let currentDeviceTime = firstPlayer.deviceCurrentTime
let trackTime = firstPlayer.currentTime
players.forEach {
$0.currentTime = trackTime
$0.play(atTime: currentDeviceTime + 0.1)
}
Where players is a list of AVAudioPlayers and firstPlayer is the first item in the array.
Notice how I am also resetting the "currentTime" which is how many seconds into the audio track you want to keep playing. Otherwise every time the user plays/pauses the track they drift out of sync!
Hi all I'm using AVQueuePlayer to play a sequence of media files (audio, video). I sometimes have PlayItems that are shorter than the durations I need i.e. I want a silence between some items. I have been considering trying to use some combination of addPeriodicTimeObserverForInterval addBoundaryTimeObserverForTimes or running my own NSTimer.
It doesn't need to be super accurate + or - 1 Second is acceptable.
I'm wondering if there is any collective wisdom out there about these using these API calls to achieve this kind of functionality ?
Why not observe the end of the items, and then, if necessary, start to play again only after a certain delay?
You start to observe the end of an AVPlayerItem like this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playEnded) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
Then, in your playEnded method, you can decide how long you need to wait, and call another method to start play of the next item after a delay.
-(void)playEnded {
[self performSelector:#selector(playNextItem) withObject:nil afterDelay:5.0];
}
Okay, I have searched online and even looked in a couple of books for the answer because I can't understand the apple documentation for the NSTimer. I am trying to implement 2 timers on the same view that each have 3 buttons (START - STOP - RESET).
The first timer counts down from 2 minutes and then beeps.
The second timer counts up from 00:00 indefinitely.
I am assuming that all of the code will be written in the methods behind the 3 different buttons but I am completely lost trying to read the apple documentation. Any help would be greatly appreciated.
Basically what you want is an event that fires every 1 second, or possibly at 1/10th second intervals, and you'll update your UI when the timer ticks.
The following will create a timer, and add it to your run loop. Save the timer somewhere so you can kill it when needed.
- (NSTimer*)createTimer {
// create timer on run loop
return [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerTicked:) userInfo:nil repeats:YES];
}
Now write a handler for the timer tick:
- (void)timerTicked:(NSTimer*)timer {
// decrement timer 1 … this is your UI, tick down and redraw
[myStopwatch tickDown];
[myStopwatch.view setNeedsDisplay];
// increment timer 2 … bump time and redraw in UI
…
}
If the user hits a button, you can reset the counts, or start or stop the ticking. To end a timer, send an invalidate message:
- (void)actionStop:(id)sender {
// stop the timer
[myTimer invalidate];
}
Hope this helps you out.
I would follow Jonathan's approach except you should use an NSDate as your reference for updating the UI. Meaning instead of updating the tick based on the NSTimer, when the NSTimer fires, you take the difference between NSDate for now and your reference date.
The reason for this is that the NSTimer has a resolution of 50-100 ms which means your timer can become pretty inaccurate after a few minutes if there's a lot going on to slow down the device. Using NSDate as a reference point will ensure that the only lag between the actual time and the time displayed is in the calculation of that difference and the rendering of the display.
I am using multiple instances of AVAudioPlayer to play multiple audio files simultaneously. I run a loop to start playing the audio files (prepareToPlay is called beforehand and the loop only makes a call to the play method)
But invariably, one of the players does not play in sync. How can I ensure that all the 4 players start playing audio simultaneously?
Thanks.
The Apple docs talk about how you can "Play multiple sounds simultaneously, one sound per audio player, with precise synchronization". Perhaps you need to call playAtTime: e.g. [myAudioPlayer playAtTime: myAudioPlayer.deviceCurrentTime + playbackDelay];
In fact, the Apple docs for playAtTime: contain the following code snippet:
NSTimeInterval shortStartDelay = 0.01; // seconds
NSTimeInterval now = player.deviceCurrentTime;
[player playAtTime: now + shortStartDelay];
[secondPlayer playAtTime: now + shortStartDelay];
They should play simultaneously (assuming you choose a large enough value for shortStartDelay -- not so soon that it happens before this thread returns or whatever).
Unfortunately, you can't. AVAudioPlayer doesn't provide any mechanism for fine-grained control of start time. The currentTime property sets the point in the file to read from, it doesn't guarantee when the AVAudioPlayer instance will start playing in system time, which is what you need to sync multiple audio streams.
When I need this behavior, I use the RemoteIO Audio Unit + the 3D Mixer Audio Unit + ExtAudioFile.
EDIT
Note that as of iOS 4, you can synchronize multiple AVAudioPlayer instances using playAtTime:
This code segment of mine allows you to do this as long as you don't have to do it instantly. You can pass in the targetTime as a timestamp for when you want to hear the sounds. The trick is to make use of time-stamps and the delay functionality of NSObject. Also, it utilizes the fact that it takes way less time to change the volume of the player than it does to change the current time. Should work almost perfectly precisely.
- (void) moveTrackPlayerTo:(double) timeInSong atTime:(double) targetTime {
[trackPlayer play];
trackPlayer.volume = 0;
double timeOrig = CFAbsoluteTimeGetCurrent();
double delay = targetTime - CFAbsoluteTimeGetCurrent();
[self performSelector:#selector(volumeTo:)
withObject:[NSNumber numberWithFloat:single.GLTrackVolume]
afterDelay:delay];
trackPlayer.currentTime = timeInSong - delay - (CFAbsoluteTimeGetCurrent() - timeOrig);
}
- (void) volumeTo:(NSNumber *) volNumb {
trackPlayer.volume = [volNumb floatValue];
}
Try to set same currentTime property value for every AVAudioPlayer object.