如何为 AVPlayer 重新加载数据,如果 URL 失败 swift
How to reload data for AVPlayer, if URL failed swift
我有 json 文件,其中包含几个 URL 文件(带 .mp3)的单词。 URL 中的一些无效(或有效,但 return 错误,所以我无论如何都得不到数据)。
这个URL我用来播放单词的发音。所以,我将执行 3 个步骤:
- 正在为某个词找到 URL。如果找不到,那什么也不会发生
- 使用此 URL 初始化 AVPlayerItem 并准备 AVPlayer。不仅仅是等待,当用户按下时。
- 当用户按下单词时播放声音
所以,首先,我正在准备我的 AVPlayer,以避免延迟播放。
我对多线程有点困惑,我不知道我应该在哪里检查我是否能够播放这个声音,我应该使用 next 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 文件,每个单词很少 URLs
"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检查一下。如果失败,则再取一个并检查...等,同时 URLs 结束,或者找到一些有效的 URL。
所有这些都必须在 AVPlayer 尝试播放声音之前完成。
如何实施以及在哪里实施?
请用简单的语言讲述和描述分辨率,因为我是 swift 和多线程方面的初学者。
我会使用 AVPlayerItem.Status
属性 查看它何时失败。在您当前的代码中,您在创建项目后立即检查状态,这将始终产生与初始化时相同的结果 AVPlayerItem
status
默认为 unknown
.
一旦您与玩家关联,AVPlayerItem
就会加入队列。为了能够跟踪您想要设置观察者的状态变化。
文档 https://developer.apple.com/documentation/avfoundation/avplayeritem 仍然建议 "old-style" 使用 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()
}
}
解决方案:
private var player: AVPlayer? {
didSet{
if player?.currentItem?.status == .failed {
if indexOfURL >= 0 {
prepareForPronunciation(indexOfURL)
}
}
}
}
如果URL结束,设置indexOfURL等于-1,否则递增它以在下一个函数调用
中使用下一个URL
我有 json 文件,其中包含几个 URL 文件(带 .mp3)的单词。 URL 中的一些无效(或有效,但 return 错误,所以我无论如何都得不到数据)。
这个URL我用来播放单词的发音。所以,我将执行 3 个步骤:
- 正在为某个词找到 URL。如果找不到,那什么也不会发生
- 使用此 URL 初始化 AVPlayerItem 并准备 AVPlayer。不仅仅是等待,当用户按下时。
- 当用户按下单词时播放声音
所以,首先,我正在准备我的 AVPlayer,以避免延迟播放。
我对多线程有点困惑,我不知道我应该在哪里检查我是否能够播放这个声音,我应该使用 next 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 文件,每个单词很少 URLs
"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检查一下。如果失败,则再取一个并检查...等,同时 URLs 结束,或者找到一些有效的 URL。 所有这些都必须在 AVPlayer 尝试播放声音之前完成。
如何实施以及在哪里实施?
请用简单的语言讲述和描述分辨率,因为我是 swift 和多线程方面的初学者。
我会使用 AVPlayerItem.Status
属性 查看它何时失败。在您当前的代码中,您在创建项目后立即检查状态,这将始终产生与初始化时相同的结果 AVPlayerItem
status
默认为 unknown
.
一旦您与玩家关联,AVPlayerItem
就会加入队列。为了能够跟踪您想要设置观察者的状态变化。
文档 https://developer.apple.com/documentation/avfoundation/avplayeritem 仍然建议 "old-style" 使用 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()
}
}
解决方案:
private var player: AVPlayer? {
didSet{
if player?.currentItem?.status == .failed {
if indexOfURL >= 0 {
prepareForPronunciation(indexOfURL)
}
}
}
}
如果URL结束,设置indexOfURL等于-1,否则递增它以在下一个函数调用
中使用下一个URL