web-dev-qa-db-ja.com

AVPlayerを使用した複数のビデオ

画面の一部で動画を再生する必要があるiPad用のiOSアプリを開発しています。コンパイル時に指定されていない順序で順番に再生する必要があるビデオファイルがいくつかあります。 1つのビデオ再生のように見える必要があります。 1つのビデオから次のビデオに移動するときに、2つのビデオの最後または最初のフレームが表示されるのに多少の遅延があることは問題ありませんが、コンテンツのないちらつきや白い画面はないはずです。ビデオにはオーディオが含まれていません。メモリ使用量を考慮することが重要です。ビデオの解像度は非常に高く、いくつかの異なるビデオシーケンスを同時に並べて再生できます。

これを取得するために、私はいくつかの解決策をずっと試してきました。それらは以下のとおりです。

1。すべてのビデオを含むAVCompositionを含むAVPlayer

このソリューションでは、隣り合わせに配置されたすべてのビデオを含むAVCompositionを使用して作成されたAVPlayerItemでのみ使用するAVPlayerがあります。特定のビデオに行くとき、コンポジションで次のビデオが始まる時間にシークします。このソリューションの問題は、プレーヤーをシークすると、シークするフレームの一部がすぐに表示されることです。これは許容できません。コンポジションで特定の時間に直接ジャンプする方法はないようです。完成したビデオの最後のフレームの画像を作成し、シーク中にAVPLayerの前にそれを表示し、シークが完了したら最後にそれを削除することで、これを解決しようとしました。 AVAssetImageGeneratorを使用して画像を作成していますが、何らかの理由で画像の品質がビデオと同じではないため、画像をビデオに重ねて表示したり非表示にしたりすると、顕著な変化があります。もう1つの問題は、1つのAVPlayerItemがすべてのビデオを保持するため、AVPlayerが大量のメモリを使用することです。

2。複数のAVPlayerItemsを持つAVPlayer

このソリューションは、各ビデオにAVPlayerItemを使用し、新しいビデオに切り替えるときにAVPlayerのアイテムを置き換えます。この問題は、AVPlayerのアイテムを切り替えると、新しいアイテムをロードしている間、しばらくの間白い画面が表示されることです。この問題を解決するには、読み込み中に最後のフレームの前に画像を配置するという解決策を使用できますが、それでも画像とビデオの品質が異なり、注目に値するという問題があります。

。AVPlayerItemを再生するために順番に交わる2つのAVPlayers

私が試した次のソリューションは、AVPlayerItemsを順番に再生する2つのAVPlayerを交互に重ねることです。したがって、プレーヤーの再生が完了すると、ビデオの最後のフレームに留まります。もう1つのAVPlayerが前面に表示され(アイテムがnilに設定されているため、透明です)、次のAVPlayerItemがそのAVPlayerに挿入されます。ロードされるとすぐに再生が始まり、2つのビデオ間のスムーズなトランザクションのような錯覚はそのまま残ります。このソリューションの問題はメモリ使用量です。場合によっては、画面上で同時に2つのビデオを再生する必要があります。これにより、AVPlayerItemが読み込まれた4つのAVPlayersが同時に表示されます。ビデオの解像度が非常に高くなる可能性があるため、これは単にメモリが多すぎます。


誰もが全体的な問題と上に投稿された解決策についての考え、提案、コメントなどを持っていますか?.

38
Peter

プロジェクトがApp Storeで公開されました。このスレッドに戻って、調査結果を共有し、最終的に何をしているかを明らかにしましょう。

うまくいかなかったもの

すべてのビデオを含む大きなAVCompositionで最初のオプションを使用した場合、小さなスクラブグリッチがなければ構成の特定の時間にジャンプする方法がなかったため、十分ではありませんでした。さらに、APIが一時停止のフレーム保証を提供できなかったため、コンポジション内の2つのビデオ間でビデオを一時停止することに問題がありました。

3つ目は、2人のAVプレーヤーを持ち、順番に交代させることで、実際にうまくいきました。特にiPad 4またはiPhone 5の場合。RAMの量が少ないデバイスは、同時にメモリに複数のビデオを置くとメモリを大量に消費するため、特にビデオを処理する必要があったため、問題でした非常に高い解像度の。

私がやったこと

さて、左はオプション番号2でした。必要に応じてビデオのAVPlayerItemを作成し、それをAVPlayerにフィードしました。このソリューションの良い点は、メモリ消費でした。 AVPlayerItemsを遅延して作成し、不要になった瞬間に破棄することで、メモリの消費を最小限に抑えることができます。これは、RAMが限られている古いデバイスをサポートするために非常に重要でした。このソリューションの問題は、あるビデオから次のビデオに移動するときに、次のビデオがメモリにロードされている間、すぐに空白の画面が表示されることでした。これを修正するための私のアイデアは、プレーヤーがバッファリングしているときに表示されるAVPlayerの後ろに画像を配置することでした。私はビデオでピクセルごとに完全に完璧な画像が必要であることを知っていたので、ビデオの最後と最初のフレームの正確なコピーである画像をキャプチャしました。このソリューションは、実際にうまく機能しました。

このソリューションの問題

ビデオ/画像がそのネイティブサイズまたはモジュール4のスケーリングでない場合、UIImageView内の画像の位置がAVPlayer内のビデオの位置と同じではないという問題がありました。つまり、UIImageViewとAVPlayerでハーフピクセルを処理する方法に問題がありました。同じようには見えませんでした。

修正方法

私のアプリケーションはさまざまなサイズで表示されるインタラクティブな方法でビデオを使用していたので、私は多くのものを試しました。 AVPlayerLayerとCALayerの倍率フィルターと縮小フィルターを同じアルゴリズムを使用するように変更してみましたが、実際には何も変更しませんでした。結局、必要なすべてのサイズのビデオのスクリーンショットを自動的に撮り、ビデオを特定のサイズにスケーリングしたときに正しい画像を使用できるiPadアプリを作成することになりました。これにより、特定のビデオを表示していたすべてのサイズでピクセルパーフェクトな画像が得られました。完璧なツールチェーンではありませんが、結果は完璧でした。

最終反射

この位置の問題が私にとって非常に目に見えた(したがって解決することが非常に重要である)主な理由は、私のアプリが再生しているビデオコンテンツが描画アニメーションであり、多くのコンテンツが固定位置にあり、画像が動いています。すべてのコンテンツを1ピクセルだけ移動すると、非常に目立ち、醜いグリッチが発生します。今年のWWDCで、私はこの問題について、AVFoundationの専門家であるAppleエンジニアと話しました。問題を彼に紹介したとき、彼の提案は基本的にオプション3を使うことでしたが、私は彼に説明しましたメモリの消費とその解決策をすでに試したので、それは不可能でした。その意味で、彼は適切な解決策を選択し、ビデオのスケーリング時にUIImage/AVPlayerの配置に関するバグレポートを提出するように私に求めました。

48
Peter

すでにこれを見ているかもしれませんが、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];

私はこれがあなたが言及したちらつきの問題を軽減するかどうかを確認するためにテストしていませんが、これが進むべき道のようです。

15
Justyn

更新— 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に保存されているため、ビデオを再生できない場所に画像を配置します。

とにかく、これが出発点です。これがどのように機能するかを理解すれば、あなたが説明したグリッチなしに、メモリ上の必要なすべてのことを実行できます。

1
James Bush