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