Java tutorial
/* * 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 java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import org.apache.http.client.ClientProtocolException; import ti.modules.titanium.media.MediaModule; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.AssetFileDescriptor; 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.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.webkit.URLUtil; import android.widget.MediaController.MediaPlayerControl; /** * 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 TiVideoView4 extends SurfaceView implements MediaPlayerControl { private String TAG = "VideoView"; // settable by the client private Uri mUri; private int mDuration; // All the stuff we need for playing and showing a video private SurfaceHolder mSurfaceHolder = null; private MediaPlayer mMediaPlayer = null; private boolean mIsPrepared; private boolean mIsPlaybackCompleted; 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 TiVideoView4(Context context) { super(context); initVideoView(); } public TiVideoView4(Context context, AttributeSet attrs) { this(context, attrs, 0); initVideoView(); } public TiVideoView4(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initVideoView(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { measureVideo(mVideoWidth, mVideoHeight, widthMeasureSpec, heightMeasureSpec); if (mSurfaceHolder != null && mMediaPlayer != null && mMediaPlayer.getCurrentPosition() > 0) { mSurfaceHolder.setFixedSize(getMeasuredWidth(), getMeasuredHeight()); } } protected void measureVideo(int videoWidth, int videoHeight, int widthMeasureSpec, int heightMeasureSpec) { //Log.i("@@@@", "onMeasure"); int width = getDefaultSize(videoWidth, widthMeasureSpec); int height = getDefaultSize(videoHeight, heightMeasureSpec); if (videoWidth > 0 && videoHeight > 0) { if (videoWidth * height > width * videoHeight) { //Log.i("@@@", "image too tall, correcting"); height = width * videoHeight / videoWidth; } else if (videoWidth * height < width * videoHeight) { //Log.i("@@@", "image too wide, correcting"); width = height * videoWidth / videoHeight; } 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; } } // Added to detect HTTP redirects before we hand off to MediaPlayer // See: http://code.google.com/p/android/issues/detail?id=10810 private void setDataSource() { try { if (mUri.getScheme().equals("http") || mUri.getScheme().equals("https")) { // Media player doesn't handle redirects, try to follow them here while (true) { // java.net.URL doesn't handle rtsp if (mUri.getScheme() != null && mUri.getScheme().equals("rtsp")) break; URL url = new URL(mUri.toString()); HttpURLConnection cn = (HttpURLConnection) url.openConnection(); cn.setInstanceFollowRedirects(false); String location = cn.getHeaderField("Location"); if (location != null) { String host = mUri.getHost(); int port = mUri.getPort(); String scheme = mUri.getScheme(); mUri = Uri.parse(location); if (mUri.getScheme() == null) { // Absolute URL on existing host/port/scheme if (scheme == null) { scheme = "http"; } String authority = port == -1 ? host : host + ":" + port; mUri = mUri.buildUpon().scheme(scheme).encodedAuthority(authority).build(); } } else { break; } } } mMediaPlayer.setDataSource(getContext(), mUri); } catch (ClientProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } 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"); getContext().sendBroadcast(i); if (mMediaPlayer != null) { mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; } try { mMediaPlayer = new MediaPlayer(); mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); mIsPrepared = false; Log.v(TAG, "reset duration to -1 in openVideo"); mDuration = -1; mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); mCurrentBufferPercentage = 0; if (URLUtil.isAssetUrl(mUri.toString())) { // DST: 20090606 detect asset url AssetFileDescriptor afd = null; try { String path = mUri.toString().substring("file:///android_asset/".length()); afd = getContext().getAssets().openFd(path); mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); } finally { if (afd != null) { afd.close(); } } } else { setDataSource(); } mMediaPlayer.setDisplay(mSurfaceHolder); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setScreenOnWhilePlaying(true); mMediaPlayer.prepareAsync(); attachMediaController(); } catch (IOException ex) { Log.w(TAG, "Unable to open content: " + mUri, ex); return; } catch (IllegalArgumentException ex) { Log.w(TAG, "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) { 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) { start(); mStartWhenPrepared = false; } } } }; private MediaPlayer.OnCompletionListener mCompletionListener = new MediaPlayer.OnCompletionListener() { public void onCompletion(MediaPlayer mp) { mIsPlaybackCompleted = true; if (mMediaController != null) { mMediaController.hide(); } if (mOnCompletionListener != null) { mOnCompletionListener.onCompletion(mMediaPlayer); } } }; private MediaPlayer.OnErrorListener mErrorListener = new MediaPlayer.OnErrorListener() { public boolean onError(MediaPlayer mp, int framework_err, int impl_err) { Log.d(TAG, "Error: " + framework_err + "," + impl_err); if (mMediaController != null) { mMediaController.hide(); } /* If an error handler has been supplied, use it and finish. */ if (mOnErrorListener != null) { if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) { 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 = getContext().getResources(); String message; if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { message = "Invalid Progressive Playback"; //messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback; } else { message = "Unknown error"; //messageId = com.android.internal.R.string.VideoView_error_text_unknown; } new AlertDialog.Builder(getContext()).setTitle("Video View").setMessage(message) .setPositiveButton("Error", 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; } if (!mIsPlaybackCompleted) { 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_MEDIA_PLAY_PAUSE) { if (mMediaPlayer.isPlaying()) { pause(); mMediaController.show(); } else { start(); mMediaController.hide(); } return true; } else if (keyCode == KeyEvent.KEYCODE_MEDIA_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() { mIsPlaybackCompleted = false; 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) { if (mDuration > 0) { return mDuration; } mDuration = mMediaPlayer.getDuration(); return mDuration; } mDuration = -1; return mDuration; } 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; } // adding stub in order to satisfy building against API level 7 public boolean canPause() { return true; } // adding stub in order to satisfy building against API level 7 public boolean canSeekBackward() { return true; } // adding stub in order to satisfy building against API level 7 public boolean canSeekForward() { return true; } }