diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java b/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java index 5c827dfe9..8680b2d2e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java @@ -104,13 +104,10 @@ public class QueueSorter { * 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 at the end of the queue may be - * unavoidable. This seems unlikely to be an issue for most users who presumably maintain - * large queues with new episodes continuously being added. + * The Smart Shuffle algorithm involves spreading episodes from each feed out over the whole + * queue. To do this, we calculate the number of episodes in each feed, then a common multiple + * (not the smallest); each episode is then spread out, and we sort the resulting list of + * episodes by "spread out factor" and feed name. * * 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: @@ -152,8 +149,17 @@ public class QueueSorter { ? (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); + // Calculate the spread + + long spread = 0; + for (Map.Entry> mapEntry : map.entrySet()) { + List feedItems = mapEntry.getValue(); + Collections.sort(feedItems, itemComparator); + if (spread == 0) { + spread = feedItems.size(); + } else if (feedItems.size() % spread != 0){ + spread *= feedItems.size(); + } } // Create a list of the individual FeedItems lists, and sort it by feed title (ascending). @@ -161,25 +167,29 @@ public class QueueSorter { List> 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())); + (f1, f2) -> f1.get(0).getFeed().getTitle().compareTo(f2.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 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); + // Spread each episode out + Map> spreadItems = new HashMap<>(); + for (List feedItems : feeds) { + long thisSpread = spread / feedItems.size(); + // Starting from 0 ensures we front-load, so the queue starts with one episode from + // each feed in the queue + long itemSpread = 0; + for (FeedItem feedItem : feedItems) { + if (!spreadItems.containsKey(itemSpread)) { + spreadItems.put(itemSpread, new ArrayList<>()); } + spreadItems.get(itemSpread).add(feedItem); + itemSpread += thisSpread; } } + + // Go through the spread items and add them to the queue + List spreads = new ArrayList<>(spreadItems.keySet()); + Collections.sort(spreads); + for (long itemSpread : spreads) { + queue.addAll(spreadItems.get(itemSpread)); + } } }