Merge pull request #2659 from mr-intj/2366-pubdate-respecting-smart-shuffle
Added "Random" and "Smart Shuffle"
This commit is contained in:
commit
e16a111a12
@ -327,6 +327,15 @@ public class QueueFragment extends Fragment {
|
||||
case R.id.queue_sort_feed_title_desc:
|
||||
QueueSorter.sort(getActivity(), QueueSorter.Rule.FEED_TITLE_DESC, true);
|
||||
return true;
|
||||
case R.id.queue_sort_random:
|
||||
QueueSorter.sort(getActivity(), QueueSorter.Rule.RANDOM, true);
|
||||
return true;
|
||||
case R.id.queue_sort_smart_shuffle_asc:
|
||||
QueueSorter.sort(getActivity(), QueueSorter.Rule.SMART_SHUFFLE_ASC, true);
|
||||
return true;
|
||||
case R.id.queue_sort_smart_shuffle_desc:
|
||||
QueueSorter.sort(getActivity(), QueueSorter.Rule.SMART_SHUFFLE_DESC, true);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -90,6 +90,25 @@
|
||||
android:title="@string/descending"/>
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/queue_sort_random"
|
||||
android:title="@string/random">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/queue_sort_smart_shuffle"
|
||||
android:title="@string/smart_shuffle">
|
||||
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/queue_sort_smart_shuffle_asc"
|
||||
android:title="@string/ascending"/>
|
||||
<item
|
||||
android:id="@+id/queue_sort_smart_shuffle_desc"
|
||||
android:title="@string/descending"/>
|
||||
</menu>
|
||||
</item>
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
|
@ -43,6 +43,7 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.util.LongList;
|
||||
import de.danoeh.antennapod.core.util.Permutor;
|
||||
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
|
||||
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
|
||||
import de.danoeh.antennapod.core.util.flattr.SimpleFlattrThing;
|
||||
@ -991,6 +992,32 @@ public class DBWriter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to sortQueue, but allows more complex reordering by providing whole-queue context.
|
||||
* @param permutor Encapsulates whole-Queue reordering logic.
|
||||
* @param broadcastUpdate <code>true</code> if this operation should trigger a
|
||||
* QueueUpdateBroadcast. This option should be set to <code>false</code>
|
||||
* if the caller wants to avoid unexpected updates of the GUI.
|
||||
*/
|
||||
public static Future<?> reorderQueue(final Permutor<FeedItem> permutor, final boolean broadcastUpdate) {
|
||||
return dbExec.submit(() -> {
|
||||
final PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
final List<FeedItem> queue = DBReader.getQueue(adapter);
|
||||
|
||||
if (queue != null) {
|
||||
permutor.reorder(queue);
|
||||
adapter.setQueue(queue);
|
||||
if (broadcastUpdate) {
|
||||
EventBus.getDefault().post(QueueEvent.sorted(queue));
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "reorderQueue: Could not load queue");
|
||||
}
|
||||
adapter.close();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the 'auto_download'-attribute of specific FeedItem.
|
||||
*
|
||||
|
@ -0,0 +1,17 @@
|
||||
package de.danoeh.antennapod.core.util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface for passing around list permutor method. This is used for cases where a simple comparator
|
||||
* won't work (e.g. Random, Smart Shuffle, etc).
|
||||
*
|
||||
* @param <E> the type of elements in the list
|
||||
*/
|
||||
public interface Permutor<E> {
|
||||
/**
|
||||
* Reorders the specified list.
|
||||
* @param queue A (modifiable) list of elements to be reordered
|
||||
*/
|
||||
void reorder(List<E> queue);
|
||||
}
|
@ -2,7 +2,12 @@ package de.danoeh.antennapod.core.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
@ -20,11 +25,15 @@ public class QueueSorter {
|
||||
DURATION_ASC,
|
||||
DURATION_DESC,
|
||||
FEED_TITLE_ASC,
|
||||
FEED_TITLE_DESC
|
||||
FEED_TITLE_DESC,
|
||||
RANDOM,
|
||||
SMART_SHUFFLE_ASC,
|
||||
SMART_SHUFFLE_DESC
|
||||
}
|
||||
|
||||
public static void sort(final Context context, final Rule rule, final boolean broadcastUpdate) {
|
||||
Comparator<FeedItem> comparator = null;
|
||||
Permutor<FeedItem> permutor = null;
|
||||
|
||||
switch (rule) {
|
||||
case EPISODE_TITLE_ASC:
|
||||
@ -68,11 +77,109 @@ public class QueueSorter {
|
||||
case FEED_TITLE_DESC:
|
||||
comparator = (f1, f2) -> f2.getFeed().getTitle().compareTo(f1.getFeed().getTitle());
|
||||
break;
|
||||
case RANDOM:
|
||||
permutor = Collections::shuffle;
|
||||
break;
|
||||
case SMART_SHUFFLE_ASC:
|
||||
permutor = (queue) -> smartShuffle(queue, true);
|
||||
break;
|
||||
case SMART_SHUFFLE_DESC:
|
||||
permutor = (queue) -> smartShuffle(queue, false);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
if (comparator != null) {
|
||||
DBWriter.sortQueue(comparator, broadcastUpdate);
|
||||
} else if (permutor != null) {
|
||||
DBWriter.reorderQueue(permutor, broadcastUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a reordering by pubdate that avoids consecutive episodes from the same feed in
|
||||
* the queue.
|
||||
*
|
||||
* A listener might want to hear episodes from any given feed in pubdate order, but would
|
||||
* prefer a more balanced ordering that avoids having to listen to clusters of consecutive
|
||||
* episodes from the same feed. This is what "Smart Shuffle" tries to accomplish.
|
||||
*
|
||||
* The Smart Shuffle algorithm involves choosing episodes (in round-robin fashion) from a
|
||||
* collection of individual, pubdate-sorted lists that each contain only items from a specific
|
||||
* feed.
|
||||
*
|
||||
* Of course, clusters of consecutive episodes <i>at the end of the queue</i> may be
|
||||
* unavoidable. This seems unlikely to be an issue for most users who presumably maintain
|
||||
* large queues with new episodes continuously being added.
|
||||
*
|
||||
* For example, given a queue containing three episodes each from three different feeds
|
||||
* (A, B, and C), a simple pubdate sort might result in a queue that looks like the following:
|
||||
*
|
||||
* B1, B2, B3, A1, A2, C1, C2, C3, A3
|
||||
*
|
||||
* (note that feed B episodes were all published before the first feed A episode, so a simple
|
||||
* pubdate sort will often result in significant clustering of episodes from a single feed)
|
||||
*
|
||||
* Using Smart Shuffle, the resulting queue would look like the following:
|
||||
*
|
||||
* A1, B1, C1, A2, B2, C2, A3, B3, C3
|
||||
*
|
||||
* (note that episodes above <i>aren't strictly ordered in terms of pubdate</i>, but episodes
|
||||
* of each feed <b>do</b> appear in pubdate order)
|
||||
*
|
||||
* @param queue A (modifiable) list of FeedItem elements to be reordered.
|
||||
* @param ascending {@code true} to use ascending pubdate in the reordering;
|
||||
* {@code false} for descending.
|
||||
*/
|
||||
private static void smartShuffle(List<FeedItem> queue, boolean ascending) {
|
||||
|
||||
// Divide FeedItems into lists by feed
|
||||
|
||||
Map<Long, List<FeedItem>> map = new HashMap<>();
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
FeedItem item = queue.remove(0);
|
||||
Long id = item.getFeedId();
|
||||
if (!map.containsKey(id)) {
|
||||
map.put(id, new ArrayList<>());
|
||||
}
|
||||
map.get(id).add(item);
|
||||
}
|
||||
|
||||
// Sort each individual list by PubDate (ascending/descending)
|
||||
|
||||
Comparator<FeedItem> itemComparator = ascending
|
||||
? (f1, f2) -> f1.getPubDate().compareTo(f2.getPubDate())
|
||||
: (f1, f2) -> f2.getPubDate().compareTo(f1.getPubDate());
|
||||
|
||||
for (Long id : map.keySet()) {
|
||||
Collections.sort(map.get(id), itemComparator);
|
||||
}
|
||||
|
||||
// Create a list of the individual FeedItems lists, and sort it by feed title (ascending).
|
||||
// Doing this ensures that the feed order we use is predictable/deterministic.
|
||||
|
||||
List<List<FeedItem>> feeds = new ArrayList<>(map.values());
|
||||
Collections.sort(feeds,
|
||||
// (we use a desc sort here, since we're iterating back-to-front below)
|
||||
(f1, f2) -> f2.get(0).getFeed().getTitle().compareTo(f1.get(0).getFeed().getTitle()));
|
||||
|
||||
// Cycle through the (sorted) feed lists in a round-robin fashion, removing the first item
|
||||
// and adding it back into to the original queue
|
||||
|
||||
while (!feeds.isEmpty()) {
|
||||
// Iterate across the (sorted) list of feeds, removing the first item in each, and
|
||||
// appending it to the queue. Note that we're iterating back-to-front here, since we
|
||||
// will be deleting feed lists as they become empty.
|
||||
for (int i = feeds.size() - 1; i >= 0; --i) {
|
||||
List<FeedItem> items = feeds.get(i);
|
||||
queue.add(items.remove(0));
|
||||
// Removed the last item in this particular feed? Then remove this feed from the
|
||||
// list of feeds.
|
||||
if (items.isEmpty()) {
|
||||
feeds.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -260,6 +260,8 @@
|
||||
<string name="duration">Duration</string>
|
||||
<string name="episode_title">Episode title</string>
|
||||
<string name="feed_title">Feed title</string>
|
||||
<string name="random">Random</string>
|
||||
<string name="smart_shuffle">Smart Shuffle</string>
|
||||
<string name="ascending">Ascending</string>
|
||||
<string name="descending">Descending</string>
|
||||
<string name="clear_queue_confirmation_msg">Please confirm that you want to clear the queue of ALL of the episodes in it</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user