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;
21 @property (readonly, assign) BOOL shouldRejectFrames;
24 static void *lock(void *opaque, void **pixels)
26 VLCMediaThumbnailer *thumbnailer = opaque;
28 *pixels = [thumbnailer dataPointer];
33 static const size_t kDefaultImageWidth = 320;
34 static const size_t kDefaultImageHeight = 240;
35 static const float kSnapshotPosition = 0.5;
37 void unlock(void *opaque, void *picture, void *const *p_pixels)
39 VLCMediaThumbnailer *thumbnailer = opaque;
42 assert([thumbnailer dataPointer] == *p_pixels);
44 // We may already have a thumbnail if we are receiving picture after the first one.
46 if ([thumbnailer thumbnail] || [thumbnailer shouldRejectFrames])
49 [thumbnailer performSelectorOnMainThread:@selector(didFetchThumbnail) withObject:nil waitUntilDone:YES];
52 void display(void *opaque, void *picture)
56 @implementation VLCMediaThumbnailer
57 @synthesize media=_media;
58 @synthesize delegate=_delegate;
59 @synthesize thumbnail=_thumbnail;
60 @synthesize dataPointer=_data;
61 @synthesize thumbnailWidth=_thumbnailWidth;
62 @synthesize thumbnailHeight=_thumbnailHeight;
63 @synthesize shouldRejectFrames=_shouldRejectFrames;
65 + (VLCMediaThumbnailer *)thumbnailerWithMedia:(VLCMedia *)media andDelegate:(id<VLCMediaThumbnailerDelegate>)delegate
67 id obj = [[[self class] alloc] init];
69 [obj setDelegate:delegate];
70 return [obj autorelease];
75 NSAssert(!_thumbnailingTimeoutTimer, @"Timer not released");
76 NSAssert(!_parsingTimeoutTimer, @"Timer not released");
77 NSAssert(!_data, @"Data not released");
78 NSAssert(!_mp, @"Not properly retained");
80 CGImageRelease(_thumbnail);
86 - (void)fetchThumbnail
88 NSAssert(!_data, @"We are already fetching a thumbnail");
90 [self retain]; // Balanced in -notifyDelegate
92 if (![_media isParsed]) {
93 [_media addObserver:self forKeyPath:@"parsed" options:0 context:NULL];
95 NSAssert(!_parsingTimeoutTimer, @"We already have a timer around");
96 _parsingTimeoutTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(mediaParsingTimedOut) userInfo:nil repeats:NO] retain];
100 [self startFetchingThumbnail];
104 - (void)startFetchingThumbnail
106 NSArray *tracks = [_media tracksInformation];
109 // Find the video track
110 NSDictionary *videoTrack = nil;
111 for (NSDictionary *track in tracks) {
112 NSString *type = [track objectForKey:VLCMediaTracksInformationType];
113 if ([type isEqualToString:VLCMediaTracksInformationTypeVideo]) {
119 unsigned imageWidth = _thumbnailWidth > 0 ? _thumbnailWidth : kDefaultImageWidth;
120 unsigned imageHeight = _thumbnailHeight > 0 ? _thumbnailHeight : kDefaultImageHeight;
123 NSLog(@"WARNING: Can't find video track info, still attempting to thumbnail in doubt");
125 int videoHeight = [[videoTrack objectForKey:VLCMediaTracksInformationVideoHeight] intValue];
126 int videoWidth = [[videoTrack objectForKey:VLCMediaTracksInformationVideoWidth] intValue];
128 // Constraining to the aspect ratio of the video.
130 if ((double)imageWidth / imageHeight < (double)videoWidth / videoHeight)
131 ratio = (double)imageHeight / videoHeight;
133 ratio = (double)imageWidth / videoWidth;
135 int newWidth = round(videoWidth * ratio);
136 int newHeight = round(videoHeight * ratio);
138 imageWidth = newWidth > 0 ? newWidth : imageWidth;
139 imageHeight = newHeight > 0 ? newHeight : imageHeight;
142 _numberOfReceivedFrames = 0;
143 NSAssert(!_shouldRejectFrames, @"Are we still running?");
145 _effectiveThumbnailHeight = imageHeight;
146 _effectiveThumbnailWidth = imageWidth;
148 _data = calloc(1, imageWidth * imageHeight * 4);
149 NSAssert(_data, @"Can't create data");
151 NSAssert(!_mp, @"We are already fetching a thumbnail");
152 _mp = libvlc_media_player_new([VLCLibrary sharedInstance]);
154 libvlc_media_add_option([_media libVLCMediaDescriptor], "no-audio");
156 libvlc_media_player_set_media(_mp, [_media libVLCMediaDescriptor]);
157 libvlc_video_set_format(_mp, "RGBA", imageWidth, imageHeight, 4 * imageWidth);
158 libvlc_video_set_callbacks(_mp, lock, unlock, display, self);
159 libvlc_media_player_play(_mp);
160 libvlc_media_player_set_position(_mp, kSnapshotPosition);
162 NSAssert(!_thumbnailingTimeoutTimer, @"We already have a timer around");
163 _thumbnailingTimeoutTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(mediaThumbnailingTimedOut) userInfo:nil repeats:NO] retain];
166 - (void)mediaParsingTimedOut
168 NSLog(@"WARNING: media thumbnailer media parsing timed out");
169 [_media removeObserver:self forKeyPath:@"parsed"];
171 [self startFetchingThumbnail];
174 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
176 if (object == _media && [keyPath isEqualToString:@"parsed"]) {
177 if ([_media isParsed]) {
178 [_parsingTimeoutTimer invalidate];
179 [_parsingTimeoutTimer release];
180 _parsingTimeoutTimer = nil;
181 [_media removeObserver:self forKeyPath:@"parsed"];
182 [self startFetchingThumbnail];
186 return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
189 - (void)didFetchThumbnail
191 if (_shouldRejectFrames)
194 // The video thread is blocking on us. Beware not to do too much work.
196 _numberOfReceivedFrames++;
198 // Make sure we are getting the right frame
199 if (libvlc_media_player_get_position(_mp) < kSnapshotPosition / 2 &&
200 // Arbitrary choice to work around broken files.
201 libvlc_media_player_get_length(_mp) > 1000 &&
202 _numberOfReceivedFrames < 10)
207 NSAssert(_data, @"We have no data");
208 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
209 const CGFloat width = _effectiveThumbnailWidth;
210 const CGFloat height = _effectiveThumbnailHeight;
211 const CGFloat pitch = 4 * width;
212 CGContextRef bitmap = CGBitmapContextCreate(_data,
218 kCGImageAlphaNoneSkipLast);
220 CGColorSpaceRelease(colorSpace);
221 NSAssert(bitmap, @"Can't create bitmap");
223 // Create the thumbnail image
224 //NSAssert(!_thumbnail, @"We already have a thumbnail");
226 CGImageRelease(_thumbnail);
227 _thumbnail = CGBitmapContextCreateImage(bitmap);
229 // Put a new context there.
230 CGContextRelease(bitmap);
232 // Make sure we don't block the video thread now
233 [self performSelector:@selector(notifyDelegate) withObject:nil afterDelay:0];
238 libvlc_media_player_stop(_mp);
239 libvlc_media_player_release(_mp);
246 _shouldRejectFrames = NO;
249 - (void)endThumbnailing
251 _shouldRejectFrames = YES;
253 [_thumbnailingTimeoutTimer invalidate];
254 [_thumbnailingTimeoutTimer release];
255 _thumbnailingTimeoutTimer = nil;
257 // Stop the media player
258 NSAssert(_mp, @"We have already destroyed mp");
260 [self performSelectorInBackground:@selector(stopAsync) withObject:nil];
262 [self autorelease]; // Balancing -fetchThumbnail
265 - (void)notifyDelegate
267 [self endThumbnailing];
270 [_delegate mediaThumbnailer:self didFinishThumbnail:_thumbnail];
274 - (void)mediaThumbnailingTimedOut
276 [self endThumbnailing];
279 [_delegate mediaThumbnailerDidTimeOut:self];