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

Изменение параметра playerItem AVPlayer в UITableView

У меня есть UITableView, содержащий несколько видеороликов для воспроизведения при прокрутке. По мере повторного использования ячеек в таблицеView я создаю экземпляр только AVPlayer для каждой строки. Когда ячейка повторно используется, я просто изменяю PlayerItem сотового плеера, вызывая [self.player replaceCurrentItemWithPlayerItem:newItem];. В настоящее время это косвенно называется внутри tableView:cellForRowAtIndexPath. При прокрутке вниз происходит заметное отставание при повторном использовании. С процессом устранения я пришел к выводу, что отставание вызвано replaceCurrentItemWithPlayerItem, прежде чем оно даже начнет играть. При удалении этой единственной строки кода (предотвращение получения игроком нового видео) отставание исчезает.

Что я пытался исправить:

У меня есть пользовательский UITableViewCell для воспроизведения этих видеороликов, и я создал метод внутри них, чтобы инициализировать новую информацию от объекта. I.E, в 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: может не вызываться в отдельном потоке, что не имеет для меня никакого смысла. Я знаю, что элементы UI никогда не должны обрабатываться в других потоках, кроме основного/UI-потока, но я бы никогда не подумал, что replaceCurrentItemWithPlayerItem попадает под эту категорию.

Теперь я ищу способ изменить элемент AVPlayer без запаздывания, но не могу найти. Я надеюсь, что я неправильно понял эту функцию, и кто-то меня исправит..

EDIT: Теперь мне сообщили, что этот вызов уже прошит, и этого не должно произойти. Однако я не вижу другого объяснения. Ниже мой cellForRowAtIndexPath:. Он находится внутри пользовательского UITableView с делегатами, установленными на self (so 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 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 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;
}

Задержка происходит в одном и том же месте каждый раз. При прокрутке вниз мы видим, что короткое отставание происходит, когда верхняя часть нижней части ячейки достигает центра экрана. Я думаю, это ничего не значит для вас, но ясно, что отставание происходит в одном и том же месте каждый раз, а именно, когда новая ячейка перегружается. После изменения playerItem AVPlayer, я делаю ничего. Я не начинаю играть в видео до конца. replaceCurrentItemWithPlayerItem: , вызывающий это заметное отставание. В документации указано, что она меняет элемент в другом потоке, но что-то в этом методе поддерживает мой пользовательский интерфейс.

Мне сказали использовать Time Profiler в инструментах, чтобы узнать, что это такое, но я понятия не имею, как это сделать. Запустив профайлер и прокручивая время от времени, это результат (изображение). Пики каждой графовой группы - это отставание, о котором я говорю. Один единственный экстремальный пик (я думаю), когда я прокрутил до конца и постучал по строке состояния, чтобы прокрутить вверх. Я не знаю, как интерпретировать результат. Я обыскал стек для replace и нашел ублюдка. Он называется здесь из Main Thread (вверху), что имеет смысл, с "временем работы" 50 мс, о котором я и не подозреваю. Соответствующая доля графика составляет от 1 минуты до половины. Для сравнения, при комментировании этой единственной строки и запуске Time Profiler графики пиков значительно ниже (максимальный пик на 13% по сравнению с тем, что на изображении, вероятно, около 60-70%).

Я не знаю, что искать..

4b9b3361

Ответ 1

Если вы делаете базовый уровень профилирования, я думаю, вы можете сузить проблему. Для меня у меня была аналогичная проблема, когда replaceCurrentItemWithPlayerItem блокировал поток пользовательского интерфейса. Я разрешил это, изучив мой код, чтобы узнать, какая линия занимает время. Для меня загрузка AVAsset занимала время. Поэтому я использовал метод loadValuesAsynchronouslyForKeys AVAsset для решения моей проблемы.

Следовательно, вы можете попробовать следующее:

-(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 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;
}

Ответ 2

Вы должны профилировать свое приложение, используя профилировщик времени, чтобы увидеть, где происходит задержка.

Документация для replaceCurrentItemWithPlayerItem: четко заявляет, что она выполняется асинхронно, поэтому она не должна быть источником перегруженности главной очереди.

Замена элемента происходит асинхронно; наблюдать за текущим чтобы узнать, когда замена будет/имела место.

Документация здесь

Если вы все еще не можете понять это, напишите больше кода и, в частности, хотя бы ваш метод cellForRowAtIndexPath.

UPDATE

В соответствии с комментарием @joey ниже предыдущий контент этого ответа больше не действителен. documentation больше не заявляет, что метод является асинхронным, поэтому он может и не быть.

Ответ 3

В своем 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.

Теперь, когда пользователь удаляет ячейку, вы отправляете вызов обратно в UIViewController, где у вас есть tableView, а в методе делегата вы должны сделать следующее:

    - (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;
    }

P.S:.

На более старых устройствах (почти все из них до появления нового iPad Pro) не указано несколько экземпляров AVPlayer. Также надеюсь, что эти фрагменты кода могут помочь вам или помочь вам в вашем квесте:)