diff --git a/res/layout/full_playback_alt.xml b/res/layout/full_playback_alt.xml index 8f43f33e..94e7df38 100644 --- a/res/layout/full_playback_alt.xml +++ b/res/layout/full_playback_alt.xml @@ -85,6 +85,32 @@ THE SOFTWARE. android:paddingBottom="5dip" android:paddingLeft="15dip" android:paddingRight="15dip" /> + + + + + + + + + + + + + + + + + + + + + + Queue cleared. Cover art Close notification + Genre + Track + Composer + Format Choose Playlist Name diff --git a/src/org/kreed/vanilla/CompatMetadata.java b/src/org/kreed/vanilla/CompatMetadata.java new file mode 100644 index 00000000..2e70aa14 --- /dev/null +++ b/src/org/kreed/vanilla/CompatMetadata.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2012 Christopher Eby + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.kreed.vanilla; + +import android.media.MediaMetadataRetriever; + +/** + * Wrapper around MediaMetadataRetriever to allow compatibility with older + * versions of Android. + */ +public class CompatMetadata { + /** + * The wrapped MediaMetadataRetriever instance. + */ + private final MediaMetadataRetriever mData; + + /** + * Create an MediaMetadataRetriever attached to the file at the given path. + */ + public CompatMetadata(String path) + { + MediaMetadataRetriever data = new MediaMetadataRetriever(); + data.setDataSource(path); + mData = data; + } + + /** + * Call {@link MediaMetadataRetriever#extractMetadata(int)}. + */ + public String extractMetadata(int keyCode) + { + return mData.extractMetadata(keyCode); + } + + /** + * Call {@link MediaMetadataRetriever#release()}. + */ + public void release() + { + mData.release(); + } +} diff --git a/src/org/kreed/vanilla/FullPlaybackActivity.java b/src/org/kreed/vanilla/FullPlaybackActivity.java index 744cc2c1..6ad6bc15 100644 --- a/src/org/kreed/vanilla/FullPlaybackActivity.java +++ b/src/org/kreed/vanilla/FullPlaybackActivity.java @@ -25,6 +25,7 @@ package org.kreed.vanilla; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Color; +import android.media.MediaMetadataRetriever; import android.os.Build; import android.os.Bundle; import android.os.Message; @@ -39,6 +40,7 @@ import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.SeekBar; +import android.widget.TableLayout; import android.widget.TextView; /** @@ -58,6 +60,7 @@ public class FullPlaybackActivity extends PlaybackActivity private SeekBar mSeekBar; private TextView mElapsedView; private TextView mDurationView; + private TableLayout mExtraInfo; private TextView mTitle; private TextView mAlbum; @@ -67,7 +70,10 @@ public class FullPlaybackActivity extends PlaybackActivity * True if the controls are visible (play, next, seek bar, etc). */ private boolean mControlsVisible; - + /** + * True if the extra info is visible. + */ + private boolean mExtraInfoVisible; /** * Current song duration in milliseconds. */ @@ -92,6 +98,17 @@ public class FullPlaybackActivity extends PlaybackActivity */ private Song mCurrentSong; + private String mGenre; + private TextView mGenreView; + private String mTrack; + private TextView mTrackView; + private String mYear; + private TextView mYearView; + private String mComposer; + private TextView mComposerView; + private String mFormat; + private TextView mFormatView; + @Override public void onCreate(Bundle icicle) { @@ -139,8 +156,10 @@ public class FullPlaybackActivity extends PlaybackActivity nextButton.setOnClickListener(this); View controlsTop = findViewById(R.id.controls_top); - if (controlsTop != null) + if (controlsTop != null) { controlsTop.setOnClickListener(this); + controlsTop.setOnLongClickListener(this); + } mTitle = (TextView)findViewById(R.id.title); mAlbum = (TextView)findViewById(R.id.album); @@ -152,6 +171,13 @@ public class FullPlaybackActivity extends PlaybackActivity mSeekBar.setMax(1000); mSeekBar.setOnSeekBarChangeListener(this); + mExtraInfo = (TableLayout)findViewById(R.id.extra_info); + mGenreView = (TextView)findViewById(R.id.genre); + mTrackView = (TextView)findViewById(R.id.track); + mYearView = (TextView)findViewById(R.id.year); + mComposerView = (TextView)findViewById(R.id.composer); + mFormatView = (TextView)findViewById(R.id.format); + mShuffleButton = (ImageButton)findViewById(R.id.shuffle); mShuffleButton.setOnClickListener(this); registerForContextMenu(mShuffleButton); @@ -160,6 +186,7 @@ public class FullPlaybackActivity extends PlaybackActivity registerForContextMenu(mEndButton); setControlsVisible(settings.getBoolean("visible_controls", true)); + setExtraInfoVisible(settings.getBoolean("visible_extra_info", false)); setDuration(0); } @@ -269,6 +296,10 @@ public class FullPlaybackActivity extends PlaybackActivity mCurrentSong = song; updateProgress(); + + if (mExtraInfoVisible) { + mHandler.sendEmptyMessage(MSG_LOAD_EXTRA_INFO); + } } /** @@ -389,7 +420,9 @@ public class FullPlaybackActivity extends PlaybackActivity } /** - * Toggles the visibility of the playback controls. + * Set the visibility of the controls views. + * + * @param visible True to show, false to hide */ private void setControlsVisible(boolean visible) { @@ -406,6 +439,89 @@ public class FullPlaybackActivity extends PlaybackActivity } } + /** + * Set the visibility of the extra metadata view. + * + * @param visible True to show, false to hide + */ + private void setExtraInfoVisible(boolean visible) + { + if (mExtraInfo == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD_MR1) + return; + + mExtraInfo.setVisibility(visible ? View.VISIBLE : View.GONE); + mExtraInfoVisible = visible; + if (visible && !mHandler.hasMessages(MSG_LOAD_EXTRA_INFO)) { + mHandler.sendEmptyMessage(MSG_LOAD_EXTRA_INFO); + } + } + + /** + * Retrieve the extra metadata for the current song. + */ + private void loadExtraInfo() + { + Song song = mCurrentSong; + + if (song == null) { + mGenre = null; + mTrack = null; + mYear = null; + mComposer = null; + mFormat = null; + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1) { + CompatMetadata data = new CompatMetadata(song.path); + + mGenre = data.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE); + mTrack = data.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER); + String composer = data.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER); + if (composer == null) + composer = data.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER); + mComposer = composer; + + String year = data.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR); + if (year == null || "0".equals(year)) { + year = null; + } else { + int dash = year.indexOf('-'); + if (dash != -1) + year = year.substring(0, dash); + } + mYear = year; + + StringBuilder sb = new StringBuilder(12); + sb.append(decodeMimeType(data.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE))); + String bitrate = data.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE); + if (bitrate.length() > 3) { + sb.append(' '); + sb.append(bitrate.substring(0, bitrate.length() - 3)); + sb.append("kbps"); + } + mFormat = sb.toString(); + + data.release(); + } + + mUiHandler.sendEmptyMessage(MSG_COMMIT_INFO); + } + + /** + * Decode the given mime type into a more human-friendly description. + */ + private String decodeMimeType(String mime) + { + if ("audio/mpeg".equals(mime)) { + return "MP3"; + } else if ("audio/mp4".equals(mime)) { + return "AAC"; + } else if ("audio/vorbis".equals(mime)) { + return "Ogg Vorbis"; + } else if ("audio/flac".equals(mime)) { + return "FLAC"; + } + return mime; + } + /** * Update the seekbar progress with the current song progress. This must be * called on the UI Handler. @@ -415,21 +531,40 @@ public class FullPlaybackActivity extends PlaybackActivity * Save the hidden_controls preference to storage. */ private static final int MSG_SAVE_CONTROLS = 14; + /** + * Call {@link #loadExtraInfo()}. + */ + private static final int MSG_LOAD_EXTRA_INFO = 15; + /** + * Pass obj to mExtraInfo.setText() + */ + private static final int MSG_COMMIT_INFO = 16; @Override public boolean handleMessage(Message message) { switch (message.what) { case MSG_SAVE_CONTROLS: { - SharedPreferences settings = PlaybackService.getSettings(this); - SharedPreferences.Editor editor = settings.edit(); + SharedPreferences.Editor editor = PlaybackService.getSettings(this).edit(); editor.putBoolean("visible_controls", mControlsVisible); + editor.putBoolean("visible_extra_info", mExtraInfoVisible); editor.commit(); break; } case MSG_UPDATE_PROGRESS: updateProgress(); break; + case MSG_LOAD_EXTRA_INFO: + loadExtraInfo(); + break; + case MSG_COMMIT_INFO: { + mGenreView.setText(mGenre); + mTrackView.setText(mTrack); + mYearView.setText(mYear); + mComposerView.setText(mComposer); + mFormatView.setText(mFormat); + break; + } default: return super.handleMessage(message); } @@ -483,11 +618,18 @@ public class FullPlaybackActivity extends PlaybackActivity @Override public boolean onLongClick(View view) { - if (view.getId() == R.id.cover_view) { + switch (view.getId()) { + case R.id.cover_view: performAction(mCoverLongPressAction); - return true; + break; + case R.id.controls_top: + setExtraInfoVisible(!mExtraInfoVisible); + mHandler.sendEmptyMessage(MSG_SAVE_CONTROLS); + break; + default: + return false; } - return false; + return true; } }