1 /* 2 * Copyright 2017 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.mediasession.ui; 18 19 import android.animation.ValueAnimator; 20 import android.content.Context; 21 import android.support.v4.media.MediaMetadataCompat; 22 import android.support.v4.media.session.MediaControllerCompat; 23 import android.support.v4.media.session.MediaSessionCompat; 24 import android.support.v4.media.session.PlaybackStateCompat; 25 import android.support.v7.widget.AppCompatSeekBar; 26 import android.util.AttributeSet; 27 import android.view.animation.LinearInterpolator; 28 import android.widget.SeekBar; 29 30 /** 31 * SeekBar that can be used with a {@link MediaSessionCompat} to track and seek in playing 32 * media. 33 */ 34 35 public class MediaSeekBar extends AppCompatSeekBar { 36 private MediaControllerCompat mMediaController; 37 private ControllerCallback mControllerCallback; 38 39 private boolean mIsTracking = false; 40 private OnSeekBarChangeListener mOnSeekBarChangeListener = new OnSeekBarChangeListener() { 41 @Override 42 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 43 } 44 45 @Override 46 public void onStartTrackingTouch(SeekBar seekBar) { 47 mIsTracking = true; 48 } 49 50 @Override 51 public void onStopTrackingTouch(SeekBar seekBar) { 52 mMediaController.getTransportControls().seekTo(getProgress()); 53 mIsTracking = false; 54 } 55 }; 56 private ValueAnimator mProgressAnimator; 57 58 public MediaSeekBar(Context context) { 59 super(context); 60 super.setOnSeekBarChangeListener(mOnSeekBarChangeListener); 61 } 62 63 public MediaSeekBar(Context context, AttributeSet attrs) { 64 super(context, attrs); 65 super.setOnSeekBarChangeListener(mOnSeekBarChangeListener); 66 } 67 68 public MediaSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { 69 super(context, attrs, defStyleAttr); 70 super.setOnSeekBarChangeListener(mOnSeekBarChangeListener); 71 } 72 73 @Override 74 public final void setOnSeekBarChangeListener(OnSeekBarChangeListener l) { 75 // Prohibit adding seek listeners to this subclass. 76 throw new UnsupportedOperationException("Cannot add listeners to a MediaSeekBar"); 77 } 78 79 public void setMediaController(final MediaControllerCompat mediaController) { 80 if (mediaController != null) { 81 mControllerCallback = new ControllerCallback(); 82 mediaController.registerCallback(mControllerCallback); 83 } else if (mMediaController != null) { 84 mMediaController.unregisterCallback(mControllerCallback); 85 mControllerCallback = null; 86 } 87 mMediaController = mediaController; 88 } 89 90 public void disconnectController() { 91 if (mMediaController != null) { 92 mMediaController.unregisterCallback(mControllerCallback); 93 mControllerCallback = null; 94 mMediaController = null; 95 } 96 } 97 98 private class ControllerCallback 99 extends MediaControllerCompat.Callback 100 implements ValueAnimator.AnimatorUpdateListener { 101 102 @Override 103 public void onSessionDestroyed() { 104 super.onSessionDestroyed(); 105 } 106 107 @Override 108 public void onPlaybackStateChanged(PlaybackStateCompat state) { 109 super.onPlaybackStateChanged(state); 110 111 // If there's an ongoing animation, stop it now. 112 if (mProgressAnimator != null) { 113 mProgressAnimator.cancel(); 114 mProgressAnimator = null; 115 } 116 117 final int progress = state != null 118 ? (int) state.getPosition() 119 : 0; 120 setProgress(progress); 121 122 // If the media is playing then the seekbar should follow it, and the easiest 123 // way to do that is to create a ValueAnimator to update it so the bar reaches 124 // the end of the media the same time as playback gets there (or close enough). 125 if (state != null && state.getState() == PlaybackStateCompat.STATE_PLAYING) { 126 final int timeToEnd = (int) ((getMax() - progress) / state.getPlaybackSpeed()); 127 128 mProgressAnimator = ValueAnimator.ofInt(progress, getMax()) 129 .setDuration(timeToEnd); 130 mProgressAnimator.setInterpolator(new LinearInterpolator()); 131 mProgressAnimator.addUpdateListener(this); 132 mProgressAnimator.start(); 133 } 134 } 135 136 @Override 137 public void onMetadataChanged(MediaMetadataCompat metadata) { 138 super.onMetadataChanged(metadata); 139 140 final int max = metadata != null 141 ? (int) metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION) 142 : 0; 143 setProgress(0); 144 setMax(max); 145 } 146 147 @Override 148 public void onAnimationUpdate(final ValueAnimator valueAnimator) { 149 // If the user is changing the slider, cancel the animation. 150 if (mIsTracking) { 151 valueAnimator.cancel(); 152 return; 153 } 154 155 final int animatedIntValue = (int) valueAnimator.getAnimatedValue(); 156 setProgress(animatedIntValue); 157 } 158 } 159 } 160