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  
19  package sk.baka.ambient.activity.main;
20  
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.util.Arrays;
24  import java.util.List;
25  
26  import sk.baka.ambient.ActionsEnum;
27  import sk.baka.ambient.AmbientApplication;
28  import sk.baka.ambient.AppState;
29  import sk.baka.ambient.ConfigurationBean;
30  import sk.baka.ambient.IApplicationListener;
31  import sk.baka.ambient.IPlaylistPlayerListener;
32  import sk.baka.ambient.R;
33  import sk.baka.ambient.collection.TrackMetadataBean;
34  import sk.baka.ambient.commons.IOUtils;
35  import sk.baka.ambient.commons.Interval;
36  import sk.baka.ambient.commons.MiscUtils;
37  import sk.baka.ambient.library.ILibraryListener;
38  import sk.baka.ambient.lrc.LRCLyrics;
39  import sk.baka.ambient.lrc.lyrdb.LyrdbTrack;
40  import sk.baka.ambient.playerservice.PlayerStateEnum;
41  import sk.baka.ambient.playlist.PlaylistItem;
42  import sk.baka.ambient.playlist.Random;
43  import sk.baka.ambient.playlist.Repeat;
44  import sk.baka.ambient.views.ButtonBar;
45  import sk.baka.ambient.views.gesturelist.GesturesListView;
46  import android.content.Intent;
47  import android.net.Uri;
48  import android.view.View;
49  import android.widget.TextView;
50  
51  /***
52   * Controls the "Context" page.
53   * 
54   * @author Martin Vysny
55   */
56  public final class ContextController extends AbstractController implements
57  		IPlaylistPlayerListener, ILibraryListener, IApplicationListener {
58  
59  	private final List<ActionsEnum> actions = Arrays.asList(
60  			ActionsEnum.GoOnline, ActionsEnum.ShowWiki, ActionsEnum.ShowLyrics,
61  			ActionsEnum.ShowKaraoke, ActionsEnum.RefreshKaraoke);
62  
63  	/***
64  	 * @param mainActivity
65  	 */
66  	public ContextController(MainActivity mainActivity) {
67  		super(R.id.contextbrowser, mainActivity);
68  		content = (TextView) mainActivity.findViewById(R.id.contextContent);
69  		final boolean offline = !app.getStateHandler().getStartupState().online;
70  		offline(offline);
71  	}
72  
73  	private boolean showingKaraoke = false;
74  
75  	private TextView content;
76  
77  	@Override
78  	public void destroy() {
79  		content = null;
80  		setSelectorVisibility(false);
81  		super.destroy();
82  	}
83  
84  	@Override
85  	protected void onAction(ActionsEnum action) {
86  		if (action == ActionsEnum.ShowKaraoke) {
87  			showingKaraoke = !showingKaraoke;
88  			final ButtonBar bar = (ButtonBar) mainActivity
89  					.findViewById(R.id.contextButtons);
90  			bar.highlight(showingKaraoke ? Interval.fromItem(3) : null);
91  			initKaraoke(
92  					app.getPlaylist().getCurrentlyPlayingTrack(),
93  					app.getPlaylist().getPlaybackState() == PlayerStateEnum.Playing);
94  			return;
95  		}
96  		if (action == ActionsEnum.RefreshKaraoke) {
97  			final TrackMetadataBean track = app.getPlaylist()
98  					.getCurrentlyPlayingTrack();
99  			if (track != null) {
100 				app.getKaraoke().deleteLyrics(track);
101 			}
102 			content.setText("");
103 			setStatus(-1);
104 			AmbientApplication.getHandler().removeCallbacks(textSwitcher);
105 			setSelectorVisibility(false);
106 			initKaraoke(
107 					track,
108 					app.getPlaylist().getPlaybackState() == PlayerStateEnum.Playing);
109 			return;
110 		}
111 		if (action == ActionsEnum.ShowLyrics) {
112 			showLyrics();
113 			return;
114 		}
115 		if (action == ActionsEnum.ShowWiki) {
116 			showWiki();
117 			return;
118 		}
119 		super.onAction(action);
120 	}
121 
122 	private LRCLyrics lyrics = null;
123 
124 	private void initKaraoke(TrackMetadataBean track, final boolean play) {
125 		if ((track == null) || (!showingKaraoke)) {
126 			content.setText("");
127 			setStatus(-1);
128 			AmbientApplication.getHandler().removeCallbacks(textSwitcher);
129 			return;
130 		}
131 		try {
132 			lyrics = null;
133 			// if the track is local, try to find lyrics locally
134 			if (track.isLocal()) {
135 				final File lrc = new File(IOUtils.stripExt(track.getLocation())
136 						+ ".lrc");
137 				if (lrc.exists()) {
138 					lyrics = LRCLyrics.parse(new FileInputStream(lrc));
139 				}
140 			}
141 			if (lyrics == null) {
142 				lyrics = app.getKaraoke().getLyrics(track);
143 			}
144 		} catch (Exception e) {
145 			app.error(ContextController.class, true,
146 					"Failed to get karaoke lyrics", e);
147 			content.setText("");
148 			setStatus(-1);
149 			return;
150 		}
151 		rescheduleKaraoke(play);
152 	}
153 
154 	public void playbackStateChanged(PlayerStateEnum state) {
155 		rescheduleKaraoke(state == PlayerStateEnum.Playing);
156 	}
157 
158 	public void playlistChanged(Interval target) {
159 		// ignore
160 	}
161 
162 	public void randomChanged(Random random) {
163 		// ignore
164 	}
165 
166 	public void repeatChanged(Repeat repeat) {
167 		// ignore
168 	}
169 
170 	public void trackChanged(PlaylistItem track, boolean play,
171 			final int positionMillis) {
172 		setSelectorVisibility(false);
173 		initKaraoke(track == null ? null : track.getTrack(), play);
174 	}
175 
176 	public void trackPositionChanged(final int position, final boolean playing) {
177 		rescheduleKaraoke(playing);
178 	}
179 
180 	private void rescheduleKaraoke(final boolean play) {
181 		AmbientApplication.getHandler().removeCallbacks(textSwitcher);
182 		if (!play || !showingKaraoke) {
183 			setStatus(-1);
184 			content.setText("");
185 			return;
186 		}
187 		if (lyrics == null) {
188 			final boolean offline = !app.getStateHandler().getStartupState().online;
189 			if (offline) {
190 				setStatus(R.string.inOfflineMode);
191 			} else {
192 				setStatus(-1);
193 			}
194 			return;
195 		}
196 		setStatus(R.string.karaokeForTrack);
197 		AmbientApplication.getHandler().post(textSwitcher);
198 	}
199 
200 	private final KaraokeTextSwitcher textSwitcher = new KaraokeTextSwitcher();
201 
202 	/***
203 	 * Once started, continuously reschedules itself and shows correct lyrics
204 	 * line.
205 	 * 
206 	 * @author Martin Vysny
207 	 */
208 	private final class KaraokeTextSwitcher implements Runnable {
209 
210 		public void run() {
211 			if ((lyrics == null) || (!showingKaraoke)) {
212 				return;
213 			}
214 			final int position = app.getPlaylist().getPosition();
215 			final String lyricsLine = lyrics.getLineToDisplay(position);
216 			final long nextLineTime = lyrics.getNextLineTime(position);
217 			if (nextLineTime < 0)
218 				return;
219 			final long delayFromNow = nextLineTime - position;
220 			AmbientApplication.getHandler().postDelayed(this, delayFromNow);
221 			content.setText(lyricsLine);
222 		}
223 	}
224 
225 	public void coverLoaded(TrackMetadataBean track) {
226 		// ignore
227 	}
228 
229 	public void libraryUpdate(boolean updateStarted, boolean interrupted,
230 			boolean userNotified) {
231 		// ignore
232 	}
233 
234 	private void setSelectorVisibility(final boolean visible) {
235 		mainView.findViewById(R.id.contextContent).setVisibility(
236 				visible ? View.GONE : View.VISIBLE);
237 		mainView.findViewById(R.id.contextLyricsSelector).setVisibility(
238 				visible ? View.VISIBLE : View.GONE);
239 		if (lyricsSelector != null) {
240 			lyricsSelector.destroy();
241 			lyricsSelector = null;
242 		}
243 		if (visible) {
244 			lyricsSelector = new LyricsSelectorController();
245 			lyricsSelector.zoom(isZoomed());
246 		} else {
247 			lyricsSelector = null;
248 		}
249 	}
250 
251 	private LyricsSelectorController lyricsSelector = null;
252 
253 	/***
254 	 * Shows an immutable list of strings.
255 	 * 
256 	 * @author Martin Vysny
257 	 */
258 	private class LyricsSelectorController extends AbstractListController {
259 		/***
260 		 * Creates new controller.
261 		 */
262 		public LyricsSelectorController() {
263 			super(R.id.contextLyricsSelector, R.id.contextLyricsSelector,
264 					ContextController.this.mainActivity);
265 		}
266 
267 		private List<LyrdbTrack> lyrics = null;
268 		private TrackMetadataBean track;
269 
270 		/***
271 		 * Sets the content and updates the list view.
272 		 * 
273 		 * @param track
274 		 *            show selector for this track.
275 		 * @param lyrics
276 		 *            the content.
277 		 */
278 		public void setContent(final TrackMetadataBean track,
279 				final List<LyrdbTrack> lyrics) {
280 			this.track = track;
281 			this.lyrics = lyrics;
282 			update(null);
283 		}
284 
285 		@Override
286 		protected void recomputeListItems() {
287 			listView.getModel().getModel().clear();
288 			listView.getModel().getModel().addAll(lyrics);
289 		}
290 
291 		public void itemActivated(int index, Object model) {
292 			// download given karaoke.
293 			final LyrdbTrack lyrics = (LyrdbTrack) model;
294 			app.getKaraoke().setLyricsForTrack(track, lyrics);
295 			setSelectorVisibility(false);
296 			initKaraoke(
297 					track,
298 					app.getPlaylist().getPlaybackState() == PlayerStateEnum.Playing);
299 		}
300 
301 		public void update(GesturesListView listView, View itemView, int index,
302 				Object model) {
303 			final boolean highlighted = listView.getHighlight().contains(index);
304 			final int bgColor;
305 			if (highlighted) {
306 				bgColor = highlightColor;
307 			} else {
308 				bgColor = 0;
309 			}
310 			itemView.setBackgroundColor(bgColor);
311 			final TextView text = (TextView) itemView;
312 			text.setText(((LyrdbTrack) model).displayableString);
313 		}
314 	}
315 
316 	public void lyricsLoaded(TrackMetadataBean track,
317 			final List<LyrdbTrack> lyrics) {
318 		if (!track.equals(app.getPlaylist().getCurrentlyPlayingTrack())) {
319 			return;
320 		}
321 		final boolean isSelecting = (lyrics != null) && !lyrics.isEmpty();
322 		setSelectorVisibility(isSelecting);
323 		if (isSelecting) {
324 			setStatus(R.string.karaokeNotFoundSuggestions);
325 			lyricsSelector.setContent(track, lyrics);
326 		} else if (lyrics != null) {
327 			setStatus(R.string.karaokeNotFound);
328 		} else {
329 			initKaraoke(
330 					track,
331 					app.getPlaylist().getPlaybackState() == PlayerStateEnum.Playing);
332 		}
333 	}
334 
335 	public void clipboardChanged() {
336 		// do nothing
337 	}
338 
339 	public void configChanged(ConfigurationBean config) {
340 		// do nothing
341 	}
342 
343 	public void offline(boolean offline) {
344 		actions.set(0, offline ? ActionsEnum.GoOnline : ActionsEnum.GoOffline);
345 		initButtonBar(R.id.contextButtons, actions);
346 		initKaraoke(app.getPlaylist().getCurrentlyPlayingTrack(), app
347 				.getPlaylist().getPlaybackState() == PlayerStateEnum.Playing);
348 	}
349 
350 	private void setStatus(final int strId) {
351 		final TextView tv = (TextView) mainView
352 				.findViewById(R.id.contextStatusBar);
353 		if (strId == -1) {
354 			tv.setText("");
355 		} else {
356 			tv.setText(strId);
357 		}
358 	}
359 
360 	public void stateChanged(AppState state) {
361 		// do nothing
362 	}
363 
364 	private TrackMetadataBean getCurrent() {
365 		final TrackMetadataBean currentTrack = app.getPlaylist()
366 				.getCurrentlyPlayingTrack();
367 		if (currentTrack == null) {
368 			app.error(MainActivity.class, true, mainActivity
369 					.getString(R.string.no_playing_track), null);
370 		}
371 		return currentTrack;
372 	}
373 
374 	private void showLyrics() {
375 		final TrackMetadataBean currentTrack = getCurrent();
376 		if (currentTrack == null) {
377 			return;
378 		}
379 		if (MiscUtils.isEmptyOrWhitespace(currentTrack.getArtist())
380 				|| MiscUtils.isEmptyOrWhitespace(currentTrack.getTitle())) {
381 			app.error(MainActivity.class, true, mainActivity
382 					.getString(R.string.no_track_meta), null);
383 			return;
384 		}
385 		// strip the track title (remove characters after the "(" char)
386 		String title = currentTrack.getTitle();
387 		final int bracket = title.indexOf('(');
388 		title = bracket < 0 ? title : title.substring(0, bracket).trim();
389 		final String browseURI = "http://letras.terra.com.br/winamp.php?musica="
390 				+ IOUtils.encodeURL(title)
391 				+ "&artista="
392 				+ IOUtils.encodeURL(currentTrack.getArtist());
393 		mainActivity.startActivity(new Intent(Intent.ACTION_VIEW, Uri
394 				.parse(browseURI)));
395 	}
396 
397 	private void showWiki() {
398 		final TrackMetadataBean currentTrack = getCurrent();
399 		if (currentTrack == null) {
400 			return;
401 		}
402 		if (MiscUtils.isEmptyOrWhitespace(currentTrack.getArtist())) {
403 			app.error(MainActivity.class, true, mainActivity
404 					.getString(R.string.no_track_meta), null);
405 			return;
406 		}
407 		final String browseURI = "http://en.wikipedia.org/wiki/"
408 				+ IOUtils.encodeURL(currentTrack.getArtist());
409 		mainActivity.startActivity(new Intent(Intent.ACTION_VIEW, Uri
410 				.parse(browseURI)));
411 	}
412 
413 	@Override
414 	protected void performZoom(boolean zoom) {
415 		initButtonBar(R.id.contextButtons, actions);
416 		if (lyricsSelector != null) {
417 			lyricsSelector.zoom(isZoomed());
418 		}
419 	}
420 }