AVPlayer currentTime wrong when rewind - swift

I have AVPlayer when I rewind an audio file, then the current time is longer than the total file duration. Who knows what the problem is, why is the current time wrong?
#objc func updateProgressBar(){
guard let value = AppDelegate.avPlayer.currentItem?.currentTime().seconds else { return }
let time = Func.getHoursMinutesSecondsFrom(seconds: value)
DispatchQueue.main.async {
self.startTime.text = time.fullTime
}
}
func durationAudio(){
// расчитывает время аудиозвука
guard let duration = AppDelegate.avPlayer.currentItem?.asset.duration else { return }
let time = Func.getHoursMinutesSecondsFrom(seconds: CMTimeGetSeconds(duration))
DispatchQueue.main.async {
self.endTime.text = time.fullTime
}
}
converts to hours, minutes, seconds
static func getHoursMinutesSecondsFrom(seconds: Double) -> (hours: Int, minutes: Int, seconds: Int, fullTime:String) {
let secs = Int(seconds)
let hours = secs / 3600
let minutes = (secs % 3600) / 60
let seconds = (secs % 3600) % 60
let duration:String!
if hours != 0 {
duration = String(format:"%02d:%02d:%02d", hours, minutes, seconds)
} else {
duration = String(format:"%02d:%02d", minutes, seconds)
}
return (hours, minutes, seconds, duration)
}
rewind audio
func seekTo(completion:Bool){
let duration = CMTimeGetSeconds(AppDelegate.avPlayer.currentItem!.asset.duration)
let value = self.sliderSong.value
let durationToSeek = Float(duration) * value
let timeScale = AppDelegate.avPlayer.currentItem!.duration.timescale
AppDelegate.avPlayer.seek(to: CMTimeMakeWithSeconds(Float64(durationToSeek), preferredTimescale: timeScale), toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) { [weak self](bool) in
guard completion else { return }
self?.seeking = false
}
}

Related

Getting blank screen in avVideo player while entering to foreground in uitableviewCell

I am using AVPlayer for a custom video player and getting a blank screen while I enter from the background.
func playSingleVideo(pauseAll: Bool = false,foreground:Bool = false) {
if let visibleCells = self.tableView.visibleCells as? [VideoCell], !visibleCells.isEmpty {
if pauseAll {
visibleCells.forEach { $0.playerLayer?.player?.pause() }
} else {
var maxHeightRequired: Int = 50
var cellToPlay: VideoCell?
visibleCells.reversed().forEach { (cell) in
let cellBounds = self.view.convert(cell.videoView.frame, from: cell.videoView)
let visibleCellHeight = Int(self.view.frame.intersection(cellBounds).height)
if visibleCellHeight >= maxHeightRequired {
maxHeightRequired = visibleCellHeight
cellToPlay = cell
}
}
visibleCells.forEach { (cell) in
if cell === cellToPlay {
cell.slider.minimumValue = 0.0
cell.playerLayer?.player?.play()
cell.btnPlay.setImage(UIImage(named: "pause-button") , for: .normal)
cell.btnPlay.setTitle("", for: .normal)
// cell.videoView.layer.insertSublayer(cell.playerLayer!, at: 0)
} else {
cell.playerLayer?.player?.pause()
cell.btnPlay.setTitle("", for: .normal)
// cell.videoView.layer.insertSublayer(cell.playerLayer!, at: 0)
cell.slider.minimumValue = 0.0
cell.btnPlay.setImage(UIImage(named: "play-button") , for: .normal)
}
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: cell.playerLayer?.player?.currentItem, queue: .main) { [weak self] _ in
cell.playerLayer?.player?.seek(to: kCMTimeZero)
cell.playerLayer?.player?.play()
}
let interval = CMTime(value: 1, timescale: 2)
cell.playerLayer?.player?.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { (progressTime) in
let seconds = CMTimeGetSeconds(progressTime)
let secondString = String(format: "%02d", Int(seconds) % 60)
let minutString = String(format: "%02d", Int(seconds) / 60)
print( "\(minutString):\(secondString)")
cell.lblElapsed.text = "\(minutString):\(secondString)"
cell.playerLayer?.player?.currentItem?.preferredForwardBufferDuration = TimeInterval(exactly: 100)!
cell.videoView.layer.insertSublayer(cell.playerLayer!, at: 0)
guard let duration = cell.playerLayer?.player?.currentItem?.duration else { return }
let seconds2 = CMTimeGetSeconds(duration)
if !seconds2.isNaN{
let sec = String(format: "%02d", Int(seconds2) % 60)
let min = String(format: "%02d", Int(seconds2) / 60)
print("\(min):\(sec)")
cell.lblTotal.text = "\(min):\(sec)"
let totsec = seconds / seconds2
if !self.isended{
cell.slider.setValue(Float(totsec), animated: true)
}
}
})
}
}
}
}

Every time I click the button it only adds 50 increments,

When I'm on the simulator and click the button it keeps adding 50. I think it may not be saving "latestTerminationDate", or maybe not pulling it. Thank you for any help
#IBOutlet weak var coalRunButton: UIButton!
var coalMayham = 1
#IBAction func coalRunButton(_ sender: Any) {
if let buttonPress = UserDefaults.standard.object(forKey: "buttonPress") as? Date,
let terminationDate = UserDefaults.standard.object(forKey: "latestTerminationDate") as? Date {
var terminationDuration = buttonPress.timeIntervalSince(terminationDate)
if terminationDuration >= 86400 {
_ = 86400
do { UserDefaults.standard.set(Date(), forKey: "latestTerminationDate")
UserDefaults.standard.synchronize()
let dailyCoalAccumulate = ((Int(terminationDuration)) * coalMayham) + Int(coalPile.text!)!
coalPile.text = String(dailyCoalAccumulate)
UserDefaults.standard.set(Data(), forKey:"totalCoal")
}
} else {
terminationDuration = 50
UserDefaults.standard.set(Date(), forKey: "latestTerminationDate")
UserDefaults.standard.synchronize()
let dailyCoalAccumulate = ((Int(terminationDuration)) * coalMayham) + Int(coalPile.text!)!
coalPile.text = String(dailyCoalAccumulate)
UserDefaults.standard.set(Data(), forKey:"totalCoal")
}
} else {
let terminationDuration = 40
UserDefaults.standard.set(Date(), forKey: "latestTerminationDate")
UserDefaults.standard.synchronize()
let dailyCoalAccumulate = ((Int(terminationDuration)) * coalMayham) + Int(coalPile.text!)!
coalPile.text = String(dailyCoalAccumulate)
UserDefaults.standard.set(Data(), forKey:"totalCoal")
}
}
This line of code:
let terminationDate = UserDefaults.standard.object(forKey: "latestTerminationDate") as? Date
Is not returning what you think it does. Because in this line of code:
UserDefaults.standard.set(Date(), forKey: "latestTerminationDate")
the Date() you are entering looks like: "2018-10-04 15:30:22 +0000"
which can not be compared to 86400
For that reason:
if terminationDuration >= 86400
fails and reverts to:
else {
terminationDuration = 50
EDIT
FIX EXAMPLE:
func getMillisecondsNow() -> Int64{
let currentDate = Date()
return getMillisecondsFromDate(date: currentDate)
}
func getMillisecondsFromDate(date: Date) -> Int64{
var d : Int64 = 0
let interval = date.timeIntervalSince1970
d = Int64(interval * 1000)
return d
}
func getTimeDifferenceFromNowInMilliseconds(time: Int64) -> Int64{
let now = getMillisecondsNow()
let diff: Int64 = now - time
return diff
}
This will be in milliseconds so you will need to use 86400000 (milliseconds in 24 hrs) instead of 86400 (seconds in 24 hrs)
Now in your code simply use this to save the termination time in Milliseconds:
let now = getMillisecondsNow()
UserDefaults.standard.set(now, forKey: "latestTerminationDate")
and this code to retrieve and get the time difference in Milliseconds:
let terminationTime = UserDefaults.standard.object(forKey: "latestTerminationDate") as? Int64
let timeDiff = getTimeDifferenceFromNowInMilliseconds(time: terminationTime)
if timeDiff >= 86400000 {
}
else {
}

Switch from Countdown to Countup

stackoverflow is my last chance. I hope you can help me. I have a countdown in my app which is set to 2 hours and 30 minutes. When it hits 0 it starts to count up to 30 minutes. After the 30 minutes are over, the countdown is back at 2:30. Everything is working fine except the part when the timer is switching from the countdown part to the countup part. Here is the problem: The timer is going negative when hitting zero.
#objc func startTimer() {
print("Countdown : \(totalTime)")
countdownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}
#objc func updateTime() {
if (totalTime > 10800) {
totalTime = totalTime - 10800
}
totalTime -= 1
var time = totalTime
//When in session...
if (time > 9000) {
//Calculate CountUP
time = 10800 - time
//if session has started
if(isSessionActive == false) {
isSessionActive = true
lastFiveMinutes = false
self.setSessionStarted()
}
} else {
//If session has ended...
if(isSessionActive == true) {
isSessionActive = false
lastFiveMinutes = false
self.setSessionEnded()
}
//If last 5 minutes have started
if(time < 300 && lastFiveMinutes == false) {
lastFiveMinutes = true
self.setLastFiveMinutes()
}
}
countdownLabel.text = "\(timeFormatted(time))"
//update Participant Count every x second, ONLY if in last 5 Minutes or Session is active
if (lastFiveMinutes == true || isSessionActive == true) {
if (totalTime % 5 == 0) {
setParticipants(n: httpController.getPar())
}
} else {
setParticipants(n: "...")
}
}
The part "var time = totalTime" is not necessary, i know.
In Android the code is exactly the same except the -=1 part, but it is working.
* Update *
Inital Value of totalTime:
#objc var totalTime = 0
And here is the next part where it gets a real value.
#objc func initCountdown() {
var currentSec = 0.0
// Get Current Time from Server
serverTimeReturn { (getResDate) -> Void in
let dFormatter = DateFormatter()
dFormatter.dateStyle = DateFormatter.Style.long
dFormatter.timeStyle = DateFormatter.Style.long
dFormatter.timeZone = NSTimeZone(abbreviation: "GMT")! as TimeZone
let dateGet = dFormatter.string(from: getResDate! as Date)
currentSec = Double((getResDate?.timeIntervalSince1970)!)
print("Formatted Hour : \(dateGet)")
let todaySec = Int(currentSec) % 86400
print("Today Sec Hour : \(todaySec)")
let countDownSec = 10800 - todaySec % 10800
// Check status to init
if(countDownSec > 9000) {
self.isSessionActive = true
self.setSessionStarted()
} else {
self.setSessionEnded()
}
if(countDownSec < 300) {
self.setLastFiveMinutes()
self.lastFiveMinutes = true
}
self.totalTime = countDownSec
}
print("Formatted Hour : \(totalTime)")
startTimer()
}
I don't see any code where you count upwards so I am not surprised it isn't working. I would introduce a boolean property isCountingDown to keep track of how to handle the timer and then have two separate methods for up and down
var isCountingDown: boolean
#objc func initCountdown() {
//existing code
isCountingDown = true
startTimer()
}
#objc func updateTime() {
if isCountingDown {
doCountDown()
} else {
doCountUp()
}
}
And then in doCountDown() and doCountUp() I would check against time is 0 or 30 and update isCountingDown accordingly
func doCountDown {
if (totalTime > 10800) {
totalTime = totalTime - 10800
}
totalTime -= 1
if totalTime == 0 {
isCountingDown = false
//Maybe return here?
}
var time = totalTime
//rest of code
}
func doCountUp() {
totalTime += 1
if totalTime == 30 {
isCountingDown = true
//Maybe return here?
}
//rest of code
}

Slider Crashes program on Iphone, but worked on simulator

I am using this code to track and update slider value, then convert to string and show on label. The issue is that, This code works on the simulator, but crashed on the actual phone. With error "unexpectedly found nil while unwrapping an Optional value". I can't figure out what could cause this, any help would be appreciated.
func updateSlider () {
sliderBar.value = Float(CMTimeGetSeconds(self.audioPlayer.currentItem!.currentTime()))
let currentTime = Int((CMTimeGetSeconds(self.audioPlayer.currentItem!.currentTime())))
let duration = Int((CMTimeGetSeconds(self.audioPlayer.currentItem!.asset.duration)))
//let total = currentTime - duration
let minutes = currentTime/60
let seconds = currentTime - minutes * 60
let minutes2 = duration/60
let seconds2 = duration - minutes2 * 60
self.lblPastTime.text = NSString(format: " %02d:%02d / %02d:%02d ",minutes2,seconds2, minutes,seconds) as String
}
Avoiding force unwrapping variables is a good way to avoid errors like that:
func updateSlider () {
guard let currentItem = self.audioPlayer.currentItem else { return }
sliderBar.value = Float(CMTimeGetSeconds(currentItem.currentTime()))
let currentTime = Int((CMTimeGetSeconds(currentItem.currentTime())))
let duration = Int((CMTimeGetSeconds(currentItem.asset.duration)))
//let total = currentTime - duration
let minutes = currentTime/60
let seconds = currentTime - minutes * 60
let minutes2 = duration/60
let seconds2 = duration - minutes2 * 60
self.lblPastTime.text = NSString(format: " %02d:%02d / %02d:%02d ",minutes2,seconds2, minutes,seconds) as? String
}
Using guard let I safely unwrap the variable, so if it's nil, the function returns.
ANSWER
Had to use audioPlayer.currentTime()
Instead of audioPlayer.currentItem!.currentTime()
func updateSlider () {
sliderBar.value = Float(CMTimeGetSeconds(audioPlayer.currentTime()))
// sliderBar.value = Float(CMTimeGetSeconds(self.audioPlayer.currentItem!.currentTime()))
let currentTime = Int((CMTimeGetSeconds(self.audioPlayer.currentItem!.currentTime())))
let duration = Int((CMTimeGetSeconds(self.audioPlayer.currentItem!.asset.duration)))
//let total = currentTime - duration
let minutes = currentTime/60
let seconds = currentTime - minutes * 60
let minutes2 = duration/60
let seconds2 = duration - minutes2 * 60
self.lblPastTime.text = NSString(format: " %02d:%02d / %02d:%02d ",minutes2,seconds2, minutes,seconds) as String
}

how to get current time in AVAudioplayer in a label while playing

I tried to build an app to play the MP3 files. I want to get current time and duration to show it in 2 labels while playing.
I used this code:
let duration = Int((player1?.duration)!)
let minutes2 = duration/60
let seconds2 = duration - minutes2 * 60
durLabel.text = NSString(format: "%02d:%02d", minutes2,seconds2) as String
let currentTime1 = Int((player1?.currentTime)!)
let minutes = currentTime1/60
let seconds = currentTime1 - minutes * 60
curLabel.text = NSString(format: "%02d:%02d", minutes,seconds) as String
In duration it shows the half time of the song.
For example:
If the duration of the song is 20 minutes and 40 seconds, it shows the half like that 10:20. But it did not make progress in the case of current time, it shows 00:00
Thanks at all.
// this is to compute and show remaining time
let duration = Int((player1?.duration - (player1?.currentTime))!)
let minutes2 = duration/60
let seconds2 = duration - minutes2 * 60
durLabel.text = NSString(format: "%02d:%02d", minutes2,seconds2) as String
//This is to show and compute current time
let currentTime1 = Int((player1?.currentTime)!)
let minutes = currentTime1/60
let seconds = currentTime1 - minutes * 60
curLabel.text = NSString(format: "%02d:%02d", minutes,seconds) as String
The player's current time will not automatically update. Instead, you have to periodically update the current time using a timer. Take a look at the following thread if you want more details.
check avaudioplayer's current playback time
If you have Slider to seek on mp3 file, like this :
#IBOutlet weak var slidetOutlet: UISlider!
You can update your UILabel like this:
self.lblPlayerTime.text = self.secondsToHoursMinutesSeconds(seconds: Double( self.slidetOutlet.value ))
to initial your Player and Slider :
func playerInit() {
guard let url = URL(string: urlAudio) else {
return
}
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession is Active")
playerItem = AVPlayerItem(url: url)
player = AVPlayer(playerItem: playerItem)
player!.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, 1), queue: DispatchQueue.main) { (CMTime) -> Void in
if self.player!.currentItem?.status == .readyToPlay {
let time : Float64 = CMTimeGetSeconds(self.player!.currentTime());
self.slidetOutlet.value = Float ( time )
self.slidetOutlet.minimumValue = 0
let duration : CMTime = self.playerItem!.asset.duration
let seconds : Float64 = CMTimeGetSeconds(duration)
self.slidetOutlet.maximumValue = Float(seconds)
self.lblStartTimePlayer.text = self.secondsToHoursMinutesSeconds(seconds: seconds)
self.lblPlayerTime.text = self.secondsToHoursMinutesSeconds(seconds: Double( self.slidetOutlet.value ))
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
and for convert the time :
func secondsToHoursMinutesSeconds (seconds : Double) -> (String) {
let (hr, minf) = modf (seconds / 3600)
let (min, secf) = modf (60 * minf)
return ("\(Int(hr)):\(Int(min)):\(Int(60 * secf))")
}