From 2339f51ad4707367a98c844840dd4f5f7295675f Mon Sep 17 00:00:00 2001 From: "Thiago F. G. Albuquerque" Date: Fri, 14 Feb 2025 21:14:42 -0300 Subject: [PATCH] [#11930] Share as YouTube temporary playlist Initial commit. --- .../local/playlist/LocalPlaylistFragment.java | 100 +++++++++++++----- .../local/playlist/PlayListShareMode.java | 8 ++ .../playlist/LocalPlaylistFragmentTest.java | 55 ++++++++++ 3 files changed, 138 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java create mode 100644 app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index c87d9cccc..3e99b01c4 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -2,6 +2,9 @@ package org.schabi.newpipe.local.playlist; import static org.schabi.newpipe.error.ErrorUtil.showUiErrorSnackbar; import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS; +import static org.schabi.newpipe.local.playlist.PlayListShareMode.WITH_TITLES; +import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST; import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout; import android.content.Context; @@ -64,12 +67,14 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; +import java.util.stream.Stream; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; +import okhttp3.HttpUrl; public class LocalPlaylistFragment extends BaseLocalListFragment, Void> implements PlaylistControlViewHolder, DebounceSavable { @@ -385,34 +390,76 @@ public class LocalPlaylistFragment extends BaseLocalListFragment Single.just(playlist.stream() - .map(PlaylistStreamEntry::getStreamEntity) - .map(streamEntity -> { - if (shouldSharePlaylistDetails) { - return context.getString(R.string.video_details_list_item, - streamEntity.getTitle(), streamEntity.getUrl()); - } else { - return streamEntity.getUrl(); - } - }) - .collect(Collectors.joining("\n")))) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(urlsText -> ShareUtils.shareText( - context, name, shouldSharePlaylistDetails - ? context.getString(R.string.share_playlist_content_details, - name, urlsText) : urlsText), - throwable -> showUiErrorSnackbar(this, "Sharing playlist", throwable))); + .flatMapSingle(playlist -> Single.just(export( shareMode + , playlist.stream().map(PlaylistStreamEntry::getStreamEntity) + , context + ) + )) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( urlsText -> ShareUtils.shareText( context + , name + , shareMode == JUST_URLS ? urlsText + : context.getString(R.string.share_playlist_content_details, name, urlsText)) + , throwable -> showUiErrorSnackbar(this, "Sharing playlist", throwable)) + ); + } + + static String export(PlayListShareMode shareMode, Stream entityStream, Context context) { + + return switch(shareMode) { + + case WITH_TITLES -> exportWithTitles(entityStream, context); + case JUST_URLS -> exportJustUrls(entityStream); + case YOUTUBE_TEMP_PLAYLIST -> exportAsYoutubeTempPlaylist(entityStream); + }; + } + + static String exportWithTitles(Stream entityStream, Context context) { + + return entityStream + .map(entity -> context.getString(R.string.video_details_list_item, entity.getTitle(), entity.getUrl())) + .collect(Collectors.joining("\n")); + } + + static String exportJustUrls(Stream entityStream) { + + return entityStream + .map(StreamEntity::getUrl) + .collect(Collectors.joining("\n")); + } + + static String exportAsYoutubeTempPlaylist(Stream entityStream) { + + String videoIDs = entityStream + .map(entity -> getYouTubeId(entity.getUrl())) + .collect(Collectors.joining(",")); + + return "http://www.youtube.com/watch_videos?video_ids=" + videoIDs; + } + + /** + * Gets the video id from a YouTube URL + */ + static String getYouTubeId(String url) { + + HttpUrl httpUrl = HttpUrl.parse(url); + + return httpUrl == null ? null + : httpUrl.queryParameter("v") + ; } public void removeWatchedStreams(final boolean removePartiallyWatched) { @@ -875,10 +922,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment - sharePlaylist(/* shouldSharePlaylistDetails= */ true) + sharePlaylist(WITH_TITLES) + ) + .setNeutralButton("Share as YouTube temporary playlist", (dialog, which) -> // TODO R.string.share_playlist_as_YouTube_temporary_playlist + sharePlaylist(YOUTUBE_TEMP_PLAYLIST) ) .setNegativeButton(R.string.share_playlist_with_list, (dialog, which) -> - sharePlaylist(/* shouldSharePlaylistDetails= */ false) + sharePlaylist(JUST_URLS) ) .show(); } diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java b/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java new file mode 100644 index 000000000..3de1effc9 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/PlayListShareMode.java @@ -0,0 +1,8 @@ +package org.schabi.newpipe.local.playlist; + +public enum PlayListShareMode { + + JUST_URLS + ,WITH_TITLES + ,YOUTUBE_TEMP_PLAYLIST +} diff --git a/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java new file mode 100644 index 000000000..41e1f6091 --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragmentTest.java @@ -0,0 +1,55 @@ +package org.schabi.newpipe.local.playlist; + +import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST; + +import androidx.annotation.NonNull; + +import org.junit.Assert; +import org.junit.Test; +import org.schabi.newpipe.database.stream.model.StreamEntity; +import org.schabi.newpipe.extractor.stream.StreamType; + +import java.util.List; +import java.util.stream.Stream; + +public class LocalPlaylistFragmentTest { + + @Test + public void youTubeTempPlaylist() { + + Stream entityStream = List.of( + + "https://www.youtube.com/watch?v=1" + ,"https://www.youtube.com/watch?v=2" + ,"https://www.youtube.com/watch?v=3" + ) + .stream() + .map(LocalPlaylistFragmentTest::newStreamEntity) + ; + + String url = LocalPlaylistFragment.export(YOUTUBE_TEMP_PLAYLIST, entityStream, null); + + Assert.assertEquals("http://www.youtube.com/watch_videos?video_ids=1,2,3", url); + } + + @NonNull + static StreamEntity newStreamEntity(String url) { + + return new StreamEntity( + + 0 + , 1 + , url + , "Title" + , StreamType.VIDEO_STREAM + , 100 + , "Uploader" + , null + , null + , null + , null + , null + , null + ); + } +}