0

I have an AVQuePlayer that is downloading videos a user posts to firebase and then playing them in order via an AVQuePlayer. Everything works fine until the video que plays all vidoes, in which case, if you try and replay the que, a SIGABRT 1 error occurs. I believe this is due to the fact that it is looking for videos to play but none are present. I have already tried to reset the playerItem array after the que finishes but it has come with no luck. Here is what I have so far.

Loads data from Firebase:

func loadStory() {
    DataService.ds.REF_POSTS.observe(.value, with: { (snapshot) in
        self.posts = []
        if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
            for snap in snapshot {
                if let dict = snap.value as? Dictionary<String, Any> {
                    let key = snap.key
                    let post = HotPosts(postID: key, postData: dict)
                    self.posts.append(post)
                    let media = post.mediaURL
                    let ref = Storage.storage().reference(forURL: media)
                    ref.downloadURL(completion: { (url, error) in
                        if error != nil {
                            print(error!)
                        } else {
                            self.videos.append(url!)
                        }
                    })

                }
            }
        }
    })
}

Downloads the urls:

 func playStory() {


    for vid in videos{


        asset = AVAsset(url: vid)
        let assetKeys = ["playable", "hasProtectedContent"]

        let item = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: assetKeys)

        item.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: [.old, .new], context: &myContext)

        self.playerItem.append(item)

    }

    player = AVQueuePlayer(items: playerItem)
    print(playerItem.count)
    let playerLayer = AVPlayerLayer(player: player)
    playerLayer.frame = self.view.bounds
    self.view.layer.addSublayer(playerLayer)


  }

Handles buffering and plays video when ready:

 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    print("playing1")
    guard context == &myContext else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }
    print("playing2")
    if keyPath == #keyPath(AVPlayerItem.status) {
        let status: AVPlayerItemStatus
        print("playing3")
        if let statusNumber = change?[.newKey] as? NSNumber {
            status = AVPlayerItemStatus(rawValue: statusNumber.intValue)!
        } else {
            status = .unknown
        }

        switch status {
        case .readyToPlay:
            print("We ARE IN THE CLEAR BOIIIIII")
            player.play()
            break

        case .failed:
            print("ITem Fialed")
            break

        case .unknown:
            print("Could Not play the video")
            break

        }

    }
}
Chris
  • 387
  • 5
  • 18

2 Answers2

0

Try subscribing to this notification and see if it works correctly for your situation:

NotificationCenter.default.addObserver(self, selector: #selector(endOfAudio), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)

I also found this in a piece of Apple code, which illustrates the use of some KVO on avQueuePlayer ( https://developer.apple.com/library/content/samplecode/avloopplayer/Listings/Projects_VideoLooper_VideoLooper_QueuePlayerLooper_swift.html )

/*
 Copyright (C) 2016 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information

 Abstract:
 An object that uses AVQueuePlayer to loop a video.
 */

 // MARK: Convenience

    private func startObserving() {
        guard let player = player, !isObserving else { return }

        player.addObserver(self, forKeyPath: ObserverContexts.playerStatusKey, options: .new, context: &ObserverContexts.playerStatus)
        player.addObserver(self, forKeyPath: ObserverContexts.currentItemKey, options: .old, context: &ObserverContexts.currentItem)
        player.addObserver(self, forKeyPath: ObserverContexts.currentItemStatusKey, options: .new, context: &ObserverContexts.currentItemStatus)

        isObserving = true
    }

    private func stopObserving() {
        guard let player = player, isObserving else { return }

        player.removeObserver(self, forKeyPath: ObserverContexts.playerStatusKey, context: &ObserverContexts.playerStatus)
        player.removeObserver(self, forKeyPath: ObserverContexts.currentItemKey, context: &ObserverContexts.currentItem)
        player.removeObserver(self, forKeyPath: ObserverContexts.currentItemStatusKey, context: &ObserverContexts.currentItemStatus)

        isObserving = false
    }

    // MARK: KVO

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &ObserverContexts.playerStatus {
            guard let newPlayerStatus = change?[.newKey] as? AVPlayerStatus else { return }

            if newPlayerStatus == AVPlayerStatus.failed {
                print("End looping since player has failed with error: \(player?.error)")
                stop()
            }
        }
        else if context == &ObserverContexts.currentItem {
            guard let player = player else { return }

            if player.items().isEmpty {
                print("Play queue emptied out due to bad player item. End looping")
                stop()
            }
            else {
                // If `loopCount` has been set, check if looping needs to stop.
                if numberOfTimesToPlay > 0 {
                    numberOfTimesPlayed = numberOfTimesPlayed + 1

                    if numberOfTimesPlayed >= numberOfTimesToPlay {
                        print("Looped \(numberOfTimesToPlay) times. Stopping.");
                        stop()
                    }
                }

                /*
                 Append the previous current item to the player's queue. An initial
                 change from a nil currentItem yields NSNull here. Check to make
                 sure the class is AVPlayerItem before appending it to the end
                 of the queue.
                 */
                if let itemRemoved = change?[.oldKey] as? AVPlayerItem {
                    itemRemoved.seek(to: kCMTimeZero)

                    stopObserving()
                    player.insert(itemRemoved, after: nil)
                    startObserving()
                }
            }
        }
        else if context == &ObserverContexts.currentItemStatus {
            guard let newPlayerItemStatus = change?[.newKey] as? AVPlayerItemStatus else { return }

            if newPlayerItemStatus == .failed {
                print("End looping since player item has failed with error: \(player?.currentItem?.error)")
                stop()
            }
        }
        else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
}
solenoid
  • 954
  • 1
  • 9
  • 20
  • Ive tried implementing this , however, it no longer Asynchronously plays the videos – Chris Aug 07 '17 at 05:36
  • I should note that I was able to get the looping to work by creating an array of class VideoPlayers (My name for the queplayer apple code) and parsing my urls from firebase and creating individual VideoPlayer objects. Then on the press of a button I iterated through the array and played each video. However the problem now lies with buffering. Ill keep updatated when I find a reasonable solution. – Chris Aug 07 '17 at 06:04
0

The problem with AVQueuePlayer is that it removes the AVPlayerItems that have finished playing. An option might be to create a copy of your AVQueuePlayer when your QueuePlayer is completely done playing, then you can just play your copy and make a new copy to play again once that one also finishes.

You could also maintain all your AVPlayerItems in an array and load up a new AVQueuePlayer whenever it's needed.

There is a project on github that might also be helpful, which extends AVQueuePlayer and maintains previous playerItems that have been played on your Queue Player. It can be found through: https://github.com/dgiovann/AVQueuePlayerPrevious

Alan
  • 1,132
  • 7
  • 15
  • Ive been considering the possibility of using a UICollectionView as an alternative. Do you think this could be a better avenue to explore – Chris Aug 07 '17 at 21:58
  • I don't fully understand what you mean by using a UICollectionView. Could you elaborate? Personally, I would use the Array. Having to make a copy everytime you start playing is more tedious and the array will constantly have the player Items saved. You can just initialize the AVQueuePlayer with the array itself. – Alan Aug 08 '17 at 07:29
  • Yea, I was just trying to explore possbile options. I think the Current plan is still the best one for the mannar – Chris Aug 08 '17 at 19:05