mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 23:03:00 +00:00 
			
		
		
		
	Merge branch 'feature-background-progress' of git://github.com/coffeemakr/NewPipe into coffeemakr-feature-background-progress
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -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(); | ||||||
|   | |||||||
| @@ -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) { | ||||||
|                 } catch (InterruptedException e) { |                         wait(sleepTime); | ||||||
|                     Log.d(TAG, "sleep failure"); |                     } | ||||||
|  |                 } catch (InterruptedException e) { | ||||||
|  |                     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: { | ||||||
|  |                         boolean isPlaying = mediaPlayer.isPlaying(); | ||||||
|  |                         if(isPlaying) { | ||||||
|                             mediaPlayer.pause(); |                             mediaPlayer.pause(); | ||||||
|                         note.contentView.setImageViewResource(R.id.notificationPlayPause, R.drawable.ic_play_circle_filled_white_24dp); |                         } else { | ||||||
|                         if(android.os.Build.VERSION.SDK_INT >=16){ |  | ||||||
|                             note.bigContentView.setImageViewResource(R.id.notificationPlayPause, R.drawable.ic_play_circle_filled_white_24dp); |  | ||||||
|                         } |  | ||||||
|                         noteMgr.notify(noteID, note); |  | ||||||
|                     } |  | ||||||
|                     else { |  | ||||||
|                             //reacquire CPU lock after auto-releasing it on pause |                             //reacquire CPU lock after auto-releasing it on pause | ||||||
|                             mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); |                             mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); | ||||||
|                             mediaPlayer.start(); |                             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); |                         synchronized (PlayerThread.this) { | ||||||
|  |                             PlayerThread.this.notifyAll(); | ||||||
|                         } |                         } | ||||||
|  |                         break; | ||||||
|                     } |                     } | ||||||
|                 else if(action.equals(ACTION_REWIND)) { |                     case ACTION_REWIND: | ||||||
|                         mediaPlayer.seekTo(0); |                         mediaPlayer.seekTo(0); | ||||||
| //                    noteMgr.notify(noteID, note); |                         synchronized (PlayerThread.this) { | ||||||
|  |                             PlayerThread.this.notifyAll(); | ||||||
|                         } |                         } | ||||||
|                 else if(action.equals(ACTION_STOP)) { |                         break; | ||||||
|  |                     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); | ||||||
|  |                         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; | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         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); |  | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * @param context | ||||||
|  |              * @inheritDoc | ||||||
|  |              */ | ||||||
|  |             public NoteBuilder(Context context, PendingIntent playPI, PendingIntent stopPI, | ||||||
|  |                                PendingIntent rewindPI, PendingIntent openDetailView) { | ||||||
|  |                 super(context); | ||||||
|  |                 setCustomContentView(createCustomContentView(playPI, stopPI, rewindPI, openDetailView)); | ||||||
|  |                 setCustomBigContentView(createCustomBigContentView(playPI, stopPI, rewindPI, openDetailView)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             private RemoteViews createCustomBigContentView(PendingIntent playPI, | ||||||
|  |                                                            PendingIntent stopPI, | ||||||
|  |                                                            PendingIntent rewindPI, | ||||||
|  |                                                            PendingIntent openDetailView) { | ||||||
|                 //possibly found the expandedView problem, |                 //possibly found the expandedView problem, | ||||||
|                 //but can't test it as I don't have a 5.0 device. -medavox |                 //but can't test it as I don't have a 5.0 device. -medavox | ||||||
|                 RemoteViews expandedView = |                 RemoteViews expandedView = | ||||||
|                         new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification_expanded); |                         new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification_expanded); | ||||||
|                 expandedView.setImageViewBitmap(R.id.notificationCover, videoThumbnail); |                 expandedView.setImageViewBitmap(R.id.notificationCover, videoThumbnail); | ||||||
|             expandedView.setTextViewText(R.id.notificationSongName, title); |  | ||||||
|                 expandedView.setTextViewText(R.id.notificationArtist, channelName); |  | ||||||
|                 expandedView.setOnClickPendingIntent(R.id.notificationStop, stopPI); |                 expandedView.setOnClickPendingIntent(R.id.notificationStop, stopPI); | ||||||
|                 expandedView.setOnClickPendingIntent(R.id.notificationPlayPause, playPI); |                 expandedView.setOnClickPendingIntent(R.id.notificationPlayPause, playPI); | ||||||
|                 expandedView.setOnClickPendingIntent(R.id.notificationRewind, rewindPI); |                 expandedView.setOnClickPendingIntent(R.id.notificationRewind, rewindPI); | ||||||
|                 expandedView.setOnClickPendingIntent(R.id.notificationContent, openDetailView); |                 expandedView.setOnClickPendingIntent(R.id.notificationContent, openDetailView); | ||||||
|  |                 return expandedView; | ||||||
|  |  | ||||||
|             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 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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Christian Schabesberger
					Christian Schabesberger