-1
  1. I want to know how to properly get the total minutes and seconds of a song. And once I get the total minutes and seconds I want it to start to countdown the time.Just show up 2 decimal places for seconds and two places for minutes.
  2. I want to know how to count up the song from the beginning and also would like to show 2 decimal places for seconds and 2 places for minutes

Thanks in advance

@objc func timerUpdate() {

    if (player != nil && player!.currentTime > 0.0) {
        increaseTime ()
        DecreaseTime()
        progressBar.setProgress(Float(player!.currentTime / player!.duration), animated:  false)

    }
    else {
        timer.invalidate()

    }
}
var totalOfTime :Double?
var minutes : Double?
var seconds : Double?
func changeSecondToMinutes() {
    minutes = player!.duration / 60.0

   seconds = minutes! / 60
   seconds! *= 100
    var rightOfSecods = Int(round(seconds!))
    seconds = Double(rightOfSecods) * 00.01
    print(seconds!)

}
var time  = 0
var minut = 0
func increaseTime () {

    startTime.text =  "\(Double(minut))   "
    print(minut)
    print(time)

}
var secondos = 0.0
func DecreaseTime() {
    seconds! -= 0.010


    print(seconds!)
    print(minutes!)
    if Double(round(10000*seconds!)/10000) == 0.00 {

        seconds! += 0.60
        minutes! -= 1
    }
    time += 1
    print(secondos)
    if time == 61 {
       minut += 1
       time = 0

    }

}
func PlayerFunction() {
 changeSecondToMinutes()

            Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerUpdate), userInfo: nil, repeats: true)
 }
Somebody
  • 67
  • 1
  • 9

2 Answers2

1

I'm using this singleton class in my project

class MediaPlayerManager: NSObject {

static let shared = MediaPlayerManager()

private var _player = AVPlayer()
private var _timer = Timer()
private var _songURL = ""
private var _isPaused = false
private var _isPlaying = false
private var _isStopped = false
private var _isRepeatEnabled = false
private var _isShuffleEnabled = false
private var _songProgressed : Float = 0.0
private var _observer: Any!
private var _rate: Float = 1.0

var songURL : String {
    set { _songURL = newValue }
    get { return _songURL }
}

var isPaused : Bool {
    set { _isPaused = newValue }
    get { return _isPaused }
}

var isPlaying : Bool {
    set { _isPlaying = newValue }
    get { return _isPlaying }
}

var isRepeatEnabled : Bool {
    set { _isRepeatEnabled = newValue }
    get { return _isRepeatEnabled }
}

var isShuffleEnabled : Bool {
    set { _isShuffleEnabled = newValue }
    get { return _isShuffleEnabled }
}

var songProgressed : Float {
    set { _songProgressed = newValue }
    get { return _songProgressed }
}

var rate : Float {
    set {
        _rate = newValue
        _player.rate = _rate
    }
    get { return _rate }
}

override init() {
    super.init()
    do {
        try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers, .allowAirPlay])
        print("Playback OK")
        try AVAudioSession.sharedInstance().setActive(true)
        print("Session is Active")
    } catch {
        print(error)
    }
    _player = AVPlayer.init()
}

func playSong() {
    if isPaused {
        _player.play()
        isPlaying = true
        isPaused = false
        return
    }
    isPlaying = true
    isPaused = false
    let url = URL(string: self.songURL)
    let item = AVPlayerItem(url: url!)
    _player = AVPlayer(playerItem: item)
    _player.play()
    isPlaying = true
    isPaused = false
    addObserver()
}

func playSong(urlString: String) {

    let url = URL(string: urlString)
    songURL = urlString
    if isPaused {
        _player.play()
        isPlaying = true
        isPaused = false
        return
    }
    isPlaying = true
    isPaused = false
    let item = AVPlayerItem(url: url!)
    _player = AVPlayer(playerItem: item)
    _player.play()
    addObserver()
}

func addObserver() {
    _player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil)
    _player.currentItem!.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .new, context: nil)
    _player.currentItem!.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
    _player.currentItem!.addObserver(self, forKeyPath: "playbackBufferFull", options: .new, context: nil)

    let interval = CMTime(value: 1, timescale: 2)
    _observer = _player.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { (progressTime) in
        self.scheduleTimer()
        let seconds = CMTimeGetSeconds(progressTime)
        let secondsString = String(format: "%02d", Int(seconds.truncatingRemainder(dividingBy: 60.0)))
        let minutesString = String(format: "%02d", Int(seconds / 60))

        print( "\(minutesString):\(secondsString)" )
        self.songProgressed = Float( seconds )

    })
}

func pauseSong() {
    if isPlaying {
        isPlaying = false
        isPaused = true
        _player.pause()
    }
}

func stopSong() {
    isPlaying = false
    isPaused = false
    invalidateTimer()
    if let observer = _observer {
        _player.removeTimeObserver(observer)
        _observer = nil
    }
    _player = AVPlayer.init()
}

func reset() {
    songURL = ""
    stopSong()
}

func seekTo(Time time: Double) {
    let seconds : Int64 = Int64(time)
    let targetTime: CMTime = CMTimeMake(value: seconds, timescale: 1)

    _player.seek(to: targetTime)
}

func getRemainingTime() -> Double {
    let duration = _player.currentItem!.duration.seconds //total time
    let currentTime = _player.currentTime().seconds //playing time

    let remainingTime = duration - currentTime
    return (remainingTime.isNaN || remainingTime.isInfinite) ? 0 : remainingTime
}

func getSongDuration() -> Double {
    return (_player.currentItem!.duration.seconds.isNaN || _player.currentItem!.duration.seconds.isInfinite) ? 0 : _player.currentItem!.duration.seconds
}

func getSongProgress() -> Double {
    return (Double(self.songProgressed).isNaN || Double(self.songProgressed).isInfinite) ? 0 : Double(self.songProgressed)
}

private func scheduleTimer() {
    if (!_timer.isValid) {
        _timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)

    }
}

private func invalidateTimer() {
    _timer.invalidate()
}

@objc func updateTimer() {
    NotificationCenter.default.post(name: .didReceiveRemainingTime, object: nil, userInfo: [Notification.Name.didReceiveRemainingTime: getRemainingTime()])
    NotificationCenter.default.post(name: .didReceiveElapsedTime, object: nil, userInfo: [Notification.Name.didReceiveElapsedTime: getSongProgress()])
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    switch keyPath {
    case "rate":
        if let rate = change?[NSKeyValueChangeKey.newKey] as? Float {
            if rate == 0.0 {
                print("playback stopped")
                isPlaying = false
                isPaused = true
                invalidateTimer()
            }
            if rate == 1.0 {
                print("normal playback")
            }
            if rate == -1.0 {
                print("reverse playback")
            }
        }
    case "playbackBufferEmpty":
        // Show loader
        print("buffer empty")
    case "playbackLikelyToKeepUp":
        // Hide loader
        print("buffer")
    case "playbackBufferFull":
        // Hide loader
        print("buffer full")
    case .none:
        break
    case .some(_):
        break
    }
}

}

Here is how you can use this class and get the remaining time

override func viewDidLoad() {
    super.viewDidLoad()
    addMediaPlayerObservers()
}

func addMediaPlayerObservers(){
    NotificationCenter.default.addObserver(self, selector: #selector(didReceiveRemainingTime), name: NSNotification.Name.didReceiveRemainingTime, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(didReceiveElapsedTime), name: NSNotification.Name.didReceiveElapsedTime, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(didSongFinished(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
}

func playSong() {
    MediaPlayerManager.shared.playSong(urlString: "song url here")
}

@objc func didReceiveRemainingTime(_ notification: NSNotification) {
    if let remainingTime = notification.userInfo?[NSNotification.Name.didReceiveRemainingTime] as? Double {
//            print("Remaining time from observer: \(remainingTime)")

        self.hmsFrom(seconds: (Int)(remainingTime)) { (hours, minutes, seconds) in
            print("hours: \(hours)")
            print("minutes: \(minutes)")
            print("seconds: \(seconds)")
        }
    }
}
@objc func didReceiveElapsedTime(_ notification: NSNotification) {
    if let elapsedTime = notification.userInfo?[NSNotification.Name.didReceiveElapsedTime] as? Double {
 //            print("Elapsed time from observer: \(elapsedTime)")

        self.hmsFrom(seconds: (Int)(elapsedTime)) { (hours, minutes, seconds) in
            print("hours: \(hours)")
            print("minutes: \(minutes)")
            print("seconds: \(seconds)")
        }
    }
}
@objc func didSongFinished(_ notification: NSNotification) {
    print("Finished")
    MediaPlayerManager.shared.stopSong()
}

This function actually convert total seconds into hours, minutes and seconds respectively

func hmsFrom(seconds: Int, completion: @escaping (_ hours: Int, _ minutes: Int, _ seconds: Int)->()) {
    completion(seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)   
}

Here is the Notification extension

extension Notification.Name {
    static let didReceiveRemainingTime = Notification.Name("didReceiveRemainingTime")
    static let didReceiveElapsedTime = Notification.Name("didReceiveElapsedTime")
}

Hope you get the desired result.

  • HI, how can i convert the player.duration into CMTimeGetSeconds, I am having trouble with that, please provide code for it thanks in advance – Somebody May 30 '19 at 21:06
  • let time = CMTimeGetSeconds(_player.currentItem!.duration) – Muhammad Maaz Ul Haq May 31 '19 at 11:07
  • if let duration = player?.duration { print(duration) let totalOfSeconds = CMTimeGetSeconds(duration) } also I tried calling player.currentItem.duration and it does not have a currentItem property, I am using AVAudioPlayer? as my player – Somebody May 31 '19 at 18:04
  • guard let player = player else { return } let remainingTime = Int(player.duration - player.currentTime) print(remainingTime) let remainingTimeCMTimeObj = CMTimeGetSeconds(CMTime(seconds: Double(remainingTime), preferredTimescale: CMTimeScale.init(Double(remainingTime)))) print(remainingTimeCMTimeObj) – Muhammad Maaz Ul Haq Jun 01 '19 at 00:58
  • remainingTime is Integer and remainingTimeCMTimeObj is CMTimeGetSeconds as you required, where as player is AVAudioPlayer – Muhammad Maaz Ul Haq Jun 01 '19 at 00:58
  • Okay thanks I’ll type it in my code and see if it works thanks – Somebody Jun 01 '19 at 22:18
0

Looks like a good case for DateComponenetsFormatter combined with some casting.

var player : AVPlayerItem? //Assuming you initialized this correctly.
var timer : Timer?

@objc func timerUpdate() {

    if let currentTime = player?.currentTime(), let duration = player?.duration, duration != CMTime.zero {

        let timeLeft = duration - currentTime

        //https://stackoverflow.com/a/43890305/5153744
        let formatter = DateComponentsFormatter()
        formatter.allowedUnits = [.hour, .minute, .second]
        formatter.unitsStyle = .positional

        let formattedCurrentTime = formatter.string(from: TimeInterval(CMTimeGetSeconds(currentTime)))!
        let formattedTimeLeft = formatter.string(from: TimeInterval(CMTimeGetSeconds(timeLeft)))!
        let formattedTimeDuration = formatter.string(from: TimeInterval(CMTimeGetSeconds(duration)))!

        print("Current Time: \(formattedCurrentTime)")
        print("Time Left: \(formattedTimeLeft)")
        print("TotalDuration: \(formattedTimeDuration)")
    }
    else {
        timer?.invalidate()
    }
}

func playerFunction() {
    timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerUpdate), userInfo: nil, repeats: true)
}
NSGangster
  • 2,397
  • 12
  • 22
  • player is var player: AVAudioPlayer? should I Change it for AVPlayerItem? – Somebody May 24 '19 at 18:21
  • No, I didn't know what kind of asset it was so I just had to make an assumption based on your code since you didn't post your instantiation. `AVPlayerItem` has those same properties I used to calculate, so no you do not need to. – NSGangster May 24 '19 at 20:15
  • it won't allow to convert CMTimeGetSeconds(currentTime) how can I do that is it because it is a AVAudioPlayer? – Somebody May 28 '19 at 20:23