Adding a looped mp4 Background to iOS app signup - ios

I want to end up applying this code to make my app look a little nicer when users are logging into their social media accounts. I've tried the following code already but my app seems to crash as soon as the mp4 ends.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var avPlayer: AVPlayer!
var avPlayerLayer: AVPlayerLayer!
var paused: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
let theURL = Bundle.main.url(forResource: "Yeet", withExtension: "mp4")
avPlayer = AVPlayer(url: theURL!)
avPlayerLayer = AVPlayerLayer(player: avPlayer)
avPlayerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
avPlayer.volume = 0
avPlayer.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none
avPlayerLayer.frame = view.layer.bounds
view.backgroundColor = UIColor.clear;
view.layer.insertSublayer(avPlayerLayer, at: 0)
NotificationCenter.default.addObserver(self, selector: Selector(("playerItemDidReachEnd:")), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: avPlayer.currentItem)
}
#objc func playerItemDidReachEnd(notification: NSNotification) {
let p: AVPlayerItem = notification.object as! AVPlayerItem
p.seek(to: CMTime.zero)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidDisappear(animated)
avPlayer.play()
paused = false
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
avPlayer.pause()
paused = true
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}

Have you tried using AVPlayerLooper?
Looks like #iwasrobbed has a solution for iOS 10+ devices. Here's the example code:
private var looper: AVPlayerLooper?
...
let queuePlayer = AVQueuePlayer(playerItem: item)
looper = AVPlayerLooper(player: queuePlayer, templateItem: item)
videoPlayerLayer.player = queuePlayer
To disable the loop after leaving the view, add this piece of code in the end:
looper?.disableLooping()

Related

IOS 14 AVPlayer plays on simulator but doesn't on a real device

I have splash screen as a short mov video for my application and I use AVPlayer (code below) for that. It stopped working with the IOS 14 physical device, while on the simulator it works fine. Could you help with that?
import UIKit
import AVKit
import AVFoundation
class PlayerVC: UIViewController, AVAudioPlayerDelegate {
lazy var player: AVPlayer = {
let fileName = "test"
let path = Bundle.main.path(forResource: fileName, ofType: "mov")!
let videoURL = URL(fileURLWithPath: path)
let player = AVPlayer(url: videoURL)
return player
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(viewTapped)))
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.shouldRasterize = true
playerLayer.frame = self.view.bounds
self.view.layer.addSublayer(playerLayer)
player.play()
NotificationCenter.default.addObserver(self, selector: #selector(self.playerDidFinishPlaying(sender:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
}
#objc func playerDidFinishPlaying(sender: NSNotification) {
loadRootVC()
}
private func loadRootVC() {
dismiss(animated: false, completion: nil)
self.onVideoFinished();
}
#objc func viewTapped() {
loadRootVC()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}

AVKit – Video clips loop just 2 times

I have got four video clips and I want to play them endlessly. But my AVQueuePlayer() loops all the videos just 2 times and then stops.
How to make them play endlessly?
Here's my code:
import UIKit
import AVKit
class ViewController: UIViewController {
private let player = AVQueuePlayer()
let clips = ["001", "002", "003", "004"]
private var token: NSKeyValueObservation?
var avPlayerView = AVPlayerViewController()
override func viewDidLoad() {
super.viewDidLoad()
self.addAllVideosToPlayer()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
self.addAllVideosToPlayer()
}
func addAllVideosToPlayer() {
avPlayerView.player = player
for clip in clips {
let urlPath = Bundle.main.path(forResource: clip, ofType: "m4v")!
let url = URL(fileURLWithPath: urlPath)
let playerItem = AVPlayerItem(url: url)
player.insert(playerItem, after: player.items().last)
token = player.observe(\.currentItem) { [weak self] player, _ in
if self!.player.items().count == 1 { self?.addAllVideosToPlayer() }
}
avPlayerView.showsPlaybackControls = false
player.volume = 0.0
}
present(avPlayerView, animated: true, completion: { self.player.play() })
}
}
I solved my problem. I deleted viewDidLoad() method and placed present() function into viewDidAppear().
Here's how it looks like:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
self.addAllVideosToPlayer()
present(avPlayerView, animated: true, completion: { self.player.play() })
}

Sync video in AVPlayerLayer and AVPlayerViewController

I'm working with AVPlayerto show the playing video using a URL in it. There are 2 parts to it:
1. Firstly, I've embedded the AVPlayer to a view's sublayer using AVPlayerLayer, i.e.
var player: AVPlayer?
func configure() {
let urlString = "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"
if let url = URL(string: urlString) {
self.player = AVPlayer(url: url)
let playerLayer = AVPlayerLayer(player: self.player)
playerLayer.frame = self.view.bounds
self.view.layer.addSublayer(playerLayer)
player.play()
}
}
The above code is working fine and the video is playing.
2. Secondly, on a UIButton tap, I'm presenting a AVPlayerViewController using the same AVPlayer instance that I created earlier, i.e.
#IBAction func onTapVideoButton(_ sender: UIButton) {
self.player?.pause()
let controller = AVPlayerViewController()
controller.player = self.player
self.present(controller, animated: true) {
self.player?.play()
}
}
The problem I'm facing here is, after the AVPlayerViewController opens, the video stops playing but the audio still plays on.
What I want is to sync the video in both AVPlayerLayer and AVPlayerViewController.
I think there is a problem when sharing a player already created to the AVPlayerViewController. I'm not sure why is stoping but it wont happen if you create a new AVPlayer for that controller. A way to sync your player and your AVPlayerViewController could be like this:
First, you create a Notification Name that you'll use when the AVPlayerViewController is dismiss (apple does not give you a way to know when the user dismiss the AVPlayerViewController):
extension Notification.Name {
static let avPlayerDidDismiss = Notification.Name("avPlayerDidDismiss")
}
Then, you extend AVPlayerViewController to post this notification when is going to be dismiss and to send the time when the user left the video:
extension AVPlayerViewController {
open override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let seekTime = player?.currentTime() {
let userInfo = ["seekTime": seekTime]
NotificationCenter.default.post(name: .avPlayerDidDismiss, object: nil, userInfo: userInfo)
}
}
}
And in your ViewController you observe that notification, get the seekTime you want to go and use it to setup your avPlayer:
class ViewController: UIViewController {
var player: AVPlayer?
deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewDidLoad() {
super.viewDidLoad()
configure()
NotificationCenter.default.addObserver(self, selector: #selector(avPlayerDidDismiss), name: .avPlayerDidDismiss, object: nil)
}
func configure() {
self.player = getPlayer()
let playerLayer = AVPlayerLayer(player: self.player)
playerLayer.frame = self.view.bounds
self.view.layer.addSublayer(playerLayer)
self.player?.play()
}
#IBAction func onTapVideoButton(_ sender: UIButton) {
self.player?.pause()
let controllerPlayer = getPlayer()
controllerPlayer.currentItem?.seek(to: self.player!.currentTime(), completionHandler: nil)
let controller = AVPlayerViewController()
controller.player = controllerPlayer
self.present(controller, animated: true, completion: {
controller.player?.play()
})
}
func getPlayer() -> AVPlayer {
let url = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")!
return AVPlayer(url: url)
}
#objc
private func avPlayerDidDismiss(_ notification: Notification) {
if let seekTime = notification.userInfo?["seekTime"] as? CMTime {
player?.currentItem?.seek(to: seekTime, completionHandler: nil)
player?.play()
}
}
}
Cons: It will send the notification for every AVPlayerViewController. You use add you as an observer when you need this info. Hope it can help.

AVPlayer video still playing after segue to next view controller

I'm currently looping my mp4 video with no playback controls, kind of like a gif but with sound. But I do not know why when I segue to the next view controller, the video is still playing. Does anybody know the simplest method to resolve this issue?
ViewController
import UIKit
import AVKit
import AVFoundation
fileprivate var playerObserver: Any?
class ScoreController: UIViewController {
#IBOutlet var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let returnValue: Int = UserDefaults.standard.integer(forKey: "userScore")
label.text = "Your Score: \(returnValue)/30"
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
let path = Bundle.main.path(forResource: "Innie-Kiss", ofType:"mp4")
let player = AVPlayer(url: URL(fileURLWithPath: path!))
let resetPlayer = {
player.seek(to: kCMTimeZero)
player.play()
}
playerObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: nil) { notification in resetPlayer() }
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
self.addChildViewController(controller)
let screenSize = UIScreen.main.bounds.size
let videoFrame = CGRect(x: 0, y: 130, width: screenSize.width, height: (screenSize.height - 130) / 2)
controller.view.frame = videoFrame
self.view.addSubview(controller.view)
player.play()
} catch {
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
fileprivate var player: AVPlayer? {
didSet { player?.play() }
}
deinit {
guard let observer = playerObserver else { return }
NotificationCenter.default.removeObserver(observer)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func doneButton(_ sender: Any) {
self.performSegue(withIdentifier: "done", sender: self)
}
}
In viewWillDisapper() or button action for segue do this :
NotificationCenter.default.removeObserver(self)
Also move this from viewDidLoad() to some function like :
var player: AVPlayer?
func audioPlayer(){
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
let path = Bundle.main.path(forResource: "Innie-Kiss", ofType:"mp4")
player = AVPlayer(url: URL(fileURLWithPath: path!))
let resetPlayer = {
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
playerObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: nil) { notification in resetPlayer() }
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
self.addChildViewController(controller)
let screenSize = UIScreen.main.bounds.size
let videoFrame = CGRect(x: 0, y: 130, width: screenSize.width, height: (screenSize.height - 130) / 2)
controller.view.frame = videoFrame
self.view.addSubview(controller.view)
player?.play()
} catch {
}
}
and make player object a global variable. var player = AVPlayer? and in viewWillDisappear make it nil.
So your viewWillDisappear should look like this :
override func viewWillDisappear(_ animated: Bool) {
NotificationCenter.default.removeObserver(self)
if player != nil{
player?.replaceCurrentItem(with: nil)
player = nil
}
}
override func viewWillDisappear(_ animated: Bool) {
}
This may be helpful to you if you are switching windows; as you perform a segue, you will execute the function included in curly brackets automatically. I'm rubbish with AV, but some of the other's code suggested to turn off the function may work here if it isn't working in the IBAction.
I'm not great with Xcode though, so mileage will probably vary.
I had this issue and fixed it by adding this line before setting player to nil:
[self.player pause];
here is my viewWillDisapear:
- (void)viewWillDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.player];
[self.player removeObserver:self forKeyPath:#"rate"];
[self.player removeTimeObserver:self.playerObserver];
self.playerObserver = nil;
[self.player pause];
self.player = nil;
[[UIDevice currentDevice] setValue:#(UIInterfaceOrientationPortrait) forKey:#"orientation"];
[UINavigationController attemptRotationToDeviceOrientation];
[StayfilmApp restrictOrientation:YES];
}
class ScoreController: UIViewController {
#IBOutlet var label: UILabel!
var player: AVPlayer?
override func viewDidLoad() {
super.viewDidLoad()
let returnValue: Int = UserDefaults.standard.integer(forKey: "userScore")
label.text = "Your Score: \(returnValue)/30"
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
let path = Bundle.main.path(forResource: "Innie-Kiss", ofType:"mp4")
player = AVPlayer(url: URL(fileURLWithPath: path!))
let resetPlayer = {
player.seek(to: kCMTimeZero)
player.play()
}
playerObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: nil) { notification in resetPlayer() }
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
self.addChildViewController(controller)
let screenSize = UIScreen.main.bounds.size
let videoFrame = CGRect(x: 0, y: 130, width: screenSize.width, height: (screenSize.height - 130) / 2)
controller.view.frame = videoFrame
self.view.addSubview(controller.view)
player.play()
} catch {
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
player.pause()
}
fileprivate var player: AVPlayer? {
didSet { player?.play() }
}
deinit {
guard let observer = playerObserver else { return }
NotificationCenter.default.removeObserver(observer)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func doneButton(_ sender: Any) {
self.performSegue(withIdentifier: "done", sender: self)
}
}
notable changes:
var player: AVPlayer?
and also
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self)
player?.pause()
}
I have the same problem when I used segue to go to the next ViewController, then I used present code with swift by using this code
#IBAction func next(_ sender: Any) {
let vc = self .storyboard?.instantiateViewController(withIdentifier:"ViewControllerAA") as! ViewControllerAA
self.present(vc,animated: true,completion: nil)
}
but the problem didn't solve , then I used this code to go to the next VC and my problem solved and the background sound of the video stoped, and the code is this below
#IBAction func next(_ sender: Any) {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerAA") as? ViewControllerAA {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = vc
}
}

How do i make a looping video in swift

How would i go about making a local .mp4 file with no sound play on a loop, so it would only take up part of the screen and have no user controls. Just a looping video, sort of like a gif. I am using xcode, swift2.
class ViewController: UIViewController {
var playerViewController = AVPlayerViewController()
var playerView = AVPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
var fileURL = NSURL(fileURLWithPath: "/Users/Mantas/Desktop/123/123/video-1453562323.mp4.mp4")
playerView = AVPlayer(URL: fileURL)
playerViewController.player = playerView
self.presentViewController(playerViewController, animated: true){
self.playerViewController.player?.play()
}
}
}
I have made this, it plays the video, but in full screen, i dont know how to make it only take up part of the screen and how to make it loop
Alternate version in Swift 3.0:
Add these properties:
fileprivate var player: AVPlayer? {
didSet { player?.play() }
}
fileprivate var playerObserver: Any?
Add this to your deinit:
deinit {
guard let observer = playerObserver else { return }
NotificationCenter.default.removeObserver(observer)
}
Add this function:
func videoPlayerLayer() -> AVPlayerLayer {
let fileURL = URL(fileURLWithPath: mediaPath)
let player = AVPlayer(url: fileURL)
let resetPlayer = {
player.seek(to: kCMTimeZero)
player.play()
}
playerObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: nil) { notification in
resetPlayer()
}
self.player = player
return AVPlayerLayer(player: player)
}
Then add to your layer wherever you feel like (viewDidLoad, viewDidAppear, viewDidFinishLayingOutSubviews):
let playerLayer = videoPlayerLayer()
playerLayer.frame = view.bounds
view.layer.insertSublayer(playerLayer, at: 0)
Adding observer when video going to finish you can make replay the video
override func viewDidAppear(animated: Bool) {
super.viewDidAppear()
var fileURL = NSURL(fileURLWithPath: "/Users/Mantas/Desktop/123/123/video-1453562323.mp4.mp4")
playerView = AVPlayer(URL: fileURL)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "playerItemDidReachEnd:",
name: AVPlayerItemDidPlayToEndTimeNotification,
object: self.playerView.currentItem) // Add observer
playerViewController.player = playerView
//amend the frame of the view
self.playerViewController.player.frame = CGRectMake(0, 0, 200, 200)
//reset the layer's frame, and re-add it to the view
var playerLayer: AVPlayerLayer = AVPlayerLayer.playerLayerWithPlayer(self.playerView)
playerLayer.frame = videoHolderView.bounds
videoHolderView.layer.addSublayer(playerLayer)
/* Full Screen
self.presentViewController(playerViewController, animated: true){
self.playerViewController.player?.play()
} */
}
func playerItemDidReachEnd(notification: NSNotification) {
self.playerView.seekToTime(kCMTimeZero)
self.playerView.play()
}
For a seemless repeating video without a black flash. Use the AVPlayerLooper like so:
private var player: AVQueuePlayer!
private var playerLayer: AVPlayerLayer!
private var playerItem: AVPlayerItem!
private var playerLooper: AVPlayerLooper!
override func viewDidLoad(){
super.viewDidLoad()
let path = Bundle.main.path(forResource: "background_cloudy", ofType: "mov")
let pathURL = URL(fileURLWithPath: path!)
let duration = Int64( ( (Float64(CMTimeGetSeconds(AVAsset(url: pathURL).duration)) * 10.0) - 1) / 10.0 )
player = AVQueuePlayer()
playerLayer = AVPlayerLayer(player: player)
playerItem = AVPlayerItem(url: pathURL)
playerLooper = AVPlayerLooper(player: player, templateItem: playerItem,
timeRange: CMTimeRange(start: kCMTimeZero, end: CMTimeMake(duration, 1)) )
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
playerLayer.frame = view.layer.bounds
view.layer.insertSublayer(playerLayer, at: 1)
}
This is tested with Swift 5, I found in https://gist.github.com/lanserxt/33fd8c479185cba181497315299e0e31
import UIKit
import AVFoundation
class LoopedVideoPlayerView: UIView {
fileprivate var videoURL: URL?
fileprivate var queuePlayer: AVQueuePlayer?
fileprivate var playerLayer: AVPlayerLayer?
fileprivate var playbackLooper: AVPlayerLooper?
func prepareVideo(_ videoURL: URL) {
let playerItem = AVPlayerItem(url: videoURL)
self.queuePlayer = AVQueuePlayer(playerItem: playerItem)
self.playerLayer = AVPlayerLayer(player: self.queuePlayer)
guard let playerLayer = self.playerLayer else {return}
guard let queuePlayer = self.queuePlayer else {return}
self.playbackLooper = AVPlayerLooper.init(player: queuePlayer, templateItem: playerItem)
playerLayer.videoGravity = .resizeAspectFill
playerLayer.frame = self.frame
self.layer.addSublayer(playerLayer)
}
func play() {
self.queuePlayer?.play()
}
func pause() {
self.queuePlayer?.pause()
}
func stop() {
self.queuePlayer?.pause()
self.queuePlayer?.seek(to: CMTime.init(seconds: 0, preferredTimescale: 1))
}
func unload() {
self.playerLayer?.removeFromSuperlayer()
self.playerLayer = nil
self.queuePlayer = nil
self.playbackLooper = nil
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
self.playerLayer?.frame = self.bounds
}
}

Resources