-
Notifications
You must be signed in to change notification settings - Fork 20
/
AVBidirectionalQueuePlayer.swift
246 lines (210 loc) · 10.9 KB
/
AVBidirectionalQueuePlayer.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
// Converted to Swift 5.3 by Swiftify v5.3.19197 - https://swiftify.com/
//
// AVBidirectionalQueuePlayer.swift
// IntervalPlayer
//
// Created by Daniel Giovannelli on 2/18/13.
// This class subclasses AVQueuePlayer to create a class with the same functionality as AVQueuePlayer
// but with the added ability to go backwards in the queue - a function that is impossible in a normal
// AVQueuePlayer since items on the queue are destroyed when they are finished playing.
//
// IMPORTANT NOTE: This version of AVQueuePlayer assumes that ARC IS ENABLED. If ARC is NOT enabled and you
// use this library, you'll get memory leaks on the two fields that have been added to the class, int
// nowPlayingIndex and NSArray itemsForPlayer.
//
// Note also that this classrequires that the AVFoundation framework be included in your project.
//
// AVBidirectionalQueuePlayer.swift
// IntervalPlayer
//
// Created by Daniel Giovannelli on 2/18/13.
//
// 2014/07/16 (JRTaal) Greatly simplified and cleaned up code, meanwhile fixed number of bugs.
// Renamed to more apt AVBidirectionalQueuePlayer
// 2018/03/29 (codinronan) expanded feature set, added accessors and additional convenience methods & events.
//
import AVFoundation
let AVBidirectionalQueueAddedItem = "AVBidirectionalQueuePlayer.AddedItem"
let AVBidirectionalQueueAddedAllItems = "AVBidirectionalQueuePlayer.AddedAllItems"
let AVBidirectionalQueueRemovedItem = "AVBidirectionalQueuePlayer.RemovedItem"
let AVBidirectionalQueueCleared = "AVBidirectionalQueuePlayer.Cleared"
class AVBidirectionalQueuePlayer: AVQueuePlayer {
var queuedAudioTracks: [AudioTrack] = []
var isPlaying: Bool {
timeControlStatus == .playing
}
var isAtBeginning: Bool {
// This function simply returns whether or not the AVBidirectionalQueuePlayer is at the first item. This is
// useful for implementing custom behavior if the user tries to play a previous item at the start of
// the queue (such as restarting the item).
currentIndex() == 0
}
var isAtEnd: Bool {
guard let currentIndex = currentIndex() else { return true }
return currentIndex >= (queuedAudioTracks.endIndex - 1)
}
var currentAudioTrack: AudioTrack? { currentItem as? AudioTrack }
override init() {
super.init()
}
init(items: [AudioTrack]) {
// This function calls the constructor for AVQueuePlayer, then sets up the nowPlayingIndex to 0 and saves the array that the player was generated from as itemsForPlayer
super.init(items: items)
queuedAudioTracks = items
}
// Two methods need to be added to the AVQueuePlayer: one which will play the last song in the queue, and one which will return if the queue is at the beginning (in case the user wishes to implement special behavior when a queue is at its first item, such as restarting a song). A getIndex method to return the current index is also provided.
// NEW METHODS
func playPreviousItem() {
// This function is the meat of this library: it allows for going backwards in an AVQueuePlayer,
// basically by clearing the player and repopulating it from the index of the last item played.
// It should be noted that if the player is on its first item, this function will do nothing. It will
// not restart the item or anything like that; if you want that functionality you can implement it
// yourself fairly easily using the isAtBeginning method to test if the player is at its start.
guard
let currentAudioTrack = currentAudioTrack,
let tempNowPlayingIndex = queuedAudioTracks.firstIndex(of: currentAudioTrack)
else {
return
}
if tempNowPlayingIndex == 0 {
let currentrate = rate
if currentrate != 0.0 {
pause()
}
seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero)
// [self play];
rate = currentrate
} else if tempNowPlayingIndex > 0 {
let currentrate = rate
if currentrate != 0.0 {
pause()
}
// Note: it is necessary to have seekToTime called twice in this method, once before and once after re-making the array. If it is not present before, the player will resume from the same spot in the next item when the previous item finishes playing; if it is not present after, the previous item will be played from the same spot that the current item was on.
seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero)
// The next two lines are necessary since RemoveAllItems resets both the nowPlayingIndex and _itemsForPlayer
let tempPlaylist = queuedAudioTracks
super.removeAllItems()
var offset = 1
while true {
let _it = tempPlaylist[tempNowPlayingIndex - offset]
if _it.error != nil {
offset += 1
}
break
}
for i in (tempNowPlayingIndex - offset)..<(tempPlaylist.count) {
let item = tempPlaylist[i]
item.seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero, completionHandler: nil)
super.insert(item, after: nil)
}
// Not a typo; see above comment
seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero)
// [self play];
rate = currentrate
}
}
func setCurrentIndex(_ currentIndex: Int) {
setCurrentIndex(currentIndex, completionHandler: { _ in })
}
func setCurrentIndex(_ newCurrentIndex: Int, completionHandler: @escaping (Bool) -> Void) {
// NSUInteger tempNowPlayingIndex = [_itemsForPlayer indexOfObject: self.currentItem];
// if (tempNowPlayingIndex != NSNotFound){
let currentrate = rate
if currentrate > 0 {
pause()
}
// Note: it is necessary to have seekToTime called twice in this method, once before and once after re-making the area. If it is not present before, the player will resume from the same spot in the next item when the previous item finishes playing; if it is not present after, the previous item will be played from the same spot that the current item was on.
seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero)
// The next two lines are necessary since RemoveAllItems resets both the nowPlayingIndex and _itemsForPlayer
let tempPlaylist = queuedAudioTracks
super.removeAllItems()
for i in newCurrentIndex..<(tempPlaylist.count) {
let item = tempPlaylist[i]
item.seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero, completionHandler: nil)
super.insert(item, after: nil)
}
// Not a typo; see above comment
seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero, completionHandler: completionHandler)
// }
}
func replaceAllItems(with items: [AudioTrack]) {
removeAllItems()
appendItems(items)
}
func appendItems(_ items: [AudioTrack]) {
for item in items {
insert(item, after: nil)
}
let center = NotificationCenter.default
center.post(name: NSNotification.Name(AVBidirectionalQueueAddedAllItems), object: self, userInfo: [
"items": items
])
}
/* The following methods of AVQueuePlayer are overridden by AVBidirectionalQueuePlayer:
– initWithItems: to keep track of the array used to create the player
+ queuePlayerWithItems: to keep track of the array used to create the player
– advanceToNextItem to update the now playing index
– insertItem:afterItem: to update the now playing index
– removeAllItems to update the now playing index
– removeItem: to update the now playing index
*/
func currentIndex() -> Int? {
guard let currentAudioTrack = currentAudioTrack else { return nil }
return queuedAudioTracks.firstIndex(of: currentAudioTrack)
}
// OVERRIDDEN AVQUEUEPLAYER METHODS
override func play() {
if isAtEnd {
// we could add a flag here to indicate looping
setCurrentIndex(0)
}
super.play()
}
override func removeAllItems() {
// This does the same thing as the normal AVQueuePlayer removeAllItems, but clears our collection copy
super.removeAllItems()
queuedAudioTracks.removeAll()
NotificationCenter.default.post(name: NSNotification.Name(AVBidirectionalQueueCleared), object: self, userInfo: nil)
}
func remove(_ item: AudioTrack) {
// This method calls the superclass to remove the items from the AVQueuePlayer itself, then removes
// any instance of the item from the itemsForPlayer array. This mimics the behavior of removeItem on
// AVQueuePlayer, which removes all instances of the item in question from the queue.
// It also subtracts 1 from the nowPlayingIndex for every time the item shows up in the itemsForPlayer
// array before the current value.
super.remove(item)
if let index = queuedAudioTracks.firstIndex(of: item) {
queuedAudioTracks.remove(at: index)
}
NotificationCenter.default.post(name: NSNotification.Name(AVBidirectionalQueueRemovedItem), object: self, userInfo: [
"item": item
])
}
func insert(_ item: AudioTrack, after afterItem: AudioTrack?) {
// This method calls the superclass to add the new item to the AVQueuePlayer, then adds that item to the
// proper location in the itemsForPlayer array and increments the nowPlayingIndex if necessary.
super.insert(item, after: afterItem)
if afterItem != nil && queuedAudioTracks.contains(afterItem!) {
// AfterItem is non-nil
if (queuedAudioTracks.firstIndex(of: afterItem!) ?? NSNotFound) < (queuedAudioTracks.count ) - 1 {
queuedAudioTracks.insert(item, at: (queuedAudioTracks.firstIndex(of: afterItem!) ?? NSNotFound) + 1)
} else {
queuedAudioTracks.append(item)
}
} else {
// afterItem is nil
queuedAudioTracks.append(item)
}
NotificationCenter.default.post(name: NSNotification.Name(AVBidirectionalQueueAddedItem), object: self, userInfo: [
"item": item
])
}
/* The following methods of AVQueuePlayer are overridden by AVBidirectionalQueuePlayer:
– initWithItems: to keep track of the array used to create the player
+ queuePlayerWithItems: to keep track of the array used to create the player
– advanceToNextItem to update the now playing index
– insertItem:afterItem: to update the now playing index
– removeAllItems to update the now playing index
– removeItem: to update the now playing index
*/
}