3

I'm having a problem which I haven't seen a post like it on here. I have an AVPlayerViewController which plays a video-based off the path from my Firebase Database (not Storage). The video plays perfectly as I want it to, but only once I watch a video that is clicked on in the UIImagePickerController elsewhere in the app.

So for example, the AVPlayer will show a black background (this occurs with all of the AVPlayer's in the app), except for when I watch a video from the UIImagePickerController which has nothing to do with any of the other views. I have no clue where to start with this. I appreciate all your help and suggestions!

Here is the example code of my AVPlayerViewController:

import UIKit
import AVFoundation
import AVKit

class VideoView: UIViewController {

private var videoURL: URL
var player: AVPlayer?
var playerController : AVPlayerViewController?

init(videoURL: URL) {
    self.videoURL = videoURL
    super.init(nibName: nil, bundle: nil)
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}


override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = UIColor.gray
    player = AVPlayer(url: videoURL)
    playerController = AVPlayerViewController()

    guard player != nil && playerController != nil else {
        return
    }
    playerController!.showsPlaybackControls = false

    playerController!.player = player!
    self.addChild(playerController!)
    self.view.addSubview(playerController!.view)
    playerController!.view.frame = view.frame
    NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.player!.currentItem)

    let cancelButton = UIButton(frame: CGRect(x: 10.0, y: 10.0, width: 30.0, height: 30.0))
    cancelButton.setImage(#imageLiteral(resourceName: "cancel"), for: UIControl.State())
    cancelButton.addTarget(self, action: #selector(cancel), for: .touchUpInside)
    view.addSubview(cancelButton)


    // Allow background audio to continue to play
    do {
        try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient)
    } catch let error as NSError {
        print(error)
    }

    do {
        try AVAudioSession.sharedInstance().setActive(true)
    } catch let error as NSError {
        print(error)
    }
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    player?.play()
}

@objc func cancel() {
    dismiss(animated: true, completion: nil)
}

@objc fileprivate func playerItemDidReachEnd(_ notification: Notification) {
    if self.player != nil {
        self.player!.seek(to: CMTime.zero)
        self.player!.play()
    }
}

}
a.masri
  • 2,439
  • 1
  • 14
  • 32
Jaqueline
  • 465
  • 2
  • 8
  • 25
  • So your problem is only `AVPlayer will show a black background` when try to load video path from firebase storage. Otherwise the player is playing well pick a video from UIImagePickerViewController/ I think I'm a right? – Ram Mar 17 '20 at 06:43
  • @Ram the video path works but only plays after elsewhere in the app I choose a video from UIImagePickerViewController. So the video I choose in the picker is completely un related to the one I want to watch, but it’s like I have to watch the picker one (I can click cancel after starting it and everything works) in order to make the AVPlayer work. Does that make sense? It’s very odd. – Jaqueline Mar 17 '20 at 15:17
  • @Ram yes so the AVPlayer shows a black screen, but once I pick a video from UIImagePickerViewController, then go back to my AVPlayer, it’s no longer a black screen and shows the video I wanted to show (not related to the one chosen in then image picker). It’s as if picking a video in the picker starts or loads the AVPlayer and allows it to show the video, even though the path is the same for the video I want to watch. Does that make more sense? – Jaqueline Mar 21 '20 at 06:51
  • @Jaqueline can you add video or gif, we need to understand the problem more? – a.masri Mar 29 '20 at 07:40

2 Answers2

0

Did you check the size of child controller view?

playerController!.view.frame = view.frame 

Put it into

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        playerController!.view.frame = view.frame
    }

or use constraints.

isHidden
  • 860
  • 5
  • 16
0

The problem is that when your view appears it might not be ready for playback. What you should do to properly handle this is to KVO on the player to make sure it is ready to play.

I have answered this before here for a similar question:

Video playback issues only on iOS 13 with AVPlayerViewController and AVPlayer when using HLS video

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


override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?) {

    // Only handle observations for the playerItemContext
    guard context == &playerItemContext else {
        super.observeValue(forKeyPath: keyPath,
                           of: object,
                           change: change,
                           context: context)
        return
    }

    if keyPath == #keyPath(AVPlayerItem.status) {
        let status: AVPlayerItemStatus
        if let statusNumber = change?[.newKey] as? NSNumber {
            status = AVPlayerItemStatus(rawValue: statusNumber.intValue)!
        } else {
            status = .unknown
        }

        // Switch over status value
        switch status {
        case .readyToPlay:
            // Player item is ready to play.
        case .failed:
            // Player item failed. See error.
        case .unknown:
            // Player item is not yet ready.
        }
    }
}

You can find documentation about it from Apple here: https://developer.apple.com/documentation/avfoundation/media_assets_playback_and_editing/responding_to_playback_state_changes

Another example of usage is here:

        playerItem?.observe(\AVPlayerItem.status, options: [.new, .initial]) { [weak self] item, _ in
            guard let self = self else { return }
            switch status {
        case .readyToPlay:
            // Player item is ready to play.
        case .failed:
            // Player item failed. See error.
        case .unknown:
            // Player item is not yet ready.
        }
Lunarchaos42
  • 253
  • 4
  • 13