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.res.Resources;
     23 import android.graphics.Canvas;
     24 import android.media.AudioManager;
     25 import android.media.ClosedCaptionRenderer;
     26 import android.media.MediaFormat;
     27 import android.media.MediaPlayer;
     28 import android.media.MediaPlayer.OnCompletionListener;
     29 import android.media.MediaPlayer.OnErrorListener;
     30 import android.media.MediaPlayer.OnInfoListener;
     31 import android.media.Metadata;
     32 import android.media.SubtitleController;
     33 import android.media.SubtitleTrack.RenderingWidget;
     34 import android.media.TtmlRenderer;
     35 import android.media.WebVttRenderer;
     36 import android.net.Uri;
     37 import android.os.Looper;
     38 import android.util.AttributeSet;
     39 import android.util.Log;
     40 import android.util.Pair;
     41 import android.view.KeyEvent;
     42 import android.view.MotionEvent;
     43 import android.view.SurfaceHolder;
     44 import android.view.SurfaceView;
     45 import android.view.View;
     46 import android.view.accessibility.AccessibilityEvent;
     47 import android.view.accessibility.AccessibilityNodeInfo;
     48 import android.widget.MediaController.MediaPlayerControl;
     49 
     50 import java.io.IOException;
     51 import java.io.InputStream;
     52 import java.util.Map;
     53 import java.util.Vector;
     54 
     55 /**
     56  * Displays a video file.  The VideoView class
     57  * can load images from various sources (such as resources or content
     58  * providers), takes care of computing its measurement from the video so that
     59  * it can be used in any layout manager, and provides various display options
     60  * such as scaling and tinting.<p>
     61  *
     62  * <em>Note: VideoView does not retain its full state when going into the
     63  * background.</em>  In particular, it does not restore the current play state,
     64  * play position, selected tracks, or any subtitle tracks added via
     65  * {@link #addSubtitleSource addSubtitleSource()}.  Applications should
     66  * save and restore these on their own in
     67  * {@link android.app.Activity#onSaveInstanceState} and
     68  * {@link android.app.Activity#onRestoreInstanceState}.<p>
     69  * Also note that the audio session id (from {@link #getAudioSessionId}) may
     70  * change from its previously returned value when the VideoView is restored.
     71  */
     72 public class VideoView extends SurfaceView
     73         implements MediaPlayerControl, SubtitleController.Anchor {
     74     private String TAG = "VideoView";
     75     // settable by the client
     76     private Uri         mUri;
     77     private Map<String, String> mHeaders;
     78 
     79     // all possible internal states
     80     private static final int STATE_ERROR              = -1;
     81     private static final int STATE_IDLE               = 0;
     82     private static final int STATE_PREPARING          = 1;
     83     private static final int STATE_PREPARED           = 2;
     84     private static final int STATE_PLAYING            = 3;
     85     private static final int STATE_PAUSED             = 4;
     86     private static final int STATE_PLAYBACK_COMPLETED = 5;
     87 
     88     // mCurrentState is a VideoView object's current state.
     89     // mTargetState is the state that a method caller intends to reach.
     90     // For instance, regardless the VideoView object's current state,
     91     // calling pause() intends to bring the object to a target state
     92     // of STATE_PAUSED.
     93     private int mCurrentState = STATE_IDLE;
     94     private int mTargetState  = STATE_IDLE;
     95 
     96     // All the stuff we need for playing and showing a video
     97     private SurfaceHolder mSurfaceHolder = null;
     98     private MediaPlayer mMediaPlayer = null;
     99     private int         mAudioSession;
    100     private int         mVideoWidth;
    101     private int         mVideoHeight;
    102     private int         mSurfaceWidth;
    103     private int         mSurfaceHeight;
    104     private MediaController mMediaController;
    105     private OnCompletionListener mOnCompletionListener;
    106     private MediaPlayer.OnPreparedListener mOnPreparedListener;
    107     private int         mCurrentBufferPercentage;
    108     private OnErrorListener mOnErrorListener;
    109     private OnInfoListener  mOnInfoListener;
    110     private int         mSeekWhenPrepared;  // recording the seek position while preparing
    111     private boolean     mCanPause;
    112     private boolean     mCanSeekBack;
    113     private boolean     mCanSeekForward;
    114 
    115     /** Subtitle rendering widget overlaid on top of the video. */
    116     private RenderingWidget mSubtitleWidget;
    117 
    118     /** Listener for changes to subtitle data, used to redraw when needed. */
    119     private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
    120 
    121     public VideoView(Context context) {
    122         super(context);
    123         initVideoView();
    124     }
    125 
    126     public VideoView(Context context, AttributeSet attrs) {
    127         this(context, attrs, 0);
    128         initVideoView();
    129     }
    130 
    131     public VideoView(Context context, AttributeSet attrs, int defStyleAttr) {
    132         this(context, attrs, defStyleAttr, 0);
    133     }
    134 
    135     public VideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    136         super(context, attrs, defStyleAttr, defStyleRes);
    137         initVideoView();
    138     }
    139 
    140     @Override
    141     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    142         //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
    143         //        + MeasureSpec.toString(heightMeasureSpec) + ")");
    144 
    145         int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
    146         int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
    147         if (mVideoWidth > 0 && mVideoHeight > 0) {
    148 
    149             int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    150             int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    151             int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    152             int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    153 
    154             if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
    155                 // the size is fixed
    156                 width = widthSpecSize;
    157                 height = heightSpecSize;
    158 
    159                 // for compatibility, we adjust size based on aspect ratio
    160                 if ( mVideoWidth * height  < width * mVideoHeight ) {
    161                     //Log.i("@@@", "image too wide, correcting");
    162                     width = height * mVideoWidth / mVideoHeight;
    163                 } else if ( mVideoWidth * height  > width * mVideoHeight ) {
    164                     //Log.i("@@@", "image too tall, correcting");
    165                     height = width * mVideoHeight / mVideoWidth;
    166                 }
    167             } else if (widthSpecMode == MeasureSpec.EXACTLY) {
    168                 // only the width is fixed, adjust the height to match aspect ratio if possible
    169                 width = widthSpecSize;
    170                 height = width * mVideoHeight / mVideoWidth;
    171                 if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
    172                     // couldn't match aspect ratio within the constraints
    173                     height = heightSpecSize;
    174                 }
    175             } else if (heightSpecMode == MeasureSpec.EXACTLY) {
    176                 // only the height is fixed, adjust the width to match aspect ratio if possible
    177                 height = heightSpecSize;
    178                 width = height * mVideoWidth / mVideoHeight;
    179                 if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
    180                     // couldn't match aspect ratio within the constraints
    181                     width = widthSpecSize;
    182                 }
    183             } else {
    184                 // neither the width nor the height are fixed, try to use actual video size
    185                 width = mVideoWidth;
    186                 height = mVideoHeight;
    187                 if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
    188                     // too tall, decrease both width and height
    189                     height = heightSpecSize;
    190                     width = height * mVideoWidth / mVideoHeight;
    191                 }
    192                 if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
    193                     // too wide, decrease both width and height
    194                     width = widthSpecSize;
    195                     height = width * mVideoHeight / mVideoWidth;
    196                 }
    197             }
    198         } else {
    199             // no size yet, just adopt the given spec sizes
    200         }
    201         setMeasuredDimension(width, height);
    202     }
    203 
    204     @Override
    205     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    206         super.onInitializeAccessibilityEvent(event);
    207         event.setClassName(VideoView.class.getName());
    208     }
    209 
    210     @Override
    211     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    212         super.onInitializeAccessibilityNodeInfo(info);
    213         info.setClassName(VideoView.class.getName());
    214     }
    215 
    216     public int resolveAdjustedSize(int desiredSize, int measureSpec) {
    217         return getDefaultSize(desiredSize, measureSpec);
    218     }
    219 
    220     private void initVideoView() {
    221         mVideoWidth = 0;
    222         mVideoHeight = 0;
    223         getHolder().addCallback(mSHCallback);
    224         getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    225         setFocusable(true);
    226         setFocusableInTouchMode(true);
    227         requestFocus();
    228         mPendingSubtitleTracks = new Vector<Pair<InputStream, MediaFormat>>();
    229         mCurrentState = STATE_IDLE;
    230         mTargetState  = STATE_IDLE;
    231     }
    232 
    233     /**
    234      * Sets video path.
    235      *
    236      * @param path the path of the video.
    237      */
    238     public void setVideoPath(String path) {
    239         setVideoURI(Uri.parse(path));
    240     }
    241 
    242     /**
    243      * Sets video URI.
    244      *
    245      * @param uri the URI of the video.
    246      */
    247     public void setVideoURI(Uri uri) {
    248         setVideoURI(uri, null);
    249     }
    250 
    251     /**
    252      * Sets video URI using specific headers.
    253      *
    254      * @param uri     the URI of the video.
    255      * @param headers the headers for the URI request.
    256      *                Note that the cross domain redirection is allowed by default, but that can be
    257      *                changed with key/value pairs through the headers parameter with
    258      *                "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
    259      *                to disallow or allow cross domain redirection.
    260      */
    261     public void setVideoURI(Uri uri, Map<String, String> headers) {
    262         mUri = uri;
    263         mHeaders = headers;
    264         mSeekWhenPrepared = 0;
    265         openVideo();
    266         requestLayout();
    267         invalidate();
    268     }
    269 
    270     /**
    271      * Adds an external subtitle source file (from the provided input stream.)
    272      *
    273      * Note that a single external subtitle source may contain multiple or no
    274      * supported tracks in it. If the source contained at least one track in
    275      * it, one will receive an {@link MediaPlayer#MEDIA_INFO_METADATA_UPDATE}
    276      * info message. Otherwise, if reading the source takes excessive time,
    277      * one will receive a {@link MediaPlayer#MEDIA_INFO_SUBTITLE_TIMED_OUT}
    278      * message. If the source contained no supported track (including an empty
    279      * source file or null input stream), one will receive a {@link
    280      * MediaPlayer#MEDIA_INFO_UNSUPPORTED_SUBTITLE} message. One can find the
    281      * total number of available tracks using {@link MediaPlayer#getTrackInfo()}
    282      * to see what additional tracks become available after this method call.
    283      *
    284      * @param is     input stream containing the subtitle data.  It will be
    285      *               closed by the media framework.
    286      * @param format the format of the subtitle track(s).  Must contain at least
    287      *               the mime type ({@link MediaFormat#KEY_MIME}) and the
    288      *               language ({@link MediaFormat#KEY_LANGUAGE}) of the file.
    289      *               If the file itself contains the language information,
    290      *               specify "und" for the language.
    291      */
    292     public void addSubtitleSource(InputStream is, MediaFormat format) {
    293         if (mMediaPlayer == null) {
    294             mPendingSubtitleTracks.add(Pair.create(is, format));
    295         } else {
    296             try {
    297                 mMediaPlayer.addSubtitleSource(is, format);
    298             } catch (IllegalStateException e) {
    299                 mInfoListener.onInfo(
    300                         mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);
    301             }
    302         }
    303     }
    304 
    305     private Vector<Pair<InputStream, MediaFormat>> mPendingSubtitleTracks;
    306 
    307     public void stopPlayback() {
    308         if (mMediaPlayer != null) {
    309             mMediaPlayer.stop();
    310             mMediaPlayer.release();
    311             mMediaPlayer = null;
    312             mCurrentState = STATE_IDLE;
    313             mTargetState  = STATE_IDLE;
    314         }
    315     }
    316 
    317     private void openVideo() {
    318         if (mUri == null || mSurfaceHolder == null) {
    319             // not ready for playback just yet, will try again later
    320             return;
    321         }
    322         AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
    323         am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
    324 
    325         // we shouldn't clear the target state, because somebody might have
    326         // called start() previously
    327         release(false);
    328         try {
    329             mMediaPlayer = new MediaPlayer();
    330             // TODO: create SubtitleController in MediaPlayer, but we need
    331             // a context for the subtitle renderers
    332             final Context context = getContext();
    333             final SubtitleController controller = new SubtitleController(
    334                     context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
    335             controller.registerRenderer(new WebVttRenderer(context));
    336             controller.registerRenderer(new TtmlRenderer(context));
    337             controller.registerRenderer(new ClosedCaptionRenderer(context));
    338             mMediaPlayer.setSubtitleAnchor(controller, this);
    339 
    340             if (mAudioSession != 0) {
    341                 mMediaPlayer.setAudioSessionId(mAudioSession);
    342             } else {
    343                 mAudioSession = mMediaPlayer.getAudioSessionId();
    344             }
    345             mMediaPlayer.setOnPreparedListener(mPreparedListener);
    346             mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
    347             mMediaPlayer.setOnCompletionListener(mCompletionListener);
    348             mMediaPlayer.setOnErrorListener(mErrorListener);
    349             mMediaPlayer.setOnInfoListener(mInfoListener);
    350             mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
    351             mCurrentBufferPercentage = 0;
    352             mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
    353             mMediaPlayer.setDisplay(mSurfaceHolder);
    354             mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    355             mMediaPlayer.setScreenOnWhilePlaying(true);
    356             mMediaPlayer.prepareAsync();
    357 
    358             for (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) {
    359                 try {
    360                     mMediaPlayer.addSubtitleSource(pending.first, pending.second);
    361                 } catch (IllegalStateException e) {
    362                     mInfoListener.onInfo(
    363                             mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);
    364                 }
    365             }
    366 
    367             // we don't set the target state here either, but preserve the
    368             // target state that was there before.
    369             mCurrentState = STATE_PREPARING;
    370             attachMediaController();
    371         } catch (IOException ex) {
    372             Log.w(TAG, "Unable to open content: " + mUri, ex);
    373             mCurrentState = STATE_ERROR;
    374             mTargetState = STATE_ERROR;
    375             mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
    376             return;
    377         } catch (IllegalArgumentException ex) {
    378             Log.w(TAG, "Unable to open content: " + mUri, ex);
    379             mCurrentState = STATE_ERROR;
    380             mTargetState = STATE_ERROR;
    381             mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
    382             return;
    383         } finally {
    384             mPendingSubtitleTracks.clear();
    385         }
    386     }
    387 
    388     public void setMediaController(MediaController controller) {
    389         if (mMediaController != null) {
    390             mMediaController.hide();
    391         }
    392         mMediaController = controller;
    393         attachMediaController();
    394     }
    395 
    396     private void attachMediaController() {
    397         if (mMediaPlayer != null && mMediaController != null) {
    398             mMediaController.setMediaPlayer(this);
    399             View anchorView = this.getParent() instanceof View ?
    400                     (View)this.getParent() : this;
    401             mMediaController.setAnchorView(anchorView);
    402             mMediaController.setEnabled(isInPlaybackState());
    403         }
    404     }
    405 
    406     MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
    407         new MediaPlayer.OnVideoSizeChangedListener() {
    408             public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
    409                 mVideoWidth = mp.getVideoWidth();
    410                 mVideoHeight = mp.getVideoHeight();
    411                 if (mVideoWidth != 0 && mVideoHeight != 0) {
    412                     getHolder().setFixedSize(mVideoWidth, mVideoHeight);
    413                     requestLayout();
    414                 }
    415             }
    416     };
    417 
    418     MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
    419         public void onPrepared(MediaPlayer mp) {
    420             mCurrentState = STATE_PREPARED;
    421 
    422             // Get the capabilities of the player for this stream
    423             Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
    424                                       MediaPlayer.BYPASS_METADATA_FILTER);
    425 
    426             if (data != null) {
    427                 mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
    428                         || data.getBoolean(Metadata.PAUSE_AVAILABLE);
    429                 mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
    430                         || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
    431                 mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
    432                         || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
    433             } else {
    434                 mCanPause = mCanSeekBack = mCanSeekForward = true;
    435             }
    436 
    437             if (mOnPreparedListener != null) {
    438                 mOnPreparedListener.onPrepared(mMediaPlayer);
    439             }
    440             if (mMediaController != null) {
    441                 mMediaController.setEnabled(true);
    442             }
    443             mVideoWidth = mp.getVideoWidth();
    444             mVideoHeight = mp.getVideoHeight();
    445 
    446             int seekToPosition = mSeekWhenPrepared;  // mSeekWhenPrepared may be changed after seekTo() call
    447             if (seekToPosition != 0) {
    448                 seekTo(seekToPosition);
    449             }
    450             if (mVideoWidth != 0 && mVideoHeight != 0) {
    451                 //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
    452                 getHolder().setFixedSize(mVideoWidth, mVideoHeight);
    453                 if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
    454                     // We didn't actually change the size (it was already at the size
    455                     // we need), so we won't get a "surface changed" callback, so
    456                     // start the video here instead of in the callback.
    457                     if (mTargetState == STATE_PLAYING) {
    458                         start();
    459                         if (mMediaController != null) {
    460                             mMediaController.show();
    461                         }
    462                     } else if (!isPlaying() &&
    463                                (seekToPosition != 0 || getCurrentPosition() > 0)) {
    464                        if (mMediaController != null) {
    465                            // Show the media controls when we're paused into a video and make 'em stick.
    466                            mMediaController.show(0);
    467                        }
    468                    }
    469                 }
    470             } else {
    471                 // We don't know the video size yet, but should start anyway.
    472                 // The video size might be reported to us later.
    473                 if (mTargetState == STATE_PLAYING) {
    474                     start();
    475                 }
    476             }
    477         }
    478     };
    479 
    480     private MediaPlayer.OnCompletionListener mCompletionListener =
    481         new MediaPlayer.OnCompletionListener() {
    482         public void onCompletion(MediaPlayer mp) {
    483             mCurrentState = STATE_PLAYBACK_COMPLETED;
    484             mTargetState = STATE_PLAYBACK_COMPLETED;
    485             if (mMediaController != null) {
    486                 mMediaController.hide();
    487             }
    488             if (mOnCompletionListener != null) {
    489                 mOnCompletionListener.onCompletion(mMediaPlayer);
    490             }
    491         }
    492     };
    493 
    494     private MediaPlayer.OnInfoListener mInfoListener =
    495         new MediaPlayer.OnInfoListener() {
    496         public  boolean onInfo(MediaPlayer mp, int arg1, int arg2) {
    497             if (mOnInfoListener != null) {
    498                 mOnInfoListener.onInfo(mp, arg1, arg2);
    499             }
    500             return true;
    501         }
    502     };
    503 
    504     private MediaPlayer.OnErrorListener mErrorListener =
    505         new MediaPlayer.OnErrorListener() {
    506         public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {
    507             Log.d(TAG, "Error: " + framework_err + "," + impl_err);
    508             mCurrentState = STATE_ERROR;
    509             mTargetState = STATE_ERROR;
    510             if (mMediaController != null) {
    511                 mMediaController.hide();
    512             }
    513 
    514             /* If an error handler has been supplied, use it and finish. */
    515             if (mOnErrorListener != null) {
    516                 if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {
    517                     return true;
    518                 }
    519             }
    520 
    521             /* Otherwise, pop up an error dialog so the user knows that
    522              * something bad has happened. Only try and pop up the dialog
    523              * if we're attached to a window. When we're going away and no
    524              * longer have a window, don't bother showing the user an error.
    525              */
    526             if (getWindowToken() != null) {
    527                 Resources r = mContext.getResources();
    528                 int messageId;
    529 
    530                 if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
    531                     messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback;
    532                 } else {
    533                     messageId = com.android.internal.R.string.VideoView_error_text_unknown;
    534                 }
    535 
    536                 new AlertDialog.Builder(mContext)
    537                         .setMessage(messageId)
    538                         .setPositiveButton(com.android.internal.R.string.VideoView_error_button,
    539                                 new DialogInterface.OnClickListener() {
    540                                     public void onClick(DialogInterface dialog, int whichButton) {
    541                                         /* If we get here, there is no onError listener, so
    542                                          * at least inform them that the video is over.
    543                                          */
    544                                         if (mOnCompletionListener != null) {
    545                                             mOnCompletionListener.onCompletion(mMediaPlayer);
    546                                         }
    547                                     }
    548                                 })
    549                         .setCancelable(false)
    550                         .show();
    551             }
    552             return true;
    553         }
    554     };
    555 
    556     private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
    557         new MediaPlayer.OnBufferingUpdateListener() {
    558         public void onBufferingUpdate(MediaPlayer mp, int percent) {
    559             mCurrentBufferPercentage = percent;
    560         }
    561     };
    562 
    563     /**
    564      * Register a callback to be invoked when the media file
    565      * is loaded and ready to go.
    566      *
    567      * @param l The callback that will be run
    568      */
    569     public void setOnPreparedListener(MediaPlayer.OnPreparedListener l)
    570     {
    571         mOnPreparedListener = l;
    572     }
    573 
    574     /**
    575      * Register a callback to be invoked when the end of a media file
    576      * has been reached during playback.
    577      *
    578      * @param l The callback that will be run
    579      */
    580     public void setOnCompletionListener(OnCompletionListener l)
    581     {
    582         mOnCompletionListener = l;
    583     }
    584 
    585     /**
    586      * Register a callback to be invoked when an error occurs
    587      * during playback or setup.  If no listener is specified,
    588      * or if the listener returned false, VideoView will inform
    589      * the user of any errors.
    590      *
    591      * @param l The callback that will be run
    592      */
    593     public void setOnErrorListener(OnErrorListener l)
    594     {
    595         mOnErrorListener = l;
    596     }
    597 
    598     /**
    599      * Register a callback to be invoked when an informational event
    600      * occurs during playback or setup.
    601      *
    602      * @param l The callback that will be run
    603      */
    604     public void setOnInfoListener(OnInfoListener l) {
    605         mOnInfoListener = l;
    606     }
    607 
    608     SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
    609     {
    610         public void surfaceChanged(SurfaceHolder holder, int format,
    611                                     int w, int h)
    612         {
    613             mSurfaceWidth = w;
    614             mSurfaceHeight = h;
    615             boolean isValidState =  (mTargetState == STATE_PLAYING);
    616             boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);
    617             if (mMediaPlayer != null && isValidState && hasValidSize) {
    618                 if (mSeekWhenPrepared != 0) {
    619                     seekTo(mSeekWhenPrepared);
    620                 }
    621                 start();
    622             }
    623         }
    624 
    625         public void surfaceCreated(SurfaceHolder holder)
    626         {
    627             mSurfaceHolder = holder;
    628             openVideo();
    629         }
    630 
    631         public void surfaceDestroyed(SurfaceHolder holder)
    632         {
    633             // after we return from this we can't use the surface any more
    634             mSurfaceHolder = null;
    635             if (mMediaController != null) mMediaController.hide();
    636             release(true);
    637         }
    638     };
    639 
    640     /*
    641      * release the media player in any state
    642      */
    643     private void release(boolean cleartargetstate) {
    644         if (mMediaPlayer != null) {
    645             mMediaPlayer.reset();
    646             mMediaPlayer.release();
    647             mMediaPlayer = null;
    648             mPendingSubtitleTracks.clear();
    649             mCurrentState = STATE_IDLE;
    650             if (cleartargetstate) {
    651                 mTargetState  = STATE_IDLE;
    652             }
    653         }
    654     }
    655 
    656     @Override
    657     public boolean onTouchEvent(MotionEvent ev) {
    658         if (isInPlaybackState() && mMediaController != null) {
    659             toggleMediaControlsVisiblity();
    660         }
    661         return false;
    662     }
    663 
    664     @Override
    665     public boolean onTrackballEvent(MotionEvent ev) {
    666         if (isInPlaybackState() && mMediaController != null) {
    667             toggleMediaControlsVisiblity();
    668         }
    669         return false;
    670     }
    671 
    672     @Override
    673     public boolean onKeyDown(int keyCode, KeyEvent event)
    674     {
    675         boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
    676                                      keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
    677                                      keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
    678                                      keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
    679                                      keyCode != KeyEvent.KEYCODE_MENU &&
    680                                      keyCode != KeyEvent.KEYCODE_CALL &&
    681                                      keyCode != KeyEvent.KEYCODE_ENDCALL;
    682         if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
    683             if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
    684                     keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
    685                 if (mMediaPlayer.isPlaying()) {
    686                     pause();
    687                     mMediaController.show();
    688                 } else {
    689                     start();
    690                     mMediaController.hide();
    691                 }
    692                 return true;
    693             } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
    694                 if (!mMediaPlayer.isPlaying()) {
    695                     start();
    696                     mMediaController.hide();
    697                 }
    698                 return true;
    699             } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
    700                     || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
    701                 if (mMediaPlayer.isPlaying()) {
    702                     pause();
    703                     mMediaController.show();
    704                 }
    705                 return true;
    706             } else {
    707                 toggleMediaControlsVisiblity();
    708             }
    709         }
    710 
    711         return super.onKeyDown(keyCode, event);
    712     }
    713 
    714     private void toggleMediaControlsVisiblity() {
    715         if (mMediaController.isShowing()) {
    716             mMediaController.hide();
    717         } else {
    718             mMediaController.show();
    719         }
    720     }
    721 
    722     @Override
    723     public void start() {
    724         if (isInPlaybackState()) {
    725             mMediaPlayer.start();
    726             mCurrentState = STATE_PLAYING;
    727         }
    728         mTargetState = STATE_PLAYING;
    729     }
    730 
    731     @Override
    732     public void pause() {
    733         if (isInPlaybackState()) {
    734             if (mMediaPlayer.isPlaying()) {
    735                 mMediaPlayer.pause();
    736                 mCurrentState = STATE_PAUSED;
    737             }
    738         }
    739         mTargetState = STATE_PAUSED;
    740     }
    741 
    742     public void suspend() {
    743         release(false);
    744     }
    745 
    746     public void resume() {
    747         openVideo();
    748     }
    749 
    750     @Override
    751     public int getDuration() {
    752         if (isInPlaybackState()) {
    753             return mMediaPlayer.getDuration();
    754         }
    755 
    756         return -1;
    757     }
    758 
    759     @Override
    760     public int getCurrentPosition() {
    761         if (isInPlaybackState()) {
    762             return mMediaPlayer.getCurrentPosition();
    763         }
    764         return 0;
    765     }
    766 
    767     @Override
    768     public void seekTo(int msec) {
    769         if (isInPlaybackState()) {
    770             mMediaPlayer.seekTo(msec);
    771             mSeekWhenPrepared = 0;
    772         } else {
    773             mSeekWhenPrepared = msec;
    774         }
    775     }
    776 
    777     @Override
    778     public boolean isPlaying() {
    779         return isInPlaybackState() && mMediaPlayer.isPlaying();
    780     }
    781 
    782     @Override
    783     public int getBufferPercentage() {
    784         if (mMediaPlayer != null) {
    785             return mCurrentBufferPercentage;
    786         }
    787         return 0;
    788     }
    789 
    790     private boolean isInPlaybackState() {
    791         return (mMediaPlayer != null &&
    792                 mCurrentState != STATE_ERROR &&
    793                 mCurrentState != STATE_IDLE &&
    794                 mCurrentState != STATE_PREPARING);
    795     }
    796 
    797     @Override
    798     public boolean canPause() {
    799         return mCanPause;
    800     }
    801 
    802     @Override
    803     public boolean canSeekBackward() {
    804         return mCanSeekBack;
    805     }
    806 
    807     @Override
    808     public boolean canSeekForward() {
    809         return mCanSeekForward;
    810     }
    811 
    812     @Override
    813     public int getAudioSessionId() {
    814         if (mAudioSession == 0) {
    815             MediaPlayer foo = new MediaPlayer();
    816             mAudioSession = foo.getAudioSessionId();
    817             foo.release();
    818         }
    819         return mAudioSession;
    820     }
    821 
    822     @Override
    823     protected void onAttachedToWindow() {
    824         super.onAttachedToWindow();
    825 
    826         if (mSubtitleWidget != null) {
    827             mSubtitleWidget.onAttachedToWindow();
    828         }
    829     }
    830 
    831     @Override
    832     protected void onDetachedFromWindow() {
    833         super.onDetachedFromWindow();
    834 
    835         if (mSubtitleWidget != null) {
    836             mSubtitleWidget.onDetachedFromWindow();
    837         }
    838     }
    839 
    840     @Override
    841     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    842         super.onLayout(changed, left, top, right, bottom);
    843 
    844         if (mSubtitleWidget != null) {
    845             measureAndLayoutSubtitleWidget();
    846         }
    847     }
    848 
    849     @Override
    850     public void draw(Canvas canvas) {
    851         super.draw(canvas);
    852 
    853         if (mSubtitleWidget != null) {
    854             final int saveCount = canvas.save();
    855             canvas.translate(getPaddingLeft(), getPaddingTop());
    856             mSubtitleWidget.draw(canvas);
    857             canvas.restoreToCount(saveCount);
    858         }
    859     }
    860 
    861     /**
    862      * Forces a measurement and layout pass for all overlaid views.
    863      *
    864      * @see #setSubtitleWidget(RenderingWidget)
    865      */
    866     private void measureAndLayoutSubtitleWidget() {
    867         final int width = getWidth() - getPaddingLeft() - getPaddingRight();
    868         final int height = getHeight() - getPaddingTop() - getPaddingBottom();
    869 
    870         mSubtitleWidget.setSize(width, height);
    871     }
    872 
    873     /** @hide */
    874     @Override
    875     public void setSubtitleWidget(RenderingWidget subtitleWidget) {
    876         if (mSubtitleWidget == subtitleWidget) {
    877             return;
    878         }
    879 
    880         final boolean attachedToWindow = isAttachedToWindow();
    881         if (mSubtitleWidget != null) {
    882             if (attachedToWindow) {
    883                 mSubtitleWidget.onDetachedFromWindow();
    884             }
    885 
    886             mSubtitleWidget.setOnChangedListener(null);
    887         }
    888 
    889         mSubtitleWidget = subtitleWidget;
    890 
    891         if (subtitleWidget != null) {
    892             if (mSubtitlesChangedListener == null) {
    893                 mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {
    894                     @Override
    895                     public void onChanged(RenderingWidget renderingWidget) {
    896                         invalidate();
    897                     }
    898                 };
    899             }
    900 
    901             setWillNotDraw(false);
    902             subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);
    903 
    904             if (attachedToWindow) {
    905                 subtitleWidget.onAttachedToWindow();
    906                 requestLayout();
    907             }
    908         } else {
    909             setWillNotDraw(true);
    910         }
    911 
    912         invalidate();
    913     }
    914 
    915     /** @hide */
    916     @Override
    917     public Looper getSubtitleLooper() {
    918         return Looper.getMainLooper();
    919     }
    920 }
    921