TransportMediator.java revision e3f8e5a462e23399945e8042ddb8025ec8fa33ac
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 development/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 TransportControllerJellybeanMR2 mController; 55 final ArrayList<TransportStateListener> mListeners 56 = new ArrayList<TransportStateListener>(); 57 final TransportControllerJellybeanMR2.TransportCallback mTransportKeyCallback 58 = new TransportControllerJellybeanMR2.TransportCallback() { 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 69 /** Synonym for {@link KeyEvent#KEYCODE_MEDIA_PLAY KeyEvent.KEYCODE_MEDIA_PLAY} */ 70 public static final int KEYCODE_MEDIA_PLAY = 126; 71 /** Synonym for {@link KeyEvent#KEYCODE_MEDIA_PAUSE KeyEvent.KEYCODE_MEDIA_PAUSE} */ 72 public static final int KEYCODE_MEDIA_PAUSE = 127; 73 /** Synonym for {@link KeyEvent#KEYCODE_MEDIA_RECORD KeyEvent.KEYCODE_MEDIA_RECORD} */ 74 public static final int KEYCODE_MEDIA_RECORD = 130; 75 76 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PREVIOUS 77 * RemoveControlClient.FLAG_KEY_MEDIA_PREVIOUS */ 78 public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0; 79 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_REWIND 80 * RemoveControlClient.FLAG_KEY_MEDIA_REWIND */ 81 public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1; 82 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY 83 * RemoveControlClient.FLAG_KEY_MEDIA_PLAY */ 84 public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2; 85 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY_PAUSE 86 * RemoveControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE */ 87 public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3; 88 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PAUSE 89 * RemoveControlClient.FLAG_KEY_MEDIA_PAUSE */ 90 public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4; 91 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_STOP 92 * RemoveControlClient.FLAG_KEY_MEDIA_STOP */ 93 public final static int FLAG_KEY_MEDIA_STOP = 1 << 5; 94 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_FAST_FORWARD 95 * RemoveControlClient.FLAG_KEY_MEDIA_FAST_FORWARD */ 96 public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6; 97 /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_NEXT 98 * RemoveControlClient.FLAG_KEY_MEDIA_NEXT */ 99 public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; 100 101 static boolean isMediaKey(int keyCode) { 102 switch (keyCode) { 103 case KEYCODE_MEDIA_PLAY: 104 case KEYCODE_MEDIA_PAUSE: 105 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 106 case KeyEvent.KEYCODE_MUTE: 107 case KeyEvent.KEYCODE_HEADSETHOOK: 108 case KeyEvent.KEYCODE_MEDIA_STOP: 109 case KeyEvent.KEYCODE_MEDIA_NEXT: 110 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 111 case KeyEvent.KEYCODE_MEDIA_REWIND: 112 case KEYCODE_MEDIA_RECORD: 113 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { 114 return true; 115 } 116 } 117 return false; 118 } 119 120 final KeyEvent.Callback mKeyEventCallback = new KeyEvent.Callback() { 121 @Override 122 public boolean onKeyDown(int keyCode, KeyEvent event) { 123 return isMediaKey(keyCode) ? mCallbacks.onMediaButtonDown(keyCode, event) : false; 124 } 125 126 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 127 return false; 128 } 129 130 @Override 131 public boolean onKeyUp(int keyCode, KeyEvent event) { 132 return isMediaKey(keyCode) ? mCallbacks.onMediaButtonUp(keyCode, event) : false; 133 } 134 135 @Override 136 public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { 137 return false; 138 } 139 }; 140 141 public TransportMediator(Activity activity, TransportPerformer callbacks) { 142 this(activity, null, callbacks); 143 } 144 145 public TransportMediator(View view, TransportPerformer callbacks) { 146 this(null, view, callbacks); 147 } 148 149 private TransportMediator(Activity activity, View view, TransportPerformer callbacks) { 150 mContext = activity != null ? activity : view.getContext(); 151 mCallbacks = callbacks; 152 mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE); 153 mView = activity != null ? activity.getWindow().getDecorView() : view; 154 mDispatcherState = KeyEventCompat.getKeyDispatcherState(mView); 155 if (Build.VERSION.SDK_INT >= 18 || Build.VERSION.CODENAME.equals("JellyBeanMR2")) { 156 mController = new TransportControllerJellybeanMR2(mContext, mAudioManager, 157 mView, mTransportKeyCallback); 158 } else { 159 mController = null; 160 } 161 } 162 163 /** 164 * Return the {@link android.media.RemoteControlClient} associated with this transport. 165 * This returns a generic Object since the RemoteControlClient is not availble before 166 * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}. Further, this class 167 * will not use RemoteControlClient in its implementation until 168 * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}. You should always check for 169 * null here and not do anything with the RemoteControlClient if none is given; this 170 * way you don't need to worry about the current platform API version. 171 */ 172 public Object getRemoteControlClient() { 173 return mController != null ? mController.getRemoteControlClient() : null; 174 } 175 176 /** 177 * Must call from {@link Activity#dispatchKeyEvent Activity.dispatchKeyEvent} to give 178 * the transport an opportunity to intercept media keys. Any such keys will show up 179 * in {@link TransportPerformer}. 180 * @param event 181 */ 182 public boolean dispatchKeyEvent(KeyEvent event) { 183 return KeyEventCompat.dispatch(event, mKeyEventCallback, mDispatcherState, this); 184 } 185 186 public void registerStateListener(TransportStateListener listener) { 187 mListeners.add(listener); 188 } 189 190 public void unregisterStateListener(TransportStateListener listener) { 191 mListeners.remove(listener); 192 } 193 194 private TransportStateListener[] getListeners() { 195 if (mListeners.size() <= 0) { 196 return null; 197 } 198 TransportStateListener listeners[] = new TransportStateListener[mListeners.size()]; 199 mListeners.toArray(listeners); 200 return listeners; 201 } 202 203 private void reportPlayingChanged() { 204 TransportStateListener[] listeners = getListeners(); 205 if (listeners != null) { 206 for (TransportStateListener listener : listeners) { 207 listener.onPlayingChanged(this); 208 } 209 } 210 } 211 212 private void reportTransportControlsChanged() { 213 TransportStateListener[] listeners = getListeners(); 214 if (listeners != null) { 215 for (TransportStateListener listener : listeners) { 216 listener.onTransportControlsChanged(this); 217 } 218 } 219 } 220 221 private void pushControllerState() { 222 if (mController != null) { 223 mController.refreshState(mCallbacks.onIsPlaying(), 224 mCallbacks.onGetTransportControlFlags()); 225 } 226 } 227 228 public void refreshState() { 229 pushControllerState(); 230 reportPlayingChanged(); 231 reportTransportControlsChanged(); 232 } 233 234 /** 235 * Move the controller into the playing state. This updates the remote control 236 * client to indicate it is playing, and takes audio focus for the app. 237 */ 238 @Override 239 public void startPlaying() { 240 if (mController != null) { 241 mController.startPlaying(); 242 } 243 mCallbacks.onStart(); 244 pushControllerState(); 245 reportPlayingChanged(); 246 } 247 248 /** 249 * Move the controller into the paused state. This updates the remote control 250 * client to indicate it is paused, but keeps audio focus. 251 */ 252 @Override 253 public void pausePlaying() { 254 if (mController != null) { 255 mController.pausePlaying(); 256 } 257 mCallbacks.onPause(); 258 pushControllerState(); 259 reportPlayingChanged(); 260 } 261 262 /** 263 * Move the controller into the stopped state. This updates the remote control 264 * client to indicate it is stopped, and removes audio focus from the app. 265 */ 266 @Override 267 public void stopPlaying() { 268 if (mController != null) { 269 mController.stopPlaying(); 270 } 271 mCallbacks.onStop(); 272 pushControllerState(); 273 reportPlayingChanged(); 274 } 275 276 @Override 277 public int getDuration() { 278 return mCallbacks.onGetDuration(); 279 } 280 281 @Override 282 public int getCurrentPosition() { 283 return mCallbacks.onGetCurrentPosition(); 284 } 285 286 @Override 287 public void seekTo(int pos) { 288 mCallbacks.onSeekTo(pos); 289 } 290 291 @Override 292 public boolean isPlaying() { 293 return mCallbacks.onIsPlaying(); 294 } 295 296 @Override 297 public int getBufferPercentage() { 298 return mCallbacks.onGetBufferPercentage(); 299 } 300 301 /** 302 * Retrieves the flags for the media transport control buttons that this transport supports. 303 * Result is a combination of the following flags: 304 * {@link #FLAG_KEY_MEDIA_PREVIOUS}, 305 * {@link #FLAG_KEY_MEDIA_REWIND}, 306 * {@link #FLAG_KEY_MEDIA_PLAY}, 307 * {@link #FLAG_KEY_MEDIA_PLAY_PAUSE}, 308 * {@link #FLAG_KEY_MEDIA_PAUSE}, 309 * {@link #FLAG_KEY_MEDIA_STOP}, 310 * {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, 311 * {@link #FLAG_KEY_MEDIA_NEXT} 312 */ 313 public int getTransportControlFlags() { 314 return mCallbacks.onGetTransportControlFlags(); 315 } 316 317 /** 318 * Optionally call when no longer using the TransportController. Its resources 319 * will also be automatically cleaned up when your activity/view is detached from 320 * its window, so you don't normally need to call this explicitly. 321 */ 322 public void destroy() { 323 mController.destroy(); 324 } 325} 326