From 9066cfe9886ac131c34d59ed0e2d287b0e3c0087 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 3 Mar 2009 19:31:44 -0800 Subject: auto import from //depot/cupcake/@135843 --- core/java/android/widget/VideoView.java | 533 ++++++++++++++++++++++++++++++++ 1 file changed, 533 insertions(+) create mode 100644 core/java/android/widget/VideoView.java (limited to 'core/java/android/widget/VideoView.java') diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java new file mode 100644 index 0000000..1227afd --- /dev/null +++ b/core/java/android/widget/VideoView.java @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; +import android.net.Uri; +import android.os.PowerManager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.widget.MediaController.MediaPlayerControl; + +import java.io.IOException; + +/** + * Displays a video file. The VideoView class + * can load images from various sources (such as resources or content + * providers), takes care of computing its measurement from the video so that + * it can be used in any layout manager, and provides various display options + * such as scaling and tinting. + */ +public class VideoView extends SurfaceView implements MediaPlayerControl { + // settable by the client + private Uri mUri; + + // All the stuff we need for playing and showing a video + private SurfaceHolder mSurfaceHolder = null; + private MediaPlayer mMediaPlayer = null; + private boolean mIsPrepared; + private int mVideoWidth; + private int mVideoHeight; + private int mSurfaceWidth; + private int mSurfaceHeight; + private MediaController mMediaController; + private OnCompletionListener mOnCompletionListener; + private MediaPlayer.OnPreparedListener mOnPreparedListener; + private int mCurrentBufferPercentage; + private OnErrorListener mOnErrorListener; + private boolean mStartWhenPrepared; + private int mSeekWhenPrepared; + + public VideoView(Context context) { + super(context); + initVideoView(); + } + + public VideoView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + initVideoView(); + } + + public VideoView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + initVideoView(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + //Log.i("@@@@", "onMeasure"); + int width = getDefaultSize(mVideoWidth, widthMeasureSpec); + int height = getDefaultSize(mVideoHeight, heightMeasureSpec); + if (mVideoWidth > 0 && mVideoHeight > 0) { + if ( mVideoWidth * height > width * mVideoHeight ) { + //Log.i("@@@", "image too tall, correcting"); + height = width * mVideoHeight / mVideoWidth; + } else if ( mVideoWidth * height < width * mVideoHeight ) { + //Log.i("@@@", "image too wide, correcting"); + width = height * mVideoWidth / mVideoHeight; + } else { + //Log.i("@@@", "aspect ratio is correct: " + + //width+"/"+height+"="+ + //mVideoWidth+"/"+mVideoHeight); + } + } + //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height); + setMeasuredDimension(width, height); + } + + public int resolveAdjustedSize(int desiredSize, int measureSpec) { + int result = desiredSize; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + /* Parent says we can be as big as we want. Just don't be larger + * than max size imposed on ourselves. + */ + result = desiredSize; + break; + + case MeasureSpec.AT_MOST: + /* Parent says we can be as big as we want, up to specSize. + * Don't be larger than specSize, and don't be larger than + * the max size imposed on ourselves. + */ + result = Math.min(desiredSize, specSize); + break; + + case MeasureSpec.EXACTLY: + // No choice. Do what we are told. + result = specSize; + break; + } + return result; +} + + private void initVideoView() { + mVideoWidth = 0; + mVideoHeight = 0; + getHolder().addCallback(mSHCallback); + getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + } + + public void setVideoPath(String path) { + setVideoURI(Uri.parse(path)); + } + + public void setVideoURI(Uri uri) { + mUri = uri; + mStartWhenPrepared = false; + mSeekWhenPrepared = 0; + openVideo(); + requestLayout(); + invalidate(); + } + + public void stopPlayback() { + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + } + } + + private void openVideo() { + if (mUri == null || mSurfaceHolder == null) { + // not ready for playback just yet, will try again later + return; + } + // Tell the music playback service to pause + // TODO: these constants need to be published somewhere in the framework. + Intent i = new Intent("com.android.music.musicservicecommand"); + i.putExtra("command", "pause"); + mContext.sendBroadcast(i); + + if (mMediaPlayer != null) { + mMediaPlayer.reset(); + mMediaPlayer.release(); + mMediaPlayer = null; + } + try { + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setOnPreparedListener(mPreparedListener); + mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); + mIsPrepared = false; + mMediaPlayer.setOnCompletionListener(mCompletionListener); + mMediaPlayer.setOnErrorListener(mErrorListener); + mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); + mCurrentBufferPercentage = 0; + mMediaPlayer.setDataSource(mContext, mUri); + mMediaPlayer.setDisplay(mSurfaceHolder); + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mMediaPlayer.setScreenOnWhilePlaying(true); + mMediaPlayer.prepareAsync(); + attachMediaController(); + } catch (IOException ex) { + Log.w("VideoView", "Unable to open content: " + mUri, ex); + return; + } catch (IllegalArgumentException ex) { + Log.w("VideoView", "Unable to open content: " + mUri, ex); + return; + } + } + + public void setMediaController(MediaController controller) { + if (mMediaController != null) { + mMediaController.hide(); + } + mMediaController = controller; + attachMediaController(); + } + + private void attachMediaController() { + if (mMediaPlayer != null && mMediaController != null) { + mMediaController.setMediaPlayer(this); + View anchorView = this.getParent() instanceof View ? + (View)this.getParent() : this; + mMediaController.setAnchorView(anchorView); + mMediaController.setEnabled(mIsPrepared); + } + } + + MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = + new MediaPlayer.OnVideoSizeChangedListener() { + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + if (mVideoWidth != 0 && mVideoHeight != 0) { + getHolder().setFixedSize(mVideoWidth, mVideoHeight); + } + } + }; + + MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { + public void onPrepared(MediaPlayer mp) { + // briefly show the mediacontroller + mIsPrepared = true; + if (mOnPreparedListener != null) { + mOnPreparedListener.onPrepared(mMediaPlayer); + } + if (mMediaController != null) { + mMediaController.setEnabled(true); + } + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + if (mVideoWidth != 0 && mVideoHeight != 0) { + //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight); + getHolder().setFixedSize(mVideoWidth, mVideoHeight); + if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) { + // We didn't actually change the size (it was already at the size + // we need), so we won't get a "surface changed" callback, so + // start the video here instead of in the callback. + if (mSeekWhenPrepared != 0) { + mMediaPlayer.seekTo(mSeekWhenPrepared); + mSeekWhenPrepared = 0; + } + if (mStartWhenPrepared) { + mMediaPlayer.start(); + mStartWhenPrepared = false; + if (mMediaController != null) { + mMediaController.show(); + } + } else if (!isPlaying() && + (mSeekWhenPrepared != 0 || getCurrentPosition() > 0)) { + if (mMediaController != null) { + // Show the media controls when we're paused into a video and make 'em stick. + mMediaController.show(0); + } + } + } + } else { + // We don't know the video size yet, but should start anyway. + // The video size might be reported to us later. + if (mSeekWhenPrepared != 0) { + mMediaPlayer.seekTo(mSeekWhenPrepared); + mSeekWhenPrepared = 0; + } + if (mStartWhenPrepared) { + mMediaPlayer.start(); + mStartWhenPrepared = false; + } + } + } + }; + + private MediaPlayer.OnCompletionListener mCompletionListener = + new MediaPlayer.OnCompletionListener() { + public void onCompletion(MediaPlayer mp) { + if (mMediaController != null) { + mMediaController.hide(); + } + if (mOnCompletionListener != null) { + mOnCompletionListener.onCompletion(mMediaPlayer); + } + } + }; + + private MediaPlayer.OnErrorListener mErrorListener = + new MediaPlayer.OnErrorListener() { + public boolean onError(MediaPlayer mp, int a, int b) { + Log.d("VideoView", "Error: " + a + "," + b); + if (mMediaController != null) { + mMediaController.hide(); + } + + /* If an error handler has been supplied, use it and finish. */ + if (mOnErrorListener != null) { + if (mOnErrorListener.onError(mMediaPlayer, a, b)) { + return true; + } + } + + /* Otherwise, pop up an error dialog so the user knows that + * something bad has happened. Only try and pop up the dialog + * if we're attached to a window. When we're going away and no + * longer have a window, don't bother showing the user an error. + */ + if (getWindowToken() != null) { + Resources r = mContext.getResources(); + new AlertDialog.Builder(mContext) + .setTitle(com.android.internal.R.string.VideoView_error_title) + .setMessage(com.android.internal.R.string.VideoView_error_text_unknown) + .setPositiveButton(com.android.internal.R.string.VideoView_error_button, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + /* If we get here, there is no onError listener, so + * at least inform them that the video is over. + */ + if (mOnCompletionListener != null) { + mOnCompletionListener.onCompletion(mMediaPlayer); + } + } + }) + .setCancelable(false) + .show(); + } + return true; + } + }; + + private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = + new MediaPlayer.OnBufferingUpdateListener() { + public void onBufferingUpdate(MediaPlayer mp, int percent) { + mCurrentBufferPercentage = percent; + } + }; + + /** + * Register a callback to be invoked when the media file + * is loaded and ready to go. + * + * @param l The callback that will be run + */ + public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) + { + mOnPreparedListener = l; + } + + /** + * Register a callback to be invoked when the end of a media file + * has been reached during playback. + * + * @param l The callback that will be run + */ + public void setOnCompletionListener(OnCompletionListener l) + { + mOnCompletionListener = l; + } + + /** + * Register a callback to be invoked when an error occurs + * during playback or setup. If no listener is specified, + * or if the listener returned false, VideoView will inform + * the user of any errors. + * + * @param l The callback that will be run + */ + public void setOnErrorListener(OnErrorListener l) + { + mOnErrorListener = l; + } + + SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() + { + public void surfaceChanged(SurfaceHolder holder, int format, + int w, int h) + { + mSurfaceWidth = w; + mSurfaceHeight = h; + if (mMediaPlayer != null && mIsPrepared && mVideoWidth == w && mVideoHeight == h) { + if (mSeekWhenPrepared != 0) { + mMediaPlayer.seekTo(mSeekWhenPrepared); + mSeekWhenPrepared = 0; + } + mMediaPlayer.start(); + if (mMediaController != null) { + mMediaController.show(); + } + } + } + + public void surfaceCreated(SurfaceHolder holder) + { + mSurfaceHolder = holder; + openVideo(); + } + + public void surfaceDestroyed(SurfaceHolder holder) + { + // after we return from this we can't use the surface any more + mSurfaceHolder = null; + if (mMediaController != null) mMediaController.hide(); + if (mMediaPlayer != null) { + mMediaPlayer.reset(); + mMediaPlayer.release(); + mMediaPlayer = null; + } + } + }; + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (mIsPrepared && mMediaPlayer != null && mMediaController != null) { + toggleMediaControlsVisiblity(); + } + return false; + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + if (mIsPrepared && mMediaPlayer != null && mMediaController != null) { + toggleMediaControlsVisiblity(); + } + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) + { + if (mIsPrepared && + keyCode != KeyEvent.KEYCODE_BACK && + keyCode != KeyEvent.KEYCODE_VOLUME_UP && + keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && + keyCode != KeyEvent.KEYCODE_MENU && + keyCode != KeyEvent.KEYCODE_CALL && + keyCode != KeyEvent.KEYCODE_ENDCALL && + mMediaPlayer != null && + mMediaController != null) { + if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || + keyCode == KeyEvent.KEYCODE_PLAYPAUSE) { + if (mMediaPlayer.isPlaying()) { + pause(); + mMediaController.show(); + } else { + start(); + mMediaController.hide(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_STOP + && mMediaPlayer.isPlaying()) { + pause(); + mMediaController.show(); + } else { + toggleMediaControlsVisiblity(); + } + } + + return super.onKeyDown(keyCode, event); + } + + private void toggleMediaControlsVisiblity() { + if (mMediaController.isShowing()) { + mMediaController.hide(); + } else { + mMediaController.show(); + } + } + + public void start() { + if (mMediaPlayer != null && mIsPrepared) { + mMediaPlayer.start(); + mStartWhenPrepared = false; + } else { + mStartWhenPrepared = true; + } + } + + public void pause() { + if (mMediaPlayer != null && mIsPrepared) { + if (mMediaPlayer.isPlaying()) { + mMediaPlayer.pause(); + } + } + mStartWhenPrepared = false; + } + + public int getDuration() { + if (mMediaPlayer != null && mIsPrepared) { + return mMediaPlayer.getDuration(); + } + return -1; + } + + public int getCurrentPosition() { + if (mMediaPlayer != null && mIsPrepared) { + return mMediaPlayer.getCurrentPosition(); + } + return 0; + } + + public void seekTo(int msec) { + if (mMediaPlayer != null && mIsPrepared) { + mMediaPlayer.seekTo(msec); + } else { + mSeekWhenPrepared = msec; + } + } + + public boolean isPlaying() { + if (mMediaPlayer != null && mIsPrepared) { + return mMediaPlayer.isPlaying(); + } + return false; + } + + public int getBufferPercentage() { + if (mMediaPlayer != null) { + return mCurrentBufferPercentage; + } + return 0; + } +} -- cgit v1.1