XOR Media

Seamless iOS Splash Video

As part of a project I’m currently working on we wanted to have a brief splash video that plays the first time our app starts up for branding purposes. The video also kills a little time as some initial processing is happening.

Nothing earth-shattering, but the video only plays the first time and we want to reiterate the branding quickly on each subsequent startup for ~1s using a standard iOS splash image.

XOR Media iOS Splash
Video

The trouble with that is if you just naively setup the splash image using the standard mechanism and then install and play the MPMoviePlayerController in application:didFinishLaunchingWithOptions you’ll get a flash of black as the splash image disappears and is replaced by the movie player which will last until it has loaded and begun playing its content. We’re aiming higher than that and wanted to get the nice polish of a splash video without the flash of black.

This flickering black problem seems to be relatively common, in one case it’s even mentioned in the context of splash screens, but there are no real solutions provided (that I could find.) I’m going to walk through the highlights of my approach and point you towards a simple to use controller class that I’ve written based on what I learned.

Before We Begin

It should be obvious that to get our splash screen and video to match up seamlessly the first frame of the video needs to match the splash image exactly. I’ve found that the best way to do this is to do a screen shot of the video in the simulator sitting at the start (don’t play it.)

I would suggest starting by importing XOSplashVideoController.[hm] in to your project and adapting the code in XOAppDelegate.m to your needs. Once you’ve fed your splash video in to the controller go in to its .m file and comment out the ”[_player play]” line in splashLoadDidChange: and start up the app. You should see the app start up and the first frame of the video appear. At this point take your screen shot and open it up in your favorite image editor.

Depending on which device you’re creating the splash image for you’ll need to make a slightly different edit. For iPad you need to chop off the status bar, top 20 pixels in portrait, resulting in a 1004 pixel tall image.

For some reason that makes no sense to me (at least in terms of the inconsistency) the iPhone splash screen should be the full resolution of the device, 320x480. To get what you need here open up the screen shot and remove the status bar from the top filling it in with the background color of the video.

Now you can take these splash images and import them in by going to the project settings, summary tab. Now we’re on to the source of the flashes.

Where the Black Flash is Coming From

With the simple solution I was seeing about 0.25-0.4s of black between the end of the splash image and the beginning of the video. It happens while the video content is being loaded for playback. It’s a little annoying that MPMoviePlayerController is implemented such that its opaque while loading the video, if that weren’t the case none of this would be necessary, but …

Fix the First

The first flash is solved by doing two simple things. Waiting to add the video player until it has loaded its content and by adding a UIImageView to the window with the exact same image used for the splash screen as a placeholder.

The one trick when adding the background image is that it needs to be shifted to account for the status bar on the iPad, but not the iPhone.

// put a background image in the window, so that it'll show as soon as the splash
// goes away, this fixes most of the black flash
UIImage *image = [UIImage imageNamed:imageName];
_backgroundImageView = [[UIImageView alloc] initWithImage:image];
CGRect backgroundFrame = frame;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
    // shift the background frame down to allow for the status bar, which shows and 
    // takes up "sapce" during the splash image on ipad
    backgroundFrame.origin.y += [[UIApplication sharedApplication] statusBarFrame].size.height;
    backgroundFrame.size.height -= [[UIApplication sharedApplication] statusBarFrame].size.height;
}
_backgroundImageView.frame = backgroundFrame;
[window addSubview:_backgroundImageView];

Now it’s on to how we wait until the video is loaded to add the player’s view to the window. Luckily MPMoviePlayerController provides a loaded notification, MPMoviePlayerLoadStateDidChangeNotification. Our initializer has the appropriate observe call.

// tell us when the video has loaded
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(splashLoadDidChange:)
                                             name:MPMoviePlayerLoadStateDidChangeNotification
                                           object:_player];

And when we’re notified we go ahead and add the player to the window and tell it to play.

- (void)splashLoadDidChange:(NSNotification *)notification
{
    // ...

    // the video has loaded so we can safely add the player to the window now
    UIWindow *window = [UIApplication sharedApplication].delegate.window;
    [window addSubview:_player.view];
    // and play it
    [_player play];

    // ...
}

Dammit There’s Still a Flash

So that got rid of the long black flash, but in it’s place there’s a new tiny black flash that happens once the video player is in the view hierarchy, but hasn’t yet shown its video content. Again no clue why it’s implemented this way, you’d think that loaded would mean the video is rendered to the appropriate view/buffer, but that’s apparently not the case.

Solve the Second

This one is pretty easy to solve, and in fact the solution is the same one used to tide things over while waiting for the video to load. This time we need to install a UIImageView in to the background of the video player which is visible during this final split second flash. Note that we’re using the same backgroundFrame we created for the first UIImageView so this one too will shift as appropriate.

// there's still a little bit of black flash left when the player is inserted
// as it starts to play, adding the splash image to the background of the player
// will get rid of it
UIImageView *playerBackground = [[UIImageView alloc] initWithImage:image];
playerBackground.frame = backgroundFrame;
[_player.backgroundView addSubview:playerBackground];

Wrap Up

And that’s that. We now have a seamless splash image to splash video transition. To take a look at all of this code in context or grab a copy of the full working example, head over to the XOSplash Github repo. Feel free to contact me if you have any questions or problems with the code. As always I’m happy to accept pull-requests if you have suggestions for improvements.