here で説明されているように、この問題にも遭遇しました
以下でこの回答を複数回テストしましたが、これまでのところ毎回機能していました。
@wallaceの回答のSwift 5バージョンで思いついたのは次のとおりです。
1- keyPath"playbackLikelyToKeepUp"
を観察する代わりに を使用し、.AVPlayerItemPlaybackStalled Notification
その内部でバッファがいっぱいかどうかを確認しますif !playerItem.isPlaybackLikelyToKeepUp {...}
2-彼を使用する代わりに、PlayerHangingNotification
という名前の関数を使用しますplayerIsHanging()
3-彼を使用する代わりに、PlayerContinueNotification
私はという名前の関数を使用しますcheckPlayerTryCount()
4-そして、内部では、何も起こらない場合を除いcheckPlayerTryCount()
て、彼の機能と同じことをすべて行います. それを避けるために、ステートメントの上に2行のコードを追加しました(void)playerContinue
} else if playerTryCount == 0 {
return
5- @wallace のコメントの下で提案された @PranoyC のように、を の代わりにplayerTryCount
最大値に設定しました。クラスプロパティとしても設定します20
10
let playerTryCountMaxLimit = 20
コメントがそうするように示唆している場所で、アクティビティインジケーター/スピナーを追加/削除する必要があります
コード:
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemPlaybackStalled(_:)),
name: NSNotification.Name.AVPlayerItemPlaybackStalled,
object: playerItem)
@objc func playerItemPlaybackStalled(_ notification: Notification) {
// The system may post this notification on a thread other than the one used to registered the observer: https://developer.apple.com/documentation/foundation/nsnotification/name/1387661-avplayeritemplaybackstalled
guard let playerItem = notification.object as? AVPlayerItem else { return }
// playerItem.isPlaybackLikelyToKeepUp == false && if the player's current time is greater than zero && the player's current time is not equal to the player's duration
if (!playerItem.isPlaybackLikelyToKeepUp) && (CMTimeCompare(playerItem.currentTime(), .zero) == 1) && (CMTimeCompare(playerItem.currentTime(), playerItem.duration) != 0) {
DispatchQueue.main.async { [weak self] in
self?.playerIsHanging()
}
}
}
var playerTryCount = -1 // this should get set to 0 when the AVPlayer starts playing
let playerTryCountMaxLimit = 20
func playerIsHanging() {
if playerTryCount <= playerTryCountMaxLimit {
playerTryCount += 1
// show spinner
checkPlayerTryCount()
} else {
// show spinner, show alert, or possibly use player?.replaceCurrentItem(with: playerItem) to start over ***BE SURE TO RESET playerTryCount = 0 ***
print("1.-----> PROBLEM")
}
}
func checkPlayerTryCount() {
guard let player = player, let playerItem = player.currentItem else { return }
// if the player's current time is equal to the player's duration
if CMTimeCompare(playerItem.currentTime(), playerItem.duration) == 0 {
// show spinner or better yet remove spinner and show a replayButton or auto rewind to the beginning ***BE SURE TO RESET playerTryCount = 0 ***
} else if playerTryCount > playerTryCountMaxLimit {
// show spinner, show alert, or possibly use player?.replaceCurrentItem(with: playerItem) to start over ***BE SURE TO RESET playerTryCount = 0 ***
print("2.-----> PROBLEM")
} else if playerTryCount == 0 {
// *** in his answer he has nothing but a return statement here but when it would hit this condition nothing would happen. I had to add these 2 lines of code for it to continue ***
playerTryCount += 1
retryCheckPlayerTryCountAgain()
return // protects against a race condition
} else if playerItem.isPlaybackLikelyToKeepUp {
// remove spinner and reset playerTryCount to zero
playerTryCount = 0
player?.play()
} else { // still hanging, not at end
playerTryCount += 1
/*
create a 0.5-second delay using .asyncAfter to see if buffering catches up
then call retryCheckPlayerTryCountAgain() in a manner that attempts to avoid any recursion or threading nightmares
*/
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
DispatchQueue.main.async { [weak self] in
// test playerTryCount again to protect against changes that might have happened during the 0.5 second delay
if self!.playerTryCount > 0 {
if self!.playerTryCount <= self!.playerTryCountMaxLimit {
self!.retryCheckPlayerTryCountAgain()
} else {
// show spinner, show alert, or possibly use player?.replaceCurrentItem(with: playerItem) to start over ***BE SURE TO RESET playerTryCount = 0 ***
print("3.-----> PROBLEM")
}
}
}
}
}
}
func retryCheckPlayerTryCountAgain() {
checkPlayerTryCount()
}