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