348 lines
10 KiB
Java
348 lines
10 KiB
Java
/*
|
|
* 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 androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import android.util.Log;
|
|
import android.widget.Toast;
|
|
|
|
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 Context context;
|
|
@Nullable
|
|
private Playback.PlaybackCallbacks callbacks;
|
|
|
|
private boolean mIsInitialized = false;
|
|
|
|
/**
|
|
* Constructor of <code>MultiPlayer</code>
|
|
*/
|
|
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 <code>player</code> 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 <code>player</code> 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.getInstance(context).gaplessPlayback()) {
|
|
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 == mCurrentMediaPlayer && mNextMediaPlayer != null) {
|
|
mIsInitialized = false;
|
|
mCurrentMediaPlayer.release();
|
|
mCurrentMediaPlayer = mNextMediaPlayer;
|
|
mIsInitialized = true;
|
|
mNextMediaPlayer = null;
|
|
if (callbacks != null)
|
|
callbacks.onTrackWentToNext();
|
|
} else {
|
|
if (callbacks != null)
|
|
callbacks.onTrackEnded();
|
|
}
|
|
}
|
|
|
|
|
|
} |