| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.support.v4.media; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.media.AudioManager; |
| import android.os.Build; |
| import android.support.v4.view.KeyEventCompat; |
| import android.view.KeyEvent; |
| import android.view.View; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * Helper for implementing a media transport control (with play, pause, skip, and |
| * other media actions). Takes care of both key events and advanced features |
| * like {@link android.media.RemoteControlClient}. This class is intended to |
| * serve as an intermediary between transport controls (whether they be on-screen |
| * controls, hardware buttons, remote controls) and the actual player. The player |
| * is represented by a single {@link TransportPerformer} that must be supplied to |
| * this class. On-screen controls that want to control and show the state of the |
| * player should do this through calls to the {@link TransportController} interface. |
| * |
| * <p>Here is a simple but fairly complete sample of a video player that is built |
| * around this class. Note that the MediaController class used here is not the one |
| * included in the standard Android framework, but a custom implementation. Real |
| * applications often implement their own transport controls, or you can copy the |
| * implementation here out of Support4Demos.</p> |
| * |
| * {@sample development/samples/Support4Demos/src/com/example/android/supportv4/media/TransportControllerActivity.java |
| * complete} |
| */ |
| public class TransportMediator extends TransportController { |
| final Context mContext; |
| final TransportPerformer mCallbacks; |
| final AudioManager mAudioManager; |
| final View mView; |
| final Object mDispatcherState; |
| final TransportMediatorJellybeanMR2 mController; |
| final ArrayList<TransportStateListener> mListeners |
| = new ArrayList<TransportStateListener>(); |
| final TransportMediatorCallback mTransportKeyCallback |
| = new TransportMediatorCallback() { |
| @Override |
| public void handleKey(KeyEvent key) { |
| key.dispatch(mKeyEventCallback); |
| } |
| @Override |
| public void handleAudioFocusChange(int focusChange) { |
| mCallbacks.onAudioFocusChange(focusChange); |
| } |
| |
| @Override |
| public long getPlaybackPosition() { |
| return mCallbacks.onGetCurrentPosition(); |
| } |
| |
| @Override |
| public void playbackPositionUpdate(long newPositionMs) { |
| mCallbacks.onSeekTo(newPositionMs); |
| } |
| }; |
| |
| /** Synonym for {@link KeyEvent#KEYCODE_MEDIA_PLAY KeyEvent.KEYCODE_MEDIA_PLAY} */ |
| public static final int KEYCODE_MEDIA_PLAY = 126; |
| /** Synonym for {@link KeyEvent#KEYCODE_MEDIA_PAUSE KeyEvent.KEYCODE_MEDIA_PAUSE} */ |
| public static final int KEYCODE_MEDIA_PAUSE = 127; |
| /** Synonym for {@link KeyEvent#KEYCODE_MEDIA_RECORD KeyEvent.KEYCODE_MEDIA_RECORD} */ |
| public static final int KEYCODE_MEDIA_RECORD = 130; |
| |
| /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PREVIOUS |
| * RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS */ |
| public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0; |
| /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_REWIND |
| * RemoteControlClient.FLAG_KEY_MEDIA_REWIND */ |
| public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1; |
| /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY |
| * RemoteControlClient.FLAG_KEY_MEDIA_PLAY */ |
| public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2; |
| /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY_PAUSE |
| * RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE */ |
| public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3; |
| /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PAUSE |
| * RemoteControlClient.FLAG_KEY_MEDIA_PAUSE */ |
| public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4; |
| /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_STOP |
| * RemoteControlClient.FLAG_KEY_MEDIA_STOP */ |
| public final static int FLAG_KEY_MEDIA_STOP = 1 << 5; |
| /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_FAST_FORWARD |
| * RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD */ |
| public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6; |
| /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_NEXT |
| * RemoteControlClient.FLAG_KEY_MEDIA_NEXT */ |
| public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; |
| |
| static boolean isMediaKey(int keyCode) { |
| switch (keyCode) { |
| case KEYCODE_MEDIA_PLAY: |
| case KEYCODE_MEDIA_PAUSE: |
| case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: |
| case KeyEvent.KEYCODE_MUTE: |
| case KeyEvent.KEYCODE_HEADSETHOOK: |
| case KeyEvent.KEYCODE_MEDIA_STOP: |
| case KeyEvent.KEYCODE_MEDIA_NEXT: |
| case KeyEvent.KEYCODE_MEDIA_PREVIOUS: |
| case KeyEvent.KEYCODE_MEDIA_REWIND: |
| case KEYCODE_MEDIA_RECORD: |
| case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| final KeyEvent.Callback mKeyEventCallback = new KeyEvent.Callback() { |
| @Override |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| return isMediaKey(keyCode) ? mCallbacks.onMediaButtonDown(keyCode, event) : false; |
| } |
| |
| public boolean onKeyLongPress(int keyCode, KeyEvent event) { |
| return false; |
| } |
| |
| @Override |
| public boolean onKeyUp(int keyCode, KeyEvent event) { |
| return isMediaKey(keyCode) ? mCallbacks.onMediaButtonUp(keyCode, event) : false; |
| } |
| |
| @Override |
| public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { |
| return false; |
| } |
| }; |
| |
| public TransportMediator(Activity activity, TransportPerformer callbacks) { |
| this(activity, null, callbacks); |
| } |
| |
| public TransportMediator(View view, TransportPerformer callbacks) { |
| this(null, view, callbacks); |
| } |
| |
| private TransportMediator(Activity activity, View view, TransportPerformer callbacks) { |
| mContext = activity != null ? activity : view.getContext(); |
| mCallbacks = callbacks; |
| mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE); |
| mView = activity != null ? activity.getWindow().getDecorView() : view; |
| mDispatcherState = KeyEventCompat.getKeyDispatcherState(mView); |
| if (Build.VERSION.SDK_INT >= 18) { // JellyBean MR2 |
| mController = new TransportMediatorJellybeanMR2(mContext, mAudioManager, |
| mView, mTransportKeyCallback); |
| } else { |
| mController = null; |
| } |
| } |
| |
| /** |
| * Return the {@link android.media.RemoteControlClient} associated with this transport. |
| * This returns a generic Object since the RemoteControlClient is not availble before |
| * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}. Further, this class |
| * will not use RemoteControlClient in its implementation until |
| * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}. You should always check for |
| * null here and not do anything with the RemoteControlClient if none is given; this |
| * way you don't need to worry about the current platform API version. |
| * |
| * <p>Note that this class takes possession of the |
| * {@link android.media.RemoteControlClient.OnGetPlaybackPositionListener} and |
| * {@link android.media.RemoteControlClient.OnPlaybackPositionUpdateListener} callbacks; |
| * you will interact with these through |
| * {@link TransportPerformer#onGetCurrentPosition() TransportPerformer.onGetCurrentPosition} and |
| * {@link TransportPerformer#onSeekTo TransportPerformer.onSeekTo}, respectively.</p> |
| */ |
| public Object getRemoteControlClient() { |
| return mController != null ? mController.getRemoteControlClient() : null; |
| } |
| |
| /** |
| * Must call from {@link Activity#dispatchKeyEvent Activity.dispatchKeyEvent} to give |
| * the transport an opportunity to intercept media keys. Any such keys will show up |
| * in {@link TransportPerformer}. |
| * @param event |
| */ |
| public boolean dispatchKeyEvent(KeyEvent event) { |
| return KeyEventCompat.dispatch(event, mKeyEventCallback, mDispatcherState, this); |
| } |
| |
| public void registerStateListener(TransportStateListener listener) { |
| mListeners.add(listener); |
| } |
| |
| public void unregisterStateListener(TransportStateListener listener) { |
| mListeners.remove(listener); |
| } |
| |
| private TransportStateListener[] getListeners() { |
| if (mListeners.size() <= 0) { |
| return null; |
| } |
| TransportStateListener listeners[] = new TransportStateListener[mListeners.size()]; |
| mListeners.toArray(listeners); |
| return listeners; |
| } |
| |
| private void reportPlayingChanged() { |
| TransportStateListener[] listeners = getListeners(); |
| if (listeners != null) { |
| for (TransportStateListener listener : listeners) { |
| listener.onPlayingChanged(this); |
| } |
| } |
| } |
| |
| private void reportTransportControlsChanged() { |
| TransportStateListener[] listeners = getListeners(); |
| if (listeners != null) { |
| for (TransportStateListener listener : listeners) { |
| listener.onTransportControlsChanged(this); |
| } |
| } |
| } |
| |
| private void pushControllerState() { |
| if (mController != null) { |
| mController.refreshState(mCallbacks.onIsPlaying(), |
| mCallbacks.onGetCurrentPosition(), |
| mCallbacks.onGetTransportControlFlags()); |
| } |
| } |
| |
| public void refreshState() { |
| pushControllerState(); |
| reportPlayingChanged(); |
| reportTransportControlsChanged(); |
| } |
| |
| /** |
| * Move the controller into the playing state. This updates the remote control |
| * client to indicate it is playing, and takes audio focus for the app. |
| */ |
| @Override |
| public void startPlaying() { |
| if (mController != null) { |
| mController.startPlaying(); |
| } |
| mCallbacks.onStart(); |
| pushControllerState(); |
| reportPlayingChanged(); |
| } |
| |
| /** |
| * Move the controller into the paused state. This updates the remote control |
| * client to indicate it is paused, but keeps audio focus. |
| */ |
| @Override |
| public void pausePlaying() { |
| if (mController != null) { |
| mController.pausePlaying(); |
| } |
| mCallbacks.onPause(); |
| pushControllerState(); |
| reportPlayingChanged(); |
| } |
| |
| /** |
| * Move the controller into the stopped state. This updates the remote control |
| * client to indicate it is stopped, and removes audio focus from the app. |
| */ |
| @Override |
| public void stopPlaying() { |
| if (mController != null) { |
| mController.stopPlaying(); |
| } |
| mCallbacks.onStop(); |
| pushControllerState(); |
| reportPlayingChanged(); |
| } |
| |
| @Override |
| public long getDuration() { |
| return mCallbacks.onGetDuration(); |
| } |
| |
| @Override |
| public long getCurrentPosition() { |
| return mCallbacks.onGetCurrentPosition(); |
| } |
| |
| @Override |
| public void seekTo(long pos) { |
| mCallbacks.onSeekTo(pos); |
| } |
| |
| @Override |
| public boolean isPlaying() { |
| return mCallbacks.onIsPlaying(); |
| } |
| |
| @Override |
| public int getBufferPercentage() { |
| return mCallbacks.onGetBufferPercentage(); |
| } |
| |
| /** |
| * Retrieves the flags for the media transport control buttons that this transport supports. |
| * Result is a combination of the following flags: |
| * {@link #FLAG_KEY_MEDIA_PREVIOUS}, |
| * {@link #FLAG_KEY_MEDIA_REWIND}, |
| * {@link #FLAG_KEY_MEDIA_PLAY}, |
| * {@link #FLAG_KEY_MEDIA_PLAY_PAUSE}, |
| * {@link #FLAG_KEY_MEDIA_PAUSE}, |
| * {@link #FLAG_KEY_MEDIA_STOP}, |
| * {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, |
| * {@link #FLAG_KEY_MEDIA_NEXT} |
| */ |
| public int getTransportControlFlags() { |
| return mCallbacks.onGetTransportControlFlags(); |
| } |
| |
| /** |
| * Optionally call when no longer using the TransportController. Its resources |
| * will also be automatically cleaned up when your activity/view is detached from |
| * its window, so you don't normally need to call this explicitly. |
| */ |
| public void destroy() { |
| mController.destroy(); |
| } |
| } |