Friday, May 22, 2009

Replacing the QuickTime logo with a custom image

IMPORTANT NOTE: As of iPhone OS 3.1, this technique no longer works. As I mentioned in the original post, this technique depended on the internal structure of the QuickTime player view hierarchy and could stop working at any time. And, so it has. I leave this post for historical purposes, but it is no longer accurate or valid. Except maybe as a warning to not depend on the internal structure of Apple's classes.

On an iPhone developer forum, someone recently asked whether it is possible to replace the QuickTime 'QT' logo that appears with the iPhone movie player when playing an audio file or stream in an iPhone app. Several people responded saying it is not possible, even though the YouTube app does it. I respectfully disagree. Presented below is a very simple technique which currently will in fact work, and is a technique I am using in an app already available on iTunes.

If you are curious what the end result looks like, take a look at the Hi Tony app on iTunes (link opens iTunes). It's a free download. [ EDIT: the technique I was originally using only works with 2.x of the iPhone OS. A new update to the Hi Tony! app which will work with 3.0 is pending with Apple.]

If you just want to see the code, it is provided below. It assumes a certain view name ("MPVideoBackgroundView") will exist in the movie player class. There's no guarantee Apple won't change this in the future. Use at your own risk.

I should point out that no hidden APIs or function calls are being used, so in theory Apple should not reject your app if you use this technique. Again, however, use at your own risk. As I've mentioned before Apple's review process can be arbitrary and subjective. Just because my app was approved using this technique does not mean yours will.

But, in the interest of "sharing the knowledge", I figured I should make this technique available to any developer that might want to try it themselves. The function for recursively searching an object for its underlying views was based on a simple example from Erica Sadun. As for finding the magical view name of "MPVideoBackgroundView", this just involved a lot of trial and error on my part examining all the subviews of the movie player.

Please note: This is just a rough example to demonstrate the principle. The code as shown below does not clean up after itself memory-wise.

[EDIT: The following code is a lot different than my original post. It has been rewritten to work with iPhone OS 2.x and 3.0]



- (bool) findVideoBackgroundView: (id) aView level: (int) level
{
NSString *name = [[aView class] description];

if ([name isEqualToString:@"MPVideoBackgroundView"]){
CGRect viewRect = CGRectMake(0, 0, 480, 320);
UIView *temp_view = [[UIImageView alloc] initWithFrame:viewRect];
[temp_view setAlpha:1.0];
[temp_view setBackgroundColor:[UIColor blackColor]];

UIImage *img = [UIImage imageNamed:@"Picture 9.png"];

int x_offset = (480 - img.size.width)/2;
int y_offset = (320 - img.size.height)/2;
CGRect titleRect = CGRectMake(x_offset, y_offset, img.size.width, img.size.height);
UIImageView *myImage = [[UIImageView alloc] initWithFrame:titleRect];
[myImage setImage:img];
[temp_view addSubview:myImage];
[aView insertSubview:temp_view atIndex:0];

return true;
}

for (UIView *subview in [aView subviews]){
if ([self findVideoBackgroundView:subview level:(level + 1)]){
return TRUE;
}
}
return FALSE;
}


- (void) monitorMovieWindow
{
// With iPhone OS 3.0, the movie view does not appear immmediately after calling play.
// It could take several seconds before the view appears.
// As a result, we run this thread in the background to look for when the movie player appears.

// There's probably a better way to do this.

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

bool movieViewNotFound = TRUE;
int maxRetry = 40; // try waiting for the movie view to appear for 4 seconds

while(movieViewNotFound && maxRetry > 0){

// According to Apple's MoviePlayer sample code, the movie player should be the second view in the window stack
NSArray *windows = [[UIApplication sharedApplication] windows];
// Locate the movie player window
UIWindow *moviePlayerWindow = [windows objectAtIndex:1];

if ([self findVideoBackgroundView: moviePlayerWindow level:0]){
movieViewNotFound = FALSE;
}else{
[NSThread sleepForTimeInterval:0.1];
-- maxRetry;
}
}
[pool release];
}


...

// Here's how you would make use of the above methods

// create a new player and initialize it with the movie URL path
MPMoviePlayerController* theMovie=[[MPMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:@"http://blahblah-insert your URL here"]];
[theMovie play];
// Call the method to overlay the QTlogo view with a custom image
[self performSelectorInBackground:@selector(monitorMovieWindow) withObject:nil];



All content copyright © 2009  Brian Stormont, unless otherwise noted.   All rights reserved.