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

от автора

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

В этой статье я хочу поделиться своим опытом работы с видео в одном из своих последних проектов для 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, т.е. 60/10*30.
Чтобы еще лучше понять представление времени с помощью 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_sync(dispatch_get_main_queue(), ^{        [videoScroller loadThumbnails];     }); }); 

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

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

P.S.
Это моя первая статья, если она окажется интересной для хабражителей, то я готов и дальше делиться своим опытом, в частности планирую написание статьи о создании слайдера, позволяющего выбрать цвет текста с произвольной палитры, которая представляет из себя просто картинку.

ссылка на оригинал статьи http://habrahabr.ru/post/204440/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *