Daniele Ravizza
Daniele Ravizza

Reputation: 121

IOS addPeriodicTimeObserverForInterval fire too many times

i have noticed a strange thing happening to my app. It's a video app, that use the AVFoundation classes.

I need to fire some events at given time. I put some code then i comment it :

/* I prepare the movie clip */
AVMutableComposition *composition = [[AVMutableComposition alloc] init];
NSBundle *bundle = [NSBundle mainBundle];
NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
NSString *path = [bundle pathForResource:@"13.VIDEO_A (BAULE)" ofType:@"mp4"];
NSURL *videoUrl = [NSURL fileURLWithPath:path];
AVURLAsset* sourceAsset = [AVURLAsset URLAssetWithURL:videoUrl options:optionsDictionary];
[composition insertTimeRange:CMTimeRangeMake(kCMTimeZero, [sourceAsset duration]) ofAsset:sourceAsset atTime:currentTime error:NULL];

In my viewDidLoad i prepare the clip. I use AVUrlAsset to be able to use the options dictionary with AVURLAssetPreferPreciseDurationAndTimingKey to have a more precise use.

/* I create the player */
AVPlayer *mPlayer = [AVPlayer playerWithPlayerItem:[AVPlayerItem playerItemWithAsset:composition]];
AVPlayerLayer *mPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:mPlayer];
mPlayerLayer.frame = CGRectMake(0.00, 96.00, 1024.00, 576.00);
[self.view.layer addSublayer: mPlayerLayer];

I create a player with a item from my AVUrlasset and then i create a layout in my view

/* I set the observer */
[mPlayer addPeriodicTimeObserverForInterval:CMTimeMake(5,25) queue:NULL usingBlock:^(CMTime time) { 
     NSLog(@"Event : value: %lld, timescale %d, seconds: %f",
         time.value, time.timescale,(float) time.value / time.timescale); }];

I set the observer, every 5/25 of second, 0,2 seconds (25 is the framerate of the movie). In my block i only write log for now.

/* Play the movie */
[mPlayer play];

At the end i play.

Seems everything working except that my log is wrong :

2012-11-15 16:43:05.382 PerfectCircle Beta[6680:707] Evento : value: 0, timescale 1, seconds: 0.000000
2012-11-15 16:43:05.410 PerfectCircle Beta[6680:707] Evento : value: 0, timescale 1, seconds: 0.000000
2012-11-15 16:43:05.563 PerfectCircle Beta[6680:707] Evento : value: 0, timescale 1, seconds: 0.000000
2012-11-15 16:43:05.580 PerfectCircle Beta[6680:707] Evento : value: 0, timescale 1, seconds: 0.000000
2012-11-15 16:43:05.747 PerfectCircle Beta[6680:707] Evento : value: 5489807, timescale 1000000000, seconds: 0.005490
2012-11-15 16:43:05.751 PerfectCircle Beta[6680:707] Evento : value: 8949705, timescale 1000000000, seconds: 0.008950
2012-11-15 16:43:05.753 PerfectCircle Beta[6680:707] Evento : value: 10679967, timescale 1000000000, seconds: 0.010680
2012-11-15 16:43:05.990 PerfectCircle Beta[6680:707] Evento : value: 248121672, timescale 1000000000, seconds: 0.248122
2012-11-15 16:43:06.169 PerfectCircle Beta[6680:707] Evento : value: 426865945, timescale 1000000000, seconds: 0.426866

After a random number of fires it's starting count well. But it fire the event 5/6 times more at start. I tried different movies and codec. If i raise the rate (es: CMTimeMake(25,25) ) nothing change.

I started my work with addBoundaryTimeObserverForTimes in this way :

NSArray *starts = [NSArray arrayWithObjects:[NSValue valueWithCMTime:CMTimeMakeWithSeconds(0.2,25)],nil];
[_player addBoundaryTimeObserverForTimes:starts queue:NULL usingBlock:^{ log_function }];

But i had the same problems. But here if i raise the rate i dont see anymore the problem (but its not good for my target). My problem is that i must count precisely how many time the movie play a precise moment. And i cannot test it with if (currenttime==0.3) because its not precise.

It's a bug ? I miss something ? Have u ever heard of something similar ?

Thanks for helping. Daniele

UPDATE : It seems to be an issue at start and end.

2012-11-15 16:43:05.747 PerfectCircle Beta[6680:707] Evento : value: 0, timescale 1, seconds: 0.000000
2012-11-15 16:43:05.747 PerfectCircle Beta[6680:707] Evento : value: 5489807, timescale 1000000000, seconds: 0.005490

The wrong logs have a different timescale towards the right ones. The same happen at the end of playback. It seems that at start and end it execute the timer but the movie isn't yet loaded or already closed. I tried put the observer after play but nothing changed.

I also tried a different and more higher timescale for mine CMTimeMake ... but no effects

Upvotes: 4

Views: 8216

Answers (2)

Fernando
Fernando

Reputation: 102

I know that this question is a bit old, but anyway.......

First things first:

If you check the documentation you may see the following statement.

The block is invoked periodically at the interval specified, interpreted according to the timeline of the current item. The block is also invoked whenever time jumps and whenever playback starts or stops. If the interval corresponds to a very short interval in real time, the player may invoke the block less frequently than requested. Even so, the player will invoke the block sufficiently often for the client to update indications of the current time appropriately in its end-user interface.

Which indicates why it is being called at the start and end playback.

About the function being called several times I guess it's happening because of the internal state changes that the player is suffering. I've checked for the 'rate', 'status' and 'playerItem' properties of the player but nothing seems to say what's happening.

One thing you can do o work around this is only consider events after the player is really playing.

Add the following code before you call the play method.

__block AVPlayer* blockPlayer = self.player;
__block typeof(self) blockSelf = self;

// Setup boundary time observer to trigger when audio really begins,
// specifically after 1/3 of a second playback
self.startObserver = [self.player addBoundaryTimeObserverForTimes:
        @[[NSValue valueWithCMTime:CMTimeAdd(self.player.currentTime, CMTimeMake(1, 3))]]
                             queue:NULL
                        usingBlock:^{                    
                          blockSelf.isPlaying = YES;    
                          // Remove the boundary time observer
                          [blockPlayer removeTimeObserver:blockSelf.startObserver];
                        }];

Now on the addPeriodicTimeObserverForInterval block you just need to check the variable that we've just assigned.

__block typeof(self) blockSelf = self;  
[self addPeriodicTimeObserverForInterval:CMTimeMake(60, 1)
                                   queue:dispatch_get_main_queue()
                              usingBlock:^(CMTime time)
                                  {
                                      if (blockSelf.isPlaying) {
                                         ... do some stuff here
                                      }
                                  }];

Well is not the cleaner solution but worked fine for me. If i find something better i'll come edit.

Upvotes: 4

amergin
amergin

Reputation: 3176

Perhaps in your CMTime call you need to set greater precision. At the moment you are setting it to 25ths of a second but that allows no leeway for rounding of float values. Try using 25,000 as your timescale and see if that works.

Upvotes: 0

Related Questions