In swift, how to detect touch while playing video in AVPlayerViewController - ios

I have programmatically added an AVPlayerViewController to a UIViewController. I am able to receive the notification when the player is finished playing (playerDidFinishPlaying). I would also like to know if a user has touched the screen while the video is playing and I have not found any related notifications.

The solution is to create a Base class of AVPlayerViewController and override touches​Began(_:​with:​) method:
Swift 2:
Custom Base Class:
class CustomAVPlayerViewController: AVPlayerViewController {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
print("touchesBegan")
}
}
ViewController:
let videoURL = NSURL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(URL: videoURL!)
let playerViewController = CustomAVPlayerViewController()
playerViewController.player = player
self.presentViewController(playerViewController, animated: true) {
playerViewController.player!.play()
}
Swift 3:
Custom Base Class:
class CustomAVPlayerViewController: AVPlayerViewController {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touchesBegan")
}
}
View Controller:
let videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(url: videoURL!)
let playerViewController = CustomAVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
Don't forget to import AVKit and import AVFoundation.
Each time you tap on the playerViewController, "touchesBegan" will be printed.

I had this same problem. The contentOverlayView is only available in tvos, so that was not an option.
I ended up adding a UIView over the UIImageView that I added the AVPlayer to. I set the background color to clear on the UIView, so it's not visible, but can receive gestures. This provides a target for the tap gesture recognizer.

I resolve the same. Subclassing AVPlayerViewController will work on iOS 11.4.1 but not on iOS 12 an above. So the solution for this is add subview on playerviewcontroller contentoverlayview and then on that subview you can add any gesture or button for detecting the touch. Here is the code snippet for the same::
// This notification is added for continuous playing of the video you can remove this in case you need video is played only once.
private func playVideo() {
guard let path = Bundle.main.path(forResource: "BG01", ofType:"mp4") else {
debugPrint("video.m4v not found")
return
}
self.player = AVPlayer(url: URL(fileURLWithPath: path))
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem, queue: .main) { [weak self] _ in
self?.player?.seek(to: kCMTimeZero)
self?.player?.play()
}
let playerController : AVPlayerViewController? = AVPlayerViewController()
let btn : UIButton = UIButton()
btn.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
btn.addTarget(self, action: #selector(touchDetect), for: .touchUpInside)
btn.backgroundColor = UIColor.clear
playerController?.contentOverlayView?.addSubview(btn)
// playerController?.homeVCProtocolDelegate = self as HomeVCProtocol
playerController?.player = player
playerController?.showsPlaybackControls = false
self.player?.play()
present(playerController!, animated: false) {
self.player?.play()
}
}
#objc func touchDetect()
{
// Here you will get the call
}

Related

problems while playing video from s3 using AVplayer

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.

Can't swipe the UIScrollView when AVPlayer is running

Hi everyone I am trying to swipe between two pages through a scrollview and it works fine but when I try to swipe while a video is playing it won't work here is my code for my ViewControllers
MainVeiwController
override func viewDidLoad() {
super.viewDidLoad()
scrollView.isPagingEnabled=true
let settings = SettingsViewController(nibName: "SettingsViewController", bundle: nil)
self.addChildViewController(settings)
self.scrollView.addSubview(settings.view)
settings.didMove(toParentViewController: self)
let diamond = DiamondViewController(nibName: "DiamondViewController", bundle: nil)
var frame1 = diamond.view.frame
frame1.origin.x = self.view.frame.size.width
diamond.view.frame=frame1
self.addChildViewController(diamond)
self.scrollView.addSubview(diamond.view)
diamond.didMove(toParentViewController: self)
self.scrollView.contentSize = CGSize(width: self.view.frame.size.width * 2, height: self.view.frame.size.height-70)
scrollView.contentOffset = CGPoint(x: scrollView.frame.size.width, y: 0)
}
Second ViewController
var playerViewController = AVPlayerViewController()
var playerView = AVPlayer()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
let fileURL = NSURL(fileURLWithPath: "PATHTOVIDEO")
playerView=AVPlayer(url: fileURL as URL)
playerViewController.showsPlaybackControls=false;
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerView.currentItem, queue: nil)
{ notification in
let t1 = CMTimeMake(1, 100);
self.playerView.seek(to: t1)
self.playerView.play()
}
playerViewController.player = playerView
self.present(playerViewController,animated:true){
self.playerViewController.player?.play()
}
}
Now what I want to know is how can I swipe between the pages even though the video is playing is there a workaround or something
Try to embed your AVPlayerViewController in your SecondViewController instead of presenting it.
Code Example:
let url = // whatever
let player = AVPlayer(URL:url)
let av = AVPlayerViewController()
av.player = player
av.view.frame = // whatever
self.addChildViewController(av)
self.view.addSubview(av.view)
av.didMoveToParentViewController(self)
Code from:
AVPlayer with playback controls of avplayerviewcontroller

MPMovieController won't dismiss on Done button

My movie file starts no problem. The done button does not dismiss the video content. No idea why? Also, Fast Forward and Rewind buttons just cause a black screen. I don't think I am using the notification functions correctly?
import Foundation
import UIKit
import MediaPlayer
class VideoViewController: UIViewController {
var moviePlayer:MPMoviePlayerController!
#IBAction func videoLaunch(sender: AnyObject) {
playVideo()
}
func playVideo() {
let path = NSBundle.mainBundle().pathForResource("MyVideo", ofType:"mp4")
let url = NSURL.fileURLWithPath(path!)
moviePlayer = MPMoviePlayerController(contentURL: url)
if let player = moviePlayer {
player.view.frame = self.view.bounds
moviePlayer?.controlStyle = MPMovieControlStyle.Fullscreen
player.prepareToPlay()
self.view.addSubview(player.view)
}
}
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "moviePlayBackDidFinish:",
name: MPMoviePlayerPlaybackDidFinishNotification,
object: moviePlayer)
func moviePlayBackDidFinish(notification: NSNotification){
self.view.removeFromSuperview()
}
}
}
You are adding player view as subview. You should remove it (removeFromSuperview) after done button pressed. Use notifications to listen for playback finish:
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "moviePlayBackDidFinish:",
name: MPMoviePlayerPlaybackDidFinishNotification,
object: moviePlayer)
and moviePlayBackDidFinish:
func moviePlayBackDidFinish(notification: NSNotification){
// remove from superview
}
You should remove moviePlayer from your superview like this:
func moviePlayBackDidFinish(notification: NSNotification){
let moviePlayer:MPMoviePlayerController = notif.object as! MPMoviePlayerController
moviePlayer.view.removeFromSuperview()
}
Because in your case you remove self.view

removeFromSuperview not working - Swift

Please see below code. I am trying to remove the video subview from view when the 'done' button is pressed or video stops playing. I show no errors in the code but the removeFromSubview method does not seem to be working. I am not sure if my syntax is wrong or if it is something to do with having the movieplayer code within the IBAction method and the moviePlayBackDidFinish outside below the viewDidLoad. Any advise much appreciated. Thanks
import Foundation
import UIKit
import MediaPlayer
class VideoViewController: UIViewController {
var moviePlayer:MPMoviePlayerController!
#IBAction func videoLaunch(sender: AnyObject) {
playVideo()
}
func playVideo() {
let path = NSBundle.mainBundle().pathForResource("MyVideo", ofType:"mp4")
let url = NSURL.fileURLWithPath(path!)
moviePlayer = MPMoviePlayerController(contentURL: url)
if let player = moviePlayer {
player.view.frame = self.view.bounds
moviePlayer?.controlStyle = MPMovieControlStyle.Fullscreen
player.prepareToPlay()
self.view.addSubview(player.view)
}
}
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "moviePlayBackDidFinish:",
name: MPMoviePlayerPlaybackDidFinishNotification,
object: moviePlayer)
func moviePlayBackDidFinish(notification: NSNotification){
self.view.removeFromSuperview()
}
}
}
You are trying to remove self.view not moviePlayer.view.
Change your moviePlayBackDidFinish code to:
func moviePlayBackDidFinish(notification: NSNotification)
{
if let player = moviePlayer
{
player.view.removeFromSuperview()
}
}

Draw button on top of AVPlayer

I have to draw a label or button on top of video relay next previous , leave comment . List of video have it, once user select one item from the table,it need to play, Once player play finished, those buttons or label should come on top of video
Here is my code :
comPlayerControl = AVPlayerViewController()
if let player = comPlayerControl {
let videoURL: String = "http://cdnapi.kaltura.com/p/11/sp/11/playManifest/entryId/"+selectedSubmission.transcodeRefId+"/format/applehttp/protocol/http/a.m3u8"
let playerItem = AVPlayerItem(URL: NSURL(string: videoURL)! )
commmentPlayer = AVPlayer(playerItem: playerItem)
player.player = commmentPlayer
player.view.frame = videoCell.frame
player.view.sizeToFit()
player.showsPlaybackControls = true
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(CommentsTableViewController.playerDidFinishPlaying(_:)),
name: AVPlayerItemDidPlayToEndTimeNotification,
object: playerItem
)
comPlayerControl.delegate = self
videoCell.addSubview(player.view)
}
func playerDidFinishPlaying(note: NSNotification) {
print("Video Finished")
let DynamicView=UIView(frame: CGRectMake(100, 200, 100, 100))
DynamicView.backgroundColor=UIColor.greenColor()
DynamicView.layer.cornerRadius=25
DynamicView.layer.borderWidth=2
DynamicView.layer.zPosition = 1;
comPlayerControl.view.addSubview(DynamicView)
}
requirement like this
You're using an AVPlayerViewController, so there's no reason to access your application's window like in Alessandro Ornano's answer. Why reinvent the wheel? Every AVPlayerViewController has a contentOverlayView property which allows you to place views between the player and the controls.
First, create a new AVPlayerItem and listen for the AVPlayerItemDidPlayToEndTimeNotification notification on that item. Load the item into your player and begin playback.
Once the item completes, the selector your specified to listen for the AVPlayerItemDidPlayToEndTimeNotification notification will be called. In that selector, access the contentOverlayView directly and add your buttons:
In some view controller or other object:
let playerVC = AVPlayerViewController()
// ...
func setupPlayer {
let playerItem = AVPlayerItem(...)
playerVC.player?.replaceCurrentItemWithPlayerItem(playerItem)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(VC.itemFinished), name: AVPlayerItemDidPlayToEndTimeNotification, object: playerItem)
self.presentViewController(playerVC, animated: true) {
self.playerVC.player?.play()
}
}
func itemFinished() {
let btn = UIButton(type: .System)
btn.addTarget(self, action: #selector(VC.buttonTapped), forControlEvents: .TouchUpInside)
self.playerVC.contentOverlayView?.addSubview(btn)
}
func buttonTapped() {
print("button was tapped")
// replay/comment logic here
}
As stated in the comments (and a rejected edit), buttons may not work in the contentOverlayView. For an alternate solution, see Pyro's answer.
You could also subclass AVPlayerViewController and do everything inside an instance of your subclass, but Apple warns against that:
Do not subclass AVPlayerViewController. Overriding this class’s methods is unsupported and results in undefined behavior.
I think the best way to make the avplayer buttons is explained here: IOS 8 Video Playback using AVPlayer and AVPlayerViewController .
So , I prefeer and agree with these instructions, but if you still want to
add these buttons you can try to add them to the self.window
if let app = UIApplication.sharedApplication().delegate as? AppDelegate, let window = app.window {
let myFirstButton = UIButton()
myFirstButton.setTitle("test", forState: .Normal)
window.addSubview(myFirstButton)
...
}
I recommend looking up AVPlayerLayer as a way to show buttons and other content on top of your video.
See the Advances in AVFoundation Playback WWDC presentation.
Also, check out the AVFoundationSimplePlayer-iOS example project.
Essentially, you create a view to host your player, and you make the layer behind the view into an AVPlayerLayer.
class PlayerView: UIView {
var player: AVPlayer? {
get {
return playerLayer.player
}
set {
playerLayer.player = newValue
}
}
var playerLayer: AVPlayerLayer {
return layer as! AVPlayerLayer
}
override class var layerClass: AnyClass {
return AVPlayerLayer.self
}
}
Based on the your question and from the comment/code of Ramis i have made a sample code which you may try
As mentioned by JAL the contentOverlayView should be the best option to display the control over the video in the AVPlayerController, but as per my sample demo the contentOverlayView don't have any user interaction for the buttons or other controls, as if you check in the 3D view of the AVPlayerController it has AVTouchIgnoringView/UIView in front of the contentOverlayView which may be problem in user interaction with contentOverlayView.
So another solution is to add the overlay view in the AVPlayerViewController
func addContentOverlayView() {
OverlayView.frame = CGRectMake(0,30,AVPlayerVC.view.bounds.width, 100)
OverlayView.hidden = true
OverlayView.backgroundColor = UIColor ( red: 0.5, green: 0.5, blue: 0.5, alpha: 0.379 )
let btnNext = UIButton(frame:CGRectMake(AVPlayerVC.view.bounds.width - 60,0,60,44))
btnNext.setTitle(">>", forState:.Normal)
btnNext.addTarget(self, action:"playNext", forControlEvents:.TouchUpInside)
// btnNext.layer.borderColor = UIColor ( red: 0.0, green: 0.0, blue: 1.0, alpha: 0.670476140202703 ).CGColor
// btnNext.layer.borderWidth = 1.0
OverlayView.addSubview(btnNext)
let btnReplay = UIButton(frame:CGRectMake((AVPlayerVC.view.bounds.width/2)-40,0,80,44))
btnReplay.setTitle("Replay", forState:.Normal)
btnReplay.addTarget(self, action:"replayVideo", forControlEvents:.TouchUpInside)
OverlayView.addSubview(btnReplay)
let btnPrevious = UIButton(frame:CGRectMake(0,0,80,44))
btnPrevious.setTitle("<<", forState:.Normal)
btnPrevious.addTarget(self, action:"previousVideo", forControlEvents:.TouchUpInside)
OverlayView.addSubview(btnPrevious)
let btnComment = UIButton(frame:CGRectMake((AVPlayerVC.view.bounds.width/2)-70,40,140,44))
btnComment.setTitle("Comments", forState:.Normal)
btnComment.addTarget(self, action:"openComments", forControlEvents:.TouchUpInside)
OverlayView.addSubview(btnComment)
AVPlayerVC.view.addSubview(OverlayView);
}
func playNext() {
prevItem = AVPlayerVC.player?.currentItem
OverlayView.hidden = true
commmentQueuePlayer.advanceToNextItem()
}
func replayVideo() {
OverlayView.hidden = true
AVPlayerVC.player?.currentItem?.seekToTime(kCMTimeZero)
AVPlayerVC.player?.play()
}
func previousVideo() {
OverlayView.hidden = true
if prevItem != AVPlayerVC.player?.currentItem {
if (commmentQueuePlayer.canInsertItem(prevItem!, afterItem:AVPlayerVC.player?.currentItem)) {
//commmentQueuePlayer.insertItem(prevItem!, afterItem:AVPlayerVC.player?.currentItem)
commmentQueuePlayer.replaceCurrentItemWithPlayerItem(prevItem)
prevItem = AVPlayerVC.player?.currentItem
replayVideo()
}
} else {
replayVideo()
//Else display alert no prev video found
}
}
func stopedPlaying() {
if prevItem == nil {
prevItem = AVPlayerVC.player?.currentItem
}
OverlayView.hidden = false
}
At the initial setup we set the AVPlayerController,AVQueuePlayer etc... at that time we can add the overlay on the AVPlayerController
For the previous item there is no direct available and as per documentation the item will be remove once it's next item is played , so we have two option like replaceCurrentItemWithPlayerItem or insertItem(item: AVPlayerItem, afterItem: AVPlayerItem?)
If you need to check complete code you can check it from :
https://gist.github.com/Pyrolr/debb4fca8f608b1300e099a5b3547031
Note: This is just like prototype, it is not working perfectly in all the cases but it can help you in understanding the basic functionality you wnat and you can improve/optimise based on your requirements
Check Below code, I am able to add the button on overlay as well as button event is being called.
var playerViewController: AVPlayerViewController?
var OverlayView = UIView()
private lazy var button1: UIButton = {
let selfType = type(of: self)
let button = UIButton(frame: .init(origin: .zero, size: .init(width: selfType.musicWidth, height: selfType.musicHeight)))
button.setImage(UIImage(named: "star"), for: .normal)
button.addTarget(self, action: #selector(button1DidSelect), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: selfType.musicWidth),
button.heightAnchor.constraint(equalToConstant: selfType.musicHeight)
])
return button
}()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
guard let url = URL(string: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4") else { return }
let player = AVPlayer(url: url)
self.playerViewController = AVPlayerViewController()
guard let playerViewController = self.playerViewController else { return }
playerViewController.player = player
present(playerViewController, animated: true) {
playerViewController.player?.play()
}
self.addButtonsOnOverlayView()
self.playerViewController?.showsPlaybackControls = false
self.isHideOverlayControls = false
}
#objc private func button1DidSelect() {
print("button1 Selected")
}
private func addButtonsOnOverlayView() {
guard let overlayView = self.playerViewController?.contentOverlayView else { return }
if !self. button1.isDescendant(of: overlayView) {
overlayView.addSubview(self.musicFirstButton)
self.playerViewController?.showsPlaybackControls = true
}
NSLayoutConstraint.activate([
button1.leadingAnchor.constraint(equalTo: overlayView.leadingAnchor, constant: 20),
button1.topAnchor.constraint(equalTo: overlayView.topAnchor, constant: 20),
])
}

Resources