TransportMediator.java revision ce35f3b7736ff6e1c84bd5536e7c18922ab63c00
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 android.support.v4.media; 18 19import android.app.Activity; 20import android.content.Context; 21import android.media.AudioManager; 22import android.os.Build; 23import android.support.v4.view.KeyEventCompat; 24import android.view.KeyEvent; 25import android.view.View; 26 27import java.util.ArrayList; 28 29/** 30 * Helper for implementing a media transport control (with play, pause, skip, and 31 * other media actions). Takes care of both key events and advanced features 32 * like {@link android.media.RemoteControlClient}. This class is intended to 33 * serve as an intermediary between transport controls (whether they be on-screen 34 * controls, hardware buttons, remote controls) and the actual player. The player 35 * is represented by a single {@link TransportPerformer} that must be supplied to 36 * this class. On-screen controls that want to control and show the state of the 37 * player should do this through calls to the {@link TransportController} interface. 38 * 39 * <p>Here is a simple but fairly complete sample of a video player that is built 40 * around this class. Note that the MediaController class used here is not the one 41 * included in the standard Android framework, but a custom implementation. Real 42 * applications often implement their own transport controls, or you can copy the 43 * implementation here out of Support4Demos.</p> 44 * 45 * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/media/TransportControllerActivity.java 46 * complete} 47 */ 48public class TransportMediator extends TransportController { 49 final Context mContext; 50 final TransportPerformer mCallbacks; 51 final AudioManager mAudioManager; 52 final View mView; 53 final Object mDispatcherState; 54 final TransportMediatorJellybeanMR2 mController; 55 final ArrayList<TransportStateListener> mListeners 56 = new ArrayList<TransportStateListener>(); 57 final TransportMediatorCallback mTransportKeyCallback 58 = new TransportMediatorCallback() { 59 @Override 60 public void handleKey(KeyEvent key) { 61 key.dispatch(mKeyEventCallback); 62 } 63 @Override 64 public void handleAudioFocusChange(int focusChange) { 65 mCallbacks.onAudioFocusChange(focusChange); 66 } 67 68 @Override 69 public long getPlaybackPosition() { 70 return mCallbacks.onGetCurrentPosition(); 71 } 72 73 @Override 74 public void playbackPositionUpdate(long newPositionMs) { 75 mCallbacks.onSeekTo(newPositionMs); 76 } 77 }; 78 79 /** Synonym for {@link KeyEvent#KEYCODE_MEDIA_PLAY KeyEvent.KEYCODE_MEDIA_PLAY} */ 80 public static final int KEYCODE_MEDIA_PLAY = 126; 81 /** Synonym for {@link KeyEvent#KEYCODE_MEDIA_PAUSE KeyEvent.KEYCODE_MEDIA_PAUSE} */ 82 public static final int KEYCODE_MEDIA_PAUSE = 127; 83 /** Synonym for {@link KeyEvent#KEYCODE_MEDIA_RECORD KeyEvent.KEYCODE_MEDIA_RECORD} */ 84 public static final int KEYCODE_MEDIA_RECORD = 130; 85 86 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PREVIOUS 87 * RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS */ 88 public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0; 89 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_REWIND 90 * RemoteControlClient.FLAG_KEY_MEDIA_REWIND */ 91 public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1; 92 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY 93 * RemoteControlClient.FLAG_KEY_MEDIA_PLAY */ 94 public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2; 95 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY_PAUSE 96 * RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE */ 97 public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3; 98 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PAUSE 99 * RemoteControlClient.FLAG_KEY_MEDIA_PAUSE */ 100 public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4; 101 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_STOP 102 * RemoteControlClient.FLAG_KEY_MEDIA_STOP */ 103 public final static int FLAG_KEY_MEDIA_STOP = 1 << 5; 104 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_FAST_FORWARD 105 * RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD */ 106 public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6; 107 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_NEXT 108 * RemoteControlClient.FLAG_KEY_MEDIA_NEXT */ 109 public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; 110 111 static boolean isMediaKey(int keyCode) { 112 switch (keyCode) { 113 case KEYCODE_MEDIA_PLAY: 114 case KEYCODE_MEDIA_PAUSE: 115 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 116 case KeyEvent.KEYCODE_MUTE: 117 case KeyEvent.KEYCODE_HEADSETHOOK: 118 case KeyEvent.KEYCODE_MEDIA_STOP: 119 case KeyEvent.KEYCODE_MEDIA_NEXT: 120 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 121 case KeyEvent.KEYCODE_MEDIA_REWIND: 122 case KEYCODE_MEDIA_RECORD: 123 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { 124 return true; 125 } 126 } 127 return false; 128 } 129 130 final KeyEvent.Callback mKeyEventCallback = new KeyEvent.Callback() { 131 @Override 132 public boolean onKeyDown(int keyCode, KeyEvent event) { 133 return isMediaKey(keyCode) ? mCallbacks.onMediaButtonDown(keyCode, event) : false; 134 } 135 136 @Override 137 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 138 return false; 139 } 140 141 @Override 142 public boolean onKeyUp(int keyCode, KeyEvent event) { 143 return isMediaKey(keyCode) ? mCallbacks.onMediaButtonUp(keyCode, event) : false; 144 } 145 146 @Override 147 public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { 148 return false; 149 } 150 }; 151 152 public TransportMediator(Activity activity, TransportPerformer callbacks) { 153 this(activity, null, callbacks); 154 } 155 156 public TransportMediator(View view, TransportPerformer callbacks) { 157 this(null, view, callbacks); 158 } 159 160 private TransportMediator(Activity activity, View view, TransportPerformer callbacks) { 161 mContext = activity != null ? activity : view.getContext(); 162 mCallbacks = callbacks; 163 mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE); 164 mView = activity != null ? activity.getWindow().getDecorView() : view; 165 mDispatcherState = mView.getKeyDispatcherState(); 166 if (Build.VERSION.SDK_INT >= 18) { // JellyBean MR2 167 mController = new TransportMediatorJellybeanMR2(mContext, mAudioManager, 168 mView, mTransportKeyCallback); 169 } else { 170 mController = null; 171 } 172 } 173 174 /** 175 * Return the {@link android.media.RemoteControlClient} associated with this transport. 176 * This returns a generic Object since the RemoteControlClient is not availble before 177 * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}. Further, this class 178 * will not use RemoteControlClient in its implementation until 179 * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}. You should always check for 180 * null here and not do anything with the RemoteControlClient if none is given; this 181 * way you don't need to worry about the current platform API version. 182 * 183 * <p>Note that this class takes possession of the 184 * {@link android.media.RemoteControlClient.OnGetPlaybackPositionListener} and 185 * {@link android.media.RemoteControlClient.OnPlaybackPositionUpdateListener} callbacks; 186 * you will interact with these through 187 * {@link TransportPerformer#onGetCurrentPosition() TransportPerformer.onGetCurrentPosition} and 188 * {@link TransportPerformer#onSeekTo TransportPerformer.onSeekTo}, respectively.</p> 189 */ 190 public Object getRemoteControlClient() { 191 return mController != null ? mController.getRemoteControlClient() : null; 192 } 193 194 /** 195 * Must call from {@link Activity#dispatchKeyEvent Activity.dispatchKeyEvent} to give 196 * the transport an opportunity to intercept media keys. Any such keys will show up 197 * in {@link TransportPerformer}. 198 * @param event 199 */ 200 public boolean dispatchKeyEvent(KeyEvent event) { 201 return event.dispatch(mKeyEventCallback, (KeyEvent.DispatcherState) mDispatcherState, this); 202 } 203 204 @Override 205 public void registerStateListener(TransportStateListener listener) { 206 mListeners.add(listener); 207 } 208 209 @Override 210 public void unregisterStateListener(TransportStateListener listener) { 211 mListeners.remove(listener); 212 } 213 214 private TransportStateListener[] getListeners() { 215 if (mListeners.size() <= 0) { 216 return null; 217 } 218 TransportStateListener listeners[] = new TransportStateListener[mListeners.size()]; 219 mListeners.toArray(listeners); 220 return listeners; 221 } 222 223 private void reportPlayingChanged() { 224 TransportStateListener[] listeners = getListeners(); 225 if (listeners != null) { 226 for (TransportStateListener listener : listeners) { 227 listener.onPlayingChanged(this); 228 } 229 } 230 } 231 232 private void reportTransportControlsChanged() { 233 TransportStateListener[] listeners = getListeners(); 234 if (listeners != null) { 235 for (TransportStateListener listener : listeners) { 236 listener.onTransportControlsChanged(this); 237 } 238 } 239 } 240 241 private void pushControllerState() { 242 if (mController != null) { 243 mController.refreshState(mCallbacks.onIsPlaying(), 244 mCallbacks.onGetCurrentPosition(), 245 mCallbacks.onGetTransportControlFlags()); 246 } 247 } 248 249 public void refreshState() { 250 pushControllerState(); 251 reportPlayingChanged(); 252 reportTransportControlsChanged(); 253 } 254 255 /** 256 * Move the controller into the playing state. This updates the remote control 257 * client to indicate it is playing, and takes audio focus for the app. 258 */ 259 @Override 260 public void startPlaying() { 261 if (mController != null) { 262 mController.startPlaying(); 263 } 264 mCallbacks.onStart(); 265 pushControllerState(); 266 reportPlayingChanged(); 267 } 268 269 /** 270 * Move the controller into the paused state. This updates the remote control 271 * client to indicate it is paused, but keeps audio focus. 272 */ 273 @Override 274 public void pausePlaying() { 275 if (mController != null) { 276 mController.pausePlaying(); 277 } 278 mCallbacks.onPause(); 279 pushControllerState(); 280 reportPlayingChanged(); 281 } 282 283 /** 284 * Move the controller into the stopped state. This updates the remote control 285 * client to indicate it is stopped, and removes audio focus from the app. 286 */ 287 @Override 288 public void stopPlaying() { 289 if (mController != null) { 290 mController.stopPlaying(); 291 } 292 mCallbacks.onStop(); 293 pushControllerState(); 294 reportPlayingChanged(); 295 } 296 297 @Override 298 public long getDuration() { 299 return mCallbacks.onGetDuration(); 300 } 301 302 @Override 303 public long getCurrentPosition() { 304 return mCallbacks.onGetCurrentPosition(); 305 } 306 307 @Override 308 public void seekTo(long pos) { 309 mCallbacks.onSeekTo(pos); 310 } 311 312 @Override 313 public boolean isPlaying() { 314 return mCallbacks.onIsPlaying(); 315 } 316 317 @Override 318 public int getBufferPercentage() { 319 return mCallbacks.onGetBufferPercentage(); 320 } 321 322 /** 323 * Retrieves the flags for the media transport control buttons that this transport supports. 324 * Result is a combination of the following flags: 325 * {@link #FLAG_KEY_MEDIA_PREVIOUS}, 326 * {@link #FLAG_KEY_MEDIA_REWIND}, 327 * {@link #FLAG_KEY_MEDIA_PLAY}, 328 * {@link #FLAG_KEY_MEDIA_PLAY_PAUSE}, 329 * {@link #FLAG_KEY_MEDIA_PAUSE}, 330 * {@link #FLAG_KEY_MEDIA_STOP}, 331 * {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, 332 * {@link #FLAG_KEY_MEDIA_NEXT} 333 */ 334 @Override 335 public int getTransportControlFlags() { 336 return mCallbacks.onGetTransportControlFlags(); 337 } 338 339 /** 340 * Optionally call when no longer using the TransportController. Its resources 341 * will also be automatically cleaned up when your activity/view is detached from 342 * its window, so you don't normally need to call this explicitly. 343 */ 344 public void destroy() { 345 mController.destroy(); 346 } 347} 348