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