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 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 84 public MediaController(Context context, boolean useFastForward) { 85 super(context); 86 mContext = context; 87 mUseFastForward = useFastForward; 88 } 89 90 public MediaController(Context context) { 91 this(context, true); 92 } 93 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 108 protected void onAttachedToWindow() { 109 super.onAttachedToWindow(); 110 if (mController != null) { 111 mController.registerStateListener(mStateListener); 112 } 113 } 114 115 @Override 116 protected void onDetachedFromWindow() { 117 super.onDetachedFromWindow(); 118 if (mController != null) { 119 mController.unregisterStateListener(mStateListener); 120 } 121 } 122 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 */ 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 198 public void refresh() { 199 updateProgress(); 200 updateButtons(); 201 updatePausePlay(); 202 } 203 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 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 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 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 307 public void setEnabled(boolean enabled) { 308 super.setEnabled(enabled); 309 updateButtons(); 310 } 311 312 @Override 313 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 314 super.onInitializeAccessibilityEvent(event); 315 event.setClassName(MediaController.class.getName()); 316 } 317 318 @Override 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 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 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