Несколько видео с AVPlayer

Я разрабатываю приложение iOS для iPad, которое должно воспроизводить видео в какой-то части экрана. У меня есть несколько видеофайлов, которые нужно воспроизводить друг за другом в порядке, который не указан во время компиляции. Должно быть, это выглядит так, как будто воспроизводится только одно видео. Это нормально, что при переходе от одного видео к другому есть некоторая задержка, когда отображается последний или первый кадр из двух видео, но не должно быть мерцания или белых экранов без содержимого. Видео не содержат аудио. Важно учитывать использование памяти. Видео имеют очень высокое разрешение, и одновременно можно воспроизводить несколько различных видеопоследовательностей.

Чтобы получить это, я пробовал несколько решений. Они перечислены ниже:

1. AVPlayer с AVComposition со всеми видео в нем

В этом решении у меня есть AVPlayer, который будет использоваться только для AVPlayerItem, созданного с использованием AVComposition, содержащего все видео, помещенные рядом друг с другом. При переходе к конкретным видео я ищу время в композиции, с которого начинается следующее видео. Проблема с этим решением заключается в том, что при поиске проигрыватель быстро показывает некоторые кадры, по которым он ищет, что недопустимо. Кажется, что нет возможности сразу перейти к определенному времени в композиции. Я попытался решить эту проблему, сделав изображение последнего кадра в видео, которое только что закончилось, затем показать его перед AVPLayer во время поиска и, наконец, удалить его после завершения поиска. Я создаю изображение с помощью AVAssetImageGenerator, но по какой-то причине качество изображения не такое, как видео, поэтому при отображении и скрытии изображения поверх видео наблюдаются заметные изменения. Другая проблема заключается в том, что AVPlayer использует много памяти, потому что один AVPlayerItem содержит все видео.

2. AVPlayer с несколькими AVPlayerItems

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

3. Два AVPlayer друг над другом по очереди воспроизводят AVPlayerItem.

Следующее решение, которое я попробовал, заключалось в том, чтобы два AVPlayer располагались друг над другом, которые по очереди воспроизводили AVPlayerItems. Поэтому, когда один из игроков закончит воспроизведение, он останется на последнем кадре видео. Другой AVPlayer будет выведен на передний план (с его параметром, установленным в nil, поэтому он будет прозрачным), и следующий AVPlayerItem будет вставлен в этот AVPlayer. Как только он будет загружен, он начнет воспроизводиться, и иллюзия плавной транзакции между двумя видео останется неизменной. Проблема с этим решением - использование памяти. В некоторых случаях мне нужно воспроизводить два видео на экране одновременно, что приводит к одновременному появлению 4 AVPlayers с загруженным AVPlayerItem. Это просто слишком много памяти, поскольку видео могут быть в очень высоком разрешении.


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


person Peter    schedule 01.02.2013    source источник
comment
Вы когда-нибудь догадывались об этом?   -  person livingtech    schedule 13.06.2013


Ответы (3)


Итак, проект теперь доступен в App Store, и пришло время вернуться к этой теме, поделиться своими выводами и рассказать, чем я закончил.

Что не сработало

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

Третий вариант с двумя AV-плеерами и возможностью их смены отлично работал на практике. Особенно на iPad 4 или iPhone 5. Устройства с меньшим объемом оперативной памяти были проблемой, поскольку одновременное хранение нескольких видео в памяти потребляло слишком много памяти. Тем более, что мне приходилось иметь дело с видео очень высокого разрешения.

Чем я закончил?

Остался вариант номер 2. Создание AVPlayerItem для видео, когда это необходимо, и передача его в AVPlayer. Плюс в этом решении - потребление памяти. Ленивое создание AVPlayerItems и их выбрасывание в тот момент, когда они больше не нужны, можно было снизить потребление памяти до минимума, что было очень важно для поддержки старых устройств с ограниченным объемом оперативной памяти. Проблема с этим решением заключалась в том, что при переходе от одного видео к другому на короткое время появлялся пустой экран, пока следующее видео загружалось в память. Моя идея исправить это заключалась в том, чтобы поместить изображение позади AVPlayer, которое будет отображаться, когда проигрыватель буферизует. Я знал, что мне нужны изображения, которые мы точно от пикселя к пикселю идеально подходят для видео, поэтому я сделал изображения, которые были точными копиями последнего и первого кадра видео. Это решение отлично работало на практике.

Проблема с этим решением

У меня была проблема, хотя положение изображения внутри UIImageView не было таким же, как положение видео внутри AVPlayer, если видео / изображение не было его собственным размером или масштабированием модуля 4 этого. Другими словами, у меня была проблема с обработкой половины пикселей с помощью UIImageView и AVPlayer. Похоже, это было не так.

Как я это исправил

Я пробовал много вещей, так как мое приложение использовало видео в интерактивном режиме, показанное в разных размерах. Я попытался изменить фильтр увеличения и фильтр минификации для AVPlayerLayer и CALayer, чтобы использовать тот же алгоритм, но на самом деле ничего не изменил. В конце концов, я создал приложение для iPad, которое могло автоматически делать скриншоты видео во всех необходимых мне размерах, а затем использовать правильное изображение, когда видео масштабировалось до определенного размера. Это давало изображения, которые были идеальными по пикселям во всех размерах, которые я показывал в конкретном видео. Не идеальный набор инструментов, но результат был идеальным.

Последнее размышление

Основная причина, по которой эта проблема положения была очень заметна для меня (и поэтому очень важна для решения), заключалась в том, что видеоконтент, который воспроизводит мое приложение, представляет собой рисованную анимацию, где большая часть контента находится в фиксированной позиции и только часть картина движется. Если весь контент перемещается только на один пиксель, это дает очень заметный и уродливый сбой. В этом году на WWDC я обсуждал эту проблему с инженером Apple, который является экспертом в AVFoundation. Когда я представил ему проблему, он предложил в основном использовать вариант 3, но я объяснил ему, что это невозможно из-за потребления памяти, и что я уже пробовал это решение. В этом свете он сказал, что я выбрал правильное решение, и попросил меня отправить отчет об ошибке для позиционирования UIImage / AVPlayer при масштабировании видео.

person Peter    schedule 01.07.2013
comment
Лучшее решение - захватить первый кадр видео и добавить его в качестве фона, а затем, когда появится представление, добавить слой видео в качестве подвида и удалить фон. - person Hashem Aboonajmi; 08.12.2014
comment
Привет, Питер, у тебя есть код для вашего решения, пожалуйста? - person Charlie; 01.04.2021

Возможно, вы уже это видели, но ознакомились ли вы с AVQueuePlayer документацией

Он предназначен для воспроизведения AVPlayerItems в очереди и является прямым подклассом AVPlayer, поэтому используйте его таким же образом. Вы настраиваете его следующим образом:

AVPlayerItem *firstItem = [AVPlayerItem playerItemWithURL: firstItemURL];
AVPlayerItem *secondItem = [AVPlayerItem playerItemWithURL: secondItemURL];

AVQueuePlayer *player = [AVQueuePlayer queuePlayerWithItems:[NSArray arrayWithObjects:firstItem, secondItem, nil]];

[player play];

Если вы хотите добавить новые элементы в очередь во время выполнения, просто используйте этот метод:

[player insertItem:thirdPlayerItem afterItem:firstPlayerItem];

Я не тестировал, уменьшит ли это проблему мерцания, о которой вы упомянули, но похоже, что это будет выход.

person Justyn    schedule 20.02.2013
comment
Спасибо за ваш ответ. К сожалению, когда AVQueuePlayer переходит от одного элемента к другому, появляется мерцание. - person Peter; 27.02.2013
comment
Могу подтвердить, что на сегодняшний день AVQueuePlayer по-прежнему не переключает видео плавно. - person Andres Canella; 13.05.2016
comment
вам нужно будет объединить видео, для этого я создаю суть с кодом: gist.github.com / davidlondono / fedef4d1a42d9ad38657c3626c2b275d - person David Alejandro Londoño Mejía; 18.05.2016
comment
Можете предложить здесь stackoverflow.com/questions/54745359/ - person Anand Gautam; 18.02.2019

Обновление - https://youtu.be/7QlaO7WxjGg

Вот ваш ответ, используя в качестве примера представление коллекции, которое будет воспроизводить 8 одновременно (обратите внимание, что никакого управления памятью не требуется; вы можете использовать ARC):

    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = (UICollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier forIndexPath:indexPath];

    // Enumerate array of visible cells' indexPaths to find a match
    if ([self.collectionView.indexPathsForVisibleItems
         indexOfObjectPassingTest:^BOOL(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
             return (obj.item == indexPath.item);
         }]) dispatch_async(dispatch_get_main_queue(), ^{
             [self drawLayerForPlayerForCell:cell atIndexPath:indexPath];
         });


    return cell;
}

- (void)drawPosterFrameForCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    [self.imageManager requestImageForAsset:AppDelegate.sharedAppDelegate.assetsFetchResults[indexPath.item]
                                                          targetSize:AssetGridThumbnailSize
                                                         contentMode:PHImageContentModeAspectFill
                                                             options:nil
                                                       resultHandler:^(UIImage *result, NSDictionary *info) {
                                                           cell.contentView.layer.contents = (__bridge id)result.CGImage;
                                                       }];
}

- (void)drawLayerForPlayerForCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    cell.contentView.layer.sublayers = nil;
    [self.imageManager requestPlayerItemForVideo:(PHAsset *)self.assetsFetchResults[indexPath.item] options:nil resultHandler:^(AVPlayerItem * _Nullable playerItem, NSDictionary * _Nullable info) {
        dispatch_sync(dispatch_get_main_queue(), ^{
            if([[info objectForKey:PHImageResultIsInCloudKey] boolValue]) {
                [self drawPosterFrameForCell:cell atIndexPath:indexPath];
            } else {
                AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:[AVPlayer playerWithPlayerItem:playerItem]];
                [playerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
                [playerLayer setBorderColor:[UIColor whiteColor].CGColor];
                [playerLayer setBorderWidth:1.0f];
                [playerLayer setFrame:cell.contentView.layer.bounds];
                [cell.contentView.layer addSublayer:playerLayer];
                [playerLayer.player play];
            }
        });
    }];
}

Метод drawPosterFrameForCell помещает изображение, где невозможно воспроизвести видео, поскольку оно хранится в iCloud, а не на устройстве.

В любом случае, это отправная точка; Как только вы поймете, как это работает, вы сможете делать все, что хотите, без каких-либо сбоев в отношении памяти, которые вы описали.

person James Bush    schedule 15.06.2016