Improving tutorial movie load times on iOS for fun and profit

A common pattern for iOS apps (including Leave Now) is to use video for tutorials and onboarding sequences. Video can be a great way to get customers engaged with an app quickly; we've seen significant improvements in retention with customers that watch our "getting started" video.

If you have a very small video that you don't want to update after your app has shipped, bundling it as a resource is quick and easy. Unfortunately, there are some problems with this approach: even if your video is small, you may need to ship multiple language versions if you add translations and that can make your application relatively large in a hurry.

Having your tutorial videos accessed over the network on demand has advantages over bundling: the videos can be as long as you want (or as long as you think your customers will watch) without affecting your application size and you can change them after you've released your app (perhaps to run an A/B test or in response to feedback).

The first step is to make sure that your videos are hosted on a CDN. There's no point in optmizing your client playback if your movie hosting is the bottleneck. We use Amazon CloudFront and have had good results.

However, even with our movie on the CDN, we have a problem. Here's the code:

- (IBAction)didTapShowVideoButton:(UIButton *)sender {
    NSURL *movieURL = [NSURL URLWithString:@"http://static.example.com/movie.mov"];
    MPMoviePlayerViewController *vc = [[MPMoviePlayerViewController alloc] initWithContentURL:movieURL];
    [self presentMoviePlayerViewControllerAnimated:vc];
}

Pretty straightforward; when the user taps the video play button we make a MPMoviePlayerViewController and present it.

The problem here is that it's slow. In my testing, it would take about five seconds for video playback to start on WiFi. Five seconds doesn't sound like a long time; however, your customer may disagree and go find something else to do.

How do we fix this? We pre-load the movie:

@property (strong, nonatomic) MPMoviePlayerViewController *moviePlayerViewController;

- (void)viewDidAppear:(BOOL)animated {
    NSURL *movieURL = [NSURL URLWithString:@"http://static.example.com/movie.mov"];
    MPMoviePlayerViewController *vc = [[MPMoviePlayerViewController alloc] initWithContentURL:movieURL];
    vc.moviePlayer.shouldAutoplay = NO;
    [vc.moviePlayer prepareToPlay];
    self.moviePlayerViewController = vc;
}

- (IBAction)didTapShowVideoButton:(UIButton *)sender {
    self.moviePlayerViewController.moviePlayer.shouldAutoplay = YES;
    [self presentMoviePlayerViewControllerAnimated:self.moviePlayerViewController];
}

As soon as our onboarding view controller appears, we create the movie player view controller and prepare it for playback. We have to tell it to not autoplay so that the moviePlayer won't start playback before we present it. This causes the moviePlayer to begin to download the movie immediately and fill the playback buffer. Then, when the customer taps the play button, we just tell the moviePlayer to autoplay and present the already prepared MPMoviePlayerViewController.

With this technique, the movie starts playing almost immediately when the play button is tapped. There is a trade off: some of the movie gets downloaded to the device even if the customer never taps the play button so this isn't a useful technique for movies larger than around 20MB. However, for smaller tutorial movies it can be extremely effective. For comparison, our tutorial video for Leave Now is currently about 4MB.

If you have a better technique or a suggestion for improvement, I'd love to hear about it. Hit me up at mark@tetherpad.com.