/* * Copyright (c) 2019 Hemanth Savarala. * * Licensed under the GNU General Public License v3 * * This is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by * the Free Software Foundation either version 3 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. */ package code.name.monkey.retromusic.service; import android.content.Context; import android.content.Intent; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.audiofx.AudioEffect; import android.net.Uri; import android.os.PowerManager; import android.util.Log; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.service.playback.Playback; import code.name.monkey.retromusic.util.PreferenceUtil; /** @author Andrew Neal, Karim Abou Zeid (kabouzeid) */ public class MultiPlayer implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { public static final String TAG = MultiPlayer.class.getSimpleName(); private MediaPlayer mCurrentMediaPlayer = new MediaPlayer(); private MediaPlayer mNextMediaPlayer; private final Context context; @Nullable private Playback.PlaybackCallbacks callbacks; private boolean mIsInitialized = false; /** Constructor of MultiPlayer */ MultiPlayer(final Context context) { this.context = context; mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); } /** * @param path The path of the file, or the http/rtsp URL of the stream you want to play * @return True if the player has been prepared and is ready to play, false otherwise */ @Override public boolean setDataSource(@NonNull final String path) { mIsInitialized = false; mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path); if (mIsInitialized) { setNextDataSource(null); } return mIsInitialized; } /** * @param player The {@link MediaPlayer} to use * @param path The path of the file, or the http/rtsp URL of the stream you want to play * @return True if the player has been prepared and is ready to play, false otherwise */ private boolean setDataSourceImpl(@NonNull final MediaPlayer player, @NonNull final String path) { if (context == null) { return false; } try { player.reset(); player.setOnPreparedListener(null); if (path.startsWith("content://")) { player.setDataSource(context, Uri.parse(path)); } else { player.setDataSource(path); } player.setAudioStreamType(AudioManager.STREAM_MUSIC); player.prepare(); } catch (Exception e) { return false; } player.setOnCompletionListener(this); player.setOnErrorListener(this); final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId()); intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName()); intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC); context.sendBroadcast(intent); return true; } /** * Set the MediaPlayer to start when this MediaPlayer finishes playback. * * @param path The path of the file, or the http/rtsp URL of the stream you want to play */ @Override public void setNextDataSource(@Nullable final String path) { if (context == null) { return; } try { mCurrentMediaPlayer.setNextMediaPlayer(null); } catch (IllegalArgumentException e) { Log.i(TAG, "Next media player is current one, continuing"); } catch (IllegalStateException e) { Log.e(TAG, "Media player not initialized!"); return; } if (mNextMediaPlayer != null) { mNextMediaPlayer.release(); mNextMediaPlayer = null; } if (path == null) { return; } if (PreferenceUtil.INSTANCE.isGapLessPlayback()) { mNextMediaPlayer = new MediaPlayer(); mNextMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); mNextMediaPlayer.setAudioSessionId(getAudioSessionId()); if (setDataSourceImpl(mNextMediaPlayer, path)) { try { mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer); } catch (@NonNull IllegalArgumentException | IllegalStateException e) { Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e); if (mNextMediaPlayer != null) { mNextMediaPlayer.release(); mNextMediaPlayer = null; } } } else { if (mNextMediaPlayer != null) { mNextMediaPlayer.release(); mNextMediaPlayer = null; } } } } /** * Sets the callbacks * * @param callbacks The callbacks to use */ @Override public void setCallbacks(@Nullable final Playback.PlaybackCallbacks callbacks) { this.callbacks = callbacks; } /** @return True if the player is ready to go, false otherwise */ @Override public boolean isInitialized() { return mIsInitialized; } /** Starts or resumes playback. */ @Override public boolean start() { try { mCurrentMediaPlayer.start(); return true; } catch (IllegalStateException e) { return false; } } /** Resets the MediaPlayer to its uninitialized state. */ @Override public void stop() { mCurrentMediaPlayer.reset(); mIsInitialized = false; } /** Releases resources associated with this MediaPlayer object. */ @Override public void release() { stop(); mCurrentMediaPlayer.release(); if (mNextMediaPlayer != null) { mNextMediaPlayer.release(); } } /** Pauses playback. Call start() to resume. */ @Override public boolean pause() { try { mCurrentMediaPlayer.pause(); return true; } catch (IllegalStateException e) { return false; } } /** Checks whether the MultiPlayer is playing. */ @Override public boolean isPlaying() { return mIsInitialized && mCurrentMediaPlayer.isPlaying(); } /** * Gets the duration of the file. * * @return The duration in milliseconds */ @Override public int duration() { if (!mIsInitialized) { return -1; } try { return mCurrentMediaPlayer.getDuration(); } catch (IllegalStateException e) { return -1; } } /** * Gets the current playback position. * * @return The current position in milliseconds */ @Override public int position() { if (!mIsInitialized) { return -1; } try { return mCurrentMediaPlayer.getCurrentPosition(); } catch (IllegalStateException e) { return -1; } } /** * Gets the current playback position. * * @param whereto The offset in milliseconds from the start to seek to * @return The offset in milliseconds from the start to seek to */ @Override public int seek(final int whereto) { try { mCurrentMediaPlayer.seekTo(whereto); return whereto; } catch (IllegalStateException e) { return -1; } } @Override public boolean setVolume(final float vol) { try { mCurrentMediaPlayer.setVolume(vol, vol); return true; } catch (IllegalStateException e) { return false; } } /** * Sets the audio session ID. * * @param sessionId The audio session ID */ @Override public boolean setAudioSessionId(final int sessionId) { try { mCurrentMediaPlayer.setAudioSessionId(sessionId); return true; } catch (@NonNull IllegalArgumentException | IllegalStateException e) { return false; } } /** * Returns the audio session ID. * * @return The current audio session ID. */ @Override public int getAudioSessionId() { return mCurrentMediaPlayer.getAudioSessionId(); } /** {@inheritDoc} */ @Override public boolean onError(final MediaPlayer mp, final int what, final int extra) { mIsInitialized = false; mCurrentMediaPlayer.release(); mCurrentMediaPlayer = new MediaPlayer(); mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); if (context != null) { Toast.makeText( context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT) .show(); } return false; } /** {@inheritDoc} */ @Override public void onCompletion(final MediaPlayer mp) { if (mp.equals(mCurrentMediaPlayer) && mNextMediaPlayer != null) { mIsInitialized = false; mCurrentMediaPlayer.release(); mCurrentMediaPlayer = mNextMediaPlayer; mIsInitialized = true; mNextMediaPlayer = null; if (callbacks != null) callbacks.onTrackWentToNext(); } else { if (callbacks != null) callbacks.onTrackEnded(); } } @Override public void setCrossFadeDuration(int duration) { } }