Control over shuffle order.

This commit is contained in:
Ryan Heise 2020-12-11 18:04:33 +11:00
parent a864c2f87d
commit e92fd8c863
23 changed files with 703 additions and 296 deletions

View File

@ -80,8 +80,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud
private Integer audioSessionId; private Integer audioSessionId;
private MediaSource mediaSource; private MediaSource mediaSource;
private Integer currentIndex; private Integer currentIndex;
private Map<LoopingMediaSource, MediaSource> loopingChildren = new HashMap<>();
private Map<LoopingMediaSource, Integer> loopingCounts = new HashMap<>();
private final Handler handler = new Handler(); private final Handler handler = new Handler();
private final Runnable bufferWatcher = new Runnable() { private final Runnable bufferWatcher = new Runnable() {
@Override @Override
@ -321,6 +319,10 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud
setShuffleModeEnabled((Integer) request.get("shuffleMode") == 1); setShuffleModeEnabled((Integer) request.get("shuffleMode") == 1);
result.success(new HashMap<String, Object>()); result.success(new HashMap<String, Object>());
break; break;
case "setShuffleOrder":
setShuffleOrder(request.get("audioSource"));
result.success(new HashMap<String, Object>());
break;
case "setAutomaticallyWaitsToMinimizeStalling": case "setAutomaticallyWaitsToMinimizeStalling":
result.success(new HashMap<String, Object>()); result.success(new HashMap<String, Object>());
break; break;
@ -332,14 +334,20 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud
case "concatenatingInsertAll": case "concatenatingInsertAll":
concatenating(request.get("id")) concatenating(request.get("id"))
.addMediaSources((Integer)request.get("index"), getAudioSources(request.get("children")), handler, () -> result.success(new HashMap<String, Object>())); .addMediaSources((Integer)request.get("index"), getAudioSources(request.get("children")), handler, () -> result.success(new HashMap<String, Object>()));
concatenating(request.get("id"))
.setShuffleOrder(decodeShuffleOrder((List<Integer>)request.get("shuffleOrder")));
break; break;
case "concatenatingRemoveRange": case "concatenatingRemoveRange":
concatenating(request.get("id")) concatenating(request.get("id"))
.removeMediaSourceRange((Integer)request.get("startIndex"), (Integer)request.get("endIndex"), handler, () -> result.success(new HashMap<String, Object>())); .removeMediaSourceRange((Integer)request.get("startIndex"), (Integer)request.get("endIndex"), handler, () -> result.success(new HashMap<String, Object>()));
concatenating(request.get("id"))
.setShuffleOrder(decodeShuffleOrder((List<Integer>)request.get("shuffleOrder")));
break; break;
case "concatenatingMove": case "concatenatingMove":
concatenating(request.get("id")) concatenating(request.get("id"))
.moveMediaSource((Integer)request.get("currentIndex"), (Integer)request.get("newIndex"), handler, () -> result.success(new HashMap<String, Object>())); .moveMediaSource((Integer)request.get("currentIndex"), (Integer)request.get("newIndex"), handler, () -> result.success(new HashMap<String, Object>()));
concatenating(request.get("id"))
.setShuffleOrder(decodeShuffleOrder((List<Integer>)request.get("shuffleOrder")));
break; break;
case "setAndroidAudioAttributes": case "setAndroidAudioAttributes":
setAudioAttributes((Integer)request.get("contentType"), (Integer)request.get("flags"), (Integer)request.get("usage")); setAudioAttributes((Integer)request.get("contentType"), (Integer)request.get("flags"), (Integer)request.get("usage"));
@ -358,39 +366,12 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud
} }
} }
// Set the shuffle order for mediaSource, with currentIndex at private ShuffleOrder decodeShuffleOrder(List<Integer> indexList) {
// the first position. Traverse the tree incrementing index at each int[] shuffleIndices = new int[indexList.size()];
// node. for (int i = 0; i < shuffleIndices.length; i++) {
private int setShuffleOrder(MediaSource mediaSource, int index) { shuffleIndices[i] = indexList.get(i);
if (mediaSource instanceof ConcatenatingMediaSource) {
final ConcatenatingMediaSource source = (ConcatenatingMediaSource)mediaSource;
// Find which child is current
Integer currentChildIndex = null;
for (int i = 0; i < source.getSize(); i++) {
final int indexBefore = index;
final MediaSource child = source.getMediaSource(i);
index = setShuffleOrder(child, index);
// If currentIndex falls within this child, make this child come first.
if (currentIndex >= indexBefore && currentIndex < index) {
currentChildIndex = i;
}
}
// Shuffle so that the current child is first in the shuffle order
source.setShuffleOrder(createShuffleOrder(source.getSize(), currentChildIndex));
} else if (mediaSource instanceof LoopingMediaSource) {
final LoopingMediaSource source = (LoopingMediaSource)mediaSource;
// The ExoPlayer API doesn't provide accessors for these so we have
// to index them ourselves.
MediaSource child = loopingChildren.get(source);
int count = loopingCounts.get(source);
for (int i = 0; i < count; i++) {
index = setShuffleOrder(child, index);
}
} else {
// An actual media item takes up one spot in the playlist.
index++;
} }
return index; return new DefaultShuffleOrder(shuffleIndices, random.nextLong());
} }
private static int[] shuffle(int length, Integer firstIndex) { private static int[] shuffle(int length, Integer firstIndex) {
@ -423,6 +404,26 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud
return (ConcatenatingMediaSource)mediaSources.get((String)index); return (ConcatenatingMediaSource)mediaSources.get((String)index);
} }
private void setShuffleOrder(final Object json) {
Map<?, ?> map = (Map<?, ?>)json;
String id = (String)map.get("id");
MediaSource mediaSource = mediaSources.get(id);
if (mediaSource == null) return;
switch ((String)map.get("type")) {
case "concatenating":
ConcatenatingMediaSource concatenatingMediaSource = (ConcatenatingMediaSource)mediaSource;
concatenatingMediaSource.setShuffleOrder(decodeShuffleOrder((List<Integer>)map.get("shuffleOrder")));
List<Object> children = (List<Object>)map.get("children");
for (Object child : children) {
setShuffleOrder(child);
}
break;
case "looping":
setShuffleOrder(map.get("child"));
break;
}
}
private MediaSource getAudioSource(final Object json) { private MediaSource getAudioSource(final Object json) {
Map<?, ?> map = (Map<?, ?>)json; Map<?, ?> map = (Map<?, ?>)json;
String id = (String)map.get("id"); String id = (String)map.get("id");
@ -455,7 +456,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud
return new ConcatenatingMediaSource( return new ConcatenatingMediaSource(
false, // isAtomic false, // isAtomic
(Boolean)map.get("useLazyPreparation"), (Boolean)map.get("useLazyPreparation"),
new DefaultShuffleOrder(mediaSources.length), decodeShuffleOrder((List<Integer>)map.get("shuffleOrder")),
mediaSources); mediaSources);
case "clipping": case "clipping":
Long start = getLong(map.get("start")); Long start = getLong(map.get("start"));
@ -466,11 +467,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud
case "looping": case "looping":
Integer count = (Integer)map.get("count"); Integer count = (Integer)map.get("count");
MediaSource looperChild = getAudioSource(map.get("child")); MediaSource looperChild = getAudioSource(map.get("child"));
LoopingMediaSource looper = new LoopingMediaSource(looperChild, count); return new LoopingMediaSource(looperChild, count);
// TODO: store both in a single map
loopingChildren.put(looper, looperChild);
loopingCounts.put(looper, count);
return looper;
default: default:
throw new IllegalArgumentException("Unknown AudioSource type: " + map.get("type")); throw new IllegalArgumentException("Unknown AudioSource type: " + map.get("type"));
} }
@ -520,9 +517,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud
errorCount = 0; errorCount = 0;
prepareResult = result; prepareResult = result;
transition(ProcessingState.loading); transition(ProcessingState.loading);
if (player.getShuffleModeEnabled()) {
setShuffleOrder(mediaSource, 0);
}
this.mediaSource = mediaSource; this.mediaSource = mediaSource;
player.prepare(mediaSource); player.prepare(mediaSource);
} }
@ -669,9 +663,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud
} }
public void setShuffleModeEnabled(final boolean enabled) { public void setShuffleModeEnabled(final boolean enabled) {
if (enabled) {
setShuffleOrder(mediaSource, 0);
}
player.setShuffleModeEnabled(enabled); player.setShuffleModeEnabled(enabled);
} }
@ -694,7 +685,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud
} }
mediaSources.clear(); mediaSources.clear();
mediaSource = null; mediaSource = null;
loopingChildren.clear();
if (player != null) { if (player != null) {
player.release(); player.release();
player = null; player = null;

View File

@ -106,6 +106,9 @@
} else if ([@"setShuffleMode" isEqualToString:call.method]) { } else if ([@"setShuffleMode" isEqualToString:call.method]) {
[self setShuffleModeEnabled:(BOOL)([request[@"shuffleMode"] intValue] == 1)]; [self setShuffleModeEnabled:(BOOL)([request[@"shuffleMode"] intValue] == 1)];
result(@{}); result(@{});
} else if ([@"setShuffleOrder" isEqualToString:call.method]) {
[self setShuffleOrder:(NSDictionary *)request[@"audioSources"]];
result(@{});
} else if ([@"setAutomaticallyWaitsToMinimizeStalling" isEqualToString:call.method]) { } else if ([@"setAutomaticallyWaitsToMinimizeStalling" isEqualToString:call.method]) {
[self setAutomaticallyWaitsToMinimizeStalling:(BOOL)[request[@"enabled"] boolValue]]; [self setAutomaticallyWaitsToMinimizeStalling:(BOOL)[request[@"enabled"] boolValue]];
result(@{}); result(@{});
@ -115,13 +118,13 @@
result(@{}); result(@{});
}]; }];
} else if ([@"concatenatingInsertAll" isEqualToString:call.method]) { } else if ([@"concatenatingInsertAll" isEqualToString:call.method]) {
[self concatenatingInsertAll:(NSString *)request[@"id"] index:[request[@"index"] intValue] sources:(NSArray *)request[@"children"]]; [self concatenatingInsertAll:(NSString *)request[@"id"] index:[request[@"index"] intValue] sources:(NSArray *)request[@"children"] shuffleOrder:(NSArray<NSNumber *> *)request[@"shuffleOrder"]];
result(@{}); result(@{});
} else if ([@"concatenatingRemoveRange" isEqualToString:call.method]) { } else if ([@"concatenatingRemoveRange" isEqualToString:call.method]) {
[self concatenatingRemoveRange:(NSString *)request[@"id"] start:[request[@"startIndex"] intValue] end:[request[@"endIndex"] intValue]]; [self concatenatingRemoveRange:(NSString *)request[@"id"] start:[request[@"startIndex"] intValue] end:[request[@"endIndex"] intValue] shuffleOrder:(NSArray<NSNumber *> *)request[@"shuffleOrder"]];
result(@{}); result(@{});
} else if ([@"concatenatingMove" isEqualToString:call.method]) { } else if ([@"concatenatingMove" isEqualToString:call.method]) {
[self concatenatingMove:(NSString *)request[@"id"] currentIndex:[request[@"currentIndex"] intValue] newIndex:[request[@"newIndex"] intValue]]; [self concatenatingMove:(NSString *)request[@"id"] currentIndex:[request[@"currentIndex"] intValue] newIndex:[request[@"newIndex"] intValue] shuffleOrder:(NSArray<NSNumber *> *)request[@"shuffleOrder"]];
result(@{}); result(@{});
} else if ([@"setAndroidAudioAttributes" isEqualToString:call.method]) { } else if ([@"setAndroidAudioAttributes" isEqualToString:call.method]) {
result(@{}); result(@{});
@ -144,22 +147,7 @@
} }
// Untested // Untested
- (void)concatenatingAdd:(NSString *)catId source:(NSDictionary *)source { - (void)concatenatingInsertAll:(NSString *)catId index:(int)index sources:(NSArray *)sources shuffleOrder:(NSArray<NSNumber *> *)shuffleOrder {
[self concatenatingInsertAll:catId index:-1 sources:@[source]];
}
// Untested
- (void)concatenatingInsert:(NSString *)catId index:(int)index source:(NSDictionary *)source {
[self concatenatingInsertAll:catId index:index sources:@[source]];
}
// Untested
- (void)concatenatingAddAll:(NSString *)catId sources:(NSArray *)sources {
[self concatenatingInsertAll:catId index:-1 sources:sources];
}
// Untested
- (void)concatenatingInsertAll:(NSString *)catId index:(int)index sources:(NSArray *)sources {
// Find all duplicates of the identified ConcatenatingAudioSource. // Find all duplicates of the identified ConcatenatingAudioSource.
NSMutableArray *matches = [[NSMutableArray alloc] init]; NSMutableArray *matches = [[NSMutableArray alloc] init];
[_audioSource findById:catId matches:matches]; [_audioSource findById:catId matches:matches];
@ -172,6 +160,7 @@
AudioSource *audioSource = audioSources[j]; AudioSource *audioSource = audioSources[j];
[catSource insertSource:audioSource atIndex:(idx + j)]; [catSource insertSource:audioSource atIndex:(idx + j)];
} }
[catSource setShuffleOrder:shuffleOrder];
} }
// Index the new audio sources. // Index the new audio sources.
_indexedAudioSources = [[NSMutableArray alloc] init]; _indexedAudioSources = [[NSMutableArray alloc] init];
@ -200,12 +189,7 @@
} }
// Untested // Untested
- (void)concatenatingRemoveAt:(NSString *)catId index:(int)index { - (void)concatenatingRemoveRange:(NSString *)catId start:(int)start end:(int)end shuffleOrder:(NSArray<NSNumber *> *)shuffleOrder {
[self concatenatingRemoveRange:catId start:index end:(index + 1)];
}
// Untested
- (void)concatenatingRemoveRange:(NSString *)catId start:(int)start end:(int)end {
// Find all duplicates of the identified ConcatenatingAudioSource. // Find all duplicates of the identified ConcatenatingAudioSource.
NSMutableArray *matches = [[NSMutableArray alloc] init]; NSMutableArray *matches = [[NSMutableArray alloc] init];
[_audioSource findById:catId matches:matches]; [_audioSource findById:catId matches:matches];
@ -214,6 +198,7 @@
ConcatenatingAudioSource *catSource = (ConcatenatingAudioSource *)matches[i]; ConcatenatingAudioSource *catSource = (ConcatenatingAudioSource *)matches[i];
int endIndex = end >= 0 ? end : catSource.count; int endIndex = end >= 0 ? end : catSource.count;
[catSource removeSourcesFromIndex:start toIndex:endIndex]; [catSource removeSourcesFromIndex:start toIndex:endIndex];
[catSource setShuffleOrder:shuffleOrder];
} }
// Re-index the remaining audio sources. // Re-index the remaining audio sources.
NSArray<IndexedAudioSource *> *oldIndexedAudioSources = _indexedAudioSources; NSArray<IndexedAudioSource *> *oldIndexedAudioSources = _indexedAudioSources;
@ -239,7 +224,7 @@
} }
// Untested // Untested
- (void)concatenatingMove:(NSString *)catId currentIndex:(int)currentIndex newIndex:(int)newIndex { - (void)concatenatingMove:(NSString *)catId currentIndex:(int)currentIndex newIndex:(int)newIndex shuffleOrder:(NSArray<NSNumber *> *)shuffleOrder {
// Find all duplicates of the identified ConcatenatingAudioSource. // Find all duplicates of the identified ConcatenatingAudioSource.
NSMutableArray *matches = [[NSMutableArray alloc] init]; NSMutableArray *matches = [[NSMutableArray alloc] init];
[_audioSource findById:catId matches:matches]; [_audioSource findById:catId matches:matches];
@ -247,6 +232,7 @@
for (int i = 0; i < matches.count; i++) { for (int i = 0; i < matches.count; i++) {
ConcatenatingAudioSource *catSource = (ConcatenatingAudioSource *)matches[i]; ConcatenatingAudioSource *catSource = (ConcatenatingAudioSource *)matches[i];
[catSource moveSourceFromIndex:currentIndex toIndex:newIndex]; [catSource moveSourceFromIndex:currentIndex toIndex:newIndex];
[catSource setShuffleOrder:shuffleOrder];
} }
// Re-index the audio sources. // Re-index the audio sources.
_indexedAudioSources = [[NSMutableArray alloc] init]; _indexedAudioSources = [[NSMutableArray alloc] init];
@ -256,11 +242,6 @@
[self broadcastPlaybackEvent]; [self broadcastPlaybackEvent];
} }
// Untested
- (void)concatenatingClear:(NSString *)catId {
[self concatenatingRemoveRange:catId start:0 end:-1];
}
- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink {
_eventSink = eventSink; _eventSink = eventSink;
return nil; return nil;
@ -408,7 +389,8 @@
return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"]]; return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"]];
} else if ([@"concatenating" isEqualToString:type]) { } else if ([@"concatenating" isEqualToString:type]) {
return [[ConcatenatingAudioSource alloc] initWithId:data[@"id"] return [[ConcatenatingAudioSource alloc] initWithId:data[@"id"]
audioSources:[self decodeAudioSources:data[@"children"]]]; audioSources:[self decodeAudioSources:data[@"children"]]
shuffleOrder:(NSArray<NSNumber *> *)data[@"shuffleOrder"]];
} else if ([@"clipping" isEqualToString:type]) { } else if ([@"clipping" isEqualToString:type]) {
return [[ClippingAudioSource alloc] initWithId:data[@"id"] return [[ClippingAudioSource alloc] initWithId:data[@"id"]
audioSource:(UriAudioSource *)[self decodeAudioSource:data[@"child"]] audioSource:(UriAudioSource *)[self decodeAudioSource:data[@"child"]]
@ -590,15 +572,12 @@
} }
- (void)updateOrder { - (void)updateOrder {
if (_shuffleModeEnabled) {
[_audioSource shuffle:0 currentIndex: _index];
}
_orderInv = [NSMutableArray arrayWithCapacity:[_indexedAudioSources count]]; _orderInv = [NSMutableArray arrayWithCapacity:[_indexedAudioSources count]];
for (int i = 0; i < [_indexedAudioSources count]; i++) { for (int i = 0; i < [_indexedAudioSources count]; i++) {
[_orderInv addObject:@(0)]; [_orderInv addObject:@(0)];
} }
if (_shuffleModeEnabled) { if (_shuffleModeEnabled) {
_order = [_audioSource getShuffleOrder]; _order = [_audioSource getShuffleIndices];
} else { } else {
NSMutableArray *order = [[NSMutableArray alloc] init]; NSMutableArray *order = [[NSMutableArray alloc] init];
for (int i = 0; i < [_indexedAudioSources count]; i++) { for (int i = 0; i < [_indexedAudioSources count]; i++) {
@ -1031,6 +1010,10 @@
[self enqueueFrom:_index]; [self enqueueFrom:_index];
} }
- (void)setShuffleOrder:(NSDictionary *)dict {
[_audioSource decodeShuffleOrder:dict];
}
- (void)dumpQueue { - (void)dumpQueue {
for (int i = 0; i < _player.items.count; i++) { for (int i = 0; i < _player.items.count; i++) {
IndexedPlayerItem *playerItem = (IndexedPlayerItem *)_player.items[i]; IndexedPlayerItem *playerItem = (IndexedPlayerItem *)_player.items[i];

View File

@ -26,12 +26,11 @@
} }
} }
- (NSArray<NSNumber *> *)getShuffleOrder { - (NSArray<NSNumber *> *)getShuffleIndices {
return @[]; return @[];
} }
- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex { - (void)decodeShuffleOrder:(NSDictionary *)dict {
return 0;
} }
@end @end

View File

@ -39,7 +39,7 @@
return _audioSource.playerItem; return _audioSource.playerItem;
} }
- (NSArray<NSNumber *> *)getShuffleOrder { - (NSArray<NSNumber *> *)getShuffleIndices {
return @[@(0)]; return @[@(0)];
} }

View File

@ -5,13 +5,14 @@
@implementation ConcatenatingAudioSource { @implementation ConcatenatingAudioSource {
NSMutableArray<AudioSource *> *_audioSources; NSMutableArray<AudioSource *> *_audioSources;
NSMutableArray<NSNumber *> *_shuffleOrder; NSArray<NSNumber *> *_shuffleOrder;
} }
- (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray<AudioSource *> *)audioSources { - (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray<AudioSource *> *)audioSources shuffleOrder:(NSArray<NSNumber *> *)shuffleOrder {
self = [super initWithId:sid]; self = [super initWithId:sid];
NSAssert(self, @"super init cannot be nil"); NSAssert(self, @"super init cannot be nil");
_audioSources = audioSources; _audioSources = audioSources;
_shuffleOrder = shuffleOrder;
return self; return self;
} }
@ -50,19 +51,19 @@
} }
} }
- (NSArray<NSNumber *> *)getShuffleOrder { - (NSArray<NSNumber *> *)getShuffleIndices {
NSMutableArray<NSNumber *> *order = [NSMutableArray new]; NSMutableArray<NSNumber *> *order = [NSMutableArray new];
int offset = (int)[order count]; int offset = (int)[order count];
NSMutableArray<NSArray<NSNumber *> *> *childOrders = [NSMutableArray new]; // array of array of ints NSMutableArray<NSArray<NSNumber *> *> *childOrders = [NSMutableArray new]; // array of array of ints
for (int i = 0; i < [_audioSources count]; i++) { for (int i = 0; i < [_audioSources count]; i++) {
AudioSource *audioSource = _audioSources[i]; AudioSource *audioSource = _audioSources[i];
NSArray<NSNumber *> *childShuffleOrder = [audioSource getShuffleOrder]; NSArray<NSNumber *> *childShuffleIndices = [audioSource getShuffleIndices];
NSMutableArray<NSNumber *> *offsetChildShuffleOrder = [NSMutableArray new]; NSMutableArray<NSNumber *> *offsetChildShuffleOrder = [NSMutableArray new];
for (int j = 0; j < [childShuffleOrder count]; j++) { for (int j = 0; j < [childShuffleIndices count]; j++) {
[offsetChildShuffleOrder addObject:@([childShuffleOrder[j] integerValue] + offset)]; [offsetChildShuffleOrder addObject:@([childShuffleIndices[j] integerValue] + offset)];
} }
[childOrders addObject:offsetChildShuffleOrder]; [childOrders addObject:offsetChildShuffleOrder];
offset += [childShuffleOrder count]; offset += [childShuffleIndices count];
} }
for (int i = 0; i < [_audioSources count]; i++) { for (int i = 0; i < [_audioSources count]; i++) {
[order addObjectsFromArray:childOrders[[_shuffleOrder[i] integerValue]]]; [order addObjectsFromArray:childOrders[[_shuffleOrder[i] integerValue]]];
@ -70,40 +71,22 @@
return order; return order;
} }
- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex { - (void)setShuffleOrder:(NSArray<NSNumber *> *)shuffleOrder {
int currentChildIndex = -1; _shuffleOrder = shuffleOrder;
}
- (void)decodeShuffleOrder:(NSDictionary *)dict {
_shuffleOrder = (NSArray<NSNumber *> *)dict[@"shuffleOrder"];
NSArray *dictChildren = (NSArray *)dict[@"children"];
if (_audioSources.count != dictChildren.count) {
NSLog(@"decodeShuffleOrder Concatenating children don't match");
return;
}
for (int i = 0; i < [_audioSources count]; i++) { for (int i = 0; i < [_audioSources count]; i++) {
int indexBefore = treeIndex;
AudioSource *child = _audioSources[i]; AudioSource *child = _audioSources[i];
treeIndex = [child shuffle:treeIndex currentIndex:currentIndex]; NSDictionary *dictChild = (NSDictionary *)dictChildren[i];
if (currentIndex >= indexBefore && currentIndex < treeIndex) { [child decodesetShuffleOrder:dictChild];
currentChildIndex = i;
} else {}
} }
// Shuffle so that the current child is first in the shuffle order
_shuffleOrder = [NSMutableArray arrayWithCapacity:[_audioSources count]];
for (int i = 0; i < [_audioSources count]; i++) {
[_shuffleOrder addObject:@(0)];
}
NSLog(@"shuffle: audioSources.count=%d and shuffleOrder.count=%d", (int)[_audioSources count], (int)[_shuffleOrder count]);
// First generate a random shuffle
for (int i = 0; i < [_audioSources count]; i++) {
int j = arc4random_uniform(i + 1);
_shuffleOrder[i] = _shuffleOrder[j];
_shuffleOrder[j] = @(i);
}
// Then bring currentIndex to the front
if (currentChildIndex != -1) {
for (int i = 1; i < [_audioSources count]; i++) {
if ([_shuffleOrder[i] integerValue] == currentChildIndex) {
NSNumber *v = _shuffleOrder[0];
_shuffleOrder[0] = _shuffleOrder[i];
_shuffleOrder[i] = v;
break;
}
}
}
return treeIndex;
} }
@end @end

View File

@ -42,10 +42,6 @@
return treeIndex + 1; return treeIndex + 1;
} }
- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex {
return treeIndex + 1;
}
- (void)attach:(AVQueuePlayer *)player { - (void)attach:(AVQueuePlayer *)player {
_isAttached = YES; _isAttached = YES;
} }

View File

@ -28,12 +28,12 @@
} }
} }
- (NSArray<NSNumber *> *)getShuffleOrder { - (NSArray<NSNumber *> *)getShuffleIndices {
NSMutableArray<NSNumber *> *order = [NSMutableArray new]; NSMutableArray<NSNumber *> *order = [NSMutableArray new];
int offset = (int)[order count]; int offset = (int)[order count];
for (int i = 0; i < [_audioSources count]; i++) { for (int i = 0; i < [_audioSources count]; i++) {
AudioSource *audioSource = _audioSources[i]; AudioSource *audioSource = _audioSources[i];
NSArray<NSNumber *> *childShuffleOrder = [audioSource getShuffleOrder]; NSArray<NSNumber *> *childShuffleOrder = [audioSource getShuffleIndices];
for (int j = 0; j < [childShuffleOrder count]; j++) { for (int j = 0; j < [childShuffleOrder count]; j++) {
[order addObject:@([childShuffleOrder[j] integerValue] + offset)]; [order addObject:@([childShuffleOrder[j] integerValue] + offset)];
} }
@ -42,12 +42,12 @@
return order; return order;
} }
- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex { - (void)decodeShuffleOrder:(NSDictionary *)dict {
// TODO: This should probably shuffle the same way on all duplicates. NSDictionary *dictChild = (NSDictionary *)dict[@"child"];
for (int i = 0; i < [_audioSources count]; i++) { for (int i = 0; i < [_audioSources count]; i++) {
treeIndex = [_audioSources[i] shuffle:treeIndex currentIndex:currentIndex]; AudioSource *child = _audioSources[i];
[child decodeShuffleOrder:dictChild];
} }
return treeIndex;
} }
@end @end

View File

@ -35,7 +35,7 @@
return _playerItem; return _playerItem;
} }
- (NSArray<NSNumber *> *)getShuffleOrder { - (NSArray<NSNumber *> *)getShuffleIndices {
return @[@(0)]; return @[@(0)];
} }

View File

@ -179,8 +179,12 @@ class _MyAppState extends State<MyApp> {
icon: shuffleModeEnabled icon: shuffleModeEnabled
? Icon(Icons.shuffle, color: Colors.orange) ? Icon(Icons.shuffle, color: Colors.orange)
: Icon(Icons.shuffle, color: Colors.grey), : Icon(Icons.shuffle, color: Colors.grey),
onPressed: () { onPressed: () async {
_player.setShuffleModeEnabled(!shuffleModeEnabled); final enable = !shuffleModeEnabled;
if (enable) {
await _player.shuffle();
}
await _player.setShuffleModeEnabled(enable);
}, },
); );
}, },

View File

@ -7,7 +7,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.5.0-nullsafety.1" version: "2.5.0-nullsafety.3"
audio_session: audio_session:
dependency: "direct main" dependency: "direct main"
description: description:
@ -21,35 +21,35 @@ packages:
name: boolean_selector name: boolean_selector
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.1" version: "2.1.0-nullsafety.3"
characters: characters:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0-nullsafety.3" version: "1.1.0-nullsafety.5"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
name: charcode name: charcode
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0-nullsafety.1" version: "1.2.0-nullsafety.3"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0-nullsafety.1" version: "1.1.0-nullsafety.3"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.15.0-nullsafety.3" version: "1.15.0-nullsafety.5"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -77,7 +77,7 @@ packages:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0-nullsafety.1" version: "1.2.0-nullsafety.3"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
@ -114,6 +114,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.16.1" version: "0.16.1"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3-nullsafety.3"
just_audio: just_audio:
dependency: "direct main" dependency: "direct main"
description: description:
@ -124,16 +131,16 @@ packages:
just_audio_platform_interface: just_audio_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: just_audio_platform_interface path: "../../just_audio_platform_interface"
url: "https://pub.dartlang.org" relative: true
source: hosted source: path
version: "1.1.1" version: "1.1.1"
just_audio_web: just_audio_web:
dependency: transitive dependency: transitive
description: description:
name: just_audio_web path: "../../just_audio_web"
url: "https://pub.dartlang.org" relative: true
source: hosted source: path
version: "0.1.1" version: "0.1.1"
matcher: matcher:
dependency: transitive dependency: transitive
@ -141,21 +148,21 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.10-nullsafety.1" version: "0.12.10-nullsafety.3"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0-nullsafety.3" version: "1.3.0-nullsafety.6"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0-nullsafety.1" version: "1.8.0-nullsafety.3"
path_provider: path_provider:
dependency: transitive dependency: transitive
description: description:
@ -230,49 +237,49 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0-nullsafety.2" version: "1.8.0-nullsafety.4"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.10.0-nullsafety.1" version: "1.10.0-nullsafety.6"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.1" version: "2.1.0-nullsafety.3"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0-nullsafety.1" version: "1.1.0-nullsafety.3"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0-nullsafety.1" version: "1.2.0-nullsafety.3"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.19-nullsafety.2" version: "0.2.19-nullsafety.6"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0-nullsafety.3" version: "1.3.0-nullsafety.5"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:
@ -286,7 +293,7 @@ packages:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.3" version: "2.1.0-nullsafety.5"
win32: win32:
dependency: transitive dependency: transitive
description: description:
@ -302,5 +309,5 @@ packages:
source: hosted source: hosted
version: "0.1.2" version: "0.1.2"
sdks: sdks:
dart: ">=2.10.0-110 <2.11.0" dart: ">=2.12.0-0.0 <3.0.0"
flutter: ">=1.12.13+hotfix.5 <2.0.0" flutter: ">=1.12.13+hotfix.5 <2.0.0"

View File

@ -7,7 +7,7 @@
- (instancetype)initWithId:(NSString *)sid; - (instancetype)initWithId:(NSString *)sid;
- (int)buildSequence:(NSMutableArray *)sequence treeIndex:(int)treeIndex; - (int)buildSequence:(NSMutableArray *)sequence treeIndex:(int)treeIndex;
- (void)findById:(NSString *)sourceId matches:(NSMutableArray<AudioSource *> *)matches; - (void)findById:(NSString *)sourceId matches:(NSMutableArray<AudioSource *> *)matches;
- (NSArray<NSNumber *> *)getShuffleOrder; - (NSArray<NSNumber *> *)getShuffleIndices;
- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex; - (void)decodeShuffleOrder:(NSDictionary *)dict;
@end @end

View File

@ -5,9 +5,10 @@
@property (readonly, nonatomic) int count; @property (readonly, nonatomic) int count;
- (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray<AudioSource *> *)audioSources; - (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray<AudioSource *> *)audioSources shuffleOrder:(NSMutableArray<NSNumber *> *)shuffleOrder;
- (void)insertSource:(AudioSource *)audioSource atIndex:(int)index; - (void)insertSource:(AudioSource *)audioSource atIndex:(int)index;
- (void)removeSourcesFromIndex:(int)start toIndex:(int)end; - (void)removeSourcesFromIndex:(int)start toIndex:(int)end;
- (void)moveSourceFromIndex:(int)currentIndex toIndex:(int)newIndex; - (void)moveSourceFromIndex:(int)currentIndex toIndex:(int)newIndex;
- (void)setShuffleOrder:(NSArray<NSNumber *> *)shuffleOrder;
@end @end

View File

@ -45,7 +45,7 @@ class AudioPlayer {
bool _disposed = false; bool _disposed = false;
PlaybackEvent _playbackEvent; PlaybackEvent _playbackEvent;
final _playbackEventSubject = BehaviorSubject<PlaybackEvent>(); final _playbackEventSubject = BehaviorSubject<PlaybackEvent>(sync: true);
Future<Duration> _durationFuture; Future<Duration> _durationFuture;
final _durationSubject = BehaviorSubject<Duration>(); final _durationSubject = BehaviorSubject<Duration>();
final _processingStateSubject = BehaviorSubject<ProcessingState>(); final _processingStateSubject = BehaviorSubject<ProcessingState>();
@ -56,7 +56,9 @@ class AudioPlayer {
final _icyMetadataSubject = BehaviorSubject<IcyMetadata>(); final _icyMetadataSubject = BehaviorSubject<IcyMetadata>();
final _playerStateSubject = BehaviorSubject<PlayerState>(); final _playerStateSubject = BehaviorSubject<PlayerState>();
final _sequenceSubject = BehaviorSubject<List<IndexedAudioSource>>(); final _sequenceSubject = BehaviorSubject<List<IndexedAudioSource>>();
final _currentIndexSubject = BehaviorSubject<int>(); final _shuffleIndicesSubject = BehaviorSubject<List<int>>();
final _shuffleIndicesInv = <int>[];
final _currentIndexSubject = BehaviorSubject<int>(sync: true);
final _sequenceStateSubject = BehaviorSubject<SequenceState>(); final _sequenceStateSubject = BehaviorSubject<SequenceState>();
final _loopModeSubject = BehaviorSubject<LoopMode>(); final _loopModeSubject = BehaviorSubject<LoopMode>();
final _shuffleModeEnabledSubject = BehaviorSubject<bool>(); final _shuffleModeEnabledSubject = BehaviorSubject<bool>();
@ -103,19 +105,29 @@ class AudioPlayer {
.map((event) => event.currentIndex) .map((event) => event.currentIndex)
.distinct() .distinct()
.handleError((err, stack) {/* noop */})); .handleError((err, stack) {/* noop */}));
currentIndexStream.listen((index) => print('### index: $index'));
_androidAudioSessionIdSubject.addStream(playbackEventStream _androidAudioSessionIdSubject.addStream(playbackEventStream
.map((event) => event.androidAudioSessionId) .map((event) => event.androidAudioSessionId)
.distinct() .distinct()
.handleError((err, stack) {/* noop */})); .handleError((err, stack) {/* noop */}));
_sequenceStateSubject.addStream( _sequenceStateSubject.addStream(Rx.combineLatest5<List<IndexedAudioSource>,
Rx.combineLatest2<List<IndexedAudioSource>, int, SequenceState>( List<int>, int, bool, LoopMode, SequenceState>(
sequenceStream, sequenceStream,
shuffleIndicesStream,
currentIndexStream, currentIndexStream,
(sequence, currentIndex) { shuffleModeEnabledStream,
loopModeStream,
(sequence, shuffleIndices, currentIndex, shuffleModeEnabled, loopMode) {
if (sequence == null) return null; if (sequence == null) return null;
if (currentIndex == null) currentIndex = 0; if (currentIndex == null) currentIndex = 0;
currentIndex = min(sequence.length - 1, max(0, currentIndex)); currentIndex = min(sequence.length - 1, max(0, currentIndex));
return SequenceState(sequence, currentIndex); return SequenceState(
sequence,
currentIndex,
shuffleIndices,
shuffleModeEnabled,
loopMode,
);
}, },
).distinct().handleError((err, stack) {/* noop */})); ).distinct().handleError((err, stack) {/* noop */}));
_playerStateSubject.addStream( _playerStateSubject.addStream(
@ -125,6 +137,8 @@ class AudioPlayer {
(playing, event) => PlayerState(playing, event.processingState)) (playing, event) => PlayerState(playing, event.processingState))
.distinct() .distinct()
.handleError((err, stack) {/* noop */})); .handleError((err, stack) {/* noop */}));
_shuffleModeEnabledSubject.add(false);
_loopModeSubject.add(LoopMode.off);
_platform.then((platform) { _platform.then((platform) {
platform.playbackEventMessageStream.listen((message) { platform.playbackEventMessageStream.listen((message) {
final playbackEvent = PlaybackEvent( final playbackEvent = PlaybackEvent(
@ -140,6 +154,8 @@ class AudioPlayer {
currentIndex: message.currentIndex, currentIndex: message.currentIndex,
androidAudioSessionId: message.androidAudioSessionId, androidAudioSessionId: message.androidAudioSessionId,
); );
print(
"### received event with currentIndex: ${playbackEvent.currentIndex}");
_durationFuture = Future.value(playbackEvent.duration); _durationFuture = Future.value(playbackEvent.duration);
if (playbackEvent.duration != _playbackEvent.duration) { if (playbackEvent.duration != _playbackEvent.duration) {
_durationSubject.add(playbackEvent.duration); _durationSubject.add(playbackEvent.duration);
@ -268,6 +284,16 @@ class AudioPlayer {
Stream<List<IndexedAudioSource>> get sequenceStream => Stream<List<IndexedAudioSource>> get sequenceStream =>
_sequenceSubject.stream; _sequenceSubject.stream;
/// The current shuffled sequence of indexed audio sources.
List<int> get shuffleIndices => _shuffleIndicesSubject.value;
/// A stream broadcasting the current shuffled sequence of indexed audio
/// sources.
Stream<List<int>> get shuffleIndicesStream => _shuffleIndicesSubject.stream;
//List<IndexedAudioSource> get _effectiveSequence =>
// shuffleModeEnabled ? shuffleIndices : sequence;
/// The index of the current item. /// The index of the current item.
int get currentIndex => _currentIndexSubject.value; int get currentIndex => _currentIndexSubject.value;
@ -282,14 +308,71 @@ class AudioPlayer {
Stream<SequenceState> get sequenceStateStream => _sequenceStateSubject.stream; Stream<SequenceState> get sequenceStateStream => _sequenceStateSubject.stream;
/// Whether there is another item after the current index. /// Whether there is another item after the current index.
bool get hasNext => bool get hasNext => _nextIndex != null;
_audioSource != null &&
currentIndex != null &&
currentIndex + 1 < sequence.length;
/// Whether there is another item before the current index. /// Whether there is another item before the current index.
bool get hasPrevious => bool get hasPrevious => _previousIndex != null;
_audioSource != null && currentIndex != null && currentIndex > 0;
int get _nextIndex => _getRelativeIndex(1);
int get _previousIndex => _getRelativeIndex(-1);
int _getRelativeIndex(int offset) {
print('### _getRelativeIndex');
print('audioSource = $_audioSource');
print('currentIndex = $currentIndex');
print('shuffleModeEnabled = $shuffleModeEnabled');
if (_audioSource == null ||
currentIndex == null ||
shuffleModeEnabled == null) return null;
if (loopMode == LoopMode.one) return currentIndex;
int result;
print('shuffleModeEnabled: $shuffleModeEnabled');
if (shuffleModeEnabled) {
print('shuffleIndices: $shuffleIndices');
print('shuffleIndicesInv: $_shuffleIndicesInv');
if (shuffleIndices == null) return null;
final shufflePos = _shuffleIndicesInv[currentIndex];
var newShufflePos = shufflePos + offset;
print(
'newshufflePos($newShufflePos) >= shuffleIndices.length(${shuffleIndices.length})');
if (newShufflePos >= shuffleIndices.length) {
if (loopMode == LoopMode.all) {
newShufflePos = 0;
} else {
return null;
}
}
if (newShufflePos < 0) {
if (loopMode == LoopMode.all) {
newShufflePos = shuffleIndices.length - 1;
} else {
return null;
}
}
result = shuffleIndices[newShufflePos];
} else {
print("sequence: $sequence");
if (sequence == null) return null;
result = currentIndex + offset;
print("result($result >= sequence.length(${sequence.length}))");
if (result >= sequence.length) {
if (loopMode == LoopMode.all) {
result = 0;
} else {
return null;
}
}
if (result < 0) {
if (loopMode == LoopMode.all) {
result = sequence.length - 1;
} else {
return null;
}
}
}
print("returning $result");
return result;
}
/// The current loop mode. /// The current loop mode.
LoopMode get loopMode => _loopModeSubject.value; LoopMode get loopMode => _loopModeSubject.value;
@ -452,6 +535,7 @@ class AudioPlayer {
if (_disposed) return null; if (_disposed) return null;
try { try {
_audioSource = source; _audioSource = source;
print("### set _audioSource = $_audioSource");
_broadcastSequence(); _broadcastSequence();
final duration = await _load(source, final duration = await _load(source,
initialPosition: initialPosition, initialIndex: initialIndex); initialPosition: initialPosition, initialIndex: initialIndex);
@ -466,7 +550,30 @@ class AudioPlayer {
} }
void _broadcastSequence() { void _broadcastSequence() {
print("### _broadcastSequence");
_sequenceSubject.add(_audioSource?.sequence); _sequenceSubject.add(_audioSource?.sequence);
print('sequence: ${_audioSource?.sequence?.length}');
_updateShuffleIndices();
}
_updateShuffleIndices() {
_shuffleIndicesSubject.add(_audioSource?.shuffleIndices);
print('shuffle indices: ${_audioSource?.shuffleIndices?.length}');
print('shuffleIndices: ${shuffleIndices?.length}');
final shuffleIndicesLength = shuffleIndices?.length ?? 0;
if (_shuffleIndicesInv.length > shuffleIndicesLength) {
_shuffleIndicesInv.removeRange(
shuffleIndicesLength, _shuffleIndicesInv.length);
} else if (_shuffleIndicesInv.length < shuffleIndicesLength) {
_shuffleIndicesInv.addAll(
List.filled(shuffleIndicesLength - _shuffleIndicesInv.length, 0));
}
for (var i = 0; i < shuffleIndicesLength; i++) {
_shuffleIndicesInv[shuffleIndices[i]] = i;
}
print('shuffleIndicesInv: ${_shuffleIndicesInv?.length}');
} }
_registerAudioSource(AudioSource source) { _registerAudioSource(AudioSource source) {
@ -475,6 +582,7 @@ class AudioPlayer {
Future<Duration> _load(AudioSource source, Future<Duration> _load(AudioSource source,
{Duration initialPosition, int initialIndex}) async { {Duration initialPosition, int initialIndex}) async {
print("### _load with initialIndex=$initialIndex");
try { try {
if (!kIsWeb && source._requiresHeaders) { if (!kIsWeb && source._requiresHeaders) {
if (_proxy == null) { if (_proxy == null) {
@ -483,6 +591,8 @@ class AudioPlayer {
} }
} }
await source._setup(this); await source._setup(this);
source._shuffle(initialIndex: initialIndex ?? 0);
_updateShuffleIndices();
_durationFuture = (await _platform) _durationFuture = (await _platform)
.load(LoadRequest( .load(LoadRequest(
audioSourceMessage: source._toMessage(), audioSourceMessage: source._toMessage(),
@ -620,6 +730,19 @@ class AudioPlayer {
enabled ? ShuffleModeMessage.all : ShuffleModeMessage.none)); enabled ? ShuffleModeMessage.all : ShuffleModeMessage.none));
} }
/// Recursively shuffles the children of the currently loaded [AudioSource].
Future<void> shuffle() async {
print(
"shuffle. _disposed: $_disposed, currentIndex: $currentIndex, _audioSource: $_audioSource");
if (_disposed) return;
if (_audioSource == null) return;
_audioSource._shuffle(initialIndex: currentIndex);
_updateShuffleIndices();
print("Shuffle: $shuffleIndices");
await (await _platform).setShuffleOrder(
SetShuffleOrderRequest(audioSourceMessage: _audioSource._toMessage()));
}
/// Sets automaticallyWaitsToMinimizeStalling for AVPlayer in iOS 10.0 or later, defaults to true. /// Sets automaticallyWaitsToMinimizeStalling for AVPlayer in iOS 10.0 or later, defaults to true.
/// Has no effect on Android clients /// Has no effect on Android clients
Future<void> setAutomaticallyWaitsToMinimizeStalling( Future<void> setAutomaticallyWaitsToMinimizeStalling(
@ -657,7 +780,7 @@ class AudioPlayer {
Future<void> seekToNext() async { Future<void> seekToNext() async {
if (_disposed) return; if (_disposed) return;
if (hasNext) { if (hasNext) {
await seek(Duration.zero, index: currentIndex + 1); await seek(Duration.zero, index: _nextIndex);
} }
} }
@ -665,7 +788,7 @@ class AudioPlayer {
Future<void> seekToPrevious() async { Future<void> seekToPrevious() async {
if (_disposed) return; if (_disposed) return;
if (hasPrevious) { if (hasPrevious) {
await seek(Duration.zero, index: currentIndex - 1); await seek(Duration.zero, index: _previousIndex);
} }
} }
@ -694,6 +817,7 @@ class AudioPlayer {
await (await _platform).dispose(DisposeRequest()); await (await _platform).dispose(DisposeRequest());
} }
_audioSource = null; _audioSource = null;
print("### set _audioSource = $_audioSource (dispose)");
_audioSources.values.forEach((s) => s._dispose()); _audioSources.values.forEach((s) => s._dispose());
_audioSources.clear(); _audioSources.clear();
_proxy?.stop(); _proxy?.stop();
@ -704,6 +828,7 @@ class AudioPlayer {
await _volumeSubject.close(); await _volumeSubject.close();
await _speedSubject.close(); await _speedSubject.close();
await _sequenceSubject.close(); await _sequenceSubject.close();
await _shuffleIndicesSubject.close();
} }
} }
@ -940,10 +1065,26 @@ class SequenceState {
/// The index of the current source in the sequence. /// The index of the current source in the sequence.
final int currentIndex; final int currentIndex;
SequenceState(this.sequence, this.currentIndex); /// The current shuffle order
final List<int> shuffleIndices;
/// Whether shuffle mode is enabled.
final bool shuffleModeEnabled;
/// The current loop mode.
final LoopMode loopMode;
SequenceState(this.sequence, this.currentIndex, this.shuffleIndices,
this.shuffleModeEnabled, this.loopMode);
/// The current source in the sequence. /// The current source in the sequence.
IndexedAudioSource get currentSource => sequence[currentIndex]; IndexedAudioSource get currentSource => sequence[currentIndex];
/// The effective sequence. This is equivalent to [sequence]. If
/// [shuffleModeEnabled] is true, this is modulated by [shuffleIndices].
List<IndexedAudioSource> get effectiveSequence => shuffleModeEnabled
? shuffleIndices.map((i) => sequence[i]).toList()
: sequence;
} }
/// A local proxy HTTP server for making remote GET requests with headers. /// A local proxy HTTP server for making remote GET requests with headers.
@ -1105,6 +1246,8 @@ abstract class AudioSource {
player._registerAudioSource(this); player._registerAudioSource(this);
} }
void _shuffle({int initialIndex});
@mustCallSuper @mustCallSuper
void _dispose() { void _dispose() {
_player = null; _player = null;
@ -1116,6 +1259,8 @@ abstract class AudioSource {
List<IndexedAudioSource> get sequence; List<IndexedAudioSource> get sequence;
List<int> get shuffleIndices;
@override @override
int get hashCode => _id.hashCode; int get hashCode => _id.hashCode;
@ -1129,8 +1274,14 @@ abstract class IndexedAudioSource extends AudioSource {
IndexedAudioSource(this.tag); IndexedAudioSource(this.tag);
@override
void _shuffle({int initialIndex}) {}
@override @override
List<IndexedAudioSource> get sequence => [this]; List<IndexedAudioSource> get sequence => [this];
@override
List<int> get shuffleIndices => [0];
} }
/// An abstract class representing audio sources that are loaded from a URI. /// An abstract class representing audio sources that are loaded from a URI.
@ -1258,11 +1409,14 @@ class HlsAudioSource extends UriAudioSource {
class ConcatenatingAudioSource extends AudioSource { class ConcatenatingAudioSource extends AudioSource {
final List<AudioSource> children; final List<AudioSource> children;
final bool useLazyPreparation; final bool useLazyPreparation;
ShuffleOrder _shuffleOrder;
ConcatenatingAudioSource({ ConcatenatingAudioSource({
@required this.children, @required this.children,
this.useLazyPreparation = true, this.useLazyPreparation = true,
}); ShuffleOrder shuffleOrder,
}) : _shuffleOrder = shuffleOrder ?? DefaultShuffleOrder()
..insert(0, children.length);
@override @override
Future<void> _setup(AudioPlayer player) async { Future<void> _setup(AudioPlayer player) async {
@ -1272,28 +1426,60 @@ class ConcatenatingAudioSource extends AudioSource {
} }
} }
@override
void _shuffle({int initialIndex}) {
int localInitialIndex;
// si = index in [sequence]
// ci = index in [children] array.
for (var ci = 0, si = 0; ci < children.length; ci++) {
final child = children[ci];
final childLength = child.sequence.length;
final initialIndexWithinThisChild = initialIndex != null &&
initialIndex >= si &&
initialIndex < si + childLength;
if (initialIndexWithinThisChild) {
localInitialIndex = ci;
}
final childInitialIndex =
initialIndexWithinThisChild ? (initialIndex - si) : null;
child._shuffle(initialIndex: childInitialIndex);
si += childLength;
}
print(
"cat _shuffle (initialIndex=$initialIndex, localInitialIndex=$localInitialIndex)");
_shuffleOrder.shuffle(initialIndex: localInitialIndex);
}
/// (Untested) Appends an [AudioSource]. /// (Untested) Appends an [AudioSource].
Future<void> add(AudioSource audioSource) async { Future<void> add(AudioSource audioSource) async {
final index = children.length; final index = children.length;
children.add(audioSource); children.add(audioSource);
_shuffleOrder.insert(index, 1);
if (_player != null) { if (_player != null) {
_player._broadcastSequence(); _player._broadcastSequence();
await audioSource._setup(_player); await audioSource._setup(_player);
await (await _player._platform).concatenatingInsertAll( await (await _player._platform).concatenatingInsertAll(
ConcatenatingInsertAllRequest( ConcatenatingInsertAllRequest(
id: _id, index: index, children: [audioSource._toMessage()])); id: _id,
index: index,
children: [audioSource._toMessage()],
shuffleOrder: _shuffleOrder.indices));
} }
} }
/// (Untested) Inserts an [AudioSource] at [index]. /// (Untested) Inserts an [AudioSource] at [index].
Future<void> insert(int index, AudioSource audioSource) async { Future<void> insert(int index, AudioSource audioSource) async {
children.insert(index, audioSource); children.insert(index, audioSource);
_shuffleOrder.insert(index, 1);
if (_player != null) { if (_player != null) {
_player._broadcastSequence(); _player._broadcastSequence();
await audioSource._setup(_player); await audioSource._setup(_player);
await (await _player._platform).concatenatingInsertAll( await (await _player._platform).concatenatingInsertAll(
ConcatenatingInsertAllRequest( ConcatenatingInsertAllRequest(
id: _id, index: index, children: [audioSource._toMessage()])); id: _id,
index: index,
children: [audioSource._toMessage()],
shuffleOrder: _shuffleOrder.indices));
} }
} }
@ -1301,6 +1487,7 @@ class ConcatenatingAudioSource extends AudioSource {
Future<void> addAll(List<AudioSource> children) async { Future<void> addAll(List<AudioSource> children) async {
int index = this.children.length; int index = this.children.length;
this.children.addAll(children); this.children.addAll(children);
_shuffleOrder.insert(index, children.length);
if (_player != null) { if (_player != null) {
_player._broadcastSequence(); _player._broadcastSequence();
for (var child in children) { for (var child in children) {
@ -1310,13 +1497,15 @@ class ConcatenatingAudioSource extends AudioSource {
ConcatenatingInsertAllRequest( ConcatenatingInsertAllRequest(
id: _id, id: _id,
index: index, index: index,
children: children.map((child) => child._toMessage()).toList())); children: children.map((child) => child._toMessage()).toList(),
shuffleOrder: _shuffleOrder.indices));
} }
} }
/// (Untested) Insert multiple [AudioSource]s at [index]. /// (Untested) Insert multiple [AudioSource]s at [index].
Future<void> insertAll(int index, List<AudioSource> children) async { Future<void> insertAll(int index, List<AudioSource> children) async {
this.children.insertAll(index, children); this.children.insertAll(index, children);
_shuffleOrder.insert(index, children.length);
if (_player != null) { if (_player != null) {
_player._broadcastSequence(); _player._broadcastSequence();
for (var child in children) { for (var child in children) {
@ -1326,7 +1515,8 @@ class ConcatenatingAudioSource extends AudioSource {
ConcatenatingInsertAllRequest( ConcatenatingInsertAllRequest(
id: _id, id: _id,
index: index, index: index,
children: children.map((child) => child._toMessage()).toList())); children: children.map((child) => child._toMessage()).toList(),
shuffleOrder: _shuffleOrder.indices));
} }
} }
@ -1334,11 +1524,15 @@ class ConcatenatingAudioSource extends AudioSource {
/// [ConcatenatingAudioSource] has already been loaded. /// [ConcatenatingAudioSource] has already been loaded.
Future<void> removeAt(int index) async { Future<void> removeAt(int index) async {
children.removeAt(index); children.removeAt(index);
_shuffleOrder.removeRange(index, index + 1);
if (_player != null) { if (_player != null) {
_player._broadcastSequence(); _player._broadcastSequence();
await (await _player._platform).concatenatingRemoveRange( await (await _player._platform).concatenatingRemoveRange(
ConcatenatingRemoveRangeRequest( ConcatenatingRemoveRangeRequest(
id: _id, startIndex: index, endIndex: index + 1)); id: _id,
startIndex: index,
endIndex: index + 1,
shuffleOrder: _shuffleOrder.indices));
} }
} }
@ -1346,33 +1540,46 @@ class ConcatenatingAudioSource extends AudioSource {
/// to [end] exclusive. /// to [end] exclusive.
Future<void> removeRange(int start, int end) async { Future<void> removeRange(int start, int end) async {
children.removeRange(start, end); children.removeRange(start, end);
_shuffleOrder.removeRange(start, end);
if (_player != null) { if (_player != null) {
_player._broadcastSequence(); _player._broadcastSequence();
await (await _player._platform).concatenatingRemoveRange( await (await _player._platform).concatenatingRemoveRange(
ConcatenatingRemoveRangeRequest( ConcatenatingRemoveRangeRequest(
id: _id, startIndex: start, endIndex: end)); id: _id,
startIndex: start,
endIndex: end,
shuffleOrder: _shuffleOrder.indices));
} }
} }
/// (Untested) Moves an [AudioSource] from [currentIndex] to [newIndex]. /// (Untested) Moves an [AudioSource] from [currentIndex] to [newIndex].
Future<void> move(int currentIndex, int newIndex) async { Future<void> move(int currentIndex, int newIndex) async {
children.insert(newIndex, children.removeAt(currentIndex)); children.insert(newIndex, children.removeAt(currentIndex));
_shuffleOrder.removeRange(currentIndex, currentIndex + 1);
_shuffleOrder.insert(newIndex, 1);
if (_player != null) { if (_player != null) {
_player._broadcastSequence(); _player._broadcastSequence();
await (await _player._platform).concatenatingMove( await (await _player._platform).concatenatingMove(
ConcatenatingMoveRequest( ConcatenatingMoveRequest(
id: _id, currentIndex: currentIndex, newIndex: newIndex)); id: _id,
currentIndex: currentIndex,
newIndex: newIndex,
shuffleOrder: _shuffleOrder.indices));
} }
} }
/// (Untested) Removes all [AudioSources]. /// (Untested) Removes all [AudioSources].
Future<void> clear() async { Future<void> clear() async {
children.clear(); children.clear();
_shuffleOrder.clear();
if (_player != null) { if (_player != null) {
_player._broadcastSequence(); _player._broadcastSequence();
await (await _player._platform).concatenatingRemoveRange( await (await _player._platform).concatenatingRemoveRange(
ConcatenatingRemoveRangeRequest( ConcatenatingRemoveRangeRequest(
id: _id, startIndex: 0, endIndex: children.length)); id: _id,
startIndex: 0,
endIndex: children.length,
shuffleOrder: _shuffleOrder.indices));
} }
} }
@ -1385,6 +1592,25 @@ class ConcatenatingAudioSource extends AudioSource {
List<IndexedAudioSource> get sequence => List<IndexedAudioSource> get sequence =>
children.expand((s) => s.sequence).toList(); children.expand((s) => s.sequence).toList();
@override
List<int> get shuffleIndices {
var offset = 0;
final childIndicesList = <List<int>>[];
for (var child in children) {
final childIndices = child.shuffleIndices.map((i) => i + offset).toList();
childIndicesList.add(childIndices);
offset += childIndices.length;
}
final indices = <int>[];
for (var index in _shuffleOrder.indices) {
final childIndices = childIndicesList[index];
//print("child indices: $childIndices");
indices.addAll(childIndices);
}
print("### returning: $indices");
return indices;
}
@override @override
bool get _requiresHeaders => bool get _requiresHeaders =>
children.any((source) => source._requiresHeaders); children.any((source) => source._requiresHeaders);
@ -1393,7 +1619,8 @@ class ConcatenatingAudioSource extends AudioSource {
AudioSourceMessage _toMessage() => ConcatenatingAudioSourceMessage( AudioSourceMessage _toMessage() => ConcatenatingAudioSourceMessage(
id: _id, id: _id,
children: children.map((child) => child._toMessage()).toList(), children: children.map((child) => child._toMessage()).toList(),
useLazyPreparation: useLazyPreparation); useLazyPreparation: useLazyPreparation,
shuffleOrder: _shuffleOrder.indices);
} }
/// An [AudioSource] that clips the audio of a [UriAudioSource] between a /// An [AudioSource] that clips the audio of a [UriAudioSource] between a
@ -1449,10 +1676,16 @@ class LoopingAudioSource extends AudioSource {
await child._setup(player); await child._setup(player);
} }
@override
void _shuffle({int initialIndex}) {}
@override @override
List<IndexedAudioSource> get sequence => List<IndexedAudioSource> get sequence =>
List.generate(count, (i) => child).expand((s) => s.sequence).toList(); List.generate(count, (i) => child).expand((s) => s.sequence).toList();
@override
List<int> get shuffleIndices => List.generate(count, (i) => i);
@override @override
bool get _requiresHeaders => child._requiresHeaders; bool get _requiresHeaders => child._requiresHeaders;
@ -1461,4 +1694,90 @@ class LoopingAudioSource extends AudioSource {
id: _id, child: child._toMessage(), count: count); id: _id, child: child._toMessage(), count: count);
} }
/// Defines the algorithm for shuffling the order of a
/// [ConcatenatingAudioSource]. See [DefaultShuffleOrder] for a default
/// implementation.
abstract class ShuffleOrder {
/// The shuffled list of indices of [AudioSource]s to play. For example,
/// [2,0,1] specifies to play the 3rd, then the 1st, then the 2nd item.
List<int> get indices;
/// Shuffles the [indices]. If [initialIndex] is provided, the [indices]
/// should be shuffled so that [initialIndex] appears in `indices[0]`.
void shuffle({int initialIndex});
/// Inserts [count] new consecutive indices starting from [index] into
/// [indices], at random positions.
void insert(int index, int count);
/// Removes the indices that are `>= start` and `< end`.
void removeRange(int start, int end);
/// Removes all indices.
void clear();
}
/// A default implementation of [ShuffleOrder].
class DefaultShuffleOrder extends ShuffleOrder {
final _random;
@override
final indices = <int>[];
DefaultShuffleOrder({Random random}) : _random = random ?? Random();
@override
void shuffle({int initialIndex}) {
assert(initialIndex == null || indices.contains(initialIndex));
print(
"### order shuffle, initialIndex: $initialIndex (existing: $indices)");
if (indices.length <= 1) return;
indices.shuffle(_random);
print("now $indices");
if (initialIndex == null) return;
final initialPos = 0;
final swapPos = indices.indexOf(initialIndex);
// Swap the indices at initialPos and swapPos.
final swapIndex = indices[initialPos];
indices[initialPos] = initialIndex;
indices[swapPos] = swapIndex;
print("final $indices");
}
@override
void insert(int index, int count) {
// Offset indices after insertion point.
for (var i = 0; i < indices.length; i++) {
if (indices[i] >= index) {
indices[i] += count;
}
}
// Insert new indices at random positions after currentIndex.
final newIndices = List.generate(count, (i) => index + i);
for (var newIndex in newIndices) {
final insertionIndex = _random.nextInt(indices.length + 1);
indices.insert(insertionIndex, newIndex);
}
}
@override
void removeRange(int start, int end) {
final count = end - start;
// Remove old indices.
final oldIndices = List.generate(count, (i) => start + i).toSet();
indices.removeWhere(oldIndices.contains);
// Offset indices after deletion point.
for (var i = 0; i < indices.length; i++) {
if (indices[i] >= end) {
indices[i] -= count;
}
}
}
@override
void clear() {
indices.clear();
}
}
enum LoopMode { off, one, all } enum LoopMode { off, one, all }

View File

@ -7,7 +7,7 @@
- (instancetype)initWithId:(NSString *)sid; - (instancetype)initWithId:(NSString *)sid;
- (int)buildSequence:(NSMutableArray *)sequence treeIndex:(int)treeIndex; - (int)buildSequence:(NSMutableArray *)sequence treeIndex:(int)treeIndex;
- (void)findById:(NSString *)sourceId matches:(NSMutableArray<AudioSource *> *)matches; - (void)findById:(NSString *)sourceId matches:(NSMutableArray<AudioSource *> *)matches;
- (NSArray<NSNumber *> *)getShuffleOrder; - (NSArray<NSNumber *> *)getShuffleIndices;
- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex; - (void)decodeShuffleOrder:(NSDictionary *)dict;
@end @end

View File

@ -5,9 +5,10 @@
@property (readonly, nonatomic) int count; @property (readonly, nonatomic) int count;
- (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray<AudioSource *> *)audioSources; - (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray<AudioSource *> *)audioSources shuffleOrder:(NSMutableArray<NSNumber *> *)shuffleOrder;
- (void)insertSource:(AudioSource *)audioSource atIndex:(int)index; - (void)insertSource:(AudioSource *)audioSource atIndex:(int)index;
- (void)removeSourcesFromIndex:(int)start toIndex:(int)end; - (void)removeSourcesFromIndex:(int)start toIndex:(int)end;
- (void)moveSourceFromIndex:(int)currentIndex toIndex:(int)newIndex; - (void)moveSourceFromIndex:(int)currentIndex toIndex:(int)newIndex;
- (void)setShuffleOrder:(NSArray<NSNumber *> *)shuffleOrder;
@end @end

View File

@ -28,7 +28,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.5.0-nullsafety.1" version: "2.5.0-nullsafety.3"
audio_session: audio_session:
dependency: "direct main" dependency: "direct main"
description: description:
@ -42,7 +42,7 @@ packages:
name: boolean_selector name: boolean_selector
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.1" version: "2.1.0-nullsafety.3"
build: build:
dependency: transitive dependency: transitive
description: description:
@ -70,14 +70,14 @@ packages:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0-nullsafety.3" version: "1.1.0-nullsafety.5"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
name: charcode name: charcode
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0-nullsafety.1" version: "1.2.0-nullsafety.3"
cli_util: cli_util:
dependency: transitive dependency: transitive
description: description:
@ -91,7 +91,7 @@ packages:
name: clock name: clock
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0-nullsafety.1" version: "1.1.0-nullsafety.3"
code_builder: code_builder:
dependency: transitive dependency: transitive
description: description:
@ -105,7 +105,7 @@ packages:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.15.0-nullsafety.3" version: "1.15.0-nullsafety.5"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -133,7 +133,7 @@ packages:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0-nullsafety.1" version: "1.2.0-nullsafety.3"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
@ -190,20 +190,20 @@ packages:
name: js name: js
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.2" version: "0.6.3-nullsafety.3"
just_audio_platform_interface: just_audio_platform_interface:
dependency: "direct main" dependency: "direct main"
description: description:
name: just_audio_platform_interface path: "../just_audio_platform_interface"
url: "https://pub.dartlang.org" relative: true
source: hosted source: path
version: "1.1.1" version: "1.1.1"
just_audio_web: just_audio_web:
dependency: "direct main" dependency: "direct main"
description: description:
name: just_audio_web path: "../just_audio_web"
url: "https://pub.dartlang.org" relative: true
source: hosted source: path
version: "0.1.1" version: "0.1.1"
logging: logging:
dependency: transitive dependency: transitive
@ -218,14 +218,14 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.10-nullsafety.1" version: "0.12.10-nullsafety.3"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0-nullsafety.3" version: "1.3.0-nullsafety.6"
mockito: mockito:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -260,7 +260,7 @@ packages:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0-nullsafety.1" version: "1.8.0-nullsafety.3"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -363,49 +363,49 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0-nullsafety.2" version: "1.8.0-nullsafety.4"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.10.0-nullsafety.1" version: "1.10.0-nullsafety.6"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.1" version: "2.1.0-nullsafety.3"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0-nullsafety.1" version: "1.1.0-nullsafety.3"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0-nullsafety.1" version: "1.2.0-nullsafety.3"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.19-nullsafety.2" version: "0.2.19-nullsafety.6"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0-nullsafety.3" version: "1.3.0-nullsafety.5"
uuid: uuid:
dependency: "direct main" dependency: "direct main"
description: description:
@ -419,7 +419,7 @@ packages:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.3" version: "2.1.0-nullsafety.5"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
@ -449,5 +449,5 @@ packages:
source: hosted source: hosted
version: "2.2.1" version: "2.2.1"
sdks: sdks:
dart: ">=2.10.0 <2.11.0" dart: ">=2.12.0-0.0 <3.0.0"
flutter: ">=1.12.13+hotfix.5 <2.0.0" flutter: ">=1.12.13+hotfix.5 <2.0.0"

View File

@ -8,8 +8,13 @@ environment:
flutter: ">=1.12.13+hotfix.5" flutter: ">=1.12.13+hotfix.5"
dependencies: dependencies:
just_audio_platform_interface: ^1.1.1 # TODO: change this back when the new platform interface is published.
just_audio_web: ^0.1.1 # just_audio_platform_interface: ^1.1.1
just_audio_platform_interface:
path: ../just_audio_platform_interface
# just_audio_web: ^0.1.1
just_audio_web:
path: ../just_audio_web
audio_session: ^0.0.9 audio_session: ^0.0.9
rxdart: ^0.24.1 rxdart: ^0.24.1
path: ^1.6.4 path: ^1.6.4

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -44,6 +45,12 @@ void runTests() {
} }
} }
void checkIndices(List<int> indices, int length) {
expect(indices.length, length);
final sorted = List.of(indices)..sort();
expect(sorted, equals(List.generate(indices.length, (i) => i)));
}
setUp(() { setUp(() {
audioSessionChannel.setMockMethodCallHandler((MethodCall methodCall) async { audioSessionChannel.setMockMethodCallHandler((MethodCall methodCall) async {
return null; return null;
@ -381,6 +388,95 @@ void runTests() {
expect(AudioSource.uri(Uri.parse('https://a.a/a#.mpd')) is DashAudioSource, expect(AudioSource.uri(Uri.parse('https://a.a/a#.mpd')) is DashAudioSource,
equals(true)); equals(true));
}); });
test('shuffle order', () async {
final shuffleOrder1 = DefaultShuffleOrder(random: Random(1001));
checkIndices(shuffleOrder1.indices, 0);
//expect(shuffleOrder1.indices, equals([]));
shuffleOrder1.insert(0, 5);
//expect(shuffleOrder1.indices, equals([3, 0, 2, 4, 1]));
checkIndices(shuffleOrder1.indices, 5);
shuffleOrder1.insert(3, 2);
checkIndices(shuffleOrder1.indices, 7);
shuffleOrder1.insert(0, 2);
checkIndices(shuffleOrder1.indices, 9);
shuffleOrder1.insert(9, 2);
checkIndices(shuffleOrder1.indices, 11);
final indices1 = List.of(shuffleOrder1.indices);
shuffleOrder1.shuffle();
expect(shuffleOrder1.indices, isNot(indices1));
checkIndices(shuffleOrder1.indices, 11);
final indices2 = List.of(shuffleOrder1.indices);
shuffleOrder1.shuffle(initialIndex: 5);
expect(shuffleOrder1.indices[0], equals(5));
expect(shuffleOrder1.indices, isNot(indices2));
checkIndices(shuffleOrder1.indices, 11);
shuffleOrder1.removeRange(4, 6);
checkIndices(shuffleOrder1.indices, 9);
shuffleOrder1.removeRange(0, 2);
checkIndices(shuffleOrder1.indices, 7);
shuffleOrder1.removeRange(5, 7);
checkIndices(shuffleOrder1.indices, 5);
shuffleOrder1.removeRange(0, 5);
checkIndices(shuffleOrder1.indices, 0);
shuffleOrder1.insert(0, 5);
checkIndices(shuffleOrder1.indices, 5);
shuffleOrder1.clear();
checkIndices(shuffleOrder1.indices, 0);
});
test('shuffle', () async {
AudioSource createSource() => ConcatenatingAudioSource(
shuffleOrder: DefaultShuffleOrder(random: Random(1001)),
children: [
LoopingAudioSource(
count: 2,
child: ClippingAudioSource(
start: Duration(seconds: 60),
end: Duration(seconds: 65),
child: AudioSource.uri(Uri.parse("https://foo.foo/foo.mp3")),
tag: 'a',
),
),
AudioSource.uri(
Uri.parse("https://bar.bar/bar.mp3"),
tag: 'b',
),
AudioSource.uri(
Uri.parse("https://baz.baz/baz.mp3"),
tag: 'c',
),
ClippingAudioSource(
child: AudioSource.uri(
Uri.parse("https://baz.baz/baz.mp3"),
tag: 'd',
),
),
],
);
final source1 = createSource();
//expect(source1.shuffleIndices, [4, 0, 1, 3, 2]);
checkIndices(source1.shuffleIndices, 5);
expect(source1.shuffleIndices.skipWhile((i) => i != 0).skip(1).first,
equals(1));
final player1 = AudioPlayer();
await player1.load(source1);
checkIndices(player1.shuffleIndices, 5);
expect(player1.shuffleIndices.first, equals(0));
await player1.seek(Duration.zero, index: 3);
await player1.shuffle();
checkIndices(player1.shuffleIndices, 5);
expect(player1.shuffleIndices.first, equals(3));
final source2 = createSource();
final player2 = AudioPlayer();
await player2.load(source2, initialIndex: 3);
checkIndices(player2.shuffleIndices, 5);
expect(player2.shuffleIndices.first, equals(3));
});
} }
class MockJustAudio extends Mock class MockJustAudio extends Mock
@ -543,6 +639,12 @@ class MockAudioPlayer implements AudioPlayerPlatform {
return SetShuffleModeResponse(); return SetShuffleModeResponse();
} }
@override
Future<SetShuffleOrderResponse> setShuffleOrder(
SetShuffleOrderRequest request) async {
return SetShuffleOrderResponse();
}
@override @override
Future<SetSpeedResponse> setSpeed(SetSpeedRequest request) async { Future<SetSpeedResponse> setSpeed(SetSpeedRequest request) async {
_speed = request.speed; _speed = request.speed;

View File

@ -103,6 +103,12 @@ abstract class AudioPlayerPlatform {
throw UnimplementedError("setShuffleMode() has not been implemented."); throw UnimplementedError("setShuffleMode() has not been implemented.");
} }
/// Sets the shuffle order.
Future<SetShuffleOrderResponse> setShuffleOrder(
SetShuffleOrderRequest request) {
throw UnimplementedError("setShuffleOrder() has not been implemented.");
}
/// On iOS and macOS, sets the automaticallyWaitsToMinimizeStalling option, /// On iOS and macOS, sets the automaticallyWaitsToMinimizeStalling option,
/// and does nothing on other platforms. /// and does nothing on other platforms.
Future<SetAutomaticallyWaitsToMinimizeStallingResponse> Future<SetAutomaticallyWaitsToMinimizeStallingResponse>
@ -431,6 +437,25 @@ class SetShuffleModeResponse {
/// The shuffle mode communicated to the platform implementation. /// The shuffle mode communicated to the platform implementation.
enum ShuffleModeMessage { none, all } enum ShuffleModeMessage { none, all }
/// Information communicated to the platform implementation when setting the
/// shuffle order.
class SetShuffleOrderRequest {
final AudioSourceMessage audioSourceMessage;
SetShuffleOrderRequest({@required this.audioSourceMessage});
Map<dynamic, dynamic> toMap() => {
'audioSource': audioSourceMessage.toMap(),
};
}
/// Information returned by the platform implementation after setting the
/// shuffle order.
class SetShuffleOrderResponse {
static SetShuffleOrderResponse fromMap(Map<dynamic, dynamic> map) =>
SetShuffleOrderResponse();
}
/// Information communicated to the platform implementation when setting the /// Information communicated to the platform implementation when setting the
/// automaticallyWaitsToMinimizeStalling option. /// automaticallyWaitsToMinimizeStalling option.
class SetAutomaticallyWaitsToMinimizeStallingRequest { class SetAutomaticallyWaitsToMinimizeStallingRequest {
@ -515,17 +540,20 @@ class ConcatenatingInsertAllRequest {
final String id; final String id;
final int index; final int index;
final List<AudioSourceMessage> children; final List<AudioSourceMessage> children;
final List<int> shuffleOrder;
ConcatenatingInsertAllRequest({ ConcatenatingInsertAllRequest({
@required this.id, @required this.id,
@required this.index, @required this.index,
@required this.children, @required this.children,
@required this.shuffleOrder,
}); });
Map<dynamic, dynamic> toMap() => { Map<dynamic, dynamic> toMap() => {
'id': id, 'id': id,
'index': index, 'index': index,
'children': children.map((child) => child.toMap()).toList(), 'children': children.map((child) => child.toMap()).toList(),
'shuffleOrder': shuffleOrder,
}; };
} }
@ -542,17 +570,20 @@ class ConcatenatingRemoveRangeRequest {
final String id; final String id;
final int startIndex; final int startIndex;
final int endIndex; final int endIndex;
final List<int> shuffleOrder;
ConcatenatingRemoveRangeRequest({ ConcatenatingRemoveRangeRequest({
@required this.id, @required this.id,
@required this.startIndex, @required this.startIndex,
@required this.endIndex, @required this.endIndex,
@required this.shuffleOrder,
}); });
Map<dynamic, dynamic> toMap() => { Map<dynamic, dynamic> toMap() => {
'id': id, 'id': id,
'startIndex': startIndex, 'startIndex': startIndex,
'endIndex': endIndex, 'endIndex': endIndex,
'shuffleOrder': shuffleOrder,
}; };
} }
@ -569,17 +600,20 @@ class ConcatenatingMoveRequest {
final String id; final String id;
final int currentIndex; final int currentIndex;
final int newIndex; final int newIndex;
final List<int> shuffleOrder;
ConcatenatingMoveRequest({ ConcatenatingMoveRequest({
@required this.id, @required this.id,
@required this.currentIndex, @required this.currentIndex,
@required this.newIndex, @required this.newIndex,
@required this.shuffleOrder,
}); });
Map<dynamic, dynamic> toMap() => { Map<dynamic, dynamic> toMap() => {
'id': id, 'id': id,
'currentIndex': currentIndex, 'currentIndex': currentIndex,
'newIndex': newIndex, 'newIndex': newIndex,
'shuffleOrder': shuffleOrder,
}; };
} }
@ -678,11 +712,13 @@ class HlsAudioSourceMessage extends UriAudioSourceMessage {
class ConcatenatingAudioSourceMessage extends AudioSourceMessage { class ConcatenatingAudioSourceMessage extends AudioSourceMessage {
final List<AudioSourceMessage> children; final List<AudioSourceMessage> children;
final bool useLazyPreparation; final bool useLazyPreparation;
final List<int> shuffleOrder;
ConcatenatingAudioSourceMessage({ ConcatenatingAudioSourceMessage({
@required String id, @required String id,
@required this.children, @required this.children,
@required this.useLazyPreparation, @required this.useLazyPreparation,
@required this.shuffleOrder,
}) : super(id: id); }) : super(id: id);
@override @override
@ -691,6 +727,7 @@ class ConcatenatingAudioSourceMessage extends AudioSourceMessage {
'id': id, 'id': id,
'children': children.map((child) => child.toMap()).toList(), 'children': children.map((child) => child.toMap()).toList(),
'useLazyPreparation': useLazyPreparation, 'useLazyPreparation': useLazyPreparation,
'shuffleOrder': shuffleOrder,
}; };
} }

View File

@ -79,6 +79,13 @@ class MethodChannelAudioPlayer extends AudioPlayerPlatform {
await _channel.invokeMethod('setShuffleMode', request?.toMap())); await _channel.invokeMethod('setShuffleMode', request?.toMap()));
} }
@override
Future<SetShuffleOrderResponse> setShuffleOrder(
SetShuffleOrderRequest request) async {
return SetShuffleOrderResponse.fromMap(
await _channel.invokeMethod('setShuffleOrder', request?.toMap()));
}
@override @override
Future<SetAutomaticallyWaitsToMinimizeStallingResponse> Future<SetAutomaticallyWaitsToMinimizeStallingResponse>
setAutomaticallyWaitsToMinimizeStalling( setAutomaticallyWaitsToMinimizeStalling(

View File

@ -112,7 +112,7 @@ class Html5AudioPlayer extends JustAudioPlayer {
final sequence = _audioSourcePlayer.sequence; final sequence = _audioSourcePlayer.sequence;
List<int> order = List<int>(sequence.length); List<int> order = List<int>(sequence.length);
if (_shuffleModeEnabled) { if (_shuffleModeEnabled) {
order = _audioSourcePlayer.shuffleOrder; order = _audioSourcePlayer.shuffleIndices;
} else { } else {
for (var i = 0; i < order.length; i++) { for (var i = 0; i < order.length; i++) {
order[i] = i; order[i] = i;
@ -181,9 +181,6 @@ class Html5AudioPlayer extends JustAudioPlayer {
_currentAudioSourcePlayer?.pause(); _currentAudioSourcePlayer?.pause();
_audioSourcePlayer = getAudioSource(request.audioSourceMessage); _audioSourcePlayer = getAudioSource(request.audioSourceMessage);
_index = request.initialIndex ?? 0; _index = request.initialIndex ?? 0;
if (_shuffleModeEnabled) {
_audioSourcePlayer?.shuffle(0, _index);
}
final duration = await _currentAudioSourcePlayer.load(); final duration = await _currentAudioSourcePlayer.load();
if (request.initialPosition != null) { if (request.initialPosition != null) {
await _currentAudioSourcePlayer await _currentAudioSourcePlayer
@ -259,12 +256,30 @@ class Html5AudioPlayer extends JustAudioPlayer {
Future<SetShuffleModeResponse> setShuffleMode( Future<SetShuffleModeResponse> setShuffleMode(
SetShuffleModeRequest request) async { SetShuffleModeRequest request) async {
_shuffleModeEnabled = request.shuffleMode == ShuffleModeMessage.all; _shuffleModeEnabled = request.shuffleMode == ShuffleModeMessage.all;
if (_shuffleModeEnabled) {
_audioSourcePlayer?.shuffle(0, _index);
}
return SetShuffleModeResponse(); return SetShuffleModeResponse();
} }
@override
Future<SetShuffleOrderResponse> setShuffleOrder(
SetShuffleOrderRequest request) async {
void internalSetShuffleOrder(AudioSourceMessage sourceMessage) {
final audioSourcePlayer = _audioSourcePlayers[sourceMessage.id];
if (audioSourcePlayer == null) return;
if (sourceMessage is ConcatenatingAudioSourceMessage &&
audioSourcePlayer is ConcatenatingAudioSourcePlayer) {
audioSourcePlayer.setShuffleOrder(sourceMessage.shuffleOrder);
for (var childMessage in sourceMessage.children) {
internalSetShuffleOrder(childMessage);
}
} else if (sourceMessage is LoopingAudioSourceMessage) {
internalSetShuffleOrder(sourceMessage.child);
}
}
internalSetShuffleOrder(request.audioSourceMessage);
return SetShuffleOrderResponse();
}
@override @override
Future<SeekResponse> seek(SeekRequest request) async { Future<SeekResponse> seek(SeekRequest request) async {
await _seek(request.position.inMilliseconds, request.index); await _seek(request.position.inMilliseconds, request.index);
@ -294,6 +309,7 @@ class Html5AudioPlayer extends JustAudioPlayer {
ConcatenatingInsertAllRequest request) async { ConcatenatingInsertAllRequest request) async {
_concatenating(request.id) _concatenating(request.id)
.insertAll(request.index, getAudioSources(request.children)); .insertAll(request.index, getAudioSources(request.children));
_concatenating(request.id).setShuffleOrder(request.shuffleOrder);
if (request.index <= _index) { if (request.index <= _index) {
_index += request.children.length; _index += request.children.length;
} }
@ -309,6 +325,7 @@ class Html5AudioPlayer extends JustAudioPlayer {
} }
_concatenating(request.id) _concatenating(request.id)
.removeRange(request.startIndex, request.endIndex); .removeRange(request.startIndex, request.endIndex);
_concatenating(request.id).setShuffleOrder(request.shuffleOrder);
if (_index >= request.startIndex && _index < request.endIndex) { if (_index >= request.startIndex && _index < request.endIndex) {
// Skip backward if there's nothing after this // Skip backward if there's nothing after this
if (request.startIndex >= _audioSourcePlayer.sequence.length) { if (request.startIndex >= _audioSourcePlayer.sequence.length) {
@ -332,6 +349,7 @@ class Html5AudioPlayer extends JustAudioPlayer {
Future<ConcatenatingMoveResponse> concatenatingMove( Future<ConcatenatingMoveResponse> concatenatingMove(
ConcatenatingMoveRequest request) async { ConcatenatingMoveRequest request) async {
_concatenating(request.id).move(request.currentIndex, request.newIndex); _concatenating(request.id).move(request.currentIndex, request.newIndex);
_concatenating(request.id).setShuffleOrder(request.shuffleOrder);
if (request.currentIndex == _index) { if (request.currentIndex == _index) {
_index = request.newIndex; _index = request.newIndex;
} else if (request.currentIndex < _index && request.newIndex >= _index) { } else if (request.currentIndex < _index && request.newIndex >= _index) {
@ -401,7 +419,8 @@ class Html5AudioPlayer extends JustAudioPlayer {
this, this,
audioSourceMessage.id, audioSourceMessage.id,
getAudioSources(audioSourceMessage.children), getAudioSources(audioSourceMessage.children),
audioSourceMessage.useLazyPreparation); audioSourceMessage.useLazyPreparation,
audioSourceMessage.shuffleOrder);
} else if (audioSourceMessage is ClippingAudioSourceMessage) { } else if (audioSourceMessage is ClippingAudioSourceMessage) {
return ClippingAudioSourcePlayer( return ClippingAudioSourcePlayer(
this, this,
@ -426,9 +445,7 @@ abstract class AudioSourcePlayer {
List<IndexedAudioSourcePlayer> get sequence; List<IndexedAudioSourcePlayer> get sequence;
List<int> get shuffleOrder; List<int> get shuffleIndices;
int shuffle(int treeIndex, int currentIndex);
} }
abstract class IndexedAudioSourcePlayer extends AudioSourcePlayer { abstract class IndexedAudioSourcePlayer extends AudioSourcePlayer {
@ -455,9 +472,6 @@ abstract class IndexedAudioSourcePlayer extends AudioSourcePlayer {
AudioElement get _audioElement => html5AudioPlayer._audioElement; AudioElement get _audioElement => html5AudioPlayer._audioElement;
@override
int shuffle(int treeIndex, int currentIndex) => treeIndex + 1;
@override @override
String toString() => "${this.runtimeType}"; String toString() => "${this.runtimeType}";
} }
@ -477,7 +491,7 @@ abstract class UriAudioSourcePlayer extends IndexedAudioSourcePlayer {
List<IndexedAudioSourcePlayer> get sequence => [this]; List<IndexedAudioSourcePlayer> get sequence => [this];
@override @override
List<int> get shuffleOrder => [0]; List<int> get shuffleIndices => [0];
@override @override
Future<Duration> load() async { Future<Duration> load() async {
@ -566,33 +580,13 @@ class HlsAudioSourcePlayer extends UriAudioSourcePlayer {
} }
class ConcatenatingAudioSourcePlayer extends AudioSourcePlayer { class ConcatenatingAudioSourcePlayer extends AudioSourcePlayer {
static List<int> generateShuffleOrder(int length, [int firstIndex]) {
final shuffleOrder = List<int>(length);
for (var i = 0; i < length; i++) {
final j = _random.nextInt(i + 1);
shuffleOrder[i] = shuffleOrder[j];
shuffleOrder[j] = i;
}
if (firstIndex != null) {
for (var i = 1; i < length; i++) {
if (shuffleOrder[i] == firstIndex) {
final v = shuffleOrder[0];
shuffleOrder[0] = shuffleOrder[i];
shuffleOrder[i] = v;
break;
}
}
}
return shuffleOrder;
}
final List<AudioSourcePlayer> audioSourcePlayers; final List<AudioSourcePlayer> audioSourcePlayers;
final bool useLazyPreparation; final bool useLazyPreparation;
List<int> _shuffleOrder; List<int> _shuffleOrder;
ConcatenatingAudioSourcePlayer(Html5AudioPlayer html5AudioPlayer, String id, ConcatenatingAudioSourcePlayer(Html5AudioPlayer html5AudioPlayer, String id,
this.audioSourcePlayers, this.useLazyPreparation) this.audioSourcePlayers, this.useLazyPreparation, List<int> shuffleOrder)
: _shuffleOrder = generateShuffleOrder(audioSourcePlayers.length), : _shuffleOrder = shuffleOrder,
super(html5AudioPlayer, id); super(html5AudioPlayer, id);
@override @override
@ -600,14 +594,14 @@ class ConcatenatingAudioSourcePlayer extends AudioSourcePlayer {
audioSourcePlayers.expand((p) => p.sequence).toList(); audioSourcePlayers.expand((p) => p.sequence).toList();
@override @override
List<int> get shuffleOrder { List<int> get shuffleIndices {
final order = <int>[]; final order = <int>[];
var offset = order.length; var offset = order.length;
final childOrders = <List<int>>[]; final childOrders = <List<int>>[];
for (var audioSourcePlayer in audioSourcePlayers) { for (var audioSourcePlayer in audioSourcePlayers) {
final childShuffleOrder = audioSourcePlayer.shuffleOrder; final childShuffleIndices = audioSourcePlayer.shuffleIndices;
childOrders.add(childShuffleOrder.map((i) => i + offset).toList()); childOrders.add(childShuffleIndices.map((i) => i + offset).toList());
offset += childShuffleOrder.length; offset += childShuffleIndices.length;
} }
for (var i = 0; i < childOrders.length; i++) { for (var i = 0; i < childOrders.length; i++) {
order.addAll(childOrders[_shuffleOrder[i]]); order.addAll(childOrders[_shuffleOrder[i]]);
@ -615,21 +609,8 @@ class ConcatenatingAudioSourcePlayer extends AudioSourcePlayer {
return order; return order;
} }
@override void setShuffleOrder(List<int> shuffleOrder) {
int shuffle(int treeIndex, int currentIndex) { _shuffleOrder = shuffleOrder;
int currentChildIndex;
for (var i = 0; i < audioSourcePlayers.length; i++) {
final indexBefore = treeIndex;
final child = audioSourcePlayers[i];
treeIndex = child.shuffle(treeIndex, currentIndex);
if (currentIndex >= indexBefore && currentIndex < treeIndex) {
currentChildIndex = i;
} else {}
}
// Shuffle so that the current child is first in the shuffle order
_shuffleOrder =
generateShuffleOrder(audioSourcePlayers.length, currentChildIndex);
return treeIndex;
} }
insertAll(int index, List<AudioSourcePlayer> players) { insertAll(int index, List<AudioSourcePlayer> players) {
@ -639,8 +620,6 @@ class ConcatenatingAudioSourcePlayer extends AudioSourcePlayer {
_shuffleOrder[i] += players.length; _shuffleOrder[i] += players.length;
} }
} }
_shuffleOrder.addAll(
List.generate(players.length, (i) => index + i).toList()..shuffle());
} }
removeRange(int start, int end) { removeRange(int start, int end) {
@ -650,7 +629,6 @@ class ConcatenatingAudioSourcePlayer extends AudioSourcePlayer {
_shuffleOrder[i] -= (end - start); _shuffleOrder[i] -= (end - start);
} }
} }
_shuffleOrder.removeWhere((i) => i >= start && i < end);
} }
move(int currentIndex, int newIndex) { move(int currentIndex, int newIndex) {
@ -675,7 +653,7 @@ class ClippingAudioSourcePlayer extends IndexedAudioSourcePlayer {
List<IndexedAudioSourcePlayer> get sequence => [this]; List<IndexedAudioSourcePlayer> get sequence => [this];
@override @override
List<int> get shuffleOrder => [0]; List<int> get shuffleIndices => [0];
@override @override
Future<Duration> load() async { Future<Duration> load() async {
@ -799,22 +777,14 @@ class LoopingAudioSourcePlayer extends AudioSourcePlayer {
.toList(); .toList();
@override @override
List<int> get shuffleOrder { List<int> get shuffleIndices {
final order = <int>[]; final order = <int>[];
var offset = order.length; var offset = order.length;
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
final childShuffleOrder = audioSourcePlayer.shuffleOrder; final childShuffleOrder = audioSourcePlayer.shuffleIndices;
order.addAll(childShuffleOrder.map((i) => i + offset).toList()); order.addAll(childShuffleOrder.map((i) => i + offset).toList());
offset += childShuffleOrder.length; offset += childShuffleOrder.length;
} }
return order; return order;
} }
@override
int shuffle(int treeIndex, int currentIndex) {
for (var i = 0; i < count; i++) {
treeIndex = audioSourcePlayer.shuffle(treeIndex, currentIndex);
}
return treeIndex;
}
} }

View File

@ -28,9 +28,9 @@ packages:
just_audio_platform_interface: just_audio_platform_interface:
dependency: "direct main" dependency: "direct main"
description: description:
name: just_audio_platform_interface path: "../just_audio_platform_interface"
url: "https://pub.dartlang.org" relative: true
source: hosted source: path
version: "1.1.1" version: "1.1.1"
meta: meta:
dependency: "direct main" dependency: "direct main"

View File

@ -11,7 +11,10 @@ flutter:
fileName: just_audio_web.dart fileName: just_audio_web.dart
dependencies: dependencies:
just_audio_platform_interface: ^1.1.1 # TODO: change this back when the new platform interface is published.
# just_audio_platform_interface: ^1.1.1
just_audio_platform_interface:
path: ../just_audio_platform_interface
flutter: flutter:
sdk: flutter sdk: flutter
flutter_web_plugins: flutter_web_plugins: