1 /***
2 * Ambient - A music player for the Android platform
3 Copyright (C) 2007 Martin Vysny
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 package sk.baka.ambient.playlist;
19
20 import java.io.Serializable;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Queue;
27
28 import sk.baka.ambient.collection.TrackMetadataBean;
29 import sk.baka.ambient.commons.Interval;
30
31 /***
32 * A regular, old-school playlist. Manages the song ordering in playlist and
33 * queues. Supports shuffle and track repeat. Based on a 'static' list of
34 * tracks.
35 *
36 * @author Martin Vysny
37 */
38 public final class StaticPlaylistStrategy implements IPlaylistStrategy,
39 Serializable {
40
41 /***
42 *
43 */
44 private static final long serialVersionUID = -8220847953995138930L;
45
46 /***
47 * Creates new static playlist strategy, optionally mimicking given
48 * strategy.
49 *
50 * @param strategy
51 * the strategy to mimic. If <code>null</code> then an empty
52 * playlist is created.
53 */
54 public StaticPlaylistStrategy(final IPlaylistStrategy strategy) {
55 super();
56 if (strategy != null) {
57
58 playlist.addAll(strategy.getPlayItems());
59 playOrder = new ArrayList<PlaylistItem>(playlist);
60
61 play(strategy.getCurrentlyPlaying());
62
63 setRandom(strategy.getRandom());
64
65 final List<Integer> queue = strategy.getQueue();
66 for (final Integer i : queue) {
67 queue(Interval.fromItem(i));
68 }
69 }
70 }
71
72 /***
73 * The playlist, as shown on screen and defined by the user.
74 */
75 private final List<PlaylistItem> playlist = new ArrayList<PlaylistItem>();
76
77 /***
78 * The unmodifiable link to the playlist.
79 */
80 private transient List<PlaylistItem> unmodifiablePlaylist;
81
82 /***
83 * Songs queued manually by the user. Items {@link Queue#poll() polled} from
84 * the queue are to be played first.
85 */
86 private final LinkedList<PlaylistItem> queue = new LinkedList<PlaylistItem>();
87
88 /***
89 * <p>
90 * The expected play order. If shuffle is disabled then this is equal to
91 * {@link #playlist}. If shuffle is enabled then this is a permutated
92 * version of the {@link #playlist}.
93 * </p>
94 */
95 private List<PlaylistItem> playOrder = new ArrayList<PlaylistItem>();
96
97 /***
98 * Currently played song. Index to the {@link #playOrder} list.
99 * <code>-1</code> if no song is currently being played.
100 */
101 private int currentTrack = -1;
102
103 /***
104 * Play tracks in this order.
105 */
106 private Random random = Random.NONE;
107
108 public int getCurrentlyPlaying() {
109 if (currentTrack == -1)
110 return -1;
111 return playlist.indexOf(playOrder.get(currentTrack));
112 }
113
114 public void shuffle() {
115 final PlaylistItem current = getCurrentlyPlayingItem();
116 Collections.shuffle(playlist);
117 if (current != null) {
118 currentTrack = playOrder.indexOf(current);
119 }
120 randomizeTracks();
121 }
122
123 public int size() {
124 return playlist.size();
125 }
126
127 public void sortByAlbumOrder() {
128 final PlaylistItem current = getCurrentlyPlayingItem();
129 Utils.sortPlaylistByAlbumOrder(playlist);
130 if (current != null) {
131 currentTrack = playOrder.indexOf(current);
132 }
133 randomizeTracks();
134 }
135
136 public List<PlaylistItem> getPlayItems() {
137 if (unmodifiablePlaylist == null) {
138 unmodifiablePlaylist = Collections.unmodifiableList(playlist);
139 }
140 return unmodifiablePlaylist;
141 }
142
143 public void clearQueue() {
144 for (PlaylistItem item : queue) {
145 item.queueOrder = 0;
146 }
147 queue.clear();
148 }
149
150 public List<Integer> getQueue() {
151 final List<Integer> result = new ArrayList<Integer>(queue.size());
152 result.addAll(Collections.nCopies(queue.size(), 0));
153 for (int i = 0; i < playlist.size(); i++) {
154 final PlaylistItem item = playlist.get(i);
155 if (item.queueOrder != 0) {
156 result.set(item.queueOrder - 1, i);
157 }
158 }
159 return result;
160 }
161
162 public void setRandom(Random random) {
163 if (this.random == random)
164 return;
165 this.random = random;
166 randomizeTracks();
167 }
168
169 private PlaylistItem getCurrentlyPlayingItem() {
170 return currentTrack < 0 ? null : playOrder.get(currentTrack);
171 }
172
173 private void randomizeTracks() {
174 final PlaylistItem current = getCurrentlyPlayingItem();
175 playOrder = Random.randomize(random, playlist,
176 currentTrack == -1 ? null : playOrder.get(currentTrack));
177 if (current != null) {
178 currentTrack = playOrder.indexOf(current);
179 }
180 }
181
182 public void remove(final Interval interval) {
183 remove(interval, true);
184 }
185
186 /***
187 * Removes given tracks.
188 *
189 * @param interval
190 * tracks to remove
191 * @param deregister
192 * if <code>true</code> then the tracks are dequeued and
193 * current track pointer is reset if needed.
194 */
195 private void remove(final Interval interval, final boolean deregister) {
196 for (int index = interval.end; index >= interval.start; index--) {
197 final PlaylistItem item = playlist.get(index);
198 if (deregister) {
199 dequeue(index);
200 }
201 final int playOrderIndex = playOrder.indexOf(item);
202 if (currentTrack == playOrderIndex) {
203 currentTrack = -1;
204 }
205 if (playOrderIndex < currentTrack)
206 currentTrack--;
207 playOrder.remove(playOrderIndex);
208 playlist.remove(index);
209 }
210 }
211
212 public void add(int i, List<TrackMetadataBean> tracks) {
213 final PlaylistItem current = getCurrentlyPlayingItem();
214 int index = i;
215 for (final TrackMetadataBean track : tracks) {
216 final PlaylistItem item = new PlaylistItem(track, 0, 0);
217 playlist.add(index++, item);
218 }
219 if (current != null) {
220 currentTrack = playOrder.indexOf(current);
221 }
222 randomizeTracks();
223 }
224
225 public void dequeue(int track) {
226 final PlaylistItem item = playlist.get(track);
227 dequeue(item);
228 }
229
230 private void dequeue(PlaylistItem item) {
231 final int queueIndex = queue.indexOf(item);
232 if (queueIndex < 0)
233 return;
234 queue.remove(queueIndex);
235 item.queueOrder = 0;
236 for (int i = queueIndex; i < queue.size(); i++) {
237 queue.get(i).queueOrder--;
238 }
239 }
240
241 public void queue(final Interval tracksToQueue) {
242 for (int track = tracksToQueue.start; track <= tracksToQueue.end; track++) {
243 final PlaylistItem item = playlist.get(track);
244 if (queue.contains(item))
245 continue;
246 queue.add(item);
247 item.queueOrder = queue.size();
248 }
249 }
250
251 public void reinit() {
252 currentTrack = -1;
253 playOrder = Random.randomize(random, playlist, null);
254 clearQueue();
255 }
256
257 public int peekNext() {
258 if (!queue.isEmpty()) {
259 final PlaylistItem item = queue.peek();
260 final int index = playlist.indexOf(item);
261 return index;
262 }
263 int result = currentTrack + 1;
264 if (result >= playOrder.size()) {
265 return -1;
266 }
267 final PlaylistItem item = playOrder.get(result);
268 return playlist.indexOf(item);
269 }
270
271 public int next() {
272 if (!queue.isEmpty()) {
273 final PlaylistItem item = queue.peek();
274 final int index = playOrder.indexOf(item);
275 dequeue(item);
276 item.playCount++;
277 currentTrack = index;
278 return playlist.indexOf(item);
279 }
280 currentTrack++;
281 if (currentTrack >= playOrder.size()) {
282 currentTrack = -1;
283 randomizeTracks();
284 return -1;
285 }
286 final PlaylistItem item = playOrder.get(currentTrack);
287 item.playCount++;
288 return playlist.indexOf(item);
289 }
290
291 public int play(int track) {
292 if (track == -1) {
293 currentTrack = -1;
294 randomizeTracks();
295 } else {
296 final PlaylistItem item = playlist.get(track);
297 currentTrack = playOrder.indexOf(item);
298 item.playCount++;
299 }
300 return track;
301 }
302
303 public int previous() {
304 if (currentTrack < 0) {
305 currentTrack = playOrder.size() - 1;
306 } else {
307 currentTrack--;
308 }
309 if (currentTrack < 0) {
310 randomizeTracks();
311 return currentTrack;
312 }
313 final PlaylistItem item = playOrder.get(currentTrack);
314 item.playCount++;
315 return playlist.indexOf(item);
316 }
317
318 public Random getRandom() {
319 return random;
320 }
321
322 public Interval move(Interval interval, int target) {
323 int targetIndex = target;
324 final PlaylistItem currentlyPlayedItem = getCurrentlyPlayingItem();
325 final List<PlaylistItem> movedTracks = new ArrayList<PlaylistItem>(
326 playlist.subList(interval.start, interval.end + 1));
327 remove(interval, false);
328 if (targetIndex > interval.end)
329 targetIndex -= interval.length;
330 final Interval result = new Interval(targetIndex, interval.length);
331 for (final PlaylistItem track : movedTracks) {
332 playlist.add(targetIndex++, track);
333 }
334 if (currentlyPlayedItem != null) {
335 currentTrack = playOrder.indexOf(currentlyPlayedItem);
336 }
337 randomizeTracks();
338 return result;
339 }
340
341 public Interval moveBy(final Interval interval, final int delta) {
342 if (delta == 0)
343 return interval;
344 final boolean down = delta > 0;
345 final int maxMove = !down ? interval.start : playlist.size()
346 - interval.end - 1;
347 if (maxMove <= 0)
348 return interval;
349 int absDelta = Math.abs(delta);
350 if (absDelta > maxMove)
351 absDelta = maxMove;
352 if (down) {
353 return move(interval, interval.end + absDelta + 1);
354 }
355 return move(interval, interval.start - absDelta);
356 }
357
358 public void replaceLocations(Map<String, String> locationMap) {
359 Utils.replaceLocations(playlist, locationMap);
360 }
361 }