AVPlayer get rid of black bars/background at top & bottom (Swift) - ios

Quick question, does anybody know how i can get rid of the black bars at the top and bottom of my video? I just started using AVPlayer and i'm just removing codes here and there in attempt to remove the black layers. Appreciate those who can help me, Thanks!
UIViewController
import UIKit
import AVKit
import AVFoundation
private var looper: AVPlayerLooper?
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let path = Bundle.main.path(forResource: "Innie-Kiss", ofType:"mp4")
let player = AVQueuePlayer(url: URL(fileURLWithPath: path!))
looper = AVPlayerLooper(player: player, templateItem: AVPlayerItem(asset: AVAsset(url: URL(fileURLWithPath: path!))))
let controller = AVPlayerViewController()
controller.player = player
let screenSize = UIScreen.main.bounds.size
let videoFrame = CGRect(x: 0, y: 200, width: screenSize.width, height: (screenSize.height - 130) / 2)
controller.view.frame = videoFrame
self.view.addSubview(controller.view)
player.play()
}
}

You need to set the videoGravity of the AVLayer. The default value is .resizeAspect which will preserve the aspect ratio with black bars on top/bottom or left/right. What you want is .resize
The AVLayer you need to set the videoGravity on is the layer property of your AVPlayer.
see this for more info: https://developer.apple.com/documentation/avfoundation/avplayerlayer/1388915-videogravity

#import AVKit
var playerController = AVPlayerViewController()
playerController.videoGravity = .resizeAspect
/*playerController.videoGravity = .resizeAspectFill*/
playerController.view.backgroundColor = UIColor.white //set color as per super or custom view.
Note: If you set the videoGravity of the AVPlayerViewController. The default value is .resizeAspect then it is possible to visibility of black color(default) in background if you wants this color would similar to your super view's color then you must set superview's or custom view's color to your playercontroller's view background color.
*Example:Suppose super view's or custom view's background color is white then playerController view's background color must be set as playerController.view.backgroundColor = UIColor.white *
Note :playerController's backgroundColor should not be set as UIColor.white ,always. It must me set as super view' background color.
If you set playerController.videoGravity = .resizeAspectFill then it will fill the player content to super view or custom view, here also no black color would be shown. So choose either .resizeAspect or resizeAspectFill as per your requirements.

Related

Presenting UIView on AVPlayerViewController Fullscreen View

Problem: I need to add a UIView on top of an AVPlayer in a way that it is still user-interactive. When I try to add it as Overlay Content, any user interactive button will not fire anymore (as it is behind the video control layer).
Desired effect: I'd like to be able to add an UIView on top of an AVPlayer so that it is kept on top of the whole view at all times even when it enters fullscreen mode.
Sample Code:
var view = UIView = {
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
view.backgroundColor = .red
return view
}
var player = AVPlayer(url: http://somewhere.com/video.format)
let controller = AVPlayerViewController()
controller.player = player
Apparently, the best way to solve this issue was just adding a touchable element into the AVPlayerController, which redirected any touch event to the respective selector.

AVPlayerLayer not sizing correctly

I have a custom UITableViewCell that displays either an image or a video. If there's a video, it initially downloads and displays the thumbnail from that video while the video is loading and preparing to play.
It all works, the problem is that I get the ratio of the thumbnail, store it in the database and when I download the thumbnail, it makes the height based on that ratio. It works great for images. But the issue is that the AVPlayerLayer frame doesn't cover the entire thumbnail and it looks like this:
Here's how I do it:
func updateView() {
if let videoUrlString = post?.videoUrl, let videoUrl = URL(string: videoUrlString) {
volumeView.isHidden = false
player = AVPlayer(url: videoUrl)
playerLayer = AVPlayerLayer(player: player)
playerLayer!.frame = postImageView.frame // thumbnail image
playerLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill
contentView.layer.addSublayer(playerLayer!)
self.volumeView.layer.zPosition = 1
layoutIfNeeded()
player?.play()
player?.isMuted = videoIsMuted
}
if let ratio = post?.ratio {
photoHeightConstraint.constant = UIScreen.main.bounds.width / ratio
layoutIfNeeded()
}
}
I'm not doing something right apparently or I'm missing something based on my final result. Any clue?
UPDATE:
I notice that if you scroll the cells slowly, the video is being displayed according to the size of the thumbnail. But if you scroll fast, it mismatches the frame. I assume it has something to do with reusability or it simply cannot catch up with the frame? Here's my code for prepareForReuse() method:
override func prepareForReuse() {
super.prepareForReuse()
profileImageView.image = UIImage(named: "placeholder")
postImageView.image = UIImage(named: "profile_placeholder")
volumeView.isHidden = true
if let p = player, let pLayer = playerLayer {
p.pause()
pLayer.removeFromSuperlayer()
}
}
The solution turned out to be rather simple. Basically each post contains either post or a video, but either way, it always contains an image. I also set the ratio of that image (the width devided by the height). So, all I had to do is set the player's width to the screen width devided by the ratio and it gives your the proper frame.
playerLayer.frame.size.height = UIScreen.main.bounds.width / postRatio

ios - replacing an image with a video player dynamically

My app (Swift 3, with a UIStoryboard) shows a list of episodes from a show. Whenever the user clicks on an episode from the table, he/she gets a detail view which shows (top to bottom):
the episode's image
the title
a description of the episode and technical information
Depending on the user type, instead of showing the episode's image, we would like to have a video player so people who are subscribed can just start watching the video (unsubscribed users would just see the image).
Based on the subscribed status of our users, a video player should be playing the content (subscribed) or showing an image (unsubscribed).
Is there a way to dynamically remove the image and replace it with the video player in the exact same spot and dimensions? And if so, what would happen with the Auto Layout contraints currently applied to the image?
Place a UIView on top of the UIImageView in storyboard. Align it to all 4 edges of UIImageView. And then hide and show them according to requirement by setting isHidden property.
Then, Insert the AVPlayer in the UIView through code.
Steps
The following solution works for both UIStoryboard and UI in code.
In InterfaceBuilder drag a UIView of preferred size onto the canvas, add your auto layout constraints and create an outlet (called "containerView")
Configure an AVPlayer with a URL or file, initialize an AVPlayerLayer instance with the player and add the layer to the containerView's layer hierarchy
Add an UIImageView on top of the container view
Based on your condition, show / hide / stop / play your movie and image
Quick code example
import UIKit
import AVFoundation
class ViewController: UIViewController {
// MARK: - Properties
#IBOutlet var containerView: UIView!
private var imageView: UIImageView!
private var isUserSubscribed = true
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Video
let url = URL(string: "https://content.jwplatform.com/manifests/vM7nH0Kl.m3u8")!
let avPlayer = AVPlayer(playerItem: AVPlayerItem(url: url))
let avPlayerLayer = AVPlayerLayer(player: avPlayer)
avPlayerLayer.frame = containerView.bounds
containerView.layer.insertSublayer(avPlayerLayer, at: 0)
// Image
imageView = UIImageView(image: UIImage(named: "someImage"))
imageView.frame = containerView.frame
containerView.addSubview(imageView)
// Condition
if isUserSubscribed {
imageView.isHidden = true
avPlayer.play()
} else {
imageView.isHidden = false
}
}
}
Result
Further options
With this approach you need to add your own playback controls, which basically boils down to adding buttons with actions, triggering AVPlayer.play(), AVPlayer.stop() etc.

AVPlayerViewController inside a view is not showing the playback controls

I am working with apple tv. I have a UIView inside my UIViewController. I am adding AVPlayerViewController as subview of my UIView. UIView's frame is CGRect(x: 50, y: 50, width: 1344, height: 756). I am setting AVPlayerViewController frame equals to my UIView's frame.
Issue is that video plays fine in its frame but the controls like pause, play, forward etc. are hidden and not working. But when I set frame as CGRect(x: 0, y: 0, width: 1920, height: 1080), controls are visible and working.
My code is as per below:
let url = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let item = AVPlayerItem(url: url!)
self.player = AVPlayer(playerItem: item)
self.avplayerController = AVPlayerViewController()
self.avplayerController.player = self.player
self.avplayerController.view.frame = videoPreviewLayer.frame
self.avplayerController.showsPlaybackControls = true
self.avplayerController.requiresLinearPlayback = true
self.addChildViewController(self.avplayerController)
self.view.addSubview(self.avplayerController.view)
self.avpController.player?.play()
Please help.
I managed to solve this using this code:
let player = AVPlayer(url: self.videoUrl!)
let avPlayerController = AVPlayerViewController()
avPlayerController.player = player;
avPlayerController.view.frame = self.videoPlayView.bounds;
self.addChildViewController(avPlayerController)
self.videoPlayView.addSubview(avPlayerController.view);
Now AVPlayerViewController shows most playback controls even as child to a uiview.
Courtesy:
https://www.techotopia.com/index.php/IOS_8_Video_Playback_using_AVPlayer_and_AVPlayerViewController
This is an expected behaviour:
AVPlayerViewController is designed such that when in full screen, the
full playback experience (scrubbing, info panel access, etc) are all
available. When in a space less than full screen, the assumption is
that it is just one of several interactive elements on screen, and
thus the view should not absorb all touch surface events as transport
control.
You can read more about this on this thread: https://forums.developer.apple.com/thread/19526

AVPlayerLayer frame doesn't fit into UIView for video player

I'm trying to display a video with this code:
self.videoPlayer = AVPlayer.init(url: videoUrl as URL)
let playerLayer = AVPlayerLayer.init(player: self.videoPlayer)
self.videoPlayerView.layer.addSublayer(playerLayer)
playerLayer.frame = self.videoPlayerView.layer.bounds
self.videoPlayer.play()
but it does not appear inside of the UIView (i.e. self.videoPlayerView). It just shows a thin Rect at the middle of the screen as shown in the screenshot. How do I fix this?
screen shot
try this...
set the layer's frame to equal the view's frame
self.videoPlayer = AVPlayer.init(url: videoUrl as URL)
let playerLayer = AVPlayerLayer.init(player: self.videoPlayer)
self.videoPlayerView.layer.addSublayer(playerLayer)
playerLayer.frame = self.videoPlayerView.frame
self.videoPlayer.play()
Firstly the code is in Swift not Obective-C, I would change that tag! Secondly, how do you define the videoPlayerView frame? Thirdly, you change the frame of the playerLayer after it has already been added. Thus the frame will not change visually! Set the frame before you add it as a subLayer. Hope that helps.

Resources