Skip to content

Commit 8080c32

Browse files
authored
Merge pull request #6345 from Imericxu/test-and-update-playqueue
Test and clean up PlayQueue
2 parents 4b27aec + c0f4719 commit 8080c32

2 files changed

Lines changed: 243 additions & 44 deletions

File tree

app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java

Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -40,29 +40,25 @@
4040
*/
4141
public abstract class PlayQueue implements Serializable {
4242
public static final boolean DEBUG = MainActivity.DEBUG;
43-
44-
private ArrayList<PlayQueueItem> backup;
45-
private ArrayList<PlayQueueItem> streams;
46-
4743
@NonNull
4844
private final AtomicInteger queueIndex;
49-
private final ArrayList<PlayQueueItem> history;
45+
private final List<PlayQueueItem> history = new ArrayList<>();
46+
47+
private List<PlayQueueItem> backup;
48+
private List<PlayQueueItem> streams;
5049

5150
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
5251
private transient Flowable<PlayQueueEvent> broadcastReceiver;
53-
54-
private transient boolean disposed;
52+
private transient boolean disposed = false;
5553

5654
PlayQueue(final int index, final List<PlayQueueItem> startWith) {
57-
streams = new ArrayList<>();
58-
streams.addAll(startWith);
59-
history = new ArrayList<>();
55+
streams = new ArrayList<>(startWith);
56+
6057
if (streams.size() > index) {
6158
history.add(streams.get(index));
6259
}
6360

6461
queueIndex = new AtomicInteger(index);
65-
disposed = false;
6662
}
6763

6864
/*//////////////////////////////////////////////////////////////////////////
@@ -137,18 +133,36 @@ public int getIndex() {
137133
public synchronized void setIndex(final int index) {
138134
final int oldIndex = getIndex();
139135

140-
int newIndex = index;
136+
final int newIndex;
137+
141138
if (index < 0) {
142139
newIndex = 0;
140+
} else if (index < streams.size()) {
141+
// Regular assignment for index in bounds
142+
newIndex = index;
143+
} else if (streams.isEmpty()) {
144+
// Out of bounds from here on
145+
// Need to check if stream is empty to prevent arithmetic error and negative index
146+
newIndex = 0;
147+
} else if (isComplete()) {
148+
// Circular indexing
149+
newIndex = index % streams.size();
150+
} else {
151+
// Index of last element
152+
newIndex = streams.size() - 1;
143153
}
144-
if (index >= streams.size()) {
145-
newIndex = isComplete() ? index % streams.size() : streams.size() - 1;
146-
}
154+
155+
queueIndex.set(newIndex);
156+
147157
if (oldIndex != newIndex) {
148158
history.add(streams.get(newIndex));
149159
}
150160

151-
queueIndex.set(newIndex);
161+
/*
162+
TODO: Documentation states that a SelectEvent will only be emitted if the new index is...
163+
different from the old one but this is emitted regardless? Not sure what this what it does
164+
exactly so I won't touch it
165+
*/
152166
broadcast(new SelectEvent(oldIndex, newIndex));
153167
}
154168

@@ -180,8 +194,6 @@ public PlayQueueItem getItem(final int index) {
180194
* @return the index of the given item
181195
*/
182196
public int indexOf(@NonNull final PlayQueueItem item) {
183-
// referential equality, can't think of a better way to do this
184-
// todo: better than this
185197
return streams.indexOf(item);
186198
}
187199

@@ -410,34 +422,42 @@ public synchronized void unsetRecovery(final int index) {
410422
}
411423

412424
/**
413-
* Shuffles the current play queue.
425+
* Shuffles the current play queue
414426
* <p>
415-
* This method first backs up the existing play queue and item being played.
416-
* Then a newly shuffled play queue will be generated along with currently
417-
* playing item placed at the beginning of the queue.
427+
* This method first backs up the existing play queue and item being played. Then a newly
428+
* shuffled play queue will be generated along with currently playing item placed at the
429+
* beginning of the queue. This item will also be added to the history.
418430
* </p>
419431
* <p>
420-
* Will emit a {@link ReorderEvent} in any context.
432+
* Will emit a {@link ReorderEvent} if shuffled.
421433
* </p>
434+
*
435+
* @implNote Does nothing if the queue has a size <= 2 (the currently playing video must stay on
436+
* top, so shuffling a size-2 list does nothing)
422437
*/
423438
public synchronized void shuffle() {
439+
// Can't shuffle an list that's empty or only has one element
440+
if (size() <= 2) {
441+
return;
442+
}
443+
// Create a backup if it doesn't already exist
424444
if (backup == null) {
425445
backup = new ArrayList<>(streams);
426446
}
427-
final int originIndex = getIndex();
428-
final PlayQueueItem current = getItem();
447+
448+
final int originalIndex = getIndex();
449+
final PlayQueueItem currentItem = getItem();
450+
429451
Collections.shuffle(streams);
430452

431-
final int newIndex = streams.indexOf(current);
432-
if (newIndex != -1) {
433-
streams.add(0, streams.remove(newIndex));
434-
}
453+
// Move currentItem to the head of the queue
454+
streams.remove(currentItem);
455+
streams.add(0, currentItem);
435456
queueIndex.set(0);
436-
if (streams.size() > 0) {
437-
history.add(streams.get(0));
438-
}
439457

440-
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
458+
history.add(currentItem);
459+
460+
broadcast(new ReorderEvent(originalIndex, 0));
441461
}
442462

443463
/**
@@ -457,7 +477,6 @@ public synchronized void unshuffle() {
457477
final int originIndex = getIndex();
458478
final PlayQueueItem current = getItem();
459479

460-
streams.clear();
461480
streams = backup;
462481
backup = null;
463482

@@ -500,22 +519,19 @@ public synchronized boolean previous() {
500519
* we don't have to do anything with new queue.
501520
* This method also gives a chance to track history of items in a queue in
502521
* VideoDetailFragment without duplicating items from two identical queues
503-
* */
522+
*/
504523
@Override
505524
public boolean equals(@Nullable final Object obj) {
506-
if (!(obj instanceof PlayQueue)
507-
|| getStreams().size() != ((PlayQueue) obj).getStreams().size()) {
525+
if (!(obj instanceof PlayQueue)) {
508526
return false;
509527
}
510-
511528
final PlayQueue other = (PlayQueue) obj;
512-
for (int i = 0; i < getStreams().size(); i++) {
513-
if (!getItem(i).getUrl().equals(other.getItem(i).getUrl())) {
514-
return false;
515-
}
516-
}
529+
return streams.equals(other.streams);
530+
}
517531

518-
return true;
532+
@Override
533+
public int hashCode() {
534+
return streams.hashCode();
519535
}
520536

521537
public boolean isDisposed() {
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package org.schabi.newpipe.player.playqueue;
2+
3+
import org.junit.Before;
4+
import org.junit.BeforeClass;
5+
import org.junit.Test;
6+
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
7+
import org.schabi.newpipe.extractor.stream.StreamType;
8+
9+
import java.util.ArrayList;
10+
import java.util.Collections;
11+
import java.util.List;
12+
import java.util.Objects;
13+
14+
import static org.junit.Assert.assertEquals;
15+
import static org.junit.Assert.assertFalse;
16+
import static org.junit.Assert.assertNotEquals;
17+
import static org.junit.Assert.assertNull;
18+
import static org.junit.Assert.assertTrue;
19+
import static org.mockito.Mockito.doReturn;
20+
import static org.mockito.Mockito.spy;
21+
22+
@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
23+
public class PlayQueueTest {
24+
static PlayQueue makePlayQueue(final int index, final List<PlayQueueItem> streams) {
25+
// I tried using Mockito, but it didn't work for some reason
26+
return new PlayQueue(index, streams) {
27+
@Override
28+
public boolean isComplete() {
29+
throw new UnsupportedOperationException();
30+
}
31+
32+
@Override
33+
public void fetch() {
34+
throw new UnsupportedOperationException();
35+
}
36+
};
37+
}
38+
39+
static PlayQueueItem makeItemWithUrl(final String url) {
40+
final StreamInfoItem infoItem = new StreamInfoItem(
41+
0, url, "", StreamType.VIDEO_STREAM
42+
);
43+
return new PlayQueueItem(infoItem);
44+
}
45+
46+
public static class SetIndexTests {
47+
private static final int SIZE = 5;
48+
private PlayQueue nonEmptyQueue;
49+
private PlayQueue emptyQueue;
50+
51+
@Before
52+
public void setup() {
53+
final List<PlayQueueItem> streams = new ArrayList<>(5);
54+
for (int i = 0; i < 5; ++i) {
55+
streams.add(makeItemWithUrl("URL_" + i));
56+
}
57+
nonEmptyQueue = spy(makePlayQueue(0, streams));
58+
emptyQueue = spy(makePlayQueue(0, new ArrayList<>()));
59+
}
60+
61+
@Test
62+
public void negative() {
63+
nonEmptyQueue.setIndex(-5);
64+
assertEquals(0, nonEmptyQueue.getIndex());
65+
66+
emptyQueue.setIndex(-5);
67+
assertEquals(0, nonEmptyQueue.getIndex());
68+
}
69+
70+
@Test
71+
public void inBounds() {
72+
nonEmptyQueue.setIndex(2);
73+
assertEquals(2, nonEmptyQueue.getIndex());
74+
75+
// emptyQueue not tested because 0 isn't technically inBounds
76+
}
77+
78+
@Test
79+
public void outOfBoundIsComplete() {
80+
doReturn(true).when(nonEmptyQueue).isComplete();
81+
nonEmptyQueue.setIndex(7);
82+
assertEquals(2, nonEmptyQueue.getIndex());
83+
84+
doReturn(true).when(emptyQueue).isComplete();
85+
emptyQueue.setIndex(2);
86+
assertEquals(0, emptyQueue.getIndex());
87+
}
88+
89+
@Test
90+
public void outOfBoundsNotComplete() {
91+
doReturn(false).when(nonEmptyQueue).isComplete();
92+
nonEmptyQueue.setIndex(7);
93+
assertEquals(SIZE - 1, nonEmptyQueue.getIndex());
94+
95+
doReturn(false).when(emptyQueue).isComplete();
96+
emptyQueue.setIndex(2);
97+
assertEquals(0, emptyQueue.getIndex());
98+
}
99+
100+
@Test
101+
public void indexZero() {
102+
nonEmptyQueue.setIndex(0);
103+
assertEquals(0, nonEmptyQueue.getIndex());
104+
105+
doReturn(true).when(emptyQueue).isComplete();
106+
emptyQueue.setIndex(0);
107+
assertEquals(0, emptyQueue.getIndex());
108+
109+
doReturn(false).when(emptyQueue).isComplete();
110+
emptyQueue.setIndex(0);
111+
assertEquals(0, emptyQueue.getIndex());
112+
}
113+
114+
@Test
115+
public void addToHistory() {
116+
nonEmptyQueue.setIndex(0);
117+
assertFalse(nonEmptyQueue.previous());
118+
119+
nonEmptyQueue.setIndex(3);
120+
assertTrue(nonEmptyQueue.previous());
121+
assertEquals("URL_0", Objects.requireNonNull(nonEmptyQueue.getItem()).getUrl());
122+
}
123+
}
124+
125+
public static class GetItemTests {
126+
private static List<PlayQueueItem> streams;
127+
private PlayQueue queue;
128+
129+
@BeforeClass
130+
public static void init() {
131+
streams = new ArrayList<>(Collections.nCopies(5, makeItemWithUrl("OTHER_URL")));
132+
streams.set(3, makeItemWithUrl("TARGET_URL"));
133+
}
134+
135+
@Before
136+
public void setup() {
137+
queue = makePlayQueue(0, streams);
138+
}
139+
140+
@Test
141+
public void inBounds() {
142+
assertEquals("TARGET_URL", Objects.requireNonNull(queue.getItem(3)).getUrl());
143+
assertEquals("OTHER_URL", Objects.requireNonNull(queue.getItem(1)).getUrl());
144+
}
145+
146+
@Test
147+
public void outOfBounds() {
148+
assertNull(queue.getItem(-1));
149+
assertNull(queue.getItem(5));
150+
}
151+
}
152+
153+
public static class EqualsTests {
154+
private final PlayQueueItem item1 = makeItemWithUrl("URL_1");
155+
private final PlayQueueItem item2 = makeItemWithUrl("URL_2");
156+
157+
@Test
158+
public void sameStreams() {
159+
final List<PlayQueueItem> streams = Collections.nCopies(5, item1);
160+
final PlayQueue queue1 = makePlayQueue(0, streams);
161+
final PlayQueue queue2 = makePlayQueue(0, streams);
162+
assertEquals(queue1, queue2);
163+
}
164+
165+
@Test
166+
public void sameSizeDifferentItems() {
167+
final List<PlayQueueItem> streams1 = Collections.nCopies(5, item1);
168+
final List<PlayQueueItem> streams2 = Collections.nCopies(5, item2);
169+
final PlayQueue queue1 = makePlayQueue(0, streams1);
170+
final PlayQueue queue2 = makePlayQueue(0, streams2);
171+
assertNotEquals(queue1, queue2);
172+
}
173+
174+
@Test
175+
public void differentSizeStreams() {
176+
final List<PlayQueueItem> streams1 = Collections.nCopies(5, item1);
177+
final List<PlayQueueItem> streams2 = Collections.nCopies(6, item2);
178+
final PlayQueue queue1 = makePlayQueue(0, streams1);
179+
final PlayQueue queue2 = makePlayQueue(0, streams2);
180+
assertNotEquals(queue1, queue2);
181+
}
182+
}
183+
}

0 commit comments

Comments
 (0)