-
Notifications
You must be signed in to change notification settings - Fork 0
/
SoundPlayer.swift
312 lines (255 loc) · 10.8 KB
/
SoundPlayer.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
import Foundation
import AVFoundation
import AudioToolbox
import MediaPlayer
//TODO: Fix notifications name. Adding postfix "2" - isn't solve a problem
let AudioPlayerOnTrackChangedNotification = "AudioPlayerOnTrackChangedNotification"
let AudioPlayerOnTrackChangedNotification2 = "AudioPlayerOnTrackChangedNotification2"
let AudioPlayerOnPlaybackStateChangedNotification = "AudioPlayerOnPlaybackStateChangedNotification"
class SoundPlayer: NSObject, AVAudioPlayerDelegate {
let audioSession = AVAudioSession.sharedInstance()
let commandCenter = MPRemoteCommandCenter.shared()
let nowPlayingInfoCenter = MPNowPlayingInfoCenter.default()
var notificationCenter = NotificationCenter.default
static let sharedInstance = SoundPlayer()
var player: AVAudioPlayer?
var timer: Timer?
var nextItemIndex: Int?
var previousItemIndex: Int?
var isPause = false
open var playbackItems: [ModelSoundProtocol]?
open var currentPlaybackItem: ModelSoundProtocol?
open var nextPlaybackItem: ModelSoundProtocol? {
guard let playbackItems = self.playbackItems, let currentPlaybackItem = self.currentPlaybackItem else { return nil }
for (index,item) in playbackItems.enumerated() {
if item.trackFileName == currentPlaybackItem.trackFileName {
nextItemIndex = index + 1
if nextItemIndex! >= playbackItems.count { return nil }
}
}
return playbackItems[nextItemIndex!]
}
open var previousPlaybackItem: ModelSoundProtocol? {
guard let playbackItems = self.playbackItems, let currentPlaybackItem = self.currentPlaybackItem else { return nil }
for (index,item) in playbackItems.enumerated() {
if item.trackFileName == currentPlaybackItem.trackFileName {
previousItemIndex = index - 1
if previousItemIndex! < 0 { return nil }
}
}
return playbackItems[previousItemIndex!]
}
var nowPlayingInfo: [String : AnyObject]?
open var currentTime: TimeInterval? {
return self.player?.currentTime
}
open var duration: TimeInterval? {
return self.player?.duration
}
open var isPlaying: Bool {
return self.player?.isPlaying ?? false
}
struct CurrentTrack {
let track: ModelSoundProtocol?
let currentTime: TimeInterval?
let duration: TimeInterval?
let isPlaying: Bool?
}
override init() {
super.init()
try! self.audioSession.setCategory(AVAudioSessionCategoryPlayback)
try! self.audioSession.setActive(true)
self.configureCommandCenter()
}
deinit {
self.timer?.invalidate()
}
open func playItems(_ playbackItems: [ModelSoundProtocol], firstItem: ModelSoundProtocol? = nil) {
self.playbackItems = playbackItems
if playbackItems.count == 0 {
self.endPlayback()
return
}
let playbackItem = firstItem ?? self.playbackItems!.first!
self.playItem(playbackItem)
}
func playItem(_ playbackItem: ModelSoundProtocol) {
if playbackItem.trackFileName != nil {
let soundUrl = StorageManager.getSoundUrl(playbackItem)
guard let audioPlayer = try? AVAudioPlayer(contentsOf: soundUrl) else {
self.endPlayback()
return
}
if self.player?.url == soundUrl {
if self.isPlaying == true {
self.pause()
isPause = true
} else {
self.play()
isPause = false
}
return
}
if let currentVolume = self.player?.volume {
audioPlayer.volume = currentVolume
}
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
audioPlayer.play()
self.player = audioPlayer
self.currentPlaybackItem = playbackItem
self.updateNowPlayingInfoForCurrentPlaybackItem()
self.updateCommandCenter()
configureTimer()
notifyOntrackChanged2()
}
}
open func togglePlayPause() {
if self.isPlaying {
self.pause()
}
else {
self.play()
}
}
open func play() {
self.player?.play()
self.updateNowPlayingInfoElapsedTime()
self.notifyOnPlaybackStateChanged()
}
open func pause() {
self.player?.pause()
self.updateNowPlayingInfoElapsedTime()
self.notifyOnPlaybackStateChanged()
}
open func nextTrack() {
guard let nextPlaybackItem = self.nextPlaybackItem else { return }
self.playItem(nextPlaybackItem)
self.updateCommandCenter()
notifyOntrackChanged2()
}
open func previousTrack() {
guard let previousPlaybackItem = self.previousPlaybackItem else { return }
self.playItem(previousPlaybackItem)
self.updateCommandCenter()
notifyOntrackChanged2()
}
open func seekTo(_ timeInterval: TimeInterval) {
self.player?.currentTime = timeInterval
self.updateNowPlayingInfoElapsedTime()
}
//MARK: - Command Center
func updateCommandCenter() {
guard let playbackItems = self.playbackItems, let currentPlaybackItem = self.currentPlaybackItem else { return }
self.commandCenter.previousTrackCommand.isEnabled = currentPlaybackItem.trackFileName != playbackItems.first!.trackFileName
self.commandCenter.nextTrackCommand.isEnabled = currentPlaybackItem.trackFileName != playbackItems.last!.trackFileName
}
func configureCommandCenter() {
self.commandCenter.playCommand.addTarget (handler: { [weak self] event -> MPRemoteCommandHandlerStatus in
guard let sself = self else { return .commandFailed }
sself.play()
return .success
})
self.commandCenter.pauseCommand.addTarget (handler: { [weak self] event -> MPRemoteCommandHandlerStatus in
guard let sself = self else { return .commandFailed }
sself.pause()
return .success
})
self.commandCenter.nextTrackCommand.addTarget (handler: { [weak self] event -> MPRemoteCommandHandlerStatus in
guard let sself = self else { return .commandFailed }
sself.nextTrack()
return .success
})
self.commandCenter.previousTrackCommand.addTarget (handler: { [weak self] event -> MPRemoteCommandHandlerStatus in
guard let sself = self else { return .commandFailed }
sself.previousTrack()
return .success
})
}
func updateNowPlayingInfoForCurrentPlaybackItem() {
guard let audioPlayer = self.player, let currentPlaybackItem = self.currentPlaybackItem else {
self.configureNowPlayingInfo(nil)
return
}
let nowPlayingInfo = [MPMediaItemPropertyTitle: currentPlaybackItem.trackName ,
MPMediaItemPropertyPlaybackDuration: audioPlayer.duration,
MPMediaItemPropertyArtwork : MPMediaItemArtwork(image: currentPlaybackItem.image!),
MPNowPlayingInfoPropertyPlaybackRate: NSNumber(value: 1.0 as Float)] as [String : Any]
// if let image = UIImage(named: currentPlaybackItem.albumImageName) {
// nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(image: image)
// }
self.configureNowPlayingInfo(nowPlayingInfo as [String : AnyObject]?)
self.updateNowPlayingInfoElapsedTime()
}
func updateNowPlayingInfoElapsedTime() {
guard var nowPlayingInfo = self.nowPlayingInfo, let audioPlayer = self.player else { return }
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = NSNumber(value: audioPlayer.currentTime as Double);
self.configureNowPlayingInfo(nowPlayingInfo)
}
func configureNowPlayingInfo(_ nowPlayingInfo: [String: AnyObject]?) {
self.nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo
self.nowPlayingInfo = nowPlayingInfo
}
//MARK: - AVAudioPlayerDelegate
open func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if self.nextPlaybackItem == nil {
self.endPlayback()
}
else {
self.nextTrack()
}
}
func endPlayback() {
self.currentPlaybackItem = nil
self.player = nil
self.updateNowPlayingInfoForCurrentPlaybackItem()
self.notifyOnTrackChanged()
self.notifyOntrackChanged2()
}
open func audioPlayerBeginInterruption(_ player: AVAudioPlayer) {
self.notifyOnPlaybackStateChanged()
}
open func audioPlayerEndInterruption(_ player: AVAudioPlayer, withOptions flags: Int) {
if AVAudioSessionInterruptionOptions(rawValue: UInt(flags)) == .shouldResume {
self.play()
}
}
//MARK: TIMER
func configureTimer() {
timer?.invalidate()
self.timer = Timer.every(0.5) { [weak self] in
guard let sself = self else { return }
sself.notifyOnPlaybackStateChanged()
sself.notifyOnTrackChanged()
}
}
//MARK: Notifications
func notifyOnPlaybackStateChanged() {
// print("post")
self.notificationCenter.post(name: Notification.Name(rawValue:AudioPlayerOnPlaybackStateChangedNotification),
object: CurrentTrack(track: currentPlaybackItem,
currentTime: currentTime,
duration: duration,
isPlaying: isPlaying))
}
func notifyOnTrackChanged() {
self.notificationCenter.post(name: Notification.Name(rawValue: AudioPlayerOnTrackChangedNotification), object: CurrentTrack(track: currentPlaybackItem,
currentTime: currentTime,
duration: duration,
isPlaying: isPlaying))
}
func notifyOntrackChanged2() {
self.notificationCenter.post(name: Notification.Name(rawValue: AudioPlayerOnTrackChangedNotification2), object: nil )
}
func loadTrackDuration(trackName: String) -> String? {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let soundUrl = documentsURL.appendingPathComponent("\(trackName)")
do {
let helpPlayer = try AVAudioPlayer(contentsOf: soundUrl)
return helpPlayer.duration.positionalTime
} catch let error {
print(error.localizedDescription)
}
return nil
}
}