...

понедельник, 2 декабря 2013 г.

[Из песочницы] Скроллер для видео и понимание представления времени в 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.

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


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий