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