2 // VLCMediaThumbnailer.m
5 // Created by Pierre d'Herbemont on 7/10/10.
6 // Copyright 2010 __MyCompanyName__. All rights reserved.
11 #import "VLCMediaThumbnailer.h"
12 #import "VLCLibVLCBridging.h"
15 @interface VLCMediaThumbnailer ()
16 - (void)didFetchThumbnail;
17 - (void)notifyDelegate;
18 - (void)fetchThumbnail;
19 - (void)startFetchingThumbnail;
20 @property (readonly, assign) void *dataPointer;
23 static void *lock(void *opaque, void **pixels)
25 VLCMediaThumbnailer *thumbnailer = opaque;
27 *pixels = [thumbnailer dataPointer];
32 static const size_t kDefaultImageWidth = 320;
33 static const size_t kDefaultImageHeight = 240;
34 static const float kSnapshotPosition = 0.5;
36 void unlock(void *opaque, void *picture, void *const *p_pixels)
38 VLCMediaThumbnailer *thumbnailer = opaque;
41 assert([thumbnailer dataPointer] == *p_pixels);
43 // We may already have a thumbnail if we are receiving picture after the first one.
45 if ([thumbnailer thumbnail])
48 [thumbnailer performSelectorOnMainThread:@selector(didFetchThumbnail) withObject:nil waitUntilDone:YES];
51 void display(void *opaque, void *picture)
55 @implementation VLCMediaThumbnailer
56 @synthesize media=_media;
57 @synthesize delegate=_delegate;
58 @synthesize thumbnail=_thumbnail;
59 @synthesize dataPointer=_data;
60 @synthesize thumbnailWidth=_thumbnailWidth;
61 @synthesize thumbnailHeight=_thumbnailHeight;
63 + (VLCMediaThumbnailer *)thumbnailerWithMedia:(VLCMedia *)media andDelegate:(id<VLCMediaThumbnailerDelegate>)delegate
65 id obj = [[[self class] alloc] init];
67 [obj setDelegate:delegate];
68 return [obj autorelease];
73 NSAssert(!_data, @"Data not released");
74 NSAssert(!_mp, @"Not properly retained");
76 CGImageRelease(_thumbnail);
82 - (void)fetchThumbnail
84 NSAssert(!_data, @"We are already fetching a thumbnail");
86 [self retain]; // Balanced in -notifyDelegate
88 if (![_media isParsed]) {
89 [_media addObserver:self forKeyPath:@"parsed" options:0 context:NULL];
91 NSAssert(!_parsingTimeoutTimer, @"We already have a timer around");
92 _parsingTimeoutTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(mediaParsingTimedOut) userInfo:nil repeats:NO] retain];
96 [self startFetchingThumbnail];
100 - (void)startFetchingThumbnail
102 NSArray *tracks = [_media tracksInformation];
104 // Find the video track
105 NSDictionary *videoTrack = nil;
106 for (NSDictionary *track in tracks) {
107 NSString *type = [track objectForKey:VLCMediaTracksInformationType];
108 if ([type isEqualToString:VLCMediaTracksInformationTypeVideo]) {
114 unsigned imageWidth = _thumbnailWidth > 0 ? _thumbnailWidth : kDefaultImageWidth;
115 unsigned imageHeight = _thumbnailHeight > 0 ? _thumbnailHeight : kDefaultImageHeight;
118 NSLog(@"WARNING: Can't find video track info, still attempting to thumbnail in doubt");
120 int videoHeight = [[videoTrack objectForKey:VLCMediaTracksInformationVideoHeight] intValue];
121 int videoWidth = [[videoTrack objectForKey:VLCMediaTracksInformationVideoWidth] intValue];
123 // Constraining to the aspect ratio of the video.
125 if ((double)imageWidth / imageHeight < (double)videoWidth / videoHeight)
126 ratio = (double)imageHeight / videoHeight;
128 ratio = (double)imageWidth / videoWidth;
130 int newWidth = round(videoWidth * ratio);
131 int newHeight = round(videoHeight * ratio);
132 NSLog(@"video %dx%d from %dx%d or %dx%d", newWidth, newHeight, videoWidth, videoHeight, imageWidth, imageHeight);
133 imageWidth = newWidth > 0 ? newWidth : imageWidth;
134 imageHeight = newHeight > 0 ? newHeight : imageHeight;
137 _effectiveThumbnailHeight = imageHeight;
138 _effectiveThumbnailWidth = imageWidth;
140 _data = calloc(1, imageWidth * imageHeight * 4);
141 NSAssert(_data, @"Can't create data");
143 NSAssert(!_mp, @"We are already fetching a thumbnail");
144 _mp = libvlc_media_player_new([VLCLibrary sharedInstance]);
146 libvlc_media_add_option([_media libVLCMediaDescriptor], "no-audio");
148 libvlc_media_player_set_media(_mp, [_media libVLCMediaDescriptor]);
149 libvlc_video_set_format(_mp, "RGBA", imageWidth, imageHeight, 4 * imageWidth);
150 libvlc_video_set_callbacks(_mp, lock, unlock, display, self);
151 libvlc_media_player_play(_mp);
152 libvlc_media_player_set_position(_mp, kSnapshotPosition);
155 - (void)mediaParsingTimedOut
157 NSLog(@"WARNING: media thumbnailer media parsing timed out");
158 [_media removeObserver:self forKeyPath:@"parsed"];
160 [self startFetchingThumbnail];
163 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
165 if (object == _media && [keyPath isEqualToString:@"parsed"]) {
166 if ([_media isParsed]) {
167 [_parsingTimeoutTimer invalidate];
168 [_parsingTimeoutTimer release];
169 _parsingTimeoutTimer = nil;
170 [_media removeObserver:self forKeyPath:@"parsed"];
171 [self startFetchingThumbnail];
175 return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
178 - (void)didFetchThumbnail
180 // The video thread is blocking on us. Beware not to do too much work.
182 // Make sure we are getting the right frame
183 if (libvlc_media_player_get_position(_mp) < kSnapshotPosition &&
184 // Arbitrary choice to work around broken files.
185 libvlc_media_player_get_length(_mp) > 1000)
188 NSAssert(_data, @"We have no data");
189 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
190 const CGFloat width = _effectiveThumbnailWidth;
191 const CGFloat height = _effectiveThumbnailHeight;
192 const CGFloat pitch = 4 * width;
193 CGContextRef bitmap = CGBitmapContextCreate(_data,
199 kCGImageAlphaNoneSkipLast);
201 CGColorSpaceRelease(colorSpace);
202 NSAssert(bitmap, @"Can't create bitmap");
204 // Create the thumbnail image
205 //NSAssert(!_thumbnail, @"We already have a thumbnail");
207 CGImageRelease(_thumbnail);
208 _thumbnail = CGBitmapContextCreateImage(bitmap);
210 // Put a new context there.
211 CGContextRelease(bitmap);
213 // Make sure we don't block the video thread now
214 [self performSelector:@selector(notifyDelegate) withObject:nil afterDelay:0];
217 - (void)notifyDelegate
219 // Stop the media player
220 NSAssert(_mp, @"We have already destroyed mp");
221 libvlc_media_player_stop(_mp);
222 libvlc_media_player_release(_mp);
230 [_delegate mediaThumbnailer:self didFinishThumbnail:_thumbnail];
232 [self release]; // Balancing -fetchThumbnail