0
votes

Goal:
I'm ultimately trying to read an external video file (.mp4, .mpg...); within an instance of AVPlayerViewController.

But I'm having all sorts of KVO problems.

  1. AVPlayerViewController's KVO observer is always firing, even though I didn't register it as shown in the console here (source: see the observeForKeyPath().print(key path) in the code-list below):

playerController.status playerController.contentDimensions playerController.playingOnExternalScreen playerController.externalPlaybackType playerController.allowsExternalPlayback playerController.hasEnabledVideo playerController.hasEnabledAudio bounds videoScaled playerController.playingOnExternalScreen view.viewWindowState bounds playerController.playing

  1. My KVO observer ("timedMetadata") isn't registering. Apparently:

self.playerItem!.addObserver(self, forKeyPath: "timedMetadata", options: NSKeyValueObservingOptions.New, context: nil)

isn't working:
a) The 'observeValueForKeyPath()' isn't firing for this observer; and
b) I get the following runtime error (after immediately removing the observer as a test):

...reason: 'Cannot remove an observer... for the key path "timedMetadata" from ... because it is not registered as an observer.'

    class EditShowVideoViewController:AVPlayerViewController {

    var playerItem:AVPlayerItem?

    override func viewDidLoad() {

        self.view.hidden = true

        if let url = NSURL(string: gEditMediumTuple!.medium as! String) {
            let asset = AVURLAsset(URL: url)
            let requestedKeys = Array(arrayLiteral: "tracks", "playable")
            asset.loadValuesAsynchronouslyForKeys(requestedKeys, completionHandler: { ()  in
                // do something
                dispatch_async(dispatch_get_main_queue(), {
                    self.playerItem = AVPlayerItem(asset: asset)
                    self.playerItem!.addObserver(self, forKeyPath: "timedMetadata", options: NSKeyValueObservingOptions.New, context: nil)
                    self.removeObserver(self, forKeyPath:"timedMetadata") //...still runtime error.
                })
            })

        } else {
            showAlert(sender: self.parentViewController!, withTitle: "No Video", withMessage: "No video is found.", alertPurpose: .noVideo)
        }
    }

    override func observeValueForKeyPath(keyPath: String?,
        ofObject object: AnyObject?, change: [String : AnyObject]?,
        context: UnsafeMutablePointer<()>) {
            print(keyPath!)
            guard keyPath == "readyForDisplay" else {return}
            guard let obj = object as? AVPlayerViewController else {return}
            guard let ok = change?[NSKeyValueChangeNewKey] as? Bool else {return}
            guard ok else {return}
            dispatch_async(dispatch_get_main_queue(), {
                self.finishConstructingInterface(obj)
            })

            self.removeObserver(self, forKeyPath:"timedMetadata")
    }

    func finishConstructingInterface (vc:AVPlayerViewController) {
        self.removeObserver(self, forKeyPath:"readyForDisplay")
        self.view.hidden = false
    }

   // ...

}

All I want to do is to load a video URL and play it.

Any advice if short of a remedy is welcomed.

1

1 Answers

0
votes

The runtime error is because you're adding the observation to the player item, but then trying to remove it from self.

In the observer callback, if the key doesn't belong to your observation then you should be calling super. You don't know what you're breaking by not doing so.

Consider adding the video VC as a child instead of adding your class as a subclass. This will keep it logically separate and prevent mixup of the KVO callbacks.

Generally speaking, using KVO for code flow management will make your code a lot harder to follow, debug and maintain. It's better to use a different mechanism and save KVO purely for change tracking and response. Also, keep at least a Boolean flag of whether you're observing so you can tidy up appropriately. You can't allow your class to be deallocated and leave a hanging observation.