1

I have a model object that has a class method that checks if the model object already exists, and if it does it returns it, or if it doesn't it creates it and then returns it. This class makes use of the VLC framework for generating data about video files and to generate a thumbnail. This is where I'm having trouble.

The VLCThumbnailer returns the thumbnail via a delegate method once it's fetchthumbnail method is called. The problem is that the delegate method doesn't get returned until AFTER my class-creation method reaches it's return function. Here's a code example.

-(AnimuProfile*)createnewProfileforFilename:(NSString*)filename{

        NSURL *fileURL = [NSURL fileURLWithPath:filename];
    VLCMedia *media = [VLCMedia mediaWithURL:fileURL];
    FilenameParser *parser = [[FilenameParser alloc]init];
    NSArray *parsedFilename = [parser parseFilename:[filename lastPathComponent]];

    NSArray *mediaArray = [media tracksInformation];
    if (mediaArray.count != 0) {
        NSDictionary *videoTrackinfo = [mediaArray objectAtIndex:0];
        _fansubGroup = parsedFilename[0];
        _seriesTitle = parsedFilename[1];
        _episodeNumber = parsedFilename[2];
        _filename = [filename lastPathComponent];
        _filepathURL = fileURL;
        _filepathString = filename;
        _watched = NO;
        _progress = [VLCTime timeWithInt:0];
        _length = [[media length]stringValue];

        NSNumber *resolution = [videoTrackinfo valueForKey:@"height"];
        _resolution = [NSString stringWithFormat:@"%@p",resolution];

        VLCMediaThumbnailer *thumbnailer = [VLCMediaThumbnailer thumbnailerWithMedia:media andDelegate:self];

        [thumbnailer fetchThumbnail];



    NSString *libPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];

    NSString *profileName = [[_filename lastPathComponent] stringByAppendingPathExtension:@"prf"];
    NSString *pathandProfileName = [libPath stringByAppendingPathComponent:profileName];
    [NSKeyedArchiver archiveRootObject:self toFile:pathandProfileName];

      return self;
}

And then the delegate methods:

#pragma mark VLC Thumbnailer delegate methods
- (void)mediaThumbnailerDidTimeOut:(VLCMediaThumbnailer *)mediaThumbnailerP{
    NSLog(@"Thumbnailer timed out on file %@",_filename);
    UIImage *filmstrip = [UIImage imageNamed:@"filmstrip"];
    _thumbnail = UIImagePNGRepresentation(filmstrip);
}
- (void)mediaThumbnailer:(VLCMediaThumbnailer *)mediaThumbnailer didFinishThumbnail:(CGImageRef)thumbnail{
    UIImage *image = [UIImage imageWithCGImage:thumbnail];
    _thumbnail = UIImagePNGRepresentation(image);

}

I know it's a nono to lock the main thread waiting for the delegate method to be called so what should be done in this instance?

Joseph Toronto
  • 1,882
  • 1
  • 15
  • 29
  • 1
    Sounds like you want a [completion block](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html) to be [passed into your method](http://stackoverflow.com/questions/7180552/implementing-a-method-taking-a-block-to-use-as-callback). – Hamish Mar 08 '16 at 16:59

2 Answers2

1

I know it's a nono to lock the main thread waiting for the delegate method to be called so what should be done in this instance?

Those delegate methods are being called on VLC's video processing thread. They aren't the main thread and, therefore, you shouldn't be calling random UIKit API directly in the return blocks.

You need to process the results when they are available. If VLC were implemented using modern patterns, it would be using completion blocks. But it isn't, so...

 - (void)mediaThumbnailer:(VLCMediaThumbnailer *)mediaThumbnailer didFinishThumbnail:(CGImageRef)thumbnail{
  {
      dispatch_async(dispatch_get_main_queue(), ^{ ... process thumbnail and update UI accordingly here ...});
  }

That is, your createnewProfileforFilename: method should start the processing, but not expect it to be finished until sometime later. Then, when that sometime later happens, you trigger the updating of the UI with the data that was processed in the background.

And, as you state, you should never block the main queue/thread.

bbum
  • 162,346
  • 23
  • 271
  • 359
  • Thanks. It seems you know a bit on how VLC works. The problem is that I'm not calling these methods from a view controller, but rather from within the model object itself. This is a tricky one. – Joseph Toronto Mar 08 '16 at 18:57
  • @JosephToronto Only as much as the source for that class that I read. :) Still not too tricky; your model will dispatch to the main thread and the main thread update method will (most likely) tell the controller layer that there is new data available, then the controller layer can take care of mucking about with the UI layer. Alternatively, you could fire off a notification, but that seems like overkill and is harder to debug anyway ("now, where did that notification come from?!"). – bbum Mar 08 '16 at 21:13
0

I was able to solve it by creating a separate class to be the delgate, make thumbnail fetch requests and then handle them.

@property NSMutableArray *queue;
@end

@implementation ThumbnailWaiter

+(id)sharedThumbnailWaiter{
    static ThumbnailWaiter *singletonInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        singletonInstance = [[self alloc] init];
    });
    return singletonInstance;
}


-(id)init{
    self = [super init];
    if (self) {


        NSMutableArray *queue = [NSMutableArray array];
        _queue = queue;


    }


    return self;
}


-(void)requestThumbnailForProfile:(AnimuProfile*)profile{


    VLCMedia *media = [VLCMedia mediaWithURL:profile.filepathURL];
    VLCMediaThumbnailer *thumbnailer = [VLCMediaThumbnailer thumbnailerWithMedia:media andDelegate:self];
    [_queue addObject:profile];
    [thumbnailer fetchThumbnail];

}




#pragma mark VLC Thumbnailer delegate methods
- (void)mediaThumbnailerDidTimeOut:(VLCMediaThumbnailer *)mediaThumbnailerP{


}

- (void)mediaThumbnailer:(VLCMediaThumbnailer *)mediaThumbnailer didFinishThumbnail:(CGImageRef)thumbnail{
    UIImage *image = [UIImage imageWithCGImage:thumbnail];
    AnimuProfile *profile = _queue.firstObject;
    profile.thumbnail = UIImagePNGRepresentation(image);
    [profile saveProfile];
    [_queue removeObjectAtIndex:0];

   }

Seems almost silly to have to do it this way but it seems to be working.

Joseph Toronto
  • 1,882
  • 1
  • 15
  • 29