Add PlaybackState broadcast messages

They can be used to retrieve the current playback
 * Duration
 * Played time
 * If the media player is playing
This commit is contained in:
Coffeemakr 2016-12-27 15:52:02 +01:00
parent 9494f3a299
commit 659d0d6115
1 changed files with 209 additions and 32 deletions

View File

@ -14,6 +14,8 @@ import android.media.AudioManager;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.IBinder; import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PowerManager; import android.os.PowerManager;
import android.support.v7.app.NotificationCompat; import android.support.v7.app.NotificationCompat;
import android.util.Log; import android.util.Log;
@ -27,6 +29,7 @@ import org.schabi.newpipe.detail.VideoItemDetailActivity;
import org.schabi.newpipe.detail.VideoItemDetailFragment; import org.schabi.newpipe.detail.VideoItemDetailFragment;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
/** /**
* Created by Adam Howard on 08/11/15. * Created by Adam Howard on 08/11/15.
@ -51,10 +54,13 @@ import java.io.IOException;
/**Plays the audio stream of videos in the background.*/ /**Plays the audio stream of videos in the background.*/
public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPreparedListener*/ { public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPreparedListener*/ {
private static final String TAG = BackgroundPlayer.class.toString(); private static final String TAG = "BackgroundPlayer";
private static final String ACTION_STOP = TAG + ".STOP"; private static final String CLASSNAME = "org.schabi.newpipe.player.BackgroundPlayer";
private static final String ACTION_PLAYPAUSE = TAG + ".PLAYPAUSE"; private static final String ACTION_STOP = CLASSNAME + ".STOP";
private static final String ACTION_REWIND = TAG + ".REWIND"; private static final String ACTION_PLAYPAUSE = CLASSNAME + ".PLAYPAUSE";
private static final String ACTION_REWIND = CLASSNAME + ".REWIND";
private static final String ACTION_PLAYBACK_STATE = CLASSNAME + ".PLAYBACK_STATE";
private static final String EXTRA_PLAYBACK_STATE = CLASSNAME + ".extras.EXTRA_PLAYBACK_STATE";
// Extra intent arguments // Extra intent arguments
public static final String TITLE = "title"; public static final String TITLE = "title";
@ -114,6 +120,7 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
isRunning = false; isRunning = false;
} }
private class PlayerThread extends Thread { private class PlayerThread extends Thread {
MediaPlayer mediaPlayer; MediaPlayer mediaPlayer;
private String source; private String source;
@ -124,6 +131,7 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
private WifiManager.WifiLock wifiLock; private WifiManager.WifiLock wifiLock;
private Bitmap videoThumbnail; private Bitmap videoThumbnail;
private NoteBuilder noteBuilder; private NoteBuilder noteBuilder;
private volatile boolean donePlaying = false;
public PlayerThread(String src, String title, BackgroundPlayer owner) { public PlayerThread(String src, String title, BackgroundPlayer owner) {
this.source = src; this.source = src;
@ -133,6 +141,43 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
} }
public boolean isDonePlaying() {
return donePlaying;
}
private boolean isPlaying() {
try {
return mediaPlayer.isPlaying();
} catch (IllegalStateException e) {
Log.w(TAG, "Unable to retrieve playing state", e);
return false;
}
}
private void setDonePlaying() {
donePlaying = true;
synchronized (PlayerThread.this) {
PlayerThread.this.notifyAll();
}
}
private PlaybackState getPlaybackState() {
try {
return new PlaybackState(mediaPlayer.getDuration(), mediaPlayer.getCurrentPosition(), isPlaying());
} catch (IllegalStateException e) {
// This isn't that nice way to handle this.
// maybe there is a better way
return PlaybackState.UNPREPARED;
}
}
private void broadcastState() {
PlaybackState state = getPlaybackState();
Intent intent = new Intent(ACTION_PLAYBACK_STATE);
intent.putExtra(EXTRA_PLAYBACK_STATE, state);
sendBroadcast(intent);
}
@Override @Override
public void run() { public void run() {
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);//cpu lock mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);//cpu lock
@ -180,6 +225,7 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
filter.addAction(ACTION_PLAYPAUSE); filter.addAction(ACTION_PLAYPAUSE);
filter.addAction(ACTION_STOP); filter.addAction(ACTION_STOP);
filter.addAction(ACTION_REWIND); filter.addAction(ACTION_REWIND);
filter.addAction(ACTION_PLAYBACK_STATE);
registerReceiver(broadcastReceiver, filter); registerReceiver(broadcastReceiver, filter);
initNotificationBuilder(); initNotificationBuilder();
@ -188,18 +234,20 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
//currently decommissioned progressbar looping update code - works, but doesn't fit inside //currently decommissioned progressbar looping update code - works, but doesn't fit inside
//Notification.MediaStyle Notification layout. //Notification.MediaStyle Notification layout.
noteMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); noteMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
/*
//update every 2s or 4 times in the video, whichever is shorter //update every 2s or 4 times in the video, whichever is shorter
int sleepTime = Math.min(2000, (int)((double)vidLength/4)); int vidLength = mediaPlayer.getDuration();
while(mediaPlayer.isPlaying()) { int sleepTime = Math.min(2000, (int)(vidLength / 4));
noteBuilder.setProgress(vidLength, mediaPlayer.getCurrentPosition(), false); while(!isDonePlaying()) {
noteMgr.notify(noteID, noteBuilder.build()); broadcastState();
try { try {
Thread.sleep(sleepTime); synchronized (this) {
wait(sleepTime);
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
Log.d(TAG, "sleep failure"); Log.e(TAG, "sleep failure", e);
} }
}*/ }
} }
/**Handles button presses from the notification. */ /**Handles button presses from the notification. */
@ -208,26 +256,46 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
//Log.i(TAG, "received broadcast action:"+action); //Log.i(TAG, "received broadcast action:"+action);
if(action.equals(ACTION_PLAYPAUSE)) { switch (action) {
boolean isPlaying = mediaPlayer.isPlaying(); case ACTION_PLAYPAUSE: {
if(isPlaying) { boolean isPlaying = mediaPlayer.isPlaying();
mediaPlayer.pause(); if(isPlaying) {
} else { mediaPlayer.pause();
//reacquire CPU lock after auto-releasing it on pause } else {
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); //reacquire CPU lock after auto-releasing it on pause
mediaPlayer.start(); mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
mediaPlayer.start();
}
noteBuilder.setIsPlaying(isPlaying);
noteMgr.notify(noteID, noteBuilder.build());
break;
} }
noteBuilder.setIsPlaying(isPlaying); case ACTION_REWIND:
noteMgr.notify(noteID, noteBuilder.build()); mediaPlayer.seekTo(0);
} synchronized (PlayerThread.this) {
else if(action.equals(ACTION_REWIND)) { PlayerThread.this.notifyAll();
mediaPlayer.seekTo(0); }
// noteMgr.notify(noteID, note); // noteMgr.notify(noteID, note);
} break;
else if(action.equals(ACTION_STOP)) { case ACTION_STOP:
//this auto-releases CPU lock //this auto-releases CPU lock
mediaPlayer.stop(); mediaPlayer.stop();
afterPlayCleanup(); afterPlayCleanup();
break;
case ACTION_PLAYBACK_STATE: {
PlaybackState playbackState = intent.getParcelableExtra(EXTRA_PLAYBACK_STATE);
Log.d(TAG, "playback state recieved: " + playbackState);
Log.d(TAG, "is unprepared: " + playbackState.equals(PlaybackState.UNPREPARED));
Log.d(TAG, "playing: " + playbackState.getPlayedTime());
if(!playbackState.equals(PlaybackState.UNPREPARED)) {
noteBuilder.setProgress(playbackState.getDuration(), playbackState.getPlayedTime(), false);
noteBuilder.setIsPlaying(playbackState.isPlaying());
} else {
noteBuilder.setProgress(0, 0, true);
}
noteMgr.notify(noteID, noteBuilder.build());
break;
}
} }
} }
}; };
@ -258,7 +326,9 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
@Override @Override
public void onCompletion(MediaPlayer mp) { public void onCompletion(MediaPlayer mp) {
setDonePlaying();
afterPlayCleanup(); afterPlayCleanup();
} }
} }
@ -390,9 +460,9 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
RemoteViews views = getContentView(), bigViews = getBigContentView(); RemoteViews views = getContentView(), bigViews = getBigContentView();
int imageSrc; int imageSrc;
if(isPlaying) { if(isPlaying) {
imageSrc = R.drawable.ic_play_circle_filled_white_24dp;
} else {
imageSrc = R.drawable.ic_pause_white_24dp; imageSrc = R.drawable.ic_pause_white_24dp;
} else {
imageSrc = R.drawable.ic_play_circle_filled_white_24dp;
} }
views.setImageViewResource(R.id.notificationPlayPause, imageSrc); views.setImageViewResource(R.id.notificationPlayPause, imageSrc);
bigViews.setImageViewResource(R.id.notificationPlayPause, imageSrc); bigViews.setImageViewResource(R.id.notificationPlayPause, imageSrc);
@ -401,4 +471,111 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
} }
} }
/**
* Represents the state of the player.
*/
public static class PlaybackState implements Parcelable {
private static final int INDEX_IS_PLAYING = 0;
private static final int INDEX_IS_PREPARED= 1;
private static final int INDEX_HAS_ERROR = 2;
private final int duration;
private final int played;
private final boolean[] booleanValues = new boolean[3];
static final PlaybackState UNPREPARED = new PlaybackState(false, false, false);
static final PlaybackState FAILED = new PlaybackState(false, false, true);
PlaybackState(Parcel in) {
duration = in.readInt();
played = in.readInt();
in.readBooleanArray(booleanValues);
}
PlaybackState(int duration, int played, boolean isPlaying) {
this.played = played;
this.duration = duration;
this.booleanValues[INDEX_IS_PLAYING] = isPlaying;
this.booleanValues[INDEX_IS_PREPARED] = true;
this.booleanValues[INDEX_HAS_ERROR] = false;
}
private PlaybackState(boolean isPlaying, boolean isPrepared, boolean hasErrors) {
this.played = 0;
this.duration = 0;
this.booleanValues[INDEX_IS_PLAYING] = isPlaying;
this.booleanValues[INDEX_IS_PREPARED] = isPrepared;
this.booleanValues[INDEX_HAS_ERROR] = hasErrors;
}
int getDuration() {
return duration;
}
int getPlayedTime() {
return played;
}
boolean isPlaying() {
return booleanValues[INDEX_IS_PLAYING];
}
boolean isPrepared() {
return booleanValues[INDEX_IS_PREPARED];
}
boolean hasErrors() {
return booleanValues[INDEX_HAS_ERROR];
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(duration);
dest.writeInt(played);
dest.writeBooleanArray(booleanValues);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<PlaybackState> CREATOR = new Creator<PlaybackState>() {
@Override
public PlaybackState createFromParcel(Parcel in) {
return new PlaybackState(in);
}
@Override
public PlaybackState[] newArray(int size) {
return new PlaybackState[size];
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PlaybackState that = (PlaybackState) o;
if (duration != that.duration) return false;
if (played != that.played) return false;
return Arrays.equals(booleanValues, that.booleanValues);
}
@Override
public int hashCode() {
if(this == UNPREPARED) return 1;
if(this == FAILED) return 2;
int result = duration;
result = 31 * result + played;
result = 31 * result + Arrays.hashCode(booleanValues);
return result + 2;
}
}
} }