diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt b/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt index c454f7eec..0d2caf126 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/item/StreamItem.kt @@ -17,6 +17,7 @@ import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM import org.schabi.newpipe.extractor.stream.StreamType.VIDEO_STREAM import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.PicassoHelper +import org.schabi.newpipe.util.StreamTypeUtil import java.util.concurrent.TimeUnit data class StreamItem( @@ -58,8 +59,6 @@ data class StreamItem( viewBinding.itemVideoTitleView.text = stream.title viewBinding.itemUploaderView.text = stream.uploader - val isLiveStream = stream.streamType == LIVE_STREAM || stream.streamType == AUDIO_LIVE_STREAM - if (stream.duration > 0) { viewBinding.itemDurationView.text = Localization.getDurationString(stream.duration) viewBinding.itemDurationView.setBackgroundColor( @@ -77,7 +76,7 @@ data class StreamItem( } else { viewBinding.itemProgressView.visibility = View.GONE } - } else if (isLiveStream) { + } else if (StreamTypeUtil.isLiveStream(stream.streamType)) { viewBinding.itemDurationView.setText(R.string.duration_live) viewBinding.itemDurationView.setBackgroundColor( ContextCompat.getColor( diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 5ad855e78..05e2bed78 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -1,5 +1,53 @@ package org.schabi.newpipe.player; +import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AD_INSERTION; +import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL; +import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION; +import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK; +import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT; +import static com.google.android.exoplayer2.Player.DiscontinuityReason; +import static com.google.android.exoplayer2.Player.EventListener; +import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; +import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; +import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; +import static com.google.android.exoplayer2.Player.RepeatMode; +import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; +import static org.schabi.newpipe.extractor.ServiceList.YouTube; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; +import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE; +import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.MainPlayer.ACTION_RECREATE_NOTIFICATION; +import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT; +import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; +import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; +import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; +import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; +import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; +import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; +import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; +import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; +import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; +import static org.schabi.newpipe.player.helper.PlayerHelper.nextResizeModeAndSaveToPrefs; +import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; +import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent; +import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs; +import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; +import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs; +import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex; +import static org.schabi.newpipe.util.ListHelper.getResolutionIndex; +import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; +import static org.schabi.newpipe.util.Localization.containsCaseInsensitive; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -94,7 +142,6 @@ import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamSegment; -import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; @@ -127,12 +174,13 @@ import org.schabi.newpipe.player.resolver.MediaSourceTag; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper; import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder; +import org.schabi.newpipe.util.StreamTypeUtil; import org.schabi.newpipe.util.DeviceUtils; -import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.SerializedCache; +import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.views.ExpandableSurfaceView; @@ -140,6 +188,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable; @@ -147,54 +196,6 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.disposables.SerialDisposable; -import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AD_INSERTION; -import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL; -import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION; -import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK; -import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT; -import static com.google.android.exoplayer2.Player.DiscontinuityReason; -import static com.google.android.exoplayer2.Player.EventListener; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static com.google.android.exoplayer2.Player.RepeatMode; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; -import static org.schabi.newpipe.extractor.ServiceList.YouTube; -import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; -import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.MainPlayer.ACTION_RECREATE_NOTIFICATION; -import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; -import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; -import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; -import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; -import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; -import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; -import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; -import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; -import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; -import static org.schabi.newpipe.player.helper.PlayerHelper.nextResizeModeAndSaveToPrefs; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; -import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs; -import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex; -import static org.schabi.newpipe.util.ListHelper.getResolutionIndex; -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; -import static org.schabi.newpipe.util.Localization.containsCaseInsensitive; - public final class Player implements EventListener, PlaybackListener, @@ -1622,12 +1623,6 @@ public final class Player implements if (isQueueVisible) { updateQueueTime(currentProgress); } - - final boolean showThumbnail = prefs.getBoolean( - context.getString(R.string.show_thumbnail_key), true); - // setMetadata only updates the metadata when any of the metadata keys are null - mediaSessionManager.setMetadata(getVideoTitle(), getUploaderName(), - showThumbnail ? getThumbnail() : null, duration); } private void startProgressLoop() { @@ -1652,10 +1647,10 @@ public final class Player implements // TODO: revert #6307 when introducing proper HLS support final int duration; if (currentItem != null - && currentItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM - && currentItem.getStreamType() != StreamType.LIVE_STREAM) { + && !StreamTypeUtil.isLiveStream(currentItem.getStreamType()) + ) { // convert seconds to milliseconds - duration = (int) (currentItem.getDuration() * 1000); + duration = (int) (currentItem.getDuration() * 1000); } else { duration = (int) simpleExoPlayer.getDuration(); } @@ -2946,6 +2941,18 @@ public final class Player implements tag.getMetadata().getPreviewFrames()); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); + + final boolean showThumbnail = prefs.getBoolean( + context.getString(R.string.show_thumbnail_key), true); + mediaSessionManager.setMetadata( + getVideoTitle(), + getUploaderName(), + showThumbnail ? Optional.ofNullable(getThumbnail()) : Optional.empty(), + StreamTypeUtil.isLiveStream(tag.getMetadata().getStreamType()) + ? -1 + : tag.getMetadata().getDuration() + ); + notifyMetadataUpdateToListeners(); if (areSegmentsVisible) { @@ -3023,9 +3030,11 @@ public final class Player implements @Nullable public Bitmap getThumbnail() { - return currentThumbnail == null - ? BitmapFactory.decodeResource(context.getResources(), R.drawable.dummy_thumbnail) - : currentThumbnail; + if (currentThumbnail == null) { + currentThumbnail = BitmapFactory.decodeResource( + context.getResources(), R.drawable.dummy_thumbnail); + } + return currentThumbnail; } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java index c7f1f9c8c..ef0d84029 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java @@ -21,6 +21,8 @@ import org.schabi.newpipe.player.mediasession.MediaSessionCallback; import org.schabi.newpipe.player.mediasession.PlayQueueNavigator; import org.schabi.newpipe.player.mediasession.PlayQueuePlaybackController; +import java.util.Optional; + public class MediaSessionManager { private static final String TAG = MediaSessionManager.class.getSimpleName(); public static final boolean DEBUG = MainActivity.DEBUG; @@ -30,6 +32,9 @@ public class MediaSessionManager { @NonNull private final MediaSessionConnector sessionConnector; + private int lastTitleHashCode; + private int lastArtistHashCode; + private long lastDuration; private int lastAlbumArtHashCode; public MediaSessionManager(@NonNull final Context context, @@ -65,47 +70,127 @@ public class MediaSessionManager { return mediaSession.getSessionToken(); } - public void setMetadata(final String title, - final String artist, - final Bitmap albumArt, - final long duration) { - if (albumArt == null || !mediaSession.isActive()) { + /** + * sets the Metadata - if required. + * + * @param title {@link MediaMetadataCompat#METADATA_KEY_TITLE} + * @param artist {@link MediaMetadataCompat#METADATA_KEY_ARTIST} + * @param optAlbumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART} + * @param duration {@link MediaMetadataCompat#METADATA_KEY_DURATION} + * - should be a negative value for unknown durations, e.g. for livestreams + */ + public void setMetadata(@NonNull final String title, + @NonNull final String artist, + @NonNull final Optional optAlbumArt, + final long duration + ) { + if (DEBUG) { + Log.d(TAG, "setMetadata called:" + + " t: " + title + + " a: " + artist + + " thumb: " + ( + optAlbumArt.isPresent() + ? optAlbumArt.get().hashCode() + : "") + + " d: " + duration); + } + + if (!mediaSession.isActive()) { + if (DEBUG) { + Log.d(TAG, "setMetadata: mediaSession not active - exiting"); + } + return; + } + + if (!checkIfMetadataShouldBeSet(title, artist, optAlbumArt, duration)) { + if (DEBUG) { + Log.d(TAG, "setMetadata: No update required - exiting"); + } return; } if (DEBUG) { - if (getMetadataAlbumArt() == null) { - Log.d(TAG, "N_getMetadataAlbumArt: thumb == null"); - } - if (getMetadataTitle() == null) { - Log.d(TAG, "N_getMetadataTitle: title == null"); - } - if (getMetadataArtist() == null) { - Log.d(TAG, "N_getMetadataArtist: artist == null"); - } - if (getMetadataDuration() <= 1) { - Log.d(TAG, "N_getMetadataDuration: duration <= 1; " + getMetadataDuration()); - } + Log.d(TAG, "setMetadata: N_Metadata update:" + + " t: " + title + + " a: " + artist + + " thumb: " + ( + optAlbumArt.isPresent() + ? optAlbumArt.get().hashCode() + : "") + + " d: " + duration); } - if (getMetadataAlbumArt() == null || getMetadataTitle() == null - || getMetadataArtist() == null || getMetadataDuration() <= 1 - || albumArt.hashCode() != lastAlbumArtHashCode) { - if (DEBUG) { - Log.d(TAG, "setMetadata: N_Metadata update: t: " + title + " a: " + artist - + " thumb: " + albumArt.hashCode() + " d: " + duration); - } + final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - mediaSession.setMetadata(new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) - .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt) - .putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, albumArt) - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration).build()); - lastAlbumArtHashCode = albumArt.hashCode(); + if (optAlbumArt.isPresent()) { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, optAlbumArt.get()); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, optAlbumArt.get()); + } + + mediaSession.setMetadata(builder.build()); + + lastTitleHashCode = title.hashCode(); + lastArtistHashCode = artist.hashCode(); + lastDuration = duration; + if (optAlbumArt.isPresent()) { + lastAlbumArtHashCode = optAlbumArt.get().hashCode(); } } + private boolean checkIfMetadataShouldBeSet( + @NonNull final String title, + @NonNull final String artist, + @NonNull final Optional optAlbumArt, + final long duration + ) { + // Check if the values have changed since the last time + if (title.hashCode() != lastTitleHashCode + || artist.hashCode() != lastArtistHashCode + || duration != lastDuration + || (optAlbumArt.isPresent() && optAlbumArt.get().hashCode() != lastAlbumArtHashCode) + ) { + if (DEBUG) { + Log.d(TAG, + "checkIfMetadataShouldBeSet: true - reason: changed values since last"); + } + return true; + } + + // Check if the currently set metadata is valid + if (getMetadataTitle() == null + || getMetadataArtist() == null + // Note that the duration can be <= 0 for live streams + ) { + if (DEBUG) { + if (getMetadataTitle() == null) { + Log.d(TAG, + "N_getMetadataTitle: title == null"); + } else if (getMetadataArtist() == null) { + Log.d(TAG, + "N_getMetadataArtist: artist == null"); + } + } + return true; + } + + // If we got an album art check if the current set AlbumArt is null + if (optAlbumArt.isPresent() && getMetadataAlbumArt() == null) { + if (DEBUG) { + if (getMetadataAlbumArt() == null) { + Log.d(TAG, "N_getMetadataAlbumArt: thumb == null"); + } + } + return true; + } + + // Default - no update required + return false; + } + + private Bitmap getMetadataAlbumArt() { return mediaSession.getController().getMetadata() .getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART); @@ -121,11 +206,6 @@ public class MediaSessionManager { .getString(MediaMetadataCompat.METADATA_KEY_ARTIST); } - private long getMetadataDuration() { - return mediaSession.getController().getMetadata() - .getLong(MediaMetadataCompat.METADATA_KEY_DURATION); - } - /** * Should be called on player destruction to prevent leakage. */ diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java index a6dcadd5e..81e629c2f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/PlaybackResolver.java @@ -14,6 +14,7 @@ import com.google.android.exoplayer2.util.Util; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.player.helper.PlayerDataSource; +import org.schabi.newpipe.util.StreamTypeUtil; public interface PlaybackResolver extends Resolver { @@ -21,7 +22,7 @@ public interface PlaybackResolver extends Resolver { default MediaSource maybeBuildLiveMediaSource(@NonNull final PlayerDataSource dataSource, @NonNull final StreamInfo info) { final StreamType streamType = info.getStreamType(); - if (!(streamType == StreamType.AUDIO_LIVE_STREAM || streamType == StreamType.LIVE_STREAM)) { + if (!StreamTypeUtil.isLiveStream(streamType)) { return null; } diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.java b/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.java new file mode 100644 index 000000000..87b3eed4f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/StreamTypeUtil.java @@ -0,0 +1,24 @@ +package org.schabi.newpipe.util; + +import org.schabi.newpipe.extractor.stream.StreamType; + +/** + * Utility class for {@link org.schabi.newpipe.extractor.stream.StreamType}. + */ +public final class StreamTypeUtil { + private StreamTypeUtil() { + // No impl pls + } + + /** + * Checks if the streamType is a livestream. + * + * @param streamType + * @return true when the streamType is a + * {@link StreamType#LIVE_STREAM} or {@link StreamType#AUDIO_LIVE_STREAM} + */ + public static boolean isLiveStream(final StreamType streamType) { + return streamType == StreamType.LIVE_STREAM + || streamType == StreamType.AUDIO_LIVE_STREAM; + } +}