Reputation: 485
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()
}
}
}
Upvotes: 3
Views: 939
Reputation: 201
Here's a solution using Combine's Publisher instead of the addObserver
approach (in SwiftUI):
https://stackoverflow.com/a/78948869/5209040
Upvotes: 0
Reputation: 253
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.
}
Upvotes: 0
Reputation: 860
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.
Upvotes: 0