MediaController.java revision 1337277f8f8802064f42bed895695758f40b50d9
1/*
2 * Copyright (C) 2013 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 com.example.android.supportv4.media;
18
19import com.example.android.supportv4.R;
20
21import android.content.Context;
22import android.util.AttributeSet;
23import android.view.LayoutInflater;
24import android.view.View;
25import android.view.accessibility.AccessibilityEvent;
26import android.view.accessibility.AccessibilityNodeInfo;
27import android.widget.FrameLayout;
28import android.widget.ImageButton;
29import android.widget.ProgressBar;
30import android.widget.SeekBar;
31import android.widget.TextView;
32
33import java.util.Formatter;
34import java.util.Locale;
35
36/**
37 * Helper for implementing media controls in an application.
38 * Use instead of the very useful android.widget.MediaController.
39 * This version is embedded inside of an application's layout.
40 */
41public class MediaController extends FrameLayout {
42
43    private MediaPlayerControl  mPlayer;
44    private Context mContext;
45    private ProgressBar mProgress;
46    private TextView mEndTime, mCurrentTime;
47    private boolean             mDragging;
48    private boolean             mUseFastForward;
49    private boolean             mFromXml;
50    private boolean             mListenersSet;
51    private View.OnClickListener mNextListener, mPrevListener;
52    StringBuilder               mFormatBuilder;
53    Formatter mFormatter;
54    private ImageButton mPauseButton;
55    private ImageButton         mFfwdButton;
56    private ImageButton         mRewButton;
57    private ImageButton         mNextButton;
58    private ImageButton         mPrevButton;
59
60    public MediaController(Context context, AttributeSet attrs) {
61        super(context, attrs);
62        mContext = context;
63        mUseFastForward = true;
64        mFromXml = true;
65        LayoutInflater inflate = (LayoutInflater)
66                mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
67        inflate.inflate(R.layout.media_controller, this, true);
68        initControllerView();
69    }
70
71    public MediaController(Context context, boolean useFastForward) {
72        super(context);
73        mContext = context;
74        mUseFastForward = useFastForward;
75    }
76
77    public MediaController(Context context) {
78        this(context, true);
79    }
80
81    public void setMediaPlayer(MediaPlayerControl player) {
82        mPlayer = player;
83        updatePausePlay();
84    }
85
86    private void initControllerView() {
87        mPauseButton = (ImageButton) findViewById(R.id.pause);
88        if (mPauseButton != null) {
89            mPauseButton.requestFocus();
90            mPauseButton.setOnClickListener(mPauseListener);
91        }
92
93        mFfwdButton = (ImageButton) findViewById(R.id.ffwd);
94        if (mFfwdButton != null) {
95            mFfwdButton.setOnClickListener(mFfwdListener);
96            if (!mFromXml) {
97                mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
98            }
99        }
100
101        mRewButton = (ImageButton) findViewById(R.id.rew);
102        if (mRewButton != null) {
103            mRewButton.setOnClickListener(mRewListener);
104            if (!mFromXml) {
105                mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
106            }
107        }
108
109        // By default these are hidden. They will be enabled when setPrevNextListeners() is called
110        mNextButton = (ImageButton) findViewById(R.id.next);
111        if (mNextButton != null && !mFromXml && !mListenersSet) {
112            mNextButton.setVisibility(View.GONE);
113        }
114        mPrevButton = (ImageButton) findViewById(R.id.prev);
115        if (mPrevButton != null && !mFromXml && !mListenersSet) {
116            mPrevButton.setVisibility(View.GONE);
117        }
118
119        mProgress = (ProgressBar) findViewById(R.id.mediacontroller_progress);
120        if (mProgress != null) {
121            if (mProgress instanceof SeekBar) {
122                SeekBar seeker = (SeekBar) mProgress;
123                seeker.setOnSeekBarChangeListener(mSeekListener);
124            }
125            mProgress.setMax(1000);
126        }
127
128        mEndTime = (TextView) findViewById(R.id.time);
129        mCurrentTime = (TextView) findViewById(R.id.time_current);
130        mFormatBuilder = new StringBuilder();
131        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
132
133        installPrevNextListeners();
134    }
135
136    /**
137     * Disable pause or seek buttons if the stream cannot be paused or seeked.
138     * This requires the control interface to be a MediaPlayerControlExt
139     */
140    private void disableUnsupportedButtons() {
141        try {
142            if (mPauseButton != null && !mPlayer.canPause()) {
143                mPauseButton.setEnabled(false);
144            }
145            if (mRewButton != null && !mPlayer.canSeekBackward()) {
146                mRewButton.setEnabled(false);
147            }
148            if (mFfwdButton != null && !mPlayer.canSeekForward()) {
149                mFfwdButton.setEnabled(false);
150            }
151        } catch (IncompatibleClassChangeError ex) {
152            // We were given an old version of the interface, that doesn't have
153            // the canPause/canSeekXYZ methods. This is OK, it just means we
154            // assume the media can be paused and seeked, and so we don't disable
155            // the buttons.
156        }
157    }
158
159    public void refresh() {
160        updateProgress();
161        disableUnsupportedButtons();
162        updatePausePlay();
163    }
164
165    private String stringForTime(int timeMs) {
166        int totalSeconds = timeMs / 1000;
167
168        int seconds = totalSeconds % 60;
169        int minutes = (totalSeconds / 60) % 60;
170        int hours   = totalSeconds / 3600;
171
172        mFormatBuilder.setLength(0);
173        if (hours > 0) {
174            return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
175        } else {
176            return mFormatter.format("%02d:%02d", minutes, seconds).toString();
177        }
178    }
179
180    public int updateProgress() {
181        if (mPlayer == null || mDragging) {
182            return 0;
183        }
184        int position = mPlayer.getCurrentPosition();
185        int duration = mPlayer.getDuration();
186        if (mProgress != null) {
187            if (duration > 0) {
188                // use long to avoid overflow
189                long pos = 1000L * position / duration;
190                mProgress.setProgress( (int) pos);
191            }
192            int percent = mPlayer.getBufferPercentage();
193            mProgress.setSecondaryProgress(percent * 10);
194        }
195
196        if (mEndTime != null)
197            mEndTime.setText(stringForTime(duration));
198        if (mCurrentTime != null)
199            mCurrentTime.setText(stringForTime(position));
200
201        return position;
202    }
203
204    private View.OnClickListener mPauseListener = new View.OnClickListener() {
205        public void onClick(View v) {
206            doPauseResume();
207        }
208    };
209
210    private void updatePausePlay() {
211        if (mPauseButton == null)
212            return;
213
214        if (mPlayer.isPlaying()) {
215            mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
216        } else {
217            mPauseButton.setImageResource(android.R.drawable.ic_media_play);
218        }
219    }
220
221    private void doPauseResume() {
222        if (mPlayer.isPlaying()) {
223            mPlayer.pause();
224        } else {
225            mPlayer.start();
226        }
227        updatePausePlay();
228    }
229
230    // There are two scenarios that can trigger the seekbar listener to trigger:
231    //
232    // The first is the user using the touchpad to adjust the posititon of the
233    // seekbar's thumb. In this case onStartTrackingTouch is called followed by
234    // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
235    // We're setting the field "mDragging" to true for the duration of the dragging
236    // session to avoid jumps in the position in case of ongoing playback.
237    //
238    // The second scenario involves the user operating the scroll ball, in this
239    // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
240    // we will simply apply the updated position without suspending regular updates.
241    private SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
242        public void onStartTrackingTouch(SeekBar bar) {
243            mDragging = true;
244        }
245
246        public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
247            if (!fromuser) {
248                // We're not interested in programmatically generated changes to
249                // the progress bar's position.
250                return;
251            }
252
253            long duration = mPlayer.getDuration();
254            long newposition = (duration * progress) / 1000L;
255            mPlayer.seekTo( (int) newposition);
256            if (mCurrentTime != null)
257                mCurrentTime.setText(stringForTime( (int) newposition));
258        }
259
260        public void onStopTrackingTouch(SeekBar bar) {
261            mDragging = false;
262            updateProgress();
263            updatePausePlay();
264        }
265    };
266
267    @Override
268    public void setEnabled(boolean enabled) {
269        if (mPauseButton != null) {
270            mPauseButton.setEnabled(enabled);
271        }
272        if (mFfwdButton != null) {
273            mFfwdButton.setEnabled(enabled);
274        }
275        if (mRewButton != null) {
276            mRewButton.setEnabled(enabled);
277        }
278        if (mNextButton != null) {
279            mNextButton.setEnabled(enabled && mNextListener != null);
280        }
281        if (mPrevButton != null) {
282            mPrevButton.setEnabled(enabled && mPrevListener != null);
283        }
284        if (mProgress != null) {
285            mProgress.setEnabled(enabled);
286        }
287        disableUnsupportedButtons();
288        super.setEnabled(enabled);
289    }
290
291    @Override
292    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
293        super.onInitializeAccessibilityEvent(event);
294        event.setClassName(MediaController.class.getName());
295    }
296
297    @Override
298    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
299        super.onInitializeAccessibilityNodeInfo(info);
300        info.setClassName(MediaController.class.getName());
301    }
302
303    private View.OnClickListener mRewListener = new View.OnClickListener() {
304        public void onClick(View v) {
305            int pos = mPlayer.getCurrentPosition();
306            pos -= 5000; // milliseconds
307            mPlayer.seekTo(pos);
308            updateProgress();
309        }
310    };
311
312    private View.OnClickListener mFfwdListener = new View.OnClickListener() {
313        public void onClick(View v) {
314            int pos = mPlayer.getCurrentPosition();
315            pos += 15000; // milliseconds
316            mPlayer.seekTo(pos);
317            updateProgress();
318        }
319    };
320
321    private void installPrevNextListeners() {
322        if (mNextButton != null) {
323            mNextButton.setOnClickListener(mNextListener);
324            mNextButton.setEnabled(mNextListener != null);
325        }
326
327        if (mPrevButton != null) {
328            mPrevButton.setOnClickListener(mPrevListener);
329            mPrevButton.setEnabled(mPrevListener != null);
330        }
331    }
332
333    public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {
334        mNextListener = next;
335        mPrevListener = prev;
336        mListenersSet = true;
337
338        installPrevNextListeners();
339
340        if (mNextButton != null && !mFromXml) {
341            mNextButton.setVisibility(View.VISIBLE);
342        }
343        if (mPrevButton != null && !mFromXml) {
344            mPrevButton.setVisibility(View.VISIBLE);
345        }
346    }
347
348    public interface MediaPlayerControl {
349        void    start();
350        void    pause();
351        int     getDuration();
352        int     getCurrentPosition();
353        void    seekTo(int pos);
354        boolean isPlaying();
355        int     getBufferPercentage();
356        boolean canPause();
357        boolean canSeekBackward();
358        boolean canSeekForward();
359    }
360}
361