added iOS source code
[wl-app.git] / iOS / WolneLektury / Screens / Player / PlayerController.swift
1 //
2 //  PlayerController.swift
3 //  WolneLektury
4 //
5 //  Created by Pawel Dabrowski on 21/09/2018.
6 //  Copyright © 2018 Fundacja Nowoczesna Polska. All rights reserved.
7 //
8
9 import UIKit
10 import AVFoundation
11 import MediaPlayer
12
13 protocol PlayerControllerDelegate: class {
14     func playerControllerDelegateTrackChanged()
15     func playerControllerDelegateUpdatePlayerProgress(timeInterval: TimeInterval)
16     func playerControllerDelegatePlayStateChanged()
17     
18 }
19
20 class PlayerController: UIResponder, AVAudioPlayerDelegate {
21     static let shared = PlayerController()
22
23     override init() {
24         super.init()
25
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:)))
37         } else {
38             // Fallback on earlier versions
39         }
40     }
41     
42     weak var delegate: PlayerControllerDelegate?
43     
44     private(set) var currentBookDetails: BookDetailsModel? {
45         didSet {
46             if let currentBookDetails = currentBookDetails {
47                 audiobookMediaModels = currentBookDetails.getAudiobookMediaModels()
48                 if currentBookDetails.currentAudioChapter < audiobookMediaModels!.count {
49                     currentAudiobookIndex = currentBookDetails.currentAudioChapter
50                 }
51                 else {
52                     currentAudiobookIndex = 0
53                 }
54             }
55             else{
56                 audiobookMediaModels = nil
57                 currentAudiobookIndex = 0
58             }
59         }
60     }
61     
62     private var audiobookMediaModels: [MediaModel]?
63     private(set) var currentAudiobookIndex: Int = 0
64     var audioPlayer:AVAudioPlayer? = nil
65     var timer:Timer?
66     var audioLength = 0.0
67     var currentAudioPath: URL?
68     private(set) var artwork: MPMediaItemArtwork?{
69         didSet{
70             updateMediaInfo()
71         }
72     }
73         
74     func setCoverImage(image: UIImage){
75         artwork = MPMediaItemArtwork(image: image)
76     }
77     
78     func getCurrentTime() -> TimeInterval {
79         guard let audioPlayer = audioPlayer else { return 0 }
80         return audioPlayer.currentTime
81     }
82     
83     func isPlaying() -> Bool {
84         guard let audioPlayer = audioPlayer else { return false }
85         return audioPlayer.isPlaying
86     }
87
88     func stopAndClear() {
89         stopTimer()
90         audioPlayer?.stop()
91         currentBookDetails = nil
92     }
93     
94     func preparePlayer(bookDetailsModel: BookDetailsModel, delegate: PlayerControllerDelegate, trackIndex: Int?) {
95         stopAndClear()
96         
97         self.delegate = delegate
98         currentBookDetails = bookDetailsModel
99         if let trackIndex = trackIndex {
100             currentAudiobookIndex = trackIndex
101         }
102         else if bookDetailsModel.currentAudioChapter < audiobookMediaModels!.count {
103             currentAudiobookIndex = bookDetailsModel.currentAudioChapter
104         }
105         prepareAudio()
106         delegate.playerControllerDelegateTrackChanged()
107     }
108     
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)
112             return
113         }
114         
115         
116         self.delegate = delegate
117         if audioPlayer.isPlaying {
118             delegate.playerControllerDelegateTrackChanged()
119         }
120         else {
121             prepareAudio()
122             playAudio()
123
124         }
125     }
126
127     func startPlaying(bookDetailsModel: BookDetailsModel, delegate: PlayerControllerDelegate, trackIndex: Int?) {
128         preparePlayer(bookDetailsModel: bookDetailsModel, delegate: delegate, trackIndex: trackIndex)
129         playAudio()
130     }
131     
132     func getCurentAudiobookMediaModel() -> MediaModel? {
133         guard let audiobookMediaModels = audiobookMediaModels, audiobookMediaModels.count > currentAudiobookIndex else { return nil }
134         
135         return audiobookMediaModels[currentAudiobookIndex]
136     }
137     
138     func updateMediaInfo(){
139         guard let audioPlayer = audioPlayer, let model = getCurentAudiobookMediaModel() else { return }
140         
141         let artistName = model.artist
142         let songName = model.name
143         
144         if let artwork = artwork {
145             MPNowPlayingInfoCenter.default().nowPlayingInfo = [MPMediaItemPropertyArtist : artistName,  MPMediaItemPropertyTitle : songName, MPMediaItemPropertyPlaybackDuration:  audioLength, MPNowPlayingInfoPropertyElapsedPlaybackTime: audioPlayer.currentTime, MPMediaItemPropertyArtwork: artwork]
146         }
147         else {
148             MPNowPlayingInfoCenter.default().nowPlayingInfo = [MPMediaItemPropertyArtist : artistName,  MPMediaItemPropertyTitle : songName, MPMediaItemPropertyPlaybackDuration:  audioLength, MPNowPlayingInfoPropertyElapsedPlaybackTime: audioPlayer.currentTime]
149         }
150     }
151     
152     // Prepare audio for playing
153     func prepareAudio(){
154         guard let currentBook = currentBookDetails, let currentMediaModel = getCurentAudiobookMediaModel() else { return }
155         
156         guard let path = NSObject.audiobookPathIfExists(audioBookUrlString: currentMediaModel.url, bookSlug: currentBook.slug) else { return }
157         
158         currentAudioPath = path
159         audioPlayer = try? AVAudioPlayer(contentsOf: path)
160
161         guard let audioPlayer = audioPlayer else { return }
162         
163         do {
164             //keep alive audio at background
165             try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
166         } catch _ {
167         }
168         do {
169             try AVAudioSession.sharedInstance().setActive(true)
170         } catch _ {
171         }
172         UIApplication.shared.beginReceivingRemoteControlEvents()
173         audioPlayer.delegate = self
174         audioLength = audioPlayer.duration
175         audioPlayer.prepareToPlay()
176         
177     }
178     
179     //MARK:- Player Controls Methods
180     func  playAudio(){
181         audioPlayer?.play()
182         startTimer()
183         saveCurrentTrackNumber()
184         updateMediaInfo()
185         delegate?.playerControllerDelegateTrackChanged()
186     }
187     
188     func playNextAudio(){
189         guard let audiobookMediaModels = audiobookMediaModels, currentAudiobookIndex < (audiobookMediaModels.count  - 1) else { return }
190         
191         currentAudiobookIndex += 1
192         
193         prepareAudio()
194         playAudio()
195     }
196     
197     func playPreviousAudio(){
198         
199         guard currentAudiobookIndex > 0 else { return }
200
201         currentAudiobookIndex -= 1
202         
203         prepareAudio()
204         playAudio()
205     }
206     
207     func forward() {
208         guard let audioPlayer = audioPlayer, audioPlayer.isPlaying else { return }
209         if audioPlayer.currentTime + 12 < audioPlayer.duration {
210             audioPlayer.currentTime += 10
211             updateMediaInfo()
212         }
213         else {
214             playNextAudio()
215         }
216     }
217
218     func rewind() {
219         guard let audioPlayer = audioPlayer, audioPlayer.isPlaying else { return }
220         if audioPlayer.currentTime > 10 {
221             audioPlayer.currentTime -= 10
222             updateMediaInfo()
223         }
224         else {
225             playPreviousAudio()
226         }
227     }
228         
229     func pauseAudioPlayer(){
230         audioPlayer?.pause()
231         updateMediaInfo()
232         delegate?.playerControllerDelegatePlayStateChanged()
233     }
234     
235     func playAudioPlayer(){
236         audioPlayer?.play()
237         updateMediaInfo()
238         delegate?.playerControllerDelegatePlayStateChanged()
239     }
240     
241     func changeTime(timeInterval: TimeInterval) {
242         guard let audioPlayer = audioPlayer else { return }
243         if audioLength > timeInterval {
244             audioPlayer.currentTime = timeInterval
245         }
246         updateMediaInfo()
247     }
248     
249     func saveCurrentTrackNumber(){
250         guard let bookDetailsModel = currentBookDetails else { return }
251         
252         DatabaseManager.shared.updateCurrentChapters(bookDetailsModel: bookDetailsModel, currentChapter: nil, totalChapters: nil, currentAudioChapter: currentAudiobookIndex, totalAudioChapters: nil)
253     }
254
255
256     func startTimer(){
257         if timer == nil {
258             timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(PlayerController.update(_:)), userInfo: nil,repeats: true)
259             timer?.fire()
260         }
261         else if timer!.isValid == false {
262             timer?.invalidate()
263             timer = nil
264             
265             timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(PlayerController.update(_:)), userInfo: nil,repeats: true)
266             timer?.fire()
267         }
268     }
269     
270     func stopTimer(){
271         timer?.invalidate()
272         
273     }
274     
275     @objc func update(_ timer: Timer){
276         guard let audioPlayer = audioPlayer else { return }
277         if !audioPlayer.isPlaying{
278             return
279         }
280         
281         delegate?.playerControllerDelegateUpdatePlayerProgress(timeInterval: audioPlayer.currentTime)
282     }
283     
284     
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) {
287         if flag == true {
288             playNextAudio()
289         }
290     }
291
292     @objc func changePlaybackPositionCommand(event: MPChangePlaybackPositionCommandEvent) -> MPRemoteCommandHandlerStatus{
293         guard let audioPlayer = audioPlayer else { return .commandFailed }
294         
295         if audioPlayer.duration - 5 < event.positionTime && audioPlayer.duration > 5 {
296             audioPlayer.currentTime = audioPlayer.duration - 5
297         }
298         else {
299             audioPlayer.currentTime = event.positionTime
300         }
301         updateMediaInfo()
302         return .success
303     }
304
305     @objc func nextTrackCommand() -> Void{
306         forward()
307     }
308     
309     @objc func previousTrackCommand() -> Void{
310         rewind()
311     }
312     
313     @objc func playCommand() -> Void{
314         playAudioPlayer()
315     }
316     
317     @objc func pauseCommand() -> Void{
318         pauseAudioPlayer()
319     }
320 }