AVPlayer «замораживает» приложение в начале буферизации аудиопотока - PullRequest
43 голосов
/ 09 октября 2011

Я использую подкласс AVQueuePlayer, и когда я добавляю новый AVPlayerItem с потоковым URL, приложение зависает примерно на секунду или две.Под заморозкой я подразумеваю, что он не реагирует на прикосновения к пользовательскому интерфейсу.Кроме того, если у меня уже воспроизводится песня, а затем добавляется еще одна в очередь, AVQueuePlayer автоматически начинает предварительную загрузку песни, пока она все еще транслирует первую.Это позволяет приложению не реагировать на прикосновения к пользовательскому интерфейсу в течение двух секунд, как при добавлении первой песни, но песня все еще воспроизводится.Таким образом, это означает, что AVQueuePlayer делает что-то в главном потоке, что вызывает явное «замораживание».

Я использую insertItem:afterItem:, чтобы добавить AVPlayerItem.Я проверил и убедился, что это был метод, который вызывал задержку.Возможно, это может быть что-то, что AVPlayerItem делает, когда он активируется AVQueuePlayer в момент добавления его в очередь.

Необходимо отметить, что я использую бета-версию Dropbox API v1 для получения потоковой передачиURL с помощью вызова этого метода:

[[self restClient] loadStreamableURLForFile:metadata.path];

Затем, когда я получаю URL потока, я отправляю его на AVQueuePlayer следующим образом:

[self.player insertItem:[AVPlayerItem playerItemWithURL:url] afterItem:nil];

Итак, мой вопрос: как мнеизбежать этого?Должен ли я выполнять предварительную загрузку аудиопотока самостоятельно без помощи AVPlayer?Если да, то как мне это сделать?

Спасибо.

Ответы [ 3 ]

69 голосов
/ 03 августа 2012

Не используйте playerItemWithURL это синхронизация. Когда вы получите ответ с URL, попробуйте следующее:

AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *keys = @[@"playable"];

[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
    [self.player insertItem:[AVPlayerItem playerItemWithAsset:asset] afterItem:nil];
}];
10 голосов
/ 20 января 2018

Удар, так как это вопрос с высоким рейтингом, и похожие вопросы онлайн либо устарели, либо не очень хороши.Вся идея довольно проста с AVKit и AVFoundation, что означает, что больше не нужно полагаться на сторонние библиотеки.Единственная проблема заключается в том, что потребовалось немного повозиться и собрать все вместе.

AVFoundation Player() инициализация с url, по-видимому, не является поточно-ориентированной, или, скорее, она не предназначена для этого.Это означает, что независимо от того, как вы инициализируете его в фоновом потоке, атрибуты проигрывателя будут загружаться в основную очередь, вызывая зависания в пользовательском интерфейсе, особенно в UITableView s и UICollectionViews.Для решения этой проблемы Apple предоставила AVAsset, который берет URL-адрес и помогает в загрузке атрибутов мультимедиа, таких как дорожка, воспроизведение, длительность и т. Д., И может делать это асинхронно, при этом лучше всего отменять этот процесс загрузки (в отличие от других очередей отправки).фоновые потоки, где завершение задачи может быть не таким простым).Это означает, что вам не нужно беспокоиться о затяжных потоках зомби в фоновом режиме, поскольку вы быстро прокручиваете таблицу или коллекцию, в конечном итоге накапливая в памяти целый набор неиспользуемых объектов.Эта функция cancellable великолепна и позволяет нам отменить любую длительную асинхронную загрузку AVAsset, если она выполняется, но только во время отключения ячейки.Процесс асинхронной загрузки может быть вызван методом loadValuesAsynchronously и может быть отменен (по желанию) в любое более позднее время (если все еще выполняется).

Не забудьте правильно обработать исключение, используя результатыloadValuesAsynchronouslySwift (3/4) , вот как вы могли бы загружать видео асинхронно и обрабатывать ситуации, если асинхронный процесс завершается неудачно (из-за медленных сетей и т. Д.) -

TL; DR

ДЛЯ ВОСПРОИЗВЕДЕНИЯ ВИДЕО

let asset = AVAsset(url: URL(string: self.YOUR_URL_STRING))
let keys: [String] = ["playable"]
var player: AVPlayer!                

asset.loadValuesAsynchronously(forKeys: keys, completionHandler: {
     var error: NSError? = nil
     let status = asset.statusOfValue(forKey: "playable", error: &error)
     switch status {
        case .loaded:
             DispatchQueue.main.async {
               let item = AVPlayerItem(asset: asset)
               self.player = AVPlayer(playerItem: item)
               let playerLayer = AVPlayerLayer(player: self.player)
               playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
               playerLayer.frame = self.YOUR_VIDEOS_UIVIEW.bounds
               self.YOUR_VIDEOS_UIVIEW.layer.addSublayer(playerLayer)
               self.player.isMuted = true
               self.player.play()
            }
            break
        case .failed:
             DispatchQueue.main.async {
                 //do something, show alert, put a placeholder image etc.
            }
            break
         case .cancelled:
            DispatchQueue.main.async {
                //do something, show alert, put a placeholder image etc.
            }
            break
         default:
            break
   }
})

ПРИМЕЧАНИЕ :

В зависимости от того, чего ваше приложение хочет достичь, вы все еще можетечтобы настроить плавность прокрутки в UITableView или UICollectionView, нужно немного поработать.Вам также может понадобиться ввести некоторое количество KVO в свойствах AVPlayerItem, чтобы оно работало, и здесь, в SO, есть множество постов, в которых подробно обсуждается AVPlayerItem KVO.


ДЛЯ ПЕРЕХОДА ЧЕРЕЗ АКТИВЫ(петли видео / GIF-файлы)

Чтобы зациклить видео, вы можете использовать тот же метод, что и выше, и ввести AVPlayerLooper.Вот пример кода для зацикливания видео (или, возможно, короткого видео в стиле GIF). Примечание использование ключа duration, которое требуется для нашего видео цикла.

let asset = AVAsset(url: URL(string: self.YOUR_URL_STRING))
let keys: [String] = ["playable","duration"]
var player: AVPlayer!
var playerLooper: AVPlayerLooper!

asset.loadValuesAsynchronously(forKeys: keys, completionHandler: {
     var error: NSError? = nil
     let status = asset.statusOfValue(forKey: "duration", error: &error)
     switch status {
        case .loaded:
             DispatchQueue.main.async {
                 let playerItem = AVPlayerItem(asset: asset)
                 self.player = AVQueuePlayer()
                 let playerLayer = AVPlayerLayer(player: self.player)

                 //define Timerange for the loop using asset.duration
                 let duration = playerItem.asset.duration
                 let start = CMTime(seconds: duration.seconds * 0, preferredTimescale: duration.timescale)
                 let end = CMTime(seconds: duration.seconds * 1, preferredTimescale: duration.timescale)
                 let timeRange = CMTimeRange(start: start, end: end)

                 self.playerLooper = AVPlayerLooper(player: self.player as! AVQueuePlayer, templateItem: playerItem, timeRange: timeRange)
                 playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
                 playerLayer.frame = self.YOUR_VIDEOS_UIVIEW.bounds
               self.YOUR_VIDEOS_UIVIEW.layer.addSublayer(playerLayer)
                 self.player.isMuted = true
                 self.player.play()
            }
            break
        case .failed:
             DispatchQueue.main.async {
                 //do something, show alert, put a placeholder image etc.
            }
            break
         case .cancelled:
            DispatchQueue.main.async {
                //do something, show alert, put a placeholder image etc.
            }
            break
         default:
            break
   }
})

РЕДАКТИРОВАНИЕ : согласно документации ,AVPlayerLooper требует, чтобы свойство duration ресурса было полностью загружено, чтобы иметь возможность циклически просматривать видео.Кроме того, timeRange: timeRange с начальным и конечным временным диапазоном в инициализации AVPlayerLooper действительно необязателен, если вы хотите бесконечный цикл.С того момента, как я опубликовал этот ответ, я также понял, что * * * * * * * * * * * * является точной только на 70-80% в цикле видео, особенно если вашему AVAsset нужно транслировать видео с URL.Чтобы решить эту проблему, существует совершенно другой (но простой) подход к циклу видео-

//this will loop the video since this is a Gif
  let interval = CMTime(value: 1, timescale: 2)
  self.timeObserverToken = self.player?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { (progressTime) in
                            if let totalDuration = self.player?.currentItem?.duration{
                                if progressTime == totalDuration{
                                    self.player?.seek(to: kCMTimeZero)
                                    self.player?.play()
                                }
                            }
                        })
5 голосов
/ 25 мая 2017

ответ Gigisommo для Swift 3 , включая комментарии из комментариев:

let asset = AVAsset(url: url)
let keys: [String] = ["playable"]

asset.loadValuesAsynchronously(forKeys: keys) { 

        DispatchQueue.main.async {

            let item = AVPlayerItem(asset: asset)
            self.playerCtrl.player = AVPlayer(playerItem: item)

        }

}
...