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