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