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..6f26738a6f 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; @@ -126,6 +127,7 @@ import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.CustomRenderersFactory; +import org.schabi.newpipe.player.helper.LegacySubtitleRenderersFactory; import org.schabi.newpipe.player.helper.LoadController; import org.schabi.newpipe.player.helper.MediaSessionManager; import org.schabi.newpipe.player.helper.PlayerDataSource; @@ -437,7 +439,8 @@ public Player(@NonNull final PlayerServiceInterface service) { renderFactory = prefs.getBoolean( context.getString( R.string.always_use_exoplayer_set_output_surface_workaround_key), false) - ? new CustomRenderersFactory(context) : new DefaultRenderersFactory(context); + ? new CustomRenderersFactory(context) + : new LegacySubtitleRenderersFactory(context); renderFactory.setEnableDecoderFallback(true); @@ -794,7 +797,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 +814,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 +2268,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 +2297,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 +2829,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 +2923,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 +3056,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..602fbe9f5f 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; @@ -19,8 +19,12 @@ * load video extension libraries is not needed in our case and has been removed. This should be * changed in the case an extension is shipped with the app, such as the AV1 one. * + * + *+ * Extends {@link LegacySubtitleRenderersFactory} so this path also keeps legacy subtitle decoding. + *
*/ -public final class CustomRenderersFactory extends DefaultRenderersFactory { +public final class CustomRenderersFactory extends LegacySubtitleRenderersFactory { public CustomRenderersFactory(final Context context) { super(context); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/LegacySubtitleRenderersFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/LegacySubtitleRenderersFactory.java new file mode 100644 index 0000000000..916b5a4855 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/helper/LegacySubtitleRenderersFactory.java @@ -0,0 +1,48 @@ +package org.schabi.newpipe.player.helper; + +import android.content.Context; +import android.os.Looper; + +import androidx.media3.exoplayer.DefaultRenderersFactory; +import androidx.media3.exoplayer.Renderer; +import androidx.media3.exoplayer.text.TextOutput; +import androidx.media3.exoplayer.text.TextRenderer; + +import java.util.ArrayList; + +/** + * A {@link DefaultRenderersFactory} that keeps legacy (renderer-side) subtitle decoding enabled. + * + *+ * We side-load subtitles as {@code SingleSampleMediaSource} (raw ttml/vtt) merged via + * {@code MergingMediaSource}. media3 1.4+ parses subtitles during extraction by default and the + * {@link TextRenderer} then only accepts {@code application/x-media3-cues}, so sideloaded tracks + * (which arrive as e.g. {@code application/ttml+xml}) made enabling a subtitle track throw + * {@code IllegalStateException} ("Legacy decoding is disabled") and killed playback. Turning legacy + * decoding back on lets those tracks parse again while still accepting the cue format. + *
+ * + *+ * This is media3's supported path for sideloaded subtitles, so it is a deliberate choice, not a + * workaround. The alternative (letting media3 parse subtitles during extraction, its default) would + * be cleaner long term but needs the subtitle pipeline moved onto standard MediaItems, which does + * not fit the custom SABR media source today. Revisit if that pipeline is ever reworked. + *
+ */ +public class LegacySubtitleRenderersFactory extends DefaultRenderersFactory { + + public LegacySubtitleRenderersFactory(final Context context) { + super(context); + } + + @Override + protected void buildTextRenderers(final Context context, + final TextOutput output, + final Looper outputLooper, + @ExtensionRendererMode final int extensionRendererMode, + final ArrayListThe 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 @@
+
+