--- /dev/null
+//
+// VLCMediaThumbnailer.m
+// VLCKit
+//
+// Created by Pierre d'Herbemont on 7/10/10.
+// Copyright 2010 __MyCompanyName__. All rights reserved.
+//
+
+#import <vlc/vlc.h>
+
+#import "VLCMediaThumbnailer.h"
+#import "VLCLibVLCBridging.h"
+
+
+@interface VLCMediaThumbnailer ()
+- (void)didFetchThumbnail;
+- (void)notifyDelegate;
+- (void)fetchThumbnail;
+- (void)startFetchingThumbnail;
+@property (readonly, assign) void *dataPointer;
+@end
+
+static void *lock(void *opaque, void **pixels)
+{
+ VLCMediaThumbnailer *thumbnailer = opaque;
+
+ *pixels = [thumbnailer dataPointer];
+ assert(*pixels);
+ return NULL;
+}
+
+static const size_t kDefaultImageWidth = 320;
+static const size_t kDefaultImageHeight = 240;
+static const float kSnapshotPosition = 0.5;
+
+void unlock(void *opaque, void *picture, void *const *p_pixels)
+{
+ VLCMediaThumbnailer *thumbnailer = opaque;
+ assert(!picture);
+
+ assert([thumbnailer dataPointer] == *p_pixels);
+
+ // We may already have a thumbnail if we are receiving picture after the first one.
+ // Just ignore.
+ if ([thumbnailer thumbnail])
+ return;
+
+ [thumbnailer performSelectorOnMainThread:@selector(didFetchThumbnail) withObject:nil waitUntilDone:YES];
+}
+
+void display(void *opaque, void *picture)
+{
+}
+
+@implementation VLCMediaThumbnailer
+@synthesize media=_media;
+@synthesize delegate=_delegate;
+@synthesize thumbnail=_thumbnail;
+@synthesize dataPointer=_data;
+@synthesize thumbnailWidth=_thumbnailWidth;
+@synthesize thumbnailHeight=_thumbnailHeight;
+
++ (VLCMediaThumbnailer *)thumbnailerWithMedia:(VLCMedia *)media andDelegate:(id<VLCMediaThumbnailerDelegate>)delegate
+{
+ id obj = [[[self class] alloc] init];
+ [obj setMedia:media];
+ [obj setDelegate:delegate];
+ return [obj autorelease];
+}
+
+- (void)dealloc
+{
+ NSAssert(!_data, @"Data not released");
+ NSAssert(!_mp, @"Not properly retained");
+ if (_thumbnail)
+ CGImageRelease(_thumbnail);
+ [_media release];
+ [super dealloc];
+}
+
+
+- (void)fetchThumbnail
+{
+ NSAssert(!_data, @"We are already fetching a thumbnail");
+
+ [self retain]; // Balanced in -notifyDelegate
+
+ if (![_media isParsed]) {
+ [_media addObserver:self forKeyPath:@"parsed" options:0 context:NULL];
+ [_media parse];
+ NSAssert(!_parsingTimeoutTimer, @"We already have a timer around");
+ _parsingTimeoutTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(mediaParsingTimedOut) userInfo:nil repeats:NO] retain];
+ return;
+ }
+
+ [self startFetchingThumbnail];
+}
+
+
+- (void)startFetchingThumbnail
+{
+ NSArray *tracks = [_media tracksInformation];
+
+ // Find the video track
+ NSDictionary *videoTrack = nil;
+ for (NSDictionary *track in tracks) {
+ NSString *type = [track objectForKey:VLCMediaTracksInformationType];
+ if ([type isEqualToString:VLCMediaTracksInformationTypeVideo]) {
+ videoTrack = track;
+ break;
+ }
+ }
+
+ unsigned imageWidth = _thumbnailWidth > 0 ? _thumbnailWidth : kDefaultImageWidth;
+ unsigned imageHeight = _thumbnailHeight > 0 ? _thumbnailHeight : kDefaultImageHeight;
+
+ if (!videoTrack)
+ NSLog(@"WARNING: Can't find video track info, still attempting to thumbnail in doubt");
+ else {
+ int videoHeight = [[videoTrack objectForKey:VLCMediaTracksInformationVideoHeight] intValue];
+ int videoWidth = [[videoTrack objectForKey:VLCMediaTracksInformationVideoWidth] intValue];
+
+ // Constraining to the aspect ratio of the video.
+ double ratio;
+ if ((double)imageWidth / imageHeight < (double)videoWidth / videoHeight)
+ ratio = (double)imageHeight / videoHeight;
+ else
+ ratio = (double)imageWidth / videoWidth;
+
+ int newWidth = round(videoWidth * ratio);
+ int newHeight = round(videoHeight * ratio);
+ NSLog(@"video %dx%d from %dx%d or %dx%d", newWidth, newHeight, videoWidth, videoHeight, imageWidth, imageHeight);
+ imageWidth = newWidth > 0 ? newWidth : imageWidth;
+ imageHeight = newHeight > 0 ? newHeight : imageHeight;
+ }
+
+ _effectiveThumbnailHeight = imageHeight;
+ _effectiveThumbnailWidth = imageWidth;
+
+ _data = calloc(1, imageWidth * imageHeight * 4);
+ NSAssert(_data, @"Can't create data");
+
+ NSAssert(!_mp, @"We are already fetching a thumbnail");
+ _mp = libvlc_media_player_new([VLCLibrary sharedInstance]);
+
+ libvlc_media_add_option([_media libVLCMediaDescriptor], "no-audio");
+
+ libvlc_media_player_set_media(_mp, [_media libVLCMediaDescriptor]);
+ libvlc_video_set_format(_mp, "RGBA", imageWidth, imageHeight, 4 * imageWidth);
+ libvlc_video_set_callbacks(_mp, lock, unlock, display, self);
+ libvlc_media_player_play(_mp);
+ libvlc_media_player_set_position(_mp, kSnapshotPosition);
+}
+
+- (void)mediaParsingTimedOut
+{
+ NSLog(@"WARNING: media thumbnailer media parsing timed out");
+ [_media removeObserver:self forKeyPath:@"parsed"];
+
+ [self startFetchingThumbnail];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
+{
+ if (object == _media && [keyPath isEqualToString:@"parsed"]) {
+ if ([_media isParsed]) {
+ [_parsingTimeoutTimer invalidate];
+ [_parsingTimeoutTimer release];
+ _parsingTimeoutTimer = nil;
+ [_media removeObserver:self forKeyPath:@"parsed"];
+ [self startFetchingThumbnail];
+ }
+ return;
+ }
+ return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+}
+
+- (void)didFetchThumbnail
+{
+ // The video thread is blocking on us. Beware not to do too much work.
+
+ // Make sure we are getting the right frame
+ if (libvlc_media_player_get_position(_mp) < kSnapshotPosition &&
+ // Arbitrary choice to work around broken files.
+ libvlc_media_player_get_length(_mp) > 1000)
+ return;
+
+ NSAssert(_data, @"We have no data");
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ const CGFloat width = _effectiveThumbnailWidth;
+ const CGFloat height = _effectiveThumbnailHeight;
+ const CGFloat pitch = 4 * width;
+ CGContextRef bitmap = CGBitmapContextCreate(_data,
+ width,
+ height,
+ 8,
+ pitch,
+ colorSpace,
+ kCGImageAlphaNoneSkipLast);
+
+ CGColorSpaceRelease(colorSpace);
+ NSAssert(bitmap, @"Can't create bitmap");
+
+ // Create the thumbnail image
+ //NSAssert(!_thumbnail, @"We already have a thumbnail");
+ if (_thumbnail)
+ CGImageRelease(_thumbnail);
+ _thumbnail = CGBitmapContextCreateImage(bitmap);
+
+ // Put a new context there.
+ CGContextRelease(bitmap);
+
+ // Make sure we don't block the video thread now
+ [self performSelector:@selector(notifyDelegate) withObject:nil afterDelay:0];
+}
+
+- (void)notifyDelegate
+{
+ // Stop the media player
+ NSAssert(_mp, @"We have already destroyed mp");
+ libvlc_media_player_stop(_mp);
+ libvlc_media_player_release(_mp);
+ _mp = NULL;
+
+ // Now release data
+ free(_data);
+ _data = NULL;
+
+ // Call delegate
+ [_delegate mediaThumbnailer:self didFinishThumbnail:_thumbnail];
+
+ [self release]; // Balancing -fetchThumbnail
+}
+
+@end