• 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.Intent;
23 import android.content.res.Resources;
24 import android.media.AudioManager;
25 import android.media.MediaPlayer;
26 import android.media.Metadata;
27 import android.media.MediaPlayer.OnCompletionListener;
28 import android.media.MediaPlayer.OnErrorListener;
29 import android.net.Uri;
30 import android.os.PowerManager;
31 import android.util.AttributeSet;
32 import android.util.Log;
33 import android.view.KeyEvent;
34 import android.view.MotionEvent;
35 import android.view.SurfaceHolder;
36 import android.view.SurfaceView;
37 import android.view.View;
38 import android.widget.MediaController.*;
39 
40 import java.io.IOException;
41 
42 /**
43  * Displays a video file.  The VideoView class
44  * can load images from various sources (such as resources or content
45  * providers), takes care of computing its measurement from the video so that
46  * it can be used in any layout manager, and provides various display options
47  * such as scaling and tinting.
48  */
49 public class VideoView extends SurfaceView implements MediaPlayerControl {
50     private String TAG = "VideoView";
51     // settable by the client
52     private Uri         mUri;
53     private int         mDuration;
54 
55     // all possible internal states
56     private static final int STATE_ERROR              = -1;
57     private static final int STATE_IDLE               = 0;
58     private static final int STATE_PREPARING          = 1;
59     private static final int STATE_PREPARED           = 2;
60     private static final int STATE_PLAYING            = 3;
61     private static final int STATE_PAUSED             = 4;
62     private static final int STATE_PLAYBACK_COMPLETED = 5;
63 
64     // mCurrentState is a VideoView object's current state.
65     // mTargetState is the state that a method caller intends to reach.
66     // For instance, regardless the VideoView object's current state,
67     // calling pause() intends to bring the object to a target state
68     // of STATE_PAUSED.
69     private int mCurrentState = STATE_IDLE;
70     private int mTargetState  = STATE_IDLE;
71 
72     // All the stuff we need for playing and showing a video
73     private SurfaceHolder mSurfaceHolder = null;
74     private MediaPlayer mMediaPlayer = null;
75     private int         mVideoWidth;
76     private int         mVideoHeight;
77     private int         mSurfaceWidth;
78     private int         mSurfaceHeight;
79     private MediaController mMediaController;
80     private OnCompletionListener mOnCompletionListener;
81     private MediaPlayer.OnPreparedListener mOnPreparedListener;
82     private int         mCurrentBufferPercentage;
83     private OnErrorListener mOnErrorListener;
84     private int         mSeekWhenPrepared;  // recording the seek position while preparing
85     private boolean     mCanPause;
86     private boolean     mCanSeekBack;
87     private boolean     mCanSeekForward;
88 
VideoView(Context context)89     public VideoView(Context context) {
90         super(context);
91         initVideoView();
92     }
93 
VideoView(Context context, AttributeSet attrs)94     public VideoView(Context context, AttributeSet attrs) {
95         this(context, attrs, 0);
96         initVideoView();
97     }
98 
VideoView(Context context, AttributeSet attrs, int defStyle)99     public VideoView(Context context, AttributeSet attrs, int defStyle) {
100         super(context, attrs, defStyle);
101         initVideoView();
102     }
103 
104     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)105     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
106         //Log.i("@@@@", "onMeasure");
107         int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
108         int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
109         if (mVideoWidth > 0 && mVideoHeight > 0) {
110             if ( mVideoWidth * height  > width * mVideoHeight ) {
111                 //Log.i("@@@", "image too tall, correcting");
112                 height = width * mVideoHeight / mVideoWidth;
113             } else if ( mVideoWidth * height  < width * mVideoHeight ) {
114                 //Log.i("@@@", "image too wide, correcting");
115                 width = height * mVideoWidth / mVideoHeight;
116             } else {
117                 //Log.i("@@@", "aspect ratio is correct: " +
118                         //width+"/"+height+"="+
119                         //mVideoWidth+"/"+mVideoHeight);
120             }
121         }
122         //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
123         setMeasuredDimension(width, height);
124     }
125 
resolveAdjustedSize(int desiredSize, int measureSpec)126     public int resolveAdjustedSize(int desiredSize, int measureSpec) {
127         int result = desiredSize;
128         int specMode = MeasureSpec.getMode(measureSpec);
129         int specSize =  MeasureSpec.getSize(measureSpec);
130 
131         switch (specMode) {
132             case MeasureSpec.UNSPECIFIED:
133                 /* Parent says we can be as big as we want. Just don't be larger
134                  * than max size imposed on ourselves.
135                  */
136                 result = desiredSize;
137                 break;
138 
139             case MeasureSpec.AT_MOST:
140                 /* Parent says we can be as big as we want, up to specSize.
141                  * Don't be larger than specSize, and don't be larger than
142                  * the max size imposed on ourselves.
143                  */
144                 result = Math.min(desiredSize, specSize);
145                 break;
146 
147             case MeasureSpec.EXACTLY:
148                 // No choice. Do what we are told.
149                 result = specSize;
150                 break;
151         }
152         return result;
153 }
154 
initVideoView()155     private void initVideoView() {
156         mVideoWidth = 0;
157         mVideoHeight = 0;
158         getHolder().addCallback(mSHCallback);
159         getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
160         setFocusable(true);
161         setFocusableInTouchMode(true);
162         requestFocus();
163         mCurrentState = STATE_IDLE;
164         mTargetState  = STATE_IDLE;
165     }
166 
setVideoPath(String path)167     public void setVideoPath(String path) {
168         setVideoURI(Uri.parse(path));
169     }
170 
setVideoURI(Uri uri)171     public void setVideoURI(Uri uri) {
172         mUri = uri;
173         mSeekWhenPrepared = 0;
174         openVideo();
175         requestLayout();
176         invalidate();
177     }
178 
stopPlayback()179     public void stopPlayback() {
180         if (mMediaPlayer != null) {
181             mMediaPlayer.stop();
182             mMediaPlayer.release();
183             mMediaPlayer = null;
184             mCurrentState = STATE_IDLE;
185             mTargetState  = STATE_IDLE;
186         }
187     }
188 
openVideo()189     private void openVideo() {
190         if (mUri == null || mSurfaceHolder == null) {
191             // not ready for playback just yet, will try again later
192             return;
193         }
194         // Tell the music playback service to pause
195         // TODO: these constants need to be published somewhere in the framework.
196         Intent i = new Intent("com.android.music.musicservicecommand");
197         i.putExtra("command", "pause");
198         mContext.sendBroadcast(i);
199 
200         // we shouldn't clear the target state, because somebody might have
201         // called start() previously
202         release(false);
203         try {
204             mMediaPlayer = new MediaPlayer();
205             mMediaPlayer.setOnPreparedListener(mPreparedListener);
206             mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
207             mDuration = -1;
208             mMediaPlayer.setOnCompletionListener(mCompletionListener);
209             mMediaPlayer.setOnErrorListener(mErrorListener);
210             mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
211             mCurrentBufferPercentage = 0;
212             mMediaPlayer.setDataSource(mContext, mUri);
213             mMediaPlayer.setDisplay(mSurfaceHolder);
214             mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
215             mMediaPlayer.setScreenOnWhilePlaying(true);
216             mMediaPlayer.prepareAsync();
217             // we don't set the target state here either, but preserve the
218             // target state that was there before.
219             mCurrentState = STATE_PREPARING;
220             attachMediaController();
221         } catch (IOException ex) {
222             Log.w(TAG, "Unable to open content: " + mUri, ex);
223             mCurrentState = STATE_ERROR;
224             mTargetState = STATE_ERROR;
225             mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
226             return;
227         } catch (IllegalArgumentException ex) {
228             Log.w(TAG, "Unable to open content: " + mUri, ex);
229             mCurrentState = STATE_ERROR;
230             mTargetState = STATE_ERROR;
231             mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
232             return;
233         }
234     }
235 
setMediaController(MediaController controller)236     public void setMediaController(MediaController controller) {
237         if (mMediaController != null) {
238             mMediaController.hide();
239         }
240         mMediaController = controller;
241         attachMediaController();
242     }
243 
attachMediaController()244     private void attachMediaController() {
245         if (mMediaPlayer != null && mMediaController != null) {
246             mMediaController.setMediaPlayer(this);
247             View anchorView = this.getParent() instanceof View ?
248                     (View)this.getParent() : this;
249             mMediaController.setAnchorView(anchorView);
250             mMediaController.setEnabled(isInPlaybackState());
251         }
252     }
253 
254     MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
255         new MediaPlayer.OnVideoSizeChangedListener() {
256             public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
257                 mVideoWidth = mp.getVideoWidth();
258                 mVideoHeight = mp.getVideoHeight();
259                 if (mVideoWidth != 0 && mVideoHeight != 0) {
260                     getHolder().setFixedSize(mVideoWidth, mVideoHeight);
261                 }
262             }
263     };
264 
265     MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
266         public void onPrepared(MediaPlayer mp) {
267             mCurrentState = STATE_PREPARED;
268 
269             // Get the capabilities of the player for this stream
270             Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
271                                       MediaPlayer.BYPASS_METADATA_FILTER);
272 
273             if (data != null) {
274                 mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
275                         || data.getBoolean(Metadata.PAUSE_AVAILABLE);
276                 mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
277                         || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
278                 mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
279                         || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
280             } else {
281                 mCanPause = mCanSeekForward = mCanSeekForward = true;
282             }
283 
284             if (mOnPreparedListener != null) {
285                 mOnPreparedListener.onPrepared(mMediaPlayer);
286             }
287             if (mMediaController != null) {
288                 mMediaController.setEnabled(true);
289             }
290             mVideoWidth = mp.getVideoWidth();
291             mVideoHeight = mp.getVideoHeight();
292 
293             int seekToPosition = mSeekWhenPrepared;  // mSeekWhenPrepared may be changed after seekTo() call
294             if (seekToPosition != 0) {
295                 seekTo(seekToPosition);
296             }
297             if (mVideoWidth != 0 && mVideoHeight != 0) {
298                 //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
299                 getHolder().setFixedSize(mVideoWidth, mVideoHeight);
300                 if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
301                     // We didn't actually change the size (it was already at the size
302                     // we need), so we won't get a "surface changed" callback, so
303                     // start the video here instead of in the callback.
304                     if (mTargetState == STATE_PLAYING) {
305                         start();
306                         if (mMediaController != null) {
307                             mMediaController.show();
308                         }
309                     } else if (!isPlaying() &&
310                                (seekToPosition != 0 || getCurrentPosition() > 0)) {
311                        if (mMediaController != null) {
312                            // Show the media controls when we're paused into a video and make 'em stick.
313                            mMediaController.show(0);
314                        }
315                    }
316                 }
317             } else {
318                 // We don't know the video size yet, but should start anyway.
319                 // The video size might be reported to us later.
320                 if (mTargetState == STATE_PLAYING) {
321                     start();
322                 }
323             }
324         }
325     };
326 
327     private MediaPlayer.OnCompletionListener mCompletionListener =
328         new MediaPlayer.OnCompletionListener() {
329         public void onCompletion(MediaPlayer mp) {
330             mCurrentState = STATE_PLAYBACK_COMPLETED;
331             mTargetState = STATE_PLAYBACK_COMPLETED;
332             if (mMediaController != null) {
333                 mMediaController.hide();
334             }
335             if (mOnCompletionListener != null) {
336                 mOnCompletionListener.onCompletion(mMediaPlayer);
337             }
338         }
339     };
340 
341     private MediaPlayer.OnErrorListener mErrorListener =
342         new MediaPlayer.OnErrorListener() {
343         public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {
344             Log.d(TAG, "Error: " + framework_err + "," + impl_err);
345             mCurrentState = STATE_ERROR;
346             mTargetState = STATE_ERROR;
347             if (mMediaController != null) {
348                 mMediaController.hide();
349             }
350 
351             /* If an error handler has been supplied, use it and finish. */
352             if (mOnErrorListener != null) {
353                 if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {
354                     return true;
355                 }
356             }
357 
358             /* Otherwise, pop up an error dialog so the user knows that
359              * something bad has happened. Only try and pop up the dialog
360              * if we're attached to a window. When we're going away and no
361              * longer have a window, don't bother showing the user an error.
362              */
363             if (getWindowToken() != null) {
364                 Resources r = mContext.getResources();
365                 int messageId;
366 
367                 if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
368                     messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback;
369                 } else {
370                     messageId = com.android.internal.R.string.VideoView_error_text_unknown;
371                 }
372 
373                 new AlertDialog.Builder(mContext)
374                         .setTitle(com.android.internal.R.string.VideoView_error_title)
375                         .setMessage(messageId)
376                         .setPositiveButton(com.android.internal.R.string.VideoView_error_button,
377                                 new DialogInterface.OnClickListener() {
378                                     public void onClick(DialogInterface dialog, int whichButton) {
379                                         /* If we get here, there is no onError listener, so
380                                          * at least inform them that the video is over.
381                                          */
382                                         if (mOnCompletionListener != null) {
383                                             mOnCompletionListener.onCompletion(mMediaPlayer);
384                                         }
385                                     }
386                                 })
387                         .setCancelable(false)
388                         .show();
389             }
390             return true;
391         }
392     };
393 
394     private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
395         new MediaPlayer.OnBufferingUpdateListener() {
396         public void onBufferingUpdate(MediaPlayer mp, int percent) {
397             mCurrentBufferPercentage = percent;
398         }
399     };
400 
401     /**
402      * Register a callback to be invoked when the media file
403      * is loaded and ready to go.
404      *
405      * @param l The callback that will be run
406      */
setOnPreparedListener(MediaPlayer.OnPreparedListener l)407     public void setOnPreparedListener(MediaPlayer.OnPreparedListener l)
408     {
409         mOnPreparedListener = l;
410     }
411 
412     /**
413      * Register a callback to be invoked when the end of a media file
414      * has been reached during playback.
415      *
416      * @param l The callback that will be run
417      */
setOnCompletionListener(OnCompletionListener l)418     public void setOnCompletionListener(OnCompletionListener l)
419     {
420         mOnCompletionListener = l;
421     }
422 
423     /**
424      * Register a callback to be invoked when an error occurs
425      * during playback or setup.  If no listener is specified,
426      * or if the listener returned false, VideoView will inform
427      * the user of any errors.
428      *
429      * @param l The callback that will be run
430      */
setOnErrorListener(OnErrorListener l)431     public void setOnErrorListener(OnErrorListener l)
432     {
433         mOnErrorListener = l;
434     }
435 
436     SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
437     {
438         public void surfaceChanged(SurfaceHolder holder, int format,
439                                     int w, int h)
440         {
441             mSurfaceWidth = w;
442             mSurfaceHeight = h;
443             boolean isValidState =  (mTargetState == STATE_PLAYING);
444             boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);
445             if (mMediaPlayer != null && isValidState && hasValidSize) {
446                 if (mSeekWhenPrepared != 0) {
447                     seekTo(mSeekWhenPrepared);
448                 }
449                 start();
450                 if (mMediaController != null) {
451                     mMediaController.show();
452                 }
453             }
454         }
455 
456         public void surfaceCreated(SurfaceHolder holder)
457         {
458             mSurfaceHolder = holder;
459             openVideo();
460         }
461 
462         public void surfaceDestroyed(SurfaceHolder holder)
463         {
464             // after we return from this we can't use the surface any more
465             mSurfaceHolder = null;
466             if (mMediaController != null) mMediaController.hide();
467             release(true);
468         }
469     };
470 
471     /*
472      * release the media player in any state
473      */
release(boolean cleartargetstate)474     private void release(boolean cleartargetstate) {
475         if (mMediaPlayer != null) {
476             mMediaPlayer.reset();
477             mMediaPlayer.release();
478             mMediaPlayer = null;
479             mCurrentState = STATE_IDLE;
480             if (cleartargetstate) {
481                 mTargetState  = STATE_IDLE;
482             }
483         }
484     }
485 
486     @Override
onTouchEvent(MotionEvent ev)487     public boolean onTouchEvent(MotionEvent ev) {
488         if (isInPlaybackState() && mMediaController != null) {
489             toggleMediaControlsVisiblity();
490         }
491         return false;
492     }
493 
494     @Override
onTrackballEvent(MotionEvent ev)495     public boolean onTrackballEvent(MotionEvent ev) {
496         if (isInPlaybackState() && mMediaController != null) {
497             toggleMediaControlsVisiblity();
498         }
499         return false;
500     }
501 
502     @Override
onKeyDown(int keyCode, KeyEvent event)503     public boolean onKeyDown(int keyCode, KeyEvent event)
504     {
505         boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
506                                      keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
507                                      keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
508                                      keyCode != KeyEvent.KEYCODE_MENU &&
509                                      keyCode != KeyEvent.KEYCODE_CALL &&
510                                      keyCode != KeyEvent.KEYCODE_ENDCALL;
511         if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
512             if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
513                     keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
514                 if (mMediaPlayer.isPlaying()) {
515                     pause();
516                     mMediaController.show();
517                 } else {
518                     start();
519                     mMediaController.hide();
520                 }
521                 return true;
522             } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
523                     && mMediaPlayer.isPlaying()) {
524                 pause();
525                 mMediaController.show();
526             } else {
527                 toggleMediaControlsVisiblity();
528             }
529         }
530 
531         return super.onKeyDown(keyCode, event);
532     }
533 
toggleMediaControlsVisiblity()534     private void toggleMediaControlsVisiblity() {
535         if (mMediaController.isShowing()) {
536             mMediaController.hide();
537         } else {
538             mMediaController.show();
539         }
540     }
541 
start()542     public void start() {
543         if (isInPlaybackState()) {
544             mMediaPlayer.start();
545             mCurrentState = STATE_PLAYING;
546         }
547         mTargetState = STATE_PLAYING;
548     }
549 
pause()550     public void pause() {
551         if (isInPlaybackState()) {
552             if (mMediaPlayer.isPlaying()) {
553                 mMediaPlayer.pause();
554                 mCurrentState = STATE_PAUSED;
555             }
556         }
557         mTargetState = STATE_PAUSED;
558     }
559 
560     // cache duration as mDuration for faster access
getDuration()561     public int getDuration() {
562         if (isInPlaybackState()) {
563             if (mDuration > 0) {
564                 return mDuration;
565             }
566             mDuration = mMediaPlayer.getDuration();
567             return mDuration;
568         }
569         mDuration = -1;
570         return mDuration;
571     }
572 
getCurrentPosition()573     public int getCurrentPosition() {
574         if (isInPlaybackState()) {
575             return mMediaPlayer.getCurrentPosition();
576         }
577         return 0;
578     }
579 
seekTo(int msec)580     public void seekTo(int msec) {
581         if (isInPlaybackState()) {
582             mMediaPlayer.seekTo(msec);
583             mSeekWhenPrepared = 0;
584         } else {
585             mSeekWhenPrepared = msec;
586         }
587     }
588 
isPlaying()589     public boolean isPlaying() {
590         return isInPlaybackState() && mMediaPlayer.isPlaying();
591     }
592 
getBufferPercentage()593     public int getBufferPercentage() {
594         if (mMediaPlayer != null) {
595             return mCurrentBufferPercentage;
596         }
597         return 0;
598     }
599 
isInPlaybackState()600     private boolean isInPlaybackState() {
601         return (mMediaPlayer != null &&
602                 mCurrentState != STATE_ERROR &&
603                 mCurrentState != STATE_IDLE &&
604                 mCurrentState != STATE_PREPARING);
605     }
606 
canPause()607     public boolean canPause() {
608         return mCanPause;
609     }
610 
canSeekBackward()611     public boolean canSeekBackward() {
612         return mCanSeekBack;
613     }
614 
canSeekForward()615     public boolean canSeekForward() {
616         return mCanSeekForward;
617     }
618 }
619