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