Using VideoPlayer in swift project - ios

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
}

Related

Borders using AVPlayer in SwiftUI

I've been playing around with SwiftUI since a few days and I am currently trying to implement a video player, I am struggling with a size issue.
This is my view code:
struct test: View {
var body: some View {
PlayerView()
.aspectRatio(contentMode: .fit)
.background(Color.blue)
}
}
This is what's inside PlayerView()
class PlayerUIView: UIView {
private let playerLayer = AVPlayerLayer()
override init(frame: CGRect) {
super.init(frame: frame)
let url = URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8")!
let player = AVPlayer(url: url)
player.isMuted = true
player.play()
playerLayer.player = player
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspect
layer.addSublayer(playerLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
}
Using this code I obtain this:
There is this extra border (that we can see in blue) that I want to get rid of and I don't know how to do it.
I've tried to set the playerLayer's videoGravity property to :
resize: I don't have the border anymore but the video is stretched
resizeAspectFill: I don't have the border anymore but the video is cut
How can I get rid of those borders while preserving the original ratio and size provided by the video?
I managed to make it work this way:
View Code
struct test: View {
var body: some View {
PlayerView()
.edgesIgnoringSafeArea(.all)
.scaledToFill()
}
}
PlayerUIView.swift:
class PlayerUIView: UIView {
private let playerLayer = AVPlayerLayer()
override init(frame: CGRect) {
super.init(frame: frame)
let url = URL(string: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8")!
let player = AVPlayer(url: url)
player.isMuted = true
player.play()
playerLayer.player = player
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspect
layer.addSublayer(playerLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
}

Swift -Why aren't CALayer images visible inside the Debug View Hierarchy

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()
}

How to deactivate AVPlayer in Swift when you navigate away from view

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
}
}

AVPlayer resizeAspect works only properly on iPhone X

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.

AVPlayer layer not showing video content sometimes

AVPlayer layer not showing video content sometimes but plays audio.
This happens some times not each time
Here is my lines of code :
override func viewDidLoad() {
super.viewDidLoad()
self.tempVideoPath = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("tmpMov.mov")
self.player = AVPlayer(url: self.tempVideoPath!)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.playerLayer = AVPlayerLayer(player: self.player)
self.playerLayer?.frame = self.videoPlayer.bounds
self.playerLayer?.backgroundColor = UIColor.yellow.cgColor
self.videoPlayer.layer.addSublayer(playerLayer!)
print("player = \(playerLayer?.bounds)")
}
Help me to solve this issue.
the problem with you code is that the bounds of videoPlayer can change.
So you need to add the following code to your view controller to change AVPlayerLayer's frame accordingly:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
playerLayer?.frame = view.bounds
}
Hope it helps.
Although the videoplayer has been initialized correctly or created from storyboard, it seems that the layer is non-existent and creates this annoying problem.
At the beginning of your viewDidAppear first, try to create the layer , than add to it>
let layer = CALayer()
self.videoPlayer.layer = layer
//self.playerView.wantsLayer = true // this line is only for osx

Resources