Merge branch 'feature-background-progress' of git://github.com/coffeemakr/NewPipe into coffeemakr-feature-background-progress

This commit is contained in:
Christian Schabesberger 2016-12-28 10:46:38 +01:00
commit a5589d0865
6 changed files with 341 additions and 101 deletions

1
.gitignore vendored
View file

@ -8,3 +8,4 @@
/.idea /.idea
/*.iml /*.iml
gradle.properties gradle.properties
*~

View file

@ -31,14 +31,14 @@ public class AudioStream {
this.bandwidth = bandwidth; this.sampling_rate = samplingRate; this.bandwidth = bandwidth; this.sampling_rate = samplingRate;
} }
// reveals wether two streams are the same, but have diferent urls // reveals whether two streams are the same, but have different urls
public boolean equalStats(AudioStream cmp) { public boolean equalStats(AudioStream cmp) {
return format == cmp.format return format == cmp.format
&& bandwidth == cmp.bandwidth && bandwidth == cmp.bandwidth
&& sampling_rate == cmp.sampling_rate; && sampling_rate == cmp.sampling_rate;
} }
// revelas wether two streams are equal // reveals whether two streams are equal
public boolean equals(AudioStream cmp) { public boolean equals(AudioStream cmp) {
return cmp != null && equalStats(cmp) return cmp != null && equalStats(cmp)
&& url == cmp.url; && url == cmp.url;

View file

@ -37,14 +37,14 @@ public abstract class StreamExtractor {
private UrlIdHandler urlIdHandler; private UrlIdHandler urlIdHandler;
private StreamPreviewInfoCollector previewInfoCollector; private StreamPreviewInfoCollector previewInfoCollector;
public class ExctractorInitException extends ExtractionException { public class ExtractorInitException extends ExtractionException {
public ExctractorInitException(String message) { public ExtractorInitException(String message) {
super(message); super(message);
} }
public ExctractorInitException(Throwable cause) { public ExtractorInitException(Throwable cause) {
super(cause); super(cause);
} }
public ExctractorInitException(String message, Throwable cause) { public ExtractorInitException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }
} }

View file

@ -86,8 +86,8 @@ public class StreamInfo extends AbstractStreamInfo {
private static StreamInfo extractImportantData( private static StreamInfo extractImportantData(
StreamInfo streamInfo, StreamExtractor extractor) StreamInfo streamInfo, StreamExtractor extractor)
throws ExtractionException, IOException { throws ExtractionException, IOException {
/* ---- importand data, withoug the video can't be displayed goes here: ---- */ /* ---- important data, withoug the video can't be displayed goes here: ---- */
// if one of these is not available an exception is ment to be thrown directly into the frontend. // if one of these is not available an exception is meant to be thrown directly into the frontend.
UrlIdHandler uiconv = extractor.getUrlIdHandler(); UrlIdHandler uiconv = extractor.getUrlIdHandler();
@ -134,7 +134,7 @@ public class StreamInfo extends AbstractStreamInfo {
streamInfo.audio_streams = new Vector<>(); streamInfo.audio_streams = new Vector<>();
} }
//todo: make this quick and dirty solution a real fallback //todo: make this quick and dirty solution a real fallback
// same as the quick and dirty aboth // same as the quick and dirty above
try { try {
streamInfo.audio_streams.addAll( streamInfo.audio_streams.addAll(
DashMpdParser.getAudioStreams(streamInfo.dashMpdUrl)); DashMpdParser.getAudioStreams(streamInfo.dashMpdUrl));
@ -173,9 +173,9 @@ public class StreamInfo extends AbstractStreamInfo {
private static StreamInfo extractOptionalData( private static StreamInfo extractOptionalData(
StreamInfo streamInfo, StreamExtractor extractor) { StreamInfo streamInfo, StreamExtractor extractor) {
/* ---- optional data goes here: ---- */ /* ---- optional data goes here: ---- */
// If one of these failes, the frontend neets to handle that they are not available. // If one of these fails, the frontend needs to handle that they are not available.
// Exceptions are therfore not thrown into the frontend, but stored into the error List, // Exceptions are therefore not thrown into the frontend, but stored into the error List,
// so the frontend can afterwads check where errors happend. // so the frontend can afterwards check where errors happened.
try { try {
streamInfo.thumbnail_url = extractor.getThumbnailUrl(); streamInfo.thumbnail_url = extractor.getThumbnailUrl();
@ -290,4 +290,4 @@ public class StreamInfo extends AbstractStreamInfo {
public int start_position = 0; public int start_position = 0;
public List<Throwable> errors = new Vector<>(); public List<Throwable> errors = new Vector<>();
} }

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;
@ -123,8 +130,8 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
private NotificationManager noteMgr; private NotificationManager noteMgr;
private WifiManager.WifiLock wifiLock; private WifiManager.WifiLock wifiLock;
private Bitmap videoThumbnail; private Bitmap videoThumbnail;
private NotificationCompat.Builder noteBuilder; private NoteBuilder noteBuilder;
private Notification note; 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;
@ -134,6 +141,45 @@ 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 synchronized 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
Log.w(TAG, this + ": Got illegal state exception while creating playback state", e);
return PlaybackState.UNPREPARED;
}
}
private void broadcastState() {
PlaybackState state = getPlaybackState();
if(state == null) return;
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
@ -181,27 +227,29 @@ 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);
note = buildNotification(); initNotificationBuilder();
startForeground(noteID, noteBuilder.build());
startForeground(noteID, note);
//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. */
@ -210,39 +258,50 @@ 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) {
if(mediaPlayer.isPlaying()) { case ACTION_PLAYPAUSE: {
mediaPlayer.pause(); boolean isPlaying = mediaPlayer.isPlaying();
note.contentView.setImageViewResource(R.id.notificationPlayPause, R.drawable.ic_play_circle_filled_white_24dp); if(isPlaying) {
if(android.os.Build.VERSION.SDK_INT >=16){ mediaPlayer.pause();
note.bigContentView.setImageViewResource(R.id.notificationPlayPause, R.drawable.ic_play_circle_filled_white_24dp); } else {
//reacquire CPU lock after auto-releasing it on pause
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
mediaPlayer.start();
} }
noteMgr.notify(noteID, note); synchronized (PlayerThread.this) {
} PlayerThread.this.notifyAll();
else {
//reacquire CPU lock after auto-releasing it on pause
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
mediaPlayer.start();
note.contentView.setImageViewResource(R.id.notificationPlayPause, R.drawable.ic_pause_white_24dp);
if(android.os.Build.VERSION.SDK_INT >=16){
note.bigContentView.setImageViewResource(R.id.notificationPlayPause, R.drawable.ic_pause_white_24dp);
} }
noteMgr.notify(noteID, note); break;
}
case ACTION_REWIND:
mediaPlayer.seekTo(0);
synchronized (PlayerThread.this) {
PlayerThread.this.notifyAll();
}
break;
case ACTION_STOP:
//this auto-releases CPU lock
mediaPlayer.stop();
afterPlayCleanup();
break;
case ACTION_PLAYBACK_STATE: {
PlaybackState playbackState = intent.getParcelableExtra(EXTRA_PLAYBACK_STATE);
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;
} }
}
else if(action.equals(ACTION_REWIND)) {
mediaPlayer.seekTo(0);
// noteMgr.notify(noteID, note);
}
else if(action.equals(ACTION_STOP)) {
//this auto-releases CPU lock
mediaPlayer.stop();
afterPlayCleanup();
} }
} }
}; };
private void afterPlayCleanup() { private void afterPlayCleanup() {
// Notify thread to stop
setDonePlaying();
//remove progress bar //remove progress bar
//noteBuilder.setProgress(0, 0, false); //noteBuilder.setProgress(0, 0, false);
@ -256,7 +315,12 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
wifiLock.release(); wifiLock.release();
//remove foreground status of service; make BackgroundPlayer killable //remove foreground status of service; make BackgroundPlayer killable
stopForeground(true); stopForeground(true);
try {
// Wait for thread to stop
PlayerThread.this.join();
} catch (InterruptedException e) {
Log.e(TAG, "unable to join player thread", e);
}
stopSelf(); stopSelf();
} }
@ -272,10 +336,14 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
} }
} }
private Notification buildNotification() { private void initNotificationBuilder() {
Notification note; Notification note;
Resources res = getApplicationContext().getResources(); Resources res = getApplicationContext().getResources();
noteBuilder = new NotificationCompat.Builder(owner);
/*
NotificationCompat.Action pauseButton = new NotificationCompat.Action.Builder
(R.drawable.ic_pause_white_24dp, "Pause", playPI).build();
*/
PendingIntent playPI = PendingIntent.getBroadcast(owner, noteID, PendingIntent playPI = PendingIntent.getBroadcast(owner, noteID,
new Intent(ACTION_PLAYPAUSE), PendingIntent.FLAG_UPDATE_CURRENT); new Intent(ACTION_PLAYPAUSE), PendingIntent.FLAG_UPDATE_CURRENT);
@ -283,10 +351,6 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
new Intent(ACTION_STOP), PendingIntent.FLAG_UPDATE_CURRENT); new Intent(ACTION_STOP), PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent rewindPI = PendingIntent.getBroadcast(owner, noteID, PendingIntent rewindPI = PendingIntent.getBroadcast(owner, noteID,
new Intent(ACTION_REWIND), PendingIntent.FLAG_UPDATE_CURRENT); new Intent(ACTION_REWIND), PendingIntent.FLAG_UPDATE_CURRENT);
/*
NotificationCompat.Action pauseButton = new NotificationCompat.Action.Builder
(R.drawable.ic_pause_white_24dp, "Pause", playPI).build();
*/
//build intent to return to video, on tapping notification //build intent to return to video, on tapping notification
Intent openDetailViewIntent = new Intent(getApplicationContext(), Intent openDetailViewIntent = new Intent(getApplicationContext(),
@ -296,58 +360,226 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
openDetailViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); openDetailViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent openDetailView = PendingIntent.getActivity(owner, noteID, PendingIntent openDetailView = PendingIntent.getActivity(owner, noteID,
openDetailViewIntent, PendingIntent.FLAG_UPDATE_CURRENT); openDetailViewIntent, PendingIntent.FLAG_UPDATE_CURRENT);
noteBuilder = new NoteBuilder(owner, playPI, stopPI, rewindPI, openDetailView);
noteBuilder noteBuilder
.setTitle(title)
.setArtist(channelName)
.setOngoing(true) .setOngoing(true)
.setDeleteIntent(stopPI) .setDeleteIntent(stopPI)
//doesn't fit with Notification.MediaStyle //doesn't fit with Notification.MediaStyle
//.setProgress(vidLength, 0, false) //.setProgress(vidLength, 0, false)
.setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp) .setSmallIcon(R.drawable.ic_play_circle_filled_white_24dp)
.setTicker(
String.format(res.getString(
R.string.background_player_time_text), title))
.setContentIntent(PendingIntent.getActivity(getApplicationContext(), .setContentIntent(PendingIntent.getActivity(getApplicationContext(),
noteID, openDetailViewIntent, noteID, openDetailViewIntent,
PendingIntent.FLAG_UPDATE_CURRENT)) PendingIntent.FLAG_UPDATE_CURRENT))
.setContentIntent(openDetailView); .setContentIntent(openDetailView)
.setCategory(Notification.CATEGORY_TRANSPORT)
//Make notification appear on lockscreen
.setVisibility(Notification.VISIBILITY_PUBLIC);
}
RemoteViews view = /**
new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification); * Notification builder which works like the real builder but uses a custom view.
view.setImageViewBitmap(R.id.notificationCover, videoThumbnail); */
view.setTextViewText(R.id.notificationSongName, title); class NoteBuilder extends NotificationCompat.Builder {
view.setTextViewText(R.id.notificationArtist, channelName);
view.setOnClickPendingIntent(R.id.notificationStop, stopPI);
view.setOnClickPendingIntent(R.id.notificationPlayPause, playPI);
view.setOnClickPendingIntent(R.id.notificationRewind, rewindPI);
view.setOnClickPendingIntent(R.id.notificationContent, openDetailView);
//possibly found the expandedView problem, /**
//but can't test it as I don't have a 5.0 device. -medavox * @param context
RemoteViews expandedView = * @inheritDoc
new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification_expanded); */
expandedView.setImageViewBitmap(R.id.notificationCover, videoThumbnail); public NoteBuilder(Context context, PendingIntent playPI, PendingIntent stopPI,
expandedView.setTextViewText(R.id.notificationSongName, title); PendingIntent rewindPI, PendingIntent openDetailView) {
expandedView.setTextViewText(R.id.notificationArtist, channelName); super(context);
expandedView.setOnClickPendingIntent(R.id.notificationStop, stopPI); setCustomContentView(createCustomContentView(playPI, stopPI, rewindPI, openDetailView));
expandedView.setOnClickPendingIntent(R.id.notificationPlayPause, playPI); setCustomBigContentView(createCustomBigContentView(playPI, stopPI, rewindPI, openDetailView));
expandedView.setOnClickPendingIntent(R.id.notificationRewind, rewindPI);
expandedView.setOnClickPendingIntent(R.id.notificationContent, openDetailView);
noteBuilder.setCategory(Notification.CATEGORY_TRANSPORT);
//Make notification appear on lockscreen
noteBuilder.setVisibility(Notification.VISIBILITY_PUBLIC);
note = noteBuilder.build();
note.contentView = view;
if (android.os.Build.VERSION.SDK_INT > 16) {
note.bigContentView = expandedView;
} }
return note; private RemoteViews createCustomBigContentView(PendingIntent playPI,
PendingIntent stopPI,
PendingIntent rewindPI,
PendingIntent openDetailView) {
//possibly found the expandedView problem,
//but can't test it as I don't have a 5.0 device. -medavox
RemoteViews expandedView =
new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification_expanded);
expandedView.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
expandedView.setOnClickPendingIntent(R.id.notificationStop, stopPI);
expandedView.setOnClickPendingIntent(R.id.notificationPlayPause, playPI);
expandedView.setOnClickPendingIntent(R.id.notificationRewind, rewindPI);
expandedView.setOnClickPendingIntent(R.id.notificationContent, openDetailView);
return expandedView;
}
private RemoteViews createCustomContentView(PendingIntent playPI, PendingIntent stopPI,
PendingIntent rewindPI,
PendingIntent openDetailView) {
RemoteViews view = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
view.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
view.setOnClickPendingIntent(R.id.notificationStop, stopPI);
view.setOnClickPendingIntent(R.id.notificationPlayPause, playPI);
view.setOnClickPendingIntent(R.id.notificationRewind, rewindPI);
view.setOnClickPendingIntent(R.id.notificationContent, openDetailView);
return view;
}
/**
* Set the title of the stream
* @param title the title of the stream
* @return this builder for chaining
*/
NoteBuilder setTitle(String title) {
setContentTitle(title);
getContentView().setTextViewText(R.id.notificationSongName, title);
getBigContentView().setTextViewText(R.id.notificationSongName, title);
setTicker(String.format(getBaseContext().getString(
R.string.background_player_time_text), title));
return this;
}
/**
* Set the artist of the stream
* @param artist the artist of the stream
* @return this builder for chaining
*/
NoteBuilder setArtist(String artist) {
setSubText(artist);
getContentView().setTextViewText(R.id.notificationArtist, artist);
getBigContentView().setTextViewText(R.id.notificationArtist, artist);
return this;
}
@Override
public android.support.v4.app.NotificationCompat.Builder setProgress(int max, int progress, boolean indeterminate) {
super.setProgress(max, progress, indeterminate);
getBigContentView().setProgressBar(R.id.playbackProgress, max, progress, indeterminate);
return this;
}
/**
* Set the isPlaying state
* @param isPlaying the is playing state
*/
public void setIsPlaying(boolean isPlaying) {
RemoteViews views = getContentView(), bigViews = getBigContentView();
int imageSrc;
if(isPlaying) {
imageSrc = R.drawable.ic_pause_white_24dp;
} else {
imageSrc = R.drawable.ic_play_circle_filled_white_24dp;
}
views.setImageViewResource(R.id.notificationPlayPause, imageSrc);
bigViews.setImageViewResource(R.id.notificationPlayPause, imageSrc);
}
}
}
/**
* 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;
} }
} }
} }

View file

@ -20,7 +20,7 @@
android:layout_above="@+id/notificationButtons" android:layout_above="@+id/notificationButtons"
android:layout_toRightOf="@+id/notificationCover" android:layout_toRightOf="@+id/notificationCover"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="vertical" > android:orientation="vertical">
<TextView <TextView
android:id="@+id/notificationSongName" android:id="@+id/notificationSongName"
@ -40,6 +40,13 @@
android:ellipsize="marquee" android:ellipsize="marquee"
android:singleLine="true" android:singleLine="true"
android:text="artist" /> android:text="artist" />
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/playbackProgress"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_marginRight="8dp" />
</LinearLayout> </LinearLayout>
<ImageButton <ImageButton