Skip to content

Instantly share code, notes, and snippets.

@0xced
Last active August 29, 2015 14:09
Show Gist options
  • Select an option

  • Save 0xced/dee6775974863270fbcf to your computer and use it in GitHub Desktop.

Select an option

Save 0xced/dee6775974863270fbcf to your computer and use it in GitHub Desktop.
Fix for Lookback (0.6.5) crash when recording is stopped while playing a movie with a MPMoviePlayerController
/**
* Add these options to `OTHER_LDFLAGS` if Lookback is not linked to the app.
* -Wl,-U,_GFGetVersion
* -Wl,-U,_OBJC_CLASS_$_GFThumbnailer2
* -Wl,-U,_OBJC_IVAR_$_GFThumbnailer2._jobs
* -Wl,-U,_OBJC_IVAR_$_GFThumbnailer2._currentJob
*/
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>
#import <dlfcn.h>
#import <objc/runtime.h>
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
#pragma clang diagnostic ignored "-Wmissing-prototypes"
#pragma mark - Private Lookback Interfaces
__attribute__((weak_import)) extern NSString *(GFGetVersion)(void);
@interface GFThumbnailerJob : NSObject
- (NSURL *) url;
@end
__asm(".weak_reference _OBJC_IVAR_$_GFThumbnailer2._jobs");
__asm(".weak_reference _OBJC_IVAR_$_GFThumbnailer2._currentJob");
__attribute__((weak_import)) @interface GFThumbnailer2 : NSObject
{
@public
NSMutableDictionary *_jobs;
GFThumbnailerJob *_currentJob;
MPMoviePlayerController *_controller;
}
- (void) jobFinished:(NSNotification *)notification;
- (void) _performNextJob;
- (void) _stopOutstandingThumbnailing;
@end
#pragma mark - _performNextJob Replacement
/**
* The original -[GFThumbnailer2 _performNextJob] method uses -[MPMoviePlayerController requestThumbnailImagesAtTimes:timeOption:]
* to generate a thumbnail. For some reason, this causes a crash when another MPMoviePlayerController is already playing a movie.
*
* *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'movie player <MPMoviePlayerControllerNew: 0x1741f7b00> has wrong activation state (1)'
*
* In order to avoid this crash, the `_performNextJob` method is rewritten to use `AVAssetImageGenerator` instead.
*/
static void PerformNextJob(GFThumbnailer2 *self, SEL _cmd)
{
[self _stopOutstandingThumbnailing];
self->_currentJob = [[self->_jobs allValues] firstObject];
if (!self->_currentJob)
return;
NSURL *url = self->_currentJob.url;
[self->_jobs removeObjectForKey:url];
AVAsset *asset = [AVAsset assetWithURL:url];
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
[imageGenerator generateCGImagesAsynchronouslyForTimes:@[ @3.0 ] completionHandler:^(CMTime requestedTime, CGImageRef imageRef, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
NSMutableDictionary *userInfo = [NSMutableDictionary new];
UIImage *image = [UIImage imageWithCGImage:imageRef];
if (image)
userInfo[MPMoviePlayerThumbnailImageKey] = image;
if (error)
userInfo[MPMoviePlayerThumbnailErrorKey] = error;
NSNotification *notification = [[NSNotification alloc] initWithName:MPMoviePlayerThumbnailImageRequestDidFinishNotification object:nil userInfo:[userInfo copy]];
[self performSelectorOnMainThread:@selector(jobFinished:) withObject:notification waitUntilDone:NO];
}];
}
@implementation GFThumbnailer2 (FixLookbackMPMoviePlayerControllerCrash)
+ (void) load
{
@autoreleasepool
{
if (!GFGetVersion)
{
NSLog(@"Could not fix Lookback/MPMoviePlayerController crash because the GFGetVersion function was not found.");
return;
}
NSSet *supportedVersions = [NSSet setWithArray:@[ @"0.6.5" ]];
NSString *lookbackVersion = GFGetVersion();
if (![supportedVersions containsObject:lookbackVersion])
{
NSLog(@"Could not fix Lookback/MPMoviePlayerController crash because version %@ is not supported.", lookbackVersion);
return;
}
Method _performNextJob = class_getInstanceMethod(self, @selector(_performNextJob));
method_setImplementation(_performNextJob, (IMP)PerformNextJob);
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment