So, instead of doing the sensible thing and getting some sleep I decided to try to figure out how to play videos from YouTube. This was a bit tricker then the other pieces as the example application doesn’t actually play videos that I saw. A lot of digging, and hair pulling, later I got something working. Kinda. Mostly. It seems some of the video feeds don’t play at the moment and some play, well, the wrong video, heh. But, I figured it works so I’ll put up how I got it to work.

First, since I need access to the video object I changed the MyTubeIKBrowserItem to hold a GDataEntryYouTubeVideo and modified the constructor to be: - (id)init:theImageID image:(NSImage *)theImage video:(GDataEntryYouTubeVideo *)theVideo;. I then added a simple method to return the video and named it, oddly enough, video.

Once I had that updated I fixed up the code in MyTubeAppController to pass in the YouTube video object when needed.

I figured, since most of the videos appear to be shockwave flash, the easiest way to play them was to embed WebKit. To that end, in the MyTubeAppController.h file I imported import <WebKit/WebView.h> and added anotherIBOutlet WebView *web; member. Don’t forget to add the WebKit framework to your Linked Frameworks.

With that done double click on MainMenu.nib to launch Interface Builder. In the Library go to Web Kit and drag the Web View to your application window. Make sure you set everything to resize correctly. I gave the WebView and the Image Browser about half the window each to start and made the WebView expand both horizontally and vertically while the Image Browser only expands horizontally. In the control window ctrl-click on the My Tube object and drag from the web circle down to the new WebView widget. That’s it for the Interface Builder portion of our show, close it down and go back to XCode.

After fixing the code for the MyTubeIKBrowserItem changes I only had to add one function:

-(void)imageBrowser:(IKImageBrowserView *)imageBrowser cellWasDoubleClickedAtIndex:(NSUInteger)index {
    MyTubeIKBrowserItem *item = [imageList objectAtIndex:index];
    GDataEntryYouTubeVideo *video = [item video];

    NSString *url = [[[[video mediaGroup] mediaContents] objectAtIndex:0] URLString];
    [web setMainFrameURL:url];
    [web reload:nil];
}

This is another method for the Image Browser. In this case when an item is double clicked this function will be run. All I’m doing is grabbing the item from our imageList and pulling the GDataEntryYouTubeVideo object out of it. Then using this video object I get the mediaGroup then the mediaContents. The mediaContents is an array so I grab the first element. (I have no idea if this is 100% correct but it seems to work). Then, using this item I call the URLString method to get the URL.

I pass the URL to our freshly created web view and tell it to reload.

Volia. You should, hopefully, see the video playing in your webview. If the video doesn’t play, after a few seconds to let it load, try another one. For some reason some of the videos don’t play for me. (Some also cause QuickTime to launch so it’s probably based on video type?)

There you have it. A simple YouTube application to pull down and play videos. It probably leaks like a sieve and I don’t know if it’s 100% correct, but it does work.

This concludes our regularly scheduled MyTube broadcasts.

Full listing of MyTubeAppController follows:

#import "MyTubeAppController.h"
#import "MyTubeIKBrowserItem.h"
#import "GData/GData.h"

@interface MyTubeAppController (Private)
- (GDataServiceGoogleYouTube *)youTubeService;
- (void)fetchEntryImageURLString:(NSString *)urlString withVideo:(GDataEntryYouTubeVideo *)video;
@end

@implementation MyTubeAppController
- (void)awakeFromNib
{
    NSArray *feedTypes = [NSArray arrayWithObjects:
            @"Most Discussed",
            @"Most Linked",
            @"Most Responded",
            @"Most Viewed",
            @"Top Favorites",
            @"Recently Featured",
            nil];

    /* set the items into the field types combo box */
    [feed_type removeAllItems];
    [feed_type addItemsWithTitles:feedTypes];
    [feed_type selectItemWithTitle:@"Most Viewed"];

    imageList = [[NSMutableArray alloc] initWithCapacity:20];
    [browser setDelegate:self];
    [browser setDataSource:self];
}

- (IBAction)grab:(id)sender
{
    GDataServiceGoogleYouTube *service = [self youTubeService];
    GDataServiceTicket *ticket;

    NSString *feedName = [[feed_type selectedItem] title];
    feedName = [feedName lowercaseString];
    feedName = [feedName stringByReplacingOccurrencesOfString:@" " withString:@"_"];

    NSURL *feedURL = [GDataServiceGoogleYouTube youTubeURLForFeedID:feedName];

    ticket = [service fetchYouTubeFeedWithURL:feedURL
                                     delegate:self
                            didFinishSelector:@selector(entryListFetchTicket:finishedWithFeed:)
                              didFailSelector:@selector(entryListFetchTicket:failedWithError:)];

    [imageList removeAllObjects];
}

/* these two functions are for handling the response from fetching a feed */
- (void)entryListFetchTicket:(GDataServiceTicket *)ticket finishedWithFeed:(GDataFeedBase *)feed {
    GDataFeedYouTubeVideo *vfeed = (GDataFeedYouTubeVideo *)feed;
    int i;

    for (i = 0; i < [[vfeed entries] count]; i++) {
        GDataEntryBase *entry = [[vfeed entries] objectAtIndex:i];
        if (![entry respondsToSelector:@selector(mediaGroup)]) continue;

        GDataEntryYouTubeVideo *video = (GDataEntryYouTubeVideo *)entry;
        NSArray *thumbnails = [[video mediaGroup] mediaThumbnails];
        if ([thumbnails count] == 0) continue;

        NSString *imageURLString = [[thumbnails objectAtIndex:0] URLString];
        [self fetchEntryImageURLString:imageURLString withVideo:video];
    }
}

- (void)entryListFetchTicket:(GDataServiceTicket *)ticket failedWithError:(NSError *)error {
    NSLog(@"Error %@", error);
}

/* These three functions handle the responses for fetching an image */
- (void)imageFetcher:(GDataHTTPFetcher *)fetcher finishedWithData:(NSData *)data {
    NSImage *image = [[[NSImage alloc] initWithData:data] autorelease];
    GDataEntryYouTubeVideo *video = [fetcher userData];

    [imageList addObject:[[MyTubeIKBrowserItem alloc] init:[[video title] stringValue] image:image video:video]];
    [browser reloadData];
}

- (void)imageFetcher:(GDataHTTPFetcher *)fetcher failedWithStatus:(int)status data:(NSData *)data {
    NSString *dataStr = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
    NSLog(@"image fetch error %d with data %@", status, dataStr);
}

- (void)imageFetcher:(GDataHTTPFetcher *)fetcher failedWithError:(NSError *)error {
    NSLog(@"Image fetch error %@", error);
}

/* these two functions are for the image browser delegate */
- (NSUInteger)numberOfItemsInImageBrowser:(IKImageBrowserView *)imageBrowser {
    return [imageList count];
}

- (id)imageBrowser:(IKImageBrowserView *)imageBrowser itemAtIndex:(NSUInteger)index {
    return [imageList objectAtIndex:index];
}

-(void)imageBrowser:(IKImageBrowserView *)imageBrowser cellWasDoubleClickedAtIndex:(NSUInteger)index {
    MyTubeIKBrowserItem *item = [imageList objectAtIndex:index];
    GDataEntryYouTubeVideo *video = [item video];

    NSString *url = [[[[video mediaGroup] mediaContents] objectAtIndex:0] URLString];
    [web setMainFrameURL:url];
    [web reload:nil];
}
@end

@implementation MyTubeAppController (Private)
- (GDataServiceGoogleYouTube *)youTubeService {
    static GDataServiceGoogleYouTube *service = nil;

    if (!service) {
        service = [[GDataServiceGoogleYouTube alloc] init];
        [service setUserAgent:@"MyTube-1.0"];
        [service setShouldCacheDatedData:YES];

        /* this is where we'd set the username/password if we accepted one */
        [service setUserCredentialsWithUsername:nil password:nil];
    }

    return service;
}

/* fetch a specific image URL from YouTube */
- (void)fetchEntryImageURLString:(NSString *)urlString withVideo:(GDataEntryYouTubeVideo *)video {
    if (!urlString) return;

    NSURL *imageURL = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
    GDataHTTPFetcher *fetcher = [GDataHTTPFetcher httpFetcherWithRequest:request];
    [fetcher setUserData:video];

    [fetcher beginFetchWithDelegate:self
                  didFinishSelector:@selector(imageFetcher:finishedWithData:)
          didFailWithStatusSelector:@selector(imageFetcher:failedWithStatus:data:)
           didFailWithErrorSelector:@selector(imageFetcher:failedWithError:)];
}
@end