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