Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2006 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 
     17 package android.widget;
     18 
     19 import android.app.AlertDialog;
     20 import android.content.Context;
     21 import android.content.DialogInterface;
     22 import android.content.Intent;
     23 import android.content.res.Resources;
     24 import android.media.AudioManager;
     25 import android.media.MediaPlayer;
     26 import android.media.Metadata;
     27 import android.media.MediaPlayer.OnCompletionListener;
     28 import android.media.MediaPlayer.OnErrorListener;
     29 import android.net.Uri;
     30 import android.util.AttributeSet;
     31 import android.util.Log;
     32 import android.view.KeyEvent;
     33 import android.view.MotionEvent;
     34 import android.view.SurfaceHolder;
     35 import android.view.SurfaceView;
     36 import android.view.View;
     37 import android.widget.MediaController.MediaPlayerControl;
     38 
     39 import java.io.IOException;
     40 import java.util.Map;
     41 
     42 /**
     43  * Displays a video file.  The VideoView class
     44  * can load images from various sources (such as resources or content
     45  * providers), takes care of computing its measurement from the video so that
     46  * it can be used in any layout manager, and provides various display options
     47  * such as scaling and tinting.
     48  */
     49 public class VideoView extends SurfaceView implements MediaPlayerControl {
     50     private String TAG = "VideoView";
     51     // settable by the client
     52     private Uri         mUri;
     53     private Map<String, String> mHeaders;
     54     private int         mDuration;
     55 
     56     // all possible internal states
     57     private static final int STATE_ERROR              = -1;
     58     private static final int STATE_IDLE               = 0;
     59     private static final int STATE_PREPARING          = 1;
     60     private static final int STATE_PREPARED           = 2;
     61     private static final int STATE_PLAYING            = 3;
     62     private static final int STATE_PAUSED             = 4;
     63     private static final int STATE_PLAYBACK_COMPLETED = 5;
     64     private static final int STATE_SUSPEND            = 6;
     65     private static final int STATE_RESUME             = 7;
     66     private static final int STATE_SUSPEND_UNSUPPORTED = 8;
     67 
     68     // mCurrentState is a VideoView object's current state.
     69     // mTargetState is the state that a method caller intends to reach.
     70     // For instance, regardless the VideoView object's current state,
     71     // calling pause() intends to bring the object to a target state
     72     // of STATE_PAUSED.
     73     private int mCurrentState = STATE_IDLE;
     74     private int mTargetState  = STATE_IDLE;
     75 
     76     // All the stuff we need for playing and showing a video
     77     private SurfaceHolder mSurfaceHolder = null;
     78     private MediaPlayer mMediaPlayer = null;
     79     private int         mVideoWidth;
     80     private int         mVideoHeight;
     81     private int         mSurfaceWidth;
     82     private int         mSurfaceHeight;
     83     private MediaController mMediaController;
     84     private OnCompletionListener mOnCompletionListener;
     85     private MediaPlayer.OnPreparedListener mOnPreparedListener;
     86     private int         mCurrentBufferPercentage;
     87     private OnErrorListener mOnErrorListener;
     88     private int         mSeekWhenPrepared;  // recording the seek position while preparing
     89     private boolean     mCanPause;
     90     private boolean     mCanSeekBack;
     91     private boolean     mCanSeekForward;
     92     private int         mStateWhenSuspended;  //state before calling suspend()
     93 
     94     public VideoView(Context context) {
     95         super(context);
     96         initVideoView();
     97     }
     98 
     99     public VideoView(Context context, AttributeSet attrs) {
    100         this(context, attrs, 0);
    101         initVideoView();
    102     }
    103 
    104     public VideoView(Context context, AttributeSet attrs, int defStyle) {
    105         super(context, attrs, defStyle);
    106         initVideoView();
    107     }
    108 
    109     @Override
    110     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    111         //Log.i("@@@@", "onMeasure");
    112         int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
    113         int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
    114         if (mVideoWidth > 0 && mVideoHeight > 0) {
    115             if ( mVideoWidth * height  > width * mVideoHeight ) {
    116                 //Log.i("@@@", "image too tall, correcting");
    117                 height = width * mVideoHeight / mVideoWidth;
    118             } else if ( mVideoWidth * height  < width * mVideoHeight ) {
    119                 //Log.i("@@@", "image too wide, correcting");
    120                 width = height * mVideoWidth / mVideoHeight;
    121             } else {
    122                 //Log.i("@@@", "aspect ratio is correct: " +
    123                         //width+"/"+height+"="+
    124                         //mVideoWidth+"/"+mVideoHeight);
    125             }
    126         }
    127         //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
    128         setMeasuredDimension(width, height);
    129     }
    130 
    131     public int resolveAdjustedSize(int desiredSize, int measureSpec) {
    132         int result = desiredSize;
    133         int specMode = MeasureSpec.getMode(measureSpec);
    134         int specSize =  MeasureSpec.getSize(measureSpec);
    135 
    136         switch (specMode) {
    137             case MeasureSpec.UNSPECIFIED:
    138                 /* Parent says we can be as big as we want. Just don't be larger
    139                  * than max size imposed on ourselves.
    140                  */
    141                 result = desiredSize;
    142                 break;
    143 
    144             case MeasureSpec.AT_MOST:
    145                 /* Parent says we can be as big as we want, up to specSize.
    146                  * Don't be larger than specSize, and don't be larger than
    147                  * the max size imposed on ourselves.
    148                  */
    149                 result = Math.min(desiredSize, specSize);
    150                 break;
    151 
    152             case MeasureSpec.EXACTLY:
    153                 // No choice. Do what we are told.
    154                 result = specSize;
    155                 break;
    156         }
    157         return result;
    158 }
    159 
    160     private void initVideoView() {
    161         mVideoWidth = 0;
    162         mVideoHeight = 0;
    163         getHolder().addCallback(mSHCallback);
    164         getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    165         setFocusable(true);
    166         setFocusableInTouchMode(true);
    167         requestFocus();
    168         mCurrentState = STATE_IDLE;
    169         mTargetState  = STATE_IDLE;
    170     }
    171 
    172     public void setVideoPath(String path) {
    173         setVideoURI(Uri.parse(path));
    174     }
    175 
    176     public void setVideoURI(Uri uri) {
    177         setVideoURI(uri, null);
    178     }
    179 
    180     /**
    181      * @hide
    182      */
    183     public void setVideoURI(Uri uri, Map<String, String> headers) {
    184         mUri = uri;
    185         mHeaders = headers;
    186         mSeekWhenPrepared = 0;
    187         openVideo();
    188         requestLayout();
    189         invalidate();
    190     }
    191 
    192     public void stopPlayback() {
    193         if (mMediaPlayer != null) {
    194             mMediaPlayer.stop();
    195             mMediaPlayer.release();
    196             mMediaPlayer = null;
    197             mCurrentState = STATE_IDLE;
    198             mTargetState  = STATE_IDLE;
    199         }
    200     }
    201 
    202     private void openVideo() {
    203         if (mUri == null || mSurfaceHolder == null) {
    204             // not ready for playback just yet, will try again later
    205             return;
    206         }
    207         // Tell the music playback service to pause
    208         // TODO: these constants need to be published somewhere in the framework.
    209         Intent i = new Intent("com.android.music.musicservicecommand");
    210         i.putExtra("command", "pause");
    211         mContext.sendBroadcast(i);
    212 
    213         // we shouldn't clear the target state, because somebody might have
    214         // called start() previously
    215         release(false);
    216         try {
    217             mMediaPlayer = new MediaPlayer();
    218             mMediaPlayer.setOnPreparedListener(mPreparedListener);
    219             mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
    220             mDuration = -1;
    221             mMediaPlayer.setOnCompletionListener(mCompletionListener);
    222             mMediaPlayer.setOnErrorListener(mErrorListener);
    223             mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
    224             mCurrentBufferPercentage = 0;
    225             mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
    226             mMediaPlayer.setDisplay(mSurfaceHolder);
    227             mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    228             mMediaPlayer.setScreenOnWhilePlaying(true);
    229             mMediaPlayer.prepareAsync();
    230             // we don't set the target state here either, but preserve the
    231             // target state that was there before.
    232             mCurrentState = STATE_PREPARING;
    233             attachMediaController();
    234         } catch (IOException ex) {
    235             Log.w(TAG, "Unable to open content: " + mUri, ex);
    236             mCurrentState = STATE_ERROR;
    237             mTargetState = STATE_ERROR;
    238             mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
    239             return;
    240         } catch (IllegalArgumentException ex) {
    241             Log.w(TAG, "Unable to open content: " + mUri, ex);
    242             mCurrentState = STATE_ERROR;
    243             mTargetState = STATE_ERROR;
    244             mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
    245             return;
    246         }
    247     }
    248 
    249     public void setMediaController(MediaController controller) {
    250         if (mMediaController != null) {
    251             mMediaController.hide();
    252         }
    253         mMediaController = controller;
    254         attachMediaController();
    255     }
    256 
    257     private void attachMediaController() {
    258         if (mMediaPlayer != null && mMediaController != null) {
    259             mMediaController.setMediaPlayer(this);
    260             View anchorView = this.getParent() instanceof View ?
    261                     (View)this.getParent() : this;
    262             mMediaController.setAnchorView(anchorView);
    263             mMediaController.setEnabled(isInPlaybackState());
    264         }
    265     }
    266 
    267     MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
    268         new MediaPlayer.OnVideoSizeChangedListener() {
    269             public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
    270                 mVideoWidth = mp.getVideoWidth();
    271                 mVideoHeight = mp.getVideoHeight();
    272                 if (mVideoWidth != 0 && mVideoHeight != 0) {
    273                     getHolder().setFixedSize(mVideoWidth, mVideoHeight);
    274                 }
    275             }
    276     };
    277 
    278     MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
    279         public void onPrepared(MediaPlayer mp) {
    280             mCurrentState = STATE_PREPARED;
    281 
    282             // Get the capabilities of the player for this stream
    283             Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
    284                                       MediaPlayer.BYPASS_METADATA_FILTER);
    285 
    286             if (data != null) {
    287                 mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
    288                         || data.getBoolean(Metadata.PAUSE_AVAILABLE);
    289                 mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
    290                         || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
    291                 mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
    292                         || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
    293             } else {
    294                 mCanPause = mCanSeekBack = mCanSeekForward = true;
    295             }
    296 
    297             if (mOnPreparedListener != null) {
    298                 mOnPreparedListener.onPrepared(mMediaPlayer);
    299             }
    300             if (mMediaController != null) {
    301                 mMediaController.setEnabled(true);
    302             }
    303             mVideoWidth = mp.getVideoWidth();
    304             mVideoHeight = mp.getVideoHeight();
    305 
    306             int seekToPosition = mSeekWhenPrepared;  // mSeekWhenPrepared may be changed after seekTo() call
    307             if (seekToPosition != 0) {
    308                 seekTo(seekToPosition);
    309             }
    310             if (mVideoWidth != 0 && mVideoHeight != 0) {
    311                 //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
    312                 getHolder().setFixedSize(mVideoWidth, mVideoHeight);
    313                 if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
    314                     // We didn't actually change the size (it was already at the size
    315                     // we need), so we won't get a "surface changed" callback, so
    316                     // start the video here instead of in the callback.
    317                     if (mTargetState == STATE_PLAYING) {
    318                         start();
    319                         if (mMediaController != null) {
    320                             mMediaController.show();
    321                         }
    322                     } else if (!isPlaying() &&
    323                                (seekToPosition != 0 || getCurrentPosition() > 0)) {
    324                        if (mMediaController != null) {
    325                            // Show the media controls when we're paused into a video and make 'em stick.
    326                            mMediaController.show(0);
    327                        }
    328                    }
    329                 }
    330             } else {
    331                 // We don't know the video size yet, but should start anyway.
    332                 // The video size might be reported to us later.
    333                 if (mTargetState == STATE_PLAYING) {
    334                     start();
    335                 }
    336             }
    337         }
    338     };
    339 
    340     private MediaPlayer.OnCompletionListener mCompletionListener =
    341         new MediaPlayer.OnCompletionListener() {
    342         public void onCompletion(MediaPlayer mp) {
    343             mCurrentState = STATE_PLAYBACK_COMPLETED;
    344             mTargetState = STATE_PLAYBACK_COMPLETED;
    345             if (mMediaController != null) {
    346                 mMediaController.hide();
    347             }
    348             if (mOnCompletionListener != null) {
    349                 mOnCompletionListener.onCompletion(mMediaPlayer);
    350             }
    351         }
    352     };
    353 
    354     private MediaPlayer.OnErrorListener mErrorListener =
    355         new MediaPlayer.OnErrorListener() {
    356         public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {
    357             Log.d(TAG, "Error: " + framework_err + "," + impl_err);
    358             mCurrentState = STATE_ERROR;
    359             mTargetState = STATE_ERROR;
    360             if (mMediaController != null) {
    361                 mMediaController.hide();
    362             }
    363 
    364             /* If an error handler has been supplied, use it and finish. */
    365             if (mOnErrorListener != null) {
    366                 if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {
    367                     return true;
    368                 }
    369             }
    370 
    371             /* Otherwise, pop up an error dialog so the user knows that
    372              * something bad has happened. Only try and pop up the dialog
    373              * if we're attached to a window. When we're going away and no
    374              * longer have a window, don't bother showing the user an error.
    375              */
    376             if (getWindowToken() != null) {
    377                 Resources r = mContext.getResources();
    378                 int messageId;
    379 
    380                 if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
    381                     messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback;
    382                 } else {
    383                     messageId = com.android.internal.R.string.VideoView_error_text_unknown;
    384                 }
    385 
    386                 new AlertDialog.Builder(mContext)
    387                         .setTitle(com.android.internal.R.string.VideoView_error_title)
    388                         .setMessage(messageId)
    389                         .setPositiveButton(com.android.internal.R.string.VideoView_error_button,
    390                                 new DialogInterface.OnClickListener() {
    391                                     public void onClick(DialogInterface dialog, int whichButton) {
    392                                         /* If we get here, there is no onError listener, so
    393                                          * at least inform them that the video is over.
    394                                          */
    395                                         if (mOnCompletionListener != null) {
    396                                             mOnCompletionListener.onCompletion(mMediaPlayer);
    397                                         }
    398                                     }
    399                                 })
    400                         .setCancelable(false)
    401                         .show();
    402             }
    403             return true;
    404         }
    405     };
    406 
    407     private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
    408         new MediaPlayer.OnBufferingUpdateListener() {
    409         public void onBufferingUpdate(MediaPlayer mp, int percent) {
    410             mCurrentBufferPercentage = percent;
    411         }
    412     };
    413 
    414     /**
    415      * Register a callback to be invoked when the media file
    416      * is loaded and ready to go.
    417      *
    418      * @param l The callback that will be run
    419      */
    420     public void setOnPreparedListener(MediaPlayer.OnPreparedListener l)
    421     {
    422         mOnPreparedListener = l;
    423     }
    424 
    425     /**
    426      * Register a callback to be invoked when the end of a media file
    427      * has been reached during playback.
    428      *
    429      * @param l The callback that will be run
    430      */
    431     public void setOnCompletionListener(OnCompletionListener l)
    432     {
    433         mOnCompletionListener = l;
    434     }
    435 
    436     /**
    437      * Register a callback to be invoked when an error occurs
    438      * during playback or setup.  If no listener is specified,
    439      * or if the listener returned false, VideoView will inform
    440      * the user of any errors.
    441      *
    442      * @param l The callback that will be run
    443      */
    444     public void setOnErrorListener(OnErrorListener l)
    445     {
    446         mOnErrorListener = l;
    447     }
    448 
    449     SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
    450     {
    451         public void surfaceChanged(SurfaceHolder holder, int format,
    452                                     int w, int h)
    453         {
    454             mSurfaceWidth = w;
    455             mSurfaceHeight = h;
    456             boolean isValidState =  (mTargetState == STATE_PLAYING);
    457             boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);
    458             if (mMediaPlayer != null && isValidState && hasValidSize) {
    459                 if (mSeekWhenPrepared != 0) {
    460                     seekTo(mSeekWhenPrepared);
    461                 }
    462                 start();
    463                 if (mMediaController != null) {
    464                     if (mMediaController.isShowing()) {
    465                         // ensure the controller will get repositioned later
    466                         mMediaController.hide();
    467                     }
    468                     mMediaController.show();
    469                 }
    470             }
    471         }
    472 
    473         public void surfaceCreated(SurfaceHolder holder)
    474         {
    475             mSurfaceHolder = holder;
    476             //resume() was called before surfaceCreated()
    477             if (mMediaPlayer != null && mCurrentState == STATE_SUSPEND
    478                    && mTargetState == STATE_RESUME) {
    479                 mMediaPlayer.setDisplay(mSurfaceHolder);
    480                 resume();
    481             } else {
    482                 openVideo();
    483             }
    484         }
    485 
    486         public void surfaceDestroyed(SurfaceHolder holder)
    487         {
    488             // after we return from this we can't use the surface any more
    489             mSurfaceHolder = null;
    490             if (mMediaController != null) mMediaController.hide();
    491             if (mCurrentState != STATE_SUSPEND) {
    492                 release(true);
    493             }
    494         }
    495     };
    496 
    497     /*
    498      * release the media player in any state
    499      */
    500     private void release(boolean cleartargetstate) {
    501         if (mMediaPlayer != null) {
    502             mMediaPlayer.reset();
    503             mMediaPlayer.release();
    504             mMediaPlayer = null;
    505             mCurrentState = STATE_IDLE;
    506             if (cleartargetstate) {
    507                 mTargetState  = STATE_IDLE;
    508             }
    509         }
    510     }
    511 
    512     @Override
    513     public boolean onTouchEvent(MotionEvent ev) {
    514         if (isInPlaybackState() && mMediaController != null) {
    515             toggleMediaControlsVisiblity();
    516         }
    517         return false;
    518     }
    519 
    520     @Override
    521     public boolean onTrackballEvent(MotionEvent ev) {
    522         if (isInPlaybackState() && mMediaController != null) {
    523             toggleMediaControlsVisiblity();
    524         }
    525         return false;
    526     }
    527 
    528     @Override
    529     public boolean onKeyDown(int keyCode, KeyEvent event)
    530     {
    531         boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
    532                                      keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
    533                                      keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
    534                                      keyCode != KeyEvent.KEYCODE_MENU &&
    535                                      keyCode != KeyEvent.KEYCODE_CALL &&
    536                                      keyCode != KeyEvent.KEYCODE_ENDCALL;
    537         if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
    538             if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
    539                     keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
    540                 if (mMediaPlayer.isPlaying()) {
    541                     pause();
    542                     mMediaController.show();
    543                 } else {
    544                     start();
    545                     mMediaController.hide();
    546                 }
    547                 return true;
    548             } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
    549                     && mMediaPlayer.isPlaying()) {
    550                 pause();
    551                 mMediaController.show();
    552             } else {
    553                 toggleMediaControlsVisiblity();
    554             }
    555         }
    556 
    557         return super.onKeyDown(keyCode, event);
    558     }
    559 
    560     private void toggleMediaControlsVisiblity() {
    561         if (mMediaController.isShowing()) {
    562             mMediaController.hide();
    563         } else {
    564             mMediaController.show();
    565         }
    566     }
    567 
    568     public void start() {
    569         if (isInPlaybackState()) {
    570             mMediaPlayer.start();
    571             mCurrentState = STATE_PLAYING;
    572         }
    573         mTargetState = STATE_PLAYING;
    574     }
    575 
    576     public void pause() {
    577         if (isInPlaybackState()) {
    578             if (mMediaPlayer.isPlaying()) {
    579                 mMediaPlayer.pause();
    580                 mCurrentState = STATE_PAUSED;
    581             }
    582         }
    583         mTargetState = STATE_PAUSED;
    584     }
    585 
    586     public void suspend() {
    587         if (isInPlaybackState()) {
    588             if (mMediaPlayer.suspend()) {
    589                 mStateWhenSuspended = mCurrentState;
    590                 mCurrentState = STATE_SUSPEND;
    591                 mTargetState = STATE_SUSPEND;
    592             } else {
    593                 release(false);
    594                 mCurrentState = STATE_SUSPEND_UNSUPPORTED;
    595                 Log.w(TAG, "Unable to suspend video. Release MediaPlayer.");
    596             }
    597         }
    598     }
    599 
    600     public void resume() {
    601         if (mSurfaceHolder == null && mCurrentState == STATE_SUSPEND){
    602             mTargetState = STATE_RESUME;
    603             return;
    604         }
    605         if (mMediaPlayer != null && mCurrentState == STATE_SUSPEND) {
    606             if (mMediaPlayer.resume()) {
    607                 mCurrentState = mStateWhenSuspended;
    608                 mTargetState = mStateWhenSuspended;
    609             } else {
    610                 Log.w(TAG, "Unable to resume video");
    611             }
    612             return;
    613         }
    614         if (mCurrentState == STATE_SUSPEND_UNSUPPORTED) {
    615             openVideo();
    616         }
    617     }
    618 
    619    // cache duration as mDuration for faster access
    620     public int getDuration() {
    621         if (isInPlaybackState()) {
    622             if (mDuration > 0) {
    623                 return mDuration;
    624             }
    625             mDuration = mMediaPlayer.getDuration();
    626             return mDuration;
    627         }
    628         mDuration = -1;
    629         return mDuration;
    630     }
    631 
    632     public int getCurrentPosition() {
    633         if (isInPlaybackState()) {
    634             return mMediaPlayer.getCurrentPosition();
    635         }
    636         return 0;
    637     }
    638 
    639     public void seekTo(int msec) {
    640         if (isInPlaybackState()) {
    641             mMediaPlayer.seekTo(msec);
    642             mSeekWhenPrepared = 0;
    643         } else {
    644             mSeekWhenPrepared = msec;
    645         }
    646     }
    647 
    648     public boolean isPlaying() {
    649         return isInPlaybackState() && mMediaPlayer.isPlaying();
    650     }
    651 
    652     public int getBufferPercentage() {
    653         if (mMediaPlayer != null) {
    654             return mCurrentBufferPercentage;
    655         }
    656         return 0;
    657     }
    658 
    659     private boolean isInPlaybackState() {
    660         return (mMediaPlayer != null &&
    661                 mCurrentState != STATE_ERROR &&
    662                 mCurrentState != STATE_IDLE &&
    663                 mCurrentState != STATE_PREPARING);
    664     }
    665 
    666     public boolean canPause() {
    667         return mCanPause;
    668     }
    669 
    670     public boolean canSeekBackward() {
    671         return mCanSeekBack;
    672     }
    673 
    674     public boolean canSeekForward() {
    675         return mCanSeekForward;
    676     }
    677 }
    678