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
19 package sk.baka.ambient.activity.main;
20
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.Comparator;
25 import java.util.List;
26 import java.util.concurrent.CopyOnWriteArrayList;
27
28 import sk.baka.ambient.ActionsEnum;
29 import sk.baka.ambient.AmbientApplication;
30 import sk.baka.ambient.R;
31 import sk.baka.ambient.collection.TrackMetadataBean;
32 import sk.baka.ambient.commons.Interval;
33 import sk.baka.ambient.stream.shoutcast.Radio;
34 import sk.baka.ambient.stream.shoutcast.ShoutcastUtils;
35 import sk.baka.ambient.views.gesturelist.GesturesListView;
36 import android.view.View;
37 import android.widget.TextView;
38
39 /***
40 * Controls the shoutcast server manager.
41 *
42 * @author Martin Vysny
43 */
44 public final class ShoutcastController extends AbstractListController {
45
46 /***
47 * Creates the shoutcast controller instance.
48 *
49 * @param activity
50 * the activity instance.
51 * @param playlistView
52 * the playlist view
53 */
54 public ShoutcastController(final MainActivity activity,
55 final GesturesListView playlistView) {
56 super(R.id.shoutcastbrowser, R.id.shoutcastList, activity);
57 initButtonBar(R.id.shoutcastButtons, actions);
58
59 listView.dragDropViews.clear();
60 listView.dragDropViews.add(playlistView);
61 }
62
63 @Override
64 public void destroy() {
65 listView.dragDropViews.clear();
66 super.destroy();
67 }
68
69 @Override
70 protected void onAction(ActionsEnum action) {
71 if (action == ActionsEnum.ShoutcastNameTrackSwitch) {
72 isRadioNames = !isRadioNames;
73 if (radioList != null) {
74 sortRadioList();
75 update(null);
76 }
77 return;
78 }
79 if (action == ActionsEnum.Back) {
80 goBack();
81 return;
82 }
83 super.onAction(action);
84 }
85
86 /***
87 * The actions to display on the Task switcher.
88 */
89 public static final List<ActionsEnum> actions = Collections
90 .unmodifiableList(Arrays.asList(ActionsEnum.Back,
91 ActionsEnum.ShoutcastNameTrackSwitch));
92
93 /***
94 * If <code>true</code> then a genre list is final being shown. If
95 * <code>false</code> then a radio list is being shown.
96 */
97 private volatile boolean isShowingGenre = true;
98
99 /***
100 * Used only when the radio list is shown. If <code>true</code> then the
101 * radio name is shown. If <code>false</code> then the currently played
102 * track name is shown.
103 */
104 private boolean isRadioNames = true;
105
106 /***
107 * Cached genres list.
108 */
109 private volatile List<String> genres = null;
110
111 /***
112 * Returns list of genres. If the genre list is not yet loaded then the
113 * download starts in the background and an empty list is returned.
114 *
115 * @return the genre list.
116 */
117 private List<String> getGenres() {
118 if (genres != null) {
119 return genres;
120 }
121 app.getBackgroundTasks().schedule(new GenreDownloader(),
122 GenreDownloader.class, true,
123 app.getString(R.string.shoutcastDownloadGenres));
124 return Collections.emptyList();
125 }
126
127 private final class GenreDownloader implements Runnable {
128 public void run() {
129 try {
130 final List<String> genres = ShoutcastUtils.parseGenres();
131 ShoutcastController.this.genres = new CopyOnWriteArrayList<String>(
132 genres);
133 updateAsync();
134 } catch (final Exception e) {
135 throw new RuntimeException(e);
136 }
137 }
138 }
139
140 private void updateAsync() {
141 AmbientApplication.getHandler().post(new Runnable() {
142 public void run() {
143 update(null);
144 }
145 });
146 }
147
148 @Override
149 protected void visibilityChanged(boolean visible) {
150 if (visible) {
151 update(null);
152 }
153 }
154
155 @Override
156 protected void recomputeListItems() {
157
158 final boolean offline = !app.getStateHandler().getStartupState().online;
159 mainView.findViewById(R.id.shoutcastbrowserControls).setVisibility(
160 offline ? View.GONE : View.VISIBLE);
161 mainView.findViewById(R.id.shoutcastOffline).setVisibility(
162 offline ? View.VISIBLE : View.GONE);
163 if (offline) {
164 return;
165 }
166 listView.getModel().getModel().clear();
167 if (!isVisible()) {
168 return;
169 }
170 if (isShowingGenre) {
171 listView.getModel().getModel().addAll(getGenres());
172 } else {
173 if (radioList != null) {
174 listView.getModel().getModel().addAll(radioList);
175 }
176 }
177 }
178
179 @Override
180 public String getHint(Interval highlight) {
181 return highlight.length + " radio(s)";
182 }
183
184 @Override
185 public boolean isComputeTracksLong(Interval interval) {
186 return !interval.isEmpty();
187 }
188
189 @Override
190 public boolean isComputeTracksOnlineOp(Interval interval) {
191 return !interval.isEmpty();
192 }
193
194 @Override
195 public boolean canComputeItems() {
196 return !isShowingGenre;
197 }
198
199 @Override
200 public boolean canHighlight() {
201 return !isShowingGenre;
202 }
203
204 public void itemActivated(int index, Object model) {
205 if (isShowingGenre) {
206 showGenre((String) model);
207 } else {
208 final Radio r = (Radio) model;
209 app.getBackgroundTasks().schedule(new PlaylistDownloader(r),
210 PlaylistDownloader.class, true,
211 app.getString(R.string.shoutcastDownloadURLs));
212 }
213 }
214
215 private final class PlaylistDownloader implements Runnable {
216 private final Radio r;
217 private volatile boolean gettingPlaylist = true;
218 private volatile List<TrackMetadataBean> playlist = null;
219
220 /***
221 * Creates new instance.
222 *
223 * @param r
224 * the radio.
225 */
226 public PlaylistDownloader(Radio r) {
227 this.r = r;
228 }
229
230 public void run() {
231 if (gettingPlaylist) {
232 playlist = getRadioStreams(r);
233 playlist = Collections.synchronizedList(playlist);
234 gettingPlaylist = false;
235 AmbientApplication.getHandler().post(this);
236 } else {
237 app.getPlaylist().add(app.getPlaylist().size(), playlist);
238 }
239 }
240 }
241
242 /***
243 * Loads the radio list.
244 *
245 * @author Martin Vysny
246 */
247 private class RadioDownloader implements Runnable {
248 private final String genre;
249
250 /***
251 * Creates new instance.
252 *
253 * @param genre
254 * the genre.
255 */
256 public RadioDownloader(final String genre) {
257 this.genre = genre;
258 }
259
260 public void run() {
261 List<Radio> list;
262 try {
263 list = ShoutcastUtils.getRadioList(genre);
264 } catch (final Exception e) {
265 throw new RuntimeException(e);
266 }
267 radioList = Collections.synchronizedList(list);
268 sortRadioList();
269 isShowingGenre = false;
270 updateAsync();
271 }
272 }
273
274 /***
275 * The radio list.
276 */
277 private volatile List<Radio> radioList = null;
278
279 @Override
280 public void removeItems(Interval remove) {
281 goBack();
282 }
283
284 private void goBack() {
285 if (isShowingGenre) {
286
287 return;
288 }
289 isShowingGenre = true;
290 radioList = null;
291 update(null);
292 }
293
294 public void update(GesturesListView listView, View itemView, int index,
295 Object model) {
296 final TextView view = (TextView) itemView;
297 if (listView.getHighlight().contains(index)) {
298 view.setBackgroundColor(highlightColor);
299 } else {
300 view.setBackgroundColor(0);
301 }
302 if (model instanceof String) {
303 view.setText((String) model);
304 } else {
305 final Radio r = (Radio) model;
306 if (isRadioNames) {
307 view.setText(r.name + " (" + r.genre + ")");
308 } else {
309 view.setText(r.currentTrack + " (" + r.genre + ")");
310 }
311 }
312 }
313
314 private List<TrackMetadataBean> getRadioStreams(final Radio radio) {
315 try {
316 return radio.getRadioURLs();
317 } catch (final Exception ex) {
318 throw new RuntimeException(ex);
319 }
320 }
321
322 @Override
323 public List<TrackMetadataBean> computeTracks(Interval highlight) {
324 if (isShowingGenre)
325 throw new IllegalStateException();
326 final List<TrackMetadataBean> result = new ArrayList<TrackMetadataBean>();
327 for (int i = highlight.start; i <= highlight.end; i++) {
328 final Radio r = radioList.get(i);
329 result.addAll(getRadioStreams(r));
330 }
331 return new CopyOnWriteArrayList<TrackMetadataBean>(result);
332 }
333
334 private void sortRadioList() {
335 if (isRadioNames) {
336 ShoutcastUtils.sortByName(radioList);
337 } else {
338 Collections.sort(radioList, new Comparator<Radio>() {
339 public int compare(Radio object1, Radio object2) {
340 return object1.currentTrack
341 .compareToIgnoreCase(object2.currentTrack);
342 }
343 });
344 }
345 }
346
347 /***
348 * Browse given genre.
349 *
350 * @param genre
351 * the genre to browse.
352 */
353 public void showGenre(final String genre) {
354
355 app.getBackgroundTasks().schedule(new RadioDownloader(genre),
356 RadioDownloader.class, true,
357 app.getString(R.string.shoutcastDownloadStations));
358 }
359
360 @Override
361 protected void performZoom(boolean zoom) {
362 super.performZoom(zoom);
363 initButtonBar(R.id.shoutcastButtons, actions);
364 }
365 }