Подтвердить что ты не робот

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

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

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

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

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

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

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

3. Два AVPlayers друг на друга по очереди проигрывают AVPlayerItem

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


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

4b9b3361

Ответ 1

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

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

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

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

Что я закончил делать

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

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

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

Как я исправил его

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

Финальное отражение

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

Ответ 2

Возможно, вы уже посмотрели на это, но вы проверили AVQueuePlayer Documentation

Он предназначен для воспроизведения 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];

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

Ответ 3

Обновление - 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, а не в устройстве.

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