View Javadoc

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  			// copy tracks
58  			playlist.addAll(strategy.getPlayItems());
59  			playOrder = new ArrayList<PlaylistItem>(playlist);
60  			// copy currently played track
61  			play(strategy.getCurrentlyPlaying());
62  			// copy random mode
63  			setRandom(strategy.getRandom());
64  			// copy queued tracks
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 }