blob: 9c61fd6997ea8f9b5013aa3e6afe84c311e6fe19 [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) {
445 show(sDefaultTimeout);
446 return true;
447 }
448
449 @Override
450 public boolean onTrackballEvent(MotionEvent ev) {
451 show(sDefaultTimeout);
452 return false;
453 }
454
455 @Override
456 public boolean dispatchKeyEvent(KeyEvent event) {
457 int keyCode = event.getKeyCode();
Jeff Brown4d396052010-10-29 21:50:21 -0700458 final boolean uniqueDown = event.getRepeatCount() == 0
459 && event.getAction() == KeyEvent.ACTION_DOWN;
460 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
461 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
462 || keyCode == KeyEvent.KEYCODE_SPACE) {
463 if (uniqueDown) {
464 doPauseResume();
465 show(sDefaultTimeout);
466 if (mPauseButton != null) {
467 mPauseButton.requestFocus();
468 }
Marco Nelissend701e022009-08-19 15:39:23 -0700469 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800470 return true;
Jeff Brown4d396052010-10-29 21:50:21 -0700471 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
472 if (uniqueDown && !mPlayer.isPlaying()) {
473 mPlayer.start();
474 updatePausePlay();
475 show(sDefaultTimeout);
476 }
477 return true;
478 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
479 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
480 if (uniqueDown && mPlayer.isPlaying()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800481 mPlayer.pause();
482 updatePausePlay();
Jeff Brown4d396052010-10-29 21:50:21 -0700483 show(sDefaultTimeout);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 }
485 return true;
Jeff Brown4d396052010-10-29 21:50:21 -0700486 } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
Jeff Brownb0418da2010-11-01 15:24:01 -0700487 || keyCode == KeyEvent.KEYCODE_VOLUME_UP
bxu10X96ac7202012-04-10 16:52:36 +0800488 || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE
489 || keyCode == KeyEvent.KEYCODE_CAMERA) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490 // don't show the controls for volume adjustment
491 return super.dispatchKeyEvent(event);
492 } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
Jeff Brown4d396052010-10-29 21:50:21 -0700493 if (uniqueDown) {
494 hide();
495 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700496 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 }
Jeff Brown4d396052010-10-29 21:50:21 -0700498
499 show(sDefaultTimeout);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500 return super.dispatchKeyEvent(event);
501 }
502
503 private View.OnClickListener mPauseListener = new View.OnClickListener() {
504 public void onClick(View v) {
505 doPauseResume();
506 show(sDefaultTimeout);
507 }
508 };
509
510 private void updatePausePlay() {
Marco Nelissenc818b142009-08-19 08:32:21 -0700511 if (mRoot == null || mPauseButton == null)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 return;
513
514 if (mPlayer.isPlaying()) {
Marco Nelissenc818b142009-08-19 08:32:21 -0700515 mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_pause);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 } else {
Marco Nelissenc818b142009-08-19 08:32:21 -0700517 mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_play);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800518 }
519 }
520
521 private void doPauseResume() {
522 if (mPlayer.isPlaying()) {
523 mPlayer.pause();
524 } else {
525 mPlayer.start();
526 }
527 updatePausePlay();
528 }
529
Andreas Huber0de7dcd2009-04-20 14:22:31 -0700530 // There are two scenarios that can trigger the seekbar listener to trigger:
531 //
532 // The first is the user using the touchpad to adjust the posititon of the
533 // seekbar's thumb. In this case onStartTrackingTouch is called followed by
534 // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
535 // We're setting the field "mDragging" to true for the duration of the dragging
536 // session to avoid jumps in the position in case of ongoing playback.
537 //
538 // The second scenario involves the user operating the scroll ball, in this
539 // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
540 // we will simply apply the updated position without suspending regular updates.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800541 private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 public void onStartTrackingTouch(SeekBar bar) {
543 show(3600000);
Andreas Huber0de7dcd2009-04-20 14:22:31 -0700544
545 mDragging = true;
546
547 // By removing these pending progress messages we make sure
548 // that a) we won't update the progress while the user adjusts
549 // the seekbar and b) once the user is done dragging the thumb
550 // we will post one of these messages to the queue again and
551 // this ensures that there will be exactly one message queued up.
552 mHandler.removeMessages(SHOW_PROGRESS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800553 }
Andreas Huber0de7dcd2009-04-20 14:22:31 -0700554
555 public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
556 if (!fromuser) {
557 // We're not interested in programmatically generated changes to
558 // the progress bar's position.
559 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560 }
Andreas Huber0de7dcd2009-04-20 14:22:31 -0700561
562 long duration = mPlayer.getDuration();
563 long newposition = (duration * progress) / 1000L;
564 mPlayer.seekTo( (int) newposition);
565 if (mCurrentTime != null)
566 mCurrentTime.setText(stringForTime( (int) newposition));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800567 }
Andreas Huber0de7dcd2009-04-20 14:22:31 -0700568
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 public void onStopTrackingTouch(SeekBar bar) {
570 mDragging = false;
571 setProgress();
572 updatePausePlay();
573 show(sDefaultTimeout);
Andreas Huber0de7dcd2009-04-20 14:22:31 -0700574
575 // Ensure that progress is properly updated in the future,
576 // the call to show() does not guarantee this because it is a
577 // no-op if we are already showing.
578 mHandler.sendEmptyMessage(SHOW_PROGRESS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 }
580 };
581
582 @Override
583 public void setEnabled(boolean enabled) {
584 if (mPauseButton != null) {
585 mPauseButton.setEnabled(enabled);
586 }
587 if (mFfwdButton != null) {
588 mFfwdButton.setEnabled(enabled);
589 }
590 if (mRewButton != null) {
591 mRewButton.setEnabled(enabled);
592 }
593 if (mNextButton != null) {
594 mNextButton.setEnabled(enabled && mNextListener != null);
595 }
596 if (mPrevButton != null) {
597 mPrevButton.setEnabled(enabled && mPrevListener != null);
598 }
599 if (mProgress != null) {
600 mProgress.setEnabled(enabled);
601 }
Marco Nelissenc818b142009-08-19 08:32:21 -0700602 disableUnsupportedButtons();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603 super.setEnabled(enabled);
604 }
605
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800606 @Override
607 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
608 super.onInitializeAccessibilityEvent(event);
609 event.setClassName(MediaController.class.getName());
610 }
611
612 @Override
613 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
614 super.onInitializeAccessibilityNodeInfo(info);
615 info.setClassName(MediaController.class.getName());
616 }
617
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 private View.OnClickListener mRewListener = new View.OnClickListener() {
619 public void onClick(View v) {
620 int pos = mPlayer.getCurrentPosition();
621 pos -= 5000; // milliseconds
622 mPlayer.seekTo(pos);
623 setProgress();
624
625 show(sDefaultTimeout);
626 }
627 };
628
629 private View.OnClickListener mFfwdListener = new View.OnClickListener() {
630 public void onClick(View v) {
631 int pos = mPlayer.getCurrentPosition();
632 pos += 15000; // milliseconds
633 mPlayer.seekTo(pos);
634 setProgress();
635
636 show(sDefaultTimeout);
637 }
638 };
639
640 private void installPrevNextListeners() {
641 if (mNextButton != null) {
642 mNextButton.setOnClickListener(mNextListener);
643 mNextButton.setEnabled(mNextListener != null);
644 }
645
646 if (mPrevButton != null) {
647 mPrevButton.setOnClickListener(mPrevListener);
648 mPrevButton.setEnabled(mPrevListener != null);
649 }
650 }
651
652 public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {
653 mNextListener = next;
654 mPrevListener = prev;
655 mListenersSet = true;
656
657 if (mRoot != null) {
658 installPrevNextListeners();
659
660 if (mNextButton != null && !mFromXml) {
661 mNextButton.setVisibility(View.VISIBLE);
662 }
663 if (mPrevButton != null && !mFromXml) {
664 mPrevButton.setVisibility(View.VISIBLE);
665 }
666 }
667 }
668
669 public interface MediaPlayerControl {
670 void start();
671 void pause();
672 int getDuration();
673 int getCurrentPosition();
674 void seekTo(int pos);
675 boolean isPlaying();
676 int getBufferPercentage();
Marco Nelissenc818b142009-08-19 08:32:21 -0700677 boolean canPause();
678 boolean canSeekBackward();
679 boolean canSeekForward();
Marco Nelissen13bfebd2013-05-09 15:36:53 -0700680
681 /**
682 * Get the audio session id for the player used by this VideoView. This can be used to
683 * apply audio effects to the audio track of a video.
684 * @return The audio session, or 0 if there was an error.
685 */
686 int getAudioSessionId();
Marco Nelissenc818b142009-08-19 08:32:21 -0700687 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688}