From e92fd8c8634ebac27b4c5194639ea1f396de65e5 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Fri, 11 Dec 2020 18:04:33 +1100 Subject: [PATCH] Control over shuffle order. --- .../com/ryanheise/just_audio/AudioPlayer.java | 84 ++-- just_audio/darwin/Classes/AudioPlayer.m | 55 +-- just_audio/darwin/Classes/AudioSource.m | 5 +- .../darwin/Classes/ClippingAudioSource.m | 2 +- .../darwin/Classes/ConcatenatingAudioSource.m | 59 +-- .../darwin/Classes/IndexedAudioSource.m | 4 - .../darwin/Classes/LoopingAudioSource.m | 12 +- just_audio/darwin/Classes/UriAudioSource.m | 2 +- just_audio/example/lib/main.dart | 8 +- just_audio/example/pubspec.lock | 57 +-- just_audio/ios/Classes/AudioSource.h | 4 +- .../ios/Classes/ConcatenatingAudioSource.h | 3 +- just_audio/lib/just_audio.dart | 369 ++++++++++++++++-- just_audio/macos/Classes/AudioSource.h | 4 +- .../macos/Classes/ConcatenatingAudioSource.h | 3 +- just_audio/pubspec.lock | 52 +-- just_audio/pubspec.yaml | 9 +- just_audio/test/just_audio_test.dart | 102 +++++ .../lib/just_audio_platform_interface.dart | 37 ++ .../lib/method_channel_just_audio.dart | 7 + just_audio_web/lib/just_audio_web.dart | 110 ++---- just_audio_web/pubspec.lock | 6 +- just_audio_web/pubspec.yaml | 5 +- 23 files changed, 703 insertions(+), 296 deletions(-) diff --git a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java index 77ea8f5..85b1f4b 100644 --- a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java +++ b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java @@ -80,8 +80,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud private Integer audioSessionId; private MediaSource mediaSource; private Integer currentIndex; - private Map loopingChildren = new HashMap<>(); - private Map loopingCounts = new HashMap<>(); private final Handler handler = new Handler(); private final Runnable bufferWatcher = new Runnable() { @Override @@ -321,6 +319,10 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud setShuffleModeEnabled((Integer) request.get("shuffleMode") == 1); result.success(new HashMap()); break; + case "setShuffleOrder": + setShuffleOrder(request.get("audioSource")); + result.success(new HashMap()); + break; case "setAutomaticallyWaitsToMinimizeStalling": result.success(new HashMap()); break; @@ -332,14 +334,20 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud case "concatenatingInsertAll": concatenating(request.get("id")) .addMediaSources((Integer)request.get("index"), getAudioSources(request.get("children")), handler, () -> result.success(new HashMap())); + concatenating(request.get("id")) + .setShuffleOrder(decodeShuffleOrder((List)request.get("shuffleOrder"))); break; case "concatenatingRemoveRange": concatenating(request.get("id")) .removeMediaSourceRange((Integer)request.get("startIndex"), (Integer)request.get("endIndex"), handler, () -> result.success(new HashMap())); + concatenating(request.get("id")) + .setShuffleOrder(decodeShuffleOrder((List)request.get("shuffleOrder"))); break; case "concatenatingMove": concatenating(request.get("id")) .moveMediaSource((Integer)request.get("currentIndex"), (Integer)request.get("newIndex"), handler, () -> result.success(new HashMap())); + concatenating(request.get("id")) + .setShuffleOrder(decodeShuffleOrder((List)request.get("shuffleOrder"))); break; case "setAndroidAudioAttributes": 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 - // the first position. Traverse the tree incrementing index at each - // node. - private int setShuffleOrder(MediaSource mediaSource, int index) { - 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++; + private ShuffleOrder decodeShuffleOrder(List indexList) { + int[] shuffleIndices = new int[indexList.size()]; + for (int i = 0; i < shuffleIndices.length; i++) { + shuffleIndices[i] = indexList.get(i); } - return index; + return new DefaultShuffleOrder(shuffleIndices, random.nextLong()); } 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); } + 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)map.get("shuffleOrder"))); + List children = (List)map.get("children"); + for (Object child : children) { + setShuffleOrder(child); + } + break; + case "looping": + setShuffleOrder(map.get("child")); + break; + } + } + private MediaSource getAudioSource(final Object json) { Map map = (Map)json; String id = (String)map.get("id"); @@ -455,7 +456,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud return new ConcatenatingMediaSource( false, // isAtomic (Boolean)map.get("useLazyPreparation"), - new DefaultShuffleOrder(mediaSources.length), + decodeShuffleOrder((List)map.get("shuffleOrder")), mediaSources); case "clipping": Long start = getLong(map.get("start")); @@ -466,11 +467,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud case "looping": Integer count = (Integer)map.get("count"); MediaSource looperChild = getAudioSource(map.get("child")); - LoopingMediaSource looper = new LoopingMediaSource(looperChild, count); - // TODO: store both in a single map - loopingChildren.put(looper, looperChild); - loopingCounts.put(looper, count); - return looper; + return new LoopingMediaSource(looperChild, count); default: throw new IllegalArgumentException("Unknown AudioSource type: " + map.get("type")); } @@ -520,9 +517,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud errorCount = 0; prepareResult = result; transition(ProcessingState.loading); - if (player.getShuffleModeEnabled()) { - setShuffleOrder(mediaSource, 0); - } this.mediaSource = mediaSource; player.prepare(mediaSource); } @@ -669,9 +663,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud } public void setShuffleModeEnabled(final boolean enabled) { - if (enabled) { - setShuffleOrder(mediaSource, 0); - } player.setShuffleModeEnabled(enabled); } @@ -694,7 +685,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud } mediaSources.clear(); mediaSource = null; - loopingChildren.clear(); if (player != null) { player.release(); player = null; diff --git a/just_audio/darwin/Classes/AudioPlayer.m b/just_audio/darwin/Classes/AudioPlayer.m index 0a7350b..071ad11 100644 --- a/just_audio/darwin/Classes/AudioPlayer.m +++ b/just_audio/darwin/Classes/AudioPlayer.m @@ -106,6 +106,9 @@ } else if ([@"setShuffleMode" isEqualToString:call.method]) { [self setShuffleModeEnabled:(BOOL)([request[@"shuffleMode"] intValue] == 1)]; result(@{}); + } else if ([@"setShuffleOrder" isEqualToString:call.method]) { + [self setShuffleOrder:(NSDictionary *)request[@"audioSources"]]; + result(@{}); } else if ([@"setAutomaticallyWaitsToMinimizeStalling" isEqualToString:call.method]) { [self setAutomaticallyWaitsToMinimizeStalling:(BOOL)[request[@"enabled"] boolValue]]; result(@{}); @@ -115,13 +118,13 @@ result(@{}); }]; } 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 *)request[@"shuffleOrder"]]; result(@{}); } 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 *)request[@"shuffleOrder"]]; result(@{}); } 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 *)request[@"shuffleOrder"]]; result(@{}); } else if ([@"setAndroidAudioAttributes" isEqualToString:call.method]) { result(@{}); @@ -144,22 +147,7 @@ } // Untested -- (void)concatenatingAdd:(NSString *)catId source:(NSDictionary *)source { - [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 { +- (void)concatenatingInsertAll:(NSString *)catId index:(int)index sources:(NSArray *)sources shuffleOrder:(NSArray *)shuffleOrder { // Find all duplicates of the identified ConcatenatingAudioSource. NSMutableArray *matches = [[NSMutableArray alloc] init]; [_audioSource findById:catId matches:matches]; @@ -172,6 +160,7 @@ AudioSource *audioSource = audioSources[j]; [catSource insertSource:audioSource atIndex:(idx + j)]; } + [catSource setShuffleOrder:shuffleOrder]; } // Index the new audio sources. _indexedAudioSources = [[NSMutableArray alloc] init]; @@ -200,12 +189,7 @@ } // Untested -- (void)concatenatingRemoveAt:(NSString *)catId index:(int)index { - [self concatenatingRemoveRange:catId start:index end:(index + 1)]; -} - -// Untested -- (void)concatenatingRemoveRange:(NSString *)catId start:(int)start end:(int)end { +- (void)concatenatingRemoveRange:(NSString *)catId start:(int)start end:(int)end shuffleOrder:(NSArray *)shuffleOrder { // Find all duplicates of the identified ConcatenatingAudioSource. NSMutableArray *matches = [[NSMutableArray alloc] init]; [_audioSource findById:catId matches:matches]; @@ -214,6 +198,7 @@ ConcatenatingAudioSource *catSource = (ConcatenatingAudioSource *)matches[i]; int endIndex = end >= 0 ? end : catSource.count; [catSource removeSourcesFromIndex:start toIndex:endIndex]; + [catSource setShuffleOrder:shuffleOrder]; } // Re-index the remaining audio sources. NSArray *oldIndexedAudioSources = _indexedAudioSources; @@ -239,7 +224,7 @@ } // Untested -- (void)concatenatingMove:(NSString *)catId currentIndex:(int)currentIndex newIndex:(int)newIndex { +- (void)concatenatingMove:(NSString *)catId currentIndex:(int)currentIndex newIndex:(int)newIndex shuffleOrder:(NSArray *)shuffleOrder { // Find all duplicates of the identified ConcatenatingAudioSource. NSMutableArray *matches = [[NSMutableArray alloc] init]; [_audioSource findById:catId matches:matches]; @@ -247,6 +232,7 @@ for (int i = 0; i < matches.count; i++) { ConcatenatingAudioSource *catSource = (ConcatenatingAudioSource *)matches[i]; [catSource moveSourceFromIndex:currentIndex toIndex:newIndex]; + [catSource setShuffleOrder:shuffleOrder]; } // Re-index the audio sources. _indexedAudioSources = [[NSMutableArray alloc] init]; @@ -256,11 +242,6 @@ [self broadcastPlaybackEvent]; } -// Untested -- (void)concatenatingClear:(NSString *)catId { - [self concatenatingRemoveRange:catId start:0 end:-1]; -} - - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { _eventSink = eventSink; return nil; @@ -408,7 +389,8 @@ return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"]]; } else if ([@"concatenating" isEqualToString:type]) { return [[ConcatenatingAudioSource alloc] initWithId:data[@"id"] - audioSources:[self decodeAudioSources:data[@"children"]]]; + audioSources:[self decodeAudioSources:data[@"children"]] + shuffleOrder:(NSArray *)data[@"shuffleOrder"]]; } else if ([@"clipping" isEqualToString:type]) { return [[ClippingAudioSource alloc] initWithId:data[@"id"] audioSource:(UriAudioSource *)[self decodeAudioSource:data[@"child"]] @@ -590,15 +572,12 @@ } - (void)updateOrder { - if (_shuffleModeEnabled) { - [_audioSource shuffle:0 currentIndex: _index]; - } _orderInv = [NSMutableArray arrayWithCapacity:[_indexedAudioSources count]]; for (int i = 0; i < [_indexedAudioSources count]; i++) { [_orderInv addObject:@(0)]; } if (_shuffleModeEnabled) { - _order = [_audioSource getShuffleOrder]; + _order = [_audioSource getShuffleIndices]; } else { NSMutableArray *order = [[NSMutableArray alloc] init]; for (int i = 0; i < [_indexedAudioSources count]; i++) { @@ -1031,6 +1010,10 @@ [self enqueueFrom:_index]; } +- (void)setShuffleOrder:(NSDictionary *)dict { + [_audioSource decodeShuffleOrder:dict]; +} + - (void)dumpQueue { for (int i = 0; i < _player.items.count; i++) { IndexedPlayerItem *playerItem = (IndexedPlayerItem *)_player.items[i]; diff --git a/just_audio/darwin/Classes/AudioSource.m b/just_audio/darwin/Classes/AudioSource.m index d615af8..8990557 100644 --- a/just_audio/darwin/Classes/AudioSource.m +++ b/just_audio/darwin/Classes/AudioSource.m @@ -26,12 +26,11 @@ } } -- (NSArray *)getShuffleOrder { +- (NSArray *)getShuffleIndices { return @[]; } -- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex { - return 0; +- (void)decodeShuffleOrder:(NSDictionary *)dict { } @end diff --git a/just_audio/darwin/Classes/ClippingAudioSource.m b/just_audio/darwin/Classes/ClippingAudioSource.m index 36b2ed9..29c1a43 100644 --- a/just_audio/darwin/Classes/ClippingAudioSource.m +++ b/just_audio/darwin/Classes/ClippingAudioSource.m @@ -39,7 +39,7 @@ return _audioSource.playerItem; } -- (NSArray *)getShuffleOrder { +- (NSArray *)getShuffleIndices { return @[@(0)]; } diff --git a/just_audio/darwin/Classes/ConcatenatingAudioSource.m b/just_audio/darwin/Classes/ConcatenatingAudioSource.m index e1dea15..900f705 100644 --- a/just_audio/darwin/Classes/ConcatenatingAudioSource.m +++ b/just_audio/darwin/Classes/ConcatenatingAudioSource.m @@ -5,13 +5,14 @@ @implementation ConcatenatingAudioSource { NSMutableArray *_audioSources; - NSMutableArray *_shuffleOrder; + NSArray *_shuffleOrder; } -- (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray *)audioSources { +- (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray *)audioSources shuffleOrder:(NSArray *)shuffleOrder { self = [super initWithId:sid]; NSAssert(self, @"super init cannot be nil"); _audioSources = audioSources; + _shuffleOrder = shuffleOrder; return self; } @@ -50,19 +51,19 @@ } } -- (NSArray *)getShuffleOrder { +- (NSArray *)getShuffleIndices { NSMutableArray *order = [NSMutableArray new]; int offset = (int)[order count]; NSMutableArray *> *childOrders = [NSMutableArray new]; // array of array of ints for (int i = 0; i < [_audioSources count]; i++) { AudioSource *audioSource = _audioSources[i]; - NSArray *childShuffleOrder = [audioSource getShuffleOrder]; + NSArray *childShuffleIndices = [audioSource getShuffleIndices]; NSMutableArray *offsetChildShuffleOrder = [NSMutableArray new]; - for (int j = 0; j < [childShuffleOrder count]; j++) { - [offsetChildShuffleOrder addObject:@([childShuffleOrder[j] integerValue] + offset)]; + for (int j = 0; j < [childShuffleIndices count]; j++) { + [offsetChildShuffleOrder addObject:@([childShuffleIndices[j] integerValue] + offset)]; } [childOrders addObject:offsetChildShuffleOrder]; - offset += [childShuffleOrder count]; + offset += [childShuffleIndices count]; } for (int i = 0; i < [_audioSources count]; i++) { [order addObjectsFromArray:childOrders[[_shuffleOrder[i] integerValue]]]; @@ -70,40 +71,22 @@ return order; } -- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex { - int currentChildIndex = -1; +- (void)setShuffleOrder:(NSArray *)shuffleOrder { + _shuffleOrder = shuffleOrder; +} + +- (void)decodeShuffleOrder:(NSDictionary *)dict { + _shuffleOrder = (NSArray *)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++) { - int indexBefore = treeIndex; AudioSource *child = _audioSources[i]; - treeIndex = [child shuffle:treeIndex currentIndex:currentIndex]; - if (currentIndex >= indexBefore && currentIndex < treeIndex) { - currentChildIndex = i; - } else {} + NSDictionary *dictChild = (NSDictionary *)dictChildren[i]; + [child decodesetShuffleOrder:dictChild]; } - // 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 diff --git a/just_audio/darwin/Classes/IndexedAudioSource.m b/just_audio/darwin/Classes/IndexedAudioSource.m index 5c94f8d..cf54757 100644 --- a/just_audio/darwin/Classes/IndexedAudioSource.m +++ b/just_audio/darwin/Classes/IndexedAudioSource.m @@ -42,10 +42,6 @@ return treeIndex + 1; } -- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex { - return treeIndex + 1; -} - - (void)attach:(AVQueuePlayer *)player { _isAttached = YES; } diff --git a/just_audio/darwin/Classes/LoopingAudioSource.m b/just_audio/darwin/Classes/LoopingAudioSource.m index 56e5040..a8bae2e 100644 --- a/just_audio/darwin/Classes/LoopingAudioSource.m +++ b/just_audio/darwin/Classes/LoopingAudioSource.m @@ -28,12 +28,12 @@ } } -- (NSArray *)getShuffleOrder { +- (NSArray *)getShuffleIndices { NSMutableArray *order = [NSMutableArray new]; int offset = (int)[order count]; for (int i = 0; i < [_audioSources count]; i++) { AudioSource *audioSource = _audioSources[i]; - NSArray *childShuffleOrder = [audioSource getShuffleOrder]; + NSArray *childShuffleOrder = [audioSource getShuffleIndices]; for (int j = 0; j < [childShuffleOrder count]; j++) { [order addObject:@([childShuffleOrder[j] integerValue] + offset)]; } @@ -42,12 +42,12 @@ return order; } -- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex { - // TODO: This should probably shuffle the same way on all duplicates. +- (void)decodeShuffleOrder:(NSDictionary *)dict { + NSDictionary *dictChild = (NSDictionary *)dict[@"child"]; 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 diff --git a/just_audio/darwin/Classes/UriAudioSource.m b/just_audio/darwin/Classes/UriAudioSource.m index 7ca975a..cb9500f 100644 --- a/just_audio/darwin/Classes/UriAudioSource.m +++ b/just_audio/darwin/Classes/UriAudioSource.m @@ -35,7 +35,7 @@ return _playerItem; } -- (NSArray *)getShuffleOrder { +- (NSArray *)getShuffleIndices { return @[@(0)]; } diff --git a/just_audio/example/lib/main.dart b/just_audio/example/lib/main.dart index 048ae47..85f7f6f 100644 --- a/just_audio/example/lib/main.dart +++ b/just_audio/example/lib/main.dart @@ -179,8 +179,12 @@ class _MyAppState extends State { icon: shuffleModeEnabled ? Icon(Icons.shuffle, color: Colors.orange) : Icon(Icons.shuffle, color: Colors.grey), - onPressed: () { - _player.setShuffleModeEnabled(!shuffleModeEnabled); + onPressed: () async { + final enable = !shuffleModeEnabled; + if (enable) { + await _player.shuffle(); + } + await _player.setShuffleModeEnabled(enable); }, ); }, diff --git a/just_audio/example/pubspec.lock b/just_audio/example/pubspec.lock index f25fe09..4555a6b 100644 --- a/just_audio/example/pubspec.lock +++ b/just_audio/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.5.0-nullsafety.3" audio_session: dependency: "direct main" description: @@ -21,35 +21,35 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0-nullsafety.3" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0-nullsafety.5" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0-nullsafety.3" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0-nullsafety.5" convert: dependency: transitive description: @@ -77,7 +77,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" ffi: dependency: transitive description: @@ -114,6 +114,13 @@ packages: url: "https://pub.dartlang.org" source: hosted 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: dependency: "direct main" description: @@ -124,16 +131,16 @@ packages: just_audio_platform_interface: dependency: transitive description: - name: just_audio_platform_interface - url: "https://pub.dartlang.org" - source: hosted + path: "../../just_audio_platform_interface" + relative: true + source: path version: "1.1.1" just_audio_web: dependency: transitive description: - name: just_audio_web - url: "https://pub.dartlang.org" - source: hosted + path: "../../just_audio_web" + relative: true + source: path version: "0.1.1" matcher: dependency: transitive @@ -141,21 +148,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10-nullsafety.3" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0-nullsafety.6" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0-nullsafety.3" path_provider: dependency: transitive description: @@ -230,49 +237,49 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.0-nullsafety.4" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0-nullsafety.6" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0-nullsafety.3" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0-nullsafety.3" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" + version: "0.2.19-nullsafety.6" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0-nullsafety.5" uuid: dependency: transitive description: @@ -286,7 +293,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0-nullsafety.5" win32: dependency: transitive description: @@ -302,5 +309,5 @@ packages: source: hosted version: "0.1.2" 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" diff --git a/just_audio/ios/Classes/AudioSource.h b/just_audio/ios/Classes/AudioSource.h index bc7f33e..33641c6 100644 --- a/just_audio/ios/Classes/AudioSource.h +++ b/just_audio/ios/Classes/AudioSource.h @@ -7,7 +7,7 @@ - (instancetype)initWithId:(NSString *)sid; - (int)buildSequence:(NSMutableArray *)sequence treeIndex:(int)treeIndex; - (void)findById:(NSString *)sourceId matches:(NSMutableArray *)matches; -- (NSArray *)getShuffleOrder; -- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex; +- (NSArray *)getShuffleIndices; +- (void)decodeShuffleOrder:(NSDictionary *)dict; @end diff --git a/just_audio/ios/Classes/ConcatenatingAudioSource.h b/just_audio/ios/Classes/ConcatenatingAudioSource.h index 2c2350a..7cabf48 100644 --- a/just_audio/ios/Classes/ConcatenatingAudioSource.h +++ b/just_audio/ios/Classes/ConcatenatingAudioSource.h @@ -5,9 +5,10 @@ @property (readonly, nonatomic) int count; -- (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray *)audioSources; +- (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray *)audioSources shuffleOrder:(NSMutableArray *)shuffleOrder; - (void)insertSource:(AudioSource *)audioSource atIndex:(int)index; - (void)removeSourcesFromIndex:(int)start toIndex:(int)end; - (void)moveSourceFromIndex:(int)currentIndex toIndex:(int)newIndex; +- (void)setShuffleOrder:(NSArray *)shuffleOrder; @end diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index 7ac642a..639a866 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -45,7 +45,7 @@ class AudioPlayer { bool _disposed = false; PlaybackEvent _playbackEvent; - final _playbackEventSubject = BehaviorSubject(); + final _playbackEventSubject = BehaviorSubject(sync: true); Future _durationFuture; final _durationSubject = BehaviorSubject(); final _processingStateSubject = BehaviorSubject(); @@ -56,7 +56,9 @@ class AudioPlayer { final _icyMetadataSubject = BehaviorSubject(); final _playerStateSubject = BehaviorSubject(); final _sequenceSubject = BehaviorSubject>(); - final _currentIndexSubject = BehaviorSubject(); + final _shuffleIndicesSubject = BehaviorSubject>(); + final _shuffleIndicesInv = []; + final _currentIndexSubject = BehaviorSubject(sync: true); final _sequenceStateSubject = BehaviorSubject(); final _loopModeSubject = BehaviorSubject(); final _shuffleModeEnabledSubject = BehaviorSubject(); @@ -103,19 +105,29 @@ class AudioPlayer { .map((event) => event.currentIndex) .distinct() .handleError((err, stack) {/* noop */})); + currentIndexStream.listen((index) => print('### index: $index')); _androidAudioSessionIdSubject.addStream(playbackEventStream .map((event) => event.androidAudioSessionId) .distinct() .handleError((err, stack) {/* noop */})); - _sequenceStateSubject.addStream( - Rx.combineLatest2, int, SequenceState>( + _sequenceStateSubject.addStream(Rx.combineLatest5, + List, int, bool, LoopMode, SequenceState>( sequenceStream, + shuffleIndicesStream, currentIndexStream, - (sequence, currentIndex) { + shuffleModeEnabledStream, + loopModeStream, + (sequence, shuffleIndices, currentIndex, shuffleModeEnabled, loopMode) { if (sequence == null) return null; if (currentIndex == null) currentIndex = 0; currentIndex = min(sequence.length - 1, max(0, currentIndex)); - return SequenceState(sequence, currentIndex); + return SequenceState( + sequence, + currentIndex, + shuffleIndices, + shuffleModeEnabled, + loopMode, + ); }, ).distinct().handleError((err, stack) {/* noop */})); _playerStateSubject.addStream( @@ -125,6 +137,8 @@ class AudioPlayer { (playing, event) => PlayerState(playing, event.processingState)) .distinct() .handleError((err, stack) {/* noop */})); + _shuffleModeEnabledSubject.add(false); + _loopModeSubject.add(LoopMode.off); _platform.then((platform) { platform.playbackEventMessageStream.listen((message) { final playbackEvent = PlaybackEvent( @@ -140,6 +154,8 @@ class AudioPlayer { currentIndex: message.currentIndex, androidAudioSessionId: message.androidAudioSessionId, ); + print( + "### received event with currentIndex: ${playbackEvent.currentIndex}"); _durationFuture = Future.value(playbackEvent.duration); if (playbackEvent.duration != _playbackEvent.duration) { _durationSubject.add(playbackEvent.duration); @@ -268,6 +284,16 @@ class AudioPlayer { Stream> get sequenceStream => _sequenceSubject.stream; + /// The current shuffled sequence of indexed audio sources. + List get shuffleIndices => _shuffleIndicesSubject.value; + + /// A stream broadcasting the current shuffled sequence of indexed audio + /// sources. + Stream> get shuffleIndicesStream => _shuffleIndicesSubject.stream; + + //List get _effectiveSequence => + // shuffleModeEnabled ? shuffleIndices : sequence; + /// The index of the current item. int get currentIndex => _currentIndexSubject.value; @@ -282,14 +308,71 @@ class AudioPlayer { Stream get sequenceStateStream => _sequenceStateSubject.stream; /// Whether there is another item after the current index. - bool get hasNext => - _audioSource != null && - currentIndex != null && - currentIndex + 1 < sequence.length; + bool get hasNext => _nextIndex != null; /// Whether there is another item before the current index. - bool get hasPrevious => - _audioSource != null && currentIndex != null && currentIndex > 0; + bool get hasPrevious => _previousIndex != null; + + 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. LoopMode get loopMode => _loopModeSubject.value; @@ -452,6 +535,7 @@ class AudioPlayer { if (_disposed) return null; try { _audioSource = source; + print("### set _audioSource = $_audioSource"); _broadcastSequence(); final duration = await _load(source, initialPosition: initialPosition, initialIndex: initialIndex); @@ -466,7 +550,30 @@ class AudioPlayer { } void _broadcastSequence() { + print("### _broadcastSequence"); _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) { @@ -475,6 +582,7 @@ class AudioPlayer { Future _load(AudioSource source, {Duration initialPosition, int initialIndex}) async { + print("### _load with initialIndex=$initialIndex"); try { if (!kIsWeb && source._requiresHeaders) { if (_proxy == null) { @@ -483,6 +591,8 @@ class AudioPlayer { } } await source._setup(this); + source._shuffle(initialIndex: initialIndex ?? 0); + _updateShuffleIndices(); _durationFuture = (await _platform) .load(LoadRequest( audioSourceMessage: source._toMessage(), @@ -620,6 +730,19 @@ class AudioPlayer { enabled ? ShuffleModeMessage.all : ShuffleModeMessage.none)); } + /// Recursively shuffles the children of the currently loaded [AudioSource]. + Future 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. /// Has no effect on Android clients Future setAutomaticallyWaitsToMinimizeStalling( @@ -657,7 +780,7 @@ class AudioPlayer { Future seekToNext() async { if (_disposed) return; if (hasNext) { - await seek(Duration.zero, index: currentIndex + 1); + await seek(Duration.zero, index: _nextIndex); } } @@ -665,7 +788,7 @@ class AudioPlayer { Future seekToPrevious() async { if (_disposed) return; 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()); } _audioSource = null; + print("### set _audioSource = $_audioSource (dispose)"); _audioSources.values.forEach((s) => s._dispose()); _audioSources.clear(); _proxy?.stop(); @@ -704,6 +828,7 @@ class AudioPlayer { await _volumeSubject.close(); await _speedSubject.close(); await _sequenceSubject.close(); + await _shuffleIndicesSubject.close(); } } @@ -940,10 +1065,26 @@ class SequenceState { /// The index of the current source in the sequence. final int currentIndex; - SequenceState(this.sequence, this.currentIndex); + /// The current shuffle order + final List 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. IndexedAudioSource get currentSource => sequence[currentIndex]; + + /// The effective sequence. This is equivalent to [sequence]. If + /// [shuffleModeEnabled] is true, this is modulated by [shuffleIndices]. + List get effectiveSequence => shuffleModeEnabled + ? shuffleIndices.map((i) => sequence[i]).toList() + : sequence; } /// A local proxy HTTP server for making remote GET requests with headers. @@ -1105,6 +1246,8 @@ abstract class AudioSource { player._registerAudioSource(this); } + void _shuffle({int initialIndex}); + @mustCallSuper void _dispose() { _player = null; @@ -1116,6 +1259,8 @@ abstract class AudioSource { List get sequence; + List get shuffleIndices; + @override int get hashCode => _id.hashCode; @@ -1129,8 +1274,14 @@ abstract class IndexedAudioSource extends AudioSource { IndexedAudioSource(this.tag); + @override + void _shuffle({int initialIndex}) {} + @override List get sequence => [this]; + + @override + List get shuffleIndices => [0]; } /// An abstract class representing audio sources that are loaded from a URI. @@ -1258,11 +1409,14 @@ class HlsAudioSource extends UriAudioSource { class ConcatenatingAudioSource extends AudioSource { final List children; final bool useLazyPreparation; + ShuffleOrder _shuffleOrder; ConcatenatingAudioSource({ @required this.children, this.useLazyPreparation = true, - }); + ShuffleOrder shuffleOrder, + }) : _shuffleOrder = shuffleOrder ?? DefaultShuffleOrder() + ..insert(0, children.length); @override Future _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]. Future add(AudioSource audioSource) async { final index = children.length; children.add(audioSource); + _shuffleOrder.insert(index, 1); if (_player != null) { _player._broadcastSequence(); await audioSource._setup(_player); await (await _player._platform).concatenatingInsertAll( ConcatenatingInsertAllRequest( - id: _id, index: index, children: [audioSource._toMessage()])); + id: _id, + index: index, + children: [audioSource._toMessage()], + shuffleOrder: _shuffleOrder.indices)); } } /// (Untested) Inserts an [AudioSource] at [index]. Future insert(int index, AudioSource audioSource) async { children.insert(index, audioSource); + _shuffleOrder.insert(index, 1); if (_player != null) { _player._broadcastSequence(); await audioSource._setup(_player); await (await _player._platform).concatenatingInsertAll( 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 addAll(List children) async { int index = this.children.length; this.children.addAll(children); + _shuffleOrder.insert(index, children.length); if (_player != null) { _player._broadcastSequence(); for (var child in children) { @@ -1310,13 +1497,15 @@ class ConcatenatingAudioSource extends AudioSource { ConcatenatingInsertAllRequest( id: _id, 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]. Future insertAll(int index, List children) async { this.children.insertAll(index, children); + _shuffleOrder.insert(index, children.length); if (_player != null) { _player._broadcastSequence(); for (var child in children) { @@ -1326,7 +1515,8 @@ class ConcatenatingAudioSource extends AudioSource { ConcatenatingInsertAllRequest( id: _id, 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. Future removeAt(int index) async { children.removeAt(index); + _shuffleOrder.removeRange(index, index + 1); if (_player != null) { _player._broadcastSequence(); await (await _player._platform).concatenatingRemoveRange( 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. Future removeRange(int start, int end) async { children.removeRange(start, end); + _shuffleOrder.removeRange(start, end); if (_player != null) { _player._broadcastSequence(); await (await _player._platform).concatenatingRemoveRange( ConcatenatingRemoveRangeRequest( - id: _id, startIndex: start, endIndex: end)); + id: _id, + startIndex: start, + endIndex: end, + shuffleOrder: _shuffleOrder.indices)); } } /// (Untested) Moves an [AudioSource] from [currentIndex] to [newIndex]. Future move(int currentIndex, int newIndex) async { children.insert(newIndex, children.removeAt(currentIndex)); + _shuffleOrder.removeRange(currentIndex, currentIndex + 1); + _shuffleOrder.insert(newIndex, 1); if (_player != null) { _player._broadcastSequence(); await (await _player._platform).concatenatingMove( ConcatenatingMoveRequest( - id: _id, currentIndex: currentIndex, newIndex: newIndex)); + id: _id, + currentIndex: currentIndex, + newIndex: newIndex, + shuffleOrder: _shuffleOrder.indices)); } } /// (Untested) Removes all [AudioSources]. Future clear() async { children.clear(); + _shuffleOrder.clear(); if (_player != null) { _player._broadcastSequence(); await (await _player._platform).concatenatingRemoveRange( 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 get sequence => children.expand((s) => s.sequence).toList(); + @override + List get shuffleIndices { + var offset = 0; + final childIndicesList = >[]; + for (var child in children) { + final childIndices = child.shuffleIndices.map((i) => i + offset).toList(); + childIndicesList.add(childIndices); + offset += childIndices.length; + } + final indices = []; + for (var index in _shuffleOrder.indices) { + final childIndices = childIndicesList[index]; + //print("child indices: $childIndices"); + indices.addAll(childIndices); + } + print("### returning: $indices"); + return indices; + } + @override bool get _requiresHeaders => children.any((source) => source._requiresHeaders); @@ -1393,7 +1619,8 @@ class ConcatenatingAudioSource extends AudioSource { AudioSourceMessage _toMessage() => ConcatenatingAudioSourceMessage( id: _id, 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 @@ -1449,10 +1676,16 @@ class LoopingAudioSource extends AudioSource { await child._setup(player); } + @override + void _shuffle({int initialIndex}) {} + @override List get sequence => List.generate(count, (i) => child).expand((s) => s.sequence).toList(); + @override + List get shuffleIndices => List.generate(count, (i) => i); + @override bool get _requiresHeaders => child._requiresHeaders; @@ -1461,4 +1694,90 @@ class LoopingAudioSource extends AudioSource { 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 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 = []; + + 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 } diff --git a/just_audio/macos/Classes/AudioSource.h b/just_audio/macos/Classes/AudioSource.h index 368a457..d9db8d8 100644 --- a/just_audio/macos/Classes/AudioSource.h +++ b/just_audio/macos/Classes/AudioSource.h @@ -7,7 +7,7 @@ - (instancetype)initWithId:(NSString *)sid; - (int)buildSequence:(NSMutableArray *)sequence treeIndex:(int)treeIndex; - (void)findById:(NSString *)sourceId matches:(NSMutableArray *)matches; -- (NSArray *)getShuffleOrder; -- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex; +- (NSArray *)getShuffleIndices; +- (void)decodeShuffleOrder:(NSDictionary *)dict; @end diff --git a/just_audio/macos/Classes/ConcatenatingAudioSource.h b/just_audio/macos/Classes/ConcatenatingAudioSource.h index 68455af..c72bf67 100644 --- a/just_audio/macos/Classes/ConcatenatingAudioSource.h +++ b/just_audio/macos/Classes/ConcatenatingAudioSource.h @@ -5,9 +5,10 @@ @property (readonly, nonatomic) int count; -- (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray *)audioSources; +- (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray *)audioSources shuffleOrder:(NSMutableArray *)shuffleOrder; - (void)insertSource:(AudioSource *)audioSource atIndex:(int)index; - (void)removeSourcesFromIndex:(int)start toIndex:(int)end; - (void)moveSourceFromIndex:(int)currentIndex toIndex:(int)newIndex; +- (void)setShuffleOrder:(NSArray *)shuffleOrder; @end diff --git a/just_audio/pubspec.lock b/just_audio/pubspec.lock index 2038a9e..df82919 100644 --- a/just_audio/pubspec.lock +++ b/just_audio/pubspec.lock @@ -28,7 +28,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.5.0-nullsafety.3" audio_session: dependency: "direct main" description: @@ -42,7 +42,7 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0-nullsafety.3" build: dependency: transitive description: @@ -70,14 +70,14 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0-nullsafety.5" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" cli_util: dependency: transitive description: @@ -91,7 +91,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0-nullsafety.3" code_builder: dependency: transitive description: @@ -105,7 +105,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0-nullsafety.5" convert: dependency: transitive description: @@ -133,7 +133,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" ffi: dependency: transitive description: @@ -190,20 +190,20 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.2" + version: "0.6.3-nullsafety.3" just_audio_platform_interface: dependency: "direct main" description: - name: just_audio_platform_interface - url: "https://pub.dartlang.org" - source: hosted + path: "../just_audio_platform_interface" + relative: true + source: path version: "1.1.1" just_audio_web: dependency: "direct main" description: - name: just_audio_web - url: "https://pub.dartlang.org" - source: hosted + path: "../just_audio_web" + relative: true + source: path version: "0.1.1" logging: dependency: transitive @@ -218,14 +218,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10-nullsafety.3" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0-nullsafety.6" mockito: dependency: "direct dev" description: @@ -260,7 +260,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0-nullsafety.3" path_provider: dependency: "direct main" description: @@ -363,49 +363,49 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.0-nullsafety.4" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0-nullsafety.6" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0-nullsafety.3" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0-nullsafety.3" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" + version: "0.2.19-nullsafety.6" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0-nullsafety.5" uuid: dependency: "direct main" description: @@ -419,7 +419,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0-nullsafety.5" watcher: dependency: transitive description: @@ -449,5 +449,5 @@ packages: source: hosted version: "2.2.1" 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" diff --git a/just_audio/pubspec.yaml b/just_audio/pubspec.yaml index 067a71f..e7db270 100644 --- a/just_audio/pubspec.yaml +++ b/just_audio/pubspec.yaml @@ -8,8 +8,13 @@ environment: flutter: ">=1.12.13+hotfix.5" dependencies: - just_audio_platform_interface: ^1.1.1 - just_audio_web: ^0.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 + # just_audio_web: ^0.1.1 + just_audio_web: + path: ../just_audio_web audio_session: ^0.0.9 rxdart: ^0.24.1 path: ^1.6.4 diff --git a/just_audio/test/just_audio_test.dart b/just_audio/test/just_audio_test.dart index 9dd69f6..c67ed03 100644 --- a/just_audio/test/just_audio_test.dart +++ b/just_audio/test/just_audio_test.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/services.dart'; @@ -44,6 +45,12 @@ void runTests() { } } + void checkIndices(List indices, int length) { + expect(indices.length, length); + final sorted = List.of(indices)..sort(); + expect(sorted, equals(List.generate(indices.length, (i) => i))); + } + setUp(() { audioSessionChannel.setMockMethodCallHandler((MethodCall methodCall) async { return null; @@ -381,6 +388,95 @@ void runTests() { expect(AudioSource.uri(Uri.parse('https://a.a/a#.mpd')) is DashAudioSource, 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 @@ -543,6 +639,12 @@ class MockAudioPlayer implements AudioPlayerPlatform { return SetShuffleModeResponse(); } + @override + Future setShuffleOrder( + SetShuffleOrderRequest request) async { + return SetShuffleOrderResponse(); + } + @override Future setSpeed(SetSpeedRequest request) async { _speed = request.speed; diff --git a/just_audio_platform_interface/lib/just_audio_platform_interface.dart b/just_audio_platform_interface/lib/just_audio_platform_interface.dart index 449a6d7..f436bbf 100644 --- a/just_audio_platform_interface/lib/just_audio_platform_interface.dart +++ b/just_audio_platform_interface/lib/just_audio_platform_interface.dart @@ -103,6 +103,12 @@ abstract class AudioPlayerPlatform { throw UnimplementedError("setShuffleMode() has not been implemented."); } + /// Sets the shuffle order. + Future setShuffleOrder( + SetShuffleOrderRequest request) { + throw UnimplementedError("setShuffleOrder() has not been implemented."); + } + /// On iOS and macOS, sets the automaticallyWaitsToMinimizeStalling option, /// and does nothing on other platforms. Future @@ -431,6 +437,25 @@ class SetShuffleModeResponse { /// The shuffle mode communicated to the platform implementation. 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 toMap() => { + 'audioSource': audioSourceMessage.toMap(), + }; +} + +/// Information returned by the platform implementation after setting the +/// shuffle order. +class SetShuffleOrderResponse { + static SetShuffleOrderResponse fromMap(Map map) => + SetShuffleOrderResponse(); +} + /// Information communicated to the platform implementation when setting the /// automaticallyWaitsToMinimizeStalling option. class SetAutomaticallyWaitsToMinimizeStallingRequest { @@ -515,17 +540,20 @@ class ConcatenatingInsertAllRequest { final String id; final int index; final List children; + final List shuffleOrder; ConcatenatingInsertAllRequest({ @required this.id, @required this.index, @required this.children, + @required this.shuffleOrder, }); Map toMap() => { 'id': id, 'index': index, 'children': children.map((child) => child.toMap()).toList(), + 'shuffleOrder': shuffleOrder, }; } @@ -542,17 +570,20 @@ class ConcatenatingRemoveRangeRequest { final String id; final int startIndex; final int endIndex; + final List shuffleOrder; ConcatenatingRemoveRangeRequest({ @required this.id, @required this.startIndex, @required this.endIndex, + @required this.shuffleOrder, }); Map toMap() => { 'id': id, 'startIndex': startIndex, 'endIndex': endIndex, + 'shuffleOrder': shuffleOrder, }; } @@ -569,17 +600,20 @@ class ConcatenatingMoveRequest { final String id; final int currentIndex; final int newIndex; + final List shuffleOrder; ConcatenatingMoveRequest({ @required this.id, @required this.currentIndex, @required this.newIndex, + @required this.shuffleOrder, }); Map toMap() => { 'id': id, 'currentIndex': currentIndex, 'newIndex': newIndex, + 'shuffleOrder': shuffleOrder, }; } @@ -678,11 +712,13 @@ class HlsAudioSourceMessage extends UriAudioSourceMessage { class ConcatenatingAudioSourceMessage extends AudioSourceMessage { final List children; final bool useLazyPreparation; + final List shuffleOrder; ConcatenatingAudioSourceMessage({ @required String id, @required this.children, @required this.useLazyPreparation, + @required this.shuffleOrder, }) : super(id: id); @override @@ -691,6 +727,7 @@ class ConcatenatingAudioSourceMessage extends AudioSourceMessage { 'id': id, 'children': children.map((child) => child.toMap()).toList(), 'useLazyPreparation': useLazyPreparation, + 'shuffleOrder': shuffleOrder, }; } diff --git a/just_audio_platform_interface/lib/method_channel_just_audio.dart b/just_audio_platform_interface/lib/method_channel_just_audio.dart index c8b7a2a..882670f 100644 --- a/just_audio_platform_interface/lib/method_channel_just_audio.dart +++ b/just_audio_platform_interface/lib/method_channel_just_audio.dart @@ -79,6 +79,13 @@ class MethodChannelAudioPlayer extends AudioPlayerPlatform { await _channel.invokeMethod('setShuffleMode', request?.toMap())); } + @override + Future setShuffleOrder( + SetShuffleOrderRequest request) async { + return SetShuffleOrderResponse.fromMap( + await _channel.invokeMethod('setShuffleOrder', request?.toMap())); + } + @override Future setAutomaticallyWaitsToMinimizeStalling( diff --git a/just_audio_web/lib/just_audio_web.dart b/just_audio_web/lib/just_audio_web.dart index a792316..d793350 100644 --- a/just_audio_web/lib/just_audio_web.dart +++ b/just_audio_web/lib/just_audio_web.dart @@ -112,7 +112,7 @@ class Html5AudioPlayer extends JustAudioPlayer { final sequence = _audioSourcePlayer.sequence; List order = List(sequence.length); if (_shuffleModeEnabled) { - order = _audioSourcePlayer.shuffleOrder; + order = _audioSourcePlayer.shuffleIndices; } else { for (var i = 0; i < order.length; i++) { order[i] = i; @@ -181,9 +181,6 @@ class Html5AudioPlayer extends JustAudioPlayer { _currentAudioSourcePlayer?.pause(); _audioSourcePlayer = getAudioSource(request.audioSourceMessage); _index = request.initialIndex ?? 0; - if (_shuffleModeEnabled) { - _audioSourcePlayer?.shuffle(0, _index); - } final duration = await _currentAudioSourcePlayer.load(); if (request.initialPosition != null) { await _currentAudioSourcePlayer @@ -259,12 +256,30 @@ class Html5AudioPlayer extends JustAudioPlayer { Future setShuffleMode( SetShuffleModeRequest request) async { _shuffleModeEnabled = request.shuffleMode == ShuffleModeMessage.all; - if (_shuffleModeEnabled) { - _audioSourcePlayer?.shuffle(0, _index); - } return SetShuffleModeResponse(); } + @override + Future 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 Future seek(SeekRequest request) async { await _seek(request.position.inMilliseconds, request.index); @@ -294,6 +309,7 @@ class Html5AudioPlayer extends JustAudioPlayer { ConcatenatingInsertAllRequest request) async { _concatenating(request.id) .insertAll(request.index, getAudioSources(request.children)); + _concatenating(request.id).setShuffleOrder(request.shuffleOrder); if (request.index <= _index) { _index += request.children.length; } @@ -309,6 +325,7 @@ class Html5AudioPlayer extends JustAudioPlayer { } _concatenating(request.id) .removeRange(request.startIndex, request.endIndex); + _concatenating(request.id).setShuffleOrder(request.shuffleOrder); if (_index >= request.startIndex && _index < request.endIndex) { // Skip backward if there's nothing after this if (request.startIndex >= _audioSourcePlayer.sequence.length) { @@ -332,6 +349,7 @@ class Html5AudioPlayer extends JustAudioPlayer { Future concatenatingMove( ConcatenatingMoveRequest request) async { _concatenating(request.id).move(request.currentIndex, request.newIndex); + _concatenating(request.id).setShuffleOrder(request.shuffleOrder); if (request.currentIndex == _index) { _index = request.newIndex; } else if (request.currentIndex < _index && request.newIndex >= _index) { @@ -401,7 +419,8 @@ class Html5AudioPlayer extends JustAudioPlayer { this, audioSourceMessage.id, getAudioSources(audioSourceMessage.children), - audioSourceMessage.useLazyPreparation); + audioSourceMessage.useLazyPreparation, + audioSourceMessage.shuffleOrder); } else if (audioSourceMessage is ClippingAudioSourceMessage) { return ClippingAudioSourcePlayer( this, @@ -426,9 +445,7 @@ abstract class AudioSourcePlayer { List get sequence; - List get shuffleOrder; - - int shuffle(int treeIndex, int currentIndex); + List get shuffleIndices; } abstract class IndexedAudioSourcePlayer extends AudioSourcePlayer { @@ -455,9 +472,6 @@ abstract class IndexedAudioSourcePlayer extends AudioSourcePlayer { AudioElement get _audioElement => html5AudioPlayer._audioElement; - @override - int shuffle(int treeIndex, int currentIndex) => treeIndex + 1; - @override String toString() => "${this.runtimeType}"; } @@ -477,7 +491,7 @@ abstract class UriAudioSourcePlayer extends IndexedAudioSourcePlayer { List get sequence => [this]; @override - List get shuffleOrder => [0]; + List get shuffleIndices => [0]; @override Future load() async { @@ -566,33 +580,13 @@ class HlsAudioSourcePlayer extends UriAudioSourcePlayer { } class ConcatenatingAudioSourcePlayer extends AudioSourcePlayer { - static List generateShuffleOrder(int length, [int firstIndex]) { - final shuffleOrder = List(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 audioSourcePlayers; final bool useLazyPreparation; List _shuffleOrder; ConcatenatingAudioSourcePlayer(Html5AudioPlayer html5AudioPlayer, String id, - this.audioSourcePlayers, this.useLazyPreparation) - : _shuffleOrder = generateShuffleOrder(audioSourcePlayers.length), + this.audioSourcePlayers, this.useLazyPreparation, List shuffleOrder) + : _shuffleOrder = shuffleOrder, super(html5AudioPlayer, id); @override @@ -600,14 +594,14 @@ class ConcatenatingAudioSourcePlayer extends AudioSourcePlayer { audioSourcePlayers.expand((p) => p.sequence).toList(); @override - List get shuffleOrder { + List get shuffleIndices { final order = []; var offset = order.length; final childOrders = >[]; for (var audioSourcePlayer in audioSourcePlayers) { - final childShuffleOrder = audioSourcePlayer.shuffleOrder; - childOrders.add(childShuffleOrder.map((i) => i + offset).toList()); - offset += childShuffleOrder.length; + final childShuffleIndices = audioSourcePlayer.shuffleIndices; + childOrders.add(childShuffleIndices.map((i) => i + offset).toList()); + offset += childShuffleIndices.length; } for (var i = 0; i < childOrders.length; i++) { order.addAll(childOrders[_shuffleOrder[i]]); @@ -615,21 +609,8 @@ class ConcatenatingAudioSourcePlayer extends AudioSourcePlayer { return order; } - @override - int shuffle(int treeIndex, int currentIndex) { - 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; + void setShuffleOrder(List shuffleOrder) { + _shuffleOrder = shuffleOrder; } insertAll(int index, List players) { @@ -639,8 +620,6 @@ class ConcatenatingAudioSourcePlayer extends AudioSourcePlayer { _shuffleOrder[i] += players.length; } } - _shuffleOrder.addAll( - List.generate(players.length, (i) => index + i).toList()..shuffle()); } removeRange(int start, int end) { @@ -650,7 +629,6 @@ class ConcatenatingAudioSourcePlayer extends AudioSourcePlayer { _shuffleOrder[i] -= (end - start); } } - _shuffleOrder.removeWhere((i) => i >= start && i < end); } move(int currentIndex, int newIndex) { @@ -675,7 +653,7 @@ class ClippingAudioSourcePlayer extends IndexedAudioSourcePlayer { List get sequence => [this]; @override - List get shuffleOrder => [0]; + List get shuffleIndices => [0]; @override Future load() async { @@ -799,22 +777,14 @@ class LoopingAudioSourcePlayer extends AudioSourcePlayer { .toList(); @override - List get shuffleOrder { + List get shuffleIndices { final order = []; var offset = order.length; for (var i = 0; i < count; i++) { - final childShuffleOrder = audioSourcePlayer.shuffleOrder; + final childShuffleOrder = audioSourcePlayer.shuffleIndices; order.addAll(childShuffleOrder.map((i) => i + offset).toList()); offset += childShuffleOrder.length; } return order; } - - @override - int shuffle(int treeIndex, int currentIndex) { - for (var i = 0; i < count; i++) { - treeIndex = audioSourcePlayer.shuffle(treeIndex, currentIndex); - } - return treeIndex; - } } diff --git a/just_audio_web/pubspec.lock b/just_audio_web/pubspec.lock index 44224d9..8b6281b 100644 --- a/just_audio_web/pubspec.lock +++ b/just_audio_web/pubspec.lock @@ -28,9 +28,9 @@ packages: just_audio_platform_interface: dependency: "direct main" description: - name: just_audio_platform_interface - url: "https://pub.dartlang.org" - source: hosted + path: "../just_audio_platform_interface" + relative: true + source: path version: "1.1.1" meta: dependency: "direct main" diff --git a/just_audio_web/pubspec.yaml b/just_audio_web/pubspec.yaml index 5bd6660..53b7c59 100644 --- a/just_audio_web/pubspec.yaml +++ b/just_audio_web/pubspec.yaml @@ -11,7 +11,10 @@ flutter: fileName: just_audio_web.dart 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: sdk: flutter flutter_web_plugins: