/* * 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. * *

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.

* * {@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 mListeners = new ArrayList(); 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. * *

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.

*/ 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(); } }