Video player: Play,Pause, forward and backward methods IOS Swift 3 - ios

How to get this buttons event/delegate(Play,Pause, forward and backward) with time difference.
Here i have put the code below. Please suggest we can use other player for this events
player = AVPlayer(url:URL(fileURLWithPath: objpdfURL));
NotificationCenter.default.addObserver(self, selector: #selector(VedioPlayVC.didfinishplaying(note:)),name:NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
NotificationCenter.default.addObserver(self, selector:#selector(VedioPlayVC.Stop(note:)), name: .kAVPlayerViewControllerDismissingNotification, object: nil);
NotificationCenter.default.addObserver(self, selector: #selector(VedioPlayVC.Jump(note:)),name:NSNotification.Name.AVPlayerItemTimeJumped, object: nil)
playerController.player = player
playerController.allowsPictureInPicturePlayback = true
playerController.delegate = self
playerController.player?.play()
self.present(playerController,animated:true,completion:nil)

Hope this will help
#IBAction func btnPlayAction(_ sender: UIButton) {
if isVideoPlaying{
player.pause()
sender.setTitle("Play", for: .normal)
}else{
player.play()
sender.setTitle("Pause", for: .normal)
}
isVideoPlaying = !isVideoPlaying
}
#IBAction func btnForwardAction(_ sender: UIButton) {
guard let duration = player.currentItem?.duration else { return }
let currentTime = CMTimeGetSeconds(player.currentTime())
let newTime = currentTime + 5.0
if newTime < (CMTimeGetSeconds(duration) - 5.0){
let time: CMTime = CMTimeMake(value: Int64(newTime*1000), timescale: 1000)
player.seek(to: time)
}
}
#IBAction func btnBackwardAction(_ sender: UIButton) {
let currentTime = CMTimeGetSeconds(player.currentTime())
var newTime = currentTime - 5.0
if newTime < 0{
newTime = 0
}
let time: CMTime = CMTimeMake(value: Int64(newTime*1000), timescale: 1000)
player.seek(to: time)
}

Related

How can I prevent the stopwatch from resetting after pause

When I hit Pause it pauses the timer. but then when I hit start, it resets to 0 instead of continue where it left off. How can I fix this?
I've tried adding a new button for reset. that works, but now I can't get the start button to keep counting after a pause. I've been struggling with getting the resume to work.
import UIKit
class TimerViewController: UIViewController {
#IBOutlet weak var lable: UILabel!
#objc var startTime = TimeInterval()
var timer = Timer()
override func viewDidLoad(){
super.viewDidLoad()
}
// Start Button
#IBAction func start(_ sender: UIButton) {
if (!timer.isValid) {
let aSelector = #selector(updateTime)
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate
}
}
// Pause Button
#IBAction func pause(_ sender: UIButton)
{
timer.invalidate()
}
// Reset Button
#IBAction func reset(_ sender: UIButton)
{
timer.invalidate()
lable.text = "00:00:00"
}
#objc func updateTime() {
let currentTime = NSDate.timeIntervalSinceReferenceDate
//Find the difference between current time and start time.
var elapsedTime: TimeInterval = currentTime - startTime
//calculate the minutes in elapsed time.
let minutes = UInt8(elapsedTime / 60.0)
elapsedTime -= (TimeInterval(minutes) * 60)
//calculate the seconds in elapsed time.
let seconds = UInt8(elapsedTime)
elapsedTime -= TimeInterval(seconds)
//find out the fraction of milliseconds to be displayed.
let fraction = UInt8(elapsedTime * 100)
//add the leading zero for minutes, seconds and millseconds and store them as string constants
let strMinutes = String(format: "%02d", minutes)
let strSeconds = String(format: "%02d", seconds)
let strFraction = String(format: "%02d", fraction)
//concatenate minuets, seconds and milliseconds as assign it to the UILabel
lable.text = "\(strMinutes):\(strSeconds):\(strFraction)"
}
}
keep the startTime variable from being modified until it is reset. maybe for example like this
var isReset: Bool = true
#IBAction func start(_ sender: UIButton) {
if (!timer.isValid) {
let aSelector = #selector(updateTime)
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
if isReset {
startTime = NSDate.timeIntervalSinceReferenceDate
isReset = false
}
}
}
#IBAction func reset(_ sender: UIButton) {
timer.invalidate()
lable.text = "00:00:00"
isReset = true
}
Cheers...

Add tone to Countdown Timer in XCODE 8 Swift 3

I am new to coding. I am trying to learn iOS development and I have created a simple countdown timer, that uses a slider to select the amount of time in seconds. I would like to add a tone to the final 9 secs of the countdown. I know how to add audio files, but I have "NO IDEA" how to add a countdown tone to the last 9 seconds of the timer.
#IBAction func startBtnTapped(_ sender: Any) {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.counter), userInfo: nil, repeats: true)
SoundPlayer4.play()
//Hide Start and Slider Buttons
startBtn.isHidden = true
sliderCtrl.isHidden = true
stopBtn.isHidden = false
}
#IBAction func stopBtnTapped(_ sender: Any) {
timer.invalidate()
seconds = 180
sliderCtrl.setValue(180, animated: true)
timerLbl.text = "180"
SoundPlayer3.play()
//Show Start and Slider Buttons
sliderCtrl.isHidden = false
startBtn.isHidden = false
stopBtn.isHidden = true
}
#IBAction func sliderCrtlUsed(_ sender: UISlider) {
seconds = Int(sender.value)
timerLbl.text = String(seconds)
}
func counter(){
seconds -= 1
timerLbl.text = String(seconds)
if (seconds == 0){
timer.invalidate()
//Show Start and Slider Buttons
startBtn.isHidden = false
sliderCtrl.isHidden = false
stopBtn.isHidden = true
//Play Audio
SoundPlayer1.play()
}
}
So can anyone help me add a countdown tone to my project?
Thank you
Put this code in counter function:
if seconds < 10 {
var AudioURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Dog bark", ofType: "mp3")!)
var AudioPlayer = AVAudioPlayer()
do {
AudioPlayer = try AVAudioPlayer(contentsOfURL: AudioURL)
} catch _ as NSError {
fatalError()
}
AudioPlayer.play()
}
Then put the file here:
And that's it!
You can read more on this question here.

Jumpy UISlider when scrubbing - Using UISlider with AVPlayer

I am using AvPlayer and am trying to set up a slider to allow scrubbing of audio files. Im having a problem with the slider jumping all over the place when its selected. It then goes back to the origin position for a second before going back to the location it was dragged to.
You cant see my cursor on the Gif, but the smooth elongated drags are me moving the knob, then the quick whips are the slider misbehaving.
Ive spent hours googling and combing through Stack Overflow and cant figure out what I'm doing wrong here, a lot of similar questions are quite old and in ObjC.
This is the section of code i think is responsible for the problem, it does handle the event of the slider being moved: Ive tried it without the if statement also and didn't see a different result.
#IBAction func horizontalSliderActioned(_ sender: Any) {
horizontalSlider.isContinuous = true
if self.horizontalSlider.isTouchInside {
audioPlayer?.pause()
let seconds : Int64 = Int64(horizontalSlider.value)
let preferredTimeScale : Int32 = 1
let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)
audioPlayerItem?.seek(to: seekTime)
audioPlayer?.play()
} else {
let duration : CMTime = (self.audioPlayer?.currentItem!.asset.duration)!
let seconds : Float64 = CMTimeGetSeconds(duration)
self.horizontalSlider.value = Float(seconds)
}
}
I will include my entire class below for reference.
import UIKit
import Parse
import AVFoundation
import AVKit
class PlayerViewController: UIViewController, AVAudioPlayerDelegate {
#IBOutlet var horizontalSlider: UISlider!
var selectedAudio: String!
var audioPlayer: AVPlayer?
var audioPlayerItem: AVPlayerItem?
var timer: Timer?
func getAudio() {
let query = PFQuery(className: "Part")
query.whereKey("objectId", equalTo: selectedAudio)
query.getFirstObjectInBackground { (object, error) in
if error != nil || object == nil {
print("The getFirstObject request failed.")
} else {
print("There is an object now get the Audio. ")
let audioFileURL = (object?.object(forKey: "partAudio") as! PFFile).url
self.audioPlayerItem = AVPlayerItem(url: NSURL(string: audioFileURL!) as! URL)
self.audioPlayer = AVPlayer(playerItem: self.audioPlayerItem)
let playerLayer = AVPlayerLayer(player: self.audioPlayer)
playerLayer.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
self.view.layer.addSublayer(playerLayer)
let duration : CMTime = (self.audioPlayer?.currentItem!.asset.duration)!
let seconds : Float64 = CMTimeGetSeconds(duration)
let maxTime : Float = Float(seconds)
self.horizontalSlider.maximumValue = maxTime
self.audioPlayer?.play()
self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(PlayerViewController.audioSliderUpdate), userInfo: nil, repeats: true)
}
}
}
#IBOutlet var playerButton: UIButton!
func playerButtonTapped() {
if audioPlayer?.rate == 0 {
audioPlayer?.play()
self.playerButton.setImage(UIImage(named: "play"), for: UIControlState.normal)
} else {
audioPlayer?.pause()
self.playerButton.setImage(UIImage(named: "pause"), for: UIControlState.normal)
}
}
override func viewDidLoad() {
super.viewDidLoad()
horizontalSlider.minimumValue = 0
horizontalSlider.value = 0
self.playerButton.addTarget(self, action: #selector(PlayerViewController.playerButtonTapped), for: UIControlEvents.touchUpInside)
getAudio()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(PlayerViewController.finishedPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.audioPlayerItem)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(animated)
// remove the timer
self.timer?.invalidate()
// remove the observer when leaving page
NotificationCenter.default.removeObserver(audioPlayer?.currentItem! as Any)
}
func finishedPlaying() {
// need option to play next track
self.playerButton.setImage(UIImage(named: "play"), for: UIControlState.normal)
let seconds : Int64 = 0
let preferredTimeScale : Int32 = 1
let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)
audioPlayerItem!.seek(to: seekTime)
}
#IBAction func horizontalSliderActioned(_ sender: Any) {
horizontalSlider.isContinuous = true
if self.horizontalSlider.isTouchInside {
audioPlayer?.pause()
let seconds : Int64 = Int64(horizontalSlider.value)
let preferredTimeScale : Int32 = 1
let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)
audioPlayerItem?.seek(to: seekTime)
audioPlayer?.play()
} else {
let duration : CMTime = (self.audioPlayer?.currentItem!.asset.duration)!
let seconds : Float64 = CMTimeGetSeconds(duration)
self.horizontalSlider.value = Float(seconds)
}
}
func audioSliderUpdate() {
let currentTime : CMTime = (self.audioPlayerItem?.currentTime())!
let seconds : Float64 = CMTimeGetSeconds(currentTime)
let time : Float = Float(seconds)
self.horizontalSlider.value = time
}
}
Swift 5, Xcode 11
I faced the same issue, it was apparently periodicTimeObserver which was causing to return incorrect time which caused lag or jump in the slider. I solved it by removing periodic time observer when the slider was changing and adding it back when seeking completion handler was called.
#objc func sliderValueChanged(_ playbackSlider: UISlider, event: UIEvent){
let seconds : Float = Float(playbackSlider.value)
let targetTime:CMTime = CMTimeMake(value: Int64(seconds), timescale: 1)
if let touchEvent = event.allTouches?.first {
switch touchEvent.phase {
case .began:
// handle drag began
//Remove observer when dragging is in progress
self.removePeriodicTimeObserver()
break
case .moved:
// handle drag moved
break
case .ended:
// handle drag ended
//Add Observer back when seeking got completed
player.seek(to: targetTime, toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] (value) in
self?.addTimeObserver()
}
break
default:
break
}
}
}
you need to remove observers and invalidate timers as soon as user selects the thumb on slider and add them back again when dragging is done
to do add targets like this where you load your player:
mySlider.addTarget(self,
action: #selector(PlayerViewController.mySliderBeganTracking(_:)),
forControlEvents:.TouchDown)
mySlider.addTarget(self,
action: #selector(PlayerViewController.mySliderEndedTracking(_:)),
forControlEvents: .TouchUpInside)
mySlider.addTarget(self,
action: #selector(PlayerViewController.mySliderEndedTracking(_:)),
forControlEvents: .TouchUpOutside )
and remove observers and invalidate timers in mySliderBeganTracking then add observers in mySliderEndedTracking
for better control on what happens in your player write 2 functions : addObservers and removeObservers and call them when needed
Make sure to do the following:
isContinuous for the slider is NOT set to false.
Pause the player before seeking.
Seek to the position and use the completion handler to resume playing.
Example code:
#objc func sliderValueChanged(sender: UISlider, event: UIEvent) {
let roundedValue = sender.value.rounded()
guard let touchEvent = event.allTouches?.first else { return }
switch touchEvent.phase {
case .began:
PlayerManager.shared.pause()
case .moved:
print("Slider moved")
case .ended:
PlayerManager.shared.seek(to: roundedValue, playAfterSeeking: true)
default: ()
}
}
And here is the function for seeking:
func seek(to: Float, playAfterSeeking: Bool) {
player?.seek(to: CMTime(value: CMTimeValue(to), timescale: 1), completionHandler: { [weak self] (status) in
if playAfterSeeking {
self?.play()
}
})
}
Try using the time slider value like below:
#IBAction func timeSliderDidChange(_ sender: UISlider) {
AVPlayerManager.sharedInstance.currentTime = Double(sender.value)
}
var currentTime: Double {
get {
return CMTimeGetSeconds(player.currentTime())
}
set {
if self.player.status == .readyToPlay {
let newTime = CMTimeMakeWithSeconds(newValue, 1)
player.seek(to: newTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero) { ( _ ) in
self.updatePlayerInfo()
}
}
}
}
and pass the value of slider when user release the slider, also don't update the slider value of current playing while user interaction happening on the slider
This is a temporary solution for me, I observed that the rebound is only once, so I set an int value isSeekInProgress:
When sliderDidFinish, isSeekInProgress = 0
In reply to avplayer time change:
if (self.isSeekInProgress > 1) {
float sliderValue = 1.f / (self.slider.maximumValue - self.slider.minimumValue) * progress;
// if (sliderValue > self.slider.value ) {
self.slider.value = sliderValue;
}else {
self.isSeekInProgress += 1;
}

AVPlayer layer showing blank screen when application running or idle for 15 minutes

I would like to create a custom video player using AVPlayer() and AVPlayerLayer() classes.
My code working fine for application fresh start but goes wrong and showing blank screen when the application running or idle for more than 15 minutes.
When the occurrences this issue all classes of my application having AVPlayerLayer showing blank screen. I don't know why this happens.
AVPlayer() and AVPlayerlayer() instances are initialized as below.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
dispatch_async(dispatch_get_main_queue(), {
let playerItem = AVPlayerItem(URL: self.itemUrl)
self.avPlayer = AVPlayer(playerItem: playerItem)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(VideoPreviewView.restartVideoFromBeginning),
name: AVPlayerItemDidPlayToEndTimeNotification,
object: self.avPlayer.currentItem)
self.avPlayerLayer = AVPlayerLayer(player: self.avPlayer)
self.view.layer.addSublayer(self.avPlayerLayer)
self.avPlayerLayer.frame = self.view.bounds
self.avPlayer.pause()
})
}
}
Play function
func playVideo(sender: AnyObject) {
avPlayer.play()
if (avPlayer.rate != 0 && avPlayer.error == nil) {
print("playing")
}
slider.hidden=false
myTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: #selector(VideoPreviewView.updateSlider), userInfo: nil, repeats: true)
slider.addTarget(self, action: #selector(VideoPreviewView.sliderValueDidChange(_: )), forControlEvents: UIControlEvents.ValueChanged)
slider.minimumValue = 0.0
slider.continuous = true
pauseButton.hidden=false
playButton.hidden=true
closeViewButton.hidden = false
}
Restart video
func restartVideoFromBeginning() {
let seconds: Int64 = 0
let preferredTimeScale: Int32 = 1
let seekTime: CMTime = CMTimeMake(seconds, preferredTimeScale)
avPlayer?.seekToTime(seekTime)
avPlayer?.pause()
pauseButton.hidden=true
playButton.hidden = false
closeViewButton.hidden=false
}
func updateSlider() {
if (avPlayer.rate != 0 && avPlayer.error == nil) {
print("playing")
}
else{
print("Not playing.")
}
currentTime = Float(CMTimeGetSeconds(avPlayer.currentTime()))
duration = avPlayer.currentItem!.asset.duration
totalDuration = Float(CMTimeGetSeconds(duration))
slider.value = currentTime // Setting slider value as current time
slider.maximumValue = totalDuration // Setting maximum value as total duration of the video
}
func sliderValueDidChange(sender: UISlider!) {
timeInSecond=slider.value
newtime = CMTimeMakeWithSeconds(Double(timeInSecond), 1)// Setting new time using slider value
avPlayer.seekToTime(newtime)
}
#IBAction func closeViewAction(sender: AnyObject) {
pauseButton.hidden=true
self.avPlayer.pause()
self.avPlayerLayer.removeFromSuperlayer()
self.dismissViewControllerAnimated(true, completion: nil)
}

Audio playback progress as UISlider in Swift

I've seen some posts about accomplishing this in Objective-C but I've been unable to do the same via Swift.
Specifically, I can't figure out how to implement addPeriodicTimeObserverForInterval in the below.
var player : AVAudioPlayer! = nil
#IBAction func playAudio(sender: AnyObject) {
playButton.selected = !(playButton.selected)
if playButton.selected {
let fileURL = NSURL(string: toPass)
player = AVAudioPlayer(contentsOfURL: fileURL, error: nil)
player.numberOfLoops = -1 // play indefinitely
player.prepareToPlay()
player.delegate = self
player.play()
startTime.text = "\(player.currentTime)"
endTime.text = NSString(format: "%.1f", player.duration)
} else {
player.stop()
}
Any assistance would be appreciated.
Thanks to the suggestion of Dare above, here's how I accomplished this:
var updater : CADisplayLink! = nil
#IBAction func playAudio(sender: AnyObject) {
playButton.selected = !(playButton.selected)
if playButton.selected {
updater = CADisplayLink(target: self, selector: Selector("trackAudio"))
updater.frameInterval = 1
updater.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
let fileURL = NSURL(string: toPass)
player = AVAudioPlayer(contentsOfURL: fileURL, error: nil)
player.numberOfLoops = -1 // play indefinitely
player.prepareToPlay()
player.delegate = self
player.play()
startTime.text = "\(player.currentTime)"
theProgressBar.minimumValue = 0
theProgressBar.maximumValue = 100 // Percentage
} else {
player.stop()
}
}
func trackAudio() {
var normalizedTime = Float(player.currentTime * 100.0 / player.duration)
theProgressBar.value = normalizedTime
}
#IBAction func cancelClicked(sender: AnyObject) {
player.stop()
updater.invalidate()
dismissViewControllerAnimated(true, completion: nil)
}
Just to elaborate on my previous comment, this is how I implemented it and it seems to work pretty well. Any Swift corrections are more than welcome, I'm still an Obj-C guy for now.
#IBAction func playAudio(sender: AnyObject) {
var playing = false
if let currentPlayer = player {
playing = player.playing;
}else{
return;
}
if !playing {
let filePath = NSBundle.mainBundle().pathForResource("3e6129f2-8d6d-4cf4-a5ec-1b51b6c8e18b", ofType: "wav")
if let path = filePath{
let fileURL = NSURL(string: path)
player = AVAudioPlayer(contentsOfURL: fileURL, error: nil)
player.numberOfLoops = -1 // play indefinitely
player.prepareToPlay()
player.delegate = self
player.play()
displayLink = CADisplayLink(target: self, selector: ("updateSliderProgress"))
displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode!)
}
} else {
player.stop()
displayLink.invalidate()
}
}
func updateSliderProgress(){
var progress = player.currentTime / player.duration
timeSlider.setValue(Float(progress), animated: false)
}
*I set time slider's range between 0 and 1 on a storyboard
Specifically for Swift I was able to handle it like this:
I set the maximum value of the scrubSlider to the duration of the music file(.mp3) that was loaded in this method
override func viewDidLoad() {
super.viewDidLoad()
do {
try player = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("bach", ofType: "mp3")!))
scrubSlider.maximumValue = Float(player.duration)
} catch {
//Error
}
_ = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(ViewController.updateScrubSlider), userInfo: nil, repeats: true)
}
I player was set to play the music at the time set by the value of the scrubber.
#IBAction func scrub(sender: AnyObject) {
player.currentTime = NSTimeInterval(scrubSlider.value)
}
Syntax has now changed in Swift 4:
updater = CADisplayLink(target: self, selector: #selector(self.trackAudio))
updater.preferredFramesPerSecond = 1
updater.add(to: RunLoop.current, forMode: RunLoopMode.commonModes)
And the function (I have previously set the progressSlider.maxValue to player.duration):
#objc func trackAudio() {
progressSlider.value = Float(player!.currentTime)
}

Resources