summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordaniel oeh <daniel.oeh@gmail.com>2013-12-20 21:07:44 +0100
committerdaniel oeh <daniel.oeh@gmail.com>2013-12-20 21:07:44 +0100
commit544898328868ae9d1081707133c9104baae29500 (patch)
tree1fbc6480c5b52788945bfb24e6b6820a1058c6c1 /src
parent513183bc84d2bc7d4f9223f7f3e26d9297badd12 (diff)
downloadAntennaPod-544898328868ae9d1081707133c9104baae29500.zip
Added first version of PlaybackServiceMediaPlayer
Diffstat (limited to 'src')
-rw-r--r--src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java817
1 files changed, 817 insertions, 0 deletions
diff --git a/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java b/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java
new file mode 100644
index 000000000..7333a4919
--- /dev/null
+++ b/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java
@@ -0,0 +1,817 @@
+package de.danoeh.antennapod.service.playback;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.RemoteControlClient;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import de.danoeh.antennapod.AppConfig;
+import de.danoeh.antennapod.feed.Chapter;
+import de.danoeh.antennapod.feed.MediaType;
+import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.receiver.MediaButtonReceiver;
+import de.danoeh.antennapod.util.playback.AudioPlayer;
+import de.danoeh.antennapod.util.playback.IPlayer;
+import de.danoeh.antennapod.util.playback.Playable;
+import de.danoeh.antennapod.util.playback.VideoPlayer;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Manages the MediaPlayer object of the PlaybackService.
+ */
+public class PlaybackServiceMediaPlayer {
+ public static final String TAG = "PlaybackServiceMediaPlayer";
+
+ /**
+ * Return value of some PSMP methods if the method call failed.
+ */
+ public static final int INVALID_TIME = -1;
+
+ private final AudioManager audioManager;
+
+ private volatile PlayerStatus playerStatus;
+ private volatile PlayerStatus statusBeforeSeeking;
+ private volatile IPlayer mediaPlayer;
+ private volatile Playable media;
+
+ private volatile boolean stream;
+ private volatile MediaType mediaType;
+ private volatile AtomicBoolean startWhenPrepared;
+ private volatile boolean pausedBecauseOfTransientAudiofocusLoss;
+
+ /**
+ * Some asynchronous calls might change the state of the MediaPlayer object. Therefore calls in other threads
+ * have to wait until these operations have finished.
+ */
+ private final ReentrantLock playerLock;
+
+ private final PSMPCallback callback;
+ private final Context context;
+
+ private final ExecutorService executor;
+
+ public PlaybackServiceMediaPlayer(Context context, PSMPCallback callback) {
+ if (context == null)
+ throw new IllegalArgumentException("context = null");
+ if (callback == null)
+ throw new IllegalArgumentException("callback = null");
+
+ this.context = context;
+ this.callback = callback;
+ this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ this.playerLock = new ReentrantLock();
+ this.startWhenPrepared = new AtomicBoolean(false);
+ executor = Executors.newSingleThreadExecutor();
+ mediaPlayer = null;
+ statusBeforeSeeking = null;
+ pausedBecauseOfTransientAudiofocusLoss = false;
+ mediaType = MediaType.UNKNOWN;
+ }
+
+ private Handler.Callback handlerCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ return false;
+ }
+ };
+
+ /**
+ * Starts or prepares playback of the specified Playable object. If another Playable object is already being played, the currently playing
+ * episode will be stopped and replaced with the new Playable object. If the Playable object is already being played, the method will
+ * not do anything.
+ * Whether playback starts immediately depends on the given parameters. See below for more details.
+ * <p/>
+ * States:
+ * During execution of the method, the object will be in the INITIALIZING state. The end state depends on the given parameters.
+ * <p/>
+ * If 'prepareImmediately' is set to true, the method will go into PREPARING state and after that into PREPARED state. If
+ * 'startWhenPrepared' is set to true, the method will additionally go into PLAYING state.
+ * <p/>
+ * If an unexpected error occurs while loading the Playable's metadata or while setting the MediaPlayers data source, the object
+ * will enter the ERROR state.
+ * <p/>
+ * This method is executed on an internal executor service.
+ *
+ * @param playable The Playable object that is supposed to be played. This parameter must not be null.
+ * @param stream The type of playback. If false, the Playable object MUST provide access to a locally available file via
+ * getLocalMediaUrl. If true, the Playable object MUST provide access to a resource that can be streamed by
+ * the Android MediaPlayer via getStreamUrl.
+ * @param startWhenPrepared Sets the 'startWhenPrepared' flag. This flag determines whether playback will start immediately after the
+ * episode has been prepared for playback. Setting this flag to true does NOT mean that the episode will be prepared
+ * for playback immediately (see 'prepareImmediately' parameter for more details)
+ * @param prepareImmediately Set to true if the method should also prepare the episode for playback.
+ */
+ public void playMediaObject(final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
+ if (playable == null)
+ throw new IllegalArgumentException("playable = null");
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ playerLock.lock();
+ playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately);
+ playerLock.unlock();
+ }
+ });
+ }
+
+ /**
+ * Internal implementation of playMediaObject. This method has an additional parameter that allows the caller to force a media player reset even if
+ * the given playable parameter is the same object as the currently playing media.
+ * <p/>
+ * This method requires the playerLock and is executed on the caller's thread.
+ *
+ * @see #playMediaObject(de.danoeh.antennapod.util.playback.Playable, boolean, boolean, boolean)
+ */
+ public void playMediaObject(final Playable playable, final boolean forceReset, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
+ if (playable == null)
+ throw new IllegalArgumentException("playable = null");
+ if (!playerLock.isHeldByCurrentThread())
+ throw new IllegalStateException("method requires playerLock");
+
+
+ if (media != null) {
+ if (!forceReset && media.getIdentifier().equals(playable.getIdentifier())) {
+ // episode is already playing -> ignore method call
+ return;
+ } else {
+ // stop playback of this episode
+ setPlayerStatus(PlayerStatus.STOPPED, null);
+ if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PREPARED) {
+ mediaPlayer.stop();
+ }
+ }
+ }
+ createMediaPlayer();
+ media = playable;
+ PlaybackServiceMediaPlayer.this.startWhenPrepared.set(startWhenPrepared);
+ setPlayerStatus(PlayerStatus.INITIALIZING, media);
+ try {
+ media.loadMetadata();
+ if (stream) {
+ mediaPlayer.setDataSource(playable.getStreamUrl());
+ } else {
+ mediaPlayer.setDataSource(playable.getLocalMediaUrl());
+ }
+ setPlayerStatus(PlayerStatus.INITIALIZED, media);
+
+ if (prepareImmediately) {
+ setPlayerStatus(PlayerStatus.PREPARING, media);
+ mediaPlayer.prepare();
+ setPlayerStatus(PlayerStatus.PREPARED, media);
+ onPrepared(startWhenPrepared);
+ }
+
+ } catch (Playable.PlayableException e) {
+ e.printStackTrace();
+ setPlayerStatus(PlayerStatus.ERROR, null);
+ } catch (IOException e) {
+ e.printStackTrace();
+ setPlayerStatus(PlayerStatus.ERROR, null);
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ setPlayerStatus(PlayerStatus.ERROR, null);
+ }
+ }
+
+
+ /**
+ * Resumes playback if the PSMP object is in PREPARED or PAUSED state. If the PSMP object is in an invalid state.
+ * nothing will happen.
+ * <p/>
+ * This method is executed on an internal executor service.
+ */
+ public void resume() {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ playerLock.lock();
+ if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
+ int focusGained = audioManager.requestAudioFocus(
+ audioFocusChangeListener, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+ if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+
+ setSpeed(Float.parseFloat(UserPreferences.getPlaybackSpeed()));
+ mediaPlayer.start();
+ if (playerStatus == PlayerStatus.PREPARED) {
+ mediaPlayer.seekTo(media.getPosition());
+ }
+ setPlayerStatus(PlayerStatus.PLAYING, media);
+ pausedBecauseOfTransientAudiofocusLoss = false;
+ if (android.os.Build.VERSION.SDK_INT >= 14) {
+ RemoteControlClient remoteControlClient = callback.getRemoteControlClient();
+ if (remoteControlClient != null) {
+ audioManager
+ .registerRemoteControlClient(remoteControlClient);
+ }
+ }
+ audioManager
+ .registerMediaButtonEventReceiver(new ComponentName(context.getPackageName(),
+ MediaButtonReceiver.class.getName()));
+ media.onPlaybackStart();
+
+ } else {
+ if (AppConfig.DEBUG) Log.e(TAG, "Failed to request audio focus");
+ }
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Call to resume() was ignored because current state of PSMP object is " + playerStatus);
+ }
+
+ playerLock.unlock();
+ }
+ });
+ }
+
+
+ /**
+ * Saves the current position and pauses playback. Note that, if audiofocus
+ * is abandoned, the lockscreen controls will also disapear.
+ * <p/>
+ * This method is executed on an internal executor service.
+ *
+ * @param abandonFocus is true if the service should release audio focus
+ * @param reinit is true if service should reinit after pausing if the media
+ * file is being streamed
+ */
+ public void pause(final boolean abandonFocus, final boolean reinit) {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ playerLock.lock();
+
+ if (playerStatus == PlayerStatus.PLAYING) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Pausing playback.");
+ mediaPlayer.pause();
+ setPlayerStatus(PlayerStatus.PAUSED, media);
+
+ if (abandonFocus) {
+ audioManager.abandonAudioFocus(audioFocusChangeListener);
+ pausedBecauseOfTransientAudiofocusLoss = false;
+ }
+ if (stream && reinit) {
+ reinit();
+ }
+ }
+
+ playerLock.unlock();
+ }
+ });
+ }
+
+ /**
+ * Prepared media player for playback if the service is in the INITALIZED
+ * state.
+ * <p/>
+ * This method is executed on an internal executor service.
+ */
+ public void prepare() {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ playerLock.lock();
+
+ if (playerStatus == PlayerStatus.INITIALIZED) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Preparing media player");
+ setPlayerStatus(PlayerStatus.PREPARING, media);
+ try {
+ mediaPlayer.prepare();
+ onPrepared(startWhenPrepared.get());
+ } catch (IOException e) {
+ e.printStackTrace();
+ setPlayerStatus(PlayerStatus.ERROR, null);
+ }
+ }
+ playerLock.unlock();
+
+ }
+ });
+ }
+
+ /**
+ * Called after media player has been prepared. This method is executed on the caller's thread.
+ */
+ void onPrepared(final boolean startWhenPrepared) {
+ playerLock.lock();
+
+ if (playerStatus != PlayerStatus.PREPARING)
+ throw new IllegalStateException("Player is not in PREPARING state");
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Resource prepared");
+
+ mediaPlayer.seekTo(media.getPosition());
+ if (media.getDuration() == 0) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting duration of media");
+ media.setDuration(mediaPlayer.getDuration());
+ }
+ setPlayerStatus(PlayerStatus.PREPARED, media);
+
+ if (startWhenPrepared) {
+ resume();
+ }
+
+ playerLock.unlock();
+ }
+
+ /**
+ * Resets the media player and moves it into INITIALIZED state.
+ * <p/>
+ * This method is executed on an internal executor service.
+ */
+ public void reinit() {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ playerLock.lock();
+
+ if (media != null) {
+ playMediaObject(media, true, stream, startWhenPrepared.get(), false);
+ } else if (mediaPlayer != null) {
+ mediaPlayer.reset();
+ } else {
+ if (AppConfig.DEBUG) Log.d(TAG, "Call to reinit was ignored: media and mediaPlayer were null");
+ }
+ playerLock.unlock();
+ }
+ });
+ }
+
+
+ /**
+ * Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
+ * Invalid time values (< 0) will be ignored.
+ * <p/>
+ * This method is executed on the caller's thread.
+ */
+ private void seekToSync(int t) {
+ if (t < 0) {
+ if (AppConfig.DEBUG) Log.d(TAG, "Received invalid value for t");
+ return;
+ }
+ playerLock.lock();
+
+ if (playerStatus == PlayerStatus.PLAYING
+ || playerStatus == PlayerStatus.PAUSED
+ || playerStatus == PlayerStatus.PREPARED) {
+ if (stream) {
+ statusBeforeSeeking = playerStatus;
+ setPlayerStatus(PlayerStatus.SEEKING, media);
+ }
+ mediaPlayer.seekTo(t);
+
+ } else if (playerStatus == PlayerStatus.INITIALIZED) {
+ media.setPosition(t);
+ startWhenPrepared.set(true);
+ prepare();
+ }
+ playerLock.unlock();
+ }
+
+ /**
+ * Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
+ * Invalid time values (< 0) will be ignored.
+ * <p/>
+ * This method is executed on an internal executor service.
+ */
+ public void seekTo(final int t) {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ seekToSync(t);
+ }
+ });
+ }
+
+ /**
+ * Seek a specific position from the current position
+ *
+ * @param d offset from current position (positive or negative)
+ */
+ public void seekDelta(final int d) {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ playerLock.lock();
+ int currentPosition = getPosition();
+ if (currentPosition != INVALID_TIME) {
+ seekToSync(currentPosition + d);
+ } else {
+ Log.e(TAG, "getPosition() returned INVALID_TIME in seekDelta");
+ }
+
+ playerLock.unlock();
+ }
+ });
+ }
+
+ /**
+ * Seek to the start of the specified chapter.
+ */
+ public void seekToChapter(Chapter c) {
+ if (c == null)
+ throw new IllegalArgumentException("c = null");
+ seekTo((int) c.getStart());
+ }
+
+ /**
+ * Returns the duration of the current media object or INVALID_TIME if the duration could not be retrieved.
+ */
+ public int getDuration() {
+ if (!playerLock.tryLock()) {
+ return INVALID_TIME;
+ }
+
+ int retVal = INVALID_TIME;
+ if (playerStatus == PlayerStatus.PLAYING
+ || playerStatus == PlayerStatus.PAUSED
+ || playerStatus == PlayerStatus.PREPARED) {
+ retVal = mediaPlayer.getDuration();
+ } else if (media != null && media.getDuration() > 0) {
+ retVal = media.getDuration();
+ }
+
+ playerLock.unlock();
+ return retVal;
+ }
+
+ /**
+ * Returns the position of the current media object or INVALID_TIME if the position could not be retrieved.
+ */
+ public int getPosition() {
+ if (!playerLock.tryLock()) {
+ return INVALID_TIME;
+ }
+
+ int retVal = INVALID_TIME;
+ if (playerStatus == PlayerStatus.PLAYING
+ || playerStatus == PlayerStatus.PAUSED
+ || playerStatus == PlayerStatus.PREPARED) {
+ retVal = mediaPlayer.getCurrentPosition();
+ } else if (media != null && media.getPosition() > 0) {
+ retVal = media.getPosition();
+ }
+
+ playerLock.unlock();
+ return retVal;
+ }
+
+ /**
+ * Returns true if the playback speed can be adjusted. This method can also return false if the PSMP object's
+ * internal MediaPlayer cannot be accessed at the moment.
+ */
+ public boolean canSetSpeed() {
+ if (!playerLock.tryLock()) {
+ return false;
+ }
+ boolean retVal = false;
+ if (mediaPlayer != null && media != null && media.getMediaType() == MediaType.AUDIO) {
+ retVal = (mediaPlayer).canSetSpeed();
+ }
+
+ playerLock.unlock();
+ return retVal;
+ }
+
+ /**
+ * Sets the playback speed.
+ * This method is executed on the caller's thread.
+ */
+ private void setSpeedSync(float speed) {
+ playerLock.lock();
+ if (media != null && media.getMediaType() == MediaType.AUDIO) {
+ AudioPlayer audioPlayer = (AudioPlayer) mediaPlayer;
+ if (audioPlayer.canSetSpeed()) {
+ audioPlayer.setPlaybackSpeed((float) speed);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Playback speed was set to " + speed);
+ callback.playbackSpeedChanged(speed);
+ }
+ }
+ playerLock.unlock();
+ }
+
+ /**
+ * Sets the playback speed.
+ * This method is executed on an internal executor service.
+ */
+ public void setSpeed(final float speed) {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ setSpeedSync(speed);
+ }
+ });
+ }
+
+ public MediaType getCurrentMediaType() {
+ return mediaType;
+ }
+
+ /**
+ * Releases internally used resources. This method should only be called when the object is not used anymore.
+ */
+ public void shutdown() {
+ executor.shutdown();
+ if (mediaPlayer != null) {
+ mediaPlayer.release();
+ }
+ }
+
+ /**
+ * Returns a PSMInfo object that contains information about the current state of the PSMP object.
+ *
+ * @return The PSMPInfo object.
+ */
+ public synchronized PSMPInfo getPSMPInfo() {
+ return new PSMPInfo(playerStatus, media);
+ }
+
+ /**
+ * Sets the player status of the PSMP object. PlayerStatus and media attributes have to be set at the same time
+ * so that getPSMPInfo can't return an invalid state (e.g. status is PLAYING, but media is null).
+ * <p/>
+ * This method will notify the callback about the change of the player status (even if the new status is the same
+ * as the old one).
+ *
+ * @param newStatus The new PlayerStatus. This must not be null.
+ * @param newMedia The new playable object of the PSMP object. This can be null.
+ */
+ private synchronized void setPlayerStatus(PlayerStatus newStatus, Playable newMedia) {
+ if (newStatus == null)
+ throw new IllegalArgumentException("newStatus = null");
+ this.playerStatus = newStatus;
+ this.media = newMedia;
+ callback.statusChanged(new PSMPInfo(playerStatus, media));
+ }
+
+ private IPlayer createMediaPlayer() {
+ if (mediaPlayer != null) {
+ mediaPlayer.release();
+ }
+ if (media == null || media.getMediaType() == MediaType.VIDEO) {
+ mediaPlayer = new VideoPlayer();
+ } else {
+ mediaPlayer = new AudioPlayer(context);
+ }
+ return setMediaPlayerListeners(mediaPlayer);
+ }
+
+ private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
+
+ @Override
+ public void onAudioFocusChange(final int focusChange) {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ playerLock.lock();
+
+ switch (focusChange) {
+ case AudioManager.AUDIOFOCUS_LOSS:
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Lost audio focus");
+ pause(true, false);
+ callback.shouldStop();
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN:
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Gained audio focus");
+ if (pausedBecauseOfTransientAudiofocusLoss) {
+ audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+ AudioManager.ADJUST_RAISE, 0);
+ resume();
+ }
+ break;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ if (playerStatus == PlayerStatus.PLAYING) {
+ if (!UserPreferences.shouldPauseForFocusLoss()) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Lost audio focus temporarily. Ducking...");
+ audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+ AudioManager.ADJUST_LOWER, 0);
+ pausedBecauseOfTransientAudiofocusLoss = true;
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing...");
+ pause(false, false);
+ pausedBecauseOfTransientAudiofocusLoss = true;
+ }
+ }
+ break;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ if (playerStatus == PlayerStatus.PLAYING) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Lost audio focus temporarily. Pausing...");
+ pause(false, false);
+ pausedBecauseOfTransientAudiofocusLoss = true;
+ }
+ }
+
+ playerLock.unlock();
+ }
+ });
+
+ }
+ };
+
+ /**
+ * Holds information about a PSMP object.
+ */
+ public class PSMPInfo {
+ public PlayerStatus playerStatus;
+ public Playable playable;
+
+ public PSMPInfo(PlayerStatus playerStatus, Playable playable) {
+ this.playerStatus = playerStatus;
+ this.playable = playable;
+ }
+ }
+
+ public static interface PSMPCallback {
+ public void statusChanged(PSMPInfo newInfo);
+
+ public void shouldStop();
+
+ public void playbackSpeedChanged(float s);
+
+ public void onBufferingUpdate(int percent);
+
+ public boolean onMediaPlayerInfo(int code);
+
+ public boolean onMediaPlayerError(Object inObj, int what, int extra);
+
+ public boolean endPlayback(boolean playNextEpisode);
+
+ public RemoteControlClient getRemoteControlClient();
+ }
+
+ private final com.aocate.media.MediaPlayer.OnPreparedListener audioPreparedListener = new com.aocate.media.MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(final com.aocate.media.MediaPlayer mp) {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ PlaybackServiceMediaPlayer.this.onPrepared(startWhenPrepared.get());
+ }
+ });
+ }
+ };
+
+ private final android.media.MediaPlayer.OnPreparedListener videoPreparedListener = new android.media.MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(android.media.MediaPlayer mp) {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ PlaybackServiceMediaPlayer.this.onPrepared(startWhenPrepared.get());
+ }
+ });
+ }
+ };
+
+ private IPlayer setMediaPlayerListeners(IPlayer mp) {
+ if (mp != null && media != null) {
+ if (media.getMediaType() == MediaType.AUDIO) {
+ ((AudioPlayer) mp).setOnPreparedListener(audioPreparedListener);
+ ((AudioPlayer) mp)
+ .setOnCompletionListener(audioCompletionListener);
+ ((AudioPlayer) mp)
+ .setOnSeekCompleteListener(audioSeekCompleteListener);
+ ((AudioPlayer) mp).setOnErrorListener(audioErrorListener);
+ ((AudioPlayer) mp)
+ .setOnBufferingUpdateListener(audioBufferingUpdateListener);
+ ((AudioPlayer) mp).setOnInfoListener(audioInfoListener);
+ } else {
+ ((VideoPlayer) mp).setOnPreparedListener(videoPreparedListener);
+ ((VideoPlayer) mp)
+ .setOnCompletionListener(videoCompletionListener);
+ ((VideoPlayer) mp)
+ .setOnSeekCompleteListener(videoSeekCompleteListener);
+ ((VideoPlayer) mp).setOnErrorListener(videoErrorListener);
+ ((VideoPlayer) mp)
+ .setOnBufferingUpdateListener(videoBufferingUpdateListener);
+ ((VideoPlayer) mp).setOnInfoListener(videoInfoListener);
+ }
+ }
+ return mp;
+ }
+
+ private final com.aocate.media.MediaPlayer.OnCompletionListener audioCompletionListener = new com.aocate.media.MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(com.aocate.media.MediaPlayer mp) {
+ genericOnCompletion();
+ }
+ };
+
+ private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(android.media.MediaPlayer mp) {
+ genericOnCompletion();
+ }
+ };
+
+ private void genericOnCompletion() {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ audioManager.abandonAudioFocus(audioFocusChangeListener);
+ callback.endPlayback(true);
+ }
+ });
+
+ }
+
+ private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() {
+ @Override
+ public void onBufferingUpdate(com.aocate.media.MediaPlayer mp,
+ int percent) {
+ genericOnBufferingUpdate(percent);
+ }
+ };
+
+ private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
+ @Override
+ public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
+ genericOnBufferingUpdate(percent);
+ }
+ };
+
+ private void genericOnBufferingUpdate(int percent) {
+ callback.onBufferingUpdate(percent);
+ }
+
+ private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() {
+ @Override
+ public boolean onInfo(com.aocate.media.MediaPlayer mp, int what,
+ int extra) {
+ return genericInfoListener(what);
+ }
+ };
+
+ private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() {
+ @Override
+ public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
+ return genericInfoListener(what);
+ }
+ };
+
+ private boolean genericInfoListener(int what) {
+ return callback.onMediaPlayerInfo(what);
+ }
+
+ private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(com.aocate.media.MediaPlayer mp, int what,
+ int extra) {
+ return genericOnError(mp, what, extra);
+ }
+ };
+
+ private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
+ return genericOnError(mp, what, extra);
+ }
+ };
+
+ private boolean genericOnError(Object inObj, int what, int extra) {
+ return callback.onMediaPlayerError(inObj, what, extra);
+ }
+
+ private final com.aocate.media.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new com.aocate.media.MediaPlayer.OnSeekCompleteListener() {
+ @Override
+ public void onSeekComplete(com.aocate.media.MediaPlayer mp) {
+ genericSeekCompleteListener();
+ }
+ };
+
+ private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
+ @Override
+ public void onSeekComplete(android.media.MediaPlayer mp) {
+ genericSeekCompleteListener();
+ }
+ };
+
+ private final void genericSeekCompleteListener() {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ playerLock.lock();
+ if (playerStatus == PlayerStatus.SEEKING) {
+ setPlayerStatus(statusBeforeSeeking, media);
+ }
+ playerLock.unlock();
+ }
+ });
+ }
+}