Using AVQueuePlayer to enforce/respect desired gaps between PlayItems - iphone

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];
}

Related

Synchronize the playback of two or more AVAudioPlayer in Iphone

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!

AVPlayer seektoTime iPhone

I have used seekToTime in my application and its working properly. But I want to have some more information about it. Which is, if I have a streaming video file of 1 minute now I want to play it from second 15th to second 45 (first 15 seconds and the last 15 seconds will not play).
How can I do it?
I know that by use of seekToTime I can play a video from 15th second but how to stop it at 45th second and also get noticed by the method that the video has played for the specified time period?
CMTime timer = CMTimeMake(15, 1);
[player seekToTime:timer];
The above code takes me to the 15th second of the streaming file but how to stop it on 45th second and get notified too?
I have searched a lot but couldn't get any info.
Thanks
EDIT:
As #codeghost suggested, simply use forwardPlaybackEndTime.
You can simply use:
yourAVPlayerItem.forwardPlaybackEndTime = CMTimeMake(10, 1);
Here 10 is the time till the AVPlayerItem will play.
Set the forwardPlaybackEndTime property on your AVPlayerItem.
I don't know if there is something build-in for this in AVPlayer, but what i would do is build an function and call it with :
[self performSelector:#selector(stopPlaying) withObject:nil afterDelay:45];
-(void)stopPlaying{
[player pause];
}
this will stop playing after 45 seconds,and of course you can put instead 45 any number of seconds that you want.
You can use [AVPlayer addPeriodicTimeObserverForInterval:queue:usingBlock:] for that purpose.
Get the AVPlayer.currentTime periodically, and call pause or stop on the exact time.

stop and restart a method

I have to stop and restart a simple method that takes from 1 to 2 seconds to execute. How can this be accomplished? I have already tried [NSObject cancelPreviousPerformRequestsWithTarget:self] but it works only with performselector after delay. I have also tried to create a new thread but it doesn't seem to work...
This is my method:
-(IBAction)MyMethod
{
NSLog(#"start");
//Here is the code that takes time to execute. It regards UI intervention,graphic calculation, x and y position etc.
NSLog(#"end");
}
I want this effect: one click on the linked UIButton and the method start (so print start log and end log). If I click the linked UIButton before the NSLog is printed, the method must stop. Is this possible?
You'll want to use a background task. I would suggest subclassing and using a NSOperation and checking for isCancelled within the body of main. See Apple's documentation on using NSOperation and NSOperationQueue.
Well, to do this with threads what I usually do is dividing things into 3 sections:
start.
processing.
finish.
And it looks like this:
-(void)start:(id)sender{
//prepare everything and anything
[NSThread detachNewThreadSelector:#selector(processing:) toTarget:self withObject:nil];
}
-(void)processing:(id)sender{
//Perform all your calculations, you can't modify UI elements here
[self performSelectorOnMainThread:#selector(finish:) withObject:nil waitUntilDone:NO];
}
-(void)finish:(id)sender{
//Wrap everything up and do any modifications to the UI
}
Now, to cancel this you could add maybe use:
Cancels perform requests previously registered with
performSelector:withObject:afterDelay:.
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument

Best way to implement MPMoviePlayer timeout

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.

iPhone - start multiple instances of AVAudioPlayer simultaneously

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.