• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.app.AlertDialog;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.content.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 
VideoView(Context context)120     public VideoView(Context context) {
121         super(context);
122         initVideoView();
123     }
124 
VideoView(Context context, AttributeSet attrs)125     public VideoView(Context context, AttributeSet attrs) {
126         this(context, attrs, 0);
127         initVideoView();
128     }
129 
VideoView(Context context, AttributeSet attrs, int defStyleAttr)130     public VideoView(Context context, AttributeSet attrs, int defStyleAttr) {
131         this(context, attrs, defStyleAttr, 0);
132     }
133 
VideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)134     public VideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
135         super(context, attrs, defStyleAttr, defStyleRes);
136         initVideoView();
137     }
138 
139     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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
getAccessibilityClassName()204     public CharSequence getAccessibilityClassName() {
205         return VideoView.class.getName();
206     }
207 
resolveAdjustedSize(int desiredSize, int measureSpec)208     public int resolveAdjustedSize(int desiredSize, int measureSpec) {
209         return getDefaultSize(desiredSize, measureSpec);
210     }
211 
initVideoView()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      */
setVideoPath(String path)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      */
setVideoURI(Uri uri)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      */
setVideoURI(Uri uri, Map<String, String> headers)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      */
addSubtitleSource(InputStream is, MediaFormat format)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 
stopPlayback()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 
openVideo()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 
setMediaController(MediaController controller)384     public void setMediaController(MediaController controller) {
385         if (mMediaController != null) {
386             mMediaController.hide();
387         }
388         mMediaController = controller;
389         attachMediaController();
390     }
391 
attachMediaController()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      */
setOnPreparedListener(MediaPlayer.OnPreparedListener l)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      */
setOnCompletionListener(OnCompletionListener l)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      */
setOnErrorListener(OnErrorListener l)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      */
setOnInfoListener(OnInfoListener l)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      */
release(boolean cleartargetstate)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
onTouchEvent(MotionEvent ev)655     public boolean onTouchEvent(MotionEvent ev) {
656         if (isInPlaybackState() && mMediaController != null) {
657             toggleMediaControlsVisiblity();
658         }
659         return false;
660     }
661 
662     @Override
onTrackballEvent(MotionEvent ev)663     public boolean onTrackballEvent(MotionEvent ev) {
664         if (isInPlaybackState() && mMediaController != null) {
665             toggleMediaControlsVisiblity();
666         }
667         return false;
668     }
669 
670     @Override
onKeyDown(int keyCode, KeyEvent event)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 
toggleMediaControlsVisiblity()712     private void toggleMediaControlsVisiblity() {
713         if (mMediaController.isShowing()) {
714             mMediaController.hide();
715         } else {
716             mMediaController.show();
717         }
718     }
719 
720     @Override
start()721     public void start() {
722         if (isInPlaybackState()) {
723             mMediaPlayer.start();
724             mCurrentState = STATE_PLAYING;
725         }
726         mTargetState = STATE_PLAYING;
727     }
728 
729     @Override
pause()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 
suspend()740     public void suspend() {
741         release(false);
742     }
743 
resume()744     public void resume() {
745         openVideo();
746     }
747 
748     @Override
getDuration()749     public int getDuration() {
750         if (isInPlaybackState()) {
751             return mMediaPlayer.getDuration();
752         }
753 
754         return -1;
755     }
756 
757     @Override
getCurrentPosition()758     public int getCurrentPosition() {
759         if (isInPlaybackState()) {
760             return mMediaPlayer.getCurrentPosition();
761         }
762         return 0;
763     }
764 
765     @Override
seekTo(int msec)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
isPlaying()776     public boolean isPlaying() {
777         return isInPlaybackState() && mMediaPlayer.isPlaying();
778     }
779 
780     @Override
getBufferPercentage()781     public int getBufferPercentage() {
782         if (mMediaPlayer != null) {
783             return mCurrentBufferPercentage;
784         }
785         return 0;
786     }
787 
isInPlaybackState()788     private boolean isInPlaybackState() {
789         return (mMediaPlayer != null &&
790                 mCurrentState != STATE_ERROR &&
791                 mCurrentState != STATE_IDLE &&
792                 mCurrentState != STATE_PREPARING);
793     }
794 
795     @Override
canPause()796     public boolean canPause() {
797         return mCanPause;
798     }
799 
800     @Override
canSeekBackward()801     public boolean canSeekBackward() {
802         return mCanSeekBack;
803     }
804 
805     @Override
canSeekForward()806     public boolean canSeekForward() {
807         return mCanSeekForward;
808     }
809 
810     @Override
getAudioSessionId()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
onAttachedToWindow()821     protected void onAttachedToWindow() {
822         super.onAttachedToWindow();
823 
824         if (mSubtitleWidget != null) {
825             mSubtitleWidget.onAttachedToWindow();
826         }
827     }
828 
829     @Override
onDetachedFromWindow()830     protected void onDetachedFromWindow() {
831         super.onDetachedFromWindow();
832 
833         if (mSubtitleWidget != null) {
834             mSubtitleWidget.onDetachedFromWindow();
835         }
836     }
837 
838     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)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
draw(Canvas canvas)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      */
measureAndLayoutSubtitleWidget()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
setSubtitleWidget(RenderingWidget subtitleWidget)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
getSubtitleLooper()915     public Looper getSubtitleLooper() {
916         return Looper.getMainLooper();
917     }
918 }
919