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