I have a navigation view from a list of video urls to a destination Videoview and then to a video player. The player uses just the AVPlayer (I don't need controls) to automatically play the video when I navigate to it.
However the video does not stop playing when I hit the 'back' button and navigate away from the PlayerView.
Any thoughts on how to fix this are appreciated.
I've included code from the Navigation view, the pass thru VideoView and the PlayerView as context. TIA.
Navigation view
NavigationView {
List(messages, id: \.self) { message in
NavigationLink(destination: VideoView(videoURL: URL(string:"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4")!, previewLength: 15)) {
Text(message)
}
}.navigationBarTitle("Answers")
VideoView code
struct VideoView: UIViewRepresentable {
var videoURL:URL
var previewLength:Double?
func makeUIView(context: Context) -> UIView {
print("making VideoView")
return PlayerView(frame: .zero, url: videoURL, previewLength: previewLength ?? 30)
}
func updateUIView(_ uiView: UIView, context: Context) {
}
Player Snippet
class PlayerView: UIView {
private let playerLayer = AVPlayerLayer()
private var previewTimer:Timer?
var previewLength:Double
init(frame: CGRect, url: URL, previewLength:Double) {
self.previewLength = previewLength
super.init(frame: frame)
// Create the video player using the URL passed in.
let player = AVPlayer(url: url)
player.volume = 2 // Will play audio if you don't set to zero
// This is the AVPlayer approach to playing video with no controls
player.play() // Set to play once created.
// Add the player to our Player Layer
playerLayer.player = player
playerLayer.videoGravity = .resizeAspectFill // Resizes content to fill whole video layer.
playerLayer.backgroundColor = UIColor.black.cgColor
previewTimer = Timer.scheduledTimer(withTimeInterval: previewLength, repeats: false, block: { (timer) in
player.seek(to: CMTime(seconds: 0, preferredTimescale: CMTimeScale(1)))
})
layer.addSublayer(playerLayer)
Here is possible approach - to use presentation mode on update, because in this scenario it is updated due to navigation back.
struct VideoView: UIViewRepresentable {
#Environment(\.presentationMode) var presentation // << here !!
var videoURL:URL
var previewLength:Double?
func makeUIView(context: Context) -> UIView {
print("making VideoView")
return PlayerView(frame: .zero, url: videoURL, previewLength: previewLength ?? 30)
}
func updateUIView(_ playerView: PlayerView, context: Context) {
// update might be called several times, so PlayerView should
// be safe for repeated calls
if presentation.wrappedValue.isPresented {
playerView.play()
} else {
playerView.stop()
}
}
}
and PlayerView should be updated to have access to player
class PlayerView: UIView {
private let playerLayer = AVPlayerLayer()
private var previewTimer:Timer?
var previewLength:Double
private var player: AVPlayer // << make property
init(frame: CGRect, url: URL, previewLength:Double) {
self.previewLength = previewLength
super.init(frame: frame)
// Create the video player using the URL passed in.
player = AVPlayer(url: url)
player.volume = 2 // Will play audio if you don't set to zero
// don't run .play() here !!!
// ... other code
}
func play() {
player.rate = 1.0
}
func stop() {
player.rate = 0.0
}
}
Related
I have already successfully hidden the playback like this controller.showsPlaybackControls = false, but this does not actually disable the controls. I can still drag around in my View to change the Video playback.
Is the an easy way to disable these controls?
I have this Custom Video Player:
struct CustomVideoPlayer: UIViewControllerRepresentable {
var player: AVPlayer
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
func makeUIViewController(context: Context) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
controller.videoGravity = .resizeAspectFill
player.actionAtItemEnd = .none
NotificationCenter.default.addObserver(context.coordinator, selector: #selector(context.coordinator.restartPlayback), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
}
class Coordinator: NSObject {
var parent: CustomVideoPlayer
init(parent: CustomVideoPlayer) {
self.parent = parent
}
#objc func restartPlayback() {
parent.player.seek(to: .zero)
}
}
}
This might not be the right way to do it, but for now I got a working solution. I just added .allowsHitTesting(false) on my VideoPlayer.
I have a video that plays inside a CALayer. Everything works fine but when I go to the Debug View Hierarchy, the CALayer that the video plays in, always shows up blank.
Why can't I see the video frame that is visible on the CALayer inside the Debug View Hierarchy?
class AVPlayerView: UIView {
var playerLayer: CALayer?
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
playerLayer?.frame = self.bounds
}
}
lazy var avPlayerView: AVPlayerView = {
let playerView = AVPlayerView()
playerView.tag = 2
return playerView
}()
func setPlayerAndPlayerLayer(url: URL) {
let asset = AVAsset(url: url)
playerItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: playerItem)
contentView.addSubview(avPlayerView)
avPlayerView.frame = self.bounds
playerLayer.frame = avPlayerView.bounds
avPlayerView.layer.addSublayer(playerLayer!)
avPlayerView.playerLayer = playerLayer
avPlayerView.layoutIfNeeded()
}
resizeAspect as the video gravity only works properly for me, when using an iPhone X.
For some reasons, the black aspect bar gets only added to the top and not to the bottom. This is how it looks like when I'm not using an iPhone X (the image is white)
This is how it should look like:
As you can see, on the iPhone X, everything looks clean and balanced as expected.
This is how I play the video:
avPlayerLayer = AVPlayerLayer(player: avPlayer)
avPlayerLayer.frame = PreviewLayer.bounds
avPlayerLayer.videoGravity = .resizeAspect //Will automatically add black bars
PreviewLayer.layer.insertSublayer(avPlayerLayer, at: 0)
let playerItem = AVPlayerItem(url: video)
avPlayer?.replaceCurrentItem(with: playerItem)
avPlayer?.play()
Try this steps
1- Check bottom constraint connect with safe are
2- Try this code
import UIKit
import AVKit
class ViewController: UIViewController {
#IBOutlet weak var playerView: UIView!
private var player: AVPlayer!
override func viewDidLoad() {
super.viewDidLoad()
playVideo(from: "Intro.mp4")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func playVideo(from file:String) {
let file = file.components(separatedBy: ".")
guard let path = Bundle.main.path(forResource: file[0], ofType:file[1]) else {
debugPrint( "\(file.joined(separator: ".")) not found")
return
}
player = AVPlayer(url: URL(fileURLWithPath: path))
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: .main) { _ in
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
let playerLayer = AVPlayerLayer(player: player)
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspect
playerLayer.frame = playerView.layer.bounds
playerView.layer.insertSublayer(playerLayer, at: 0)
player.play()
}
}
Result on iphone X
Result on iphone 7
Note : if you need video full view on all devices try to link Bottom
constraint and Top constraint with superview not with safe area
Make your videoLayer View a subview of:
class VideoContainerView: UIView {
var playerLayer: CALayer?
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
playerLayer?.frame = self.bounds
}
}
and add:
self.VideoShareLayer.playerLayer = avPlayerLayer;
before inserting the layer.
I am trying to display a video inside a cell. Instead, it just comes up as a black box. It is possible it is not initializing the player. I do, however see the black box so I know it is initializing the VideoPlayerView. When I print the frame of the playerLayer it prints (0.0, 0.0, 0.0, 0.0), possibly due to how I am initializing the view. Is there any way to extend the playerlayer to the edges of the view without using frame?
class VideoPlayerView : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.black
let videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(url: videoURL!)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.bounds
//print(playerLayer.frame)
self.layer.addSublayer(playerLayer)
player.play()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Thanks!
I've tried this a couple of different ways and ended up using a custom UIView class approach (like you did) but a few differences. For example, your class could look like this:
class PlayerView: UIView {
var player: AVPlayer? {
get {
return playerLayer.player
}
set {
playerLayer.player = newValue
}
}
var playerLayer: AVPlayerLayer {
return layer as! AVPlayerLayer
}
// Override UIView property
override static var layerClass: AnyClass {
return AVPlayerLayer.self
}
}
Then, you add a UIView in IB, set the class to PlayerView (instead of default UIView), add your constraints like normal and drag an outlet to your controller like any other control. To use it, you do the following:
playerView.player = AVPlayer(url: url)
playerView.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
Then trigger the Play command however your application needs it.
You could create this view dynamically using the same method but I tend to use IB most of the time.
Hope this helps!
You can set constraints to playerLayer, for example:
let width = UIScreen.main.bounds.width
let height = 300
let playerLayer = AVPlayerLayer(player: player)
playerLayer.translatesAutoresizingMaskIntoConstraints = false
playerLayer.widthAnchor.constraint(equalToConstant: width).isActive = true
playerLayer.heightAnchor.constraint(equalToConstant: height).isActive = true
I figured out that all you need to do is add this code:
Turns out when you resize it doesn't automatically resize layer sizes.
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
playerLayer?.frame = self.bounds
}
Hi all i am trying to play a video from s3 using Avplayer. Now if i play the video, the video starts playback after the whole video is buffered. so I added player.automaticallyWaitsToMinimizeStalling = false, but now the video automatically pauses
import UIKit
import AVFoundation
import AVKit
class ViewController: UIViewController {
var player: AVPlayer!
var item : AVPlayerItem!
override func viewDidLoad() {
super.viewDidLoad()
item = AVPlayerItem(url: URL(string: "https://cent-churchconnect.s3-ap-southeast-2.amazonaws.com/cent-churchconnect/testAdmin/eb8cc8b5-80e0-468a-a2c9-979cf1b5ac76_toystory.mp4")!)
player = AVPlayer(playerItem: item)
let controller = AVPlayerViewController()
present(controller, animated: true) { _ in }
controller.player = player
addChildViewController(controller)
view.addSubview(controller.view)
controller.view.frame = CGRect(x: 0, y: 50, width: self.view.frame.size.width, height: 300)
controller.player = player
controller.showsPlaybackControls = true
if #available(iOS 10.0, *) {
player.automaticallyWaitsToMinimizeStalling = false
player.play()
} else {
// Fallback on earlier versions
}
}
}
I had the same issue below steps worked for me,
Try to use method func playImmediately(atRate:) and make sure the property player.automaticallyWaitsToMinimizeStalling = false is set properly.
Use method func playImmediately(atRate:) instead of func play()
I used the cocoa pod https://github.com/piemonte/Player.
You can use the delegate methods to control the playback.