• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
VideoView(Context context)94     public VideoView(Context context) {
95         super(context);
96         initVideoView();
97     }
98 
VideoView(Context context, AttributeSet attrs)99     public VideoView(Context context, AttributeSet attrs) {
100         this(context, attrs, 0);
101         initVideoView();
102     }
103 
VideoView(Context context, AttributeSet attrs, int defStyle)104     public VideoView(Context context, AttributeSet attrs, int defStyle) {
105         super(context, attrs, defStyle);
106         initVideoView();
107     }
108 
109     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 
resolveAdjustedSize(int desiredSize, int measureSpec)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 
initVideoView()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 
setVideoPath(String path)172     public void setVideoPath(String path) {
173         setVideoURI(Uri.parse(path));
174     }
175 
setVideoURI(Uri uri)176     public void setVideoURI(Uri uri) {
177         setVideoURI(uri, null);
178     }
179 
180     /**
181      * @hide
182      */
setVideoURI(Uri uri, Map<String, String> headers)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 
stopPlayback()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 
openVideo()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 
setMediaController(MediaController controller)249     public void setMediaController(MediaController controller) {
250         if (mMediaController != null) {
251             mMediaController.hide();
252         }
253         mMediaController = controller;
254         attachMediaController();
255     }
256 
attachMediaController()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      */
setOnPreparedListener(MediaPlayer.OnPreparedListener l)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      */
setOnCompletionListener(OnCompletionListener l)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      */
setOnErrorListener(OnErrorListener l)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      */
release(boolean cleartargetstate)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
onTouchEvent(MotionEvent ev)513     public boolean onTouchEvent(MotionEvent ev) {
514         if (isInPlaybackState() && mMediaController != null) {
515             toggleMediaControlsVisiblity();
516         }
517         return false;
518     }
519 
520     @Override
onTrackballEvent(MotionEvent ev)521     public boolean onTrackballEvent(MotionEvent ev) {
522         if (isInPlaybackState() && mMediaController != null) {
523             toggleMediaControlsVisiblity();
524         }
525         return false;
526     }
527 
528     @Override
onKeyDown(int keyCode, KeyEvent event)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 
toggleMediaControlsVisiblity()560     private void toggleMediaControlsVisiblity() {
561         if (mMediaController.isShowing()) {
562             mMediaController.hide();
563         } else {
564             mMediaController.show();
565         }
566     }
567 
start()568     public void start() {
569         if (isInPlaybackState()) {
570             mMediaPlayer.start();
571             mCurrentState = STATE_PLAYING;
572         }
573         mTargetState = STATE_PLAYING;
574     }
575 
pause()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 
suspend()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 
resume()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
getDuration()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 
getCurrentPosition()632     public int getCurrentPosition() {
633         if (isInPlaybackState()) {
634             return mMediaPlayer.getCurrentPosition();
635         }
636         return 0;
637     }
638 
seekTo(int msec)639     public void seekTo(int msec) {
640         if (isInPlaybackState()) {
641             mMediaPlayer.seekTo(msec);
642             mSeekWhenPrepared = 0;
643         } else {
644             mSeekWhenPrepared = msec;
645         }
646     }
647 
isPlaying()648     public boolean isPlaying() {
649         return isInPlaybackState() && mMediaPlayer.isPlaying();
650     }
651 
getBufferPercentage()652     public int getBufferPercentage() {
653         if (mMediaPlayer != null) {
654             return mCurrentBufferPercentage;
655         }
656         return 0;
657     }
658 
isInPlaybackState()659     private boolean isInPlaybackState() {
660         return (mMediaPlayer != null &&
661                 mCurrentState != STATE_ERROR &&
662                 mCurrentState != STATE_IDLE &&
663                 mCurrentState != STATE_PREPARING);
664     }
665 
canPause()666     public boolean canPause() {
667         return mCanPause;
668     }
669 
canSeekBackward()670     public boolean canSeekBackward() {
671         return mCanSeekBack;
672     }
673 
canSeekForward()674     public boolean canSeekForward() {
675         return mCanSeekForward;
676     }
677 }
678