| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.widget; |
| |
| import android.compat.annotation.UnsupportedAppUsage; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.PixelFormat; |
| import android.media.AudioManager; |
| import android.os.Build; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.Gravity; |
| import android.view.KeyEvent; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.Window; |
| import android.view.WindowManager; |
| import android.view.accessibility.AccessibilityManager; |
| import android.widget.SeekBar.OnSeekBarChangeListener; |
| |
| import com.android.internal.policy.PhoneWindow; |
| |
| import java.util.Formatter; |
| import java.util.Locale; |
| |
| /** |
| * A view containing controls for a MediaPlayer. Typically contains the |
| * buttons like "Play/Pause", "Rewind", "Fast Forward" and a progress |
| * slider. It takes care of synchronizing the controls with the state |
| * of the MediaPlayer. |
| * <p> |
| * The way to use this class is to instantiate it programmatically. |
| * The MediaController will create a default set of controls |
| * and put them in a window floating above your application. Specifically, |
| * the controls will float above the view specified with setAnchorView(). |
| * The window will disappear if left idle for three seconds and reappear |
| * when the user touches the anchor view. |
| * <p> |
| * Functions like show() and hide() have no effect when MediaController |
| * is created in an xml layout. |
| * |
| * MediaController will hide and |
| * show the buttons according to these rules: |
| * <ul> |
| * <li> The "previous" and "next" buttons are hidden until setPrevNextListeners() |
| * has been called |
| * <li> The "previous" and "next" buttons are visible but disabled if |
| * setPrevNextListeners() was called with null listeners |
| * <li> The "rewind" and "fastforward" buttons are shown unless requested |
| * otherwise by using the MediaController(Context, boolean) constructor |
| * with the boolean set to false |
| * </ul> |
| */ |
| public class MediaController extends FrameLayout { |
| |
| @UnsupportedAppUsage |
| private MediaPlayerControl mPlayer; |
| @UnsupportedAppUsage |
| private final Context mContext; |
| @UnsupportedAppUsage |
| private View mAnchor; |
| @UnsupportedAppUsage |
| private View mRoot; |
| @UnsupportedAppUsage |
| private WindowManager mWindowManager; |
| @UnsupportedAppUsage |
| private Window mWindow; |
| @UnsupportedAppUsage |
| private View mDecor; |
| @UnsupportedAppUsage |
| private WindowManager.LayoutParams mDecorLayoutParams; |
| @UnsupportedAppUsage |
| private ProgressBar mProgress; |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) |
| private TextView mEndTime; |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) |
| private TextView mCurrentTime; |
| @UnsupportedAppUsage |
| private boolean mShowing; |
| private boolean mDragging; |
| private static final int sDefaultTimeout = 3000; |
| private final boolean mUseFastForward; |
| private boolean mFromXml; |
| private boolean mListenersSet; |
| private View.OnClickListener mNextListener, mPrevListener; |
| StringBuilder mFormatBuilder; |
| Formatter mFormatter; |
| @UnsupportedAppUsage |
| private ImageButton mPauseButton; |
| @UnsupportedAppUsage |
| private ImageButton mFfwdButton; |
| @UnsupportedAppUsage |
| private ImageButton mRewButton; |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) |
| private ImageButton mNextButton; |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) |
| private ImageButton mPrevButton; |
| private CharSequence mPlayDescription; |
| private CharSequence mPauseDescription; |
| private final AccessibilityManager mAccessibilityManager; |
| |
| public MediaController(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| mRoot = this; |
| mContext = context; |
| mUseFastForward = true; |
| mFromXml = true; |
| mAccessibilityManager = AccessibilityManager.getInstance(context); |
| } |
| |
| @Override |
| public void onFinishInflate() { |
| if (mRoot != null) |
| initControllerView(mRoot); |
| } |
| |
| public MediaController(Context context, boolean useFastForward) { |
| super(context); |
| mContext = context; |
| mUseFastForward = useFastForward; |
| initFloatingWindowLayout(); |
| initFloatingWindow(); |
| mAccessibilityManager = AccessibilityManager.getInstance(context); |
| } |
| |
| public MediaController(Context context) { |
| this(context, true); |
| } |
| |
| private void initFloatingWindow() { |
| mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); |
| mWindow = new PhoneWindow(mContext); |
| mWindow.setWindowManager(mWindowManager, null, null); |
| mWindow.requestFeature(Window.FEATURE_NO_TITLE); |
| mDecor = mWindow.getDecorView(); |
| mDecor.setOnTouchListener(mTouchListener); |
| mWindow.setContentView(this); |
| mWindow.setBackgroundDrawableResource(android.R.color.transparent); |
| |
| // While the media controller is up, the volume control keys should |
| // affect the media stream type |
| mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC); |
| |
| setFocusable(true); |
| setFocusableInTouchMode(true); |
| setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); |
| requestFocus(); |
| } |
| |
| // Allocate and initialize the static parts of mDecorLayoutParams. Must |
| // also call updateFloatingWindowLayout() to fill in the dynamic parts |
| // (y and width) before mDecorLayoutParams can be used. |
| private void initFloatingWindowLayout() { |
| mDecorLayoutParams = new WindowManager.LayoutParams(); |
| WindowManager.LayoutParams p = mDecorLayoutParams; |
| p.gravity = Gravity.TOP | Gravity.LEFT; |
| p.height = LayoutParams.WRAP_CONTENT; |
| p.x = 0; |
| p.format = PixelFormat.TRANSLUCENT; |
| p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; |
| p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
| | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
| | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; |
| p.token = null; |
| p.windowAnimations = 0; // android.R.style.DropDownAnimationDown; |
| } |
| |
| // Update the dynamic parts of mDecorLayoutParams |
| // Must be called with mAnchor != NULL. |
| private void updateFloatingWindowLayout() { |
| int [] anchorPos = new int[2]; |
| mAnchor.getLocationOnScreen(anchorPos); |
| |
| // we need to know the size of the controller so we can properly position it |
| // within its space |
| mDecor.measure(MeasureSpec.makeMeasureSpec(mAnchor.getWidth(), MeasureSpec.AT_MOST), |
| MeasureSpec.makeMeasureSpec(mAnchor.getHeight(), MeasureSpec.AT_MOST)); |
| |
| WindowManager.LayoutParams p = mDecorLayoutParams; |
| p.width = mAnchor.getWidth(); |
| p.x = anchorPos[0] + (mAnchor.getWidth() - p.width) / 2; |
| p.y = anchorPos[1] + mAnchor.getHeight() - mDecor.getMeasuredHeight(); |
| } |
| |
| // This is called whenever mAnchor's layout bound changes |
| private final OnLayoutChangeListener mLayoutChangeListener = |
| new OnLayoutChangeListener() { |
| @Override |
| public void onLayoutChange(View v, int left, int top, int right, |
| int bottom, int oldLeft, int oldTop, int oldRight, |
| int oldBottom) { |
| updateFloatingWindowLayout(); |
| if (mShowing) { |
| mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams); |
| } |
| } |
| }; |
| |
| private final OnTouchListener mTouchListener = new OnTouchListener() { |
| @Override |
| public boolean onTouch(View v, MotionEvent event) { |
| if (event.getAction() == MotionEvent.ACTION_DOWN) { |
| if (mShowing) { |
| hide(); |
| } |
| } |
| return false; |
| } |
| }; |
| |
| public void setMediaPlayer(MediaPlayerControl player) { |
| mPlayer = player; |
| updatePausePlay(); |
| } |
| |
| /** |
| * Set the view that acts as the anchor for the control view. |
| * This can for example be a VideoView, or your Activity's main view. |
| * When VideoView calls this method, it will use the VideoView's parent |
| * as the anchor. |
| * @param view The view to which to anchor the controller when it is visible. |
| */ |
| public void setAnchorView(View view) { |
| if (mAnchor != null) { |
| mAnchor.removeOnLayoutChangeListener(mLayoutChangeListener); |
| } |
| mAnchor = view; |
| if (mAnchor != null) { |
| mAnchor.addOnLayoutChangeListener(mLayoutChangeListener); |
| } |
| |
| FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams( |
| ViewGroup.LayoutParams.MATCH_PARENT, |
| ViewGroup.LayoutParams.MATCH_PARENT |
| ); |
| |
| removeAllViews(); |
| View v = makeControllerView(); |
| addView(v, frameParams); |
| } |
| |
| /** |
| * Create the view that holds the widgets that control playback. |
| * Derived classes can override this to create their own. |
| * @return The controller view. |
| * @hide This doesn't work as advertised |
| */ |
| protected View makeControllerView() { |
| LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
| mRoot = inflate.inflate(com.android.internal.R.layout.media_controller, null); |
| |
| initControllerView(mRoot); |
| |
| return mRoot; |
| } |
| |
| private void initControllerView(View v) { |
| Resources res = mContext.getResources(); |
| mPlayDescription = res |
| .getText(com.android.internal.R.string.lockscreen_transport_play_description); |
| mPauseDescription = res |
| .getText(com.android.internal.R.string.lockscreen_transport_pause_description); |
| mPauseButton = v.findViewById(com.android.internal.R.id.pause); |
| if (mPauseButton != null) { |
| mPauseButton.requestFocus(); |
| mPauseButton.setOnClickListener(mPauseListener); |
| } |
| |
| mFfwdButton = v.findViewById(com.android.internal.R.id.ffwd); |
| if (mFfwdButton != null) { |
| mFfwdButton.setOnClickListener(mFfwdListener); |
| if (!mFromXml) { |
| mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE); |
| } |
| } |
| |
| mRewButton = v.findViewById(com.android.internal.R.id.rew); |
| if (mRewButton != null) { |
| mRewButton.setOnClickListener(mRewListener); |
| if (!mFromXml) { |
| mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE); |
| } |
| } |
| |
| // By default these are hidden. They will be enabled when setPrevNextListeners() is called |
| mNextButton = v.findViewById(com.android.internal.R.id.next); |
| if (mNextButton != null && !mFromXml && !mListenersSet) { |
| mNextButton.setVisibility(View.GONE); |
| } |
| mPrevButton = v.findViewById(com.android.internal.R.id.prev); |
| if (mPrevButton != null && !mFromXml && !mListenersSet) { |
| mPrevButton.setVisibility(View.GONE); |
| } |
| |
| mProgress = v.findViewById(com.android.internal.R.id.mediacontroller_progress); |
| if (mProgress != null) { |
| if (mProgress instanceof SeekBar) { |
| SeekBar seeker = (SeekBar) mProgress; |
| seeker.setOnSeekBarChangeListener(mSeekListener); |
| } |
| mProgress.setMax(1000); |
| } |
| |
| mEndTime = v.findViewById(com.android.internal.R.id.time); |
| mCurrentTime = v.findViewById(com.android.internal.R.id.time_current); |
| mFormatBuilder = new StringBuilder(); |
| mFormatter = new Formatter(mFormatBuilder, Locale.getDefault()); |
| |
| installPrevNextListeners(); |
| } |
| |
| /** |
| * Show the controller on screen. It will go away |
| * automatically after 3 seconds of inactivity. |
| */ |
| public void show() { |
| show(sDefaultTimeout); |
| } |
| |
| /** |
| * Disable pause or seek buttons if the stream cannot be paused or seeked. |
| * This requires the control interface to be a MediaPlayerControlExt |
| */ |
| private void disableUnsupportedButtons() { |
| try { |
| if (mPauseButton != null && !mPlayer.canPause()) { |
| mPauseButton.setEnabled(false); |
| } |
| if (mRewButton != null && !mPlayer.canSeekBackward()) { |
| mRewButton.setEnabled(false); |
| } |
| if (mFfwdButton != null && !mPlayer.canSeekForward()) { |
| mFfwdButton.setEnabled(false); |
| } |
| // TODO What we really should do is add a canSeek to the MediaPlayerControl interface; |
| // this scheme can break the case when applications want to allow seek through the |
| // progress bar but disable forward/backward buttons. |
| // |
| // However, currently the flags SEEK_BACKWARD_AVAILABLE, SEEK_FORWARD_AVAILABLE, |
| // and SEEK_AVAILABLE are all (un)set together; as such the aforementioned issue |
| // shouldn't arise in existing applications. |
| if (mProgress != null && !mPlayer.canSeekBackward() && !mPlayer.canSeekForward()) { |
| mProgress.setEnabled(false); |
| } |
| } catch (IncompatibleClassChangeError ex) { |
| // We were given an old version of the interface, that doesn't have |
| // the canPause/canSeekXYZ methods. This is OK, it just means we |
| // assume the media can be paused and seeked, and so we don't disable |
| // the buttons. |
| } |
| } |
| |
| /** |
| * Show the controller on screen. It will go away |
| * automatically after 'timeout' milliseconds of inactivity. |
| * @param timeout The timeout in milliseconds. Use 0 to show |
| * the controller until hide() is called. |
| */ |
| public void show(int timeout) { |
| if (!mShowing && mAnchor != null) { |
| setProgress(); |
| if (mPauseButton != null) { |
| mPauseButton.requestFocus(); |
| } |
| disableUnsupportedButtons(); |
| updateFloatingWindowLayout(); |
| mWindowManager.addView(mDecor, mDecorLayoutParams); |
| mShowing = true; |
| } |
| updatePausePlay(); |
| |
| // cause the progress bar to be updated even if mShowing |
| // was already true. This happens, for example, if we're |
| // paused with the progress bar showing the user hits play. |
| post(mShowProgress); |
| |
| if (timeout != 0 && !mAccessibilityManager.isTouchExplorationEnabled()) { |
| removeCallbacks(mFadeOut); |
| postDelayed(mFadeOut, timeout); |
| } |
| } |
| |
| public boolean isShowing() { |
| return mShowing; |
| } |
| |
| /** |
| * Remove the controller from the screen. |
| */ |
| public void hide() { |
| if (mAnchor == null) |
| return; |
| |
| if (mShowing) { |
| try { |
| removeCallbacks(mShowProgress); |
| mWindowManager.removeView(mDecor); |
| } catch (IllegalArgumentException ex) { |
| Log.w("MediaController", "already removed"); |
| } |
| mShowing = false; |
| } |
| } |
| |
| private final Runnable mFadeOut = new Runnable() { |
| @Override |
| public void run() { |
| hide(); |
| } |
| }; |
| |
| private final Runnable mShowProgress = new Runnable() { |
| @Override |
| public void run() { |
| int pos = setProgress(); |
| if (!mDragging && mShowing && mPlayer.isPlaying()) { |
| postDelayed(mShowProgress, 1000 - (pos % 1000)); |
| } |
| } |
| }; |
| |
| private String stringForTime(int timeMs) { |
| int totalSeconds = timeMs / 1000; |
| |
| int seconds = totalSeconds % 60; |
| int minutes = (totalSeconds / 60) % 60; |
| int hours = totalSeconds / 3600; |
| |
| mFormatBuilder.setLength(0); |
| if (hours > 0) { |
| return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString(); |
| } else { |
| return mFormatter.format("%02d:%02d", minutes, seconds).toString(); |
| } |
| } |
| |
| private int setProgress() { |
| if (mPlayer == null || mDragging) { |
| return 0; |
| } |
| int position = mPlayer.getCurrentPosition(); |
| int duration = mPlayer.getDuration(); |
| if (mProgress != null) { |
| if (duration > 0) { |
| // use long to avoid overflow |
| long pos = 1000L * position / duration; |
| mProgress.setProgress( (int) pos); |
| } |
| int percent = mPlayer.getBufferPercentage(); |
| mProgress.setSecondaryProgress(percent * 10); |
| } |
| |
| if (mEndTime != null) |
| mEndTime.setText(stringForTime(duration)); |
| if (mCurrentTime != null) |
| mCurrentTime.setText(stringForTime(position)); |
| |
| return position; |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| switch (event.getAction()) { |
| case MotionEvent.ACTION_DOWN: |
| show(0); // show until hide is called |
| break; |
| case MotionEvent.ACTION_UP: |
| show(sDefaultTimeout); // start timeout |
| break; |
| case MotionEvent.ACTION_CANCEL: |
| hide(); |
| break; |
| default: |
| break; |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean onTrackballEvent(MotionEvent ev) { |
| show(sDefaultTimeout); |
| return false; |
| } |
| |
| @Override |
| public boolean dispatchKeyEvent(KeyEvent event) { |
| int keyCode = event.getKeyCode(); |
| final boolean uniqueDown = event.getRepeatCount() == 0 |
| && event.getAction() == KeyEvent.ACTION_DOWN; |
| if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK |
| || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE |
| || keyCode == KeyEvent.KEYCODE_SPACE) { |
| if (uniqueDown) { |
| doPauseResume(); |
| show(sDefaultTimeout); |
| if (mPauseButton != null) { |
| mPauseButton.requestFocus(); |
| } |
| } |
| return true; |
| } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { |
| if (uniqueDown && !mPlayer.isPlaying()) { |
| mPlayer.start(); |
| updatePausePlay(); |
| show(sDefaultTimeout); |
| } |
| return true; |
| } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP |
| || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { |
| if (uniqueDown && mPlayer.isPlaying()) { |
| mPlayer.pause(); |
| updatePausePlay(); |
| show(sDefaultTimeout); |
| } |
| return true; |
| } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN |
| || keyCode == KeyEvent.KEYCODE_VOLUME_UP |
| || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE |
| || keyCode == KeyEvent.KEYCODE_CAMERA) { |
| // don't show the controls for volume adjustment |
| return super.dispatchKeyEvent(event); |
| } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) { |
| if (uniqueDown) { |
| hide(); |
| } |
| return true; |
| } |
| |
| show(sDefaultTimeout); |
| return super.dispatchKeyEvent(event); |
| } |
| |
| private final View.OnClickListener mPauseListener = new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| doPauseResume(); |
| show(sDefaultTimeout); |
| } |
| }; |
| |
| @UnsupportedAppUsage |
| private void updatePausePlay() { |
| if (mRoot == null || mPauseButton == null) |
| return; |
| |
| if (mPlayer.isPlaying()) { |
| mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_pause); |
| mPauseButton.setContentDescription(mPauseDescription); |
| } else { |
| mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_play); |
| mPauseButton.setContentDescription(mPlayDescription); |
| } |
| } |
| |
| private void doPauseResume() { |
| if (mPlayer.isPlaying()) { |
| mPlayer.pause(); |
| } else { |
| mPlayer.start(); |
| } |
| updatePausePlay(); |
| } |
| |
| // There are two scenarios that can trigger the seekbar listener to trigger: |
| // |
| // The first is the user using the touchpad to adjust the posititon of the |
| // seekbar's thumb. In this case onStartTrackingTouch is called followed by |
| // a number of onProgressChanged notifications, concluded by onStopTrackingTouch. |
| // We're setting the field "mDragging" to true for the duration of the dragging |
| // session to avoid jumps in the position in case of ongoing playback. |
| // |
| // The second scenario involves the user operating the scroll ball, in this |
| // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications, |
| // we will simply apply the updated position without suspending regular updates. |
| @UnsupportedAppUsage |
| private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { |
| @Override |
| public void onStartTrackingTouch(SeekBar bar) { |
| show(3600000); |
| |
| mDragging = true; |
| |
| // By removing these pending progress messages we make sure |
| // that a) we won't update the progress while the user adjusts |
| // the seekbar and b) once the user is done dragging the thumb |
| // we will post one of these messages to the queue again and |
| // this ensures that there will be exactly one message queued up. |
| removeCallbacks(mShowProgress); |
| } |
| |
| @Override |
| public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) { |
| if (!fromuser) { |
| // We're not interested in programmatically generated changes to |
| // the progress bar's position. |
| return; |
| } |
| |
| long duration = mPlayer.getDuration(); |
| long newposition = (duration * progress) / 1000L; |
| mPlayer.seekTo( (int) newposition); |
| if (mCurrentTime != null) |
| mCurrentTime.setText(stringForTime( (int) newposition)); |
| } |
| |
| @Override |
| public void onStopTrackingTouch(SeekBar bar) { |
| mDragging = false; |
| setProgress(); |
| updatePausePlay(); |
| show(sDefaultTimeout); |
| |
| // Ensure that progress is properly updated in the future, |
| // the call to show() does not guarantee this because it is a |
| // no-op if we are already showing. |
| post(mShowProgress); |
| } |
| }; |
| |
| @Override |
| public void setEnabled(boolean enabled) { |
| if (mPauseButton != null) { |
| mPauseButton.setEnabled(enabled); |
| } |
| if (mFfwdButton != null) { |
| mFfwdButton.setEnabled(enabled); |
| } |
| if (mRewButton != null) { |
| mRewButton.setEnabled(enabled); |
| } |
| if (mNextButton != null) { |
| mNextButton.setEnabled(enabled && mNextListener != null); |
| } |
| if (mPrevButton != null) { |
| mPrevButton.setEnabled(enabled && mPrevListener != null); |
| } |
| if (mProgress != null) { |
| mProgress.setEnabled(enabled); |
| } |
| disableUnsupportedButtons(); |
| super.setEnabled(enabled); |
| } |
| |
| @Override |
| public CharSequence getAccessibilityClassName() { |
| return MediaController.class.getName(); |
| } |
| |
| @UnsupportedAppUsage |
| private final View.OnClickListener mRewListener = new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| int pos = mPlayer.getCurrentPosition(); |
| pos -= 5000; // milliseconds |
| mPlayer.seekTo(pos); |
| setProgress(); |
| |
| show(sDefaultTimeout); |
| } |
| }; |
| |
| @UnsupportedAppUsage |
| private final View.OnClickListener mFfwdListener = new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| int pos = mPlayer.getCurrentPosition(); |
| pos += 15000; // milliseconds |
| mPlayer.seekTo(pos); |
| setProgress(); |
| |
| show(sDefaultTimeout); |
| } |
| }; |
| |
| private void installPrevNextListeners() { |
| if (mNextButton != null) { |
| mNextButton.setOnClickListener(mNextListener); |
| mNextButton.setEnabled(mNextListener != null); |
| } |
| |
| if (mPrevButton != null) { |
| mPrevButton.setOnClickListener(mPrevListener); |
| mPrevButton.setEnabled(mPrevListener != null); |
| } |
| } |
| |
| public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) { |
| mNextListener = next; |
| mPrevListener = prev; |
| mListenersSet = true; |
| |
| if (mRoot != null) { |
| installPrevNextListeners(); |
| |
| if (mNextButton != null && !mFromXml) { |
| mNextButton.setVisibility(View.VISIBLE); |
| } |
| if (mPrevButton != null && !mFromXml) { |
| mPrevButton.setVisibility(View.VISIBLE); |
| } |
| } |
| } |
| |
| public interface MediaPlayerControl { |
| void start(); |
| void pause(); |
| int getDuration(); |
| int getCurrentPosition(); |
| void seekTo(int pos); |
| boolean isPlaying(); |
| int getBufferPercentage(); |
| boolean canPause(); |
| boolean canSeekBackward(); |
| boolean canSeekForward(); |
| |
| /** |
| * Get the audio session id for the player used by this VideoView. This can be used to |
| * apply audio effects to the audio track of a video. |
| * @return The audio session, or 0 if there was an error. |
| */ |
| int getAudioSessionId(); |
| } |
| } |