Skip to content

Commit

Permalink
Change shuffle order to make queue edits intuitive
Browse files Browse the repository at this point in the history
  • Loading branch information
ray-kast committed Aug 7, 2023
1 parent e69b201 commit 1b252e5
Showing 1 changed file with 77 additions and 4 deletions.
81 changes: 77 additions & 4 deletions lib/services/music_player_background_task.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';

import 'package:android_id/android_id.dart';
import 'package:audio_service/audio_service.dart';
Expand All @@ -15,6 +16,65 @@ import 'finamp_settings_helper.dart';
import 'finamp_user_helper.dart';
import 'jellyfin_api_helper.dart';

// Largely copied from just_audio's DefaultShuffleOrder, but with a deeply
// stupid hack to insert() to make Play Next work
class FinampShuffleOrder extends ShuffleOrder {
final Random _random;
@override
final indices = <int>[];

FinampShuffleOrder({Random? random}) : _random = random ?? Random();

@override
void shuffle({int? initialIndex}) {
assert(initialIndex == null || indices.contains(initialIndex));
if (indices.length <= 1) return;
indices.shuffle(_random);
if (initialIndex == null) return;

const initialPos = 0;
final swapPos = indices.indexOf(initialIndex);
// Swap the indices at initialPos and swapPos.
final swapIndex = indices[initialPos];
indices[initialPos] = initialIndex;
indices[swapPos] = swapIndex;
}

@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;
}
}

final newIndices = List.generate(count, (i) => index + i);
// Only shuffle inserted indices amongst themselves, but keep them contiguous
newIndices.shuffle(_random);
indices.insertAll(index, newIndices);
}

@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();
}
}

/// This provider handles the currently playing music so that multiple widgets
/// can control music.
class MusicPlayerBackgroundTask extends BaseAudioHandler {
Expand All @@ -30,8 +90,10 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
FinampSettingsHelper.finampSettings.bufferDuration,
)),
);
ConcatenatingAudioSource _queueAudioSource =
ConcatenatingAudioSource(children: []);
ConcatenatingAudioSource _queueAudioSource = ConcatenatingAudioSource(
children: [],
shuffleOrder: FinampShuffleOrder(),
);
final _audioServiceBackgroundTaskLogger = Logger("MusicPlayerBackgroundTask");
final _jellyfinApiHelper = GetIt.instance<JellyfinApiHelper>();
final _finampUserHelper = GetIt.instance<FinampUserHelper>();
Expand Down Expand Up @@ -218,10 +280,20 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {

Future<void> insertQueueItemsNext(List<MediaItem> mediaItems) async {
try {
var idx = _player.currentIndex;
if (idx != null) {
if (_player.shuffleModeEnabled) {
var next = _player.shuffleIndices?.indexOf(idx);
idx = next == -1 || next == null ? null : next + 1;
} else {
++idx;
}
}
idx ??= 0;

final sources =
await Future.wait(mediaItems.map((i) => _mediaItemToAudioSource(i)));
await _queueAudioSource.insertAll(
(_player.currentIndex ?? -1) + 1, sources);
await _queueAudioSource.insertAll(idx, sources);
queue.add(_queueFromSource());
} catch (e) {
_audioServiceBackgroundTaskLogger.severe(e);
Expand All @@ -241,6 +313,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
// Create a new ConcatenatingAudioSource with the new queue.
_queueAudioSource = ConcatenatingAudioSource(
children: audioSources,
shuffleOrder: FinampShuffleOrder(),
);

try {
Expand Down

0 comments on commit 1b252e5

Please sign in to comment.