]> git.sesse.net Git - vlc/blob - projects/macosx/framework/Sources/VLCMediaThumbnailer.m
Merge branch 'master' into lpcm_encoder
[vlc] / projects / macosx / framework / Sources / VLCMediaThumbnailer.m
1 //
2 //  VLCMediaThumbnailer.m
3 //  VLCKit
4 //
5 //  Created by Pierre d'Herbemont on 7/10/10.
6 //  Copyright 2010 __MyCompanyName__. All rights reserved.
7 //
8
9 #import <vlc/vlc.h>
10
11 #import "VLCMediaThumbnailer.h"
12 #import "VLCLibVLCBridging.h"
13
14
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;
22 @end
23
24 static void *lock(void *opaque, void **pixels)
25 {
26     VLCMediaThumbnailer *thumbnailer = opaque;
27
28     *pixels = [thumbnailer dataPointer];
29     assert(*pixels);
30     return NULL;
31 }
32
33 static const size_t kDefaultImageWidth = 320;
34 static const size_t kDefaultImageHeight = 240;
35 static const float kSnapshotPosition = 0.5;
36
37 void unlock(void *opaque, void *picture, void *const *p_pixels)
38 {
39     VLCMediaThumbnailer *thumbnailer = opaque;
40     assert(!picture);
41
42     assert([thumbnailer dataPointer] == *p_pixels);
43
44     // We may already have a thumbnail if we are receiving picture after the first one.
45     // Just ignore.
46     if ([thumbnailer thumbnail] || [thumbnailer shouldRejectFrames])
47         return;
48
49     [thumbnailer performSelectorOnMainThread:@selector(didFetchThumbnail) withObject:nil waitUntilDone:YES];
50 }
51
52 void display(void *opaque, void *picture)
53 {
54 }
55
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;
64
65 + (VLCMediaThumbnailer *)thumbnailerWithMedia:(VLCMedia *)media andDelegate:(id<VLCMediaThumbnailerDelegate>)delegate
66 {
67     id obj = [[[self class] alloc] init];
68     [obj setMedia:media];
69     [obj setDelegate:delegate];
70     return [obj autorelease];
71 }
72
73 - (void)dealloc
74 {
75     NSAssert(!_thumbnailingTimeoutTimer, @"Timer not released");
76     NSAssert(!_parsingTimeoutTimer, @"Timer not released");
77     NSAssert(!_data, @"Data not released");
78     NSAssert(!_mp, @"Not properly retained");
79     if (_thumbnail)
80         CGImageRelease(_thumbnail);
81     [_media release];
82     [super dealloc];
83 }
84
85
86 - (void)fetchThumbnail
87 {
88     NSAssert(!_data, @"We are already fetching a thumbnail");
89
90     [self retain]; // Balanced in -notifyDelegate
91
92     if (![_media isParsed]) {
93         [_media addObserver:self forKeyPath:@"parsed" options:0 context:NULL];
94         [_media parse];
95         NSAssert(!_parsingTimeoutTimer, @"We already have a timer around");
96         _parsingTimeoutTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(mediaParsingTimedOut) userInfo:nil repeats:NO] retain];
97         return;
98     }
99
100     [self startFetchingThumbnail];
101 }
102
103
104 - (void)startFetchingThumbnail
105 {
106     NSArray *tracks = [_media tracksInformation];
107
108
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]) {
114             videoTrack = track;
115             break;
116         }
117     }
118
119     unsigned imageWidth = _thumbnailWidth > 0 ? _thumbnailWidth : kDefaultImageWidth;
120     unsigned imageHeight = _thumbnailHeight > 0 ? _thumbnailHeight : kDefaultImageHeight;
121
122     if (!videoTrack)
123         NSLog(@"WARNING: Can't find video track info, still attempting to thumbnail in doubt");
124     else {
125         int videoHeight = [[videoTrack objectForKey:VLCMediaTracksInformationVideoHeight] intValue];
126         int videoWidth = [[videoTrack objectForKey:VLCMediaTracksInformationVideoWidth] intValue];
127
128         // Constraining to the aspect ratio of the video.
129         double ratio;
130         if ((double)imageWidth / imageHeight < (double)videoWidth / videoHeight)
131             ratio = (double)imageHeight / videoHeight;
132         else
133             ratio = (double)imageWidth / videoWidth;
134
135         int newWidth = round(videoWidth * ratio);
136         int newHeight = round(videoHeight * ratio);
137
138         imageWidth = newWidth > 0 ? newWidth : imageWidth;
139         imageHeight = newHeight > 0 ? newHeight : imageHeight;
140     }
141
142     _numberOfReceivedFrames = 0;
143     NSAssert(!_shouldRejectFrames, @"Are we still running?");
144
145     _effectiveThumbnailHeight = imageHeight;
146     _effectiveThumbnailWidth = imageWidth;
147
148     _data = calloc(1, imageWidth * imageHeight * 4);
149     NSAssert(_data, @"Can't create data");
150
151     NSAssert(!_mp, @"We are already fetching a thumbnail");
152     _mp = libvlc_media_player_new([VLCLibrary sharedInstance]);
153
154     libvlc_media_add_option([_media libVLCMediaDescriptor], "no-audio");
155
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);
161
162     NSAssert(!_thumbnailingTimeoutTimer, @"We already have a timer around");
163     _thumbnailingTimeoutTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(mediaThumbnailingTimedOut) userInfo:nil repeats:NO] retain];
164 }
165
166 - (void)mediaParsingTimedOut
167 {
168     NSLog(@"WARNING: media thumbnailer media parsing timed out");
169     [_media removeObserver:self forKeyPath:@"parsed"];
170
171     [self startFetchingThumbnail];
172 }
173
174 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
175 {
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];
183         }
184         return;
185     }
186     return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
187 }
188
189 - (void)didFetchThumbnail
190 {
191     if (_shouldRejectFrames)
192         return;
193
194     // The video thread is blocking on us. Beware not to do too much work.
195
196     _numberOfReceivedFrames++;
197
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)
203     {
204         return;
205     }
206
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,
213                                  width,
214                                  height,
215                                  8,
216                                  pitch,
217                                  colorSpace,
218                                  kCGImageAlphaNoneSkipLast);
219
220     CGColorSpaceRelease(colorSpace);
221     NSAssert(bitmap, @"Can't create bitmap");
222
223     // Create the thumbnail image
224     //NSAssert(!_thumbnail, @"We already have a thumbnail");
225     if (_thumbnail)
226         CGImageRelease(_thumbnail);
227     _thumbnail = CGBitmapContextCreateImage(bitmap);
228
229     // Put a new context there.
230     CGContextRelease(bitmap);
231
232     // Make sure we don't block the video thread now
233     [self performSelector:@selector(notifyDelegate) withObject:nil afterDelay:0];
234 }
235
236 - (void)stopAsync
237 {
238     libvlc_media_player_stop(_mp);
239     libvlc_media_player_release(_mp);
240     _mp = NULL;
241
242     // Now release data
243     free(_data);
244     _data = NULL;
245
246     _shouldRejectFrames = NO;
247 }
248
249 - (void)endThumbnailing
250 {
251     _shouldRejectFrames = YES;
252
253     [_thumbnailingTimeoutTimer invalidate];
254     [_thumbnailingTimeoutTimer release];
255     _thumbnailingTimeoutTimer = nil;
256
257     // Stop the media player
258     NSAssert(_mp, @"We have already destroyed mp");
259
260     [self performSelectorInBackground:@selector(stopAsync) withObject:nil];
261
262     [self autorelease]; // Balancing -fetchThumbnail
263 }
264
265 - (void)notifyDelegate
266 {
267     [self endThumbnailing];
268
269     // Call delegate
270     [_delegate mediaThumbnailer:self didFinishThumbnail:_thumbnail];
271
272 }
273
274 - (void)mediaThumbnailingTimedOut
275 {
276     [self endThumbnailing];
277
278     // Call delegate
279     [_delegate mediaThumbnailerDidTimeOut:self];
280 }
281 @end