blob: 177f6aafcb2c917dcf7ebd02da47cc3dce718013 [file] [log] [blame]
/*
* 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.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 frameworks/support/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;
}
@Override
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 = mView.getKeyDispatcherState();
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 event.dispatch(mKeyEventCallback, (KeyEvent.DispatcherState) mDispatcherState, this);
}
@Override
public void registerStateListener(TransportStateListener listener) {
mListeners.add(listener);
}
@Override
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}
*/
@Override
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();
}
}