Swift 3, XCode 8 - Changing Local Video on Button Press - AVPlayer - ios

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()
}

Related

Autoplaying an MP4 video in a UIView that is on loop like a GIF?

Once users go to this specific page, I want the video to immediately start playing inside of a UIView with no controls. It will just loop like a gif. This is code I found off of github and it doesn't seem to work. It just shows the white UIView when I pull up the page. My file name is correct as well.
import Foundation
import UIKit
import AVKit
import AVFoundation
class RampWallPush: UIViewController {
#IBOutlet weak var videoView: UIView!
var player: AVPlayer?
override func viewDidLoad() {
super.viewDidLoad()
// Load video resource
if let videoUrl = Bundle.main.url(forResource: "small", withExtension: "mp4") {
// Init video
self.player = AVPlayer(url: videoUrl)
self.player?.isMuted = true
self.player?.actionAtItemEnd = .none
// Add player layer
let playerLayer = AVPlayerLayer(player: player)
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
playerLayer.frame = view.frame
// Add video layer
self.videoView.layer.addSublayer(playerLayer)
// Play video
self.player?.play()
// Observe end
NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem)
}
}
// MARK: - Loop video when ended.
#objc func playerItemDidReachEnd(notification: NSNotification) {
self.player?.seek(to: CMTime.zero)
self.player?.play()
}
}
Your code is works you need to verify the file exists in your project and .......
// Load video resource
if let videoUrl = Bundle.main.url(forResource: "sample", withExtension: "mp4") {
// exists
}
else {
print("NoFile")
}
So select the file and
You need to change the frame
playerLayer.frame = CGRect(x:0,y:0,width:200,height:300)
self.videoView.layer.addSublayer(playerLayer)
playerLayer.center = videoView.center
Created a demo Here

using avplayerkit issue while playing video black screen coming

hello i am new to swift and i am using AVPlayerViewController for plaing video from my url but issue is that i am not able to load video only black screen showing let me show my code
Code
import UIKit
import AVKit
import AVFoundation
class ViewController: UIViewController {
#IBOutlet weak var viewPlayer: UIView!
var player: AVPlayer!
var avpController = AVPlayerViewController()
var url = "https://www.youtube.com/watch?v=HsQvAnCGxzY"
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: self.url)
player = AVPlayer(url: url!)
avpController.player = player
avpController.view.frame.size.height = viewPlayer.frame.size.height
avpController.view.frame.size.width = viewPlayer.frame.size.width
self.viewPlayer.addSubview(avpController.view)
// Do any additional setup after loading the view, typically from a nib.
}
}
Please refer code given and tell me where i am done wrong so that i can able to play video Thanks In Advance
Actually Its problem of URL .
AVPlayer never supports YouTube video. It supports mp4 format.
If you want to play a video in your ViewController, you have to use AVPlayerLayer. You have to implement player controllers yourself.
override func viewDidLoad() {
super.viewDidLoad()
// creating video url
guard let videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4") else {
return
}
// create AVPlayer
let player = AVPlayer(url: videoURL)
// setup AVPlayerLayer
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.view.layer.addSublayer(playerLayer)
// start playing
player.play()
}
Otherwise use AVPlayerViewController. Instead of using its view present the viewController itself.
override func viewDidLoad() {
super.viewDidLoad()
// creating video url
guard let videoURL = URL(string: "https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4") else {
return
}
// setup AVPlayer and AVPlayerViewController
let player = AVPlayer(url: videoURL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
}
override func viewDidAppear(_ animated: Bool) {
// presenting playerViewController only after the viewControllers view was added to a view hierarchy.
self.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
}

Swift: play videos in turns from a for loop

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.

play video in background swift 3

hello im trying to play video in background but its give an error and the error is " unexpectedly found nil while unwrapping an Optional value " and im already set the video in Bundle and select copy if need
this the my code
import UIKit
import AVFoundation
class ViewController: UIViewController {
var avPlayer: AVPlayer!
var avPlayerLayer: AVPlayerLayer!
var paused: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
let url = Bundle.main.url(forResource: "Grad_Cap_Toss", withExtension: "mp4")
avPlayer = AVPlayer(url: url!)
avPlayerLayer = AVPlayerLayer(player: avPlayer)
avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
avPlayer.volume = 0
avPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.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)
}
please any body have ideas for that , thank you
First, drag and drop video file in your project.
Then import AVFundation.
Add example: var player: AVPlayer?
#IBOutlet weak var videoView: UIView! - Connect view from storyboard to your VC class.
After that, you paste this code into your ViewController.
call "playBackgoundVideo()" in your viewDidLoad()
private func playBackgoundVideo() {
if let filePath = Bundle.main.path(forResource: "your_video_here", ofType:"mp4") {
let filePathUrl = NSURL.fileURL(withPath: filePath)
player = AVPlayer(url: filePathUrl)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.videoView.bounds
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem, queue: nil) { (_) in
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
self.videoView.layer.addSublayer(playerLayer)
player?.play()
}
}
This is swift 4.0
it works ^__^
I had a similar problem and followed the instructions from Rick's answer to resolve it.
My problem was that I didn't have the video file in the bundle resources.
Check if your video file is listed in Copy Bundle Resources under Build Phases in the project. If not , use the + button to add the file in there.
Also you might be missing the avPlayer.play() statement in your code.
I was also trying to play a video in the background of my login page's view controller. I think the main issue is that the syntax you're using is deprecated.
Your code might look like the following:
import UIKit
import AVKit
import AVFoundation
class ViewController: UIViewController {
#IBOutlet var blackOverlay: UIView!
// init video background and its path
var player: AVPlayer?
let videoURL: NSURL = Bundle.main.url(forResource: "videoName", withExtension: "mp4")! as NSURL
override func viewDidLoad() {
super.viewDidLoad()
// Adds a black overlay to the looped video in question
blackOverlay.alpha = 0.75;
blackOverlay.layer.zPosition = 0;
// begin implementing the avplayer
player = AVPlayer(url: videoURL as URL)
player?.actionAtItemEnd = .none
player?.isMuted = true
let playerLayer = AVPlayerLayer(player: player)
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
playerLayer.zPosition = -1
playerLayer.frame = view.frame
view.layer.addSublayer(playerLayer)
player?.play()
// add observer to watch for video end in order to loop video
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.loopVideo), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.player)
// if video ends, will restart
func playerItemDidReachEnd() {
player!.seek(to: kCMTimeZero)
}
}
// maybe add this loop at the end, after viewDidLoad
// func loopVideo() { player?.seek(to: kCMTimeZero) player?.play()}}
Please let me know if this helps!
Drag and drop video file in your project.
Then import AVFundation
List item
#IBOutlet weak var videoView: UIView!, #IBOutlet weak var label: UILabel!,#IBOutlet weak var image: UIImageView!,#IBOutlet weak var loginBtn: UIButton!
Connect view from storyboard to your VC class.
And then paste the given code in your ViewController and call playVideo() function in viewDidLoad().
func playVideo(){
guard let path = Bundle.main.path(forResource: "intro", ofType: "mp4") else {
return
}
let player = AVPlayer(url: URL(fileURLWithPath: path))
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
playerLayer.videoGravity = .resizeAspectFill
self.videoLayer.layer.addSublayer(playerLayer)
player.play()
videoLayer.bringSubviewToFront(image)
videoLayer.bringSubviewToFront(label)
videoLayer.bringSubviewToFront(loginBtn)
videoLayer.bringSubviewToFront(signupBtn)
}
If url is nil and the video is being successfully copied into the bundle, the likely problem is case sensitivity. Make sure the filename is using the correct case. The iOS device filesystem is case sensitive, where as the iPhone simulator is not.
I had a same problem when I tried to play video background. I think it is the problem of video resource.
I resolved the problem like this way.
When you drag your video, you should check both of 'Copy items if needed' and 'Add to targets'.
Or you can just right click on the project navigator, click 'Add' to add resource.

AVPlayer play with update label cause an issue

I have an issue with AVPlayer play and same time updates label.
I have timer that updates label with progress of player, but that stuck my video at same position...check below image..After button click timer starts and cause an issue..
Code:
func playVideo() {
let path = NSBundle.mainBundle().pathForResource("videoname", ofType:"mp4")
let fileURL = NSURL(fileURLWithPath: path!)
moviePlayer = AVPlayer(URL: fileURL)
// Play the video
moviePlayer!.play()
}
#IBAction func progresss(sender: UIButton) {
self.meterTimer = NSTimer.scheduledTimerWithTimeInterval(0.1,
target:self,
selector:"updateAudioMeter:",
userInfo:nil,
repeats:true)
}
func updateAudioMeter(timer:NSTimer) {
lbltime.text = "\(CMTimeGetSeconds(moviePlayer!.currentItem.currentTime()))"
}
Sample project link
Source code
Note:please add video in bundle if you are use sample project.I just delete video because of file size
Question
How to avoid this? and why this happens because when I used MPMovieController it works great.
From your sample code the problem is below function:
override func viewDidLayoutSubviews() {
let layer = AVPlayerLayer(player: moviePlayer)
layer.frame = self.viewPlayer.frame
self.viewPlayer.layer.addSublayer(layer)
}
This will resize your layer view every time when your label update so just remove and add that code into playVideo method and your method will be:
func playVideo() {
let path = NSBundle.mainBundle().pathForResource("video", ofType: "MOV")
let url = NSURL.fileURLWithPath(path!)
moviePlayer = AVPlayer(URL: url)
moviePlayer!.allowsExternalPlayback = false
var introPlayerLayer = AVPlayerLayer(player: moviePlayer)
viewPlayer.layer.addSublayer(introPlayerLayer)
introPlayerLayer.frame = viewPlayer.bounds
moviePlayer!.play()
}
And call this method in viewDidAppear instead of viewDidLoad method and your complete code will be:
import UIKit
import MediaPlayer
import AVFoundation
class ViewController: UIViewController {
var moviePlayer : AVPlayer?
var meterTimer : NSTimer!
#IBOutlet weak var viewPlayer: UIView!
#IBOutlet weak var lbltime: UILabel!
override func viewDidAppear(animated: Bool) {
playVideo()
}
func playVideo() {
let path = NSBundle.mainBundle().pathForResource("video", ofType: "MOV")
let url = NSURL.fileURLWithPath(path!)
moviePlayer = AVPlayer(URL: url)
moviePlayer!.allowsExternalPlayback = false
var introPlayerLayer = AVPlayerLayer(player: moviePlayer)
viewPlayer.layer.addSublayer(introPlayerLayer)
introPlayerLayer.frame = viewPlayer.bounds
moviePlayer!.play()
}
#IBAction func progresss(sender: UIButton) {
moviePlayer?.seekToTime(kCMTimeZero)
moviePlayer?.addPeriodicTimeObserverForInterval(CMTimeMakeWithSeconds(1, 1), queue: nil, usingBlock: {
(CMTime) -> Void in
self.updateProgressBar()
})
}
func updateProgressBar(){
var timeNow = Int(self.moviePlayer!.currentTime().value) / Int(self.moviePlayer!.currentTime().timescale)
var currentMins = timeNow / 60
var currentSec = timeNow % 60
var duration: String = "\(currentMins):\(currentSec)"
self.lbltime.text = duration
}
}
I have written a sample code for Objective-C, you can convert same in swift for your project accordingly.
// Implement following code in AVPlayer observer method for `AVPlayerStatusReadyToPlay` state
[avPlayer addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC)
queue:NULL
usingBlock:
^(CMTime time)
{
// update label value here, it keeps invoking periodically internally.
CMTime playerDuration = [self playerItemDuration];
}];
// Method to get Player current time
- (CMTime)playerItemDuration
{
AVPlayerItem *thePlayerItem = [avPlayer currentItem];
if (thePlayerItem.status == AVPlayerItemStatusReadyToPlay)
{
return([avPlayerItem duration]);
}
return(kCMTimeInvalid);
}
if you simply want to update it then invoke playerItemDuration to get current time, but make it asynchronous process.

Resources