Pull to refresh

Скроллер для видео и понимание представления времени в Objective-C

Programming *Development for iOS *Objective C *
Sandbox


Здраствуй, Хабражитель!

В этой статье я хочу поделиться своим опытом работы с видео в одном из своих последних проектов для iOS. Не буду углубляться в подробности, лишь опишу одну из задач, которую не удалось решить с помощью поиска по хабру, гитхабу и остальному интернету. Задача состояла в следующем: сделать скроллер для видео, да не простой, а чтобы был как в стандартной галерее iOS 7.



Т.к. для воспроизведения видео использовался стандартный компонент MPMoviePlayerViewController, а он поддерживает перемотку видео в любую позицию, то основная задача состояла в том, чтобы получить из видео картинки через равные промежутки времени и положить их на UIView, таким образом, чтобы они оказывались примерно под текущей позицией в видео. Забегая немного вперед хочу сказать, что по ходу дела пришлось решить еще пару проблем: тормоза при генерации картинок из видео на iPad, и разная длина слайдера в вертикальной и горизонтальной ориентации устройства.

Итак, для начала нам нужно понять, каким образом можно получить картинки из видео. И в этом нам поможет AVAssetImageGenerator. Этот класс специально создан для того, чтобы получать картинки с произвольного места видео. Будем считать, что наш тестовый файл располагается в домашней папке и называется test.mov:

NSString *filepath = [NSString stringWithFormat:@"%@/Documents/test.mov", NSHomeDirectory()];
NSURL *fileURL = [NSURL fileURLWithPath:filepath];


Пример использования AVAssetImageGenerator:

// create asset
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:fileURL options:nil];
// create generator
AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
// time for preview
CMTime time = CMTimeMake(1, 2);
// get image ref
CGImageRef imageRef = [generator copyCGImageAtTime:time actualTime:nil error:nil];
// create image
UIImage *image = [UIImage imageWithCGImage:oneRef];


Раньше я не сталкивался с CMTime, и для того, чтобы разделить время на равные промежутки, не плохо было бы понять, что представляет из себя эта структура данных.

CMTimeMake принимает на вход два аргумента: value и timescale. Я ознакомился с официальной документацией и хочу объяснить простыми словами тем, кто не в курсе, что это за аргументы.
Во-первых, timescale, это число, на которое будет разделена каждая секунда. С помощью этого аргумента задается точность, с которой мы можем указать нужный момент времени. Например, если timescale указать равным 10, то можно получить 1/10 часть секунды.
В свою очередь value указывает на нужную часть времени, с учетом timescale. Например, мы имеем видео длиной 60 секунд, timescale равен 10, чтобы получить 30 секунду, value должно быть равно 300.
Чтобы еще лучше понять представление времени с помощью CMTime, скажу что количество секунд в текущий момент видео равняется value/timescale. Из предыдущего примера 30 секунда равна 300/10. Если понимать перевод времени из секунд в CMTime и обратно, то дальше ни каких проблем с этой структурой не должно возникнуть.

Идем дальше, теперь нам нужно узнать длину видео. Это довольно просто, созданный ранее объект asset уже имеет нужное нам свойство.

CMTime duration = asset.duration;


Хорошо, у нас есть все для того, чтобы нарезать видео на кучу картинок. Теперь встает вопрос, сколько их нужно для портретной и альбомной ориентации устройств. Первое на что нужно обратить внимание, это высота скроллера в стандартной галерее iPhone и iPad. Да, она почти одинаковая, различается только ширина. Не трудно догадаться, что количество картинок равно ширине слайдера поделенной на ширину одной картинки. Я решил, что сделаю квадратные картинки 29х29 точек. Тут есть один тонкий момент, в генераторе размер картинок нужно указывать в пикселях, поэтому там будет значение 58х58.

generator.maximumSize = CGSizeMake(58.0, 58.0);


Для простоты и удобства, число картинок я указал в дефайнах

#define iPad (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
#define ThumbnailsCountInPortrait (iPad ? 25 : 10)
#define ThumbnailsCountInLandscape (iPad ? 38 : 15)


Теперь все готово для генерации картинок. Я сделал два разных массива, т.к. в портретной и альбомной ориентации картинки из видео будут разные.

NSMutableArray *portraitThumbnails = [NSMutableArray array];
NSMutableArray *landscapeThumbnails = [NSMutableArray array];

// generate portrait thumbnails
for (NSInteger i=0; i < ThumbnailsCountInPortrait; i++) {
    CMTime time = CMTimeMake(duration.value/ThumbnailsCountInPortrait*i, duration.timescale);
    CGImageRef oneRef = [generator copyCGImageAtTime:time actualTime:nil error:nil];
    [portraitThumbnails addObject:[UIImage imageWithCGImage:oneRef]];
}
    
// generate landscape thumbnails
for (NSInteger i=0; i < ThumbnailsCountInLandscape; i++) {
    CMTime time = CMTimeMake(duration.value/ThumbnailsCountInLandscape*i, duration.timescale);
    CGImageRef oneRef = [generator copyCGImageAtTime:time actualTime:nil error:nil];
    [landscapeThumbnails addObject:[UIImage imageWithCGImage:oneRef]];
}


Не думаю, что тут будет уместно рассказывать как размещать полученные картинки в ряд на UIView, и тем более как их брать из разных массивов при разной ориентации устройства. В этом на самом деле нет ничего сложного и все это можно увидеть в готовом примере.

На последок я хотел бы рассказать про метод решения проблемы с тормозами. Т.к. слайдер инициализируется при загрузке контроллера, то имеет место быть задержка анимации перехода к текущему контроллеру. Самое простое решение — dispatch_async. Эта крайне полезная штука позволяет выполнить содержимое блока асин­хрон­но в фоне, не притормаживая приложение.

Пример использования:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    [videoScroller initializeThumbnails];
        
    dispatch_async(dispatch_get_main_queue(), ^{
       [videoScroller loadThumbnails];
    });
});


Думаю понятно, что videoScroller это наш объект, который инициализирует в фоне свои данные, а потом загружает их.

Рабочий пример можно взять тут: https://github.com/iBlacksus/BLVideoScroller

P.S.
Это моя первая статья, если она окажется интересной для хабражителей, то я готов и дальше делиться своим опытом, в частности планирую написание статьи о создании слайдера, позволяющего выбрать цвет текста с произвольной палитры, которая представляет из себя просто картинку.
Tags: видеоскроллерobjective-cios development
Hubs: Programming Development for iOS Objective C
Total votes 23: ↑23 and ↓0 +23
Comments 8
Comments Comments 8

Popular right now