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
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
160 }
161
162 public void randomChanged(Random random) {
163
164 }
165
166 public void repeatChanged(Repeat repeat) {
167
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
227 }
228
229 public void libraryUpdate(boolean updateStarted, boolean interrupted,
230 boolean userNotified) {
231
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
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
337 }
338
339 public void configChanged(ConfigurationBean config) {
340
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
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
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 }