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.