0

With some help from stackoverflow, I managed to implement a timer that checks the currentPlaybackTime variable in a mediaplayer video every 1/10th of a second.

I also have an NSArray of Cue objects that have a time attribute (NSNumber). I want some code that simply checks if the currentPlaybackTime matches one of the time attributes in the cue array and returns the cue object.

My progress so far:

- (void) PollPlayerTimer_tick:(NSObject *)sender {
    if (self.movieController.playbackState == MPMoviePlaybackStatePlaying)
        self.lastRecordedPlaybackTime = self.movieController.currentPlaybackTime;

        if (decidingStatement) {
        // Do something
        }
}

The decidingStatement has to be something like:

[NSNumber numberWithDouble:self.movieController.currentPlaybackTime] doubleValue] is equal to one of the time attributes in cues. It doesn’t seem to work using [NSNumber numberWithDouble:self.movieController.currentPlaybackTime] doubleValue] == 3.9 (for example) because currentPlaybackTime could be 3.99585 which is not exactly 3.9. It works if I check if currentPlaybackTime is within plus or minus 0.1 seconds of 3.9.

The second problem is that I have to search the cues array to get the cue time. I could iterate through the array to get the time attribute. But iterating over an array ten times a second doesn’t seem like the best practise.

There must be a better way!

Update

danh has a great solution below, the code currently looks like:

- (void) PollPlayerTimer_tick:(NSObject *)sender {

    if (self.movieController.playbackState == MPMoviePlaybackStatePlaying)
        self.lastRecordedPlaybackTime = self.movieController.currentPlaybackTime;
    NSInteger *decidingStatement = [self indexOfCueMatching:[[NSNumber numberWithDouble:self.movieController.currentPlaybackTime] doubleValue] in:self.video.cues];
        if (decidingStatement) {

        }

}

- (NSComparisonResult)number:(NSNumber *)nA nearlyEquals:(NSNumber *)nB within:(double)epsilon {

    double dNa = [nA doubleValue];
    double dNb = [nB doubleValue];

    if (fabs(dNa - dNb) < epsilon) return NSOrderedSame;
    if (dNa < dNb) return NSOrderedAscending;
    return NSOrderedDescending;
}

- (NSInteger)indexOfCueMatching:(NSNumber *)playbackTime in:(NSArray *)cues {

    NSInteger index = self.lastCheckedIndex+1;
    if (index >= cues.count) return NSNotFound;

    NSComparisonResult compResult = [self number:[cues[index] time] nearlyEquals:playbackTime within:0.1];
    while (index<cues.count && compResult == NSOrderedAscending) {
        compResult = [self number:[cues[++index] time] nearlyEquals:playbackTime within:0.1];
    }
    self.lastCheckedIndex = index;

    return (compResult == NSOrderedSame)? index : NSNotFound;
}
Community
  • 1
  • 1
Dol
  • 944
  • 3
  • 10
  • 25

1 Answers1

1

Problem one is to match NSNumber doubles within some tolerance. That would look something like this:

- (NSComparisonResult)number:(NSNumber *)nA nearlyEquals:(NSNumber *)nB within:(double)epsilon {

    double diff = [nA doubleValue] - [nB doubleValue];

    if (fabs(diff) < epsilon) return NSOrderedSame;
    else return (diff < 0.0)? NSOrderedAscending : NSOrderedDescending;
}

Problem two is a more algorithmic. Finding the cue in the array of numbers given an arbitrary time from the player is definitely a search, but it's a search with some very big hints: a) the cues are probably sorted, b) the playback times are appearing sequentially, increasing monotonically.

Keeping just a little state, like the index of the last place where you checked, you should need to do very little or no iterating through your array.

@property (assign, nonatomic) NSInteger lastCheckedIndex;

Somewhere, before you begin, assign self.lastCheckedIndex = -1. Then, each time your timer fires, get the current playback time, wrap it as [NSNumber numberWithDouble:, and call something like this:

- (NSInteger)indexOfCueMatching:(NSNumber *)playbackTime in:(NSArray *)cues {

    NSInteger index = self.lastCheckedIndex+1;
    if (index >= cues.count) return NSNotFound;

    NSComparisonResult compResult = [self number:cues[index] nearlyEquals:playbackTime within:0.1];
    while (index<cues.count && compResult == NSOrderedAscending) {
        compResult = [self number:cues[++index] nearlyEquals:playbackTime within:0.1];
    }
    self.lastCheckedIndex = index;

    return (compResult == NSOrderedSame)? index : NSNotFound;
}

This should answer either the index in your cue array that matches, or NSNotFound if no matching cue is found. Moreover, this should update the lastCheckedIndex knowing that the next time coming from the player will be larger than the last, and the next time in your cue array will be larger as well.

EDIT - specifically, use it like this...

NSTimeInterval playbackTime = self.movieController.currentPlaybackTime;
self.lastRecordedPlaybackTime = playbackTime;

NSNumber *playbackTimeNumber = [NSNumber numberWithDouble:playbackTime];
NSInteger index = [self indexOfCueMatching:playbackTimeNumber in:self.video.cues];

if (index != NSNotFound) {
    // whatever we do when we find a matching cue
}

Hope that's helpful.

danh
  • 62,181
  • 10
  • 95
  • 136
  • That makes sense actually. Thanks for the explication! I'm going to test it and be back (I'm new with Objective C and therefore a little slow :))! – Dol Jan 04 '14 at 19:15
  • 1
    good luck. I just noticed that the procedure hangs when we run out of cue positions in the array. modified slightly to catch that. please see edit. – danh Jan 04 '14 at 19:19
  • Almost there! I like this solution but have one small error to get rid of before I can test it. I updated the question with the code implemented but the line `NSInteger *decidingStatement = [self indexOfCueMatching:[[NSNumber numberWithDouble:self.movieController.currentPlaybackTime] doubleValue] in:self.video.cues];` is complaining about `[[NSNumber numberWithDouble:self.movieController.currentPlaybackTime] doubleValue]` with "`Sending 'double' to parameter of incompatible type 'NSNumber *'`". Any idea why? That line seems to work ok everywhere else.. – Dol Jan 04 '14 at 20:10
  • 1
    You've declared deciding statement as an integer pointer. I'll edit my answer to be more explicit about using it. – danh Jan 04 '14 at 20:53
  • Thanks, just debugging the last little error, trying to get through it myself quickly.. – Dol Jan 04 '14 at 21:26
  • I had to change `cues[index]` to `[cues[index] time]`. The application is running fine but for some reason `return (compResult == NSOrderedSame)? index : NSNotFound;` always returns `NSNotFound`. – Dol Jan 04 '14 at 21:57
  • For some reason `playbackTime` is returning `0`, therefore firing at the beginning of the video, but still never returning `index` – Dol Jan 04 '14 at 22:03
  • 1
    Not sure about why the playback time is being reported incorrectly. Sounds like a separate SO question. The code I supplied assumes that cues contains NSNumbers with doubles (time intervals) relative to the start of the stream. Not sure what object responds to 'time'. – danh Jan 04 '14 at 22:22
  • Yeah I thought that too so I tested it with a 4 double, still the same. Thanks for your help. I'll make a new question. – Dol Jan 04 '14 at 22:26
  • Time is just an NSNumber, 4.0 actually in my database for the first Cue. I'll play around with it and get it working. Thanks again! – Dol Jan 04 '14 at 22:29