2 // PlayerController.swift
5 // Created by Pawel Dabrowski on 21/09/2018.
6 // Copyright © 2018 Fundacja Nowoczesna Polska. All rights reserved.
13 protocol PlayerControllerDelegate: class {
14 func playerControllerDelegateTrackChanged()
15 func playerControllerDelegateUpdatePlayerProgress(timeInterval: TimeInterval)
16 func playerControllerDelegatePlayStateChanged()
20 class PlayerController: UIResponder, AVAudioPlayerDelegate {
21 static let shared = PlayerController()
26 let commandCenter = MPRemoteCommandCenter.shared()
27 commandCenter.playCommand.isEnabled = true
28 commandCenter.pauseCommand.isEnabled = true
29 commandCenter.nextTrackCommand.isEnabled = true
30 commandCenter.previousTrackCommand.isEnabled = true
31 commandCenter.nextTrackCommand.addTarget(self, action: #selector(PlayerController.nextTrackCommand))
32 commandCenter.previousTrackCommand.addTarget(self, action: #selector(PlayerController.previousTrackCommand))
33 commandCenter.playCommand.addTarget(self, action: #selector(PlayerController.playCommand))
34 commandCenter.pauseCommand.addTarget(self, action: #selector(PlayerController.pauseCommand))
35 if #available(iOS 9.1, *) {
36 commandCenter.changePlaybackPositionCommand.addTarget(self, action: #selector(PlayerController.changePlaybackPositionCommand(event:)))
38 // Fallback on earlier versions
42 weak var delegate: PlayerControllerDelegate?
44 private(set) var currentBookDetails: BookDetailsModel? {
46 if let currentBookDetails = currentBookDetails {
47 audiobookMediaModels = currentBookDetails.getAudiobookMediaModels()
48 if currentBookDetails.currentAudioChapter < audiobookMediaModels!.count {
49 currentAudiobookIndex = currentBookDetails.currentAudioChapter
52 currentAudiobookIndex = 0
56 audiobookMediaModels = nil
57 currentAudiobookIndex = 0
62 private var audiobookMediaModels: [MediaModel]?
63 private(set) var currentAudiobookIndex: Int = 0
64 var audioPlayer:AVAudioPlayer? = nil
67 var currentAudioPath: URL?
68 private(set) var artwork: MPMediaItemArtwork?{
74 func setCoverImage(image: UIImage){
75 artwork = MPMediaItemArtwork(image: image)
78 func getCurrentTime() -> TimeInterval {
79 guard let audioPlayer = audioPlayer else { return 0 }
80 return audioPlayer.currentTime
83 func isPlaying() -> Bool {
84 guard let audioPlayer = audioPlayer else { return false }
85 return audioPlayer.isPlaying
91 currentBookDetails = nil
94 func preparePlayer(bookDetailsModel: BookDetailsModel, delegate: PlayerControllerDelegate, trackIndex: Int?) {
97 self.delegate = delegate
98 currentBookDetails = bookDetailsModel
99 if let trackIndex = trackIndex {
100 currentAudiobookIndex = trackIndex
102 else if bookDetailsModel.currentAudioChapter < audiobookMediaModels!.count {
103 currentAudiobookIndex = bookDetailsModel.currentAudioChapter
106 delegate.playerControllerDelegateTrackChanged()
109 func startOrContinuePlaying(bookDetailsModel: BookDetailsModel, delegate: PlayerControllerDelegate) {
110 guard let audioPlayer = audioPlayer, let currentBookDetails = currentBookDetails, currentBookDetails.slug == bookDetailsModel.slug else {
111 startPlaying(bookDetailsModel: bookDetailsModel, delegate: delegate, trackIndex: nil)
116 self.delegate = delegate
117 if audioPlayer.isPlaying {
118 delegate.playerControllerDelegateTrackChanged()
127 func startPlaying(bookDetailsModel: BookDetailsModel, delegate: PlayerControllerDelegate, trackIndex: Int?) {
128 preparePlayer(bookDetailsModel: bookDetailsModel, delegate: delegate, trackIndex: trackIndex)
132 func getCurentAudiobookMediaModel() -> MediaModel? {
133 guard let audiobookMediaModels = audiobookMediaModels, audiobookMediaModels.count > currentAudiobookIndex else { return nil }
135 return audiobookMediaModels[currentAudiobookIndex]
138 func updateMediaInfo(){
139 guard let audioPlayer = audioPlayer, let model = getCurentAudiobookMediaModel() else { return }
141 let artistName = model.artist
142 let songName = model.name
144 if let artwork = artwork {
145 MPNowPlayingInfoCenter.default().nowPlayingInfo = [MPMediaItemPropertyArtist : artistName, MPMediaItemPropertyTitle : songName, MPMediaItemPropertyPlaybackDuration: audioLength, MPNowPlayingInfoPropertyElapsedPlaybackTime: audioPlayer.currentTime, MPMediaItemPropertyArtwork: artwork]
148 MPNowPlayingInfoCenter.default().nowPlayingInfo = [MPMediaItemPropertyArtist : artistName, MPMediaItemPropertyTitle : songName, MPMediaItemPropertyPlaybackDuration: audioLength, MPNowPlayingInfoPropertyElapsedPlaybackTime: audioPlayer.currentTime]
152 // Prepare audio for playing
154 guard let currentBook = currentBookDetails, let currentMediaModel = getCurentAudiobookMediaModel() else { return }
156 guard let path = NSObject.audiobookPathIfExists(audioBookUrlString: currentMediaModel.url, bookSlug: currentBook.slug) else { return }
158 currentAudioPath = path
159 audioPlayer = try? AVAudioPlayer(contentsOf: path)
161 guard let audioPlayer = audioPlayer else { return }
164 //keep alive audio at background
165 try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
169 try AVAudioSession.sharedInstance().setActive(true)
172 UIApplication.shared.beginReceivingRemoteControlEvents()
173 audioPlayer.delegate = self
174 audioLength = audioPlayer.duration
175 audioPlayer.prepareToPlay()
179 //MARK:- Player Controls Methods
183 saveCurrentTrackNumber()
185 delegate?.playerControllerDelegateTrackChanged()
188 func playNextAudio(){
189 guard let audiobookMediaModels = audiobookMediaModels, currentAudiobookIndex < (audiobookMediaModels.count - 1) else { return }
191 currentAudiobookIndex += 1
197 func playPreviousAudio(){
199 guard currentAudiobookIndex > 0 else { return }
201 currentAudiobookIndex -= 1
208 guard let audioPlayer = audioPlayer, audioPlayer.isPlaying else { return }
209 if audioPlayer.currentTime + 12 < audioPlayer.duration {
210 audioPlayer.currentTime += 10
219 guard let audioPlayer = audioPlayer, audioPlayer.isPlaying else { return }
220 if audioPlayer.currentTime > 10 {
221 audioPlayer.currentTime -= 10
229 func pauseAudioPlayer(){
232 delegate?.playerControllerDelegatePlayStateChanged()
235 func playAudioPlayer(){
238 delegate?.playerControllerDelegatePlayStateChanged()
241 func changeTime(timeInterval: TimeInterval) {
242 guard let audioPlayer = audioPlayer else { return }
243 if audioLength > timeInterval {
244 audioPlayer.currentTime = timeInterval
249 func saveCurrentTrackNumber(){
250 guard let bookDetailsModel = currentBookDetails else { return }
252 DatabaseManager.shared.updateCurrentChapters(bookDetailsModel: bookDetailsModel, currentChapter: nil, totalChapters: nil, currentAudioChapter: currentAudiobookIndex, totalAudioChapters: nil)
258 timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(PlayerController.update(_:)), userInfo: nil,repeats: true)
261 else if timer!.isValid == false {
265 timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(PlayerController.update(_:)), userInfo: nil,repeats: true)
275 @objc func update(_ timer: Timer){
276 guard let audioPlayer = audioPlayer else { return }
277 if !audioPlayer.isPlaying{
281 delegate?.playerControllerDelegateUpdatePlayerProgress(timeInterval: audioPlayer.currentTime)
285 /* audioPlayerDidFinishPlaying:successfully: is called when a sound has finished playing. This method is NOT called if the player is stopped due to an interruption. */
286 public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
292 @objc func changePlaybackPositionCommand(event: MPChangePlaybackPositionCommandEvent) -> MPRemoteCommandHandlerStatus{
293 guard let audioPlayer = audioPlayer else { return .commandFailed }
295 if audioPlayer.duration - 5 < event.positionTime && audioPlayer.duration > 5 {
296 audioPlayer.currentTime = audioPlayer.duration - 5
299 audioPlayer.currentTime = event.positionTime
305 @objc func nextTrackCommand() -> Void{
309 @objc func previousTrackCommand() -> Void{
313 @objc func playCommand() -> Void{
317 @objc func pauseCommand() -> Void{