I have a uiView where I am playing a video with custom controls.
When I pause the video , it goes to the beginning of the video and pauses instead of pausing at that particular frame
Following is my Code
func playButtonTapped(cell: ViewTableCell) {
guard let indexPath = self.tableView.indexPath(for: cell) else {
return
}
let url = Bundle.main.path(forResource:ArtistFeeds.sharedInstance.videoFeeds[indexPath.row].videoUrl, ofType:"mp4")
let path = NSURL.fileURL(withPath: url!)
currentCell = tableView.cellForRow(at: indexPath) as! ViewTableCell
currentCell.videoPlayerItem = AVPlayerItem.init(url: path)
let playImage = UIImage(named: "image_video_play") as UIImage?
let pauseImage = UIImage(named: "image_video_pause") as UIImage?
if currentCell.avPlayer?.rate == 1.0 {
currentCell.stopPlayback()
currentCell.playButton.isHidden = false
currentCell.playButton.setImage(playImage, for: .normal)
} else {
currentCell.startPlayback()
currentCell.playButton.isHidden = true
currentCell.playButton.setImage(pauseImage, for: .normal)
}
}
I am setting up the videoPlayer in the ViewTableCell class as follows
var avPlayer: AVPlayer?
var avPlayerLayer: AVPlayerLayer?
var videoPlayerItem: AVPlayerItem? = nil {
didSet {
avPlayer?.replaceCurrentItem(with: self.videoPlayerItem)
}
}
#IBAction func playButtonAction(_ sender: UIButton) {
self.delegate?.playButtonTapped(cell: self)
}
func setupMoviePlayer(){
self.avPlayer = AVPlayer.init(playerItem: self.videoPlayerItem)
avPlayerLayer = AVPlayerLayer(player: avPlayer)
avPlayerLayer?.videoGravity = AVLayerVideoGravityResizeAspect
avPlayer?.volume = 3
avPlayer?.actionAtItemEnd = .none
avPlayerLayer?.frame = videoView.bounds
self.backgroundColor = .clear
videoView.layer.insertSublayer(avPlayerLayer!, at: 0)
let interval = CMTime(value: 1, timescale: 2)
avPlayer?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using :{ (progressTime) in
let seconds = CMTimeGetSeconds(progressTime)
if let duration = self.avPlayer?.currentItem?.duration{
let durationSeconds = CMTimeGetSeconds(duration)
self.videoSlider.value = Float(seconds/durationSeconds)
}
})
}
func stopPlayback(){
self.avPlayer?.pause()
}
func startPlayback(){
self.avPlayer?.play()
}
The problem is on every on click of the play button "currentCell.videoPlayerItem = AVPlayerItem.init(url: path)" is being called which is replacing the videoPlayerItem.
I want to pause the video at that particular frame and not go back to the beginning of the video.
How can I overcome this problem. Any help will be appreciated.
You replace the videoPlayerItem every times you press the button. That's why it resets:
avPlayer?.replaceCurrentItem(with: self.videoPlayerItem)
Related
I'm building an app where users can select an audio file to be played. That normally works and it plays it even when screen is locked. The problem is that after the audio file has been changed by the user the first audio file still plays in the background and overlaps with the file the user wants to listen to.
let player: AVPlayer = {
let avPlay = AVPlayer()
avPlay.allowsExternalPlayback = false
avPlay.automaticallyWaitsToMinimizeStalling = false
return avPlay
}()
fileprivate func setupLockScreenCurrentTime(){
var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo
// nowPlayingInfo = nil
guard let currentItem = player.currentItem else{return}
let durationInSecs = CMTimeGetSeconds(currentItem.duration)
// let elapsed = CMTimeGetSeconds(player.currentTime())
nowPlayingInfo?[MPMediaItemPropertyPlaybackDuration] = durationInSecs
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
fileprivate func playSermon(){
let credential = URLCredential(user: "1234user" , password: "password", persistence: .forSession)
let protectionSpace = URLProtectionSpace(host: "example.com", port: 443, protocol: "https", realm: "Restricted", authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
URLCredentialStorage.shared.setDefaultCredential(credential, for: protectionSpace)
let ssurl = SermonHelper.sharedInstance.url
let urlNew:String = ssurl.replacingOccurrences(of: " ", with: "%20")
guard let url = URL(string: urlNew) else {return}
let playerItem = AVPlayerItem(url: url)
player.replaceCurrentItem(with: playerItem)
player.isMuted = false
player.play()
observePlayerCurrentTime()
}
fileprivate func setupRemoteControl(){
UIApplication.shared.beginReceivingRemoteControlEvents()
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.player.play()
self.enlargeEpisodeImageView()
self.playPauseButton.setImage(UIImage(named: "my_pause2"), for: .normal)
self.setupElapsedTime(rate: 1)
return .success
}
commandCenter.pauseCommand.isEnabled = true
commandCenter.pauseCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.player.pause()
self.shrinkEpisodeImageView()
self.playPauseButton.setImage(UIImage(named: "my_play2"), for: .normal)
self.setupElapsedTime(rate: 0)
return .success
}
commandCenter.togglePlayPauseCommand.isEnabled = true
commandCenter.togglePlayPauseCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
if self.player.timeControlStatus == .playing {
self.player.pause()
self.shrinkEpisodeImageView()
self.playPauseButton.setImage(UIImage(named: "my_play2"), for: .normal)
}
else {
self.player.play()
self.enlargeEpisodeImageView()
self.playPauseButton.setImage(UIImage(named: "my_pause2"), for: .normal)
}
return .success
}
commandCenter.skipForwardCommand.isEnabled = true
commandCenter.skipForwardCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.seekToCurrentTime(delta: 15)
let t = CMTimeAdd(self.player.currentTime(), CMTimeMake(15, 1))
MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(t)
return .success
}
commandCenter.skipBackwardCommand.isEnabled = true
commandCenter.skipBackwardCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.seekToCurrentTime(delta: -15)
let n = CMTimeAdd(self.player.currentTime(), CMTimeMake(-15, 1))
MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(n)
return .success
}
}
I expect the audio being played to be replaced by the new audio file selected by the user, but I get the audio from two files being played simultaneously.
Nowhere in your code is indicated when you stop playing the current sound before replacing it with the new sound in playSermon(). Try to do that. Most probably that will solve your problem.
I'm playing a song using AVAudioPlayer. I need a progress bar to show the progress of the song.
My issue is that the progress bar's progress isn't working properly. Within 2-3 seconds, it finishes its progress.
func playMusic() {
do {
player = try AVAudioPlayer(contentsOf: (currentSong?.mediaURL)!)
guard let player = player else { return }
player.prepareToPlay()
player.play()
updater = CADisplayLink(target: self, selector: #selector(self.musicProgress))
updater.frameInterval = 1
updater.add(to: RunLoop.current, forMode: RunLoop.Mode.common)
playButton.setImage(UIImage.init(named: "pause"), for: .normal)
} catch let error as NSError {
print(error.description)
}
}
#objc func musicProgress() {
let normalizedTime = Float(self.player?.currentTime as! Double * 100.0 / (self.player?.duration as! Double) )
self.progressMusic.progress = normalizedTime
}
The issue is here:
let normalizedTime = Float(self.player?.currentTime as! Double * 100.0 / (self.player?.duration as! Double) )
With this you will get a value between 0.0 and 100.0, but according to UIProgressView documentation, progress must be between 0.0 and 1.0. Try
let normalizedTime = Float(self.player?.currentTime as! Double / (self.player?.duration as! Double) )
I'm trying to create a Snapchat-like app where the user can take a video by holding down a button. However, when the user is done taking a video, and the delegate calls fileOutput, the given url outputFileURL cannot be played using an AVPlayer. I know that the video was actually recorded though, because I can upload the file to Firebase and download it from there.
Here's my code for the fileOutput function:
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
if error == nil {
videoURL = outputFileURL
flipHiddenViews()
// playback video
player = AVPlayer(url: outputFileURL)
playerLayer = AVPlayerLayer(player: player)
playerLayer?.frame = self.view.bounds
self.view.layer.addSublayer(playerLayer!)
player?.play()
} else {
print(error?.localizedDescription)
}
}
Here's how I initialize the button that the user holds down:
let photoButton:UIButton = {
let but = UIButton(type: .custom)
but.layer.cornerRadius = 40
but.layer.borderColor = UIColor.white.cgColor
but.layer.borderWidth = 4
but.clipsToBounds = true
but.addTarget(self, action: #selector(takeVideo), for: .touchDown)
but.addTarget(self, action: #selector(stopVideo), for: [.touchUpInside, .touchUpOutside])
but.translatesAutoresizingMaskIntoConstraints = false
return but
}()
Here's the takeVideo function:
#objc func takeVideo() {
let recordingDelegate:AVCaptureFileOutputRecordingDelegate? = self
if captureSession?.outputs != nil && videoFileOutput != nil {
captureSession?.removeOutput(videoFileOutput!)
}
videoFileOutput = AVCaptureMovieFileOutput()
self.captureSession?.addOutput(videoFileOutput!)
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let filePath = documentsURL.appendingPathComponent("temp")
// Do recording and save the output to the `filePath`
videoFileOutput?.startRecording(to: filePath, recordingDelegate: recordingDelegate!)
}
And finally, the stopVideo function:
#objc func stopVideo() {
videoFileOutput?.stopRecording()
}
What am I doing wrong?
Try this instead:
// playback video
player = AVPlayer(url: outputFileURL)
let playerController = AVPlayerViewController()
playerController.player = player
present(playerController, animated: true) {
player?.play()
}
Adding a .mov at the end of the "temp" name did it for me:
let filePath = documentsURL.appendingPathComponent("temp.mov")
I have a tableview which can display images or videos.
When it comes to images I get no problems.
But when it comes to video I'm getting an error on the console which I don't understand.
The error is : "Task .<2> finished with error - code: -999"
From the research I've made it should be exit before execution.
But I really don't understand what it means.
Here's my code for the tableView Cell:
var post: Post? {
didSet {
updateView()
}
}
func updateView() {
captionLbl.text = post?.caption
usernameLbl.text = "Test"
if (post?.isVideo)! {
if let videoThumbUrlString = post?.videoThumbUrl {
let videoThumbUrl = URL(string: videoThumbUrlString)
postImgView.sd_setImage(with: videoThumbUrl)
}
}
if !(post?.isVideo)! {
if let photoUrlString = post?.photoUrl {
let photoUrl = URL(string: photoUrlString)
postImgView.sd_setImage(with: photoUrl)
}
}
}
func createPlayer() {
if (post?.isVideo)! {
if let videoUrlString = post?.videoUrl, let url = URL(string: videoUrlString) {
player = AVPlayer(url: url)
playerLayer = AVPlayerLayer(player: player)
playerLayer?.frame = self.postImgView.bounds
playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.postImgView.layer.addSublayer(playerLayer!)
player?.play()
activityIndicatorView.startAnimating()
playBtn.isHidden = true
}
}
}
#IBAction func playBtnPressed(_ sender: Any) {
createPlayer()
}
override func prepareForReuse() {
super.prepareForReuse()
playerLayer?.removeFromSuperlayer()
player?.pause()
activityIndicatorView.stopAnimating()
}
When i tap on playButton the video plays but i get the error I've mentioned before.
I'm not sure if is connected but also the activity indicator never stops running.
Hope somebody can help!
Thank you.
---------------------UPDATE
The full console log message is:
"
2017-10-30 14:10:00.996661+0100 PhotoApp[92272:543501] Task .<2> finished with error - code: -999
"
Well, error does not show something. But try next change:
if let videoUrlString = post?.videoUrl, let url = URL(string: videoUrlString) {
let asset = AVURLAsset(url: url) // < --
player = AVPlayer(playerItem: AVPlayerItem(asset: asset)) // < --
playerLayer = AVPlayerLayer(player: player)
playerLayer?.frame = self.postImgView.bounds
playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.postImgView.layer.addSublayer(playerLayer!)
player?.play()
activityIndicatorView.startAnimating()
playBtn.isHidden = true
}
In my application I have used AVPlayer to play videos, If we play a video and go bock to previous page video is playing in backgraound well but if we play the same video again we have to show continuation of the video and it should not begin from start , Here I can able to set slider position according to the video duration but the screen not showing video content sound is coming.
For first time to play any video i called below method
func setup() {
UserDefaults.standard.set(videoString as NSString, forKey: CurrentURL)
UserDefaults.standard.synchronize()
let targetTime:CMTime = CMTimeMake(0, 1)
mediaPlayer.seek(to: targetTime)
mediaPlayer.pause()
print("Video URL \(videoString)")
let url = NSURL(string: videoString)
let playerItem = AVPlayerItem(url: url! as URL)
mediaPlayer=AVPlayer(playerItem: playerItem)
let playerLayer=AVPlayerLayer(player: mediaPlayer)
playerLayer.frame = self.view.bounds
playerLayer.backgroundColor = UIColor.black.cgColor
//playerLayer.videoGravity = AVLayerVideoGravityResize
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect
self.view.layer.addSublayer(playerLayer)
AppDelegate.shared().showLoading()
videoView.isHidden = false
mediaPlayer.play()
let duration : CMTime = mediaPlayer.currentItem!.asset.duration
let seconds : Float64 = CMTimeGetSeconds(duration)
videoSlider.maximumValue = Float(seconds)
videoPlayButton.setImage(UIImage(named: "Pause"), for: .normal)
videoPlaying = true
//Adding Default Periodic Observer On Player
mediaPlayer.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, 1), queue: DispatchQueue.main) { (CMTime) -> Void in
if mediaPlayer.currentItem?.status == .readyToPlay {
AppDelegate.shared().removeLoading()
self.videoSlider.value = Float(CMTimeGetSeconds(mediaPlayer.currentTime()))
let currentTime : Int = Int(CMTimeGetSeconds(mediaPlayer.currentTime()))
let minutes = currentTime/60
let seconds = currentTime - minutes * 60
self.durationLabel.text = NSString(format: "%02d:%02d", minutes,seconds) as String
}
}
}
If user plays same video second time i'm checking current url which is stored in userdefaults if it is playing same video i did like following
let runningSrtring = UserDefaults.standard.object(forKey: CurrentURL) as! NSString
if runningSrtring.isEqual(to: videoString) {
//self.view.layer.sublayers?.forEach { $0.removeFromSuperlayer() }
videoPlayButton.setImage(UIImage(named: "Pause"), for: .normal)
let duration : CMTime = mediaPlayer.currentItem!.asset.duration
let seconds : Float64 = CMTimeGetSeconds(duration)
self.videoSlider.value = Float(CMTimeGetSeconds((mediaPlayer.currentTime())))
videoSlider.minimumValue = 0
videoSlider.maximumValue = Float(seconds)
let playerLayer = AVPlayerLayer(player: mediaPlayer)
playerLayer.frame = self.view.frame
playerLayer.backgroundColor = UIColor.black.cgColor
//playerLayer.videoGravity = AVLayerVideoGravityResize
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect
self.view.layer.addSublayer(playerLayer)
mediaPlayer.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, 1), queue: DispatchQueue.main) { (CMTime) -> Void in
if mediaPlayer.currentItem?.status == .readyToPlay {
self.videoSlider.value = Float(CMTimeGetSeconds((mediaPlayer.currentTime())))
let currentTime : Int = Int(CMTimeGetSeconds(mediaPlayer.currentTime()))
let minutes = currentTime/60
let seconds = currentTime - minutes * 60
self.durationLabel.text = NSString(format: "%02d:%02d", minutes,seconds) as String
}
}
}
But the lauer is not showing video content if i play same video again , here if the user play same video which is already running it should continue from current duration instead of restarting