Как перезагрузить данные для AVPlayer, если URL не удался swift - PullRequest
0 голосов
/ 18 ноября 2018

У меня есть файл JSON с несколькими URL-адресами (с .mp3) для слов. Некоторые из URL недействительны (или допустимы, но возвращают ошибку, поэтому я все равно не получаю данные).

Этот URL я использую для воспроизведения произношения слова. Итак, я иду бросить 3 шага:

  1. Поиск URL для определенного слова. Если не можете найти, то ничего не происходит
  2. Используйте этот URL для инициализации AVPlayerItem и подготовки AVPlayer. Чем просто ждать, когда пользователь нажмет.
  3. Воспроизведение звука, когда пользователь нажимает на слово

Итак, во-первых, я готовлю свой AVPlayer, чтобы избежать задержки в игре.

Я немного запутался с многопоточностью, и я не понимаю, где я должен проверить, могу ли я воспроизвести этот звук, или нет, и я должен использовать следующий URL.

Код:

extension WordCell {

func playPronunciation() {
    player?.play()
    player?.seek(to: .zero)
}

func prepareForPronunciation() {
    if let word = myLabel.text {
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
        DispatchQueue.global(qos: .userInteractive).async { [weak self] in
            let foundURL = self?.findURL(for: word)
            if let url = foundURL {
                let playerItem = AVPlayerItem(url: url)

                //here "playerItem.status" always equals .unknown (cause not initialized yet)
                if playerItem.status == .failed {
                     //self?.giveNextUrl() - will do some code there
                }
                self?.player = AVPlayer(playerItem: playerItem)
                self?.player!.volume = 1.0
            }
            // this is also not correct, because networking continueing
            // but i don't know where to place it
            DispatchQueue.main.async {
                UIApplication.shared.isNetworkActivityIndicatorVisible = false
            }
        }
    }
}

// right now i take URL from file, where there is only one.
// but i will use file with a few URL for one word
private func findURL(for word: String) -> URL? {

    if let path = Bundle.main.path(forResource: "data", ofType: "json") {
        do {
            let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
            let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
            if let jsonResult = jsonResult as? [String: String] {
                if let url = jsonResult[word] {
                    return URL(string: url)
                } else {
                    return nil
                }
            }
        } catch {
            return nil
        }
    }
    return nil
}

}

Это файл JSON с несколькими URL на слово

"abel": [
    "http://static.sfdict.com/staticrep/dictaudio/A00/A0015900.mp3",
    "http://img2.tfd.com/pron/mp3/en/US/d5/d5djdgdyslht.mp3",
    "http://img2.tfd.com/pron/mp3/en/UK/d5/d5djdgdyslht.mp3",
    "http://www.yourdictionary.com/audio/a/ab/abel.mp3"
],
"abele": [
    "http://www.yourdictionary.com/audio/a/ab/abele.mp3",
    "http://static.sfdict.com/staticrep/dictaudio/A00/A0016300.mp3",
    "http://www.oxforddictionaries.com/media/english/uk_pron/a/abe/abele/abele__gb_2_8.mp3",
    "http://s3.amazonaws.com/audio.vocabulary.com/1.0/us/A/1B3JGI7ALNB2K.mp3",
    "http://www.oxforddictionaries.com/media/english/uk_pron/a/abe/abele/abele__gb_1_8.mp3"
],

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

Как это реализовать и где?

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

Ответы [ 2 ]

0 голосов
/ 23 ноября 2018

Решение:

 private var player: AVPlayer? {
    didSet{
        if player?.currentItem?.status == .failed {
            if indexOfURL >= 0 {
                prepareForPronunciation(indexOfURL)
            }
        }
    }
}

Если URL-адреса завершены, установите indexOfURL равным -1, в противном случае увеличьте его, чтобы использовать следующий URL-адрес в следующем вызове функции

0 голосов
/ 23 ноября 2018

Я бы использовал свойство AVPlayerItem.Status, чтобы увидеть, когда оно не удалось.В вашем текущем коде вы проверяете статус сразу после создания элемента, который всегда будет давать тот же результат, что и при инициализации AVPlayerItem status по умолчанию unknown.

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

В документации https://developer.apple.com/documentation/avfoundation/avplayeritem все еще предлагается «старый стиль» с использованием addObserver, но в зависимости от ваших предпочтений я бы выбралболее новый блочный стиль.

// make sure to keep a strong reference to the observer (e.g. in your controller) otherwise the observer will be de-initialised and no changes updates will occur
var observerStatus: NSKeyValueObservation?


// in your method where you setup your player item
observerStatus = playerItem.observe(\.status, changeHandler: { (item, value) in
    debugPrint("status: \(item.status.rawValue)")
    if item.status == .failed {
        // enqueue new asset with diff url
    }
})

Вы также можете настроить аналогичного наблюдателя на экземпляре AVPlayer.


Обновлено с полным примером -этот код далек от совершенства, но демонстрирует пользу наблюдателя

import UIKit
import AVFoundation

class ViewController: UIViewController {
    var observerStatus: NSKeyValueObservation?
    var currentTrack = -1
    let urls = [
        "https://sample-videos.com/audio/mp3/crowd-cheerin.mp3", // "https://sample-videos.com/audio/mp3/crowd-cheering.mp3"
        "https://sample-videos.com/audio/mp3/wave.mp3"
    ]
    var player: AVPlayer? {
        didSet {
            guard let p = player else { return debugPrint("no player") }
            debugPrint("status: \(p.currentItem?.status == .unknown)") // this is set before status changes from unknown
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        nextTrack()
    }

    func nextTrack() {
        currentTrack += 1
        guard let url = URL(string: urls[currentTrack]) else { return }
        let item = AVPlayerItem(url: url)

        observerStatus = item.observe(\.status, changeHandler: { [weak self] (item, value) in
            switch item.status {
            case .unknown:
                debugPrint("status: unknown")
            case .readyToPlay:
                debugPrint("status: ready to play")
            case .failed:
                debugPrint("playback failed")
                self?.nextTrack()
            }
        })

        if player == nil {
            player = AVPlayer(playerItem: item)
        } else {
            player?.replaceCurrentItem(with: item)
        }
        player?.play()
    }
}
...