0

Our goal is to instantly display 3-5 seconds of a video clip when someone taps a button.

Displaying actual video involves a few second delay which is unacceptable. We need the playback to feel instantaneous.

Converting those 3-5 seconds into an animated PNG is a great alternative, but animated PNGs won't work in an iPhone app.

The best alternative now is to take the same images used to create the animated PNG, and animate the series of images in JavaScript.

Can anyone think of a better alternative?

Crashalot
  • 33,605
  • 61
  • 269
  • 439
  • Have you thought about preloading the video? – CrimsonDiego Jun 15 '12 at 22:35
  • Yes, it's more the overhead of loading the player. Do you know how to minimize the latency of the player loading and displaying the video (assuming the video is pre-loaded)? – Crashalot Jun 15 '12 at 22:36
  • If you preloaded the player as well, but kept it hidden (and not playing), would this not accomplish your goal? If the video player is shown based on user action, it gives you a few moments after the page loads to load it in the background, correct? – CrimsonDiego Jun 15 '12 at 22:39
  • What do you mean with animated PNGs? GIFs? Anyway, check out the animationImages property from UIImageView, I think it's what you want - http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIImageView_Class/Reference/Reference.html Good luck! – Ricard Pérez del Campo Jun 15 '12 at 22:49
  • Ricard: [APNG](http://en.wikipedia.org/wiki/APNG) And he's talking about Mobile Safari (with the possiblity of UIWebViews, I assume). UIImageView is probably not going to be very helpful here. Did you read the question? – CrimsonDiego Jun 15 '12 at 22:52

2 Answers2

1

Unless you preload them, images have the same problem as video, at least a 700ms delay on most cellular.

What you could do is have an image sprite acting as a film reel, similar to what google does for their animated doodles. You basically put your entire animation in one long image, place it as a background, and then using JavaScript advance the background position every frame. For example, if you had a 3s 200x100 image you wanted to advance every 100ms, you could do:

<div class="thumb" style="background-image: url(vid1.png); width:200px; height:100px" data-frames="30">
</div>

$('.thumb').bind('click', function() {
    var $img = $(this);
    var startTime = Date.now();
    var frames = $img.attr('data-frames');
    var width = $img.width();

    setInterval(function() {
    var frame = Math.round((Date.now() - startTime) / 100) % frames;
        $img.css('background-position', -frame * width + 'px 0';
    }, 100);
});

Update

Because you're just using an image here, you are no longer bound by format. Your best option could be to re-encode the filmstrip as a 60% or 85% JPG, dramatically reducing the file size. Since you're animating, quality becomes less of a factor.

Update 2

I meant to include frame skipping for cases where timeouts aren't perfect.

Brian Nickel
  • 26,890
  • 5
  • 80
  • 110
  • Load time for that is going to be atrocious, though - possibly longer than the video delay, especially over cellular. At least with GIF and video, you can play them before they fully load. – CrimsonDiego Jun 15 '12 at 22:43
  • Updated. You could overcome that by turning it into a JPG, which has become one of my favorite formats for big files on mobile thanks to its quality-shaving and tiny screens. I personally would adjust the number of frames and frames per second to keep the file around 30KB, making the load time identical to a 0 byte file. – Brian Nickel Jun 15 '12 at 22:53
  • That actually is a viable alternative assuming you can keep the filesize down. He did say 3-5 seconds, so even assuming 28fps thats <150 'frames' which with high compression on JPG wouldn't be too bad. **+1** although I still think preloading both video and videoplayer is a more versatile option though. – CrimsonDiego Jun 15 '12 at 22:56
  • Bear in mind that preloading video will kill a data plan, especially if we're dealing with users that just want to preview videos and not necessarily watch them. Mobile Safari also makes it intentionally difficult to start a video without explicit user action. – Brian Nickel Jun 15 '12 at 23:13
  • Thanks, everyone! @CrimsonDiego, is preloading the video and player an option if we're using PhoneGap? Or do we need to do this in Objective-C? It's not clear how to preload the Vimeo player, for instance. – Crashalot Jun 19 '12 at 01:57
0

UIImageView supports animationImages. This is an animated PNG for all intents and purposes. @Brian is correct, though, you'll have a little latency unless you preload.

So my advice is to preload.

That is just as true for video. You can get a MPMoviePlayerController all set to play, just don't add the view and play until the button is pressed.

Animation images idea:

- (void)readySet {

    NSMutableArray *images = [NSMutableArray array];

    for (int i=0; i<kIMAGE_COUNT; i++) {
        NSString *fn = [NSString stringWithFormat:@"image%d.png"];
        UIImage *image = [UIImage imageNamed:fn];
        [images addObject:image];
    }
    self.myUIImageView.animationImages = [NSArray arrayWithArray:images];
}

// go runs fast if we run readySet first
//
- (void)go {

    [self.myUIImageView startAnimating];
}

Video idea

// A little more complex because we need to get notified that it's ready to play,
// but the same principal...

- (void)readySet {

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayerLoadStateChanged:) name:MPMoviePlayerLoadStateDidChangeNotification object:nil];

    MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentURL:url];

    player.shouldAutoplay = NO;
    [player prepareToPlay];
    self.mp = player;
}

- (void)moviePlayerLoadStateChanged:(NSNotification*)notification {

    MPMovieLoadState state = [self.mp loadState];
    if (state & MPMovieLoadStatePlaythroughOK) {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerLoadStateDidChangeNotification object:nil];

        self.mp.view.frame = CGRect(/* where it will appear */)
    }
}

// go runs fast if we run readySet first (and the movie is ready)
//
- (void)go {

    MPMovieLoadState state = [self.mp loadState];
    if (state & MPMovieLoadStatePlaythroughOK) {
        [self.view addSubview:self.mp.view];
        [self.mp play];
    } else {
        self.mp.shouldAutoplay = YES;
    }
}
danh
  • 62,181
  • 10
  • 95
  • 136