blob: 329b0df0bc11ea01d045d486add36aa10228203f [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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;
Marco Nelissenc818b142009-08-19 08:32:21 -070026import android.media.Metadata;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.media.MediaPlayer.OnCompletionListener;
28import android.media.MediaPlayer.OnErrorListener;
James Donga0ba7942012-07-27 17:30:38 -070029import android.media.MediaPlayer.OnInfoListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.net.Uri;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.util.AttributeSet;
32import android.util.Log;
33import android.view.KeyEvent;
34import android.view.MotionEvent;
35import android.view.SurfaceHolder;
36import android.view.SurfaceView;
37import android.view.View;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080038import android.view.accessibility.AccessibilityEvent;
39import android.view.accessibility.AccessibilityNodeInfo;
Owen Lina8381df2010-11-09 19:32:30 +080040import android.widget.MediaController.MediaPlayerControl;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041
42import java.io.IOException;
Andreas Huber25643002010-01-28 11:19:57 -080043import java.util.Map;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044
45/**
46 * Displays a video file. The VideoView class
47 * can load images from various sources (such as resources or content
48 * providers), takes care of computing its measurement from the video so that
49 * it can be used in any layout manager, and provides various display options
50 * such as scaling and tinting.
51 */
52public class VideoView extends SurfaceView implements MediaPlayerControl {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070053 private String TAG = "VideoView";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054 // settable by the client
55 private Uri mUri;
Andreas Huber25643002010-01-28 11:19:57 -080056 private Map<String, String> mHeaders;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057
James Dongfdf3ac62009-07-06 12:37:45 -070058 // all possible internal states
59 private static final int STATE_ERROR = -1;
60 private static final int STATE_IDLE = 0;
61 private static final int STATE_PREPARING = 1;
62 private static final int STATE_PREPARED = 2;
63 private static final int STATE_PLAYING = 3;
64 private static final int STATE_PAUSED = 4;
65 private static final int STATE_PLAYBACK_COMPLETED = 5;
66
67 // mCurrentState is a VideoView object's current state.
68 // mTargetState is the state that a method caller intends to reach.
69 // For instance, regardless the VideoView object's current state,
70 // calling pause() intends to bring the object to a target state
71 // of STATE_PAUSED.
72 private int mCurrentState = STATE_IDLE;
73 private int mTargetState = STATE_IDLE;
74
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 // All the stuff we need for playing and showing a video
76 private SurfaceHolder mSurfaceHolder = null;
77 private MediaPlayer mMediaPlayer = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078 private int mVideoWidth;
79 private int mVideoHeight;
80 private int mSurfaceWidth;
81 private int mSurfaceHeight;
82 private MediaController mMediaController;
83 private OnCompletionListener mOnCompletionListener;
84 private MediaPlayer.OnPreparedListener mOnPreparedListener;
85 private int mCurrentBufferPercentage;
86 private OnErrorListener mOnErrorListener;
James Donga0ba7942012-07-27 17:30:38 -070087 private OnInfoListener mOnInfoListener;
James Dongfdf3ac62009-07-06 12:37:45 -070088 private int mSeekWhenPrepared; // recording the seek position while preparing
Marco Nelissenc818b142009-08-19 08:32:21 -070089 private boolean mCanPause;
90 private boolean mCanSeekBack;
91 private boolean mCanSeekForward;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080092
93 public VideoView(Context context) {
94 super(context);
95 initVideoView();
96 }
Andreas Huber25643002010-01-28 11:19:57 -080097
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098 public VideoView(Context context, AttributeSet attrs) {
99 this(context, attrs, 0);
100 initVideoView();
101 }
Andreas Huber25643002010-01-28 11:19:57 -0800102
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 public VideoView(Context context, AttributeSet attrs, int defStyle) {
104 super(context, attrs, defStyle);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105 initVideoView();
106 }
107
108 @Override
109 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
110 //Log.i("@@@@", "onMeasure");
111 int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
112 int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
113 if (mVideoWidth > 0 && mVideoHeight > 0) {
114 if ( mVideoWidth * height > width * mVideoHeight ) {
115 //Log.i("@@@", "image too tall, correcting");
116 height = width * mVideoHeight / mVideoWidth;
117 } else if ( mVideoWidth * height < width * mVideoHeight ) {
118 //Log.i("@@@", "image too wide, correcting");
119 width = height * mVideoWidth / mVideoHeight;
120 } else {
121 //Log.i("@@@", "aspect ratio is correct: " +
122 //width+"/"+height+"="+
123 //mVideoWidth+"/"+mVideoHeight);
124 }
125 }
126 //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
127 setMeasuredDimension(width, height);
128 }
Andreas Huber25643002010-01-28 11:19:57 -0800129
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800130 @Override
131 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
132 super.onInitializeAccessibilityEvent(event);
133 event.setClassName(VideoView.class.getName());
134 }
135
136 @Override
137 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
138 super.onInitializeAccessibilityNodeInfo(info);
139 info.setClassName(VideoView.class.getName());
140 }
141
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 public int resolveAdjustedSize(int desiredSize, int measureSpec) {
143 int result = desiredSize;
144 int specMode = MeasureSpec.getMode(measureSpec);
145 int specSize = MeasureSpec.getSize(measureSpec);
146
147 switch (specMode) {
148 case MeasureSpec.UNSPECIFIED:
149 /* Parent says we can be as big as we want. Just don't be larger
150 * than max size imposed on ourselves.
151 */
152 result = desiredSize;
153 break;
154
155 case MeasureSpec.AT_MOST:
Andreas Huber25643002010-01-28 11:19:57 -0800156 /* Parent says we can be as big as we want, up to specSize.
157 * Don't be larger than specSize, and don't be larger than
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 * the max size imposed on ourselves.
159 */
160 result = Math.min(desiredSize, specSize);
161 break;
Andreas Huber25643002010-01-28 11:19:57 -0800162
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800163 case MeasureSpec.EXACTLY:
164 // No choice. Do what we are told.
165 result = specSize;
166 break;
167 }
168 return result;
169}
Andreas Huber25643002010-01-28 11:19:57 -0800170
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 private void initVideoView() {
172 mVideoWidth = 0;
173 mVideoHeight = 0;
174 getHolder().addCallback(mSHCallback);
175 getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
176 setFocusable(true);
177 setFocusableInTouchMode(true);
178 requestFocus();
James Dongfdf3ac62009-07-06 12:37:45 -0700179 mCurrentState = STATE_IDLE;
180 mTargetState = STATE_IDLE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 }
182
183 public void setVideoPath(String path) {
184 setVideoURI(Uri.parse(path));
185 }
186
187 public void setVideoURI(Uri uri) {
Andreas Huber25643002010-01-28 11:19:57 -0800188 setVideoURI(uri, null);
189 }
190
191 /**
192 * @hide
193 */
194 public void setVideoURI(Uri uri, Map<String, String> headers) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195 mUri = uri;
Andreas Huber25643002010-01-28 11:19:57 -0800196 mHeaders = headers;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 mSeekWhenPrepared = 0;
198 openVideo();
199 requestLayout();
200 invalidate();
201 }
Andreas Huber25643002010-01-28 11:19:57 -0800202
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203 public void stopPlayback() {
204 if (mMediaPlayer != null) {
205 mMediaPlayer.stop();
206 mMediaPlayer.release();
207 mMediaPlayer = null;
James Dongfdf3ac62009-07-06 12:37:45 -0700208 mCurrentState = STATE_IDLE;
209 mTargetState = STATE_IDLE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 }
211 }
212
213 private void openVideo() {
214 if (mUri == null || mSurfaceHolder == null) {
215 // not ready for playback just yet, will try again later
216 return;
217 }
Andreas Huber25643002010-01-28 11:19:57 -0800218 // Tell the music playback service to pause
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800219 // TODO: these constants need to be published somewhere in the framework.
220 Intent i = new Intent("com.android.music.musicservicecommand");
221 i.putExtra("command", "pause");
222 mContext.sendBroadcast(i);
James Dongfdf3ac62009-07-06 12:37:45 -0700223
Marco Nelissendddeee62009-07-10 14:14:52 -0700224 // we shouldn't clear the target state, because somebody might have
225 // called start() previously
226 release(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 try {
228 mMediaPlayer = new MediaPlayer();
229 mMediaPlayer.setOnPreparedListener(mPreparedListener);
230 mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 mMediaPlayer.setOnCompletionListener(mCompletionListener);
232 mMediaPlayer.setOnErrorListener(mErrorListener);
James Donga0ba7942012-07-27 17:30:38 -0700233 mMediaPlayer.setOnInfoListener(mOnInfoListener);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
235 mCurrentBufferPercentage = 0;
Andreas Huber25643002010-01-28 11:19:57 -0800236 mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 mMediaPlayer.setDisplay(mSurfaceHolder);
238 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
239 mMediaPlayer.setScreenOnWhilePlaying(true);
240 mMediaPlayer.prepareAsync();
Marco Nelissendddeee62009-07-10 14:14:52 -0700241 // we don't set the target state here either, but preserve the
242 // target state that was there before.
James Dongfdf3ac62009-07-06 12:37:45 -0700243 mCurrentState = STATE_PREPARING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244 attachMediaController();
245 } catch (IOException ex) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700246 Log.w(TAG, "Unable to open content: " + mUri, ex);
James Dongfdf3ac62009-07-06 12:37:45 -0700247 mCurrentState = STATE_ERROR;
248 mTargetState = STATE_ERROR;
Andrei Popescu020d2e32009-09-30 14:54:55 +0100249 mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 return;
251 } catch (IllegalArgumentException ex) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700252 Log.w(TAG, "Unable to open content: " + mUri, ex);
James Dongfdf3ac62009-07-06 12:37:45 -0700253 mCurrentState = STATE_ERROR;
254 mTargetState = STATE_ERROR;
Andrei Popescu020d2e32009-09-30 14:54:55 +0100255 mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256 return;
257 }
258 }
Andreas Huber25643002010-01-28 11:19:57 -0800259
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 public void setMediaController(MediaController controller) {
261 if (mMediaController != null) {
262 mMediaController.hide();
263 }
264 mMediaController = controller;
265 attachMediaController();
266 }
267
268 private void attachMediaController() {
269 if (mMediaPlayer != null && mMediaController != null) {
270 mMediaController.setMediaPlayer(this);
271 View anchorView = this.getParent() instanceof View ?
272 (View)this.getParent() : this;
273 mMediaController.setAnchorView(anchorView);
James Dongfdf3ac62009-07-06 12:37:45 -0700274 mMediaController.setEnabled(isInPlaybackState());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800275 }
276 }
Andreas Huber25643002010-01-28 11:19:57 -0800277
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278 MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
279 new MediaPlayer.OnVideoSizeChangedListener() {
280 public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
281 mVideoWidth = mp.getVideoWidth();
282 mVideoHeight = mp.getVideoHeight();
283 if (mVideoWidth != 0 && mVideoHeight != 0) {
284 getHolder().setFixedSize(mVideoWidth, mVideoHeight);
Teng-Hui Zhuf5d102e2012-08-23 15:24:29 -0700285 requestLayout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800286 }
287 }
288 };
Andreas Huber25643002010-01-28 11:19:57 -0800289
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290 MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
291 public void onPrepared(MediaPlayer mp) {
James Dongfdf3ac62009-07-06 12:37:45 -0700292 mCurrentState = STATE_PREPARED;
Marco Nelissenc818b142009-08-19 08:32:21 -0700293
294 // Get the capabilities of the player for this stream
295 Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
296 MediaPlayer.BYPASS_METADATA_FILTER);
Andreas Huberd44d33b2009-08-20 11:12:27 -0700297
298 if (data != null) {
299 mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
300 || data.getBoolean(Metadata.PAUSE_AVAILABLE);
301 mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
302 || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
303 mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
304 || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
305 } else {
Andreas Huber2869e952010-03-09 09:18:01 -0800306 mCanPause = mCanSeekBack = mCanSeekForward = true;
Andreas Huberd44d33b2009-08-20 11:12:27 -0700307 }
Marco Nelissenc818b142009-08-19 08:32:21 -0700308
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 if (mOnPreparedListener != null) {
310 mOnPreparedListener.onPrepared(mMediaPlayer);
311 }
312 if (mMediaController != null) {
313 mMediaController.setEnabled(true);
314 }
315 mVideoWidth = mp.getVideoWidth();
316 mVideoHeight = mp.getVideoHeight();
Marco Nelissenc818b142009-08-19 08:32:21 -0700317
James Dongfdf3ac62009-07-06 12:37:45 -0700318 int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call
319 if (seekToPosition != 0) {
320 seekTo(seekToPosition);
321 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800322 if (mVideoWidth != 0 && mVideoHeight != 0) {
323 //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
324 getHolder().setFixedSize(mVideoWidth, mVideoHeight);
325 if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
326 // We didn't actually change the size (it was already at the size
327 // we need), so we won't get a "surface changed" callback, so
328 // start the video here instead of in the callback.
James Dongfdf3ac62009-07-06 12:37:45 -0700329 if (mTargetState == STATE_PLAYING) {
James Dongaa00e392009-07-02 16:26:39 -0700330 start();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800331 if (mMediaController != null) {
332 mMediaController.show();
333 }
334 } else if (!isPlaying() &&
James Dongfdf3ac62009-07-06 12:37:45 -0700335 (seekToPosition != 0 || getCurrentPosition() > 0)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 if (mMediaController != null) {
337 // Show the media controls when we're paused into a video and make 'em stick.
338 mMediaController.show(0);
339 }
340 }
341 }
342 } else {
343 // We don't know the video size yet, but should start anyway.
344 // The video size might be reported to us later.
James Dongfdf3ac62009-07-06 12:37:45 -0700345 if (mTargetState == STATE_PLAYING) {
James Dongaa00e392009-07-02 16:26:39 -0700346 start();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800347 }
348 }
349 }
350 };
351
352 private MediaPlayer.OnCompletionListener mCompletionListener =
353 new MediaPlayer.OnCompletionListener() {
354 public void onCompletion(MediaPlayer mp) {
James Dongfdf3ac62009-07-06 12:37:45 -0700355 mCurrentState = STATE_PLAYBACK_COMPLETED;
356 mTargetState = STATE_PLAYBACK_COMPLETED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 if (mMediaController != null) {
358 mMediaController.hide();
359 }
360 if (mOnCompletionListener != null) {
361 mOnCompletionListener.onCompletion(mMediaPlayer);
362 }
363 }
364 };
365
366 private MediaPlayer.OnErrorListener mErrorListener =
367 new MediaPlayer.OnErrorListener() {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700368 public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {
369 Log.d(TAG, "Error: " + framework_err + "," + impl_err);
James Dongfdf3ac62009-07-06 12:37:45 -0700370 mCurrentState = STATE_ERROR;
371 mTargetState = STATE_ERROR;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800372 if (mMediaController != null) {
373 mMediaController.hide();
374 }
375
376 /* If an error handler has been supplied, use it and finish. */
377 if (mOnErrorListener != null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700378 if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 return true;
380 }
381 }
382
383 /* Otherwise, pop up an error dialog so the user knows that
384 * something bad has happened. Only try and pop up the dialog
385 * if we're attached to a window. When we're going away and no
386 * longer have a window, don't bother showing the user an error.
387 */
388 if (getWindowToken() != null) {
389 Resources r = mContext.getResources();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700390 int messageId;
391
392 if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
393 messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback;
394 } else {
395 messageId = com.android.internal.R.string.VideoView_error_text_unknown;
396 }
397
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800398 new AlertDialog.Builder(mContext)
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700399 .setMessage(messageId)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800400 .setPositiveButton(com.android.internal.R.string.VideoView_error_button,
401 new DialogInterface.OnClickListener() {
402 public void onClick(DialogInterface dialog, int whichButton) {
403 /* If we get here, there is no onError listener, so
404 * at least inform them that the video is over.
405 */
406 if (mOnCompletionListener != null) {
407 mOnCompletionListener.onCompletion(mMediaPlayer);
408 }
409 }
410 })
411 .setCancelable(false)
412 .show();
413 }
414 return true;
415 }
416 };
417
418 private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
419 new MediaPlayer.OnBufferingUpdateListener() {
420 public void onBufferingUpdate(MediaPlayer mp, int percent) {
421 mCurrentBufferPercentage = percent;
422 }
423 };
424
425 /**
426 * Register a callback to be invoked when the media file
427 * is loaded and ready to go.
428 *
429 * @param l The callback that will be run
430 */
431 public void setOnPreparedListener(MediaPlayer.OnPreparedListener l)
432 {
433 mOnPreparedListener = l;
434 }
435
436 /**
437 * Register a callback to be invoked when the end of a media file
438 * has been reached during playback.
439 *
440 * @param l The callback that will be run
441 */
442 public void setOnCompletionListener(OnCompletionListener l)
443 {
444 mOnCompletionListener = l;
445 }
446
447 /**
448 * Register a callback to be invoked when an error occurs
449 * during playback or setup. If no listener is specified,
450 * or if the listener returned false, VideoView will inform
451 * the user of any errors.
452 *
453 * @param l The callback that will be run
454 */
455 public void setOnErrorListener(OnErrorListener l)
456 {
457 mOnErrorListener = l;
458 }
459
James Donga0ba7942012-07-27 17:30:38 -0700460 /**
461 * Register a callback to be invoked when an informational event
462 * occurs during playback or setup.
463 *
464 * @param l The callback that will be run
465 */
466 public void setOnInfoListener(OnInfoListener l) {
467 mOnInfoListener = l;
468 }
469
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800470 SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
471 {
472 public void surfaceChanged(SurfaceHolder holder, int format,
473 int w, int h)
474 {
475 mSurfaceWidth = w;
476 mSurfaceHeight = h;
James Dongfdf3ac62009-07-06 12:37:45 -0700477 boolean isValidState = (mTargetState == STATE_PLAYING);
478 boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);
479 if (mMediaPlayer != null && isValidState && hasValidSize) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800480 if (mSeekWhenPrepared != 0) {
James Dongfdf3ac62009-07-06 12:37:45 -0700481 seekTo(mSeekWhenPrepared);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800482 }
James Dongfdf3ac62009-07-06 12:37:45 -0700483 start();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 }
485 }
486
487 public void surfaceCreated(SurfaceHolder holder)
488 {
489 mSurfaceHolder = holder;
Andreas Huber69b8d692010-10-29 12:00:20 -0700490 openVideo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 }
492
493 public void surfaceDestroyed(SurfaceHolder holder)
494 {
495 // after we return from this we can't use the surface any more
496 mSurfaceHolder = null;
497 if (mMediaController != null) mMediaController.hide();
Andreas Huber69b8d692010-10-29 12:00:20 -0700498 release(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499 }
500 };
501
James Dongfdf3ac62009-07-06 12:37:45 -0700502 /*
503 * release the media player in any state
504 */
Marco Nelissendddeee62009-07-10 14:14:52 -0700505 private void release(boolean cleartargetstate) {
James Dongfdf3ac62009-07-06 12:37:45 -0700506 if (mMediaPlayer != null) {
507 mMediaPlayer.reset();
508 mMediaPlayer.release();
509 mMediaPlayer = null;
510 mCurrentState = STATE_IDLE;
Marco Nelissendddeee62009-07-10 14:14:52 -0700511 if (cleartargetstate) {
512 mTargetState = STATE_IDLE;
513 }
James Dongfdf3ac62009-07-06 12:37:45 -0700514 }
515 }
516
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800517 @Override
518 public boolean onTouchEvent(MotionEvent ev) {
James Dongfdf3ac62009-07-06 12:37:45 -0700519 if (isInPlaybackState() && mMediaController != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 toggleMediaControlsVisiblity();
521 }
522 return false;
523 }
Andreas Huber25643002010-01-28 11:19:57 -0800524
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800525 @Override
526 public boolean onTrackballEvent(MotionEvent ev) {
James Dongfdf3ac62009-07-06 12:37:45 -0700527 if (isInPlaybackState() && mMediaController != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528 toggleMediaControlsVisiblity();
529 }
530 return false;
531 }
Andreas Huber25643002010-01-28 11:19:57 -0800532
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800533 @Override
534 public boolean onKeyDown(int keyCode, KeyEvent event)
535 {
James Dongfdf3ac62009-07-06 12:37:45 -0700536 boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
537 keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
538 keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
Jeff Brownb0418da2010-11-01 15:24:01 -0700539 keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
James Dongfdf3ac62009-07-06 12:37:45 -0700540 keyCode != KeyEvent.KEYCODE_MENU &&
541 keyCode != KeyEvent.KEYCODE_CALL &&
542 keyCode != KeyEvent.KEYCODE_ENDCALL;
543 if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700545 keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 if (mMediaPlayer.isPlaying()) {
547 pause();
548 mMediaController.show();
549 } else {
550 start();
551 mMediaController.hide();
552 }
553 return true;
Jeff Brown4d396052010-10-29 21:50:21 -0700554 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
Chih-Chung Changd7db7012011-02-10 18:11:27 +0800555 if (!mMediaPlayer.isPlaying()) {
Jeff Brown4d396052010-10-29 21:50:21 -0700556 start();
557 mMediaController.hide();
558 }
559 return true;
Andreas Huber25643002010-01-28 11:19:57 -0800560 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
Jeff Brown4d396052010-10-29 21:50:21 -0700561 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
Chih-Chung Changd7db7012011-02-10 18:11:27 +0800562 if (mMediaPlayer.isPlaying()) {
Jeff Brown4d396052010-10-29 21:50:21 -0700563 pause();
564 mMediaController.show();
565 }
566 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800567 } else {
568 toggleMediaControlsVisiblity();
569 }
570 }
571
572 return super.onKeyDown(keyCode, event);
573 }
574
575 private void toggleMediaControlsVisiblity() {
Andreas Huber25643002010-01-28 11:19:57 -0800576 if (mMediaController.isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800577 mMediaController.hide();
578 } else {
579 mMediaController.show();
580 }
581 }
Andreas Huber25643002010-01-28 11:19:57 -0800582
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 public void start() {
James Dongfdf3ac62009-07-06 12:37:45 -0700584 if (isInPlaybackState()) {
585 mMediaPlayer.start();
586 mCurrentState = STATE_PLAYING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800587 }
James Dongfdf3ac62009-07-06 12:37:45 -0700588 mTargetState = STATE_PLAYING;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800589 }
Andreas Huber25643002010-01-28 11:19:57 -0800590
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800591 public void pause() {
James Dongfdf3ac62009-07-06 12:37:45 -0700592 if (isInPlaybackState()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800593 if (mMediaPlayer.isPlaying()) {
594 mMediaPlayer.pause();
James Dongfdf3ac62009-07-06 12:37:45 -0700595 mCurrentState = STATE_PAUSED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800596 }
597 }
James Dongfdf3ac62009-07-06 12:37:45 -0700598 mTargetState = STATE_PAUSED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800599 }
Andreas Huber25643002010-01-28 11:19:57 -0800600
Gloria Wang2e1818a2010-02-22 14:49:13 -0800601 public void suspend() {
Andreas Huber69b8d692010-10-29 12:00:20 -0700602 release(false);
Gloria Wang2e1818a2010-02-22 14:49:13 -0800603 }
604
605 public void resume() {
Andreas Huber69b8d692010-10-29 12:00:20 -0700606 openVideo();
Gloria Wang2e1818a2010-02-22 14:49:13 -0800607 }
608
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800609 public int getDuration() {
James Dongfdf3ac62009-07-06 12:37:45 -0700610 if (isInPlaybackState()) {
Andreas Huber585c07e2012-11-27 15:06:56 -0800611 return mMediaPlayer.getDuration();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 }
Andreas Huber585c07e2012-11-27 15:06:56 -0800613
614 return -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800615 }
Andreas Huber25643002010-01-28 11:19:57 -0800616
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800617 public int getCurrentPosition() {
James Dongfdf3ac62009-07-06 12:37:45 -0700618 if (isInPlaybackState()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800619 return mMediaPlayer.getCurrentPosition();
620 }
621 return 0;
622 }
Andreas Huber25643002010-01-28 11:19:57 -0800623
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800624 public void seekTo(int msec) {
James Dongfdf3ac62009-07-06 12:37:45 -0700625 if (isInPlaybackState()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 mMediaPlayer.seekTo(msec);
James Dongfdf3ac62009-07-06 12:37:45 -0700627 mSeekWhenPrepared = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800628 } else {
629 mSeekWhenPrepared = msec;
630 }
Andreas Huber25643002010-01-28 11:19:57 -0800631 }
632
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800633 public boolean isPlaying() {
James Dongfdf3ac62009-07-06 12:37:45 -0700634 return isInPlaybackState() && mMediaPlayer.isPlaying();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635 }
Andreas Huber25643002010-01-28 11:19:57 -0800636
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800637 public int getBufferPercentage() {
638 if (mMediaPlayer != null) {
639 return mCurrentBufferPercentage;
640 }
641 return 0;
642 }
James Dongfdf3ac62009-07-06 12:37:45 -0700643
644 private boolean isInPlaybackState() {
645 return (mMediaPlayer != null &&
646 mCurrentState != STATE_ERROR &&
647 mCurrentState != STATE_IDLE &&
648 mCurrentState != STATE_PREPARING);
649 }
Marco Nelissenc818b142009-08-19 08:32:21 -0700650
651 public boolean canPause() {
652 return mCanPause;
653 }
654
655 public boolean canSeekBackward() {
656 return mCanSeekBack;
657 }
658
659 public boolean canSeekForward() {
660 return mCanSeekForward;
661 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800662}