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