mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2025-10-03 09:49:21 +02:00
Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts: # app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java # app/src/main/java/org/schabi/newpipe/player/Player.java # app/src/main/java/org/schabi/newpipe/player/PlayerService.java # app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java # app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.java
This commit is contained in:
commit
c98f56bf7b
14 changed files with 268 additions and 244 deletions
|
@ -88,6 +88,7 @@ import org.schabi.newpipe.local.dialog.PlaylistDialog
|
|||
import org.schabi.newpipe.local.history.HistoryRecordManager
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment
|
||||
import org.schabi.newpipe.player.Player
|
||||
import org.schabi.newpipe.player.PlayerIntentType
|
||||
import org.schabi.newpipe.player.PlayerService
|
||||
import org.schabi.newpipe.player.PlayerType
|
||||
import org.schabi.newpipe.player.event.OnKeyDownListener
|
||||
|
@ -1044,8 +1045,10 @@ class VideoDetailFragment :
|
|||
tryAddVideoPlayerView()
|
||||
|
||||
val playerIntent = NavigationHelper.getPlayerIntent(
|
||||
requireContext(), PlayerService::class.java, queue, true, autoPlayEnabled
|
||||
requireContext(), PlayerService::class.java, queue, PlayerIntentType.AllOthers
|
||||
)
|
||||
.putExtra(Player.PLAY_WHEN_READY, autoPlayEnabled)
|
||||
.putExtra(Player.RESUME_PLAYBACK, true)
|
||||
ContextCompat.startForegroundService(activity, playerIntent)
|
||||
}
|
||||
|
||||
|
|
|
@ -24,12 +24,12 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJ
|
|||
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 org.schabi.newpipe.extractor.ServiceList.YouTube;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs;
|
||||
|
@ -61,6 +61,7 @@ import android.view.LayoutInflater;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.IntentCompat;
|
||||
import androidx.core.math.MathUtils;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
|
@ -107,6 +108,7 @@ import org.schabi.newpipe.player.playback.MediaSourceManager;
|
|||
import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType;
|
||||
|
@ -116,6 +118,7 @@ import org.schabi.newpipe.player.ui.PlayerUiList;
|
|||
import org.schabi.newpipe.player.ui.PopupPlayerUi;
|
||||
import org.schabi.newpipe.player.ui.VideoPlayerUi;
|
||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.SerializedCache;
|
||||
|
@ -123,15 +126,18 @@ import org.schabi.newpipe.util.StreamTypeUtil;
|
|||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import coil3.target.Target;
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.disposables.SerialDisposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* The ExoPlayer wrapper & Player business logic.
|
||||
|
@ -157,15 +163,13 @@ public final class Player implements PlaybackListener, Listener {
|
|||
// Intent
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static final String REPEAT_MODE = "repeat_mode";
|
||||
public static final String PLAYBACK_QUALITY = "playback_quality";
|
||||
public static final String PLAY_QUEUE_KEY = "play_queue_key";
|
||||
public static final String ENQUEUE = "enqueue";
|
||||
public static final String ENQUEUE_NEXT = "enqueue_next";
|
||||
public static final String RESUME_PLAYBACK = "resume_playback";
|
||||
public static final String PLAY_WHEN_READY = "play_when_ready";
|
||||
public static final String PLAYER_TYPE = "player_type";
|
||||
public static final String IS_MUTED = "is_muted";
|
||||
public static final String PLAYER_INTENT_TYPE = "player_intent_type";
|
||||
public static final String PLAYER_INTENT_DATA = "player_intent_data";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Time constants
|
||||
|
@ -251,6 +255,8 @@ public final class Player implements PlaybackListener, Listener {
|
|||
private final SerialDisposable progressUpdateDisposable = new SerialDisposable();
|
||||
@NonNull
|
||||
private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable();
|
||||
@NonNull
|
||||
private final CompositeDisposable streamItemDisposable = new CompositeDisposable();
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
|
@ -343,49 +349,120 @@ public final class Player implements PlaybackListener, Listener {
|
|||
|
||||
@SuppressWarnings("MethodLength")
|
||||
public void handleIntent(@NonNull final Intent intent) {
|
||||
// fail fast if no play queue was provided
|
||||
final String queueCache = intent.getStringExtra(PLAY_QUEUE_KEY);
|
||||
if (queueCache == null) {
|
||||
final var playerIntentType = IntentCompat.getSerializableExtra(intent, PLAYER_INTENT_TYPE,
|
||||
PlayerIntentType.class);
|
||||
if (playerIntentType == null) {
|
||||
return;
|
||||
}
|
||||
final PlayQueue newQueue = SerializedCache.getInstance().take(queueCache, PlayQueue.class);
|
||||
if (newQueue == null) {
|
||||
return;
|
||||
// TODO: this should be in the second switch below, but I’m not sure whether I
|
||||
// can move the initUIs stuff without breaking the setup for edge cases somehow.
|
||||
// when playing from a timestamp, keep the current player as-is.
|
||||
if (playerIntentType != PlayerIntentType.TimestampChange) {
|
||||
playerType = IntentCompat.getSerializableExtra(intent, PLAYER_TYPE, PlayerType.class);
|
||||
}
|
||||
|
||||
final PlayerType oldPlayerType = playerType;
|
||||
playerType = PlayerType.retrieveFromIntent(intent);
|
||||
initUIsForCurrentPlayerType();
|
||||
// We need to setup audioOnly before super(), see "sourceOf"
|
||||
isAudioOnly = audioPlayerSelected();
|
||||
|
||||
if (intent.hasExtra(PLAYBACK_QUALITY)) {
|
||||
videoResolver.setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY));
|
||||
}
|
||||
|
||||
// Resolve enqueue intents
|
||||
if (intent.getBooleanExtra(ENQUEUE, false) && playQueue != null) {
|
||||
playQueue.append(newQueue.getStreams());
|
||||
return;
|
||||
final boolean playWhenReady = intent.getBooleanExtra(PLAY_WHEN_READY, true);
|
||||
|
||||
// Resolve enqueue next intents
|
||||
} else if (intent.getBooleanExtra(ENQUEUE_NEXT, false) && playQueue != null) {
|
||||
final int currentIndex = playQueue.getIndex();
|
||||
switch (playerIntentType) {
|
||||
case Enqueue -> {
|
||||
if (playQueue != null) {
|
||||
final PlayQueue newQueue = getPlayQueueFromCache(intent);
|
||||
if (newQueue == null) {
|
||||
return;
|
||||
}
|
||||
playQueue.append(newQueue.getStreams());
|
||||
playQueue.move(playQueue.size() - 1, currentIndex + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
final PlaybackParameters savedParameters = retrievePlaybackParametersFromPrefs(this);
|
||||
final float playbackSpeed = savedParameters.speed;
|
||||
final float playbackPitch = savedParameters.pitch;
|
||||
final boolean playbackSkipSilence = getPrefs().getBoolean(getContext().getString(
|
||||
R.string.playback_skip_silence_key), getPlaybackSkipSilence());
|
||||
// TODO: This falls through to the old logic, there was no playQueue
|
||||
// yet so we should start the player and add the new video
|
||||
break;
|
||||
}
|
||||
case EnqueueNext -> {
|
||||
if (playQueue != null) {
|
||||
final PlayQueue newQueue = getPlayQueueFromCache(intent);
|
||||
if (newQueue == null) {
|
||||
return;
|
||||
}
|
||||
final PlayQueueItem newItem = newQueue.getStreams().get(0);
|
||||
newQueue.enqueueNext(newItem, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: This falls through to the old logic, there was no playQueue
|
||||
// yet so we should start the player and add the new video
|
||||
break;
|
||||
}
|
||||
case TimestampChange -> {
|
||||
final var data = Objects.requireNonNull(IntentCompat.getParcelableExtra(intent,
|
||||
PLAYER_INTENT_DATA, TimestampChangeData.class));
|
||||
final Single<StreamInfo> single =
|
||||
ExtractorHelper.getStreamInfo(data.getServiceId(), data.getUrl(), false);
|
||||
streamItemDisposable.add(single.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(info -> {
|
||||
final @Nullable PlayQueue oldPlayQueue = playQueue;
|
||||
info.setStartPosition(data.getSeconds());
|
||||
final PlayQueueItem item = new PlayQueueItem(info);
|
||||
|
||||
// If the stream is already playing,
|
||||
// we can just seek to the appropriate timestamp
|
||||
if (oldPlayQueue != null && item.equals(oldPlayQueue.getItem())) {
|
||||
// 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) {
|
||||
simpleExoPlayer.prepare();
|
||||
}
|
||||
simpleExoPlayer.seekTo(oldPlayQueue.getIndex(),
|
||||
data.getSeconds() * 1000L);
|
||||
simpleExoPlayer.setPlayWhenReady(playWhenReady);
|
||||
|
||||
} else {
|
||||
final PlayQueue newPlayQueue;
|
||||
|
||||
// If there is no queue yet, just add our item
|
||||
if (oldPlayQueue == null) {
|
||||
newPlayQueue = new SinglePlayQueue(item);
|
||||
|
||||
// else we add the timestamped stream behind the current video
|
||||
// and start playing it.
|
||||
} else {
|
||||
oldPlayQueue.enqueueNext(item, true);
|
||||
oldPlayQueue.offsetIndex(1);
|
||||
newPlayQueue = oldPlayQueue;
|
||||
}
|
||||
initPlayback(newPlayQueue, playWhenReady);
|
||||
}
|
||||
|
||||
}, throwable -> {
|
||||
// This will only show a snackbar if the passed context has a root view:
|
||||
// otherwise it will resort to showing a notification, so we are safe
|
||||
// here.
|
||||
final var info = new ErrorInfo(throwable, UserAction.PLAY_ON_POPUP,
|
||||
data.getUrl(), null, data.getUrl());
|
||||
ErrorUtil.createNotification(context, info);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
case AllOthers -> {
|
||||
// fallthrough; TODO: put other intent data in separate cases
|
||||
}
|
||||
}
|
||||
|
||||
final PlayQueue newQueue = getPlayQueueFromCache(intent);
|
||||
if (newQueue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// branching parameters for below
|
||||
final boolean samePlayQueue = playQueue != null && playQueue.equalStreamsAndIndex(newQueue);
|
||||
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
|
||||
final boolean playWhenReady = intent.getBooleanExtra(PLAY_WHEN_READY, true);
|
||||
final boolean isMuted = intent.getBooleanExtra(IS_MUTED, isMuted());
|
||||
|
||||
/*
|
||||
* TODO As seen in #7427 this does not work:
|
||||
|
@ -400,7 +477,7 @@ public final class Player implements PlaybackListener, Listener {
|
|||
if (!exoPlayerIsNull()
|
||||
&& newQueue.size() == 1 && newQueue.getItem() != null
|
||||
&& playQueue != null && playQueue.size() == 1 && playQueue.getItem() != null
|
||||
&& newQueue.getItem().getUrl().equals(playQueue.getItem().getUrl())
|
||||
&& newQueue.getItem().equals(playQueue.getItem())
|
||||
&& newQueue.getItem().getRecoveryPosition() != Long.MIN_VALUE) {
|
||||
// Player can have state = IDLE when playback is stopped or failed
|
||||
// and we should retry in this case
|
||||
|
@ -426,7 +503,8 @@ public final class Player implements PlaybackListener, Listener {
|
|||
|
||||
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false)
|
||||
&& DependentPreferenceHelper.getResumePlaybackEnabled(context)
|
||||
&& !samePlayQueue
|
||||
// !samePlayQueue
|
||||
&& (playQueue == null || !playQueue.equalStreamsAndIndex(newQueue))
|
||||
&& !newQueue.isEmpty()
|
||||
&& newQueue.getItem() != null
|
||||
&& newQueue.getItem().getRecoveryPosition() == Long.MIN_VALUE) {
|
||||
|
@ -442,30 +520,30 @@ public final class Player implements PlaybackListener, Listener {
|
|||
newQueue.setRecovery(newQueue.getIndex(),
|
||||
state.getProgressMillis());
|
||||
}
|
||||
initPlayback(newQueue, repeatMode, playbackSpeed, playbackPitch,
|
||||
playbackSkipSilence, playWhenReady, isMuted);
|
||||
initPlayback(newQueue, playWhenReady);
|
||||
},
|
||||
error -> {
|
||||
if (DEBUG) {
|
||||
Log.w(TAG, "Failed to start playback", error);
|
||||
}
|
||||
// In case any error we can start playback without history
|
||||
initPlayback(newQueue, repeatMode, playbackSpeed, playbackPitch,
|
||||
playbackSkipSilence, playWhenReady, isMuted);
|
||||
initPlayback(newQueue, playWhenReady);
|
||||
},
|
||||
() -> {
|
||||
// Completed but not found in history
|
||||
initPlayback(newQueue, repeatMode, playbackSpeed, playbackPitch,
|
||||
playbackSkipSilence, playWhenReady, isMuted);
|
||||
initPlayback(newQueue, playWhenReady);
|
||||
}
|
||||
));
|
||||
} else {
|
||||
// Good to go...
|
||||
// In a case of equal PlayQueues we can re-init old one but only when it is disposed
|
||||
initPlayback(samePlayQueue ? playQueue : newQueue, repeatMode, playbackSpeed,
|
||||
playbackPitch, playbackSkipSilence, playWhenReady, isMuted);
|
||||
initPlayback(samePlayQueue ? playQueue : newQueue, playWhenReady);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void handleIntentPost(final PlayerType oldPlayerType) {
|
||||
if (oldPlayerType != playerType && playQueue != null) {
|
||||
// If playerType changes from one to another we should reload the player
|
||||
// (to disable/enable video stream or to set quality)
|
||||
|
@ -476,6 +554,19 @@ public final class Player implements PlaybackListener, Listener {
|
|||
NavigationHelper.sendPlayerStartedEvent(context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PlayQueue getPlayQueueFromCache(@NonNull final Intent intent) {
|
||||
final String queueCache = intent.getStringExtra(PLAY_QUEUE_KEY);
|
||||
if (queueCache == null) {
|
||||
return null;
|
||||
}
|
||||
final PlayQueue newQueue = SerializedCache.getInstance().take(queueCache, PlayQueue.class);
|
||||
if (newQueue == null) {
|
||||
return null;
|
||||
}
|
||||
return newQueue;
|
||||
}
|
||||
|
||||
private void initUIsForCurrentPlayerType() {
|
||||
if ((UIs.get(MainPlayerUi.class) != null && playerType == PlayerType.MAIN)
|
||||
|| (UIs.get(PopupPlayerUi.class) != null
|
||||
|
@ -511,16 +602,13 @@ public final class Player implements PlaybackListener, Listener {
|
|||
}
|
||||
|
||||
private void initPlayback(@NonNull final PlayQueue queue,
|
||||
@RepeatMode final int repeatMode,
|
||||
final float playbackSpeed,
|
||||
final float playbackPitch,
|
||||
final boolean playbackSkipSilence,
|
||||
final boolean playOnReady,
|
||||
final boolean isMuted) {
|
||||
final boolean playOnReady) {
|
||||
destroyPlayer();
|
||||
initPlayer(playOnReady);
|
||||
setRepeatMode(repeatMode);
|
||||
setPlaybackParameters(playbackSpeed, playbackPitch, playbackSkipSilence);
|
||||
final boolean playbackSkipSilence = getPrefs().getBoolean(getContext().getString(
|
||||
R.string.playback_skip_silence_key), getPlaybackSkipSilence());
|
||||
final PlaybackParameters savedParameters = retrievePlaybackParametersFromPrefs(this);
|
||||
setPlaybackParameters(savedParameters.speed, savedParameters.pitch, playbackSkipSilence);
|
||||
|
||||
playQueue = queue;
|
||||
playQueue.init();
|
||||
|
@ -528,7 +616,7 @@ public final class Player implements PlaybackListener, Listener {
|
|||
|
||||
UIs.call(PlayerUi::initPlayback);
|
||||
|
||||
simpleExoPlayer.setVolume(isMuted ? 0 : 1);
|
||||
simpleExoPlayer.setVolume(isMuted() ? 0 : 1);
|
||||
notifyQueueUpdateToListeners();
|
||||
}
|
||||
|
||||
|
@ -616,6 +704,7 @@ public final class Player implements PlaybackListener, Listener {
|
|||
|
||||
databaseUpdateDisposable.clear();
|
||||
progressUpdateDisposable.set(null);
|
||||
streamItemDisposable.clear();
|
||||
|
||||
UIs.destroyAllOfType(null);
|
||||
}
|
||||
|
@ -1171,16 +1260,25 @@ public final class Player implements PlaybackListener, Listener {
|
|||
return exoPlayerIsNull() ? REPEAT_MODE_OFF : simpleExoPlayer.getRepeatMode();
|
||||
}
|
||||
|
||||
public void setRepeatMode(@RepeatMode final int repeatMode) {
|
||||
public void cycleNextRepeatMode() {
|
||||
if (!exoPlayerIsNull()) {
|
||||
@RepeatMode final int repeatMode;
|
||||
switch (simpleExoPlayer.getRepeatMode()) {
|
||||
case REPEAT_MODE_OFF:
|
||||
repeatMode = REPEAT_MODE_ONE;
|
||||
break;
|
||||
case REPEAT_MODE_ONE:
|
||||
repeatMode = REPEAT_MODE_ALL;
|
||||
break;
|
||||
case REPEAT_MODE_ALL:
|
||||
default:
|
||||
repeatMode = REPEAT_MODE_OFF;
|
||||
break;
|
||||
}
|
||||
simpleExoPlayer.setRepeatMode(repeatMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void cycleNextRepeatMode() {
|
||||
setRepeatMode(nextRepeatMode(getRepeatMode()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRepeatModeChanged(@RepeatMode final int repeatMode) {
|
||||
if (DEBUG) {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package org.schabi.newpipe.player
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
// We model this as an enum class plus one struct for each enum value
|
||||
// so we can consume it from Java properly. After converting to Kotlin,
|
||||
// we could switch to a sealed enum class & a proper Kotlin `when` match.
|
||||
enum class PlayerIntentType {
|
||||
Enqueue,
|
||||
EnqueueNext,
|
||||
TimestampChange,
|
||||
AllOthers
|
||||
}
|
||||
|
||||
/**
|
||||
* A timestamp on the given was clicked and we should switch the playing stream to it.
|
||||
*/
|
||||
@Parcelize
|
||||
data class TimestampChangeData(
|
||||
val serviceId: Int,
|
||||
val url: String,
|
||||
val seconds: Int
|
||||
) : Parcelable
|
|
@ -151,7 +151,9 @@ class PlayerService : MediaBrowserServiceCompat() {
|
|||
}
|
||||
|
||||
if (p != null) {
|
||||
val oldPlayerType = p.playerType
|
||||
p.handleIntent(intent)
|
||||
p.handleIntentPost(oldPlayerType)
|
||||
p.UIs().get(MediaSessionPlayerUi::class)
|
||||
?.handleMediaButtonIntent(intent)
|
||||
}
|
||||
|
|
|
@ -1,32 +1,7 @@
|
|||
package org.schabi.newpipe.player;
|
||||
|
||||
import static org.schabi.newpipe.player.Player.PLAYER_TYPE;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
public enum PlayerType {
|
||||
MAIN,
|
||||
AUDIO,
|
||||
POPUP;
|
||||
|
||||
/**
|
||||
* @return an integer representing this {@link PlayerType}, to be used to save it in intents
|
||||
* @see #retrieveFromIntent(Intent) Use retrieveFromIntent() to retrieve and convert player type
|
||||
* integers from an intent
|
||||
*/
|
||||
public int valueForIntent() {
|
||||
return ordinal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param intent the intent to retrieve a player type from
|
||||
* @return the player type integer retrieved from the intent, converted back into a {@link
|
||||
* PlayerType}, or {@link PlayerType#MAIN} if there is no player type extra in the
|
||||
* intent
|
||||
* @throws ArrayIndexOutOfBoundsException if the intent contains an invalid player type integer
|
||||
* @see #valueForIntent() Use valueForIntent() to obtain valid player type integers
|
||||
*/
|
||||
public static PlayerType retrieveFromIntent(final Intent intent) {
|
||||
return values()[intent.getIntExtra(PLAYER_TYPE, MAIN.valueForIntent())];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
package org.schabi.newpipe.player.helper;
|
||||
|
||||
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 org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_WIFI;
|
||||
|
@ -25,7 +22,6 @@ import androidx.core.content.ContextCompat;
|
|||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player.RepeatMode;
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
||||
|
@ -410,23 +406,9 @@ public final class PlayerHelper {
|
|||
return singlePlayQueue;
|
||||
}
|
||||
|
||||
|
||||
// endregion
|
||||
// region Utils used by player
|
||||
|
||||
@RepeatMode
|
||||
public static int nextRepeatMode(@RepeatMode final int repeatMode) {
|
||||
switch (repeatMode) {
|
||||
case REPEAT_MODE_OFF:
|
||||
return REPEAT_MODE_ONE;
|
||||
case REPEAT_MODE_ONE:
|
||||
return REPEAT_MODE_ALL;
|
||||
case REPEAT_MODE_ALL:
|
||||
default:
|
||||
return REPEAT_MODE_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
@ResizeMode
|
||||
public static int retrieveResizeModeFromPrefs(final Player player) {
|
||||
return player.getPrefs().getInt(player.getContext().getString(R.string.last_resize_mode),
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.core.content.ContextCompat;
|
|||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.player.Player;
|
||||
import org.schabi.newpipe.player.PlayerIntentType;
|
||||
import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
|
@ -254,7 +255,9 @@ public final class NotificationUtil {
|
|||
} else {
|
||||
// We are playing in fragment. Don't open another activity just show fragment. That's it
|
||||
final Intent intent = NavigationHelper.getPlayerIntent(
|
||||
player.getContext(), MainActivity.class, null, true);
|
||||
player.getContext(), MainActivity.class, null,
|
||||
PlayerIntentType.AllOthers);
|
||||
intent.putExtra(Player.RESUME_PLAYBACK, true);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setAction(Intent.ACTION_MAIN);
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
|
|
|
@ -254,6 +254,22 @@ abstract class PlayQueue internal constructor(
|
|||
broadcast(AppendEvent(itemList.size))
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given item after the current stream.
|
||||
*
|
||||
* @param item item to add.
|
||||
* @param skipIfSame if set, skip adding if the next stream is the same stream.
|
||||
*/
|
||||
fun enqueueNext(item: PlayQueueItem, skipIfSame: Boolean) {
|
||||
val currentIndex = index
|
||||
// if the next item is the same item as the one we want to enqueue, skip if flag is true
|
||||
if (skipIfSame && item == getItem(currentIndex + 1)) {
|
||||
return
|
||||
}
|
||||
append(listOf(item))
|
||||
move(size() - 1, currentIndex + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the item at the given index from the play queue.
|
||||
*
|
||||
|
|
|
@ -16,7 +16,9 @@ public final class SinglePlayQueue extends PlayQueue {
|
|||
public SinglePlayQueue(final StreamInfo info) {
|
||||
super(0, List.of(new PlayQueueItem(info)));
|
||||
}
|
||||
|
||||
public SinglePlayQueue(final PlayQueueItem item) {
|
||||
super(0, List.of(item));
|
||||
}
|
||||
public SinglePlayQueue(final StreamInfo info, final long startPosition) {
|
||||
super(0, List.of(new PlayQueueItem(info)));
|
||||
getItem().setRecoveryPosition(startPosition);
|
||||
|
|
|
@ -58,8 +58,10 @@ import org.schabi.newpipe.local.subscription.SubscriptionFragment;
|
|||
import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment;
|
||||
import org.schabi.newpipe.player.PlayQueueActivity;
|
||||
import org.schabi.newpipe.player.Player;
|
||||
import org.schabi.newpipe.player.PlayerIntentType;
|
||||
import org.schabi.newpipe.player.PlayerService;
|
||||
import org.schabi.newpipe.player.PlayerType;
|
||||
import org.schabi.newpipe.player.TimestampChangeData;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
|
@ -69,6 +71,7 @@ import org.schabi.newpipe.settings.SettingsV2Activity;
|
|||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class NavigationHelper {
|
||||
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
|
||||
|
@ -87,54 +90,32 @@ public final class NavigationHelper {
|
|||
public static <T> Intent getPlayerIntent(@NonNull final Context context,
|
||||
@NonNull final Class<T> targetClazz,
|
||||
@Nullable final PlayQueue playQueue,
|
||||
final boolean resumePlayback) {
|
||||
final Intent intent = new Intent(context, targetClazz);
|
||||
|
||||
if (playQueue != null) {
|
||||
final String cacheKey = SerializedCache.getInstance().put(playQueue, PlayQueue.class);
|
||||
if (cacheKey != null) {
|
||||
intent.putExtra(Player.PLAY_QUEUE_KEY, cacheKey);
|
||||
}
|
||||
}
|
||||
intent.putExtra(Player.PLAYER_TYPE, PlayerType.MAIN.valueForIntent());
|
||||
intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
|
||||
intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true);
|
||||
|
||||
return intent;
|
||||
@NonNull final PlayerIntentType playerIntentType) {
|
||||
final String cacheKey = Optional.ofNullable(playQueue)
|
||||
.map(queue -> SerializedCache.getInstance().put(queue, PlayQueue.class))
|
||||
.orElse(null);
|
||||
return new Intent(context, targetClazz)
|
||||
.putExtra(Player.PLAY_QUEUE_KEY, cacheKey)
|
||||
.putExtra(Player.PLAYER_TYPE, PlayerType.MAIN)
|
||||
.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true)
|
||||
.putExtra(Player.PLAYER_INTENT_TYPE, playerIntentType);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T> Intent getPlayerIntent(@NonNull final Context context,
|
||||
@NonNull final Class<T> targetClazz,
|
||||
@Nullable final PlayQueue playQueue,
|
||||
final boolean resumePlayback,
|
||||
final boolean playWhenReady) {
|
||||
return getPlayerIntent(context, targetClazz, playQueue, resumePlayback)
|
||||
.putExtra(Player.PLAY_WHEN_READY, playWhenReady);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T> Intent getPlayerEnqueueIntent(@NonNull final Context context,
|
||||
@NonNull final Class<T> targetClazz,
|
||||
@Nullable final PlayQueue playQueue) {
|
||||
// when enqueueing `resumePlayback` is always `false` since:
|
||||
// - if there is a video already playing, the value of `resumePlayback` just doesn't make
|
||||
// any difference.
|
||||
// - if there is nothing already playing, it is useful for the enqueue action to have a
|
||||
// slightly different behaviour than the normal play action: the latter resumes playback,
|
||||
// the former doesn't. (note that enqueue can be triggered when nothing is playing only
|
||||
// by long pressing the video detail fragment, playlist or channel controls
|
||||
return getPlayerIntent(context, targetClazz, playQueue, false)
|
||||
.putExtra(Player.ENQUEUE, true);
|
||||
public static Intent getPlayerTimestampIntent(@NonNull final Context context,
|
||||
@NonNull final TimestampChangeData data) {
|
||||
return new Intent(context, PlayerService.class)
|
||||
.putExtra(Player.PLAYER_INTENT_TYPE, PlayerIntentType.TimestampChange)
|
||||
.putExtra(Player.PLAYER_INTENT_DATA, data);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T> Intent getPlayerEnqueueNextIntent(@NonNull final Context context,
|
||||
@NonNull final Class<T> targetClazz,
|
||||
@Nullable final PlayQueue playQueue) {
|
||||
return getPlayerIntent(context, targetClazz, playQueue, PlayerIntentType.EnqueueNext)
|
||||
// see comment in `getPlayerEnqueueIntent` as to why `resumePlayback` is false
|
||||
return getPlayerIntent(context, targetClazz, playQueue, false)
|
||||
.putExtra(Player.ENQUEUE_NEXT, true);
|
||||
.putExtra(Player.RESUME_PLAYBACK, false);
|
||||
}
|
||||
|
||||
/* PLAY */
|
||||
|
@ -168,8 +149,10 @@ public final class NavigationHelper {
|
|||
|
||||
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
|
||||
|
||||
final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback);
|
||||
intent.putExtra(Player.PLAYER_TYPE, PlayerType.POPUP.valueForIntent());
|
||||
final var intent = getPlayerIntent(context, PlayerService.class, queue,
|
||||
PlayerIntentType.AllOthers)
|
||||
.putExtra(Player.PLAYER_TYPE, PlayerType.POPUP)
|
||||
.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
}
|
||||
|
||||
|
@ -179,8 +162,10 @@ public final class NavigationHelper {
|
|||
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
|
||||
final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback);
|
||||
intent.putExtra(Player.PLAYER_TYPE, PlayerType.AUDIO.valueForIntent());
|
||||
final Intent intent = getPlayerIntent(context, PlayerService.class, queue,
|
||||
PlayerIntentType.AllOthers)
|
||||
.putExtra(Player.PLAYER_TYPE, PlayerType.AUDIO)
|
||||
.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
}
|
||||
|
||||
|
@ -193,9 +178,18 @@ public final class NavigationHelper {
|
|||
}
|
||||
|
||||
Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show();
|
||||
final Intent intent = getPlayerEnqueueIntent(context, PlayerService.class, queue);
|
||||
|
||||
intent.putExtra(Player.PLAYER_TYPE, playerType.valueForIntent());
|
||||
// when enqueueing `resumePlayback` is always `false` since:
|
||||
// - if there is a video already playing, the value of `resumePlayback` just doesn't make
|
||||
// any difference.
|
||||
// - if there is nothing already playing, it is useful for the enqueue action to have a
|
||||
// slightly different behaviour than the normal play action: the latter resumes playback,
|
||||
// the former doesn't. (note that enqueue can be triggered when nothing is playing only
|
||||
// by long pressing the video detail fragment, playlist or channel controls
|
||||
final Intent intent = getPlayerIntent(context, PlayerService.class, queue,
|
||||
PlayerIntentType.Enqueue)
|
||||
.putExtra(Player.RESUME_PLAYBACK, false)
|
||||
.putExtra(Player.PLAYER_TYPE, playerType);
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
}
|
||||
|
||||
|
@ -217,9 +211,8 @@ public final class NavigationHelper {
|
|||
playerType = PlayerType.AUDIO;
|
||||
}
|
||||
Toast.makeText(context, R.string.enqueued_next, Toast.LENGTH_SHORT).show();
|
||||
final Intent intent = getPlayerEnqueueNextIntent(context, PlayerService.class, queue);
|
||||
|
||||
intent.putExtra(Player.PLAYER_TYPE, playerType.valueForIntent());
|
||||
final Intent intent = getPlayerEnqueueNextIntent(context, PlayerService.class, queue)
|
||||
.putExtra(Player.PLAYER_TYPE, playerType);
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,63 +1,27 @@
|
|||
package org.schabi.newpipe.util.text;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.player.TimestampChangeData;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public final class InternalUrlsHandler {
|
||||
private static final String TAG = InternalUrlsHandler.class.getSimpleName();
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
||||
private static final Pattern AMPERSAND_TIMESTAMP_PATTERN = Pattern.compile("(.*)&t=(\\d+)");
|
||||
private static final Pattern HASHTAG_TIMESTAMP_PATTERN =
|
||||
Pattern.compile("(.*)#timestamp=(\\d+)");
|
||||
|
||||
private InternalUrlsHandler() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a YouTube timestamp comment URL in NewPipe.
|
||||
* <p>
|
||||
* This method will check if the provided url is a YouTube comment description URL ({@code
|
||||
* https://www.youtube.com/watch?v=}video_id{@code #timestamp=}time_in_seconds). If yes, the
|
||||
* popup player will be opened when the user will click on the timestamp in the comment,
|
||||
* at the time and for the video indicated in the timestamp.
|
||||
*
|
||||
* @param disposables a field of the Activity/Fragment class that calls this method
|
||||
* @param context the context to use
|
||||
* @param url the URL to check if it can be handled
|
||||
* @return true if the URL can be handled by NewPipe, false if it cannot
|
||||
*/
|
||||
public static boolean handleUrlCommentsTimestamp(@NonNull final CompositeDisposable
|
||||
disposables,
|
||||
final Context context,
|
||||
@NonNull final String url) {
|
||||
return handleUrl(context, url, HASHTAG_TIMESTAMP_PATTERN, disposables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a YouTube timestamp description URL in NewPipe.
|
||||
* <p>
|
||||
|
@ -66,36 +30,13 @@ public final class InternalUrlsHandler {
|
|||
* player will be opened when the user will click on the timestamp in the video description,
|
||||
* at the time and for the video indicated in the timestamp.
|
||||
*
|
||||
* @param disposables a field of the Activity/Fragment class that calls this method
|
||||
* @param context the context to use
|
||||
* @param url the URL to check if it can be handled
|
||||
* @return true if the URL can be handled by NewPipe, false if it cannot
|
||||
*/
|
||||
public static boolean handleUrlDescriptionTimestamp(@NonNull final CompositeDisposable
|
||||
disposables,
|
||||
final Context context,
|
||||
public static boolean handleUrlDescriptionTimestamp(final Context context,
|
||||
@NonNull final String url) {
|
||||
return handleUrl(context, url, AMPERSAND_TIMESTAMP_PATTERN, disposables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an URL in NewPipe.
|
||||
* <p>
|
||||
* This method will check if the provided url can be handled in NewPipe or not. If this is a
|
||||
* service URL with a timestamp, the popup player will be opened and true will be returned;
|
||||
* else, false will be returned.
|
||||
*
|
||||
* @param context the context to use
|
||||
* @param url the URL to check if it can be handled
|
||||
* @param pattern the pattern to use
|
||||
* @param disposables a field of the Activity/Fragment class that calls this method
|
||||
* @return true if the URL can be handled by NewPipe, false if it cannot
|
||||
*/
|
||||
private static boolean handleUrl(final Context context,
|
||||
@NonNull final String url,
|
||||
@NonNull final Pattern pattern,
|
||||
@NonNull final CompositeDisposable disposables) {
|
||||
final Matcher matcher = pattern.matcher(url);
|
||||
final Matcher matcher = AMPERSAND_TIMESTAMP_PATTERN.matcher(url);
|
||||
if (!matcher.matches()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -120,7 +61,7 @@ public final class InternalUrlsHandler {
|
|||
}
|
||||
|
||||
if (linkType == StreamingService.LinkType.STREAM && seconds != -1) {
|
||||
return playOnPopup(context, matchedUrl, service, seconds, disposables);
|
||||
return playOnPopup(context, matchedUrl, service, seconds);
|
||||
} else {
|
||||
NavigationHelper.openRouterActivity(context, matchedUrl);
|
||||
return true;
|
||||
|
@ -134,15 +75,12 @@ public final class InternalUrlsHandler {
|
|||
* @param url the URL of the content
|
||||
* @param service the service of the content
|
||||
* @param seconds the position in seconds at which the floating player will start
|
||||
* @param disposables disposables created by the method are added here and their lifecycle
|
||||
* should be handled by the calling class
|
||||
* @return true if the playback of the content has successfully started or false if not
|
||||
*/
|
||||
public static boolean playOnPopup(final Context context,
|
||||
final String url,
|
||||
@NonNull final StreamingService service,
|
||||
final int seconds,
|
||||
@NonNull final CompositeDisposable disposables) {
|
||||
final int seconds) {
|
||||
final LinkHandlerFactory factory = service.getStreamLHFactory();
|
||||
final String cleanUrl;
|
||||
|
||||
|
@ -152,19 +90,14 @@ public final class InternalUrlsHandler {
|
|||
return false;
|
||||
}
|
||||
|
||||
final Single<StreamInfo> single =
|
||||
ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false);
|
||||
disposables.add(single.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(info -> {
|
||||
final PlayQueue playQueue = new SinglePlayQueue(info, seconds * 1000L);
|
||||
NavigationHelper.playOnPopupPlayer(context, playQueue, false);
|
||||
}, throwable -> {
|
||||
// This will only show a snackbar if the passed context has a root view:
|
||||
// otherwise it will resort to showing a notification, so we are safe here.
|
||||
ErrorUtil.showSnackbar(context,
|
||||
new ErrorInfo(throwable, UserAction.PLAY_ON_POPUP, url, null, url));
|
||||
}));
|
||||
final Intent intent = NavigationHelper.getPlayerTimestampIntent(context,
|
||||
new TimestampChangeData(
|
||||
service.getServiceId(),
|
||||
cleanUrl,
|
||||
seconds
|
||||
));
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -192,7 +192,7 @@ public final class TextLinkifier {
|
|||
* <p>
|
||||
* Instead of using an {@link android.content.Intent#ACTION_VIEW} intent in the description of
|
||||
* a content, this method will parse the {@link CharSequence} and replace all current web links
|
||||
* with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
|
||||
* with {@link ShareUtils#openUrlInBrowser(Context, String)}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
|
@ -240,7 +240,7 @@ public final class TextLinkifier {
|
|||
for (final URLSpan span : urls) {
|
||||
final String url = span.getURL();
|
||||
final LongPressClickableSpan longPressClickableSpan =
|
||||
new UrlLongPressClickableSpan(context, disposables, url);
|
||||
new UrlLongPressClickableSpan(context, url);
|
||||
|
||||
textBlockLinked.setSpan(longPressClickableSpan,
|
||||
textBlockLinked.getSpanStart(span),
|
||||
|
|
|
@ -46,7 +46,7 @@ final class TimestampLongPressClickableSpan extends LongPressClickableSpan {
|
|||
@Override
|
||||
public void onClick(@NonNull final View view) {
|
||||
playOnPopup(context, relatedStreamUrl, relatedInfoService,
|
||||
timestampMatchDTO.seconds(), disposables);
|
||||
timestampMatchDTO.seconds());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -7,29 +7,22 @@ import androidx.annotation.NonNull;
|
|||
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
|
||||
final class UrlLongPressClickableSpan extends LongPressClickableSpan {
|
||||
|
||||
@NonNull
|
||||
private final Context context;
|
||||
@NonNull
|
||||
private final CompositeDisposable disposables;
|
||||
@NonNull
|
||||
private final String url;
|
||||
|
||||
UrlLongPressClickableSpan(@NonNull final Context context,
|
||||
@NonNull final CompositeDisposable disposables,
|
||||
@NonNull final String url) {
|
||||
this.context = context;
|
||||
this.disposables = disposables;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(@NonNull final View view) {
|
||||
if (!InternalUrlsHandler.handleUrlDescriptionTimestamp(
|
||||
disposables, context, url)) {
|
||||
if (!InternalUrlsHandler.handleUrlDescriptionTimestamp(context, url)) {
|
||||
ShareUtils.openUrlInApp(context, url);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue