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 17 package android.widget; 18 19 import android.app.AlertDialog; 20 import android.content.Context; 21 import android.content.DialogInterface; 22 import android.content.Intent; 23 import android.content.res.Resources; 24 import android.graphics.Canvas; 25 import android.media.AudioManager; 26 import android.media.MediaFormat; 27 import android.media.MediaPlayer; 28 import android.media.MediaPlayer.OnCompletionListener; 29 import android.media.MediaPlayer.OnErrorListener; 30 import android.media.MediaPlayer.OnInfoListener; 31 import android.media.Metadata; 32 import android.media.SubtitleController; 33 import android.media.SubtitleTrack.RenderingWidget; 34 import android.media.WebVttRenderer; 35 import android.net.Uri; 36 import android.os.Looper; 37 import android.util.AttributeSet; 38 import android.util.Log; 39 import android.util.Pair; 40 import android.view.KeyEvent; 41 import android.view.MotionEvent; 42 import android.view.SurfaceHolder; 43 import android.view.SurfaceView; 44 import android.view.View; 45 import android.view.accessibility.AccessibilityEvent; 46 import android.view.accessibility.AccessibilityNodeInfo; 47 import android.widget.MediaController.MediaPlayerControl; 48 49 import java.io.IOException; 50 import java.io.InputStream; 51 import java.util.Map; 52 import java.util.Vector; 53 54 /** 55 * Displays a video file. The VideoView class 56 * can load images from various sources (such as resources or content 57 * providers), takes care of computing its measurement from the video so that 58 * it can be used in any layout manager, and provides various display options 59 * such as scaling and tinting.<p> 60 * 61 * <em>Note: VideoView does not retain its full state when going into the 62 * background.</em> In particular, it does not restore the current play state, 63 * play position, selected tracks, or any subtitle tracks added via 64 * {@link #addSubtitleSource addSubtitleSource()}. Applications should 65 * save and restore these on their own in 66 * {@link android.app.Activity#onSaveInstanceState} and 67 * {@link android.app.Activity#onRestoreInstanceState}.<p> 68 * Also note that the audio session id (from {@link #getAudioSessionId}) may 69 * change from its previously returned value when the VideoView is restored. 70 */ 71 public class VideoView extends SurfaceView 72 implements MediaPlayerControl, SubtitleController.Anchor { 73 private String TAG = "VideoView"; 74 // settable by the client 75 private Uri mUri; 76 private Map<String, String> mHeaders; 77 78 // all possible internal states 79 private static final int STATE_ERROR = -1; 80 private static final int STATE_IDLE = 0; 81 private static final int STATE_PREPARING = 1; 82 private static final int STATE_PREPARED = 2; 83 private static final int STATE_PLAYING = 3; 84 private static final int STATE_PAUSED = 4; 85 private static final int STATE_PLAYBACK_COMPLETED = 5; 86 87 // mCurrentState is a VideoView object's current state. 88 // mTargetState is the state that a method caller intends to reach. 89 // For instance, regardless the VideoView object's current state, 90 // calling pause() intends to bring the object to a target state 91 // of STATE_PAUSED. 92 private int mCurrentState = STATE_IDLE; 93 private int mTargetState = STATE_IDLE; 94 95 // All the stuff we need for playing and showing a video 96 private SurfaceHolder mSurfaceHolder = null; 97 private MediaPlayer mMediaPlayer = null; 98 private int mAudioSession; 99 private int mVideoWidth; 100 private int mVideoHeight; 101 private int mSurfaceWidth; 102 private int mSurfaceHeight; 103 private MediaController mMediaController; 104 private OnCompletionListener mOnCompletionListener; 105 private MediaPlayer.OnPreparedListener mOnPreparedListener; 106 private int mCurrentBufferPercentage; 107 private OnErrorListener mOnErrorListener; 108 private OnInfoListener mOnInfoListener; 109 private int mSeekWhenPrepared; // recording the seek position while preparing 110 private boolean mCanPause; 111 private boolean mCanSeekBack; 112 private boolean mCanSeekForward; 113 114 /** Subtitle rendering widget overlaid on top of the video. */ 115 private RenderingWidget mSubtitleWidget; 116 117 /** Listener for changes to subtitle data, used to redraw when needed. */ 118 private RenderingWidget.OnChangedListener mSubtitlesChangedListener; 119 120 public VideoView(Context context) { 121 super(context); 122 initVideoView(); 123 } 124 125 public VideoView(Context context, AttributeSet attrs) { 126 this(context, attrs, 0); 127 initVideoView(); 128 } 129 130 public VideoView(Context context, AttributeSet attrs, int defStyle) { 131 super(context, attrs, defStyle); 132 initVideoView(); 133 } 134 135 @Override 136 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 137 //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", " 138 // + MeasureSpec.toString(heightMeasureSpec) + ")"); 139 140 int width = getDefaultSize(mVideoWidth, widthMeasureSpec); 141 int height = getDefaultSize(mVideoHeight, heightMeasureSpec); 142 if (mVideoWidth > 0 && mVideoHeight > 0) { 143 144 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 145 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 146 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 147 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 148 149 if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) { 150 // the size is fixed 151 width = widthSpecSize; 152 height = heightSpecSize; 153 154 // for compatibility, we adjust size based on aspect ratio 155 if ( mVideoWidth * height < width * mVideoHeight ) { 156 //Log.i("@@@", "image too wide, correcting"); 157 width = height * mVideoWidth / mVideoHeight; 158 } else if ( mVideoWidth * height > width * mVideoHeight ) { 159 //Log.i("@@@", "image too tall, correcting"); 160 height = width * mVideoHeight / mVideoWidth; 161 } 162 } else if (widthSpecMode == MeasureSpec.EXACTLY) { 163 // only the width is fixed, adjust the height to match aspect ratio if possible 164 width = widthSpecSize; 165 height = width * mVideoHeight / mVideoWidth; 166 if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { 167 // couldn't match aspect ratio within the constraints 168 height = heightSpecSize; 169 } 170 } else if (heightSpecMode == MeasureSpec.EXACTLY) { 171 // only the height is fixed, adjust the width to match aspect ratio if possible 172 height = heightSpecSize; 173 width = height * mVideoWidth / mVideoHeight; 174 if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { 175 // couldn't match aspect ratio within the constraints 176 width = widthSpecSize; 177 } 178 } else { 179 // neither the width nor the height are fixed, try to use actual video size 180 width = mVideoWidth; 181 height = mVideoHeight; 182 if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { 183 // too tall, decrease both width and height 184 height = heightSpecSize; 185 width = height * mVideoWidth / mVideoHeight; 186 } 187 if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { 188 // too wide, decrease both width and height 189 width = widthSpecSize; 190 height = width * mVideoHeight / mVideoWidth; 191 } 192 } 193 } else { 194 // no size yet, just adopt the given spec sizes 195 } 196 setMeasuredDimension(width, height); 197 } 198 199 @Override 200 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 201 super.onInitializeAccessibilityEvent(event); 202 event.setClassName(VideoView.class.getName()); 203 } 204 205 @Override 206 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 207 super.onInitializeAccessibilityNodeInfo(info); 208 info.setClassName(VideoView.class.getName()); 209 } 210 211 public int resolveAdjustedSize(int desiredSize, int measureSpec) { 212 return getDefaultSize(desiredSize, measureSpec); 213 } 214 215 private void initVideoView() { 216 mVideoWidth = 0; 217 mVideoHeight = 0; 218 getHolder().addCallback(mSHCallback); 219 getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 220 setFocusable(true); 221 setFocusableInTouchMode(true); 222 requestFocus(); 223 mPendingSubtitleTracks = new Vector<Pair<InputStream, MediaFormat>>(); 224 mCurrentState = STATE_IDLE; 225 mTargetState = STATE_IDLE; 226 } 227 228 public void setVideoPath(String path) { 229 setVideoURI(Uri.parse(path)); 230 } 231 232 public void setVideoURI(Uri uri) { 233 setVideoURI(uri, null); 234 } 235 236 /** 237 * @hide 238 */ 239 public void setVideoURI(Uri uri, Map<String, String> headers) { 240 mUri = uri; 241 mHeaders = headers; 242 mSeekWhenPrepared = 0; 243 openVideo(); 244 requestLayout(); 245 invalidate(); 246 } 247 248 /** 249 * Adds an external subtitle source file (from the provided input stream.) 250 * 251 * Note that a single external subtitle source may contain multiple or no 252 * supported tracks in it. If the source contained at least one track in 253 * it, one will receive an {@link MediaPlayer#MEDIA_INFO_METADATA_UPDATE} 254 * info message. Otherwise, if reading the source takes excessive time, 255 * one will receive a {@link MediaPlayer#MEDIA_INFO_SUBTITLE_TIMED_OUT} 256 * message. If the source contained no supported track (including an empty 257 * source file or null input stream), one will receive a {@link 258 * MediaPlayer#MEDIA_INFO_UNSUPPORTED_SUBTITLE} message. One can find the 259 * total number of available tracks using {@link MediaPlayer#getTrackInfo()} 260 * to see what additional tracks become available after this method call. 261 * 262 * @param is input stream containing the subtitle data. It will be 263 * closed by the media framework. 264 * @param format the format of the subtitle track(s). Must contain at least 265 * the mime type ({@link MediaFormat#KEY_MIME}) and the 266 * language ({@link MediaFormat#KEY_LANGUAGE}) of the file. 267 * If the file itself contains the language information, 268 * specify "und" for the language. 269 */ 270 public void addSubtitleSource(InputStream is, MediaFormat format) { 271 if (mMediaPlayer == null) { 272 mPendingSubtitleTracks.add(Pair.create(is, format)); 273 } else { 274 try { 275 mMediaPlayer.addSubtitleSource(is, format); 276 } catch (IllegalStateException e) { 277 mInfoListener.onInfo( 278 mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0); 279 } 280 } 281 } 282 283 private Vector<Pair<InputStream, MediaFormat>> mPendingSubtitleTracks; 284 285 public void stopPlayback() { 286 if (mMediaPlayer != null) { 287 mMediaPlayer.stop(); 288 mMediaPlayer.release(); 289 mMediaPlayer = null; 290 mCurrentState = STATE_IDLE; 291 mTargetState = STATE_IDLE; 292 } 293 } 294 295 private void openVideo() { 296 if (mUri == null || mSurfaceHolder == null) { 297 // not ready for playback just yet, will try again later 298 return; 299 } 300 // Tell the music playback service to pause 301 // TODO: these constants need to be published somewhere in the framework. 302 Intent i = new Intent("com.android.music.musicservicecommand"); 303 i.putExtra("command", "pause"); 304 mContext.sendBroadcast(i); 305 306 // we shouldn't clear the target state, because somebody might have 307 // called start() previously 308 release(false); 309 try { 310 mMediaPlayer = new MediaPlayer(); 311 // TODO: create SubtitleController in MediaPlayer, but we need 312 // a context for the subtitle renderers 313 final Context context = getContext(); 314 final SubtitleController controller = new SubtitleController( 315 context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer); 316 controller.registerRenderer(new WebVttRenderer(context)); 317 mMediaPlayer.setSubtitleAnchor(controller, this); 318 319 if (mAudioSession != 0) { 320 mMediaPlayer.setAudioSessionId(mAudioSession); 321 } else { 322 mAudioSession = mMediaPlayer.getAudioSessionId(); 323 } 324 mMediaPlayer.setOnPreparedListener(mPreparedListener); 325 mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); 326 mMediaPlayer.setOnCompletionListener(mCompletionListener); 327 mMediaPlayer.setOnErrorListener(mErrorListener); 328 mMediaPlayer.setOnInfoListener(mInfoListener); 329 mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); 330 mCurrentBufferPercentage = 0; 331 mMediaPlayer.setDataSource(mContext, mUri, mHeaders); 332 mMediaPlayer.setDisplay(mSurfaceHolder); 333 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 334 mMediaPlayer.setScreenOnWhilePlaying(true); 335 mMediaPlayer.prepareAsync(); 336 337 for (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) { 338 try { 339 mMediaPlayer.addSubtitleSource(pending.first, pending.second); 340 } catch (IllegalStateException e) { 341 mInfoListener.onInfo( 342 mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0); 343 } 344 } 345 346 // we don't set the target state here either, but preserve the 347 // target state that was there before. 348 mCurrentState = STATE_PREPARING; 349 attachMediaController(); 350 } catch (IOException ex) { 351 Log.w(TAG, "Unable to open content: " + mUri, ex); 352 mCurrentState = STATE_ERROR; 353 mTargetState = STATE_ERROR; 354 mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); 355 return; 356 } catch (IllegalArgumentException ex) { 357 Log.w(TAG, "Unable to open content: " + mUri, ex); 358 mCurrentState = STATE_ERROR; 359 mTargetState = STATE_ERROR; 360 mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); 361 return; 362 } finally { 363 mPendingSubtitleTracks.clear(); 364 } 365 } 366 367 public void setMediaController(MediaController controller) { 368 if (mMediaController != null) { 369 mMediaController.hide(); 370 } 371 mMediaController = controller; 372 attachMediaController(); 373 } 374 375 private void attachMediaController() { 376 if (mMediaPlayer != null && mMediaController != null) { 377 mMediaController.setMediaPlayer(this); 378 View anchorView = this.getParent() instanceof View ? 379 (View)this.getParent() : this; 380 mMediaController.setAnchorView(anchorView); 381 mMediaController.setEnabled(isInPlaybackState()); 382 } 383 } 384 385 MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = 386 new MediaPlayer.OnVideoSizeChangedListener() { 387 public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { 388 mVideoWidth = mp.getVideoWidth(); 389 mVideoHeight = mp.getVideoHeight(); 390 if (mVideoWidth != 0 && mVideoHeight != 0) { 391 getHolder().setFixedSize(mVideoWidth, mVideoHeight); 392 requestLayout(); 393 } 394 } 395 }; 396 397 MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { 398 public void onPrepared(MediaPlayer mp) { 399 mCurrentState = STATE_PREPARED; 400 401 // Get the capabilities of the player for this stream 402 Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL, 403 MediaPlayer.BYPASS_METADATA_FILTER); 404 405 if (data != null) { 406 mCanPause = !data.has(Metadata.PAUSE_AVAILABLE) 407 || data.getBoolean(Metadata.PAUSE_AVAILABLE); 408 mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE) 409 || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE); 410 mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE) 411 || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE); 412 } else { 413 mCanPause = mCanSeekBack = mCanSeekForward = true; 414 } 415 416 if (mOnPreparedListener != null) { 417 mOnPreparedListener.onPrepared(mMediaPlayer); 418 } 419 if (mMediaController != null) { 420 mMediaController.setEnabled(true); 421 } 422 mVideoWidth = mp.getVideoWidth(); 423 mVideoHeight = mp.getVideoHeight(); 424 425 int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call 426 if (seekToPosition != 0) { 427 seekTo(seekToPosition); 428 } 429 if (mVideoWidth != 0 && mVideoHeight != 0) { 430 //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight); 431 getHolder().setFixedSize(mVideoWidth, mVideoHeight); 432 if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) { 433 // We didn't actually change the size (it was already at the size 434 // we need), so we won't get a "surface changed" callback, so 435 // start the video here instead of in the callback. 436 if (mTargetState == STATE_PLAYING) { 437 start(); 438 if (mMediaController != null) { 439 mMediaController.show(); 440 } 441 } else if (!isPlaying() && 442 (seekToPosition != 0 || getCurrentPosition() > 0)) { 443 if (mMediaController != null) { 444 // Show the media controls when we're paused into a video and make 'em stick. 445 mMediaController.show(0); 446 } 447 } 448 } 449 } else { 450 // We don't know the video size yet, but should start anyway. 451 // The video size might be reported to us later. 452 if (mTargetState == STATE_PLAYING) { 453 start(); 454 } 455 } 456 } 457 }; 458 459 private MediaPlayer.OnCompletionListener mCompletionListener = 460 new MediaPlayer.OnCompletionListener() { 461 public void onCompletion(MediaPlayer mp) { 462 mCurrentState = STATE_PLAYBACK_COMPLETED; 463 mTargetState = STATE_PLAYBACK_COMPLETED; 464 if (mMediaController != null) { 465 mMediaController.hide(); 466 } 467 if (mOnCompletionListener != null) { 468 mOnCompletionListener.onCompletion(mMediaPlayer); 469 } 470 } 471 }; 472 473 private MediaPlayer.OnInfoListener mInfoListener = 474 new MediaPlayer.OnInfoListener() { 475 public boolean onInfo(MediaPlayer mp, int arg1, int arg2) { 476 if (mOnInfoListener != null) { 477 mOnInfoListener.onInfo(mp, arg1, arg2); 478 } 479 return true; 480 } 481 }; 482 483 private MediaPlayer.OnErrorListener mErrorListener = 484 new MediaPlayer.OnErrorListener() { 485 public boolean onError(MediaPlayer mp, int framework_err, int impl_err) { 486 Log.d(TAG, "Error: " + framework_err + "," + impl_err); 487 mCurrentState = STATE_ERROR; 488 mTargetState = STATE_ERROR; 489 if (mMediaController != null) { 490 mMediaController.hide(); 491 } 492 493 /* If an error handler has been supplied, use it and finish. */ 494 if (mOnErrorListener != null) { 495 if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) { 496 return true; 497 } 498 } 499 500 /* Otherwise, pop up an error dialog so the user knows that 501 * something bad has happened. Only try and pop up the dialog 502 * if we're attached to a window. When we're going away and no 503 * longer have a window, don't bother showing the user an error. 504 */ 505 if (getWindowToken() != null) { 506 Resources r = mContext.getResources(); 507 int messageId; 508 509 if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { 510 messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback; 511 } else { 512 messageId = com.android.internal.R.string.VideoView_error_text_unknown; 513 } 514 515 new AlertDialog.Builder(mContext) 516 .setMessage(messageId) 517 .setPositiveButton(com.android.internal.R.string.VideoView_error_button, 518 new DialogInterface.OnClickListener() { 519 public void onClick(DialogInterface dialog, int whichButton) { 520 /* If we get here, there is no onError listener, so 521 * at least inform them that the video is over. 522 */ 523 if (mOnCompletionListener != null) { 524 mOnCompletionListener.onCompletion(mMediaPlayer); 525 } 526 } 527 }) 528 .setCancelable(false) 529 .show(); 530 } 531 return true; 532 } 533 }; 534 535 private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = 536 new MediaPlayer.OnBufferingUpdateListener() { 537 public void onBufferingUpdate(MediaPlayer mp, int percent) { 538 mCurrentBufferPercentage = percent; 539 } 540 }; 541 542 /** 543 * Register a callback to be invoked when the media file 544 * is loaded and ready to go. 545 * 546 * @param l The callback that will be run 547 */ 548 public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) 549 { 550 mOnPreparedListener = l; 551 } 552 553 /** 554 * Register a callback to be invoked when the end of a media file 555 * has been reached during playback. 556 * 557 * @param l The callback that will be run 558 */ 559 public void setOnCompletionListener(OnCompletionListener l) 560 { 561 mOnCompletionListener = l; 562 } 563 564 /** 565 * Register a callback to be invoked when an error occurs 566 * during playback or setup. If no listener is specified, 567 * or if the listener returned false, VideoView will inform 568 * the user of any errors. 569 * 570 * @param l The callback that will be run 571 */ 572 public void setOnErrorListener(OnErrorListener l) 573 { 574 mOnErrorListener = l; 575 } 576 577 /** 578 * Register a callback to be invoked when an informational event 579 * occurs during playback or setup. 580 * 581 * @param l The callback that will be run 582 */ 583 public void setOnInfoListener(OnInfoListener l) { 584 mOnInfoListener = l; 585 } 586 587 SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() 588 { 589 public void surfaceChanged(SurfaceHolder holder, int format, 590 int w, int h) 591 { 592 mSurfaceWidth = w; 593 mSurfaceHeight = h; 594 boolean isValidState = (mTargetState == STATE_PLAYING); 595 boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h); 596 if (mMediaPlayer != null && isValidState && hasValidSize) { 597 if (mSeekWhenPrepared != 0) { 598 seekTo(mSeekWhenPrepared); 599 } 600 start(); 601 } 602 } 603 604 public void surfaceCreated(SurfaceHolder holder) 605 { 606 mSurfaceHolder = holder; 607 openVideo(); 608 } 609 610 public void surfaceDestroyed(SurfaceHolder holder) 611 { 612 // after we return from this we can't use the surface any more 613 mSurfaceHolder = null; 614 if (mMediaController != null) mMediaController.hide(); 615 release(true); 616 } 617 }; 618 619 /* 620 * release the media player in any state 621 */ 622 private void release(boolean cleartargetstate) { 623 if (mMediaPlayer != null) { 624 mMediaPlayer.reset(); 625 mMediaPlayer.release(); 626 mMediaPlayer = null; 627 mPendingSubtitleTracks.clear(); 628 mCurrentState = STATE_IDLE; 629 if (cleartargetstate) { 630 mTargetState = STATE_IDLE; 631 } 632 } 633 } 634 635 @Override 636 public boolean onTouchEvent(MotionEvent ev) { 637 if (isInPlaybackState() && mMediaController != null) { 638 toggleMediaControlsVisiblity(); 639 } 640 return false; 641 } 642 643 @Override 644 public boolean onTrackballEvent(MotionEvent ev) { 645 if (isInPlaybackState() && mMediaController != null) { 646 toggleMediaControlsVisiblity(); 647 } 648 return false; 649 } 650 651 @Override 652 public boolean onKeyDown(int keyCode, KeyEvent event) 653 { 654 boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && 655 keyCode != KeyEvent.KEYCODE_VOLUME_UP && 656 keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && 657 keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && 658 keyCode != KeyEvent.KEYCODE_MENU && 659 keyCode != KeyEvent.KEYCODE_CALL && 660 keyCode != KeyEvent.KEYCODE_ENDCALL; 661 if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) { 662 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || 663 keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { 664 if (mMediaPlayer.isPlaying()) { 665 pause(); 666 mMediaController.show(); 667 } else { 668 start(); 669 mMediaController.hide(); 670 } 671 return true; 672 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { 673 if (!mMediaPlayer.isPlaying()) { 674 start(); 675 mMediaController.hide(); 676 } 677 return true; 678 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP 679 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { 680 if (mMediaPlayer.isPlaying()) { 681 pause(); 682 mMediaController.show(); 683 } 684 return true; 685 } else { 686 toggleMediaControlsVisiblity(); 687 } 688 } 689 690 return super.onKeyDown(keyCode, event); 691 } 692 693 private void toggleMediaControlsVisiblity() { 694 if (mMediaController.isShowing()) { 695 mMediaController.hide(); 696 } else { 697 mMediaController.show(); 698 } 699 } 700 701 @Override 702 public void start() { 703 if (isInPlaybackState()) { 704 mMediaPlayer.start(); 705 mCurrentState = STATE_PLAYING; 706 } 707 mTargetState = STATE_PLAYING; 708 } 709 710 @Override 711 public void pause() { 712 if (isInPlaybackState()) { 713 if (mMediaPlayer.isPlaying()) { 714 mMediaPlayer.pause(); 715 mCurrentState = STATE_PAUSED; 716 } 717 } 718 mTargetState = STATE_PAUSED; 719 } 720 721 public void suspend() { 722 release(false); 723 } 724 725 public void resume() { 726 openVideo(); 727 } 728 729 @Override 730 public int getDuration() { 731 if (isInPlaybackState()) { 732 return mMediaPlayer.getDuration(); 733 } 734 735 return -1; 736 } 737 738 @Override 739 public int getCurrentPosition() { 740 if (isInPlaybackState()) { 741 return mMediaPlayer.getCurrentPosition(); 742 } 743 return 0; 744 } 745 746 @Override 747 public void seekTo(int msec) { 748 if (isInPlaybackState()) { 749 mMediaPlayer.seekTo(msec); 750 mSeekWhenPrepared = 0; 751 } else { 752 mSeekWhenPrepared = msec; 753 } 754 } 755 756 @Override 757 public boolean isPlaying() { 758 return isInPlaybackState() && mMediaPlayer.isPlaying(); 759 } 760 761 @Override 762 public int getBufferPercentage() { 763 if (mMediaPlayer != null) { 764 return mCurrentBufferPercentage; 765 } 766 return 0; 767 } 768 769 private boolean isInPlaybackState() { 770 return (mMediaPlayer != null && 771 mCurrentState != STATE_ERROR && 772 mCurrentState != STATE_IDLE && 773 mCurrentState != STATE_PREPARING); 774 } 775 776 @Override 777 public boolean canPause() { 778 return mCanPause; 779 } 780 781 @Override 782 public boolean canSeekBackward() { 783 return mCanSeekBack; 784 } 785 786 @Override 787 public boolean canSeekForward() { 788 return mCanSeekForward; 789 } 790 791 @Override 792 public int getAudioSessionId() { 793 if (mAudioSession == 0) { 794 MediaPlayer foo = new MediaPlayer(); 795 mAudioSession = foo.getAudioSessionId(); 796 foo.release(); 797 } 798 return mAudioSession; 799 } 800 801 @Override 802 protected void onAttachedToWindow() { 803 super.onAttachedToWindow(); 804 805 if (mSubtitleWidget != null) { 806 mSubtitleWidget.onAttachedToWindow(); 807 } 808 } 809 810 @Override 811 protected void onDetachedFromWindow() { 812 super.onDetachedFromWindow(); 813 814 if (mSubtitleWidget != null) { 815 mSubtitleWidget.onDetachedFromWindow(); 816 } 817 } 818 819 @Override 820 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 821 super.onLayout(changed, left, top, right, bottom); 822 823 if (mSubtitleWidget != null) { 824 measureAndLayoutSubtitleWidget(); 825 } 826 } 827 828 @Override 829 public void draw(Canvas canvas) { 830 super.draw(canvas); 831 832 if (mSubtitleWidget != null) { 833 final int saveCount = canvas.save(); 834 canvas.translate(getPaddingLeft(), getPaddingTop()); 835 mSubtitleWidget.draw(canvas); 836 canvas.restoreToCount(saveCount); 837 } 838 } 839 840 /** 841 * Forces a measurement and layout pass for all overlaid views. 842 * 843 * @see #setSubtitleWidget(RenderingWidget) 844 */ 845 private void measureAndLayoutSubtitleWidget() { 846 final int width = getWidth() - getPaddingLeft() - getPaddingRight(); 847 final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 848 849 mSubtitleWidget.setSize(width, height); 850 } 851 852 /** @hide */ 853 @Override 854 public void setSubtitleWidget(RenderingWidget subtitleWidget) { 855 if (mSubtitleWidget == subtitleWidget) { 856 return; 857 } 858 859 final boolean attachedToWindow = isAttachedToWindow(); 860 if (mSubtitleWidget != null) { 861 if (attachedToWindow) { 862 mSubtitleWidget.onDetachedFromWindow(); 863 } 864 865 mSubtitleWidget.setOnChangedListener(null); 866 } 867 868 mSubtitleWidget = subtitleWidget; 869 870 if (subtitleWidget != null) { 871 if (mSubtitlesChangedListener == null) { 872 mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() { 873 @Override 874 public void onChanged(RenderingWidget renderingWidget) { 875 invalidate(); 876 } 877 }; 878 } 879 880 setWillNotDraw(false); 881 subtitleWidget.setOnChangedListener(mSubtitlesChangedListener); 882 883 if (attachedToWindow) { 884 subtitleWidget.onAttachedToWindow(); 885 requestLayout(); 886 } 887 } else { 888 setWillNotDraw(true); 889 } 890 891 invalidate(); 892 } 893 894 /** @hide */ 895 @Override 896 public Looper getSubtitleLooper() { 897 return Looper.getMainLooper(); 898 } 899 } 900