/* * Copyright 2008-2013, ETH Zürich, Samuel Welten, Michael Kuhn, Tobias Langner, * Sandro Affentranger, Lukas Bossard, Michael Grob, Rahul Jain, * Dominic Langenegger, Sonia Mayor Alonso, Roger Odermatt, Tobias Schlueter, * Yannick Stucki, Sebastian Wendland, Samuel Zehnder, Samuel Zihlmann, * Samuel Zweifel * * This file is part of Jukefox. * * Jukefox is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or any later version. Jukefox is * distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * Jukefox. If not, see <http://www.gnu.org/licenses/>. */ package ch.ethz.dcg.pancho3.view.tabs; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import ch.ethz.dcg.jukefox.commons.utils.AndroidUtils; import ch.ethz.dcg.jukefox.commons.utils.JoinableThread; import ch.ethz.dcg.jukefox.commons.utils.Log; import ch.ethz.dcg.jukefox.commons.utils.SongTimeFormatter; import ch.ethz.dcg.jukefox.controller.player.IOnPlayerStateChangeListener; import ch.ethz.dcg.jukefox.controller.player.IOnPlaylistStateChangeListener; import ch.ethz.dcg.jukefox.model.collection.BaseAlbum; import ch.ethz.dcg.jukefox.model.collection.BaseArtist; import ch.ethz.dcg.jukefox.model.collection.BaseSong; import ch.ethz.dcg.jukefox.model.collection.IBaseListItem; import ch.ethz.dcg.jukefox.model.collection.IReadOnlyPlaylist; import ch.ethz.dcg.jukefox.model.collection.PlaylistSong; import ch.ethz.dcg.jukefox.model.commons.EmptyPlaylistException; import ch.ethz.dcg.jukefox.model.commons.NoAlbumArtException; import ch.ethz.dcg.jukefox.model.player.PlayModeType; import ch.ethz.dcg.jukefox.model.player.PlayerState; import ch.ethz.dcg.jukefox.playmode.IPlayMode; import ch.ethz.dcg.jukefox.playmode.smartshuffle.SmartShufflePlayMode2; import ch.ethz.dcg.jukefox.playmode.smartshuffle.agents.IAgent; import ch.ethz.dcg.pancho3.R; import ch.ethz.dcg.pancho3.controller.eventhandlers.PlayerActivityEventListener; import ch.ethz.dcg.pancho3.view.commons.BitmapReflection; import ch.ethz.dcg.pancho3.view.commons.SongProgressBar; import ch.ethz.dcg.pancho3.view.overlays.AgentVotesToast; import ch.ethz.dcg.pancho3.view.tabs.lists.MoveableTextListAdapter; import com.commonsware.cwac.tlv.TouchListView; public class PlayerActivity extends JukefoxTabActivity { public static final String TAG = PlayerActivity.class.getSimpleName(); private PlayerActivityEventListener eventListener; private Handler handler; private Timer progressUpdateTimer; private SongProgressBar songProgressBar; private TextView progressText; private SongTimeFormatter songTimeFormatter; private TextView nowPlayingText; private TouchListView list; private ImageView albumArt; private IOnPlaylistStateChangeListener playlistStateEventListener; private IOnPlayerStateChangeListener playerStateEventListener; private MoveableTextListAdapter<IBaseListItem> adapter; private TouchListView.DropListener onDrop = new TouchListView.DropListener() { @Override public void drop(int from, int to) { Log.v(TAG, "Move playlist item from " + from + " to " + to); eventListener.movePlaylistElement(from, to); } }; private TouchListView.RemoveListener onRemove = new TouchListView.RemoveListener() { @Override public void remove(int which) { adapter.remove(adapter.getItem(which)); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.player); Log.v(TAG, "Settings tabs"); setCurrentTab(Tab.PLAYER); Log.v(TAG, "Registering Listeners"); registerButtonEventListeners(); // Progress Bar is initialized as soon as possible to avoid flickering // of the progress songProgressBar = (SongProgressBar) findViewById(R.id.songProgressBar); songProgressBar.setMax(100); songProgressBar.setProgress(0); eventListener = controller.createPlayerViewEventListener(this); registerListEventListeners(); Log.v(TAG, "initialize view..."); handler = new Handler(); initializeView(); setCoverClickHint(); Log.v(TAG, "initialize view completed"); if (!AndroidUtils.isSdCardOk()) { eventListener.sdCardProblemDetected(); finish(); return; } if (applicationState.isFirstStart()) { Log.d(TAG, "first start."); eventListener.detectedFirstStart(); } Log.d(TAG, "PlayerActivity.onCreate() finished."); } private void setCoverClickHint() { int numberCoverClicked = getSettings().getCoverHintCountPlayer(); if (numberCoverClicked < PlayerActivityEventListener.NUMBER_COVER_HINT_THRESSHOLD) { findViewById(R.id.clickCover).setVisibility(View.VISIBLE); } } @Override protected void onResume() { Log.v(TAG, "onResume()"); super.onResume(); if (settings.isAutomaticallyShowCover()) { eventListener.showAlbumArt(); } else { eventListener.hideAlbumArt(); } if (playerController.isReady()) { startUpdateTimer(); } Log.v(TAG, "onResume() finished."); } @Override protected void onPause() { cancelUpdateTimer(); super.onPause(); } private void registerListEventListeners() { list = (TouchListView) findViewById(R.id.playlist); list.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) { eventListener.onPlaylistItemClicked(position); } }); list.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int position, long arg3) { return eventListener.onPlaylistItemLongClicked(position); } }); list.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { eventListener.onScroll(firstVisibleItem); } }); } private void initializeView() { // Do rest of operations in a thread to allow the onCreate method to // complete JoinableThread initializer = new JoinableThread(new Runnable() { @Override public void run() { waitForPlaybackFunctionality(); // View operations must run in the main thread handler.post(new Runnable() { @Override public void run() { // Log.v(TAG, "Setting cover art"); albumArt = (ImageView) findViewById(R.id.bigCoverArt); // if (!settings.isAutomaticallyShowCover() && // (settings.isUseGalleryBackground() || // settings.isUseWallpaperBackground())) { // albumArt.setVisibility(View.GONE); // } songTimeFormatter = new SongTimeFormatter(); songProgressBar.setReactOnMoveEvents(true); songProgressBar.setOnProgressChangeListener(new SongProgressBar.OnProgressChangeListener() { public void onProgressChanged(View v, int progress) { eventListener.setProgress(progress); } }); progressText = (TextView) findViewById(R.id.songProgressText); nowPlayingText = (TextView) findViewById(R.id.nowPlayingText); // Log.v(TAG, "Register player events"); registerPlayerEventListeners(); // Log.v(TAG, "updating view"); updateView(); // Log.v(TAG, "updated view"); } }); } }); initializer.start(); } private void updateView() { updatePlayerState(playerController.getPlayerState()); try { updateSongInfo(playerController.getCurrentSong()); } catch (EmptyPlaylistException e) { // Log.w(TAG, e); updateSongInfo(null); } loadPlaylist(playerController.getCurrentPlaylist()); list.setSelection(PlayerActivity.this.playerController.getCurrentPlaylist().getPositionInList()); } private void updateSongInfo(final BaseSong<? extends BaseArtist, ? extends BaseAlbum> currentSong) { handler.post(new Runnable() { @Override public void run() { if (currentSong == null) { Log.v(TAG, "currentSong == null"); nowPlayingText.setText(getString(R.string.artist_title_place_holder)); albumArt.setImageResource(R.drawable.d137_fox_reflection); return; } nowPlayingText.setText(currentSong.getArtist().getName() + " - " + currentSong.getName()); Log.v(TAG, "currentSong != null"); updateProgress(); setAlbumArt(currentSong); try { list.setSelection(PlayerActivity.this.playerController.getCurrentPlaylist().getPositionInList()); // final View v = list.getSelectedView(); // if (v != null) { // JukefoxApplication.getHandler().post(new Runnable() { // // @Override // public void run() { // v // .setBackgroundResource(R.drawable.d046_bg_button); // } // // }); // } updateHighlightedListEntry(); } catch (EmptyPlaylistException e1) { // TODO Auto-generated catch block Log.w(TAG, e1); } } }); } private void setAlbumArt(final BaseSong<? extends BaseArtist, ? extends BaseAlbum> currentSong) { try { Bitmap bitmap = albumArtProvider.getAlbumArt(currentSong.getAlbum(), false); BitmapReflection.setReflectionToImageViewAsync(bitmap, albumArt); } catch (NoAlbumArtException e) { Log.w(TAG, e); albumArt.setImageResource(R.drawable.d005_empty_cd); } } private void updateHighlightedListEntry() throws EmptyPlaylistException { if (adapter != null) { adapter.setHighlightPosition(PlayerActivity.this.playerController.getCurrentPlaylist().getPositionInList()); } } private void registerPlayerEventListeners() { // applicationState.waitForPlaybackFunctionality(); playlistStateEventListener = new IOnPlaylistStateChangeListener() { @Override public void onCurrentSongChanged(PlaylistSong<BaseArtist, BaseAlbum> newSong) { updateSongInfo(newSong); // Show a toast with the votes of the top agents IPlayMode playMode = playerController.getPlayMode(); if (playMode.getPlayModeType() == PlayModeType.SMART_SHUFFLE) { if (playMode instanceof SmartShufflePlayMode2) { SmartShufflePlayMode2 smartShufflePlayMode = (SmartShufflePlayMode2) playMode; Map<IAgent, Float> agentVotes = smartShufflePlayMode.getAgentVotesForCurrentSong(); new AgentVotesToast(newSong, agentVotes, PlayerActivity.this).show(); } } } @Override public void onPlayModeChanged(IPlayMode newPlayMode) { } @Override public void onPlaylistChanged(IReadOnlyPlaylist newPlaylist) { Log.v(TAG, "Playlist changed. New size: " + newPlaylist.getPlaylistSize()); loadPlaylist(newPlaylist); } }; super.playerController.addOnPlaylistStateChangeListener(playlistStateEventListener); playerStateEventListener = new IOnPlayerStateChangeListener() { @Override public void onPlayerStateChanged(PlayerState newPlayerState) { Log.v(TAG, "Player state changed"); updatePlayerState(newPlayerState); } @Override public void onSongCompleted(PlaylistSong<BaseArtist, BaseAlbum> song) { } @Override public void onSongSkipped(PlaylistSong<BaseArtist, BaseAlbum> song, int position) { } @Override public void onSongStarted(PlaylistSong<BaseArtist, BaseAlbum> song) { } }; super.playerController.addOnPlayerStateChangeListener(playerStateEventListener); } @Override protected void onDestroy() { super.playerController.removeOnPlayerStateChangeListener(playerStateEventListener); super.playerController.removeOnPlaylistStateChangeListener(playlistStateEventListener); super.onDestroy(); } private void updatePlayerState(PlayerState newPlayerState) { if (newPlayerState == PlayerState.PLAY) { startUpdateTimer(); } else { cancelUpdateTimer(); updateProgress(); } } private void cancelUpdateTimer() { if (progressUpdateTimer != null) { progressUpdateTimer.cancel(); } } private void startUpdateTimer() { cancelUpdateTimer(); progressUpdateTimer = new Timer(); progressUpdateTimer.schedule(new TimerTask() { @Override public void run() { updateProgress(); } }, 0, 1000); Log.w(TAG, "started update timer"); } public void updateProgress() { handler.post(new Runnable() { @Override public void run() { int songPos = playerController.getPlaybackPosition() < 0 ? 0 : playerController.getPlaybackPosition(); int songDuration = playerController.getDuration() < 0 ? 0 : playerController.getDuration(); // Log.v(TAG, "songDuration: " + songDuration); // Log.v(TAG, "songPos: " + songPos); // Check for null to avoid race conditions at the start if (songProgressBar != null) { songProgressBar.setMax(songDuration); songProgressBar.setProgress(songPos); } if (progressText != null) { progressText.setText(songTimeFormatter.format(songPos) + "/" + songTimeFormatter.format(songDuration)); } } }); } private void loadPlaylist(final IReadOnlyPlaylist playlist) { handler.post(new Runnable() { @Override public void run() { // int position = list.getFirstVisiblePosition(); adapter = new MoveableTextListAdapter(PlayerActivity.this, R.layout.moveabletextlistitem, playlist .getSongList()); try { adapter.setHighlightPosition(PlayerActivity.this.playerController.getCurrentSongIndex()); } catch (EmptyPlaylistException e) { Log.w(TAG, "Empty playlist: Cannot highlight position."); } list.setAdapter(adapter); try { list.setSelection(PlayerActivity.this.playerController.getCurrentSongIndex()); } catch (EmptyPlaylistException e) { Log.w(TAG, "Empty playlist: Cannot select song."); } list.setDropListener(onDrop); list.setRemoveListener(onRemove); } }); } private void registerButtonEventListeners() { final ImageView albumArt = (ImageView) findViewById(R.id.bigCoverArt); albumArt.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return eventListener.onAlbumArtTouch(event, albumArt); } }); ListView list = (ListView) findViewById(R.id.playlist); list.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return eventListener.onListTouch(event, albumArt); } }); } public Handler getHandler() { return handler; } public TouchListView getList() { return list; } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); // Log.v(TAG, "onNewIntent()"); if (applicationState.isImporting()) { showStatusInfo(getString(R.string.jukefox_is_currently_importing)); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (eventListener.onKey(keyCode, event)) { return true; } return super.onKeyDown(keyCode, event); } }