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.
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.