@@ -96,8 +96,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
9696 private AtomicBoolean isLoadingComplete ;
9797 /* Has the playlist been modified (e.g. items reordered or deleted) */
9898 private AtomicBoolean isModified ;
99- /* Is the playlist currently being processed to remove watched videos */
100- private boolean isRemovingWatched = false ;
99+ /* Flag to prevent simultaneous rewrites of the playlist */
100+ private boolean isRewritingPlaylist = false ;
101101
102102 public static LocalPlaylistFragment getInstance (final long playlistId , final String name ) {
103103 final LocalPlaylistFragment instance = new LocalPlaylistFragment ();
@@ -354,7 +354,7 @@ public boolean onOptionsItemSelected(final MenuItem item) {
354354 } else if (item .getItemId () == R .id .menu_item_rename_playlist ) {
355355 createRenameDialog ();
356356 } else if (item .getItemId () == R .id .menu_item_remove_watched ) {
357- if (!isRemovingWatched ) {
357+ if (!isRewritingPlaylist ) {
358358 new AlertDialog .Builder (requireContext ())
359359 .setMessage (R .string .remove_watched_popup_warning )
360360 .setTitle (R .string .remove_watched_popup_title )
@@ -368,6 +368,10 @@ public boolean onOptionsItemSelected(final MenuItem item) {
368368 .create ()
369369 .show ();
370370 }
371+ } else if (item .getItemId () == R .id .menu_item_remove_duplicates ) {
372+ if (!isRewritingPlaylist ) {
373+ openRemoveDuplicatesDialog ();
374+ }
371375 } else {
372376 return super .onOptionsItemSelected (item );
373377 }
@@ -389,10 +393,10 @@ public void sharePlaylist() {
389393 }
390394
391395 public void removeWatchedStreams (final boolean removePartiallyWatched ) {
392- if (isRemovingWatched ) {
396+ if (isRewritingPlaylist ) {
393397 return ;
394398 }
395- isRemovingWatched = true ;
399+ isRewritingPlaylist = true ;
396400 showLoading ();
397401
398402 final var recordManager = new HistoryRecordManager (getContext ());
@@ -470,7 +474,7 @@ public void removeWatchedStreams(final boolean removePartiallyWatched) {
470474 }
471475
472476 hideLoading ();
473- isRemovingWatched = false ;
477+ isRewritingPlaylist = false ;
474478 }, throwable -> showError (new ErrorInfo (throwable , UserAction .REQUESTED_BOOKMARK ,
475479 "Removing watched videos, partially watched=" + removePartiallyWatched ))));
476480 }
@@ -629,6 +633,43 @@ private void updateThumbnailUrl() {
629633 changeThumbnailStreamId (thumbnailStreamId , false );
630634 }
631635
636+ private void openRemoveDuplicatesDialog () {
637+ final AlertDialog .Builder builder = new AlertDialog .Builder (this .getActivity ());
638+
639+ builder .setTitle (R .string .remove_duplicates_title )
640+ .setMessage (R .string .remove_duplicates_message )
641+ .setPositiveButton (R .string .ok ,
642+ (dialog , i ) -> removeDuplicatesInPlaylist ())
643+ .setNeutralButton (R .string .cancel , null );
644+
645+ builder .create ().show ();
646+ }
647+
648+ private void removeDuplicatesInPlaylist () {
649+ if (isRewritingPlaylist ) {
650+ return ;
651+ }
652+ isRewritingPlaylist = true ;
653+ showLoading ();
654+
655+ final var streamsMaybe = playlistManager
656+ .getDistinctPlaylistStreams (playlistId ).firstElement ();
657+
658+
659+ disposables .add (streamsMaybe .subscribeOn (Schedulers .io ())
660+ .observeOn (AndroidSchedulers .mainThread ())
661+ .subscribe (itemsToKeep -> {
662+ itemListAdapter .clearStreamItemList ();
663+ itemListAdapter .addItems (itemsToKeep );
664+ setVideoCount (itemListAdapter .getItemsList ().size ());
665+ saveChanges ();
666+
667+ hideLoading ();
668+ isRewritingPlaylist = false ;
669+ }, throwable -> showError (new ErrorInfo (throwable , UserAction .REQUESTED_BOOKMARK ,
670+ "Removing duplicated streams" ))));
671+ }
672+
632673 private void deleteItem (final PlaylistStreamEntry item ) {
633674 if (itemListAdapter == null ) {
634675 return ;
0 commit comments