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