Как перезапустить AVPlayerItem?

Я инициализирую AVPlayerItem методом initWithUrl:.

Проблема в том, что при прерывании инициализации (например, потеря соединения)

Допустим, у нас есть следующее:

self.avPlayerItem = [[AVPlayerItem alloc] initWithUrl:url];

1. Что делать? Что происходит с экземпляром avPlayerItem при потере соединения? Есть ли какой-то протокол для реализации, чтобы определить, была ли инициализация успешной или нет?

2. Что я заметил, так это то, что после прерывания инициализации следующая «успешная» инициализация avPlayerItem вообще не имеет дорожек (они присутствуют, когда до этого не было прерывания). Чтобы снова инициализировать avPlayerItem дорожками из источника, пользователю необходимо закройте и снова откройте приложение.


person travdu    schedule 16.09.2015    source источник


Ответы (2)


При работе с кодом, который зависит от подключения к Интернету, вы должны убедиться, что есть отказоустойчивые устройства, которые срабатывают, когда что-то идет не так, потому что в какой-то момент это произойдет.

То, что должно быть сделано?

1) Выполнить весь код, который зависит от подключения к Интернету, в фоновом потоке. Вы же не хотите загромождать пользовательский интерфейс.

2) Если возможно, проверьте загруженный файл перед использованием.

Есть разные способы сделать это, но я думаю, что я бы использовал NSURLConnection или аналогичный API для загрузки файла во временную папку. НА ФОНОВОЙ ТЕМЕ.

При загрузке я бы инициализировал AVAsset, используя временный URL-адрес. AVAsset имеет некоторые полезные свойства, такие как playable, которые помогут вам проверить, правильно ли загружен файл. (У NSURLConnectionDelegate также есть метод, который уведомляет об ошибках загрузки.)

Если вы зашли так далеко, вы можете создать AVPlayerItem со своим AVAsset и вперед. Не забудьте в какой-то момент стереть содержимое временной папки, если вы не держитесь за загруженный контент.

Помните, что вы хотите воспроизвести файл в основном потоке, но все остальные загрузки и проверки, вероятно, лучше выполнять в фоновом потоке; вы определенно хотите использовать NSURLConnection из фонового потока.

person Rob Sanders    schedule 16.09.2015
comment
спасибо за отличный ответ. Я использую GCD для загрузки файла. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ ... Я пытался использовать [AVURLAsset initWithUrl:options:] и пытался инициализировать AVPlayerItem с этим ресурсом. Элемент инициализирован, но все еще не имеет дорожек. Возможно я не правильно чистил инстанс. Но я вижу в коде, что все наблюдатели удаляются, а avplayeritem (также avplayer.currentitem и т. д.) устанавливается на ноль, когда что-то идет не так..) - person travdu; 17.09.2015
comment
AVAsset имеет свойство tracks, так что вы можете получить информацию о треке перед воспроизведением. Так что, если вы ожидаете 2 трека, а получаете 1, значит, что-то пошло не так. Я ожидаю, что где-то в процессе инициализации есть ошибка, и поэтому у вас проблемы. Либо так, либо вы загрузили поврежденный файл. - person Rob Sanders; 17.09.2015
comment
Я ожидаю одну дорожку, а получаю 0. Файл в порядке, потому что когда я запускаю приложение и снова пытаюсь получить тот же файл, все работает нормально. У меня проблема только тогда, когда initWithUrl прерывается с потерей соединения. Потом каждый раз после прерывания получаю 0 треков даже при доступе к другим файлам. Как я говорю. Когда соединение не теряется во время инициализации, все работает нормально, в том числе и при последующем доступе к другим файлам. - person travdu; 17.09.2015
comment
Хорошо, это, скорее всего, потому, что он пытается загрузить наполовину загруженный файл. Если вы используете NSURLConnection, вы можете поймать это прерывание, удалить загруженный файл и загрузить его повторно. Если вы не хотите использовать NSURLConnection, вы можете просто предположить, что если у вас 0 треков, вам нужно удалить файл и загрузить его заново. Вы можете изящно справиться с этим, показав предупреждение пользователю о чем угодно. - person Rob Sanders; 17.09.2015

Сначала сохраните AVPlayerItem в объекте NSPurgeableData и воспроизведите его; сохраните объект данных в объекте NSCache, чтобы автоматически удалить объект из памяти после того, как он воспроизвелся, или когда соединение разрывается, а прежний AVPlayerItem заменяется новым (все эти вещи вы должны делать в любом случае, независимо от конкретная проблема, которую вы описываете). Вот некоторый код, чтобы вы начали:

void (^cachePlayerItem)(AVPlayerItem *, NSCache *, NSString *) = ^(AVPlayerItem *playerItem, NSCache *cache, NSString *key) {
    NSURL *fileURL = [(AVURLAsset *)playerItem.asset URL];
    NSPurgeableData *data = [NSPurgeableData dataWithContentsOfURL:fileURL];
    [data beginContentAccess];
    [cache setObject:data forKey:key];
    [data endContentAccess];
    [data discardContentIfPossible];
};

Поместите этот блок в любом месте файла реализации, определив его в заголовочном файле с помощью:

typedef void (^cachePlayerItemBlock)(AVPlayerItem *, NSCache *, NSString *);

Вызовите его в методе с помощью:

cachePlayerItem(playerItem, playerItems, phAsset.localIdentifier);

Принимая во внимание, что playerItem — это AVPlayerItem, playerItems — это кеш NSCache, и, в зависимости от того, из какого актива вы получаете AVPlayerItem, какой-то уникальный для него идентификатор (или, в приведенном выше примере, связанный с ним актив).

Кстати, я соответственно настроил свои кеши в AppDelegate:

- (NSCache *)thumbnailCache {
    __block NSCache *t = self->_thumbnailCache;
    if (!t) {
        t = [[NSCache alloc] init];
        [t setName:@"Thumbnail Cache"];
        [t setEvictsObjectsWithDiscardedContent:TRUE];
        [t setCountLimit:self.assetsFetchResults.count];
        self->_thumbnailCache = t;
    }
    return t;
}

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

Чтобы создать глобально доступный кеш в AppDelegate, добавьте их в файлы заголовков и реализации соответственно:

+ (AppDelegate *)sharedAppDelegate;

...а также:

+ (AppDelegate *)sharedAppDelegate
{
    return (AppDelegate *)[[UIApplication sharedApplication] delegate];
}

Чтобы сослаться на кеш в других файлах классов, импортируйте заголовочный файл AppDelegate и, при желании, создайте ярлык для метода SharedApplication класса AppDelegate:

#import "AppDelegate.h"

#define AppDelegate ((AppDelegate *)[[UIApplication sharedApplication] delegate])

Кроме того, на кеш можно ссылаться с помощью...:

AppDelegate.thumbnailCache

...вместо:

AppDelegate.sharedAppDelegate.thumbnailCache
person James Bush    schedule 22.10.2016