diff --git a/app/build.gradle b/app/build.gradle index 421e7d3b95..06f3a6b5c8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,7 +13,8 @@ android { defaultConfig { applicationId "InfinityLoop1309.NewPipeEnhanced" - minSdk 21 + // media3 1.9+ requires API 23 (media3-session). Bumped from 21 -> drops Android 5.0/5.1. + minSdk 23 //noinspection ExpiredTargetSdkVersion targetSdk 33 versionCode 1100 @@ -137,7 +138,7 @@ ext { androidxRoomVersion = '2.4.2' androidxWorkVersion = '2.10.2' - exoPlayerVersion = '2.18.7' + media3Version = '1.10.1' googleAutoServiceVersion = '1.0.1' groupieVersion = '2.10.0' markwonVersion = '4.6.2' @@ -271,8 +272,14 @@ dependencies { implementation "com.squareup.okhttp3:okhttp:3.12.13" // Media player - implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}" - implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}" + implementation "androidx.media3:media3-exoplayer:${media3Version}" + implementation "androidx.media3:media3-exoplayer-dash:${media3Version}" + implementation "androidx.media3:media3-exoplayer-hls:${media3Version}" + implementation "androidx.media3:media3-exoplayer-smoothstreaming:${media3Version}" + implementation "androidx.media3:media3-datasource:${media3Version}" + implementation "androidx.media3:media3-ui:${media3Version}" + implementation "androidx.media3:media3-session:${media3Version}" + implementation "androidx.media3:media3-common:${media3Version}" // Metadata generator for service descriptors compileOnly "com.google.auto.service:auto-service-annotations:${googleAutoServiceVersion}" diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index aa849655a9..a34ae5ed22 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -2,7 +2,7 @@ package org.schabi.newpipe.error import android.os.Parcelable import androidx.annotation.StringRes -import com.google.android.exoplayer2.ExoPlaybackException +import androidx.media3.exoplayer.ExoPlaybackException import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.schabi.newpipe.R diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 63835ed6e7..c86d04ecf0 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -46,8 +46,8 @@ import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; -import com.google.android.exoplayer2.PlaybackException; -import com.google.android.exoplayer2.PlaybackParameters; +import androidx.media3.common.PlaybackException; +import androidx.media3.common.PlaybackParameters; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.tabs.TabLayout; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java index ae704e88c9..af6c41c2be 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java @@ -13,9 +13,9 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.PlaybackException; +import androidx.media3.common.C; +import androidx.media3.exoplayer.ExoPlaybackException; +import androidx.media3.common.PlaybackException; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; @@ -29,9 +29,9 @@ import java.util.Map; import java.util.function.Supplier; -import static com.google.android.exoplayer2.PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW; -import static com.google.android.exoplayer2.PlaybackException.ERROR_CODE_DECODING_FAILED; -import static com.google.android.exoplayer2.PlaybackException.ERROR_CODE_UNSPECIFIED; +import static androidx.media3.common.PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW; +import static androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FAILED; +import static androidx.media3.common.PlaybackException.ERROR_CODE_UNSPECIFIED; /** * Outsourced logic for crashing the player in the {@link VideoDetailFragment}. diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java index 205b786408..c06540db9d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java @@ -26,8 +26,8 @@ import java.util.List; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; +import static androidx.media3.common.Player.REPEAT_MODE_ALL; +import static androidx.media3.common.Player.REPEAT_MODE_ONE; import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; @@ -269,19 +269,19 @@ private NotificationCompat.Action getAction( case NotificationConstants.SMART_REWIND_PREVIOUS: if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) { - return getAction(player, R.drawable.exo_notification_previous, + return getAction(player, R.drawable.exo_icon_previous, R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS); } else { - return getAction(player, R.drawable.exo_controls_rewind, + return getAction(player, R.drawable.exo_icon_rewind, R.string.exo_controls_rewind_description, ACTION_FAST_REWIND); } case NotificationConstants.SMART_FORWARD_NEXT: if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) { - return getAction(player, R.drawable.exo_notification_next, + return getAction(player, R.drawable.exo_icon_next, R.string.exo_controls_next_description, ACTION_PLAY_NEXT); } else { - return getAction(player, R.drawable.exo_controls_fastforward, + return getAction(player, R.drawable.exo_icon_fastforward, R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD); } @@ -303,10 +303,10 @@ private NotificationCompat.Action getAction( || player.getCurrentState() == Player.STATE_PREFLIGHT || player.getCurrentState() == Player.STATE_BLOCKED || player.getCurrentState() == Player.STATE_BUFFERING) { - return getAction(player, R.drawable.exo_notification_pause, + return getAction(player, R.drawable.exo_icon_pause, R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE); } else { - return getAction(player, R.drawable.exo_notification_play, + return getAction(player, R.drawable.exo_icon_play, R.string.exo_controls_play_description, ACTION_PLAY_PAUSE); } @@ -324,10 +324,10 @@ private NotificationCompat.Action getAction( case NotificationConstants.SHUFFLE: if (player.getPlayQueue() != null && player.getPlayQueue().isShuffled()) { - return getAction(player, R.drawable.exo_controls_shuffle_on, + return getAction(player, R.drawable.exo_icon_shuffle_on, R.string.exo_controls_shuffle_on_description, ACTION_SHUFFLE); } else { - return getAction(player, R.drawable.exo_controls_shuffle_off, + return getAction(player, R.drawable.exo_icon_shuffle_off, R.string.exo_controls_shuffle_off_description, ACTION_SHUFFLE); } diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java index b9fba40c65..ea0c233108 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -21,7 +21,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.exoplayer2.PlaybackParameters; +import androidx.media3.common.PlaybackParameters; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding; @@ -553,17 +553,17 @@ private void onStateChanged(final int state) { private void onPlayModeChanged(final int repeatMode, final boolean shuffled) { switch (repeatMode) { - case com.google.android.exoplayer2.Player.REPEAT_MODE_OFF: + case androidx.media3.common.Player.REPEAT_MODE_OFF: queueControlBinding.controlRepeat - .setImageResource(R.drawable.exo_controls_repeat_off); + .setImageResource(R.drawable.exo_icon_repeat_off); break; - case com.google.android.exoplayer2.Player.REPEAT_MODE_ONE: + case androidx.media3.common.Player.REPEAT_MODE_ONE: queueControlBinding.controlRepeat - .setImageResource(R.drawable.exo_controls_repeat_one); + .setImageResource(R.drawable.exo_icon_repeat_one); break; - case com.google.android.exoplayer2.Player.REPEAT_MODE_ALL: + case androidx.media3.common.Player.REPEAT_MODE_ALL: queueControlBinding.controlRepeat - .setImageResource(R.drawable.exo_controls_repeat_all); + .setImageResource(R.drawable.exo_icon_repeat_all); break; } 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 23e803fee1..6a0773423d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -1,18 +1,18 @@ package org.schabi.newpipe.player; -import static com.google.android.exoplayer2.PlaybackException.*; -import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AUTO_TRANSITION; -import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL; -import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_REMOVE; -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.DISCONTINUITY_REASON_SKIP; -import static com.google.android.exoplayer2.Player.DiscontinuityReason; -import static com.google.android.exoplayer2.Player.Listener; -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 androidx.media3.common.PlaybackException.*; +import static androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION; +import static androidx.media3.common.Player.DISCONTINUITY_REASON_INTERNAL; +import static androidx.media3.common.Player.DISCONTINUITY_REASON_REMOVE; +import static androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK; +import static androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT; +import static androidx.media3.common.Player.DISCONTINUITY_REASON_SKIP; +import static androidx.media3.common.Player.DiscontinuityReason; +import static androidx.media3.common.Player.Listener; +import static androidx.media3.common.Player.REPEAT_MODE_ALL; +import static androidx.media3.common.Player.REPEAT_MODE_OFF; +import static androidx.media3.common.Player.REPEAT_MODE_ONE; +import static androidx.media3.common.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; @@ -78,22 +78,23 @@ import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.exoplayer2.*; -import com.google.android.exoplayer2.Player.PositionInfo; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.Tracks; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.TrackGroup; -import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.text.CueGroup; -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.trackselection.MappingTrackSelector; -import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; -import com.google.android.exoplayer2.ui.CaptionStyleCompat; -import com.google.android.exoplayer2.ui.SubtitleView; -import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.video.VideoSize; +import androidx.media3.common.*; +import androidx.media3.exoplayer.*; +import androidx.media3.common.Player.PositionInfo; +import androidx.media3.common.Timeline; +import androidx.media3.common.Tracks; +import androidx.media3.exoplayer.source.MediaSource; +import androidx.media3.common.TrackGroup; +import androidx.media3.exoplayer.source.TrackGroupArray; +import androidx.media3.common.text.CueGroup; +import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; +import androidx.media3.exoplayer.trackselection.MappingTrackSelector; +import androidx.media3.ui.AspectRatioFrameLayout; +import androidx.media3.ui.CaptionStyleCompat; +import androidx.media3.ui.SubtitleView; +import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter; +import androidx.media3.common.util.Util; +import androidx.media3.common.VideoSize; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; @@ -794,7 +795,7 @@ public void handleIntent(@NonNull final Intent intent) { // Player can have state = IDLE when playback is stopped or failed // and we should retry in this case if (simpleExoPlayer.getPlaybackState() - == com.google.android.exoplayer2.Player.STATE_IDLE) { + == androidx.media3.common.Player.STATE_IDLE) { simpleExoPlayer.prepare(); } if (shouldSeek()) { @@ -811,7 +812,7 @@ public void handleIntent(@NonNull final Intent intent) { // Player can have state = IDLE when playback is stopped or failed // and we should retry in this case if (simpleExoPlayer.getPlaybackState() - == com.google.android.exoplayer2.Player.STATE_IDLE) { + == androidx.media3.common.Player.STATE_IDLE) { simpleExoPlayer.prepare(); } simpleExoPlayer.setPlayWhenReady(playWhenReady); @@ -2265,7 +2266,7 @@ public void onPlayWhenReadyChanged(final boolean playWhenReady, final int reason + "reason = [" + reason + "]"); } final int playbackState = exoPlayerIsNull() - ? com.google.android.exoplayer2.Player.STATE_IDLE + ? androidx.media3.common.Player.STATE_IDLE : simpleExoPlayer.getPlaybackState(); updatePlaybackState(playWhenReady, playbackState); } @@ -2294,22 +2295,22 @@ private void updatePlaybackState(final boolean playWhenReady, final int playback } switch (playbackState) { - case com.google.android.exoplayer2.Player.STATE_IDLE: // 1 + case androidx.media3.common.Player.STATE_IDLE: // 1 isPrepared = false; break; - case com.google.android.exoplayer2.Player.STATE_BUFFERING: // 2 + case androidx.media3.common.Player.STATE_BUFFERING: // 2 if (isPrepared) { changeState(STATE_BUFFERING); } break; - case com.google.android.exoplayer2.Player.STATE_READY: //3 + case androidx.media3.common.Player.STATE_READY: //3 if (!isPrepared) { isPrepared = true; onPrepared(playWhenReady); } changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED); break; - case com.google.android.exoplayer2.Player.STATE_ENDED: // 4 + case androidx.media3.common.Player.STATE_ENDED: // 4 changeState(STATE_COMPLETED); saveStreamProgressStateCompleted(); isPrepared = false; @@ -2826,13 +2827,13 @@ private void setRepeatModeButton(final AppCompatImageButton imageButton, @RepeatMode final int repeatMode) { switch (repeatMode) { case REPEAT_MODE_OFF: - imageButton.setImageResource(R.drawable.exo_controls_repeat_off); + imageButton.setImageResource(R.drawable.exo_icon_repeat_off); break; case REPEAT_MODE_ONE: - imageButton.setImageResource(R.drawable.exo_controls_repeat_one); + imageButton.setImageResource(R.drawable.exo_icon_repeat_one); break; case REPEAT_MODE_ALL: - imageButton.setImageResource(R.drawable.exo_controls_repeat_all); + imageButton.setImageResource(R.drawable.exo_icon_repeat_all); break; } } @@ -2920,13 +2921,13 @@ public void onScreenRotationButtonClicked() { * This is done because not all source resolution errors are {@link PlaybackException}, which * are also captured by {@link ExoPlayer} and stops the playback.
* - * @param player The {@link com.google.android.exoplayer2.Player} whose state changed. - * @param events The {@link com.google.android.exoplayer2.Player.Events} that has triggered + * @param player The {@link androidx.media3.common.Player} whose state changed. + * @param events The {@link androidx.media3.common.Player.Events} that has triggered * the player state changes. **/ @Override - public void onEvents(@NonNull final com.google.android.exoplayer2.Player player, - @NonNull final com.google.android.exoplayer2.Player.Events events) { + public void onEvents(@NonNull final androidx.media3.common.Player player, + @NonNull final androidx.media3.common.Player.Events events) { Listener.super.onEvents(player, events); MediaItemTag.from(player.getCurrentMediaItem()).ifPresent(tag -> { if (tag == currentMetadata) { @@ -3053,7 +3054,7 @@ public void onPrepare() { //region Errors /** - * Process exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}. + * Process exceptions produced by {@link androidx.media3.exoplayer.ExoPlayer ExoPlayer}. *There are multiple types of errors:
** It adds more headers to {@code videoplayback} URLs, such as {@code Origin}, {@code Referer} @@ -182,7 +182,7 @@ public Factory setAllowCrossProtocolRedirects( * *
* Note that it must be not enabled on streams which are using a {@link - * com.google.android.exoplayer2.source.ProgressiveMediaSource}, as it will break playback + * androidx.media3.exoplayer.source.ProgressiveMediaSource}, as it will break playback * for them (some exceptions may be thrown). *
* diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java index b5520e8bee..553b12c81f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java @@ -1,7 +1,7 @@ package org.schabi.newpipe.player.event; -import com.google.android.exoplayer2.PlaybackParameters; +import androidx.media3.common.PlaybackParameters; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.playqueue.PlayQueue; diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java index 359eab8b28..5b52455bf5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java @@ -1,6 +1,6 @@ package org.schabi.newpipe.player.event; -import com.google.android.exoplayer2.PlaybackException; +import androidx.media3.common.PlaybackException; public interface PlayerServiceEventListener extends PlayerEventListener { void onFullscreenStateChanged(boolean fullscreen); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java index 8613ef57bc..d880e6aed3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java @@ -15,8 +15,8 @@ import androidx.media.AudioManagerCompat; import androidx.preference.PreferenceManager; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.analytics.AnalyticsListener; +import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.analytics.AnalyticsListener; import org.schabi.newpipe.R; public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, AnalyticsListener { diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java index 202ff85412..dc5e520978 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java @@ -6,16 +6,16 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.database.StandaloneDatabaseProvider; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultDataSource; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; -import com.google.android.exoplayer2.upstream.FileDataSource; -import com.google.android.exoplayer2.upstream.TransferListener; -import com.google.android.exoplayer2.upstream.cache.CacheDataSink; -import com.google.android.exoplayer2.upstream.cache.CacheDataSource; -import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; -import com.google.android.exoplayer2.upstream.cache.SimpleCache; +import androidx.media3.database.StandaloneDatabaseProvider; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.DefaultDataSource; +import androidx.media3.datasource.DefaultHttpDataSource; +import androidx.media3.datasource.FileDataSource; +import androidx.media3.datasource.TransferListener; +import androidx.media3.datasource.cache.CacheDataSink; +import androidx.media3.datasource.cache.CacheDataSource; +import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor; +import androidx.media3.datasource.cache.SimpleCache; import org.schabi.newpipe.player.datasource.YoutubeHttpDataSource; diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CustomMediaCodecVideoRenderer.java b/app/src/main/java/org/schabi/newpipe/player/helper/CustomMediaCodecVideoRenderer.java index 66ac6d50bc..5769346cd2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/CustomMediaCodecVideoRenderer.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CustomMediaCodecVideoRenderer.java @@ -5,10 +5,10 @@ import androidx.annotation.Nullable; -import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter; -import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; -import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; -import com.google.android.exoplayer2.video.VideoRendererEventListener; +import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter; +import androidx.media3.exoplayer.mediacodec.MediaCodecSelector; +import androidx.media3.exoplayer.video.MediaCodecVideoRenderer; +import androidx.media3.exoplayer.video.VideoRendererEventListener; /** * A {@link MediaCodecVideoRenderer} which always enable the output surface workaround that diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CustomRenderersFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CustomRenderersFactory.java index 668b48c306..d7f4066a2c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/CustomRenderersFactory.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CustomRenderersFactory.java @@ -3,10 +3,10 @@ import android.content.Context; import android.os.Handler; -import com.google.android.exoplayer2.DefaultRenderersFactory; -import com.google.android.exoplayer2.Renderer; -import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; -import com.google.android.exoplayer2.video.VideoRendererEventListener; +import androidx.media3.exoplayer.DefaultRenderersFactory; +import androidx.media3.exoplayer.Renderer; +import androidx.media3.exoplayer.mediacodec.MediaCodecSelector; +import androidx.media3.exoplayer.video.VideoRendererEventListener; import java.util.ArrayList; diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java index ec0e4e4a72..bcdcd96437 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java @@ -1,6 +1,6 @@ package org.schabi.newpipe.player.helper; -import com.google.android.exoplayer2.DefaultLoadControl; +import androidx.media3.exoplayer.DefaultLoadControl; public class LoadController extends DefaultLoadControl { 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 72c1be0362..fa231b28c3 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 @@ -2,6 +2,7 @@ import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -15,38 +16,54 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.media.session.MediaButtonReceiver; - -import com.google.android.exoplayer2.ForwardingPlayer; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector.PlaybackPreparer; +import androidx.media3.common.Player; +import androidx.media3.common.util.Util; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.player.NotificationUtil; import org.schabi.newpipe.player.mediasession.MediaSessionCallback; -import org.schabi.newpipe.player.mediasession.PlayQueueNavigator; +import org.schabi.newpipe.player.mediasession.PlaybackPreparer; import org.schabi.newpipe.player.playback.PlayerMediaSession; import org.schabi.newpipe.util.StreamTypeUtil; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Optional; -import static org.schabi.newpipe.player.PlayerService.*; +import static org.schabi.newpipe.player.PlayerService.ACTION_CHANGE_PLAY_MODE; +import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; public class MediaSessionManager { private static final String TAG = MediaSessionManager.class.getSimpleName(); public static final boolean DEBUG = MainActivity.DEBUG; + private static final int MAX_QUEUE_SIZE = 10; + + private static final long PLAYBACK_ACTIONS = PlaybackStateCompat.ACTION_SEEK_TO + | PlaybackStateCompat.ACTION_PLAY + | PlaybackStateCompat.ACTION_PAUSE + | PlaybackStateCompat.ACTION_PLAY_PAUSE + | PlaybackStateCompat.ACTION_SKIP_TO_NEXT + | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM + | PlaybackStateCompat.ACTION_SET_REPEAT_MODE + | PlaybackStateCompat.ACTION_STOP + | PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID; + @NonNull private final MediaSessionCompat mediaSession; @NonNull - private final MediaSessionConnector sessionConnector; + private final Player exoPlayer; + @NonNull + private final MediaSessionCallback callback; + @Nullable + private final PlaybackPreparer playbackPreparer; private final boolean isExternalSession; - private int lastTitleHashCode; - private int lastArtistHashCode; - private long lastDuration; - private int lastAlbumArtHashCode; + @Nullable + private PlaybackStateCompat.CustomAction errorAction; private org.schabi.newpipe.player.Player player; @@ -68,90 +85,205 @@ public MediaSessionManager(@NonNull final Context context, @NonNull final MediaSessionCallback callback, @Nullable final MediaSessionCompat existingSession, @Nullable final PlaybackPreparer playbackPreparer) { - if (DEBUG) { - Log.d(TAG, "MediaSessionManager called"); - } - mediaSession = existingSession != null ? existingSession : new MediaSessionCompat(context, TAG); + this.exoPlayer = player; + this.callback = callback; + this.playbackPreparer = playbackPreparer; + mediaSession = existingSession != null ? existingSession + : new MediaSessionCompat(context, TAG); isExternalSession = existingSession != null; mediaSession.setActive(true); - mediaSession.setPlaybackState(new PlaybackStateCompat.Builder() - .setState(PlaybackStateCompat.STATE_NONE, -1, 1) - .setActions(PlaybackStateCompat.ACTION_SEEK_TO - | PlaybackStateCompat.ACTION_PLAY - | PlaybackStateCompat.ACTION_PAUSE // was play and pause now play/pause - | PlaybackStateCompat.ACTION_SKIP_TO_NEXT - | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - | PlaybackStateCompat.ACTION_SET_REPEAT_MODE - | PlaybackStateCompat.ACTION_STOP - | PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID) - .build()); - - sessionConnector = new MediaSessionConnector(mediaSession); - sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback)); - sessionConnector.setPlayer(new ForwardingPlayer(player) { - @Override - public void play() { - callback.play(); + mediaSession.setCallback(sessionCallback); + + exoPlayer.addListener(playerListener); + updatePlaybackState(); + } + + private final Player.Listener playerListener = new Player.Listener() { + @Override + public void onEvents(@NonNull final Player p, @NonNull final Player.Events events) { + if (events.containsAny( + Player.EVENT_PLAYBACK_STATE_CHANGED, + Player.EVENT_PLAY_WHEN_READY_CHANGED, + Player.EVENT_IS_PLAYING_CHANGED, + Player.EVENT_POSITION_DISCONTINUITY, + Player.EVENT_REPEAT_MODE_CHANGED, + Player.EVENT_PLAYBACK_PARAMETERS_CHANGED)) { + updatePlaybackState(); + } + if (events.containsAny( + Player.EVENT_MEDIA_ITEM_TRANSITION, + Player.EVENT_MEDIA_METADATA_CHANGED, + Player.EVENT_TIMELINE_CHANGED)) { + updateMetadata(); + publishQueue(); } + } + }; + + private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() { + @Override + public void onPlay() { + callback.play(); + } - @Override - public void pause() { - callback.pause(); + @Override + public void onPause() { + callback.pause(); + } + + @Override + public void onSkipToNext() { + callback.playNext(); + } + + @Override + public void onSkipToPrevious() { + callback.playPrevious(); + } + + @Override + public void onSkipToQueueItem(final long id) { + callback.playItemAtIndex((int) id); + } + + @Override + public void onSeekTo(final long pos) { + exoPlayer.seekTo(pos); + } + + @Override + public void onStop() { + callback.close(); + } + + @Override + public void onCustomAction(final String action, final Bundle extras) { + if (ACTION_CHANGE_PLAY_MODE.equals(action)) { + callback.changePlayMode(); + } else if (ACTION_CLOSE.equals(action)) { + callback.close(); } - }); - MediaSessionConnector.CustomActionProvider[] providers = new MediaSessionConnector.CustomActionProvider[2]; - providers[0] = new MediaSessionConnector.CustomActionProvider() { - @Override - public void onCustomAction(@NonNull Player player, @NonNull String action, @Nullable Bundle extras) { - if (action.equals(ACTION_CHANGE_PLAY_MODE)) { - callback.changePlayMode(); - } + } + + @Override + public void onPrepare() { + if (playbackPreparer != null) { + playbackPreparer.onPrepare(false); } + } - @Nullable - @Override - public PlaybackStateCompat.CustomAction getCustomAction(Player player) { - switch (((PlayerMediaSession)callback).mode){ - case 0: - default: - return new android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder( - ACTION_CHANGE_PLAY_MODE, "Shuffle", R.drawable.shuffle_disabled).build(); - case 1: - return new android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder( - ACTION_CHANGE_PLAY_MODE, "Repeat all", R.drawable.exo_controls_shuffle_on).build(); - case 2: - return new android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder( - ACTION_CHANGE_PLAY_MODE, "Repeat none", R.drawable.exo_controls_repeat_one).build(); - case 3: - return new android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder( - ACTION_CHANGE_PLAY_MODE, "Repeat one", R.drawable.exo_controls_repeat_all).build(); - } + @Override + public void onPlayFromMediaId(final String mediaId, final Bundle extras) { + if (playbackPreparer != null) { + playbackPreparer.onPrepareFromMediaId(mediaId, true, extras); } - }; - providers[1] = new MediaSessionConnector.CustomActionProvider() { - @Override - public void onCustomAction(@NonNull Player player, @NonNull String action, @Nullable Bundle extras) { - if (action.equals(ACTION_CLOSE)) { - callback.close(); - } + } + + @Override + public void onPrepareFromMediaId(final String mediaId, final Bundle extras) { + if (playbackPreparer != null) { + playbackPreparer.onPrepareFromMediaId(mediaId, false, extras); } + } - @Nullable - @Override - public PlaybackStateCompat.CustomAction getCustomAction(Player player) { - // Close - return new android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder( - ACTION_CLOSE, "Close", R.drawable.ic_close).build(); + @Override + public void onPlayFromSearch(final String query, final Bundle extras) { + if (playbackPreparer != null) { + playbackPreparer.onPrepareFromSearch(query, true, extras); } - }; - sessionConnector.setCustomActionProviders(providers); - sessionConnector.setMetadataDeduplicationEnabled(true); - sessionConnector.setMediaMetadataProvider(exoPlayer -> buildMediaMetadata()); - - // Set PlaybackPreparer if provided (for Android Auto support) - if (playbackPreparer != null) { - sessionConnector.setPlaybackPreparer(playbackPreparer); } + + @Override + public void onPlayFromUri(final Uri uri, final Bundle extras) { + if (playbackPreparer != null) { + playbackPreparer.onPrepareFromUri(uri, true, extras); + } + } + }; + + private void updatePlaybackState() { + final PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder() + .setActions(PLAYBACK_ACTIONS + | (playbackPreparer != null + ? playbackPreparer.getSupportedPrepareActions() : 0)) + .setState(toPlaybackStateCompat(), exoPlayer.getCurrentPosition(), + exoPlayer.getPlaybackParameters().speed); + builder.addCustomAction(changePlayModeAction()); + builder.addCustomAction(new PlaybackStateCompat.CustomAction.Builder( + ACTION_CLOSE, "Close", R.drawable.ic_close).build()); + if (errorAction != null) { + builder.addCustomAction(errorAction); + } + mediaSession.setPlaybackState(builder.build()); + } + + @PlaybackStateCompat.State + private int toPlaybackStateCompat() { + switch (exoPlayer.getPlaybackState()) { + case Player.STATE_BUFFERING: + return PlaybackStateCompat.STATE_BUFFERING; + case Player.STATE_READY: + return exoPlayer.getPlayWhenReady() + ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED; + case Player.STATE_ENDED: + return PlaybackStateCompat.STATE_STOPPED; + case Player.STATE_IDLE: + default: + return PlaybackStateCompat.STATE_NONE; + } + } + + @NonNull + private PlaybackStateCompat.CustomAction changePlayModeAction() { + final int mode = callback instanceof PlayerMediaSession + ? ((PlayerMediaSession) callback).mode : 0; + switch (mode) { + case 1: + return new PlaybackStateCompat.CustomAction.Builder(ACTION_CHANGE_PLAY_MODE, + "Repeat all", R.drawable.exo_icon_shuffle_on).build(); + case 2: + return new PlaybackStateCompat.CustomAction.Builder(ACTION_CHANGE_PLAY_MODE, + "Repeat none", R.drawable.exo_icon_repeat_one).build(); + case 3: + return new PlaybackStateCompat.CustomAction.Builder(ACTION_CHANGE_PLAY_MODE, + "Repeat one", R.drawable.exo_icon_repeat_all).build(); + case 0: + default: + return new PlaybackStateCompat.CustomAction.Builder(ACTION_CHANGE_PLAY_MODE, + "Shuffle", R.drawable.shuffle_disabled).build(); + } + } + + private void publishQueue() { + final int windowCount = callback.getQueueSize(); + if (windowCount == 0) { + mediaSession.setQueue(Collections.emptyList()); + return; + } + final int currentWindowIndex = callback.getCurrentPlayingIndex(); + final int queueSize = Math.min(MAX_QUEUE_SIZE, windowCount); + final int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0, + windowCount - queueSize); + + final ListThe default is {@code null}, which causes the default user agent of the underlying - * platform to be used. - * - * @param userAgent The user agent that will be used, or {@code null} to use the default user - * agent of the underlying platform. - * @return This factory. - */ - public NiconicoLiveHttpDataSource.Factory setUserAgent(@Nullable String userAgent) { - this.userAgent = userAgent; + public Factory setUserAgent(@Nullable final String userAgent) { + inner.setUserAgent(userAgent); return this; } - /** - * Sets the connect timeout, in milliseconds. - * - *
The default is {@link PurifiedHttpDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS}. - * - * @param connectTimeoutMs The connect timeout, in milliseconds, that will be used. - * @return This factory. - */ - public NiconicoLiveHttpDataSource.Factory setConnectTimeoutMs(int connectTimeoutMs) { - this.connectTimeoutMs = connectTimeoutMs; + public Factory setConnectTimeoutMs(final int connectTimeoutMs) { + inner.setConnectTimeoutMs(connectTimeoutMs); return this; } - /** - * Sets the read timeout, in milliseconds. - * - *
The default is {@link PurifiedHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS}. - * - * @param readTimeoutMs The connect timeout, in milliseconds, that will be used. - * @return This factory. - */ - public NiconicoLiveHttpDataSource.Factory setReadTimeoutMs(int readTimeoutMs) { - this.readTimeoutMs = readTimeoutMs; + public Factory setReadTimeoutMs(final int readTimeoutMs) { + inner.setReadTimeoutMs(readTimeoutMs); return this; } - /** - * Sets whether to allow cross protocol redirects. - * - *
The default is {@code false}. - * - * @param allowCrossProtocolRedirects Whether to allow cross protocol redirects. - * @return This factory. - */ - public NiconicoLiveHttpDataSource.Factory setAllowCrossProtocolRedirects(boolean allowCrossProtocolRedirects) { - this.allowCrossProtocolRedirects = allowCrossProtocolRedirects; + public Factory setAllowCrossProtocolRedirects(final boolean allowCrossProtocolRedirects) { + inner.setAllowCrossProtocolRedirects(allowCrossProtocolRedirects); return this; } - /** - * Sets a content type {@link Predicate}. If a content type is rejected by the predicate then a - * {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link - * PurifiedHttpDataSource#open(DataSpec)}. - * - *
The default is {@code null}.
- *
- * @param contentTypePredicate The content type {@link Predicate}, or {@code null} to clear a
- * predicate that was previously set.
- * @return This factory.
- */
- public NiconicoLiveHttpDataSource.Factory setContentTypePredicate(@Nullable Predicate The default is {@code null}.
- *
- * See {@link DataSource#addTransferListener(TransferListener)}.
- *
- * @param transferListener The listener that will be used.
- * @return This factory.
- */
- public NiconicoLiveHttpDataSource.Factory setTransferListener(@Nullable TransferListener transferListener) {
- this.transferListener = transferListener;
+ public Factory setTransferListener(@Nullable final TransferListener transferListener) {
+ inner.setTransferListener(transferListener);
return this;
}
- /**
- * Sets whether we should keep the POST method and body when we have HTTP 302 redirects for a
- * POST request.
- */
- public NiconicoLiveHttpDataSource.Factory setKeepPostFor302Redirects(boolean keepPostFor302Redirects) {
- this.keepPostFor302Redirects = keepPostFor302Redirects;
+ public Factory setKeepPostFor302Redirects(final boolean keepPostFor302Redirects) {
+ inner.setKeepPostFor302Redirects(keepPostFor302Redirects);
return this;
}
@Override
public NiconicoLiveHttpDataSource createDataSource() {
- NiconicoLiveHttpDataSource dataSource =
- new NiconicoLiveHttpDataSource(
- userAgent,
- connectTimeoutMs,
- readTimeoutMs,
- allowCrossProtocolRedirects,
- defaultRequestProperties,
- contentTypePredicate,
- keepPostFor302Redirects,
- url);
- if (transferListener != null) {
- dataSource.addTransferListener(transferListener);
- }
- return dataSource;
+ return new NiconicoLiveHttpDataSource(inner.createDataSource(), url);
}
}
- NiconicoLiveHttpDataSource(@Nullable String userAgent, int connectTimeoutMillis, int readTimeoutMillis, boolean allowCrossProtocolRedirects, @Nullable RequestProperties defaultRequestProperties
- , @Nullable Predicate The default is {@code null}, which causes the default user agent of the underlying
- * platform to be used.
- *
- * @param userAgent The user agent that will be used, or {@code null} to use the default user
- * agent of the underlying platform.
- * @return This factory.
- */
- public PurifiedHttpDataSource.Factory setUserAgent(@Nullable String userAgent) {
- this.userAgent = userAgent;
+ public Factory setUserAgent(@Nullable final String userAgent) {
+ inner.setUserAgent(userAgent);
return this;
}
- /**
- * Sets the connect timeout, in milliseconds.
- *
- * The default is {@link PurifiedHttpDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS}.
- *
- * @param connectTimeoutMs The connect timeout, in milliseconds, that will be used.
- * @return This factory.
- */
- public PurifiedHttpDataSource.Factory setConnectTimeoutMs(int connectTimeoutMs) {
- this.connectTimeoutMs = connectTimeoutMs;
+ public Factory setConnectTimeoutMs(final int connectTimeoutMs) {
+ inner.setConnectTimeoutMs(connectTimeoutMs);
return this;
}
- /**
- * Sets the read timeout, in milliseconds.
- *
- * The default is {@link PurifiedHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS}.
- *
- * @param readTimeoutMs The connect timeout, in milliseconds, that will be used.
- * @return This factory.
- */
- public PurifiedHttpDataSource.Factory setReadTimeoutMs(int readTimeoutMs) {
- this.readTimeoutMs = readTimeoutMs;
+ public Factory setReadTimeoutMs(final int readTimeoutMs) {
+ inner.setReadTimeoutMs(readTimeoutMs);
return this;
}
- /**
- * Sets whether to allow cross protocol redirects.
- *
- * The default is {@code false}.
- *
- * @param allowCrossProtocolRedirects Whether to allow cross protocol redirects.
- * @return This factory.
- */
- public PurifiedHttpDataSource.Factory setAllowCrossProtocolRedirects(boolean allowCrossProtocolRedirects) {
- this.allowCrossProtocolRedirects = allowCrossProtocolRedirects;
+ public Factory setAllowCrossProtocolRedirects(final boolean allowCrossProtocolRedirects) {
+ inner.setAllowCrossProtocolRedirects(allowCrossProtocolRedirects);
return this;
}
- /**
- * Sets a content type {@link Predicate}. If a content type is rejected by the predicate then a
- * {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link
- * PurifiedHttpDataSource#open(DataSpec)}.
- *
- * The default is {@code null}.
- *
- * @param contentTypePredicate The content type {@link Predicate}, or {@code null} to clear a
- * predicate that was previously set.
- * @return This factory.
- */
- public PurifiedHttpDataSource.Factory setContentTypePredicate(@Nullable Predicate The default is {@code null}.
- *
- * See {@link DataSource#addTransferListener(TransferListener)}.
- *
- * @param transferListener The listener that will be used.
- * @return This factory.
- */
- public PurifiedHttpDataSource.Factory setTransferListener(@Nullable TransferListener transferListener) {
- this.transferListener = transferListener;
+ public Factory setTransferListener(@Nullable final TransferListener transferListener) {
+ inner.setTransferListener(transferListener);
return this;
}
- /**
- * Sets whether we should keep the POST method and body when we have HTTP 302 redirects for a
- * POST request.
- */
- public PurifiedHttpDataSource.Factory setKeepPostFor302Redirects(boolean keepPostFor302Redirects) {
- this.keepPostFor302Redirects = keepPostFor302Redirects;
+ public Factory setKeepPostFor302Redirects(final boolean keepPostFor302Redirects) {
+ inner.setKeepPostFor302Redirects(keepPostFor302Redirects);
return this;
}
@Override
public PurifiedHttpDataSource createDataSource() {
- PurifiedHttpDataSource dataSource =
- new PurifiedHttpDataSource(
- userAgent,
- connectTimeoutMs,
- readTimeoutMs,
- allowCrossProtocolRedirects,
- defaultRequestProperties,
- contentTypePredicate,
- keepPostFor302Redirects);
- if (transferListener != null) {
- dataSource.addTransferListener(transferListener);
- }
- return dataSource;
+ return new PurifiedHttpDataSource(inner.createDataSource());
}
}
- PurifiedHttpDataSource(
- @Nullable String userAgent,
- int connectTimeoutMillis,
- int readTimeoutMillis,
- boolean allowCrossProtocolRedirects,
- @Nullable RequestProperties defaultRequestProperties,
- @Nullable Predicate
diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java
index 7376070015..e482646026 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java
@@ -3,7 +3,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.source.MediaSource;
+import androidx.media3.exoplayer.source.MediaSource;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java
index cd0f377155..265c50a1c7 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java
@@ -9,15 +9,15 @@
import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
-import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
+import static androidx.media3.common.Player.REPEAT_MODE_OFF;
import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode;
public class PlayerMediaSession implements MediaSessionCallback {
public final Player player;
- private final com.google.android.exoplayer2.Player exoPlayer;
+ private final androidx.media3.common.Player exoPlayer;
public int mode = 0;
- public PlayerMediaSession(final Player player, final com.google.android.exoplayer2.Player exoPlayer) {
+ public PlayerMediaSession(final Player player, final androidx.media3.common.Player exoPlayer) {
this.player = player;
this.exoPlayer = exoPlayer;
refresh();
@@ -114,10 +114,10 @@ public void changePlayMode() {
player.onShuffleClicked();
break;
case 1: // repeat_one
- player.setRepeatMode(com.google.android.exoplayer2.Player.REPEAT_MODE_ONE);
+ player.setRepeatMode(androidx.media3.common.Player.REPEAT_MODE_ONE);
break;
case 2: // repeat_all
- player.setRepeatMode(com.google.android.exoplayer2.Player.REPEAT_MODE_ALL);
+ player.setRepeatMode(androidx.media3.common.Player.REPEAT_MODE_ALL);
break;
case 3: // repeat_none
default:
@@ -132,9 +132,9 @@ public void close(){
public void refresh(){
if (exoPlayer.getShuffleModeEnabled()) {
this.mode = 1;
- } else if (exoPlayer.getRepeatMode() == com.google.android.exoplayer2.Player.REPEAT_MODE_ONE) {
+ } else if (exoPlayer.getRepeatMode() == androidx.media3.common.Player.REPEAT_MODE_ONE) {
this.mode = 2;
- } else if (exoPlayer.getRepeatMode() == com.google.android.exoplayer2.Player.REPEAT_MODE_ALL) {
+ } else if (exoPlayer.getRepeatMode() == androidx.media3.common.Player.REPEAT_MODE_ALL) {
this.mode = 3;
} else {
this.mode = 0;
diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/SurfaceHolderCallback.java b/app/src/main/java/org/schabi/newpipe/player/playback/SurfaceHolderCallback.java
index da6cb36d4f..b02a9240d3 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playback/SurfaceHolderCallback.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playback/SurfaceHolderCallback.java
@@ -3,8 +3,8 @@
import android.content.Context;
import android.view.SurfaceHolder;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.video.PlaceholderSurface;
+import androidx.media3.common.Player;
+import androidx.media3.exoplayer.video.PlaceholderSurface;
/**
* Prevent error message: 'Unrecoverable player error occurred'
diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java
index 7e3f590078..d44b64b556 100644
--- a/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java
+++ b/app/src/main/java/org/schabi/newpipe/player/resolver/AudioPlaybackResolver.java
@@ -9,7 +9,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.source.MediaSource;
+import androidx.media3.exoplayer.source.MediaSource;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/CustomDataSourceFactory.java b/app/src/main/java/org/schabi/newpipe/player/resolver/CustomDataSourceFactory.java
index 82dff6ec9b..06fe0b3656 100644
--- a/app/src/main/java/org/schabi/newpipe/player/resolver/CustomDataSourceFactory.java
+++ b/app/src/main/java/org/schabi/newpipe/player/resolver/CustomDataSourceFactory.java
@@ -4,9 +4,9 @@
import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.upstream.ByteArrayDataSource;
-import com.google.android.exoplayer2.upstream.DataSource;
-import com.google.android.exoplayer2.upstream.TransferListener;
+import androidx.media3.datasource.ByteArrayDataSource;
+import androidx.media3.datasource.DataSource;
+import androidx.media3.datasource.TransferListener;
public class CustomDataSourceFactory implements DataSource.Factory {
private final Context context;
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 d8d9ecb0d6..e15a73033d 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
@@ -6,19 +6,19 @@
import android.net.Uri;
import android.util.Log;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.MediaItem;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.ProgressiveMediaSource;
-import com.google.android.exoplayer2.source.dash.DashMediaSource;
-import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
-import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser;
-import com.google.android.exoplayer2.source.hls.HlsMediaSource;
-import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist;
-import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
-import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
-import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
-import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
+import androidx.media3.common.C;
+import androidx.media3.common.MediaItem;
+import androidx.media3.exoplayer.source.MediaSource;
+import androidx.media3.exoplayer.source.ProgressiveMediaSource;
+import androidx.media3.exoplayer.dash.DashMediaSource;
+import androidx.media3.exoplayer.dash.manifest.DashManifest;
+import androidx.media3.exoplayer.dash.manifest.DashManifestParser;
+import androidx.media3.exoplayer.hls.HlsMediaSource;
+import androidx.media3.exoplayer.hls.playlist.HlsPlaylist;
+import androidx.media3.exoplayer.hls.playlist.HlsPlaylistParser;
+import androidx.media3.exoplayer.smoothstreaming.SsMediaSource;
+import androidx.media3.exoplayer.smoothstreaming.manifest.SsManifest;
+import androidx.media3.exoplayer.smoothstreaming.manifest.SsManifestParser;
import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException;
diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java
index 9c9324081c..ce6fef5d63 100644
--- a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java
+++ b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java
@@ -7,11 +7,11 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.MediaItem;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.MergingMediaSource;
-import com.google.android.exoplayer2.source.SingleSampleMediaSource;
+import androidx.media3.common.C;
+import androidx.media3.common.MediaItem;
+import androidx.media3.exoplayer.source.MediaSource;
+import androidx.media3.exoplayer.source.MergingMediaSource;
+import androidx.media3.exoplayer.source.SingleSampleMediaSource;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream;
@@ -30,7 +30,7 @@
import java.util.Optional;
import java.util.stream.Collectors;
-import static com.google.android.exoplayer2.C.TIME_UNSET;
+import static androidx.media3.common.C.TIME_UNSET;
import static org.schabi.newpipe.util.ListHelper.*;
public class VideoPlaybackResolver implements PlaybackResolver {
diff --git a/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java b/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java
index 010e1be56f..a6f43415f2 100644
--- a/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java
+++ b/app/src/main/java/org/schabi/newpipe/views/ExpandableSurfaceView.java
@@ -5,10 +5,10 @@
import android.util.AttributeSet;
import android.view.SurfaceView;
-import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
+import androidx.media3.ui.AspectRatioFrameLayout;
-import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT;
-import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
+import static androidx.media3.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT;
+import static androidx.media3.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
public class ExpandableSurfaceView extends SurfaceView {
private int resizeMode = RESIZE_MODE_FIT;
diff --git a/app/src/main/java/us/shandian/giga/hls/manifest/HlsPlaylistParser.kt b/app/src/main/java/us/shandian/giga/hls/manifest/HlsPlaylistParser.kt
index 49590671a6..5174924acb 100644
--- a/app/src/main/java/us/shandian/giga/hls/manifest/HlsPlaylistParser.kt
+++ b/app/src/main/java/us/shandian/giga/hls/manifest/HlsPlaylistParser.kt
@@ -1,13 +1,13 @@
package us.shandian.giga.hls.manifest
import android.net.Uri
-import com.google.android.exoplayer2.C
-import com.google.android.exoplayer2.Format
-import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist as ExoHlsMediaPlaylist
-import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment as ExoHlsSegment
-import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.SegmentBase as ExoHlsSegmentBase
-import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist as ExoHlsMultivariantPlaylist
-import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser as ExoHlsPlaylistParser
+import androidx.media3.common.C
+import androidx.media3.common.Format
+import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist as ExoHlsMediaPlaylist
+import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Segment as ExoHlsSegment
+import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.SegmentBase as ExoHlsSegmentBase
+import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist as ExoHlsMultivariantPlaylist
+import androidx.media3.exoplayer.hls.playlist.HlsPlaylistParser as ExoHlsPlaylistParser
import java.io.ByteArrayInputStream
import java.io.IOException
import java.net.URI
diff --git a/app/src/main/res/drawable-anydpi-v21/exo_icon_fastforward.xml b/app/src/main/res/drawable-anydpi-v21/exo_icon_fastforward.xml
new file mode 100644
index 0000000000..4b86e109e9
--- /dev/null
+++ b/app/src/main/res/drawable-anydpi-v21/exo_icon_fastforward.xml
@@ -0,0 +1,25 @@
+
+