Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
0b391a9ef3
|
@ -8,3 +8,4 @@
|
||||||
/.idea
|
/.idea
|
||||||
/*.iml
|
/*.iml
|
||||||
gradle.properties
|
gradle.properties
|
||||||
|
*~
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue