blob: 546cc5f654b7b37304c4792c23edafd3dcfca7d8 [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.content.Context;
20import android.graphics.PixelFormat;
21import android.media.AudioManager;
22import android.os.Handler;
23import android.os.Message;
24import android.util.AttributeSet;
25import android.util.Log;
26import android.view.Gravity;
27import android.view.KeyEvent;
28import android.view.LayoutInflater;
29import android.view.MotionEvent;
30import android.view.View;
31import android.view.ViewGroup;
32import android.view.Window;
33import android.view.WindowManager;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080034import android.view.accessibility.AccessibilityEvent;
35import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.widget.SeekBar.OnSeekBarChangeListener;
37
38import com.android.internal.policy.PolicyManager;
39
40import java.util.Formatter;
41import java.util.Locale;
42
43/**
44 * A view containing controls for a MediaPlayer. Typically contains the
45 * buttons like "Play/Pause", "Rewind", "Fast Forward" and a progress
46 * slider. It takes care of synchronizing the controls with the state
47 * of the MediaPlayer.
48 * <p>
49 * The way to use this class is to instantiate it programatically.
50 * The MediaController will create a default set of controls
51 * and put them in a window floating above your application. Specifically,
52 * the controls will float above the view specified with setAnchorView().
53 * The window will disappear if left idle for three seconds and reappear
54 * when the user touches the anchor view.
55 * <p>
56 * Functions like show() and hide() have no effect when MediaController
57 * is created in an xml layout.
58 *
59 * MediaController will hide and
60 * show the buttons according to these rules:
61 * <ul>
62 * <li> The "previous" and "next" buttons are hidden until setPrevNextListeners()
63 * has been called
64 * <li> The "previous" and "next" buttons are visible but disabled if
65 * setPrevNextListeners() was called with null listeners
66 * <li> The "rewind" and "fastforward" buttons are shown unless requested
67 * otherwise by using the MediaController(Context, boolean) constructor
68 * with the boolean set to false
69 * </ul>
70 */
71public class MediaController extends FrameLayout {
72
73 private MediaPlayerControl mPlayer;
74 private Context mContext;
75 private View mAnchor;
76 private View mRoot;
77 private WindowManager mWindowManager;
78 private Window mWindow;
79 private View mDecor;
Chih-Chung Chang85d4ea62011-09-28 17:57:10 +080080 private WindowManager.LayoutParams mDecorLayoutParams;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 private ProgressBar mProgress;
82 private TextView mEndTime, mCurrentTime;
83 private boolean mShowing;
84 private boolean mDragging;
85 private static final int sDefaultTimeout = 3000;
86 private static final int FADE_OUT = 1;
87 private static final int SHOW_PROGRESS = 2;
88 private boolean mUseFastForward;
89 private boolean mFromXml;
90 private boolean mListenersSet;
91 private View.OnClickListener mNextListener, mPrevListener;
92 StringBuilder mFormatBuilder;
93 Formatter mFormatter;
94 private ImageButton mPauseButton;
95 private ImageButton mFfwdButton;
96 private ImageButton mRewButton;
97 private ImageButton mNextButton;
98 private ImageButton mPrevButton;
99
100 public MediaController(Context context, AttributeSet attrs) {
101 super(context, attrs);
102 mRoot = this;
103 mContext = context;
104 mUseFastForward = true;
105 mFromXml = true;
106 }
107
108 @Override
109 public void onFinishInflate() {
110 if (mRoot != null)
111 initControllerView(mRoot);
112 }
113
114 public MediaController(Context context, boolean useFastForward) {
115 super(context);
116 mContext = context;
117 mUseFastForward = useFastForward;
Chih-Chung Chang02dd17d2011-09-29 12:49:04 +0800118 initFloatingWindowLayout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 initFloatingWindow();
120 }
121
122 public MediaController(Context context) {
Chih-Chung Chang02dd17d2011-09-29 12:49:04 +0800123 this(context, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 }
125
126 private void initFloatingWindow() {
Christian Mehlmaueref367522010-05-31 23:08:30 +0200127 mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 mWindow = PolicyManager.makeNewWindow(mContext);
129 mWindow.setWindowManager(mWindowManager, null, null);
130 mWindow.requestFeature(Window.FEATURE_NO_TITLE);
131 mDecor = mWindow.getDecorView();
132 mDecor.setOnTouchListener(mTouchListener);
133 mWindow.setContentView(this);
134 mWindow.setBackgroundDrawableResource(android.R.color.transparent);
135
136 // While the media controller is up, the volume control keys should
137 // affect the media stream type
138 mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);
139
140 setFocusable(true);
141 setFocusableInTouchMode(true);
142 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
143 requestFocus();
144 }
145
Chih-Chung Chang85d4ea62011-09-28 17:57:10 +0800146 // Allocate and initialize the static parts of mDecorLayoutParams. Must
147 // also call updateFloatingWindowLayout() to fill in the dynamic parts
148 // (y and width) before mDecorLayoutParams can be used.
149 private void initFloatingWindowLayout() {
150 mDecorLayoutParams = new WindowManager.LayoutParams();
151 WindowManager.LayoutParams p = mDecorLayoutParams;
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800152 p.gravity = Gravity.TOP | Gravity.LEFT;
Chih-Chung Chang85d4ea62011-09-28 17:57:10 +0800153 p.height = LayoutParams.WRAP_CONTENT;
154 p.x = 0;
155 p.format = PixelFormat.TRANSLUCENT;
156 p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
157 p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
158 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
159 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
160 p.token = null;
161 p.windowAnimations = 0; // android.R.style.DropDownAnimationDown;
162 }
163
164 // Update the dynamic parts of mDecorLayoutParams
165 // Must be called with mAnchor != NULL.
166 private void updateFloatingWindowLayout() {
167 int [] anchorPos = new int[2];
168 mAnchor.getLocationOnScreen(anchorPos);
169
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800170 // we need to know the size of the controller so we can properly position it
171 // within its space
172 mDecor.measure(MeasureSpec.makeMeasureSpec(mAnchor.getWidth(), MeasureSpec.AT_MOST),
173 MeasureSpec.makeMeasureSpec(mAnchor.getHeight(), MeasureSpec.AT_MOST));
174
Chih-Chung Chang85d4ea62011-09-28 17:57:10 +0800175 WindowManager.LayoutParams p = mDecorLayoutParams;
176 p.width = mAnchor.getWidth();
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800177 p.x = anchorPos[0] + (mAnchor.getWidth() - p.width) / 2;
178 p.y = anchorPos[1] + mAnchor.getHeight() - mDecor.getMeasuredHeight();
Chih-Chung Chang85d4ea62011-09-28 17:57:10 +0800179 }
180
181 // This is called whenever mAnchor's layout bound changes
182 private OnLayoutChangeListener mLayoutChangeListener =
183 new OnLayoutChangeListener() {
184 public void onLayoutChange(View v, int left, int top, int right,
185 int bottom, int oldLeft, int oldTop, int oldRight,
186 int oldBottom) {
187 updateFloatingWindowLayout();
188 if (mShowing) {
189 mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams);
190 }
191 }
192 };
193
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194 private OnTouchListener mTouchListener = new OnTouchListener() {
195 public boolean onTouch(View v, MotionEvent event) {
196 if (event.getAction() == MotionEvent.ACTION_DOWN) {
197 if (mShowing) {
198 hide();
199 }
200 }
201 return false;
202 }
203 };
204
205 public void setMediaPlayer(MediaPlayerControl player) {
206 mPlayer = player;
207 updatePausePlay();
208 }
209
210 /**
211 * Set the view that acts as the anchor for the control view.
212 * This can for example be a VideoView, or your Activity's main view.
Marco Nelissen0b2d8722012-11-12 15:20:57 -0800213 * When VideoView calls this method, it will use the VideoView's parent
214 * as the anchor.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 * @param view The view to which to anchor the controller when it is visible.
216 */
217 public void setAnchorView(View view) {
Chih-Chung Chang85d4ea62011-09-28 17:57:10 +0800218 if (mAnchor != null) {
219 mAnchor.removeOnLayoutChangeListener(mLayoutChangeListener);
220 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 mAnchor = view;
Chih-Chung Chang85d4ea62011-09-28 17:57:10 +0800222 if (mAnchor != null) {
223 mAnchor.addOnLayoutChangeListener(mLayoutChangeListener);
224 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225
226 FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
Romain Guy980a9382010-01-08 15:06:28 -0800227 ViewGroup.LayoutParams.MATCH_PARENT,
228 ViewGroup.LayoutParams.MATCH_PARENT
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 );
230
231 removeAllViews();
232 View v = makeControllerView();
233 addView(v, frameParams);
234 }
235
236 /**
237 * Create the view that holds the widgets that control playback.
238 * Derived classes can override this to create their own.
239 * @return The controller view.
240 * @hide This doesn't work as advertised
241 */
242 protected View makeControllerView() {
243 LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
244 mRoot = inflate.inflate(com.android.internal.R.layout.media_controller, null);
245
246 initControllerView(mRoot);
247
248 return mRoot;
249 }
250
251 private void initControllerView(View v) {
252 mPauseButton = (ImageButton) v.findViewById(com.android.internal.R.id.pause);
253 if (mPauseButton != null) {
254 mPauseButton.requestFocus();
255 mPauseButton.setOnClickListener(mPauseListener);
256 }
257
258 mFfwdButton = (ImageButton) v.findViewById(com.android.internal.R.id.ffwd);
259 if (mFfwdButton != null) {
260 mFfwdButton.setOnClickListener(mFfwdListener);
261 if (!mFromXml) {
262 mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
263 }
264 }
265
266 mRewButton = (ImageButton) v.findViewById(com.android.internal.R.id.rew);
267 if (mRewButton != null) {
268 mRewButton.setOnClickListener(mRewListener);
269 if (!mFromXml) {
270 mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
271 }
272 }
273
274 // By default these are hidden. They will be enabled when setPrevNextListeners() is called
275 mNextButton = (ImageButton) v.findViewById(com.android.internal.R.id.next);
276 if (mNextButton != null && !mFromXml && !mListenersSet) {
277 mNextButton.setVisibility(View.GONE);
278 }
279 mPrevButton = (ImageButton) v.findViewById(com.android.internal.R.id.prev);
280 if (mPrevButton != null && !mFromXml && !mListenersSet) {
281 mPrevButton.setVisibility(View.GONE);
282 }
283
284 mProgress = (ProgressBar) v.findViewById(com.android.internal.R.id.mediacontroller_progress);
285 if (mProgress != null) {
286 if (mProgress instanceof SeekBar) {
287 SeekBar seeker = (SeekBar) mProgress;
288 seeker.setOnSeekBarChangeListener(mSeekListener);
289 }
290 mProgress.setMax(1000);
291 }
292
293 mEndTime = (TextView) v.findViewById(com.android.internal.R.id.time);
294 mCurrentTime = (TextView) v.findViewById(com.android.internal.R.id.time_current);
295 mFormatBuilder = new StringBuilder();
296 mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
297
298 installPrevNextListeners();
299 }
300
301 /**
302 * Show the controller on screen. It will go away
303 * automatically after 3 seconds of inactivity.
304 */
305 public void show() {
306 show(sDefaultTimeout);
307 }
308
309 /**
Marco Nelissenc818b142009-08-19 08:32:21 -0700310 * Disable pause or seek buttons if the stream cannot be paused or seeked.
311 * This requires the control interface to be a MediaPlayerControlExt
312 */
313 private void disableUnsupportedButtons() {
314 try {
315 if (mPauseButton != null && !mPlayer.canPause()) {
316 mPauseButton.setEnabled(false);
317 }
318 if (mRewButton != null && !mPlayer.canSeekBackward()) {
319 mRewButton.setEnabled(false);
320 }
321 if (mFfwdButton != null && !mPlayer.canSeekForward()) {
322 mFfwdButton.setEnabled(false);
323 }
324 } catch (IncompatibleClassChangeError ex) {
325 // We were given an old version of the interface, that doesn't have
326 // the canPause/canSeekXYZ methods. This is OK, it just means we
327 // assume the media can be paused and seeked, and so we don't disable
328 // the buttons.
329 }
330 }
331
332 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333 * Show the controller on screen. It will go away
334 * automatically after 'timeout' milliseconds of inactivity.
335 * @param timeout The timeout in milliseconds. Use 0 to show
336 * the controller until hide() is called.
337 */
338 public void show(int timeout) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800339 if (!mShowing && mAnchor != null) {
340 setProgress();
Marco Nelissend701e022009-08-19 15:39:23 -0700341 if (mPauseButton != null) {
342 mPauseButton.requestFocus();
343 }
Marco Nelissenc818b142009-08-19 08:32:21 -0700344 disableUnsupportedButtons();
Chih-Chung Chang85d4ea62011-09-28 17:57:10 +0800345 updateFloatingWindowLayout();
346 mWindowManager.addView(mDecor, mDecorLayoutParams);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800347 mShowing = true;
348 }
349 updatePausePlay();
350
351 // cause the progress bar to be updated even if mShowing
352 // was already true. This happens, for example, if we're
353 // paused with the progress bar showing the user hits play.
354 mHandler.sendEmptyMessage(SHOW_PROGRESS);
355
356 Message msg = mHandler.obtainMessage(FADE_OUT);
357 if (timeout != 0) {
358 mHandler.removeMessages(FADE_OUT);
359 mHandler.sendMessageDelayed(msg, timeout);
360 }
361 }
362
363 public boolean isShowing() {
364 return mShowing;
365 }
366
367 /**
368 * Remove the controller from the screen.
369 */
370 public void hide() {
371 if (mAnchor == null)
372 return;
373
374 if (mShowing) {
375 try {
376 mHandler.removeMessages(SHOW_PROGRESS);
377 mWindowManager.removeView(mDecor);
378 } catch (IllegalArgumentException ex) {
379 Log.w("MediaController", "already removed");
380 }
381 mShowing = false;
382 }
383 }
384
385 private Handler mHandler = new Handler() {
386 @Override
387 public void handleMessage(Message msg) {
388 int pos;
389 switch (msg.what) {
390 case FADE_OUT:
391 hide();
392 break;
393 case SHOW_PROGRESS:
394 pos = setProgress();
395 if (!mDragging && mShowing && mPlayer.isPlaying()) {
396 msg = obtainMessage(SHOW_PROGRESS);
397 sendMessageDelayed(msg, 1000 - (pos % 1000));
398 }
399 break;
400 }
401 }
402 };
403
404 private String stringForTime(int timeMs) {
405 int totalSeconds = timeMs / 1000;
406
407 int seconds = totalSeconds % 60;
408 int minutes = (totalSeconds / 60) % 60;
409 int hours = totalSeconds / 3600;
410
411 mFormatBuilder.setLength(0);
412 if (hours > 0) {
413 return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
414 } else {
415 return mFormatter.format("%02d:%02d", minutes, seconds).toString();
416 }
417 }
418
419 private int setProgress() {
420 if (mPlayer == null || mDragging) {
421 return 0;
422 }
423 int position = mPlayer.getCurrentPosition();
424 int duration = mPlayer.getDuration();
425 if (mProgress != null) {
426 if (duration > 0) {
427 // use long to avoid overflow
428 long pos = 1000L * position / duration;
429 mProgress.setProgress( (int) pos);
430 }
431 int percent = mPlayer.getBufferPercentage();
432 mProgress.setSecondaryProgress(percent * 10);
433 }
434
435 if (mEndTime != null)
436 mEndTime.setText(stringForTime(duration));
437 if (mCurrentTime != null)
438 mCurrentTime.setText(stringForTime(position));
439
440 return position;
441 }
442
443 @Override
444 public boolean onTouchEvent(MotionEvent event) {
Robert Shihcfe4b592013-11-06 11:53:56 -0800445 switch (event.getAction()) {
446 case MotionEvent.ACTION_DOWN:
447 show(0); // show until hide is called
448 break;
449 case MotionEvent.ACTION_UP:
450 show(sDefaultTimeout); // start timeout
451 break;
452 case MotionEvent.ACTION_CANCEL:
453 hide();
454 break;
455 default:
456 break;
457 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458 return true;
459 }
460
461 @Override
462 public boolean onTrackballEvent(MotionEvent ev) {
463 show(sDefaultTimeout);
464 return false;
465 }
466
467 @Override
468 public boolean dispatchKeyEvent(KeyEvent event) {
469 int keyCode = event.getKeyCode();
Jeff Brown4d396052010-10-29 21:50:21 -0700470 final boolean uniqueDown = event.getRepeatCount() == 0
471 && event.getAction() == KeyEvent.ACTION_DOWN;
472 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
473 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
474 || keyCode == KeyEvent.KEYCODE_SPACE) {
475 if (uniqueDown) {
476 doPauseResume();
477 show(sDefaultTimeout);
478 if (mPauseButton != null) {
479 mPauseButton.requestFocus();
480 }
Marco Nelissend701e022009-08-19 15:39:23 -0700481 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800482 return true;
Jeff Brown4d396052010-10-29 21:50:21 -0700483 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
484 if (uniqueDown && !mPlayer.isPlaying()) {
485 mPlayer.start();
486 updatePausePlay();
487 show(sDefaultTimeout);
488 }
489 return true;
490 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
491 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
492 if (uniqueDown && mPlayer.isPlaying()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 mPlayer.pause();
494 updatePausePlay();
Jeff Brown4d396052010-10-29 21:50:21 -0700495 show(sDefaultTimeout);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496 }
497 return true;
Jeff Brown4d396052010-10-29 21:50:21 -0700498 } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
Jeff Brownb0418da2010-11-01 15:24:01 -0700499 || keyCode == KeyEvent.KEYCODE_VOLUME_UP
bxu10X96ac7202012-04-10 16:52:36 +0800500 || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE
501 || keyCode == KeyEvent.KEYCODE_CAMERA) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800502 // don't show the controls for volume adjustment
503 return super.dispatchKeyEvent(event);
504 } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
Jeff Brown4d396052010-10-29 21:50:21 -0700505 if (uniqueDown) {
506 hide();
507 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700508 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800509 }
Jeff Brown4d396052010-10-29 21:50:21 -0700510
511 show(sDefaultTimeout);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 return super.dispatchKeyEvent(event);
513 }
514
515 private View.OnClickListener mPauseListener = new View.OnClickListener() {
516 public void onClick(View v) {
517 doPauseResume();
518 show(sDefaultTimeout);
519 }
520 };
521
522 private void updatePausePlay() {
Marco Nelissenc818b142009-08-19 08:32:21 -0700523 if (mRoot == null || mPauseButton == null)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 return;
525
526 if (mPlayer.isPlaying()) {
Marco Nelissenc818b142009-08-19 08:32:21 -0700527 mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_pause);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528 } else {
Marco Nelissenc818b142009-08-19 08:32:21 -0700529 mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_play);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800530 }
531 }
532
533 private void doPauseResume() {
534 if (mPlayer.isPlaying()) {
535 mPlayer.pause();
536 } else {
537 mPlayer.start();
538 }
539 updatePausePlay();
540 }
541
Andreas Huber0de7dcd2009-04-20 14:22:31 -0700542 // There are two scenarios that can trigger the seekbar listener to trigger:
543 //
544 // The first is the user using the touchpad to adjust the posititon of the
545 // seekbar's thumb. In this case onStartTrackingTouch is called followed by
546 // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
547 // We're setting the field "mDragging" to true for the duration of the dragging
548 // session to avoid jumps in the position in case of ongoing playback.
549 //
550 // The second scenario involves the user operating the scroll ball, in this
551 // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
552 // we will simply apply the updated position without suspending regular updates.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800553 private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554 public void onStartTrackingTouch(SeekBar bar) {
555 show(3600000);
Andreas Huber0de7dcd2009-04-20 14:22:31 -0700556
557 mDragging = true;
558
559 // By removing these pending progress messages we make sure
560 // that a) we won't update the progress while the user adjusts
561 // the seekbar and b) once the user is done dragging the thumb
562 // we will post one of these messages to the queue again and
563 // this ensures that there will be exactly one message queued up.
564 mHandler.removeMessages(SHOW_PROGRESS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565 }
Andreas Huber0de7dcd2009-04-20 14:22:31 -0700566
567 public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
568 if (!fromuser) {
569 // We're not interested in programmatically generated changes to
570 // the progress bar's position.
571 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800572 }
Andreas Huber0de7dcd2009-04-20 14:22:31 -0700573
574 long duration = mPlayer.getDuration();
575 long newposition = (duration * progress) / 1000L;
576 mPlayer.seekTo( (int) newposition);
577 if (mCurrentTime != null)
578 mCurrentTime.setText(stringForTime( (int) newposition));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 }
Andreas Huber0de7dcd2009-04-20 14:22:31 -0700580
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800581 public void onStopTrackingTouch(SeekBar bar) {
582 mDragging = false;
583 setProgress();
584 updatePausePlay();
585 show(sDefaultTimeout);
Andreas Huber0de7dcd2009-04-20 14:22:31 -0700586
587 // Ensure that progress is properly updated in the future,
588 // the call to show() does not guarantee this because it is a
589 // no-op if we are already showing.
590 mHandler.sendEmptyMessage(SHOW_PROGRESS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800591 }
592 };
593
594 @Override
595 public void setEnabled(boolean enabled) {
596 if (mPauseButton != null) {
597 mPauseButton.setEnabled(enabled);
598 }
599 if (mFfwdButton != null) {
600 mFfwdButton.setEnabled(enabled);
601 }
602 if (mRewButton != null) {
603 mRewButton.setEnabled(enabled);
604 }
605 if (mNextButton != null) {
606 mNextButton.setEnabled(enabled && mNextListener != null);
607 }
608 if (mPrevButton != null) {
609 mPrevButton.setEnabled(enabled && mPrevListener != null);
610 }
611 if (mProgress != null) {
612 mProgress.setEnabled(enabled);
613 }
Marco Nelissenc818b142009-08-19 08:32:21 -0700614 disableUnsupportedButtons();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800615 super.setEnabled(enabled);
616 }
617
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800618 @Override
619 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
620 super.onInitializeAccessibilityEvent(event);
621 event.setClassName(MediaController.class.getName());
622 }
623
624 @Override
625 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
626 super.onInitializeAccessibilityNodeInfo(info);
627 info.setClassName(MediaController.class.getName());
628 }
629
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630 private View.OnClickListener mRewListener = new View.OnClickListener() {
631 public void onClick(View v) {
632 int pos = mPlayer.getCurrentPosition();
633 pos -= 5000; // milliseconds
634 mPlayer.seekTo(pos);
635 setProgress();
636
637 show(sDefaultTimeout);
638 }
639 };
640
641 private View.OnClickListener mFfwdListener = new View.OnClickListener() {
642 public void onClick(View v) {
643 int pos = mPlayer.getCurrentPosition();
644 pos += 15000; // milliseconds
645 mPlayer.seekTo(pos);
646 setProgress();
647
648 show(sDefaultTimeout);
649 }
650 };
651
652 private void installPrevNextListeners() {
653 if (mNextButton != null) {
654 mNextButton.setOnClickListener(mNextListener);
655 mNextButton.setEnabled(mNextListener != null);
656 }
657
658 if (mPrevButton != null) {
659 mPrevButton.setOnClickListener(mPrevListener);
660 mPrevButton.setEnabled(mPrevListener != null);
661 }
662 }
663
664 public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {
665 mNextListener = next;
666 mPrevListener = prev;
667 mListenersSet = true;
668
669 if (mRoot != null) {
670 installPrevNextListeners();
671
672 if (mNextButton != null && !mFromXml) {
673 mNextButton.setVisibility(View.VISIBLE);
674 }
675 if (mPrevButton != null && !mFromXml) {
676 mPrevButton.setVisibility(View.VISIBLE);
677 }
678 }
679 }
680
681 public interface MediaPlayerControl {
682 void start();
683 void pause();
684 int getDuration();
685 int getCurrentPosition();
686 void seekTo(int pos);
687 boolean isPlaying();
688 int getBufferPercentage();
Marco Nelissenc818b142009-08-19 08:32:21 -0700689 boolean canPause();
690 boolean canSeekBackward();
691 boolean canSeekForward();
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700692
693 /**
694 * Get the audio session id for the player used by this VideoView. This can be used to
695 * apply audio effects to the audio track of a video.
696 * @return The audio session, or 0 if there was an error.
697 */
698 int getAudioSessionId();
Marco Nelissenc818b142009-08-19 08:32:21 -0700699 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800700}