AudioPreview.java revision 8d08ec235831d71fdd7f7b6f7757c2bc19528fae
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.music; 18 19import android.app.Activity; 20import android.content.AsyncQueryHandler; 21import android.content.ContentResolver; 22import android.content.Context; 23import android.content.Intent; 24import android.database.Cursor; 25import android.media.AudioManager; 26import android.media.MediaPlayer; 27import android.media.AudioManager.OnAudioFocusChangeListener; 28import android.media.MediaPlayer.OnCompletionListener; 29import android.media.MediaPlayer.OnErrorListener; 30import android.media.MediaPlayer.OnPreparedListener; 31import android.net.Uri; 32import android.os.Bundle; 33import android.os.Handler; 34import android.provider.MediaStore; 35import android.provider.OpenableColumns; 36import android.text.TextUtils; 37import android.util.Log; 38import android.view.KeyEvent; 39import android.view.Menu; 40import android.view.MenuItem; 41import android.view.View; 42import android.view.Window; 43import android.view.WindowManager; 44import android.widget.ImageButton; 45import android.widget.ProgressBar; 46import android.widget.SeekBar; 47import android.widget.TextView; 48import android.widget.SeekBar.OnSeekBarChangeListener; 49 50import java.io.IOException; 51 52public class AudioPreview extends Activity implements OnPreparedListener, OnErrorListener, OnCompletionListener 53{ 54 private final static String TAG = "AudioPreview"; 55 private MediaPlayer mPlayer; 56 private TextView mTextLine1; 57 private TextView mTextLine2; 58 private TextView mLoadingText; 59 private SeekBar mSeekBar; 60 private Handler mProgressRefresher; 61 private boolean mSeeking = false; 62 private int mDuration; 63 private Uri mUri; 64 private long mMediaId = -1; 65 private static final int OPEN_IN_MUSIC = 1; 66 private AudioManager mAudioManager; 67 private boolean mPausedByTransientLossOfFocus; 68 69 @Override 70 public void onCreate(Bundle icicle) { 71 super.onCreate(icicle); 72 73 Intent intent = getIntent(); 74 if (intent == null) { 75 finish(); 76 return; 77 } 78 mUri = intent.getData(); 79 if (mUri == null) { 80 finish(); 81 return; 82 } 83 String scheme = mUri.getScheme(); 84 85 setVolumeControlStream(AudioManager.STREAM_MUSIC); 86 requestWindowFeature(Window.FEATURE_NO_TITLE); 87 setContentView(R.layout.audiopreview); 88 getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, 89 WindowManager.LayoutParams.WRAP_CONTENT); 90 91 mTextLine1 = (TextView) findViewById(R.id.line1); 92 mTextLine2 = (TextView) findViewById(R.id.line2); 93 mLoadingText = (TextView) findViewById(R.id.loading); 94 if (scheme.equals("http")) { 95 String msg = getString(R.string.streamloadingtext, mUri.getHost()); 96 mLoadingText.setText(msg); 97 } else { 98 mLoadingText.setVisibility(View.GONE); 99 } 100 mSeekBar = (SeekBar) findViewById(R.id.progress); 101 mProgressRefresher = new Handler(); 102 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 103 104 MediaPlayer player = (MediaPlayer) getLastNonConfigurationInstance(); 105 boolean prepare = false; 106 if (player == null) { 107 player = new MediaPlayer(); 108 prepare = true; 109 } else { 110 mPlayer = player; 111 showPostPrepareUI(); 112 } 113 player.setOnPreparedListener(this); 114 player.setOnErrorListener(this); 115 player.setOnCompletionListener(this); 116 if (mPlayer == null) { 117 // mPlayer will be set by the onPrepared callback 118 try { 119 player.setDataSource(this, mUri); 120 player.prepareAsync(); 121 } catch (IOException ex) { 122 finish(); 123 return; 124 } 125 } 126 127 AsyncQueryHandler mAsyncQueryHandler = new AsyncQueryHandler(getContentResolver()) { 128 @Override 129 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 130 int titleIdx = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE); 131 int artistIdx = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST); 132 int idIdx = cursor.getColumnIndex(MediaStore.Audio.Media._ID); 133 int displaynameIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); 134 if (cursor.moveToFirst()) { 135 136 if (idIdx >=0) { 137 mMediaId = cursor.getLong(idIdx); 138 } 139 140 if (titleIdx >= 0) { 141 String title = cursor.getString(titleIdx); 142 mTextLine1.setText(title); 143 if (artistIdx >= 0) { 144 String artist = cursor.getString(artistIdx); 145 mTextLine2.setText(artist); 146 } 147 } else if (displaynameIdx >= 0) { 148 String name = cursor.getString(displaynameIdx); 149 mTextLine1.setText(name); 150 } else { 151 // Couldn't find anything to display, what to do now? 152 Log.w(TAG, "Cursor had no names for us"); 153 } 154 } else { 155 // What to do here? When would this even happen? 156 Log.w(TAG, "empty cursor"); 157 } 158 setNames(); 159 } 160 }; 161 162 if (scheme.equals(ContentResolver.SCHEME_CONTENT)) { 163 if (mUri.getAuthority() == MediaStore.AUTHORITY) { 164 // try to get title and artist from the media content provider 165 mAsyncQueryHandler.startQuery(0, null, mUri, new String [] { 166 MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.ARTIST}, 167 null, null, null); 168 } else { 169 // try to get the display name from another content provider 170 mAsyncQueryHandler.startQuery(0, null, mUri, new String [] { 171 OpenableColumns.DISPLAY_NAME}, 172 null, null, null); 173 } 174 } else if (scheme.equals("file")) { 175 // check if this file is in the media database (clicking on a download 176 // in the download manager might follow this path 177 String path = mUri.getPath(); 178 mAsyncQueryHandler.startQuery(0, null, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 179 new String [] {MediaStore.Audio.Media._ID, 180 MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.ARTIST}, 181 MediaStore.Audio.Media.DATA + "=?", new String [] {path}, null); 182 } else { 183 // We can't get metadata from the file/stream itself yet, because 184 // that API is hidden, so instead we display the URI being played 185 // (below, in onPrepared) 186 } 187 } 188 189 @Override 190 public Object onRetainNonConfigurationInstance() { 191 MediaPlayer player = mPlayer; 192 mPlayer = null; 193 return player; 194 } 195 196 197 @Override 198 public void onDestroy() { 199 mProgressRefresher.removeCallbacksAndMessages(null); 200 if (mPlayer != null) { 201 mPlayer.release(); 202 mAudioManager.abandonAudioFocus(mAudioFocusListener); 203 } 204 super.onDestroy(); 205 } 206 207 public void onPrepared(MediaPlayer mp) { 208 if (isFinishing()) return; 209 mPlayer = mp; 210 start(); 211 setNames(); 212 showPostPrepareUI(); 213 } 214 215 private void showPostPrepareUI() { 216 ProgressBar pb = (ProgressBar) findViewById(R.id.spinner); 217 pb.setVisibility(View.GONE); 218 mSeekBar.setVisibility(View.VISIBLE); 219 mDuration = mPlayer.getDuration(); 220 mSeekBar.setMax(mDuration); 221 mSeekBar.setOnSeekBarChangeListener(mSeekListener); 222 mLoadingText.setVisibility(View.GONE); 223 View v = findViewById(R.id.titleandbuttons); 224 v.setVisibility(View.VISIBLE); 225 start(); // because it requests audio focus 226 updatePlayPause(); 227 } 228 229 private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() { 230 public void onAudioFocusChange(int focusChange) { 231 if (mPlayer == null) { 232 // this activity has handed its MediaPlayer off to the next activity 233 // (e.g. portrait/landscape switch) and should abandon its focus 234 mAudioManager.abandonAudioFocus(this); 235 return; 236 } 237 switch (focusChange) { 238 case AudioManager.AUDIOFOCUS_LOSS: 239 mPausedByTransientLossOfFocus = false; 240 mPlayer.pause(); 241 break; 242 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 243 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 244 if (mPlayer.isPlaying()) { 245 mPausedByTransientLossOfFocus = true; 246 mPlayer.pause(); 247 } 248 break; 249 case AudioManager.AUDIOFOCUS_GAIN: 250 if (mPausedByTransientLossOfFocus) { 251 mPausedByTransientLossOfFocus = false; 252 start(); 253 } 254 break; 255 } 256 updatePlayPause(); 257 } 258 }; 259 260 private void start() { 261 mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC, 262 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); 263 mPlayer.start(); 264 mProgressRefresher.postDelayed(new ProgressRefresher(), 200); 265 } 266 267 public void setNames() { 268 if (TextUtils.isEmpty(mTextLine1.getText())) { 269 mTextLine1.setText(mUri.getLastPathSegment()); 270 } 271 if (TextUtils.isEmpty(mTextLine2.getText())) { 272 mTextLine2.setVisibility(View.GONE); 273 } else { 274 mTextLine2.setVisibility(View.VISIBLE); 275 } 276 } 277 278 class ProgressRefresher implements Runnable { 279 280 public void run() { 281 if (!mSeeking) { 282 int progress = mPlayer.getCurrentPosition() / mDuration; 283 mSeekBar.setProgress(mPlayer.getCurrentPosition()); 284 } 285 mProgressRefresher.removeCallbacksAndMessages(null); 286 mProgressRefresher.postDelayed(new ProgressRefresher(), 200); 287 } 288 } 289 290 private void updatePlayPause() { 291 ImageButton b = (ImageButton) findViewById(R.id.playpause); 292 if (b != null) { 293 if (mPlayer.isPlaying()) { 294 b.setImageResource(R.drawable.btn_playback_ic_pause_small); 295 } else { 296 b.setImageResource(R.drawable.btn_playback_ic_play_small); 297 mProgressRefresher.removeCallbacksAndMessages(null); 298 } 299 } 300 } 301 302 private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { 303 public void onStartTrackingTouch(SeekBar bar) { 304 mSeeking = true; 305 } 306 public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) { 307 if (!fromuser) { 308 return; 309 } 310 mPlayer.seekTo(progress); 311 } 312 public void onStopTrackingTouch(SeekBar bar) { 313 mSeeking = false; 314 } 315 }; 316 317 public boolean onError(MediaPlayer mp, int what, int extra) { 318 finish(); 319 return true; 320 } 321 322 public void onCompletion(MediaPlayer mp) { 323 mSeekBar.setProgress(mDuration); 324 updatePlayPause(); 325 } 326 327 public void playPauseClicked(View v) { 328 if (mPlayer.isPlaying()) { 329 mPlayer.pause(); 330 } else { 331 start(); 332 } 333 updatePlayPause(); 334 } 335 336 @Override 337 public boolean onCreateOptionsMenu(Menu menu) { 338 super.onCreateOptionsMenu(menu); 339 // TODO: if mMediaId != -1, then the playing file has an entry in the media 340 // database, and we could open it in the full music app instead. 341 // Ideally, we would hand off the currently running mediaplayer 342 // to the music UI, which can probably be done via a public static 343 menu.add(0, OPEN_IN_MUSIC, 0, "open in music"); 344 return true; 345 } 346 347 @Override 348 public boolean onPrepareOptionsMenu(Menu menu) { 349 MenuItem item = menu.findItem(OPEN_IN_MUSIC); 350 if (mMediaId >= 0) { 351 item.setVisible(true); 352 return true; 353 } 354 item.setVisible(false); 355 return false; 356 } 357 358 @Override 359 public boolean onKeyDown(int keyCode, KeyEvent event) { 360 switch (keyCode) { 361 case KeyEvent.KEYCODE_HEADSETHOOK: 362 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 363 if (mPlayer.isPlaying()) { 364 mPlayer.pause(); 365 } else { 366 start(); 367 } 368 updatePlayPause(); 369 return true; 370 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 371 case KeyEvent.KEYCODE_MEDIA_NEXT: 372 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 373 case KeyEvent.KEYCODE_MEDIA_REWIND: 374 return true; 375 case KeyEvent.KEYCODE_MEDIA_STOP: 376 finish(); 377 return true; 378 } 379 return super.onKeyDown(keyCode, event); 380 } 381} 382