Home | History | Annotate | Download | only in playback
      1 /*
      2  * Copyright (C) 2014 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 package com.android.onemedia.playback;
     17 
     18 import org.apache.http.Header;
     19 import org.apache.http.HttpResponse;
     20 import org.apache.http.client.methods.HttpGet;
     21 
     22 import android.content.Context;
     23 import android.media.AudioManager;
     24 import android.media.AudioManager.OnAudioFocusChangeListener;
     25 import android.media.MediaPlayer;
     26 import android.media.MediaPlayer.OnBufferingUpdateListener;
     27 import android.media.MediaPlayer.OnCompletionListener;
     28 import android.media.MediaPlayer.OnErrorListener;
     29 import android.media.MediaPlayer.OnPreparedListener;
     30 import android.net.Uri;
     31 import android.net.http.AndroidHttpClient;
     32 import android.os.AsyncTask;
     33 import android.os.Bundle;
     34 import android.os.Handler;
     35 import android.util.Log;
     36 import android.view.SurfaceHolder;
     37 
     38 import java.io.IOException;
     39 import java.util.Map;
     40 
     41 /**
     42  * Helper class for wrapping a MediaPlayer and doing a lot of the default work
     43  * to play audio. This class is not currently thread safe and all calls to it
     44  * should be made on the same thread.
     45  */
     46 public class LocalRenderer extends Renderer implements OnPreparedListener,
     47         OnBufferingUpdateListener, OnCompletionListener, OnErrorListener,
     48         OnAudioFocusChangeListener {
     49     private static final String TAG = "MediaPlayerManager";
     50     private static final boolean DEBUG = false;
     51     private static long sDebugInstanceId = 0;
     52 
     53     private static final String[] SUPPORTED_FEATURES = {
     54             FEATURE_SET_CONTENT,
     55             FEATURE_SET_NEXT_CONTENT,
     56             FEATURE_PLAY,
     57             FEATURE_PAUSE,
     58             FEATURE_NEXT,
     59             FEATURE_PREVIOUS,
     60             FEATURE_SEEK_TO,
     61             FEATURE_STOP
     62     };
     63 
     64     /**
     65      * These are the states where it is valid to call play directly on the
     66      * MediaPlayer.
     67      */
     68     private static final int CAN_PLAY = STATE_READY | STATE_PAUSED | STATE_ENDED;
     69     /**
     70      * These are the states where we expect the MediaPlayer to be ready in the
     71      * future, so we can set a flag to start playing when it is.
     72      */
     73     private static final int CAN_READY_PLAY = STATE_INIT | STATE_PREPARING;
     74     /**
     75      * The states when it is valid to call pause on the MediaPlayer.
     76      */
     77     private static final int CAN_PAUSE = STATE_PLAYING;
     78     /**
     79      * The states where it is valid to call seek on the MediaPlayer.
     80      */
     81     private static final int CAN_SEEK = STATE_READY | STATE_PLAYING | STATE_PAUSED | STATE_ENDED;
     82     /**
     83      * The states where we expect the MediaPlayer to be ready in the future and
     84      * can store a seek position to set later.
     85      */
     86     private static final int CAN_READY_SEEK = STATE_INIT | STATE_PREPARING;
     87     /**
     88      * The states where it is valid to call stop on the MediaPlayer.
     89      */
     90     private static final int CAN_STOP = STATE_READY | STATE_PLAYING | STATE_PAUSED | STATE_ENDED;
     91     /**
     92      * The states where it is valid to get the current play position and the
     93      * duration from the MediaPlayer.
     94      */
     95     private static final int CAN_GET_POSITION = STATE_READY | STATE_PLAYING | STATE_PAUSED;
     96 
     97 
     98 
     99     private class PlayerContent {
    100         public final String source;
    101         public final Map<String, String> headers;
    102 
    103         public PlayerContent(String source, Map<String, String> headers) {
    104             this.source = source;
    105             this.headers = headers;
    106         }
    107     }
    108 
    109     private class AsyncErrorRetriever extends AsyncTask<HttpGet, Void, Void> {
    110         private final long errorId;
    111         private boolean closeHttpClient;
    112 
    113         public AsyncErrorRetriever(long errorId) {
    114             this.errorId = errorId;
    115             closeHttpClient = false;
    116         }
    117 
    118         public boolean cancelRequestLocked(boolean closeHttp) {
    119             closeHttpClient = closeHttp;
    120             return this.cancel(false);
    121         }
    122 
    123         @Override
    124         protected Void doInBackground(HttpGet[] params) {
    125             synchronized (mErrorLock) {
    126                 if (isCancelled() || mHttpClient == null) {
    127                     if (mErrorRetriever == this) {
    128                         mErrorRetriever = null;
    129                     }
    130                     return null;
    131                 }
    132                 mSafeToCloseClient = false;
    133             }
    134             final PlaybackError error = new PlaybackError();
    135             try {
    136                 HttpResponse response = mHttpClient.execute(params[0]);
    137                 synchronized (mErrorLock) {
    138                     if (mErrorId != errorId || mError == null) {
    139                         // A new error has occurred, abort
    140                         return null;
    141                     }
    142                     error.type = mError.type;
    143                     error.extra = mError.extra;
    144                     error.errorMessage = mError.errorMessage;
    145                 }
    146                 final int code = response.getStatusLine().getStatusCode();
    147                 if (code >= 300) {
    148                     error.extra = code;
    149                 }
    150                 final Bundle errorExtras = new Bundle();
    151                 Header[] headers = response.getAllHeaders();
    152                 if (headers != null && headers.length > 0) {
    153                     for (Header header : headers) {
    154                         errorExtras.putString(header.getName(), header.getValue());
    155                     }
    156                     error.errorExtras = errorExtras;
    157                 }
    158             } catch (IOException e) {
    159                 Log.e(TAG, "IOException requesting from server, unable to get more exact error");
    160             } finally {
    161                 synchronized (mErrorLock) {
    162                     mSafeToCloseClient = true;
    163                     if (mErrorRetriever == this) {
    164                         mErrorRetriever = null;
    165                     }
    166                     if (isCancelled()) {
    167                         if (closeHttpClient) {
    168                             mHttpClient.close();
    169                             mHttpClient = null;
    170                         }
    171                         return null;
    172                     }
    173                 }
    174             }
    175             mHandler.post(new Runnable() {
    176                     @Override
    177                 public void run() {
    178                     synchronized (mErrorLock) {
    179                         if (mErrorId == errorId) {
    180                             setError(error.type, error.extra, error.errorExtras, null);
    181                         }
    182                     }
    183                 }
    184             });
    185             return null;
    186         }
    187     }
    188 
    189     private int mState = STATE_INIT;
    190 
    191     private AudioManager mAudioManager;
    192     private MediaPlayer mPlayer;
    193     private PlayerContent mContent;
    194     private MediaPlayer mNextPlayer;
    195     private PlayerContent mNextContent;
    196     private SurfaceHolder mHolder;
    197     private SurfaceHolder.Callback mHolderCB;
    198     private Context mContext;
    199 
    200     private Handler mHandler = new Handler();
    201 
    202     private AndroidHttpClient mHttpClient = AndroidHttpClient.newInstance("TUQ");
    203     // The ongoing error request thread if there is one. This should only be
    204     // modified while mErrorLock is held.
    205     private AsyncErrorRetriever mErrorRetriever;
    206     // This is set to false while a server request is being made to retrieve
    207     // the current error. It should only be set while mErrorLock is held.
    208     private boolean mSafeToCloseClient = true;
    209     private final Object mErrorLock = new Object();
    210     // A tracking id for the current error. This should only be modified while
    211     // mErrorLock is held.
    212     private long mErrorId = 0;
    213     // The current error state of this player. This is cleared when the state
    214     // leaves an error state and set when it enters one. This should only be
    215     // modified when mErrorLock is held.
    216     private PlaybackError mError;
    217 
    218     private boolean mPlayOnReady;
    219     private int mSeekOnReady;
    220     private boolean mHasAudioFocus;
    221     private long mDebugId = sDebugInstanceId++;
    222 
    223     public LocalRenderer(Context context, Bundle params) {
    224         super(context, params);
    225         mContext = context;
    226         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    227     }
    228 
    229     @Override
    230     protected void initFeatures(Bundle params) {
    231         for (String feature : SUPPORTED_FEATURES) {
    232             mFeatures.add(feature);
    233         }
    234     }
    235 
    236     /**
    237      * Call this when completely finished with the MediaPlayerManager to have it
    238      * clean up. The instance may not be used again after this is called.
    239      */
    240     @Override
    241     public void onDestroy() {
    242         synchronized (mErrorLock) {
    243             if (DEBUG) {
    244                 Log.d(TAG, "onDestroy, error retriever? " + mErrorRetriever + " safe to close? "
    245                         + mSafeToCloseClient + " client? " + mHttpClient);
    246             }
    247             if (mErrorRetriever != null) {
    248                 mErrorRetriever.cancelRequestLocked(true);
    249                 mErrorRetriever = null;
    250             }
    251             // Increment the error id to ensure no errors are sent after this
    252             // point.
    253             mErrorId++;
    254             if (mSafeToCloseClient) {
    255                 mHttpClient.close();
    256                 mHttpClient = null;
    257             }
    258         }
    259     }
    260 
    261     @Override
    262     public void onPrepared(MediaPlayer player) {
    263         if (!isCurrentPlayer(player)) {
    264             return;
    265         }
    266         setState(STATE_READY);
    267         if (DEBUG) {
    268             Log.d(TAG, mDebugId + ": Finished preparing, seekOnReady is " + mSeekOnReady);
    269         }
    270         if (mSeekOnReady >= 0) {
    271             onSeekTo(mSeekOnReady);
    272             mSeekOnReady = -1;
    273         }
    274         if (mPlayOnReady) {
    275             player.start();
    276             setState(STATE_PLAYING);
    277         }
    278     }
    279 
    280     @Override
    281     public void onBufferingUpdate(MediaPlayer player, int percent) {
    282         if (!isCurrentPlayer(player)) {
    283             return;
    284         }
    285         pushOnBufferingUpdate(percent);
    286     }
    287 
    288     @Override
    289     public void onCompletion(MediaPlayer player) {
    290         if (!isCurrentPlayer(player)) {
    291             return;
    292         }
    293         if (DEBUG) {
    294             Log.d(TAG, mDebugId + ": Completed item. Have next item? " + (mNextPlayer != null));
    295         }
    296         if (mNextPlayer != null) {
    297             if (mPlayer != null) {
    298                 mPlayer.release();
    299             }
    300             mPlayer = mNextPlayer;
    301             mContent = mNextContent;
    302             mNextPlayer = null;
    303             mNextContent = null;
    304             pushOnNextStarted();
    305             return;
    306         }
    307         setState(STATE_ENDED);
    308     }
    309 
    310     @Override
    311     public boolean onError(MediaPlayer player, int what, int extra) {
    312         if (!isCurrentPlayer(player)) {
    313             return false;
    314         }
    315         if (DEBUG) {
    316             Log.d(TAG, mDebugId + ": Entered error state, what: " + what + " extra: " + extra);
    317         }
    318         synchronized (mErrorLock) {
    319             ++mErrorId;
    320             mError = new PlaybackError();
    321             mError.type = what;
    322             mError.extra = extra;
    323         }
    324 
    325         if (what == MediaPlayer.MEDIA_ERROR_UNKNOWN && extra == MediaPlayer.MEDIA_ERROR_IO
    326                 && mContent != null && mContent.source.startsWith("http")) {
    327             HttpGet request = new HttpGet(mContent.source);
    328             if (mContent.headers != null) {
    329                 for (String key : mContent.headers.keySet()) {
    330                     request.addHeader(key, mContent.headers.get(key));
    331                 }
    332             }
    333             synchronized (mErrorLock) {
    334                 if (mErrorRetriever != null) {
    335                     mErrorRetriever.cancelRequestLocked(false);
    336                 }
    337                 mErrorRetriever = new AsyncErrorRetriever(mErrorId);
    338                 mErrorRetriever.execute(request);
    339             }
    340         } else {
    341             setError(what, extra, null, null);
    342         }
    343         return true;
    344     }
    345 
    346     @Override
    347     public void onAudioFocusChange(int focusChange) {
    348         // TODO figure out appropriate logic for handling focus loss at the TUQ
    349         // level.
    350         switch (focusChange) {
    351             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
    352             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
    353                 if (mState == STATE_PLAYING) {
    354                     onPause();
    355                     mPlayOnReady = true;
    356                 }
    357                 mHasAudioFocus = false;
    358                 break;
    359             case AudioManager.AUDIOFOCUS_LOSS:
    360                 if (mState == STATE_PLAYING) {
    361                     onPause();
    362                     mPlayOnReady = false;
    363                 }
    364                 pushOnFocusLost();
    365                 mHasAudioFocus = false;
    366                 break;
    367             case AudioManager.AUDIOFOCUS_GAIN:
    368                 mHasAudioFocus = true;
    369                 if (mPlayOnReady) {
    370                     onPlay();
    371                 }
    372                 break;
    373             default:
    374                 Log.d(TAG, "Unknown focus change event " + focusChange);
    375                 break;
    376         }
    377     }
    378 
    379     @Override
    380     public void setContent(Bundle request) {
    381         setContent(request, null);
    382     }
    383 
    384     /**
    385      * Prepares the player for the given playback request. If the holder is null
    386      * it is assumed this is an audio only source. If playOnReady is set to true
    387      * the media will begin playing as soon as it can.
    388      *
    389      * @see RequestUtils for the set of valid keys.
    390      */
    391     public void setContent(Bundle request, SurfaceHolder holder) {
    392         String source = request.getString(RequestUtils.EXTRA_KEY_SOURCE);
    393         Map<String, String> headers = null; // request.mHeaders;
    394         boolean playOnReady = true; // request.mPlayOnReady;
    395         if (DEBUG) {
    396             Log.d(TAG, mDebugId + ": Settings new content. Have a player? " + (mPlayer != null)
    397                     + " have a next player? " + (mNextPlayer != null));
    398         }
    399         cleanUpPlayer();
    400         setState(STATE_PREPARING);
    401         mPlayOnReady = playOnReady;
    402         mSeekOnReady = -1;
    403         final MediaPlayer newPlayer = new MediaPlayer();
    404 
    405         requestAudioFocus();
    406 
    407         mPlayer = newPlayer;
    408         mContent = new PlayerContent(source, headers);
    409         try {
    410             if (headers != null) {
    411                 Uri sourceUri = Uri.parse(source);
    412                 newPlayer.setDataSource(mContext, sourceUri, headers);
    413             } else {
    414                 newPlayer.setDataSource(source);
    415             }
    416         } catch (Exception e) {
    417             setError(Listener.ERROR_LOAD_FAILED, 0, null, e);
    418             return;
    419         }
    420         if (isHolderReady(holder, newPlayer)) {
    421             preparePlayer(newPlayer, true);
    422         }
    423     }
    424 
    425     @Override
    426     public void setNextContent(Bundle request) {
    427         String source = request.getString(RequestUtils.EXTRA_KEY_SOURCE);
    428         Map<String, String> headers = null; // request.mHeaders;
    429 
    430         // TODO support video
    431 
    432         if (DEBUG) {
    433             Log.d(TAG, mDebugId + ": Setting next content. Have player? " + (mPlayer != null)
    434                     + " have next player? " + (mNextPlayer != null));
    435         }
    436 
    437         if (mPlayer == null) {
    438             // The manager isn't being used to play anything, don't try to
    439             // set a next.
    440             return;
    441         }
    442         if (mNextPlayer != null) {
    443             // Before setting up the new one clear out the old one and release
    444             // it to ensure it doesn't play.
    445             mPlayer.setNextMediaPlayer(null);
    446             mNextPlayer.release();
    447             mNextPlayer = null;
    448             mNextContent = null;
    449         }
    450         if (source == null) {
    451             // If there's no new content we're done
    452             return;
    453         }
    454         final MediaPlayer newPlayer = new MediaPlayer();
    455 
    456         try {
    457             if (headers != null) {
    458                 Uri sourceUri = Uri.parse(source);
    459                 newPlayer.setDataSource(mContext, sourceUri, headers);
    460             } else {
    461                 newPlayer.setDataSource(source);
    462             }
    463         } catch (Exception e) {
    464             newPlayer.release();
    465             // Don't return an error until we get to this item in playback
    466             return;
    467         }
    468 
    469         if (preparePlayer(newPlayer, false)) {
    470             mPlayer.setNextMediaPlayer(newPlayer);
    471             mNextPlayer = newPlayer;
    472             mNextContent = new PlayerContent(source, headers);
    473         }
    474     }
    475 
    476     private void requestAudioFocus() {
    477         int result = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
    478                 AudioManager.AUDIOFOCUS_GAIN);
    479         mHasAudioFocus = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    480     }
    481 
    482     /**
    483      * Start the player if possible or queue it to play when ready. If the
    484      * player is in a state where it will never be ready returns false.
    485      *
    486      * @return true if the content was started or will be started later
    487      */
    488     @Override
    489     public boolean onPlay() {
    490         MediaPlayer player = mPlayer;
    491         if (player != null && mState == STATE_PLAYING) {
    492             // already playing, just return
    493             return true;
    494         }
    495         if (!mHasAudioFocus) {
    496             requestAudioFocus();
    497         }
    498         if (player != null && canPlay()) {
    499             player.start();
    500             setState(STATE_PLAYING);
    501         } else if (canReadyPlay()) {
    502             mPlayOnReady = true;
    503         } else if (!isPlaying()) {
    504             return false;
    505         }
    506         return true;
    507     }
    508 
    509     /**
    510      * Pause the player if possible or set it to not play when ready. If the
    511      * player is in a state where it will never be ready returns false.
    512      *
    513      * @return true if the content was paused or will wait to play when ready
    514      *         later
    515      */
    516     @Override
    517     public boolean onPause() {
    518         MediaPlayer player = mPlayer;
    519         // If the user paused us make sure we won't start playing again until
    520         // asked to
    521         mPlayOnReady = false;
    522         if (player != null && (mState & CAN_PAUSE) != 0) {
    523             player.pause();
    524             setState(STATE_PAUSED);
    525         } else if (!isPaused()) {
    526             return false;
    527         }
    528         return true;
    529     }
    530 
    531     /**
    532      * Seek to a given position in the media. If the seek succeeded or will be
    533      * performed when loading is complete returns true. If the position is not
    534      * in range or the player will never be ready returns false.
    535      *
    536      * @param position The position to seek to in milliseconds
    537      * @return true if playback was moved or will be moved when ready
    538      */
    539     @Override
    540     public boolean onSeekTo(int position) {
    541         MediaPlayer player = mPlayer;
    542         if (player != null && (mState & CAN_SEEK) != 0) {
    543             if (position < 0 || position >= getDuration()) {
    544                 return false;
    545             } else {
    546                 if (mState == STATE_ENDED) {
    547                     player.start();
    548                     player.pause();
    549                     setState(STATE_PAUSED);
    550                 }
    551                 player.seekTo(position);
    552             }
    553         } else if ((mState & CAN_READY_SEEK) != 0) {
    554             mSeekOnReady = position;
    555         } else {
    556             return false;
    557         }
    558         return true;
    559     }
    560 
    561     /**
    562      * Stop the player. It cannot be used again until
    563      * {@link #setContent(String, boolean)} is called.
    564      *
    565      * @return true if stopping the player succeeded
    566      */
    567     @Override
    568     public boolean onStop() {
    569         cleanUpPlayer();
    570         setState(STATE_STOPPED);
    571         return true;
    572     }
    573 
    574     public boolean isPlaying() {
    575         return mState == STATE_PLAYING;
    576     }
    577 
    578     public boolean isPaused() {
    579         return mState == STATE_PAUSED;
    580     }
    581 
    582     @Override
    583     public long getSeekPosition() {
    584         return ((mState & CAN_GET_POSITION) == 0) ? -1 : mPlayer.getCurrentPosition();
    585     }
    586 
    587     @Override
    588     public long getDuration() {
    589         return ((mState & CAN_GET_POSITION) == 0) ? -1 : mPlayer.getDuration();
    590     }
    591 
    592     private boolean canPlay() {
    593         return ((mState & CAN_PLAY) != 0) && mHasAudioFocus;
    594     }
    595 
    596     private boolean canReadyPlay() {
    597         return (mState & CAN_PLAY) != 0 || (mState & CAN_READY_PLAY) != 0;
    598     }
    599 
    600     /**
    601      * Sends a state update if the listener exists
    602      */
    603     private void setState(int state) {
    604         if (state == mState) {
    605             return;
    606         }
    607         Log.d(TAG, "Entering state " + state + " from state " + mState);
    608         mState = state;
    609         if (state != STATE_ERROR) {
    610             // Don't notify error here, it'll get sent via onError
    611             pushOnStateChanged(state);
    612         }
    613     }
    614 
    615     private boolean preparePlayer(final MediaPlayer player, boolean current) {
    616         player.setOnPreparedListener(this);
    617         player.setOnBufferingUpdateListener(this);
    618         player.setOnCompletionListener(this);
    619         player.setOnErrorListener(this);
    620         try {
    621             player.prepareAsync();
    622             if (current) {
    623                 setState(STATE_PREPARING);
    624             }
    625         } catch (IllegalStateException e) {
    626             if (current) {
    627                 setError(Listener.ERROR_PREPARE_ERROR, 0, null, e);
    628             }
    629             return false;
    630         }
    631         return true;
    632     }
    633 
    634     /**
    635      * @param extra
    636      * @param e
    637      */
    638     private void setError(int type, int extra, Bundle extras, Exception e) {
    639         setState(STATE_ERROR);
    640         pushOnError(type, extra, extras, e);
    641         cleanUpPlayer();
    642         return;
    643     }
    644 
    645     /**
    646      * Checks if the holder is ready and either sets up a callback to wait for
    647      * it or sets it directly. If
    648      *
    649      * @param holder
    650      * @param player
    651      * @return
    652      */
    653     private boolean isHolderReady(final SurfaceHolder holder, final MediaPlayer player) {
    654         mHolder = holder;
    655         if (holder != null) {
    656             if (holder.getSurface() != null && holder.getSurface().isValid()) {
    657                 player.setDisplay(holder);
    658                 return true;
    659             } else {
    660                 Log.w(TAG, "Holder not null, waiting for it to be ready");
    661                 // If the holder isn't ready yet add a callback to set the
    662                 // holder when it's ready.
    663                 SurfaceHolder.Callback cb = new SurfaceHolder.Callback() {
    664                         @Override
    665                     public void surfaceDestroyed(SurfaceHolder arg0) {
    666                     }
    667 
    668                         @Override
    669                     public void surfaceCreated(SurfaceHolder arg0) {
    670                         if (player.equals(mPlayer)) {
    671                             player.setDisplay(arg0);
    672                             preparePlayer(player, true);
    673                         }
    674                     }
    675 
    676                         @Override
    677                     public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
    678                     }
    679                 };
    680                 mHolderCB = cb;
    681                 holder.addCallback(cb);
    682                 return false;
    683             }
    684         }
    685         return true;
    686     }
    687 
    688     private void cleanUpPlayer() {
    689         if (DEBUG) {
    690             Log.d(TAG, mDebugId + ": Cleaning up current player");
    691         }
    692         synchronized (mErrorLock) {
    693             mError = null;
    694             if (mErrorRetriever != null) {
    695                 mErrorRetriever.cancelRequestLocked(false);
    696                 // Don't set to null as we may need to cancel again with true if
    697                 // the object gets destroyed.
    698             }
    699         }
    700         mAudioManager.abandonAudioFocus(this);
    701 
    702         SurfaceHolder.Callback cb = mHolderCB;
    703         mHolderCB = null;
    704         SurfaceHolder holder = mHolder;
    705         mHolder = null;
    706         if (holder != null && cb != null) {
    707             holder.removeCallback(cb);
    708         }
    709 
    710         MediaPlayer player = mPlayer;
    711         mPlayer = null;
    712         if (player != null) {
    713             player.reset();
    714             player.release();
    715         }
    716     }
    717 
    718     private boolean isCurrentPlayer(MediaPlayer player) {
    719         return player.equals(mPlayer);
    720     }
    721 }
    722