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