Merge pull request #2690 from K1rakishou/(#1570)-lock-screen-video-thumbnail

Show video thumbnail on the lock screen
This commit is contained in:
Tobias Groza 2020-01-05 10:36:38 +01:00 committed by GitHub
commit 5bf1df9f14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 159 additions and 13 deletions

View file

@ -25,12 +25,17 @@ import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.RemoteViews; import android.widget.RemoteViews;
@ -48,6 +53,7 @@ import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
import org.schabi.newpipe.player.resolver.MediaSourceTag; import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.util.BitmapUtils;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
@ -75,6 +81,7 @@ public final class BackgroundPlayer extends Service {
private BasePlayerImpl basePlayerImpl; private BasePlayerImpl basePlayerImpl;
private LockManager lockManager; private LockManager lockManager;
private SharedPreferences sharedPreferences;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Service-Activity Binder // Service-Activity Binder
@ -107,6 +114,7 @@ public final class BackgroundPlayer extends Service {
if (DEBUG) Log.d(TAG, "onCreate() called"); if (DEBUG) Log.d(TAG, "onCreate() called");
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)); notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
lockManager = new LockManager(this); lockManager = new LockManager(this);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
ThemeHelper.setTheme(this); ThemeHelper.setTheme(this);
basePlayerImpl = new BasePlayerImpl(this); basePlayerImpl = new BasePlayerImpl(this);
@ -199,12 +207,45 @@ public final class BackgroundPlayer extends Service {
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCustomContentView(notRemoteView) .setCustomContentView(notRemoteView)
.setCustomBigContentView(bigNotRemoteView); .setCustomBigContentView(bigNotRemoteView);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setLockScreenThumbnail(builder);
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
builder.setPriority(NotificationCompat.PRIORITY_MAX); builder.setPriority(NotificationCompat.PRIORITY_MAX);
} }
return builder; return builder;
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void setLockScreenThumbnail(NotificationCompat.Builder builder) {
boolean isLockScreenThumbnailEnabled = sharedPreferences.getBoolean(
getString(R.string.enable_lock_screen_video_thumbnail_key),
true
);
if (isLockScreenThumbnailEnabled) {
basePlayerImpl.mediaSessionManager.setLockScreenArt(
builder,
getCenteredThumbnailBitmap()
);
} else {
basePlayerImpl.mediaSessionManager.clearLockScreenArt(builder);
}
}
@Nullable
private Bitmap getCenteredThumbnailBitmap() {
int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
return BitmapUtils.centerCrop(
basePlayerImpl.getThumbnail(),
screenWidth,
screenHeight);
}
private void setupNotification(RemoteViews remoteViews) { private void setupNotification(RemoteViews remoteViews) {
if (basePlayerImpl == null) return; if (basePlayerImpl == null) return;
@ -252,8 +293,10 @@ public final class BackgroundPlayer extends Service {
//if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]"); //if (DEBUG) Log.d(TAG, "updateNotification() called with: drawableId = [" + drawableId + "]");
if (notBuilder == null) return; if (notBuilder == null) return;
if (drawableId != -1) { if (drawableId != -1) {
if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId); if (notRemoteView != null)
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId); notRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
if (bigNotRemoteView != null)
bigNotRemoteView.setImageViewResource(R.id.notificationPlayPause, drawableId);
} }
notificationManager.notify(NOTIFICATION_ID, notBuilder.build()); notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
timesNotificationUpdated++; timesNotificationUpdated++;
@ -280,7 +323,8 @@ public final class BackgroundPlayer extends Service {
protected class BasePlayerImpl extends BasePlayer { protected class BasePlayerImpl extends BasePlayer {
@NonNull final private AudioPlaybackResolver resolver; @NonNull
final private AudioPlaybackResolver resolver;
private int cachedDuration; private int cachedDuration;
private String cachedDurationString; private String cachedDurationString;
@ -299,8 +343,10 @@ public final class BackgroundPlayer extends Service {
super.handleIntent(intent); super.handleIntent(intent);
resetNotification(); resetNotification();
if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false); if (bigNotRemoteView != null)
if (notRemoteView != null) notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false); bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
if (notRemoteView != null)
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 0, false);
startForeground(NOTIFICATION_ID, notBuilder.build()); startForeground(NOTIFICATION_ID, notBuilder.build());
} }
@ -335,6 +381,7 @@ public final class BackgroundPlayer extends Service {
updateNotificationThumbnail(); updateNotificationThumbnail();
updateNotification(-1); updateNotification(-1);
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// States Implementation // States Implementation
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -358,10 +405,13 @@ public final class BackgroundPlayer extends Service {
if (!shouldUpdateOnProgress) return; if (!shouldUpdateOnProgress) return;
if (timesNotificationUpdated > NOTIFICATION_UPDATES_BEFORE_RESET) { if (timesNotificationUpdated > NOTIFICATION_UPDATES_BEFORE_RESET) {
resetNotification(); resetNotification();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Oreo*/) updateNotificationThumbnail();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Oreo*/) {
updateNotificationThumbnail();
}
} }
if (bigNotRemoteView != null) { if (bigNotRemoteView != null) {
if(cachedDuration != duration) { if (cachedDuration != duration) {
cachedDuration = duration; cachedDuration = duration;
cachedDurationString = getTimeString(duration); cachedDurationString = getTimeString(duration);
} }
@ -389,8 +439,10 @@ public final class BackgroundPlayer extends Service {
@Override @Override
public void destroy() { public void destroy() {
super.destroy(); super.destroy();
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, null); if (notRemoteView != null)
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, null); notRemoteView.setImageViewBitmap(R.id.notificationCover, null);
if (bigNotRemoteView != null)
bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, null);
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////

View file

@ -2,12 +2,19 @@ package org.schabi.newpipe.player.helper;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap;
import android.media.MediaMetadata;
import android.os.Build;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.MediaSessionCompat;
import android.view.KeyEvent; import android.view.KeyEvent;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.media.session.MediaButtonReceiver; import androidx.media.session.MediaButtonReceiver;
import androidx.media.app.NotificationCompat.MediaStyle;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
@ -19,8 +26,10 @@ import org.schabi.newpipe.player.mediasession.PlayQueuePlaybackController;
public class MediaSessionManager { public class MediaSessionManager {
private static final String TAG = "MediaSessionManager"; private static final String TAG = "MediaSessionManager";
@NonNull private final MediaSessionCompat mediaSession; @NonNull
@NonNull private final MediaSessionConnector sessionConnector; private final MediaSessionCompat mediaSession;
@NonNull
private final MediaSessionConnector sessionConnector;
public MediaSessionManager(@NonNull final Context context, public MediaSessionManager(@NonNull final Context context,
@NonNull final Player player, @NonNull final Player player,
@ -40,13 +49,45 @@ public class MediaSessionManager {
return MediaButtonReceiver.handleIntent(mediaSession, intent); return MediaButtonReceiver.handleIntent(mediaSession, intent);
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void setLockScreenArt(NotificationCompat.Builder builder, @Nullable Bitmap thumbnailBitmap) {
if (thumbnailBitmap == null || !mediaSession.isActive()) {
return;
}
mediaSession.setMetadata(
new MediaMetadataCompat.Builder()
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, thumbnailBitmap)
.build()
);
MediaStyle mediaStyle = new MediaStyle()
.setMediaSession(mediaSession.getSessionToken());
builder.setStyle(mediaStyle);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void clearLockScreenArt(NotificationCompat.Builder builder) {
mediaSession.setMetadata(
new MediaMetadataCompat.Builder()
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, null)
.build()
);
MediaStyle mediaStyle = new MediaStyle()
.setMediaSession(mediaSession.getSessionToken());
builder.setStyle(mediaStyle);
}
/** /**
* Should be called on player destruction to prevent leakage. * Should be called on player destruction to prevent leakage.
* */ */
public void dispose() { public void dispose() {
this.sessionConnector.setPlayer(null); this.sessionConnector.setPlayer(null);
this.sessionConnector.setQueueNavigator(null); this.sessionConnector.setQueueNavigator(null);
this.mediaSession.setActive(false); this.mediaSession.setActive(false);
this.mediaSession.release(); this.mediaSession.release();
} }
} }

View file

@ -0,0 +1,43 @@
package org.schabi.newpipe.util;
import android.graphics.Bitmap;
import androidx.annotation.Nullable;
public class BitmapUtils {
@Nullable
public static Bitmap centerCrop(Bitmap inputBitmap, int newWidth, int newHeight) {
if (inputBitmap == null || inputBitmap.isRecycled()) {
return null;
}
float sourceWidth = inputBitmap.getWidth();
float sourceHeight = inputBitmap.getHeight();
float xScale = newWidth / sourceWidth;
float yScale = newHeight / sourceHeight;
float newXScale;
float newYScale;
if (yScale > xScale) {
newXScale = xScale / yScale;
newYScale = 1.0f;
} else {
newXScale = 1.0f;
newYScale = yScale / xScale;
}
float scaledWidth = newXScale * sourceWidth;
float scaledHeight = newYScale * sourceHeight;
int left = (int) ((sourceWidth - scaledWidth) / 2);
int top = (int) ((sourceHeight - scaledHeight) / 2);
int width = (int) scaledWidth;
int height = (int) scaledHeight;
return Bitmap.createBitmap(inputBitmap, left, top, width, height);
}
}

View file

@ -175,6 +175,7 @@
<string name="main_page_content_key" translatable="false">main_page_content</string> <string name="main_page_content_key" translatable="false">main_page_content</string>
<string name="enable_playback_resume_key" translatable="false">enable_playback_resume</string> <string name="enable_playback_resume_key" translatable="false">enable_playback_resume</string>
<string name="enable_playback_state_lists_key" translatable="false">enable_playback_state_lists</string> <string name="enable_playback_state_lists_key" translatable="false">enable_playback_state_lists</string>
<string name="enable_lock_screen_video_thumbnail_key" translatable="false">enable_lock_screen_video_thumbnail</string>
<string name="import_data" translatable="false">import_data</string> <string name="import_data" translatable="false">import_data</string>
<string name="export_data" translatable="false">export_data</string> <string name="export_data" translatable="false">export_data</string>

View file

@ -58,7 +58,9 @@
<string name="kore_not_found">Kore app not found. Install it?</string> <string name="kore_not_found">Kore app not found. Install it?</string>
<string name="kore_package" translatable="false">org.xbmc.kore</string> <string name="kore_package" translatable="false">org.xbmc.kore</string>
<string name="show_play_with_kodi_title">Show \"Play with Kodi\" option</string> <string name="show_play_with_kodi_title">Show \"Play with Kodi\" option</string>
<string name="enable_lock_screen_video_thumbnail_title">Enable lock screen video thumbnail</string>
<string name="show_play_with_kodi_summary">Display an option to play a video via Kodi media center</string> <string name="show_play_with_kodi_summary">Display an option to play a video via Kodi media center</string>
<string name="enable_lock_screen_video_thumbnail_summary">When using the background player a video thumbnail will be displayed on the lock screen</string>
<string name="play_audio">Audio</string> <string name="play_audio">Audio</string>
<string name="default_audio_format_title">Default audio format</string> <string name="default_audio_format_title">Default audio format</string>
<string name="default_video_format_title">Default video format</string> <string name="default_video_format_title">Default video format</string>

View file

@ -81,6 +81,13 @@
android:summary="@string/show_play_with_kodi_summary" android:summary="@string/show_play_with_kodi_summary"
android:title="@string/show_play_with_kodi_title"/> android:title="@string/show_play_with_kodi_title"/>
<SwitchPreference
app:iconSpaceReserved="false"
android:defaultValue="true"
android:key="@string/enable_lock_screen_video_thumbnail_key"
android:summary="@string/enable_lock_screen_video_thumbnail_summary"
android:title="@string/enable_lock_screen_video_thumbnail_title"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory