How we built topsee – the technical details – an intro movie

November 5th, 2009

This is the second post in our technical series about topsee. If you’re not interested in iPhone development, feel free to skip this post, I promise we won’t be offended!

One of the interesting things in working with ustwo™ is they are far more design oriented than we are; it’s one of the reasons we chose to work with them! This also means that they will give us designs that take us out of our comfort zone, which can only be a good thing.

The design for topsee includes a rather snazzy intro movie – it’s going to play while we are loading the views and finding your location. Easy peasy we thought, the iPhone plays movies, we have a movie, what can possibly go wrong?

OK, first things first, lets do a bit of research about playing a movie on the iPhone. A little bit of Googling tells me that I’m going to be working with the MPMoviePlayerController which looks pretty simple, there’s just three interesting methods, init, play and stop.

I take my movie and I create a simple test application (you can get this from github if you’re interested). I use the least amount of code possible just to make sure that it all works.

1
2
3
4
5
6
7
8
9
- (void)applicationDidFinishLaunching:(UIApplication *)application {    
 
    // Override point for customization after application launch
    [window makeKeyAndVisible];
 
    // Play the intro movie
    MPMoviePlayerController *moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"intro_animation" ofType:@"mov"]]];
    [moviePlayer play];
}

Well, it does technically work but the status bar is visible, the movie plays sideways, the controls appear if it’s touched, it doesn’t loop, it’s got an ugly blank flicker at the start and I’m pretty sure that I’m going to be leaking memory as I’m not releasing the moviePlayer object. Wow, six bugs in two lines of code – not a personal record but it’s getting close!

OK, taking these bugs one at a time, lets fix the sideways problem. It turns out that the iPhone can only play movies in landscape mode. However, you can rotate the ui back to portrait as soon as you start playing the movie and if you make your movie rotated 90° the other way, the user will never notice! If that sounds ridiculous, I agree – why we can’t we just play a movie in portrait to begin with? You’ll have to ask apple!

While I’m here, we can fix the status bar problem as well. My app delegate now looks like this :

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)applicationDidFinishLaunching:(UIApplication *)application {    
 
    // Override point for customization after application launch
    [window makeKeyAndVisible];
 
    // Play the intro movie
    MPMoviePlayerController *moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"intro_animation" ofType:@"mov"]]];
    [moviePlayer play];
 
    // Keep the ui rotated correctly
    [[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
    [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO];
}

It turns out that removing the controls is fairly trivial as well, we just add the following line just before we play the movie.

1
    moviePlayer.movieControlMode = MPMovieControlModeHidden;

The remaining three bugs are going to require a bit more work to fix so I’ve tagged where we are so far in our github project.

In the documentation, our moviePlayer object is going to fire off some notifications that will help us understand what’s going on. Reading up on these sheds some light on the flicker that we get before the movie starts to play. When our moviePlayer starts, it loads and prepares the movie before starting to play. It’s this pause that’s causing our flicker – the Default.png has vanished because the app has started but the movie is still pre-loading so can’t display anything yet.

Luckily, we can fix this with a bit of trickery. Before we start to play the movie, we can load Default.png as a background image for our window. As long as we make sure that Default.png is the same as the first frame of the movie, the user will never notice the difference! This is very simply done by changing our app delegate slightly so it looks like:

1
2
3
4
    // Override point for customization after application launch
    movieFirstFrame = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Default.png"]];
    [window addSubview:movieFirstFrame];
    [window makeKeyAndVisible];

and setting the background color of our movie player to clear like so:

    moviePlayer.backgroundColor = [UIColor clearColor];

So, the final bug is to make the movie loop properly. We can use the MPMoviePlayerPlaybackDidFinishNotification and restart the movie each time that it finishes in this method:

1
2
3
4
5
6
7
8
// When the playback finishes, loop the movie
- (void) moviePlayBackDidFinish:(NSNotification*)notification {
    MPMoviePlayerController *moviePlayer = (MPMoviePlayerController *)notification.object;
    moviePlayer.initialPlaybackTime = 0.0f;
    [moviePlayer play];
    [[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
    [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO];
}

and listening for notifications in the applicationDidFinishLaunching: method like so:

1
2
3
4
5
    // Listen to the movie's notifications
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(moviePlayBackDidFinish:)
                                                 name:MPMoviePlayerPlaybackDidFinishNotification
                                               object:moviePlayer];

You know what, we’re almost almost there, only the memory leak to fix. Luckily, I think that the memory leak will fix itself in the future – as I know that I don’t want to be releasing the moviePlayer until I have finished with it, I’m going to release it when I have finished setting up my app and want to stop the movie. As this is an example, instead of actually doing any setup work, I’m just going to start a timer to pretend that it’s doing something; I’m going to pretend that in 15 seconds time I have finished:

1
    [NSTimer scheduledTimerWithTimeInterval:15.0 target:self selector:@selector(startupComplete:) userInfo:nil repeats:NO];

This will schedule a call to startupComplete: after 15 seconds so we’d better have a method in place. This method has to stop the moviePlayer, release it and remove the first frame image:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void) startupComplete:(NSTimer *)timer {
 
    // Stop listening to the movie player
    [[NSNotificationCenter defaultCenter] removeObserver:self
    name:MPMoviePlayerPlaybackDidFinishNotification
    object:moviePlayer];
    [moviePlayer stop];
    [moviePlayer release];
    moviePlayer = nil;
 
    // Remove the first frame image and reveal the ui
    [movieFirstFrame removeFromSuperview];
}

That’s it! We now have a startup movie that will play and loop until the app has finished doing whatever it needs to startup. In the case of topsee, this was using CLLocationManager to find our location and get the top things nearby but it could be anything at all. User tests that we’ve done show that before we added the startup movie, users were bored with the startup time; after we added the movie, users were more than happy with the time the app took to start as long as they could watch a pretty picture!

You can get the finished project from github – please download it and try it out for yourselves; throw breakpoints in, play with the code etc. If you have any feedback, it’s more than welcome; just add a comment below.

Next time, we’ll look at getting the app to work in the underground with no internet connection – that means CoreData and NSThread.

Sam


Leave a Comment