Reputation: 1550
(NOTE: I am running iOS 9 and am doing HTTP Live Streaming)
I have an AVPlayer
and want to show a loading indicator while the player is buffering. When the player starts playing with a slow connection KVO gets called for these properties:
isPlaybackBufferEmpty
isPlaybackLikelyToKeepUp
isPlaybackBufferFull
The problem is that these same properties do not get called again when buffering is done (when I say done I mean that the video is ok to play again.) My goal is to hide the loading indicator at the correct time but these don't get called again.
I searched online and found this radar: http://www.openradar.me/25931165 Not sure if it's 100% related
Any thoughts?
// MARK: - Key-Value Observing Method
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(AVPlayerItem.status) {
switch playerItem.status {
case .unknown:
break
case .readyToPlay:
player.play()
case .failed:
postPlaybackDidFailWithErrorNotification(error: error)
}
}
else if keyPath == #keyPath(AVPlayer.currentItem.isPlaybackBufferEmpty) {
guard let currentItem = player.currentItem else {
return
}
if currentItem.isPlaybackBufferEmpty {
print("isPlaybackBufferEmpty = YES")
} else {
print("isPlaybackBufferEmpty = NO")
}
}
else if keyPath == #keyPath(AVPlayer.currentItem.isPlaybackLikelyToKeepUp) {
guard let currentItem = player.currentItem else {
return
}
if currentItem.isPlaybackLikelyToKeepUp {
print("isPlaybackLikelyToKeepUp = YES")
//player.play()
} else {
print("isPlaybackLikelyToKeepUp = NO")
}
}
else if keyPath == #keyPath(AVPlayer.currentItem.isPlaybackBufferFull) {
guard let currentItem = player.currentItem else {
return
}
if currentItem.isPlaybackBufferFull {
print("isPlaybackBufferFull = YES")
//player.play()
} else {
print("isPlaybackBufferFull = NO")
}
}
else if keyPath == #keyPath(AVPlayer.currentItem) {
// Cleanup if needed.
if player.currentItem == nil {
video = nil
playerItem = nil
}
}
else if keyPath == #keyPath(AVPlayer.rate) {
updateMetadata()
NotificationCenter.default.post(name: AssetPlaybackManager.NotificationName.playerRateDidChangeNotification, object: nil)
}
else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
Upvotes: 5
Views: 11561
Reputation: 464
Here is the complete implementation
import SwiftUI
import AVKit
struct MyVideoPlayer:UIViewControllerRepresentable{
var player:AVPlayer
func makeUIViewController(context: Context) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = player
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
uiViewController.player = player
}
}
struct PlayerView: View {
@State private var player:AVPlayer = AVPlayer(url: URL(string: "https://embed-ssl.wistia.com/deliveries/cc8402e8c16cc8f36d3f63bd29eb82f99f4b5f88/accudvh5jy.mp4")!)
@State private var isLoading:Bool = true
var body: some View {
ZStack{
MyVideoPlayer(player:player).onAppear(){
player.play()
self.player.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main, using: { [self] time in
if self.player.timeControlStatus == .playing {
isLoading = false
debugPrint("#player - info: isPlaying")
} else if(player.currentItem?.isPlaybackLikelyToKeepUp == false ) {
isLoading = true
debugPrint("#player - info: isWaiting") //Buffering
}
})
}
.onDisappear(){
player.pause()
}
if(isLoading){
ProgressView().tint(.white)
}
}
.ignoresSafeArea(.all)
}
}
#Preview {
PlayerView()
}
Upvotes: 0
Reputation: 699
You can use timeControlStatus but it is available only above iOS 10.
According to apple's official documentation
A status that indicates whether playback is currently in progress, paused indefinitely, or suspended while waiting for appropriate network conditions
Add this observer to the player.
player.addObserver(self, forKeyPath: “timeControlStatus”, options: [.old, .new], context: nil)
Then,Observe the changes in
func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
method.Use below code inside above method
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "timeControlStatus", let change = change, let newValue = change[NSKeyValueChangeKey.newKey] as? Int, let oldValue = change[NSKeyValueChangeKey.oldKey] as? Int {
let oldStatus = AVPlayer.TimeControlStatus(rawValue: oldValue)
let newStatus = AVPlayer.TimeControlStatus(rawValue: newValue)
if newStatus != oldStatus {
DispatchQueue.main.async {[weak self] in
if newStatus == .playing || newStatus == .paused {
self?.loaderView.isHidden = true
} else {
self?.loaderView.isHidden = false
}
}
}
}
}
This is tested on iOS 11 above with swift 4 and It is working.
Upvotes: 12
Reputation: 1950
Use the below function for setting the progress as well as status of buffering for AVPlayer:
func addObserver() {
let intervel : CMTime = CMTimeMake(value: 10, timescale: 10)
observer = player?.addPeriodicTimeObserver(forInterval: intervel, queue: DispatchQueue.main) { [weak self] time in
guard let `self` = self,
let playerItem = self.player?.currentItem else { return }
let currentTime : Float64 = CMTimeGetSeconds(time)
let totalDuration = CMTimeGetSeconds(playerItem.asset.duration)
//this is the slider value update if you are using UISlider.
let sliderValue = (currentTime/totalDuration)
if currentTime >= totalDuration {
if let observer = self.observer{
//removing time observer
self.player?.removeTimeObserver(observer)
self.observer = nil
}
}
let playbackLikelyToKeepUp = self.player?.currentItem?.isPlaybackLikelyToKeepUp
if playbackLikelyToKeepUp == false{
print(self.player?.rate)
print("IsBuffering")
self.lock()
} else {
//stop the activity indicator
print("Buffering completed")
self.unlock()
}
}
}
Upvotes: 0