Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notification Improvements #3178

Merged
merged 29 commits into from
Sep 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4abf6b2
Notification Improvements
cool-student Mar 2, 2020
7d499ff
Use vector drawables for close and replay
wb9688 May 14, 2020
caf7c55
Log only in debug build
wb9688 May 15, 2020
6de03f2
Fix crash when playing stream in background with shuffle in notification
wb9688 Jul 31, 2020
963ee4d
Merge branch 'dev' into pr3178
Stypox Aug 2, 2020
1a8ff81
Use AppCompatImageButton to not crash on 4.4
wb9688 Aug 3, 2020
adef9a8
Rename notification functions: they are not background player only
Stypox Aug 15, 2020
e08480f
Completely remove old player notification
Stypox Aug 15, 2020
c79997e
Show hourglass icon when buffering
Stypox Aug 15, 2020
7766fd1
Extract hardcoded strings into strings.xml and improve them
Stypox Aug 15, 2020
8b3a093
Various notification code improvements
Stypox Sep 3, 2020
97ff9e9
Merge branch 'dev' into pr3178
Stypox Sep 3, 2020
628575d
Clean up MediaSessionManager
Stypox Sep 3, 2020
408e819
Extract duplicate setActivityTitle code to own function
Stypox Sep 7, 2020
71b32fe
Add notification costumization settings menu
Stypox Sep 8, 2020
9cf0bc6
Make notification creation and cancelling more consistent
Stypox Sep 8, 2020
bc8954f
Fix notification content intent not being updated when needed
Stypox Sep 8, 2020
bd34c7e
Make player foreground playback-specific in manifest
Stypox Sep 8, 2020
a13e6b6
Merge branch 'dev' into pr3178
Stypox Sep 8, 2020
1605e50
Update notification when play queue is edited
Stypox Sep 9, 2020
52e89c1
Prevent seeking out of video duration in player
Stypox Sep 9, 2020
5846fba
Change "image" to "thumbnail"
Stypox Sep 9, 2020
bccfe50
Fix seekbar invisible or not updating
Stypox Sep 10, 2020
2017e6a
Refactor MediaSessionManager
Stypox Sep 10, 2020
59e7eba
Random adjustements to notification
Stypox Sep 16, 2020
7317737
Fix invisible queue close button
Stypox Sep 19, 2020
11e048a
Remove hardcoded and duplicate strings, use exoplayer ones
Stypox Sep 22, 2020
814efbf
Remove ACTION_BUFFERING, update buffering only if needed
Stypox Sep 22, 2020
b4e073c
Show replay icon in notification when player state is completed
Stypox Sep 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@
</receiver>

<service
android:name=".player.MainPlayer"
android:exported="false">
android:name=".player.MainPlayer"
android:exported="false"
android:foregroundServiceType="mediaPlayback">
opusforlife2 marked this conversation as resolved.
Show resolved Hide resolved
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/org/schabi/newpipe/RouterActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ private void showDialog(final List<AdapterChoiceItem> choices) {

final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
final LinearLayout rootLayout = (LinearLayout) inflater.inflate(
R.layout.preferred_player_dialog_view, null, false);
R.layout.single_choice_dialog_view, null, false);
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);

final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
Expand Down
13 changes: 12 additions & 1 deletion app/src/main/java/org/schabi/newpipe/player/BasePlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ public abstract class BasePlayer implements
@NonNull
protected final HistoryRecordManager recordManager;
@NonNull
protected final SharedPreferences sharedPreferences;
@NonNull
protected final CustomTrackSelector trackSelector;
@NonNull
protected final PlayerDataSource dataSource;
Expand Down Expand Up @@ -211,6 +213,7 @@ public void onReceive(final Context ctx, final Intent intent) {
setupBroadcastReceiver(intentFilter);

this.recordManager = new HistoryRecordManager(context);
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);

this.progressUpdateReactor = new SerialDisposable();
this.databaseUpdateReactor = new CompositeDisposable();
Expand Down Expand Up @@ -1239,7 +1242,15 @@ public void seekTo(final long positionMillis) {
Log.d(TAG, "seekBy() called with: position = [" + positionMillis + "]");
}
if (simpleExoPlayer != null) {
simpleExoPlayer.seekTo(positionMillis);
// prevent invalid positions when fast-forwarding/-rewinding
long normalizedPositionMillis = positionMillis;
if (normalizedPositionMillis < 0) {
normalizedPositionMillis = 0;
} else if (normalizedPositionMillis > simpleExoPlayer.getDuration()) {
normalizedPositionMillis = simpleExoPlayer.getDuration();
}

simpleExoPlayer.seekTo(normalizedPositionMillis);
Stypox marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
277 changes: 25 additions & 252 deletions app/src/main/java/org/schabi/newpipe/player/MainPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,18 @@

package org.schabi.newpipe.player;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import androidx.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.view.ViewGroup;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;

import com.google.android.exoplayer2.Player;
import android.view.ViewGroup;
import android.view.WindowManager;

import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.BitmapUtils;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper;

import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
Expand All @@ -64,7 +47,6 @@ public final class MainPlayer extends Service {

private VideoPlayerImpl playerImpl;
private WindowManager windowManager;
private SharedPreferences sharedPreferences;

private final IBinder mBinder = new MainPlayer.LocalBinder();

Expand All @@ -78,30 +60,26 @@ public enum PlayerType {
// Notification
//////////////////////////////////////////////////////////////////////////*/

static final int NOTIFICATION_ID = 123789;
private NotificationManager notificationManager;
private NotificationCompat.Builder notBuilder;
private RemoteViews notRemoteView;
private RemoteViews bigNotRemoteView;

static final String ACTION_CLOSE =
"org.schabi.newpipe.player.MainPlayer.CLOSE";
static final String ACTION_PLAY_PAUSE =
"org.schabi.newpipe.player.MainPlayer.PLAY_PAUSE";
static final String ACTION_OPEN_CONTROLS =
"org.schabi.newpipe.player.MainPlayer.OPEN_CONTROLS";
static final String ACTION_REPEAT =
"org.schabi.newpipe.player.MainPlayer.REPEAT";
static final String ACTION_PLAY_NEXT =
"org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT";
static final String ACTION_PLAY_PREVIOUS =
"org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS";
static final String ACTION_FAST_REWIND =
"org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND";
static final String ACTION_FAST_FORWARD =
"org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD";

private static final String SET_IMAGE_RESOURCE_METHOD = "setImageResource";
static final String ACTION_CLOSE
= "org.schabi.newpipe.player.MainPlayer.CLOSE";
static final String ACTION_PLAY_PAUSE
= "org.schabi.newpipe.player.MainPlayer.PLAY_PAUSE";
static final String ACTION_OPEN_CONTROLS
= "org.schabi.newpipe.player.MainPlayer.OPEN_CONTROLS";
static final String ACTION_REPEAT
= "org.schabi.newpipe.player.MainPlayer.REPEAT";
static final String ACTION_PLAY_NEXT
= "org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT";
static final String ACTION_PLAY_PREVIOUS
= "org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS";
static final String ACTION_FAST_REWIND
= "org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND";
static final String ACTION_FAST_FORWARD
= "org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD";
static final String ACTION_SHUFFLE
= "org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE";
public static final String ACTION_RECREATE_NOTIFICATION
= "org.schabi.newpipe.player.MainPlayer.ACTION_RECREATE_NOTIFICATION";

/*//////////////////////////////////////////////////////////////////////////
// Service's LifeCycle
Expand All @@ -113,9 +91,7 @@ public void onCreate() {
Log.d(TAG, "onCreate() called");
}
assureCorrectAppLanguage(this);
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());

ThemeHelper.setTheme(this);
createView();
Expand Down Expand Up @@ -143,7 +119,7 @@ public int onStartCommand(final Intent intent, final int flags, final int startI

if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
|| intent.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY) != null) {
showNotificationAndStartForeground();
NotificationUtil.getInstance().createNotificationAndStartForeground(playerImpl, this);
}

playerImpl.handleIntent(intent);
Expand Down Expand Up @@ -176,7 +152,7 @@ public void stop(final boolean autoplayEnabled) {
// So we should hide the notification at all.
// When autoplay enabled such notification flashing is annoying so skip this case
if (!autoplayEnabled) {
stopForeground(true);
NotificationUtil.getInstance().cancelNotificationAndStopForeground(this);
}
}
}
Expand Down Expand Up @@ -227,11 +203,8 @@ private void onClose() {
playerImpl.removePopupFromView();
playerImpl.destroy();
}
if (notificationManager != null) {
notificationManager.cancel(NOTIFICATION_ID);
}

stopForeground(true);
NotificationUtil.getInstance().cancelNotificationAndStopForeground(this);
stopSelf();
}

Expand Down Expand Up @@ -270,206 +243,6 @@ public void removeViewFromParent() {
}
}

private void showNotificationAndStartForeground() {
resetNotification();
if (getBigNotRemoteView() != null) {
getBigNotRemoteView().setProgressBar(R.id.notificationProgressBar, 100, 0, false);
}
if (getNotRemoteView() != null) {
getNotRemoteView().setProgressBar(R.id.notificationProgressBar, 100, 0, false);
}
startForeground(NOTIFICATION_ID, getNotBuilder().build());
}

/*//////////////////////////////////////////////////////////////////////////
// Notification
//////////////////////////////////////////////////////////////////////////*/

void resetNotification() {
notBuilder = createNotification();
playerImpl.timesNotificationUpdated = 0;
}

private NotificationCompat.Builder createNotification() {
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
R.layout.player_notification);
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
R.layout.player_notification_expanded);

setupNotification(notRemoteView);
setupNotification(bigNotRemoteView);

final NotificationCompat.Builder builder = new NotificationCompat
.Builder(this, getString(R.string.notification_channel_id))
.setOngoing(true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCustomContentView(notRemoteView)
.setCustomBigContentView(bigNotRemoteView);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setLockScreenThumbnail(builder);
}

builder.setPriority(NotificationCompat.PRIORITY_MAX);
return builder;
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void setLockScreenThumbnail(final NotificationCompat.Builder builder) {
final boolean isLockScreenThumbnailEnabled = sharedPreferences.getBoolean(
getString(R.string.enable_lock_screen_video_thumbnail_key), true);

if (isLockScreenThumbnailEnabled) {
playerImpl.mediaSessionManager.setLockScreenArt(
builder,
getCenteredThumbnailBitmap()
);
} else {
playerImpl.mediaSessionManager.clearLockScreenArt(builder);
}
}

@Nullable
private Bitmap getCenteredThumbnailBitmap() {
final int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
final int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;

return BitmapUtils.centerCrop(playerImpl.getThumbnail(), screenWidth, screenHeight);
}

private void setupNotification(final RemoteViews remoteViews) {
// Don't show anything until player is playing
if (playerImpl == null) {
return;
}

remoteViews.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
remoteViews.setTextViewText(R.id.notificationArtist, playerImpl.getUploaderName());
remoteViews.setImageViewBitmap(R.id.notificationCover, playerImpl.getThumbnail());

remoteViews.setOnClickPendingIntent(R.id.notificationPlayPause,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationStop,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
// Starts VideoDetailFragment or opens BackgroundPlayerActivity.
remoteViews.setOnClickPendingIntent(R.id.notificationContent,
PendingIntent.getActivity(this, NOTIFICATION_ID,
getIntentForNotification(), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationRepeat,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));


if (playerImpl.playQueue != null && playerImpl.playQueue.size() > 1) {
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_previous);
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_next);
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT));
} else {
remoteViews.setInt(R.id.notificationFRewind, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_rewind);
remoteViews.setInt(R.id.notificationFForward, SET_IMAGE_RESOURCE_METHOD,
R.drawable.exo_controls_fastforward);
remoteViews.setOnClickPendingIntent(R.id.notificationFRewind,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT));
remoteViews.setOnClickPendingIntent(R.id.notificationFForward,
PendingIntent.getBroadcast(this, NOTIFICATION_ID,
new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT));
}

setRepeatModeIcon(remoteViews, playerImpl.getRepeatMode());
}

/**
* Updates the notification, and the play/pause button in it.
* Used for changes on the remoteView
*
* @param drawableId if != -1, sets the drawable with that id on the play/pause button
*/
synchronized void updateNotification(final int drawableId) {
/*if (DEBUG) {
Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
}*/
if (notBuilder == null) {
return;
}
if (drawableId != -1) {
if (notRemoteView != null) {
notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
}
if (bigNotRemoteView != null) {
bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
}
}
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
playerImpl.timesNotificationUpdated++;
}

/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/

private void setRepeatModeIcon(final RemoteViews remoteViews, final int repeatMode) {
if (remoteViews == null) {
return;
}

switch (repeatMode) {
case Player.REPEAT_MODE_OFF:
remoteViews.setInt(R.id.notificationRepeat,
SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_off);
break;
case Player.REPEAT_MODE_ONE:
remoteViews.setInt(R.id.notificationRepeat,
SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_one);
break;
case Player.REPEAT_MODE_ALL:
remoteViews.setInt(R.id.notificationRepeat,
SET_IMAGE_RESOURCE_METHOD, R.drawable.exo_controls_repeat_all);
break;
}
}

private Intent getIntentForNotification() {
final Intent intent;
if (playerImpl.audioPlayerSelected() || playerImpl.popupPlayerSelected()) {
// Means we play in popup or audio only. Let's show BackgroundPlayerActivity
intent = NavigationHelper.getBackgroundPlayerActivityIntent(getApplicationContext());
} else {
// We are playing in fragment. Don't open another activity just show fragment. That's it
intent = NavigationHelper.getPlayerIntent(this, MainActivity.class, null, true);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
}
return intent;
}

/*//////////////////////////////////////////////////////////////////////////
// Getters
//////////////////////////////////////////////////////////////////////////*/

NotificationCompat.Builder getNotBuilder() {
return notBuilder;
}

RemoteViews getBigNotRemoteView() {
return bigNotRemoteView;
}

RemoteViews getNotRemoteView() {
return notRemoteView;
}


public class LocalBinder extends Binder {

Expand Down
Loading