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