web-dev-qa-db-ja.com

UITableViewでAVPlayerのplayerItemを変更する

スクロール時に再生するビデオの数を含むUITableViewがあります。 tableViewのセルが再利用されているため、行ごとに1つのAVPlayerのみをインスタンス化します。セルを再利用するときは、[self.player replaceCurrentItemWithPlayerItem:newItem];を呼び出して、セルのプレーヤーのPlayerItemを変更するだけです。これは現在、tableView:cellForRowAtIndexPath内で間接的に呼び出されています。下にスクロールすると、再利用時に顕著な遅延が発生します。消去法により、ラグはreplaceCurrentItemWithPlayerItemが原因で、プレイが開始される前であると結論付けました。この1行のコードを削除すると(プレーヤーが新しいビデオを取得できないようにする)、遅延がなくなります。

私が修正しようとしたこと:

これらのビデオを再生するためのカスタムUITableViewCellがあり、オブジェクトからの新しい情報で初期化するために、これらの中にメソッドを作成しました。つまり、cellForRowAtIndexPath:[cell initializeNewObject:newObject];を呼び出して、次のメソッドを実行します。

//In CustomCell.m
-(void)initializeNewObject:(CustomObject*)newObject
{ /*...*/
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        AVPlayerItem *xPlayerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:newObject.url]];
        AVPlayer *dummy = self.player;
        [dummy replaceCurrentItemWithPlayerItem:xPlayerItem];
        dispatch_async(dispatch_get_main_queue(), ^{
            self.player = dummy;
            playerItem = xPlayerItem;
        }
    }/*...*/
}

これを実行すると、アイテムの交換の呼び出しを完全に削除した場合と同じ結果が得られます。どうやら、この関数はスレッド化できません。これから何を期待していたのか完全にはわかりません。これを機能させるには、copyのクリーンなAVPlayerが必要だと思いますが、少し検索したところ、replaceCurrentItemWithPlayerItem:ができるというコメントがいくつか見つかりましたnot別のスレッドで呼び出されますが、これは私には意味がありません。 UI要素をmain/UI-thread以外のスレッドで処理してはならないことは知っていますが、replaceCurrentItemWithPlayerItemがこのカテゴリに分類されるとは思いません。

AVPlayerのアイテムをラグなしで変更する方法を探していますが、見つかりません。この関数のスレッドが間違っていることを理解し、誰かが私を修正してくれることを願っています。

編集:この呼び出しはすでにスレッド化されており、実際には発生しないはずであることが通知されました。しかし、他に説明はありません。以下は私のcellForRowAtIndexPath:です。これはカスタムUITableView内にあり、デリゲートはselfに設定されています(つまり、self == tableView

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CustomCell *cell = [self dequeueReusableCellWithIdentifier:kCellIdentifier];
    if(!cell)
        cell = [[[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil] objectAtIndex:0];

    //Array 'data' contains all objects to be shown. The objects each have titles, url's etc.
    CustomVideoObject *currentObject = [data objectAtIndex:indexPath.row];
    //When a cell later starts playing, I store a pointer to the cell in this tableView named 'playing'
    //After a quick scroll, the dequeueing cell might be the cell currently playing - resetting
    if(playing == cell)
        playing = nil;
    //This call will insert the correct URL, title, etc for the new video, in the custom cell
    [cell initializeNewObject:currentObject];
    return cell;
}

現在「機能している」が遅れているinitializeNewObject(これはCustomCell.m内にあります:

-(void)initializeNewObject:(CustomObject*)o
{
    //If this cell is being dequeued/re-used, its player might still be playing the old file
    [self.player pause];
    self.currentObject = o;
    /*
    //Setting text-variables for title etc. Removed from this post, but by commenting them out in the code, nothing improves.
    */

    //Replace the old playerItem in the cell's player
    NSURL *url = [NSURL URLWithString:self.currentObject.url];
    AVAsset *newAsset = [AVAsset assetWithURL:url];
    AVPlayerItem *newItem = [AVPlayerItem playerItemWithAsset:newAsset];
    [self.player replaceCurrentItemWithPlayerItem:newItem];

    //The last line above, replaceCurrentItemWithPlayerItem:, is the 'bad guy'.
    //When commenting that one out, all lag is gone (of course, no videos will be playing either)
    //The lag still occurs even if I never call [self.player play], which leads me
    //to believe that nothing after this function can cause the lag

    self.isPlaying = NO;
}

ラグは毎回まったく同じ場所で発生しています。速く下にスクロールすると、一番下のセルの上部が画面の中央に到達したときに短いラグが発生することがわかります。これはあなたにとって何の意味もないと思いますが、毎回、つまり新しいセルがデキューされているときに、まったく同じ場所でラグが発生していることは明らかです。 AVPlayerのplayerItemを変更した後、nothingを実行します。私は後でまでビデオの再生を開始しません。 replaceCurrentItemWithPlayerItem:isこの顕著な遅延を引き起こします。ドキュメントには、別のスレッドのアイテムが変更されていると記載されていますが、そのメソッドの何かがUIを保持しています。

InstrumentsのTimeProfilerを使用してそれが何であるかを調べるように言われましたが、その方法がわかりません。プロファイラーを実行し、たまにスクロールすると、これが結果です (画像)。各グラフグループのピークは、私が話しているラグです。極端なピークは(私が思うに)一番下までスクロールし、ステータスバーをタップして一番上までスクロールしたときです。結果の解釈方法がわかりません。スタックでreplaceを検索したところ、ろくでなしが見つかりました。ここではMain Thread(上部)から呼び出されます。これは理にかなっています。「実行時間」は50ミリ秒で、私には考えられません。グラフの関連する割合は、約1分半の期間です。比較すると、その1行をコメントアウトして、Time Profilerを再度実行すると、グラフのピークは大幅に低くなります(画像にあるものと比較して、最高のピークは13%で、おそらく約60〜70%です)。

何を探すべきかわからない。

40
Sti

基本的なレベルのプロファイリングを行うと、問題を絞り込むことができると思います。私の場合、replaceCurrentItemWithPlayerItemがUIスレッドをブロックしていたという同様の問題がありました。コードを調べて、どの行に時間がかかっているかを調べることで解決しました。私にとって、AVAssetの読み込みには時間がかかりました。そこで、AVAssetのloadValuesAsynchronouslyForKeysメソッドを使用して問題を解決しました。

したがって、次のことを試すことができます。

-(void)initializeNewObject:(CustomObject*)o
{
    //If this cell is being dequeued/re-used, its player might still be playing the old file
    [self.player pause];
    self.currentObject = o;
    /*
    //Setting text-variables for title etc. Removed from this post, but by commenting them out in the code, nothing improves.
    */

    //Replace the old playerItem in the cell's player
    NSURL *url = [NSURL URLWithString:self.currentObject.url];
    AVAsset *newAsset = [AVAsset assetWithURL:url];
    [newAsset loadValuesAsynchronouslyForKeys:@[@"duration"] completionHandler:^{
        AVPlayerItem *newItem = [AVPlayerItem playerItemWithAsset:newAsset];
        [self.player replaceCurrentItemWithPlayerItem:newItem];
    }];


    //The last line above, replaceCurrentItemWithPlayerItem:, is the 'bad guy'.
    //When commenting that one out, all lag is gone (of course, no videos will be playing either)
    //The lag still occurs even if I never call [self.player play], which leads me
    //to believe that nothing after this function can cause the lag

    self.isPlaying = NO;
}
1
manishg

タイムプロファイラーを使用してアプリのプロファイリングを行い、ラグが実際に発生している場所を確認する必要があります。

replaceCurrentItemWithPlayerItem:のドキュメントには、非同期で実行されることが明記されているため、メインキューのジャンプの原因になることはありません。

アイテムの置き換えは非同期で行われます。 currentItemプロパティを観察して、置換がいつ発生するか/発生したかを確認します。

ドキュメントはこちら

それでも理解できない場合は、さらにコードを投稿してください。特に、少なくともcellForRowAtIndexPathメソッドを投稿してください。

[〜#〜]更新[〜#〜]

以下の@joeyのコメントによると、この回答の以前の内容は無効になっています。 documentation は、メソッドが非同期であることを示しなくなったため、非同期でない可能性があります。

0
Dima

CustomCell.mでは、サムネイルを使用して特定のセルビデオの画像を表示する必要があります。サムネイルをセル内のボタンに追加し、CustomCell.hでカスタムデリゲートを作成して、新しくタップしたときに呼び出されます。次のようなボタンを作成しました:

@class CustomCell;

@protocol CustomCellDelegate <NSObject>

- (void)userTappedPlayButtonOnCell:(CustomCell *)cell;

@end

また、.hにボタンのアクションを追加します。

@interface CustomCell : UITableViewCell

@property (strong, nonatomic) IBOutlet UIButton *playVideoButton;

 - (IBAction)didTappedPlayVideo;
 - (void)settupVideoWithURL:(NSString *)stringURL;
 - (void)removeVideoAndShowPlaceholderImage:(UIImage *)placeholder;

@end

「didTappedPlayVideo」でも次のことを行います。

- (void)didTappedPlayVideo{

[self.delegate userTappedPlayButtonOnCell:self];

}

メソッド:settupVideoWithURLはプレーヤーを初期化するために使用され、メソッド:removeVideoAndShowPlaceholderImageはビデオを停止し、AVPlayerItemとAVPlayerをnilにします。

これで、ユーザーがセルをタップすると、tableViewがあるUIViewControllerに呼び出しが返され、デリゲートメソッドで次の操作を行う必要があります。

    - (void)userTappedPlayButtonOnCell:(CustomCell *)cell{

    //check if this is the first time when the user plays a video
    if(self.previousPlayedVideoIndexPath){
          CustomCell *previousCell = (CustomCell *)[tableView cellForRowAtIndexPath:self.previousPlayedVideoIndexPath];
          [previousCell removeVideoAndShowPlaceholderImage: [UIImage imageNamed:@"img.png"]];
    }
    [cell settupVideoWithURL:@"YOURvideoURL"];
    self.previousPlayedVideoIndexPath = cell.indexPath;
    }

追伸:

古いデバイス(新しいiPad Proまではほとんどすべて)では、AVPlayerのインスタンスが複数あることは示されていません。また、このコードのチャンクがあなたの探求を導いたり助けたりできることを願っています:)

0
Laur Stefan