I am working on an app where I need to play videos from a FOR loop i.e. play local video against the string provided in each iteration of the loop.
The problem is when I call the playVideo() func from the loop, all the videos play simultaneously. I want them to play one after the other and then dismiss the AVPlayerLayer i.e. remove from the superlayer.
When a single video is played, the playerLayer gets dismissed but in case of more than one, the playerLayer is intact.
How to make them play one after other?
I have read about using dispatch queues, but don't know much about them.
code :
func parseString(string: String){
var stringArray = string.componentsSeparatedByString(" ")
logTextView.text = ""
for i in 0..<stringArray.count{
playVideo(stringArray[i].lowercaseString)
}
}
var player: AVPlayer!
var playerLayer: AVPlayerLayer!
// video player
private func playVideo(name: String) {
guard let path = NSBundle.mainBundle().pathForResource(name, ofType:"mp4") else {
print("file not found")
return
}
player = AVPlayer(URL: NSURL(fileURLWithPath: path))
playerLayer = AVPlayerLayer(player: player)
// playerController.player = player
self.playerLayer.frame = self.view.bounds
self.view!.layer.addSublayer(self.playerLayer)
self.player!.play()
// playing = true
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.playerDidFinishPlaying(_:)),
name: AVPlayerItemDidPlayToEndTimeNotification, object: player.currentItem)
}
func playerDidFinishPlaying(note: NSNotification) {
print("Video Finished")
self.playerLayer.removeFromSuperlayer()
// playing = false
}
Any help would be appreciated
Don't use for loop then. Take an int i = 0, then increase (i++) until i becomes more than stringArray.count
if (i == stringArray.count) {
//end
}
call the function, where you are doing the above execution, from playerDidFinishPlaying(note: NSNotification).
Hope this will help.
Related
I added a video to my viewController() as a background and I want to loop it or in other words make it repeat itself forever.
Here is all the code:
import UIKit
import AVKit
import AVFoundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
let player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "background", ofType: "mp4")!))
let layer = AVPlayerLayer(player: player)
layer.frame = view.bounds
layer.videoGravity = .resizeAspectFill
layer.repeatCount = .greatestFiniteMagnitude
layer.repeatDuration = .greatestFiniteMagnitude
view.layer.addSublayer(layer)
player.play()
}
}
When I run the app It only plays once and never repeats itself.
Any suggestions? Thanks.
One solution is to observe AVPlayerItemDidPlayToEndTime and then simply restart the video.
NotificationCenter.default.addObserver(
forName: .AVPlayerItemDidPlayToEndTime,
object: player.currentItem,
queue: .main
) { [weak player] _ in
player?.seek(to: .zero)
player?.play()
}
Don't forget to detach this observer whenever it's no longer necessary.
Use an AVPlayerLooper. Its name tells you that this is exactly what it's for.
https://developer.apple.com/documentation/avfoundation/avplayerlooper
A couple comments.
According to AVPlayerLayer doc: Set one or the other. Not both.
If both repeatDuration and repeatCount are specified the behavior is
undefined.
In general, I've struggled with getting AVPlayerLayer to repeat mp4's using the repeatCount or repeatDuration way. Instead I use AVQueuePlayer with a AVPlayerLooper. Credit to: https://developer.apple.com/forums/thread/658909
import AVFoundation
var playerQueue: AVQueuePlayer!
var playerLayer: AVPlayerLayer!
var playerLooper: AVPlayerLooper!
private func configLooper(file: String, ext: String) {
guard let videoURL = Bundle.main.url(forResource: file, withExtension: ext) else { return }
playerQueue = AVQueuePlayer()
playerLayer = AVPlayerLayer(player: playerQueue)
let playerItem = AVPlayerItem(url: videoURL)
playerLooper = AVPlayerLooper(player: playerQueue, templateItem: playerItem)
playerLayer.frame = view.bounds
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
view.layer.insertSublayer(playerLayer, below: someOtherView.layer)
playerQueue.play()
}
I have been looking around for a while on how to correctly accomplish this. I have looked here and here. And have used the top answer here, to try and accomplish this however for me the recorded video does not ever even begin to loop. The first frame shows up but does not play the video, thus I am wondering what's wrong.
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
if (error != nil) {
print("Error recording movie11: \(error!.localizedDescription)")
} else {
isSettingThumbnail = false
let videoRecorded = outputURL! as URL
playRecordedVideo(video: videoRecorded)
if !captureSession.isRunning {
DispatchQueue.global(qos: .background).async {
self.startRunningCaptureSession()
}
}
}
}
The function used in the above method is bellow.
func playRecordedVideo(video: URL) {
thumbImage = nil
playerQueue = AVQueuePlayer(playerItem: AVPlayerItem(url: video))
playerLayer = AVPlayerLayer(player: playerQueue)
playerLayer.frame = (camBaseLayer?.bounds)!
playerLayer?.layoutIfNeeded()
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
playerLayer.isHidden = false
camBaseLayer?.layer.insertSublayer(playerLayer, above: previewLayer)
playerItem1 = AVPlayerItem(url: video)
playerLooper = AVPlayerLooper(player: playerQueue, templateItem: playerItem1)
self.playerQueue?.play()
}
I have attempted the following in the function above:
var num = 0
if num == 0 {
self.playerQueue?.play()
num+=1
} else {
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.playerQueue.currentItem, queue: nil, using: { (_) in
DispatchQueue.main.async {
self.playerQueue.seek(to: CMTime.zero)
self.playerQueue.play()
}
})
}
This however just causes the video to not even play when it comes up.
The goal is to prevent the "gap" you see when the video is looped
Update:
Ok so actually the variable num should be outside of the function which then would make the NS code run but that ends up freezing the view like it did before.
Update 2:
I have not been able to find a solution to this as of yet. But what is for certain is that
` NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.playerQueue.currentItem, queue: nil, using: { (_) in
DispatchQueue.main.async {
self.playerQueue.seek(to: CMTime.zero)
self.playerQueue.play()
}
})
`
Causes the video to not even loop. Any reason as to why this happens?
I did the same thing a few years back using this piece of code:
var player: AVPlayer!
var playerLayer: AVPlayerLayer!
private func playVideo(name: String) {
guard let path = Bundle.main.path(forResource: name, ofType:"mp4") else { return }
player = AVPlayer(url: NSURL(fileURLWithPath: path) as URL)
playerLayer = AVPlayerLayer(player: player)
self.playerLayer.frame = SOME_BOUNDS
self.player!.play()
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(note:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
}
#objc func playerDidFinishPlaying(note: NSNotification) {
print("Video Finished")
self.playerLayer.removeFromSuperlayer()
playVideo(name: "SOME_NAME")
}
Hope this points you to your desired functionality
I have been created page control, and in that the background videos will be get loaded, the videos is getting played but the loop is not working, i had refered in stackoverflow but i couldn't get the right answer
here is mine code:
override func viewDidLoad() {
super.viewDidLoad()
self.pageController.currentPage = 0
loadVideo(currentPage: 0)
}
func loadVideo(currentPage: Int) {
DispatchQueue.main.async {
self.videoPath = Bundle.main.url(forResource: "BoardVideoArray[currentPage]", withExtension: "mp4")
self.player = AVPlayer(url: self.videoPath!)
self.player?.actionAtItemEnd = .none
self.player?.isMuted = true
self.playerLayer = AVPlayerLayer(player: self.player)
self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
self.playerLayer.zPosition = -1
}
playerLayer.frame = videoVIew.frame
videoVIew.layer.addSublayer(playerLayer)
videoVIew.bringSubview(toFront: pageController)
player?.play()
//loop video
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: nil, using: { (_) in
DispatchQueue.main.async {
self.player?.seek(to: kCMTimeZero)
self.player = AVPlayer(url: self.videoPath!)
self.player.play()
}
})
}
I have declared the player and avplayerLayer out of scope
var player: AVPlayer!
var playerLayer = AVPlayerLayer()
I spend more than 4 hours, but i couldn't found the mistake which i have made.
The issue is video is not geting looping
Try this way.
guard let path = Bundle.main.path(forResource: "video", ofType:"m4v") else {
debugPrint("video.m4v not found")
return
}
//VideoPlayer is a subClass of AVPlayer
let player = VideoPlayer(url: URL(fileURLWithPath: path))
playerViewController = AVPlayerViewController()
playerViewController.videoGravity = AVLayerVideoGravityResizeAspectFill
playerViewController.player = player
//self.videoPlayerView is a UIView in which i want to show video.
playerViewController.view.frame = self.videoPlayerView.bounds
self.videoPlayerView.addSubview(playerViewController.view)
playerViewController.showsPlaybackControls = false
playerViewController.player?.play()
player.shouldMute(mute: true)
NotificationCenter.default.addObserver(self, selector: #selector(HomeViewController.videoDidStopPlaying(notification:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
func videoDidStopPlaying(notification:Notification) {
self.playerViewController.player?.seek(to: kCMTimeZero)
self.playerViewController.player?.play()
}
I wrote a Swift UIView subclass called SDLoopingVideoView that plays and loops any compatible AVPlayer video file and automatically scales it to fill the view. It can be entirely setup in Interface Builder This work great as a video background and you won't have to worry about the video looping since it's managed automatically. You can find it on Github here: SDLoopingVideoView
I'm trying to create an app that will change videos on each button press.
Currently: The video loops and the button does nothing
Goals: The button will cycle to the next video based on the array index. I plan on associating another array with text descriptions to go along with each video.
Thoughts: I think a JSON based implementation would probably work better. This is my first iOS app and I'm trying to keep it simple. Thoughts and help are greatly appreciated!
Here is the function that loops the video. The name of name of the video is held in an array called videoId:
class WorkoutViewController: UIViewController{
#IBOutlet weak var videoView: UIView!
#IBOutlet weak var infoCardView: UIView!
var currentVidIndex: Int = 0
var player: AVPlayer?
var playerLayer: AVPlayerLayer?
override func viewDidLoad() {
super.viewDidLoad()
shadowBackground()
}
var videoId: [String] = ["bodyweight_fitness_arch","bodyweight_fitness_assisted_squat","bodyweight_fitness_band_dislocates"]
func setLoopingVideo(){
let path = Bundle.main.path(forResource: videoId[currentVidIndex], ofType: "mp4")
let url = URL.init(fileURLWithPath: path!)
let player = AVPlayer(url: url)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = videoView.bounds
self.videoView.layer.insertSublayer(playerLayer, at: 0)
player.play()
// Create observer to monitor when video ends, when it does so set the video time to the start (zero)
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime,object: player.currentItem, queue: nil)
{
Notification in player.seek(to: kCMTimeZero)
player.play()
}
func shadowBackground(){
infoCardView.layer.cornerRadius = 3.0
infoCardView.layer.shadowColor =
UIColor.black.withAlphaComponent(0.6).cgColor
infoCardView.layer.shadowOffset = CGSize(width: 0, height: 0)
infoCardView.layer.shadowOpacity = 0.9
videoView.layer.cornerRadius = 3.0
videoView.layer.shadowColor =
UIColor.black.withAlphaComponent(0.6).cgColor
videoView.layer.shadowOffset = CGSize(width: 0, height: 0)
videoView.layer.shadowOpacity = 0.9
}
override func viewDidAppear(_ animated: Bool) {
setLoopingVideo()
}
#IBAction func showNextVideo(_ sender: UIButton) {
if currentVidIndex < videoId.count{
currentVidIndex += 1
print (currentVidIndex)
} else {
NSLog("Nothing")
}
}
What it looks like
If you want to change a video on every button click, you have to create always new AVPlayer object.
create and remember your AVPlayer object
create and remember your AVPlayerLayer
-- when button clicked --
remove AVPlayerLayer from the parent
stop AVPlayer (old video)
goto step 1 with new video URL
Here was my solution:
func clearVid(){
if let player = self.player{
player.pause()
self.player = nil
}
if let layer = self.playerLayer{
layer.removeFromSuperlayer()
self.playerLayer = nil
}
self.videoView.layer.sublayers?.removeAll()
}
I use following code to reveal two different video-sources as background. The "selectVideo" (SegmentedControl) is used to select video. The problem is down below.
#IBAction func selectVideo(sender: AnyObject) {
if self.Controller.selectedIndex == 1 {
self.videoBackgroundCustomer()
}
if self.Controller.selectedIndex == 0 {
self.videoBackgroundDriver()
}
}
func videoBackgroundDriver() {
//Load video background.
let videoURL: NSURL = NSBundle.mainBundle().URLForResource("background_video_2", withExtension: "mp4")!
player = AVPlayer(URL: videoURL)
videoBackground()
}
//Video background customer
func videoBackgroundCustomer() {
//Load video background.
let videoURL: NSURL = NSBundle.mainBundle().URLForResource("background_video_1", withExtension: "mp4")!
player = AVPlayer(URL: videoURL)
videoBackground()
}
//Vieobackground-code part 2, provides with less code.
func videoBackground() {
player?.actionAtItemEnd = .None
player?.muted = true
let playerLayer = AVPlayerLayer(player: player)
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
playerLayer.zPosition = -1
playerLayer.frame = view.frame
view.layer.addSublayer(playerLayer)
player?.play()
//call loop video
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(LoginViewController.loopVideo), name: AVPlayerItemDidPlayToEndTimeNotification, object: player!.currentItem)
}
//Loop video
func loopVideo() {
player?.seekToTime(kCMTimeZero)
player?.play()
}
Problem: The video restarts when the last video should have ended. And not when the most recent video ends.
How do I make it repeat playing after the last playing video finished? Thanks
After investigation of the problem firstly take a look at documentation and return values of the method.
- (void)seekToTime:(CMTime)time;
Method that you are using return void and could not be compared to the CMTime
For solving of you're problem try this solution:
First you need to subscribe you're class to the notification that will indicate that video is ended.
NSNotificationCenter.defaultCenter().addObserver(self,selector: "itemDidReachEnd:",
name: AVPlayerItemDidPlayToEndTimeNotification,
object: player.currentItem)
And than define method to handle this notification.
func itemDidReachEnd(notification: NSNotification) {
player.seekToTime(kCMTimeZero)
player.play()
}
In this case you are tracking when video ends and you start it again.