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