How to detect when AVPlayer starts/stops playing in swift - swift

I have an AVPlayer inside a scrollView and for some reason when the AVPlayer plays the scrollview jumps to the top causing the video to not be visible on the screen as the video is torward the bottom of the scrollView. So inorder to stop the scrollView from jumping I want to disable and enable scrolling when the user starts/stops the video. Not sure if that will solve my problem but I'm hoping someone knows the right way to add an observer or notification for the player so I can test it out.
This is the code I have inside my ViewController that contains some static text and the AVPlayer.
Code Below:
let path = NSBundle.mainBundle().pathForResource("carbon_video", ofType:"mp4")
let url = NSURL.fileURLWithPath(path!)
var player = AVPlayerViewController()
var avPlayer = AVPlayer(URL: url)
player.player = avPlayer
self.addChildViewController(player)
player.view.translatesAutoresizingMaskIntoConstraints = false
player.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.New, context: nil)
player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.New, context: nil)

I think this can help you
func playerItemDidReachEnd(notification: NSNotification) {
let p: AVPlayerItem = notification.object as! AVPlayerItem
p.seekToTime(kCMTimeZero)
}
override func viewDidAppear(animated: Bool) {
avPlayer.play()
paused = false
// Disable scrolling
}
override func viewDidDisappear(animated: Bool) {
avPlayer.pause()
paused = true
// Enable scrolling
}

Related

Pre-load videos and cache them - swift

I have collectionView and each cell has a video displayed.
When a cell is tapped I present a ViewController that plays the video.
The code that does this in the ViewController is as follows:
fileprivate func configureVideo(with moment : Moment){
guard let urlString = moment.videoUrl else { return }
guard let videoUrl = URL(string: urlString) else { return }
self.player = AVPlayer(url: videoUrl)
dispatchGroup.enter()
self.player!.addObserver(self, forKeyPath: #keyPath(player.currentItem.isPlaybackLikelyToKeepUp),
options: .new, context: &playbackLikelyToKeepUpContext)
NotificationCenter.default.addObserver(self, selector: #selector(videoReachedEnd), name: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem)
self.playerLayer = AVPlayerLayer(player: player)
self.view.layer.insertSublayer(playerLayer!, at: 0)
self.playerLayer?.frame = self.view.bounds
self.player?.play()
}
The fact that the video is being loaded when the controller is presented makes the process of video loading longer than I would like it to.
I would like that the videos are being loaded before, when I am scrolling through the cells of the collectionView, so that when I tap on a cell and the controller is presented, the video is already ready to play.
But I don't know what would be the right approach for it.
If you have any suggestions I am willing to listen.

Call Controller of an autoplayed video in ViewController

I have set it up so that when a ViewController is displayed, a video starts automatically and at the end it switches to a different ViewController.
The problem is that if the app is put in the background while viewing it, the video freezes and you have to restart the application.
I thought about setting the classic pause / play controllers to appear when you press the screen so you can continue watching, but I don't know how to do that.
Or do you have another solution to prevent the video from freezing?
import UIKit
import AVKit
import AVFoundation
class View8BaController: UIViewController {
func setupAVPlayer() {
let videoURL = Bundle.main.url(forResource: "8B-A", withExtension: "mp4") // Get video url
let avAssets = AVAsset(url: videoURL!) // Create assets to get duration of video.
let avPlayer = AVPlayer(url: videoURL!) // Create avPlayer instance
let avPlayerLayer = AVPlayerLayer(player: avPlayer) // Create avPlayerLayer instance
avPlayerLayer.frame = self.view.bounds // Set bounds of avPlayerLayer
self.view.layer.addSublayer(avPlayerLayer) // Add avPlayerLayer to view's layer.
avPlayer.play() // Play video
// Add observer for every second to check video completed or not,
// If video play is completed then redirect to desire view controller.
avPlayer.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: 1) , queue: .main) { [weak self] time in
if time == avAssets.duration {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SCENA7") as! SCENA7ViewController
self?.navigationController?.pushViewController(vc, animated: false)
}
}
}
//------------------------------------------------------------------------------
override func viewDidLoad() {
super.viewDidLoad()
}
//------------------------------------------------------------------------------
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.setupAVPlayer() // Call method to setup AVPlayer & AVPlayerLayer to play video
}
}
have you tried telling the video to play in the sceneDelegate?
add to the class, right above func setupAVPlayer(),
var avPlayer: AvPlayer!
then in your sceneDelegate outside any functions
let view8Ba = View8BaController()
to create an instance of the view controller. Then you can access the properties in the following:
func sceneWillEnterForeground(_ scene: UIScene) {
if view8Ba.viewIfLoaded?.window != nil {
view8Ba.avPlayer.play()
}
}
This will tell your video to start playing again when the app comes back from the background.
If you want to add a play/pause when you tap the screen you can add a tap gesture recognizer and another view to the current view controller and set the background to clear (in storyboard drag the new view to the white bar on top of the view controller)
then call
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
pauseScreenView.frame = View8BaController.bounds
}
This tells the new view you added where to be on the viewController.
In the IBAction of the tap gesture recognizer
#IBAction func screenTapped(_ sender: Any) {
View8BaController.addSubview(pauseScreenView)
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
self.pauseScreenView.removeFromSuperview()
}
}
This adds the new view to the top of the viewController and then removes it after 10 seconds.
In that new view you can add a button that will play/pause your video
#IBAction func pauseVideo(_ sender: UIButton) {
if avPlayer.timeControlStatus == .playing {
avPlayer.pause()
pauseButton.setImage(playImage, for: .normal)
}else {
avPlayer.play()
pauseButton.setImage(pauseImage, for: .normal)
}
}

How can I show same video as a background in all View Controllers without any interruption during the pages transition?

There are 2 pages(ViewController) in Main.storyboard: HomeViewController and DetailViewController
I gave a storyboard ID for the DetailViewController:DetailPage
I have added a button into first page to take user to the second page.
I also have added a back button into second page to take user to the first page back.
I need to show a video in the background in all pages.
So I have added an UIView component in all pages to show a video.
I gave 0,0,0,0 constraint values for these 2 UIView components in each pages.
First let me share source codes and then I would like to ask my questions.
HomeViewController.swift file
import UIKit
import AVFoundation
class HomeViewController: UIViewController {
private var player: AVPlayer!
#IBOutlet weak var outlet4TheVideoUiView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
configureVideoIssue()
}
override func viewWillAppear(_ animated: Bool) {
configureVideoIssue()
}
func configureVideoIssue()
{
// BACKGROUND VIDEO SCOPE STARTS
let path = URL(fileURLWithPath: Bundle.main.path(forResource: "clouds", ofType: "mp4")!)
let player = AVPlayer(url: path)
self.player = player
let newLayer = AVPlayerLayer(player: player)
newLayer.frame = self.view.bounds
outlet4TheVideoUiView.frame = self.view.bounds
outlet4TheVideoUiView.layer.addSublayer(newLayer)
newLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
player.play()
// video bitince tekrar oynatmak için
player.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none
NotificationCenter.default.addObserver(self, selector: #selector(self.videoDidPlayToEnd(notification:)),
name: NSNotification.Name(rawValue: "AVPlayerItemDidPlayToEndTimeNotification"), object: player.currentItem)
NotificationCenter.default.addObserver(self, selector: #selector(enteredBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(enteredForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
// BACKGROUND VIDEO SCOPE ENDS
}
#objc func enteredBackground() {
player.pause()
}
#objc func enteredForeground() {
player.play()
}
#objc func videoDidPlayToEnd(notification: Notification)
{
let player: AVPlayerItem = notification.object as! AVPlayerItem
player.seek(to: .zero, completionHandler: nil)
}
#IBAction func linkBtnClick(_ sender: UIButton) {
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DetailPage") as! DetailViewController
controller.modalPresentationStyle = .fullScreen
//present(controller, animated: true, completion: nil)
show(controller, sender: nil)
}
}
DetailViewController.swift file
import UIKit
import AVFoundation
class DetailViewController: UIViewController {
private var player: AVPlayer!
#IBOutlet weak var outlet4TheVideoUiView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
configureVideoIssue()
// Do any additional setup after loading the view.
}
func configureVideoIssue()
{
// BACKGROUND VIDEO SCOPE STARTS
let path = URL(fileURLWithPath: Bundle.main.path(forResource: "clouds", ofType: "mp4")!)
let player = AVPlayer(url: path)
self.player = player
let newLayer = AVPlayerLayer(player: player)
newLayer.frame = self.view.bounds
outlet4TheVideoUiView.frame = self.view.bounds
outlet4TheVideoUiView.layer.addSublayer(newLayer)
newLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
player.play()
// video bitince tekrar oynatmak için
player.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none
NotificationCenter.default.addObserver(self, selector: #selector(self.videoDidPlayToEnd(notification:)),
name: NSNotification.Name(rawValue: "AVPlayerItemDidPlayToEndTimeNotification"), object: player.currentItem)
NotificationCenter.default.addObserver(self, selector: #selector(enteredBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(enteredForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
// BACKGROUND VIDEO SCOPE ENDS
}
#objc func enteredBackground() {
player.pause()
}
#objc func enteredForeground() {
player.play()
}
#objc func videoDidPlayToEnd(notification: Notification)
{
let player: AVPlayerItem = notification.object as! AVPlayerItem
player.seek(to: .zero, completionHandler: nil)
}
#IBAction func btnBackClick(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
}
First Question: I am sure that it shouldn't be like this because same codes are written in both ViewControllers. So how can I avoid this? Any suggestions? I should use same video ui view object as a background for all view controllers. Does it make sense? If it does not make sense. What should I do? For example, I use asp.net user control component in Visual Studio for this scenario. I create one video component(user control) and I can use this component for all pages as a background.
Second Question: (if we can't find a solution to the first question)Let's assume that app user sees Homepage right now. And let's assume that user clicks button to see secondpage(detail page)after 10th second. Button click action takes user to the second page but video starts from the first second. Video should continue from 10th second or 11th second. How can I do this?
Third Question: Second page comes from the bottom after i click button in first page. Can second page comes with fading first page out animation and appearing second page animation?
Important details: Video should stop after app is minimized and should continue after app is maximized by app user. And other important detail is: buttons are not inside of the video view. User can see buttons above the video with this way. Let me show layers for the components in order with an image:
One way that you can do this is set your view on the rootWindow and then set all your views' background color to clear.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let window=self.window!
backgroundVideoView.bounds = window.bounds
window.addSubview(backgroundVideoView)
playVideo()
return true
}
Here, backgroundVideoView refers to the view which will be playing your video.
Although, I must warn you, this will consume a lot of memory but will get you the desired behavior.

Xcode How to detect when a childview within scrollview is opened

I have a viewcontroller with a scrollview inside it. I have three child viewcontrollers linked to this scrollview. In one of the child viewcontrollers, I want to start playing videos with AVPlayer when the user swipes to that view.
Currently with my code, begins the video when the scrollview is loaded even when I am not in that childview.
I have tried calling the download method in viewDidAppear and viewWillAppear.
override func viewDidLoad() {
super.viewDidLoad()
download()
}
func download() {
let fireStoreRef = Firestore.firestore().collection("")
fireStoreRef.getDocuments { (snapshot, err) in
if let snapshot = snapshot{
for document in snapshot.documents{
let stringURL = document.get("videoURL")
if stringURL != nil {
let videoURL = URL(string: stringURL as! String)
self.videoURLs.append(videoURL!)
}
}
self.play(url: self.videoURLs[0])
}
}
}
func play(url: URL) {
let player = AVPlayer(url: url)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.view.layer.addSublayer(playerLayer)
player.play()
}
Would like the AVplayer to begin playing when user swipes to specific childview of scrollview

Stop video playback on Siri Remote Menu Button press

In my application I do play a video with that function
func easyRandom_play(episodeTitle:String){
self.backgroundMusic?.stop()
backgroundMusic?.stop()
let link = "http://46.235.26.87/\(__TITLENAME_FOLDERNAME_DOWNLOAD__)/\(episodeTitle).m4v"
print(link)
let url:NSURL = NSURL(string: link)!
let player = AVPlayer(URL: url)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.presentViewController(playerViewController, animated: true) {
playerViewController.player!.play()
}
}
Now there is no way to stop the video - normally the menu button should take me back and end the playback.
Is there a way to implement this?
You can add a UITapGestureRecognizer to handle the Menu button.
override func viewDidLoad() {
// Setup Menu Button recognizer
let menuGesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleMenuGesture(_:)))
menuGesture.allowedPressTypes = [NSNumber(integer: UIPressType.Menu.rawValue)]
self.view.addGestureRecognizer(menuGesture)
}
func handleMenuGesture(tap: UITapGestureRecognizer) {
print("Menu Gesture")
// Stop video and dismiss view controller here
}