/*
* Copyright (C) 2014 Haruki Hasegawa
*
* 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 com.h6ah4i.android.media.standard;
import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import android.content.Context;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import com.h6ah4i.android.media.IBasicMediaPlayer;
import com.h6ah4i.android.media.compat.AudioAttributes;
import com.h6ah4i.android.media.compat.MediaPlayerCompat;
public class StandardMediaPlayer implements IBasicMediaPlayer {
private static final String TAG = "StandardMediaPlayer";
private static final String UNEXPECTED_EXCEPTION_MESSAGE = "Unexpected exception occurred";
private static final boolean LOCAL_LOGV = false;
private static final boolean LOCAL_LOGD = true;
// constants
private static final int SEEK_POS_NOSET = -1;
private MediaPlayer mPlayer;
private MediaPlayerStateManager mState;
private InternalHandler mHandler;
private WeakReference<StandardMediaPlayer> mNextMediaPlayerRef;
private IBasicMediaPlayer.OnCompletionListener mUserOnCompletionListener;
private IBasicMediaPlayer.OnPreparedListener mUserOnPreparedListener;
private IBasicMediaPlayer.OnSeekCompleteListener mUserOnSeekCompleteListener;
private IBasicMediaPlayer.OnBufferingUpdateListener mUserOnBufferingUpdateListener;
private IBasicMediaPlayer.OnInfoListener mUserOnInfoListener;
private IBasicMediaPlayer.OnErrorListener mUserOnErrorListener;
private boolean mIsLooping;
private int mDuration;
private boolean mUsingNuPlayer;
private int mSeekPosition = SEEK_POS_NOSET;
private int mPendingSeekPosition = SEEK_POS_NOSET;
private WeakReference<Handler> mSuperEventHandler;
private static abstract class SkipCondition {
public boolean prevCheck(MediaPlayerStateManager stateManager) { return false; }
public boolean postCheck(MediaPlayerStateManager stateManager) { return false; }
}
private static class SkipConditionIfErrorBeforePreparedPrepared extends SkipCondition {
@Override
public boolean prevCheck(MediaPlayerStateManager stateManager) {
if (stateManager.isStateError()) {
return !isPrepared(stateManager.getPrevErrorState());
}
return false;
}
}
private static final SkipCondition SKIP_CONDITION_NEVER = null;
private static final SkipCondition SKIP_CONDITION_ERROR_BEFORE_PREPARED = new SkipConditionIfErrorBeforePreparedPrepared();
private android.media.MediaPlayer.OnCompletionListener mHookOnCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(android.media.MediaPlayer mp) {
StandardMediaPlayer.this.handleOnCompletion(mp);
}
};
private android.media.MediaPlayer.OnPreparedListener mHookOnPreparedListener = new android.media.MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(android.media.MediaPlayer mp) {
StandardMediaPlayer.this.handleOnPrepared(mp, true);
}
};
private android.media.MediaPlayer.OnSeekCompleteListener mHookOnSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(android.media.MediaPlayer mp) {
StandardMediaPlayer.this.handleOnSeekComplete(mp);
}
};
private android.media.MediaPlayer.OnBufferingUpdateListener mHookOnBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
StandardMediaPlayer.this.handleOnBufferingUpdate(mp, percent);
}
};
private android.media.MediaPlayer.OnInfoListener mHookOnInfoListener = new android.media.MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
return StandardMediaPlayer.this.handleOnInfo(mp, what, extra);
}
};
private android.media.MediaPlayer.OnErrorListener mHookOnErrorListener = new android.media.MediaPlayer.OnErrorListener() {
@Override
public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
return StandardMediaPlayer.this.handleOnError(mp, what, extra);
}
};
public StandardMediaPlayer() {
try {
mNextMediaPlayerRef = new WeakReference<StandardMediaPlayer>(null);
mUsingNuPlayer = NuPlayerDetector.isUsingNuPlayer();
mHandler = new InternalHandler(this);
mState = new MediaPlayerStateManager();
mPlayer = new MediaPlayer();
mSuperEventHandler = new WeakReference<Handler>(obtainSuperMediaPlayerInternalEventHandler(mPlayer));
if (LOCAL_LOGD) {
Log.d(TAG, "[" + System.identityHashCode(mPlayer) + "] Create MediaPlayer instance");
}
mPlayer.setOnCompletionListener(mHookOnCompletionListener);
mPlayer.setOnPreparedListener(mHookOnPreparedListener);
mPlayer.setOnSeekCompleteListener(mHookOnSeekCompleteListener);
mPlayer.setOnBufferingUpdateListener(mHookOnBufferingUpdateListener);
mPlayer.setOnInfoListener(mHookOnInfoListener);
mPlayer.setOnErrorListener(mHookOnErrorListener);
} catch (Exception e) {
Log.e(TAG, makeUnexpectedExceptionCaughtMessage("StandardMediaPlayer()"), e);
// call release() method to transit to END state
releaseInternal();
}
}
/**
* Get underlying MediaPlayer instance.
*
* @return underlying MediaPlayer instance.
*/
public MediaPlayer getMediaPlayer() {
return mPlayer;
}
@Override
public void release() {
releaseInternal();
}
private void releaseInternal() {
if (mPlayer != null) {
mPlayer.setOnCompletionListener(null);
mPlayer.setOnPreparedListener(null);
mPlayer.setOnSeekCompleteListener(null);
mPlayer.setOnBufferingUpdateListener(null);
mPlayer.setOnInfoListener(null);
mPlayer.setOnErrorListener(null);
if (LOCAL_LOGD) {
Log.d(TAG, "[" + System.identityHashCode(mPlayer) + "] Release MediaPlayer instance");
}
mPlayer.release();
mPlayer = null;
}
if (mHandler != null) {
mHandler.release();
mHandler = null;
}
if (mSuperEventHandler != null) {
mSuperEventHandler.clear();
mSuperEventHandler = null;
}
mHookOnCompletionListener = null;
mHookOnPreparedListener = null;
mHookOnSeekCompleteListener = null;
mHookOnBufferingUpdateListener = null;
mHookOnInfoListener = null;
mHookOnErrorListener = null;
mUserOnCompletionListener = null;
mUserOnPreparedListener = null;
mUserOnSeekCompleteListener = null;
mUserOnInfoListener = null;
mUserOnErrorListener = null;
mSeekPosition = SEEK_POS_NOSET;
mPendingSeekPosition = SEEK_POS_NOSET;
mNextMediaPlayerRef.clear();
mState.transitToEndState();
}
@Override
public void reset() {
checkIsNotReleased();
if (!mState.canCallReset()) {
return;
}
if (mPlayer != null) {
mPlayer.reset();
}
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
// Clear all messages to avoid unexpected error callback
Handler superEventHandler = (mSuperEventHandler != null) ? mSuperEventHandler.get() : null;
if (superEventHandler != null) {
superEventHandler.removeCallbacksAndMessages(null);
}
mIsLooping = false;
mSeekPosition = SEEK_POS_NOSET;
mPendingSeekPosition = SEEK_POS_NOSET;
mDuration = 0;
mState.transitToIdleState();
}
@Override
public int getAudioSessionId() {
checkIsNotReleased();
return mPlayer.getAudioSessionId();
}
@Override
public void setAudioSessionId(int sessionId) throws IllegalArgumentException, IllegalStateException {
checkIsNotReleased();
mPlayer.setAudioSessionId(sessionId);
}
@Override
public void start() throws IllegalStateException {
final String METHOD_NAME = "start()";
checkIsNotReleased();
if (!mState.canCallStart()) {
transitToErrorStateAndCallback(METHOD_NAME, MEDIA_ERROR_UNKNOWN, 0, SKIP_CONDITION_NEVER);
return;
}
try {
mPlayer.start();
mState.transitToStartedState();
} catch (IllegalStateException e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw e; // re-throw
} catch (Exception e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw new IllegalStateException(UNEXPECTED_EXCEPTION_MESSAGE, e);
}
}
@Override
public void stop() throws IllegalStateException {
final String METHOD_NAME = "stop()";
checkIsNotReleased();
if (!mState.canCallStop()) {
transitToErrorStateAndCallback(METHOD_NAME, MEDIA_ERROR_UNKNOWN, 0, SKIP_CONDITION_ERROR_BEFORE_PREPARED);
return;
}
try {
mPlayer.stop();
mSeekPosition = SEEK_POS_NOSET;
mPendingSeekPosition = SEEK_POS_NOSET;
mState.transitToStoppedState();
} catch (IllegalStateException e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw e; // re-throw
} catch (Exception e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw new IllegalStateException(UNEXPECTED_EXCEPTION_MESSAGE, e);
}
}
@Override
public void setDataSource(Context context, Uri uri) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
final String METHOD_NAME = "setDataSource()";
try {
if (!mState.canCallSetDataSource()) {
if (isStateError()) {
return;
}
transitToErrorStateAndThrowException(METHOD_NAME);
return;
}
mPlayer.setDataSource(context, uri);
mState.transitToInitialized();
} catch (IOException e) {
throw e; // re-throw
} catch (IllegalArgumentException e) {
throw e; // re-throw
} catch (SecurityException e) {
throw e; // re-throw
} catch (IllegalStateException e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw e; // re-throw
} catch (NullPointerException e) {
// NOTE-1:
// Some devices throws NPE when file not found.
throw new IOException("File not found...?", e);
// NOTE-2:
// Maybe, passing a wrong context.
// If running on InstrumentationTestCase, getInstrumentation().getTargetContext() method should be used.
// Log.w(TAG, METHOD_NAME + ", maybe passing a wrong context object.", e);
// throw new IllegalStateException(UNEXPECTED_EXCEPTION_MESSAGE, e);
} catch (Exception e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw new IllegalStateException(UNEXPECTED_EXCEPTION_MESSAGE, e);
}
}
@Override
public void setDataSource(FileDescriptor fd) throws IOException, IllegalArgumentException, IllegalStateException {
final String METHOD_NAME = "setDataSource()";
if (!mState.canCallSetDataSource()) {
if (isStateError()) {
return;
}
transitToErrorStateAndThrowException(METHOD_NAME);
return;
}
try {
mPlayer.setDataSource(fd);
mState.transitToInitialized();
} catch (IOException e) {
throw e; // re-throw
} catch (IllegalArgumentException e) {
throw e; // re-throw
} catch (SecurityException e) {
throw e; // re-throw
} catch (IllegalStateException e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw e; // re-throw
} catch (Exception e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw new IllegalStateException(UNEXPECTED_EXCEPTION_MESSAGE, e);
}
}
@Override
public void setDataSource(FileDescriptor fd, long offset, long length) throws IOException, IllegalArgumentException, IllegalStateException {
final String METHOD_NAME = "setDataSource()";
if (!mState.canCallSetDataSource()) {
if (isStateError()) {
return;
}
transitToErrorStateAndThrowException(METHOD_NAME);
return;
}
try {
mPlayer.setDataSource(fd, offset, length);
mState.transitToInitialized();
} catch (IOException e) {
throw e; // re-throw
} catch (IllegalArgumentException e) {
throw e; // re-throw
} catch (SecurityException e) {
throw e; // re-throw
} catch (IllegalStateException e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw e; // re-throw
} catch (Exception e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw new IllegalStateException(UNEXPECTED_EXCEPTION_MESSAGE, e);
}
}
@Override
public void setDataSource(String path) throws IOException, IllegalArgumentException, IllegalStateException {
final String METHOD_NAME = "setDataSource()";
if (!mState.canCallSetDataSource()) {
if (isStateError()) {
return;
}
transitToErrorStateAndThrowException(METHOD_NAME);
return;
}
try {
mPlayer.setDataSource(path);
mState.transitToInitialized();
} catch (IOException e) {
throw e; // re-throw
} catch (IllegalArgumentException e) {
throw e; // re-throw
} catch (SecurityException e) {
throw e; // re-throw
} catch (IllegalStateException e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw e; // re-throw
} catch (Exception e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw new IllegalStateException(UNEXPECTED_EXCEPTION_MESSAGE, e);
}
}
@Override
public void pause() throws IllegalStateException {
final String METHOD_NAME = "pause()";
checkIsNotReleased();
if (!mState.canCallPause()) {
transitToErrorStateAndCallback(METHOD_NAME, MEDIA_ERROR_UNKNOWN, 0, SKIP_CONDITION_ERROR_BEFORE_PREPARED);
return;
}
try {
mPlayer.pause();
if (mState.getState() == MediaPlayerStateManager.STATE_STARTED) {
mState.transitToPausedState();
}
} catch (IllegalStateException e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw e; // re-throw
} catch (Exception e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw new IllegalStateException(UNEXPECTED_EXCEPTION_MESSAGE, e);
}
}
@Override
public void prepare() throws IOException, IllegalStateException {
final String METHOD_NAME = "prepare()";
if (!mState.canCallPrepare()) {
final String msg = makeMethodCalledInvalidStateMessage(METHOD_NAME);
mState.transitToErrorState();
throw new IllegalStateException(msg);
}
try {
mPlayer.prepare();
handleOnPrepared(mPlayer, false);
} catch (IOException e) {
throw e; // re-throw
} catch (IllegalStateException e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw e; // re-throw
} catch (Exception e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw new IllegalStateException(UNEXPECTED_EXCEPTION_MESSAGE, e);
}
}
@Override
public void prepareAsync() throws IllegalStateException {
final String METHOD_NAME = "prepareAsync()";
if (!mState.canCallPrepareAsync()) {
final String msg = makeMethodCalledInvalidStateMessage(METHOD_NAME);
mState.transitToErrorState();
throw new IllegalStateException(msg);
}
try {
mPlayer.prepareAsync();
mState.transitToPreparingState();
} catch (IllegalStateException e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw e; // re-throw
} catch (Exception e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw new IllegalStateException(UNEXPECTED_EXCEPTION_MESSAGE, e);
}
}
@Override
public void seekTo(int msec) throws IllegalStateException {
final String METHOD_NAME = "seekTo()";
checkIsNotReleased();
if (!mState.canCallSeekTo()) {
transitToErrorStateAndCallback(METHOD_NAME, MEDIA_ERROR_UNKNOWN, 0, SKIP_CONDITION_ERROR_BEFORE_PREPARED);
return;
}
try {
// workaround: some devices don't raise onSeekCompletion()
// event if the seek position is greater than the duration
msec = Math.min(Math.max(mDuration - 1, 0), msec);
if (!isSeeking()) {
mPlayer.seekTo(msec);
mSeekPosition = msec;
} else {
mPendingSeekPosition = msec;
}
} catch (IllegalStateException e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw e; // re-throw
} catch (Exception e) {
Log.w(TAG, makeUnexpectedExceptionCaughtMessage(METHOD_NAME), e);
mState.transitToErrorState();
throw new IllegalStateException(UNEXPECTED_EXCEPTION_MESSAGE, e);
}
}
@Override
public int getDuration() {
final String METHOD_NAME = "getDuration()";
checkIsNotReleased();
if (!mState.canCallGetDuration()) {
if (!isStateError()) {
transitToErrorStateAndCallback(METHOD_NAME, MEDIA_ERROR_UNKNOWN, 0, SKIP_CONDITION_NEVER);
return 0;
}
}
return mDuration;
}
@Override
public int getCurrentPosition() {
checkIsNotReleased();
if (!mState.canCallGetCurrentPosition()) {
return 0;
}
int position = mPlayer.getCurrentPosition();
if (!isPrepared()) {
// [Workaround]
// super.getCurrentPosition() may returns invalid (uninitialized ?)
// before calling prepare() method
position = 0;
}
// [Workaround]
// super.getCurrentPosition() may returns invalid value
return Math.min(position, mDuration);
}
@Override
public void setOnCompletionListener(IBasicMediaPlayer.OnCompletionListener listener) {
if (isReleased()) {
return;
}
mUserOnCompletionListener = listener;
}
@Override
public void setOnPreparedListener(IBasicMediaPlayer.OnPreparedListener listener) {
if (isReleased()) {
return;
}
mUserOnPreparedListener = listener;
}
@Override
public void setOnSeekCompleteListener(IBasicMediaPlayer.OnSeekCompleteListener listener) {
if (isReleased()) {
return;
}
mUserOnSeekCompleteListener = listener;
}
@Override
public void setOnBufferingUpdateListener(IBasicMediaPlayer.OnBufferingUpdateListener listener) {
if (isReleased()) {
return;
}
mUserOnBufferingUpdateListener = listener;
}
@Override
public void setOnInfoListener(IBasicMediaPlayer.OnInfoListener listener) {
if (isReleased()) {
return;
}
mUserOnInfoListener = listener;
}
@Override
public void setOnErrorListener(IBasicMediaPlayer.OnErrorListener listener) {
if (isReleased()) {
return;
}
mUserOnErrorListener = listener;
}
@Override
public void setAuxEffectSendLevel(float level) {
checkIsNotReleased();
// [Workaround]
// Hack: Can't handle some invalid values
if (Float.isNaN(level) || level < 0.0f)
level = Float.POSITIVE_INFINITY;
mPlayer.setAuxEffectSendLevel(level);
}
@Override
public void setWakeMode(Context context, int mode) {
checkIsNotReleased();
if (context == null) {
throw new IllegalArgumentException("The argument context must not be null");
}
mPlayer.setWakeMode(context, mode);
}
@Override
public void setNextMediaPlayer(IBasicMediaPlayer next) {
final String METHOD_NAME = "setNextMediaPlayer()";
checkIsNotReleased();
if (next != null && !(next instanceof StandardMediaPlayer)) {
throw new IllegalArgumentException("Not StandardMediaPlayer instance");
}
if (next == this) {
throw new IllegalArgumentException("Passed self instance (next == this)");
}
StandardMediaPlayer next2 = (StandardMediaPlayer) next;
if (next != null && !isReadyForNextPlayer(next2)) {
throw new IllegalStateException(
"The internal state of the passed player object is not acceptable for next player (state = " + next2.mState.getCurrentStateCodeString() + ")");
}
final int state = mState.getState();
// NOTE:
// MediaPlayer.setNextMediaPlayer() throws IllegalArgumentsException
if (isSetNextMediaPlayerThrowsIAE(state)) {
final String msg = makeMethodCalledInvalidStateMessage(METHOD_NAME);
throw new IllegalStateException(msg);
}
mNextMediaPlayerRef = new WeakReference<StandardMediaPlayer>((StandardMediaPlayer) next);
applyNextMediaPlayer();
}
@Override
public void setAudioAttributes(AudioAttributes attributes) {
if (attributes == null) {
throw new IllegalArgumentException("The argument attributes must not be null.");
}
checkIsNotReleased();
if (MediaPlayerCompat.supportsSetAudioAttributes()) {
MediaPlayerCompat.setAudioAttributes(mPlayer, attributes);
} else {
if (LOCAL_LOGV) {
Log.v(TAG, "setAudioAttributes() is not supported, just ignored.");
}
}
}
@Override
public void setLooping(boolean looping) {
checkIsNotReleased();
// [Workaround]
// Stock MediaPlayer doesn't support setLooping() method in idle state
if (isBeforePrepared()) {
mIsLooping = looping;
} else {
mIsLooping = looping;
applyLoopingState();
applyNextMediaPlayer();
}
}
@Override
public boolean isLooping() {
checkIsNotReleased();
return mIsLooping;
}
@Override
public boolean isPlaying() throws IllegalStateException {
checkIsNotReleased();
if (needLoopPointCallbackEmulation()) {
return (mState.getState() == MediaPlayerStateManager.STATE_STARTED);
} else {
return mPlayer.isPlaying();
}
}
@Override
public void attachAuxEffect(int effectId) {
checkIsNotReleased();
if (isStateError()) {
return;
}
mPlayer.attachAuxEffect(effectId);
}
@Override
public void setVolume(float leftVolume, float rightVolume) {
checkIsNotReleased();
mPlayer.setVolume(leftVolume, rightVolume);
}
@Override
public void setAudioStreamType(int streamtype) {
checkIsNotReleased();
if (isStateError()) {
return;
}
mPlayer.setAudioStreamType(streamtype);
}
private void applyNextMediaPlayer() {
if (MediaPlayerCompat.supportsSetNextMediaPlayer()) {
StandardMediaPlayer nextStandardMediaPlayer = mNextMediaPlayerRef.get();
MediaPlayer next = (nextStandardMediaPlayer != null) ? nextStandardMediaPlayer.mPlayer : null;
if ((next != null) && mUsingNuPlayer && mIsLooping) {
// To avoid the bug of NuPlayer (next player is preferred than looping setting)
next = null;
}
if ((next != null) && !isReadyForNextPlayer(nextStandardMediaPlayer)) {
next = null;
}
if (!isSetNextMediaPlayerThrowsIAE(mState.getState())) {
MediaPlayerCompat.setNextMediaPlayer(mPlayer, next);
}
}
}
private static boolean isReadyForNextPlayer(StandardMediaPlayer next) {
if (next == null) {
return false;
}
int state = next.mState.getState();
return (state == MediaPlayerStateManager.STATE_PREPARED ||
state == MediaPlayerStateManager.STATE_PAUSED ||
state == MediaPlayerStateManager.STATE_PLAYBACK_COMPLETED);
}
private void applyLoopingState() {
if (!needLoopPointCallbackEmulation()) {
mPlayer.setLooping(mIsLooping);
}
}
protected void handleOnCompletion(MediaPlayer mp) {
mSeekPosition = SEEK_POS_NOSET;
mPendingSeekPosition = SEEK_POS_NOSET;
boolean looped = false;
// check whether looped
if (needLoopPointCallbackEmulation() && mIsLooping) {
try {
mPlayer.seekTo(0);
mPlayer.start();
looped = true;
} catch (Exception e) {
// eat all exceptions here
Log.e(TAG, "An exception occurred in handleOnCompletion()", e);
}
} else if (isCompletionOnLoopPointSupported() && mIsLooping && mPlayer.isPlaying()) {
looped = true;
}
if (looped) {
onNotifyLoopPoint();
return;
}
if (!MediaPlayerCompat.supportsSetNextMediaPlayer()) {
// emulate setNextMediaPlayer() behavior
StandardMediaPlayer nextmp = mNextMediaPlayerRef.get();
try {
if (nextmp != null) {
nextmp.start();
}
} catch (Exception e) {
// eat all exceptions here
Log.e(TAG, "An exception occurred in handleOnCompletion()", e);
}
}
mState.transitToPlaybackCompleted();
if (mUserOnCompletionListener != null) {
mUserOnCompletionListener.onCompletion(this);
}
}
private void onNotifyLoopPoint() {
if (mUserOnSeekCompleteListener != null) {
// emulate AwesomePlayer behavior
mUserOnSeekCompleteListener.onSeekComplete(this);
}
}
protected void handleOnPrepared(MediaPlayer mp, boolean invokeCallback) {
mDuration = mPlayer.getDuration();
applyLoopingState();
mState.transitToPreparedState();
if (invokeCallback && mUserOnPreparedListener != null) {
mUserOnPreparedListener.onPrepared(this);
}
}
protected void handleOnSeekComplete(MediaPlayer mp) {
final int seekPosition = mSeekPosition;
final int pendingSeekPosition = mPendingSeekPosition;
mSeekPosition = SEEK_POS_NOSET;
mPendingSeekPosition = SEEK_POS_NOSET;
if (pendingSeekPosition >= 0) {
mSeekPosition = pendingSeekPosition;
mp.seekTo(pendingSeekPosition);
}
if (pendingSeekPosition == SEEK_POS_NOSET) {
if (seekPosition == SEEK_POS_NOSET) {
if (isSeekCompletionOnLoopPointSupported()) {
onNotifyLoopPoint();
}
} else {
if (mUserOnSeekCompleteListener != null) {
mUserOnSeekCompleteListener.onSeekComplete(this);
}
}
}
}
protected void handleOnBufferingUpdate(MediaPlayer mp, int percent) {
if (mUserOnBufferingUpdateListener != null) {
mUserOnBufferingUpdateListener.onBufferingUpdate(this, percent);
}
}
protected boolean handleOnError(MediaPlayer mp, int what, int extra) {
mState.transitToErrorState();
if (isReleased()) {
return false;
}
if (mUserOnErrorListener != null) {
return mUserOnErrorListener.onError(this, what, extra);
}
return false;
}
protected boolean handleOnInfo(MediaPlayer mp, int what, int extra) {
if (mUserOnInfoListener != null) {
return mUserOnInfoListener.onInfo(this, what, extra);
}
return false;
}
private void transitToErrorStateAndCallback(String methodName, int what, int extra, SkipCondition skipCallback) {
final String msg = makeMethodCalledInvalidStateMessage(methodName);
boolean skip = false;
if (LOCAL_LOGV) {
Log.v(TAG, msg);
}
if (skipCallback != null) {
skip |= skipCallback.prevCheck(mState);
}
mState.transitToErrorState();
if (!skip && (skipCallback != null)) {
skip |= skipCallback.postCheck(mState);
}
if (skip) {
// don't raise error callback if the error has been occurred before prepared state.
return;
}
postError(what, extra);
}
private void transitToErrorStateAndThrowException(String methodName) {
final String msg = makeMethodCalledInvalidStateMessage(methodName);
mState.transitToErrorState();
throw new IllegalStateException(msg);
}
private void postError(int what, int extra) {
final int prevState = mState.getPrevErrorState();
if (mHandler != null) {
mHandler.postError(prevState, what, extra);
}
}
private void checkIsNotReleased() throws IllegalStateException {
if (isReleased()) {
throw new IllegalStateException(
"The media player instance has already released");
}
}
private boolean isReleased() {
return mState.isStateEnd();
}
private boolean isStateError() {
return mState.isStateError();
}
private boolean isBeforePrepared() {
final int state = mState.getState();
return (state == MediaPlayerStateManager.STATE_IDLE ||
state == MediaPlayerStateManager.STATE_INITIALIZED ||
state == MediaPlayerStateManager.STATE_PREPARING);
}
private boolean isPrepared() {
return isPrepared(mState.getState());
}
private static boolean isPrepared(int state) {
return (state == MediaPlayerStateManager.STATE_PREPARED ||
state == MediaPlayerStateManager.STATE_STARTED ||
state == MediaPlayerStateManager.STATE_PAUSED ||
state == MediaPlayerStateManager.STATE_PLAYBACK_COMPLETED);
}
private boolean isCompletionOnLoopPointSupported() {
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) && (mUsingNuPlayer);
}
private boolean isSeekCompletionOnLoopPointSupported() {
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) && (!mUsingNuPlayer);
}
private boolean needLoopPointCallbackEmulation() {
return !isSeekCompletionOnLoopPointSupported() && !isCompletionOnLoopPointSupported();
}
private String makeMethodCalledInvalidStateMessage(String messageName) {
return messageName + " method cannot be called during " + mState.getCurrentStateCodeString() + " state";
}
private static String makeUnexpectedExceptionCaughtMessage(String messageName) {
return "Unexpected exception: " + messageName;
}
private void handlePostedError(int prevState, int what, int extra) {
boolean handled = false;
// raise onError()
if (mUserOnErrorListener != null) {
handled = mUserOnErrorListener.onError(this, what, extra);
}
// raise onCompletion()
if (!handled && (mUserOnCompletionListener != null)) {
mUserOnCompletionListener.onCompletion(this);
}
}
private boolean isSeeking() {
return (mSeekPosition >= 0);
}
private static boolean isSetNextMediaPlayerThrowsIAE(int state) {
return state == MediaPlayerStateManager.STATE_IDLE ||
state == MediaPlayerStateManager.STATE_END ||
state == MediaPlayerStateManager.STATE_ERROR;
}
private static Handler obtainSuperMediaPlayerInternalEventHandler(MediaPlayer player) {
if (player == null) {
return null;
}
try {
Field f = MediaPlayer.class.getDeclaredField("mEventHandler");
f.setAccessible(true);
return (Handler) f.get(player);
} catch (NoSuchFieldException e) {
} catch (IllegalAccessException e) {
}
return null;
}
private static class InternalHandler extends Handler {
private WeakReference<StandardMediaPlayer> mRefHolder;
public static final int MSG_RAISE_ERROR = 1;
public InternalHandler(StandardMediaPlayer holder) {
mRefHolder = new WeakReference<StandardMediaPlayer>(holder);
}
@Override
public void handleMessage(Message msg) {
StandardMediaPlayer holder = (StandardMediaPlayer) mRefHolder.get();
if (holder == null) {
return;
}
switch (msg.what) {
case MSG_RAISE_ERROR: {
ErrorInfo info = (ErrorInfo) msg.obj;
holder.handlePostedError(info.prevState, info.what, info.extra);
}
break;
}
}
public void release() {
removeCallbacksAndMessages(null);
mRefHolder.clear();
}
public void postError(int prevState, int what, int extra) {
sendMessage(obtainMessage(MSG_RAISE_ERROR, new ErrorInfo(prevState, what, extra)));
}
private static class ErrorInfo {
public int prevState;
public int what;
public int extra;
public ErrorInfo(int prevState, int what, int extra) {
this.prevState = prevState;
this.what = what;
this.extra = extra;
}
}
}
}