]> git.sesse.net Git - vlc/blobdiff - projects/macosx/framework/Sources/VLCMediaThumbnailer.m
VLCKit: Import MobileVLCKit.
[vlc] / projects / macosx / framework / Sources / VLCMediaThumbnailer.m
diff --git a/projects/macosx/framework/Sources/VLCMediaThumbnailer.m b/projects/macosx/framework/Sources/VLCMediaThumbnailer.m
new file mode 100644 (file)
index 0000000..71ad3d1
--- /dev/null
@@ -0,0 +1,235 @@
+//
+//  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