Clean ups.

This commit is contained in:
Karim Abou Zeid 2016-03-12 12:46:37 +01:00
commit 34f8ffbaf2
3 changed files with 175 additions and 85 deletions

View file

@ -1,11 +1,11 @@
package com.kabouzeid.gramophone.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.Handler;
import android.os.PowerManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -13,32 +13,30 @@ import android.util.Log;
import android.widget.Toast;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.service.Playback.Playback;
import com.kabouzeid.gramophone.util.PreferenceUtil;
import java.lang.ref.WeakReference;
/**
* @author Andrew Neal, Karim Abou Zeid (kabouzeid)
*/
public class MultiPlayer implements MediaPlayer.OnErrorListener,
MediaPlayer.OnCompletionListener {
public class MultiPlayer implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
public static final String TAG = MultiPlayer.class.getSimpleName();
private final WeakReference<MusicService> mService;
private DoubleMediaPlayer mCurrentMediaPlayer = new DoubleMediaPlayer();
private DoubleMediaPlayer mNextMediaPlayer;
private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
private MediaPlayer mNextMediaPlayer;
private Handler mHandler;
private Context context;
@Nullable
private Playback.PlaybackCallbacks callbacks;
private boolean mIsInitialized = false;
/**
* Constructor of <code>MultiPlayer</code>
*/
public MultiPlayer(final MusicService service) {
mService = new WeakReference<>(service);
mCurrentMediaPlayer.setWakeMode(service, PowerManager.PARTIAL_WAKE_LOCK);
public MultiPlayer(final Context context) {
this.context = context;
mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
}
/**
@ -47,6 +45,7 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
* @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);
@ -64,15 +63,14 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
* ready to play, false otherwise
*/
private boolean setDataSourceImpl(@NonNull final MediaPlayer player, @NonNull final String path) {
MusicService service = mService.get();
if (service == null) {
if (context == null) {
return false;
}
try {
player.reset();
player.setOnPreparedListener(null);
if (path.startsWith("content://")) {
player.setDataSource(service, Uri.parse(path));
player.setDataSource(context, Uri.parse(path));
} else {
player.setDataSource(path);
}
@ -85,9 +83,9 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
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, service.getPackageName());
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC);
service.sendBroadcast(intent);
context.sendBroadcast(intent);
return true;
}
@ -97,9 +95,9 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
* @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) {
MusicService service = mService.get();
if (service == null) {
if (context == null) {
return;
}
try {
@ -117,9 +115,9 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
if (path == null) {
return;
}
if (PreferenceUtil.getInstance(mService.get()).gaplessPlayback()) {
mNextMediaPlayer = new MediaPlayer();
mNextMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
if (PreferenceUtil.getInstance(context).gaplessPlayback()) {
mNextMediaPlayer = new DoubleMediaPlayer();
mNextMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
if (setDataSourceImpl(mNextMediaPlayer, path)) {
try {
@ -141,17 +139,19 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
}
/**
* Sets the handler
* Sets the callbacks
*
* @param handler The handler to use
* @param callbacks The callbacks to use
*/
public void setHandler(final Handler handler) {
mHandler = handler;
@Override
public void setCallbacks(final Playback.PlaybackCallbacks callbacks) {
this.callbacks = callbacks;
}
/**
* @return True if the player is ready to go, false otherwise
*/
@Override
public boolean isInitialized() {
return mIsInitialized;
}
@ -159,6 +159,7 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
/**
* Starts or resumes playback.
*/
@Override
public boolean start() {
try {
mCurrentMediaPlayer.start();
@ -171,6 +172,7 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
/**
* Resets the MediaPlayer to its uninitialized state.
*/
@Override
public void stop() {
mCurrentMediaPlayer.reset();
mIsInitialized = false;
@ -179,6 +181,7 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
/**
* Releases resources associated with this MediaPlayer object.
*/
@Override
public void release() {
stop();
mCurrentMediaPlayer.release();
@ -190,6 +193,7 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
/**
* Pauses playback. Call start() to resume.
*/
@Override
public boolean pause() {
try {
mCurrentMediaPlayer.pause();
@ -202,6 +206,7 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
/**
* Checks whether the MultiPlayer is playing.
*/
@Override
public boolean isPlaying() {
return mIsInitialized && mCurrentMediaPlayer.isPlaying();
}
@ -211,6 +216,7 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
*
* @return The duration in milliseconds
*/
@Override
public int duration() {
if (!mIsInitialized) {
return -1;
@ -227,6 +233,7 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
*
* @return The current position in milliseconds
*/
@Override
public int position() {
if (!mIsInitialized) {
return -1;
@ -244,6 +251,7 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
* @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);
@ -253,6 +261,7 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
}
}
@Override
public boolean setVolume(final float vol) {
try {
mCurrentMediaPlayer.setVolume(vol, vol);
@ -267,6 +276,7 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
*
* @param sessionId The audio session ID
*/
@Override
public boolean setAudioSessionId(final int sessionId) {
try {
mCurrentMediaPlayer.setAudioSessionId(sessionId);
@ -281,6 +291,7 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
*
* @return The current audio session ID.
*/
@Override
public int getAudioSessionId() {
return mCurrentMediaPlayer.getAudioSessionId();
}
@ -292,9 +303,11 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
public boolean onError(final MediaPlayer mp, final int what, final int extra) {
mIsInitialized = false;
mCurrentMediaPlayer.release();
mCurrentMediaPlayer = new MediaPlayer();
mCurrentMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
Toast.makeText(mService.get(), mService.get().getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
mCurrentMediaPlayer = new DoubleMediaPlayer();
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;
}
@ -303,21 +316,46 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
*/
@Override
public void onCompletion(final MediaPlayer mp) {
MusicService service = mService.get();
if (service == null) {
return;
}
if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
mIsInitialized = false;
mCurrentMediaPlayer.release();
mCurrentMediaPlayer = mNextMediaPlayer;
mIsInitialized = true;
mNextMediaPlayer = null;
mHandler.sendEmptyMessage(MusicService.TRACK_WENT_TO_NEXT);
if (callbacks != null)
callbacks.onTrackWentToNext();
} else {
service.acquireWakeLock(30000);
mHandler.sendEmptyMessage(MusicService.TRACK_ENDED);
mHandler.sendEmptyMessage(MusicService.RELEASE_WAKELOCK);
if (callbacks != null)
callbacks.onTrackEnded();
}
}
private static final class DoubleMediaPlayer extends MediaPlayer implements MediaPlayer.OnCompletionListener {
private MediaPlayer mNextPlayer;
private OnCompletionListener mCompletion;
public DoubleMediaPlayer() {
super.setOnCompletionListener(this);
}
@Override
public void setNextMediaPlayer(final MediaPlayer next) {
mNextPlayer = next;
}
@Override
public void setOnCompletionListener(final OnCompletionListener listener) {
mCompletion = listener;
}
@Override
public void onCompletion(final MediaPlayer mp) {
if (mNextPlayer != null) {
// SystemClock.sleep(25);
mNextPlayer.start();
}
mCompletion.onCompletion(this);
}
}
}

View file

@ -46,6 +46,7 @@ import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.provider.HistoryStore;
import com.kabouzeid.gramophone.provider.MusicPlaybackQueueStore;
import com.kabouzeid.gramophone.provider.SongPlayCountStore;
import com.kabouzeid.gramophone.service.Playback.Playback;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.PreferenceUtil;
import com.kabouzeid.gramophone.util.Util;
@ -57,7 +58,7 @@ import java.util.List;
/**
* @author Karim Abou Zeid (kabouzeid), Andrew Neal
*/
public class MusicService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener {
public class MusicService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks {
public static final String TAG = MusicService.class.getSimpleName();
public static final String PHONOGRAPH_PACKAGE_NAME = "com.kabouzeid.gramophone";
@ -100,7 +101,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
private static final int UNDUCK = 7;
private final IBinder musicBind = new MusicBinder();
private MultiPlayer player;
private Playback playback;
private ArrayList<Song> playingQueue = new ArrayList<>();
private ArrayList<Song> originalPlayingQueue = new ArrayList<>();
private int position = -1;
@ -114,7 +115,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
@SuppressWarnings("deprecation")
private RemoteControlClient remoteControlClient;
private PowerManager.WakeLock wakeLock;
private MusicPlayerHandler playerHandler;
private PlaybackHandler playerHandler;
private final AudioManager.OnAudioFocusChangeListener audioFocusListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(final int focusChange) {
@ -161,17 +162,15 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
wakeLock.setReferenceCounted(false);
musicPlayerHandlerThread = new HandlerThread("MusicPlayerHandler",
Process.THREAD_PRIORITY_BACKGROUND);
musicPlayerHandlerThread = new HandlerThread("PlaybackHandler");
musicPlayerHandlerThread.start();
playerHandler = new MusicPlayerHandler(this, musicPlayerHandlerThread.getLooper());
playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper());
player = new MultiPlayer(this);
player.setHandler(playerHandler);
playback = new MultiPlayer(this);
playback.setCallbacks(this);
// queue saving needs to run on a separate thread so that it doesn't block the player handler events
queueSaveHandlerThread = new HandlerThread("QueueSaveHandler",
Process.THREAD_PRIORITY_BACKGROUND);
// queue saving needs to run on a separate thread so that it doesn't block the playback handler events
queueSaveHandlerThread = new HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND);
queueSaveHandlerThread.start();
queueSaveHandler = new QueueSaveHandler(this, queueSaveHandlerThread.getLooper());
@ -326,20 +325,20 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
} else {
queueSaveHandlerThread.quit();
}
player.release();
player = null;
playback.release();
playback = null;
}
public void stop() {
pausedByTransientLossOfFocus = false;
savePositionInTrack();
player.stop();
playback.stop();
notifyChange(PLAY_STATE_CHANGED);
getAudioManager().abandonAudioFocus(audioFocusListener);
}
public boolean isPlaying() {
return player.isPlaying();
return playback.isPlaying();
}
public void saveState() {
@ -369,17 +368,13 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
return position;
}
private void setPositionInternal(int position) {
this.position = position;
}
public void playNextSong(boolean force) {
playSongAt(getNextPosition(force));
}
private boolean openTrackAndPrepareNextAt(int position) {
synchronized (this) {
setPositionInternal(position);
this.position = position;
boolean prepared = openCurrent();
if (prepared) prepareNextImpl();
notifyChange(META_CHANGED);
@ -391,7 +386,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
private boolean openCurrent() {
synchronized (this) {
try {
return player.setDataSource(getTrackUri(getCurrentSong()));
return playback.setDataSource(getTrackUri(getCurrentSong()));
} catch (Exception e) {
return false;
}
@ -407,7 +402,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
synchronized (this) {
try {
int nextPosition = getNextPosition(false);
player.setNextDataSource(getTrackUri(getSongAt(nextPosition)));
playback.setNextDataSource(getTrackUri(getSongAt(nextPosition)));
this.nextPosition = nextPosition;
return true;
} catch (Exception e) {
@ -418,7 +413,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
private void closeAudioEffectSession() {
final Intent audioEffectsIntent = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, player.getAudioSessionId());
audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback.getAudioSessionId());
audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
sendBroadcast(audioEffectsIntent);
}
@ -606,7 +601,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
this.originalPlayingQueue = restoredOriginalQueue;
this.playingQueue = restoredQueue;
setPositionInternal(restoredPosition);
position = restoredPosition;
openCurrent();
prepareNext();
@ -668,7 +663,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
private void rePosition(int deletedPosition) {
int currentPosition = getPosition();
if (deletedPosition < currentPosition) {
setPositionInternal(currentPosition - 1);
position = currentPosition - 1;
} else if (deletedPosition == currentPosition) {
if (playingQueue.size() > deletedPosition) {
setPosition(position);
@ -688,11 +683,11 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
originalPlayingQueue.add(to, tmpSong);
}
if (from > currentPosition && to <= currentPosition) {
setPositionInternal(currentPosition + 1);
position = currentPosition + 1;
} else if (from < currentPosition && to >= currentPosition) {
setPositionInternal(currentPosition - 1);
position = currentPosition - 1;
} else if (from == currentPosition) {
setPositionInternal(to);
position = to;
}
notifyChange(QUEUE_CHANGED);
}
@ -727,8 +722,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
public void pause() {
pausedByTransientLossOfFocus = false;
if (player.isPlaying()) {
player.pause();
if (playback.isPlaying()) {
playback.pause();
notifyChange(PLAY_STATE_CHANGED);
}
}
@ -736,12 +731,12 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
public void play() {
synchronized (this) {
if (requestFocus()) {
if (!player.isPlaying()) {
if (!player.isInitialized()) {
if (!playback.isPlaying()) {
if (!playback.isInitialized()) {
playSongAt(getPosition());
} else {
registerReceiversAndRemoteControlClient();
player.start();
playback.start();
if (notHandledMetaChangedForCurrentTrack) {
handleChange(META_CHANGED);
notHandledMetaChangedForCurrentTrack = false;
@ -795,15 +790,15 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
}
public int getSongProgressMillis() {
return player.position();
return playback.position();
}
public int getSongDurationMillis() {
return player.duration();
return playback.duration();
}
public int seek(int millis) {
int newPosition = player.seek(millis);
int newPosition = playback.seek(millis);
savePositionInTrack();
return newPosition;
}
@ -842,7 +837,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
case SHUFFLE_MODE_SHUFFLE:
this.shuffleMode = shuffleMode;
ShuffleHelper.makeShuffleList(this.getPlayingQueue(), getPosition());
setPositionInternal(0);
position = 0;
break;
case SHUFFLE_MODE_NONE:
this.shuffleMode = shuffleMode;
@ -854,7 +849,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
newPosition = getPlayingQueue().indexOf(song);
}
}
setPositionInternal(newPosition);
position = newPosition;
break;
}
notifyChange(SHUFFLE_MODE_CHANGED);
@ -924,7 +919,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
}
public int getAudioSessionId() {
return player.getAudioSessionId();
return playback.getAudioSessionId();
}
public void releaseWakeLock() {
@ -944,7 +939,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
if (sharedPreferences.getBoolean(key, false)) {
prepareNext();
} else {
player.setNextDataSource(null);
playback.setNextDataSource(null);
}
break;
case PreferenceUtil.ALBUM_ART_ON_LOCKSCREEN:
@ -957,6 +952,17 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
}
}
@Override
public void onTrackWentToNext() {
playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
}
@Override
public void onTrackEnded() {
acquireWakeLock(30000);
playerHandler.sendEmptyMessage(TRACK_ENDED);
}
private static final class QueueSaveHandler extends Handler {
@NonNull
private final WeakReference<MusicService> mService;
@ -977,12 +983,12 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
}
}
private static final class MusicPlayerHandler extends Handler {
private static final class PlaybackHandler extends Handler {
@NonNull
private final WeakReference<MusicService> mService;
private float currentDuckVolume = 1.0f;
public MusicPlayerHandler(final MusicService service, @NonNull final Looper looper) {
public PlaybackHandler(final MusicService service, @NonNull final Looper looper) {
super(looper);
mService = new WeakReference<>(service);
}
@ -1002,7 +1008,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
} else {
currentDuckVolume = .2f;
}
service.player.setVolume(currentDuckVolume);
service.playback.setVolume(currentDuckVolume);
break;
case UNDUCK:
@ -1012,7 +1018,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
} else {
currentDuckVolume = 1.0f;
}
service.player.setVolume(currentDuckVolume);
service.playback.setVolume(currentDuckVolume);
break;
case TRACK_WENT_TO_NEXT:
@ -1020,7 +1026,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
service.pause();
service.seek(0);
} else {
service.setPositionInternal(service.nextPosition);
service.position = service.nextPosition;
service.prepareNextImpl();
service.notifyChange(META_CHANGED);
}
@ -1033,6 +1039,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
} else {
service.playNextSong(false);
}
sendEmptyMessage(RELEASE_WAKELOCK);
break;
case RELEASE_WAKELOCK:
@ -1065,14 +1072,14 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
break;
case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback and release media player
// Lost focus for an unbounded amount of time: stop playback and release media playback
service.pause();
service.unregisterReceiversAndRemoteControlClient();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// Lost focus for a short time, but we have to stop
// playback. We don't release the media player because playback
// playback. We don't release the media playback because playback
// is likely to resume
boolean wasPlaying = service.isPlaying();
service.pause();

View file

@ -0,0 +1,45 @@
package com.kabouzeid.gramophone.service.Playback;
import android.support.annotation.Nullable;
/**
* @author Karim Abou Zeid (kabouzeid)
*/
public interface Playback {
boolean setDataSource(String path);
void setNextDataSource(@Nullable String path);
void setCallbacks(PlaybackCallbacks callbacks);
boolean isInitialized();
boolean start();
void stop();
void release();
boolean pause();
boolean isPlaying();
int duration();
int position();
int seek(int whereto);
boolean setVolume(float vol);
boolean setAudioSessionId(int sessionId);
int getAudioSessionId();
interface PlaybackCallbacks {
void onTrackWentToNext();
void onTrackEnded();
}
}