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