]> git.sesse.net Git - vlc/blob - projects/macosx/framework/Sources/VLCMediaThumbnailer.m
VLCKit: Import MobileVLCKit.
[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 @end
22
23 static void *lock(void *opaque, void **pixels)
24 {
25     VLCMediaThumbnailer *thumbnailer = opaque;
26
27     *pixels = [thumbnailer dataPointer];
28     assert(*pixels);
29     return NULL;
30 }
31
32 static const size_t kDefaultImageWidth = 320;
33 static const size_t kDefaultImageHeight = 240;
34 static const float kSnapshotPosition = 0.5;
35
36 void unlock(void *opaque, void *picture, void *const *p_pixels)
37 {
38     VLCMediaThumbnailer *thumbnailer = opaque;
39     assert(!picture);
40
41     assert([thumbnailer dataPointer] == *p_pixels);
42
43     // We may already have a thumbnail if we are receiving picture after the first one.
44     // Just ignore.
45     if ([thumbnailer thumbnail])
46         return;
47
48     [thumbnailer performSelectorOnMainThread:@selector(didFetchThumbnail) withObject:nil waitUntilDone:YES];
49 }
50
51 void display(void *opaque, void *picture)
52 {
53 }
54
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;
62
63 + (VLCMediaThumbnailer *)thumbnailerWithMedia:(VLCMedia *)media andDelegate:(id<VLCMediaThumbnailerDelegate>)delegate
64 {
65     id obj = [[[self class] alloc] init];
66     [obj setMedia:media];
67     [obj setDelegate:delegate];
68     return [obj autorelease];
69 }
70
71 - (void)dealloc
72 {
73     NSAssert(!_data, @"Data not released");
74     NSAssert(!_mp, @"Not properly retained");
75     if (_thumbnail)
76         CGImageRelease(_thumbnail);
77     [_media release];
78     [super dealloc];
79 }
80
81
82 - (void)fetchThumbnail
83 {
84     NSAssert(!_data, @"We are already fetching a thumbnail");
85
86     [self retain]; // Balanced in -notifyDelegate
87
88     if (![_media isParsed]) {
89         [_media addObserver:self forKeyPath:@"parsed" options:0 context:NULL];
90         [_media parse];
91         NSAssert(!_parsingTimeoutTimer, @"We already have a timer around");
92         _parsingTimeoutTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(mediaParsingTimedOut) userInfo:nil repeats:NO] retain];
93         return;
94     }
95
96     [self startFetchingThumbnail];
97 }
98
99
100 - (void)startFetchingThumbnail
101 {
102     NSArray *tracks = [_media tracksInformation];
103
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]) {
109             videoTrack = track;
110             break;
111         }
112     }
113
114     unsigned imageWidth = _thumbnailWidth > 0 ? _thumbnailWidth : kDefaultImageWidth;
115     unsigned imageHeight = _thumbnailHeight > 0 ? _thumbnailHeight : kDefaultImageHeight;
116
117     if (!videoTrack)
118         NSLog(@"WARNING: Can't find video track info, still attempting to thumbnail in doubt");
119     else {
120         int videoHeight = [[videoTrack objectForKey:VLCMediaTracksInformationVideoHeight] intValue];
121         int videoWidth = [[videoTrack objectForKey:VLCMediaTracksInformationVideoWidth] intValue];
122
123         // Constraining to the aspect ratio of the video.
124         double ratio;
125         if ((double)imageWidth / imageHeight < (double)videoWidth / videoHeight)
126             ratio = (double)imageHeight / videoHeight;
127         else
128             ratio = (double)imageWidth / videoWidth;
129
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;
135     }
136
137     _effectiveThumbnailHeight = imageHeight;
138     _effectiveThumbnailWidth = imageWidth;
139
140     _data = calloc(1, imageWidth * imageHeight * 4);
141     NSAssert(_data, @"Can't create data");
142
143     NSAssert(!_mp, @"We are already fetching a thumbnail");
144     _mp = libvlc_media_player_new([VLCLibrary sharedInstance]);
145
146     libvlc_media_add_option([_media libVLCMediaDescriptor], "no-audio");
147
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);
153 }
154
155 - (void)mediaParsingTimedOut
156 {
157     NSLog(@"WARNING: media thumbnailer media parsing timed out");
158     [_media removeObserver:self forKeyPath:@"parsed"];
159
160     [self startFetchingThumbnail];
161 }
162
163 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
164 {
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];
172         }
173         return;
174     }
175     return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
176 }
177
178 - (void)didFetchThumbnail
179 {
180     // The video thread is blocking on us. Beware not to do too much work.
181
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)
186         return;
187
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,
194                                  width,
195                                  height,
196                                  8,
197                                  pitch,
198                                  colorSpace,
199                                  kCGImageAlphaNoneSkipLast);
200
201     CGColorSpaceRelease(colorSpace);
202     NSAssert(bitmap, @"Can't create bitmap");
203
204     // Create the thumbnail image
205     //NSAssert(!_thumbnail, @"We already have a thumbnail");
206     if (_thumbnail)
207         CGImageRelease(_thumbnail);
208     _thumbnail = CGBitmapContextCreateImage(bitmap);
209
210     // Put a new context there.
211     CGContextRelease(bitmap);
212
213     // Make sure we don't block the video thread now
214     [self performSelector:@selector(notifyDelegate) withObject:nil afterDelay:0];
215 }
216
217 - (void)notifyDelegate
218 {
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);
223     _mp = NULL;
224
225     // Now release data
226     free(_data);
227     _data = NULL;
228
229     // Call delegate
230     [_delegate mediaThumbnailer:self didFinishThumbnail:_thumbnail];
231
232     [self release]; // Balancing -fetchThumbnail
233 }
234
235 @end