queue links implemented; cleanup
This commit is contained in:
parent
cbb8559954
commit
a031fedd39
13 changed files with 306 additions and 105 deletions
|
@ -10,8 +10,8 @@ import 'domain/repositories/music_data_repository.dart';
|
||||||
import 'presentation/state/audio_store.dart';
|
import 'presentation/state/audio_store.dart';
|
||||||
import 'presentation/state/music_data_store.dart';
|
import 'presentation/state/music_data_store.dart';
|
||||||
import 'presentation/state/navigation_store.dart';
|
import 'presentation/state/navigation_store.dart';
|
||||||
import 'system/datasources/audio_manager.dart';
|
import 'system/audio/audio_manager.dart';
|
||||||
import 'system/datasources/audio_manager_contract.dart';
|
import 'system/audio/audio_manager_contract.dart';
|
||||||
import 'system/datasources/local_music_fetcher.dart';
|
import 'system/datasources/local_music_fetcher.dart';
|
||||||
import 'system/datasources/local_music_fetcher_contract.dart';
|
import 'system/datasources/local_music_fetcher_contract.dart';
|
||||||
import 'system/datasources/moor_music_data_source.dart';
|
import 'system/datasources/moor_music_data_source.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../system/datasources/audio_player_task.dart';
|
import '../../system/audio/audio_player_task.dart';
|
||||||
|
|
||||||
class AudioServiceWidget extends StatefulWidget {
|
class AudioServiceWidget extends StatefulWidget {
|
||||||
const AudioServiceWidget({@required this.child});
|
const AudioServiceWidget({@required this.child});
|
||||||
|
|
|
@ -4,13 +4,15 @@ import 'dart:ui';
|
||||||
|
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:just_audio/just_audio.dart';
|
import 'package:just_audio/just_audio.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:moor/isolate.dart';
|
import 'package:moor/isolate.dart';
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
|
|
||||||
import '../../domain/entities/shuffle_mode.dart';
|
import '../../domain/entities/shuffle_mode.dart';
|
||||||
|
import '../datasources/moor_music_data_source.dart';
|
||||||
|
import '../models/queue_item.dart';
|
||||||
import '../models/song_model.dart';
|
import '../models/song_model.dart';
|
||||||
import 'moor_music_data_source.dart';
|
import 'queue_generator.dart';
|
||||||
import 'queue_manager.dart';
|
|
||||||
|
|
||||||
const String INIT = 'INIT';
|
const String INIT = 'INIT';
|
||||||
const String PLAY_WITH_CONTEXT = 'PLAY_WITH_CONTEXT';
|
const String PLAY_WITH_CONTEXT = 'PLAY_WITH_CONTEXT';
|
||||||
|
@ -22,13 +24,15 @@ const String KEY_INDEX = 'INDEX';
|
||||||
class AudioPlayerTask extends BackgroundAudioTask {
|
class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
final audioPlayer = AudioPlayer();
|
final audioPlayer = AudioPlayer();
|
||||||
MoorMusicDataSource moorMusicDataSource;
|
MoorMusicDataSource moorMusicDataSource;
|
||||||
QueueManager qm;
|
QueueGenerator queueGenerator;
|
||||||
|
|
||||||
|
// TODO: confusing naming
|
||||||
List<MediaItem> originalPlaybackContext = <MediaItem>[];
|
List<MediaItem> originalPlaybackContext = <MediaItem>[];
|
||||||
List<MediaItem> playbackContext = <MediaItem>[];
|
List<QueueItem> playbackContext = <QueueItem>[];
|
||||||
|
|
||||||
|
// TODO: this is not trivial: queue is loaded by audioplayer
|
||||||
|
// this reference enables direct manipulation of the loaded queue
|
||||||
ConcatenatingAudioSource queue;
|
ConcatenatingAudioSource queue;
|
||||||
List<int> permutation;
|
|
||||||
|
|
||||||
ShuffleMode _shuffleMode = ShuffleMode.none;
|
ShuffleMode _shuffleMode = ShuffleMode.none;
|
||||||
ShuffleMode get shuffleMode => _shuffleMode;
|
ShuffleMode get shuffleMode => _shuffleMode;
|
||||||
|
@ -40,10 +44,9 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
int _playbackIndex = -1;
|
int _playbackIndex = -1;
|
||||||
int get playbackIndex => _playbackIndex;
|
int get playbackIndex => _playbackIndex;
|
||||||
set playbackIndex(int i) {
|
set playbackIndex(int i) {
|
||||||
print(i);
|
|
||||||
if (i != null) {
|
if (i != null) {
|
||||||
_playbackIndex = i;
|
_playbackIndex = i;
|
||||||
AudioServiceBackground.setMediaItem(playbackContext[i]);
|
AudioServiceBackground.setMediaItem(playbackContext[i].mediaItem);
|
||||||
AudioServiceBackground.sendCustomEvent({KEY_INDEX: i});
|
AudioServiceBackground.sendCustomEvent({KEY_INDEX: i});
|
||||||
|
|
||||||
AudioServiceBackground.setState(
|
AudioServiceBackground.setState(
|
||||||
|
@ -61,6 +64,12 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static final _log = Logger('AudioPlayerTask')
|
||||||
|
..onRecord.listen((record) {
|
||||||
|
print(
|
||||||
|
'${record.time} [${record.level.name}] ${record.loggerName}: ${record.message}');
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onStop() async {
|
Future<void> onStop() async {
|
||||||
await audioPlayer.stop();
|
await audioPlayer.stop();
|
||||||
|
@ -112,19 +121,20 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
print('AudioPlayerTask.init');
|
print('AudioPlayerTask.init');
|
||||||
audioPlayer.playerStateStream.listen((event) => handlePlayerState(event));
|
audioPlayer.playerStateStream.listen((event) => handlePlayerState(event));
|
||||||
|
audioPlayer.currentIndexStream.listen((event) => playbackIndex = event);
|
||||||
audioPlayer.sequenceStateStream
|
audioPlayer.sequenceStateStream
|
||||||
.listen((event) => playbackIndex = event?.currentIndex);
|
.listen((event) => handleSequenceState(event));
|
||||||
|
|
||||||
final connectPort = IsolateNameServer.lookupPortByName(MOOR_ISOLATE);
|
final connectPort = IsolateNameServer.lookupPortByName(MOOR_ISOLATE);
|
||||||
final MoorIsolate moorIsolate = MoorIsolate.fromConnectPort(connectPort);
|
final MoorIsolate moorIsolate = MoorIsolate.fromConnectPort(connectPort);
|
||||||
final DatabaseConnection databaseConnection = await moorIsolate.connect();
|
final DatabaseConnection databaseConnection = await moorIsolate.connect();
|
||||||
moorMusicDataSource = MoorMusicDataSource.connect(databaseConnection);
|
moorMusicDataSource = MoorMusicDataSource.connect(databaseConnection);
|
||||||
|
|
||||||
qm = QueueManager(moorMusicDataSource);
|
queueGenerator = QueueGenerator(moorMusicDataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> playWithContext(List<String> context, int index) async {
|
Future<void> playWithContext(List<String> context, int index) async {
|
||||||
final mediaItems = await qm.getMediaItemsFromPaths(context);
|
final mediaItems = await queueGenerator.getMediaItemsFromPaths(context);
|
||||||
playPlaylist(mediaItems, index);
|
playPlaylist(mediaItems, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,22 +147,44 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
Future<void> setShuffleMode(ShuffleMode mode) async {
|
Future<void> setShuffleMode(ShuffleMode mode) async {
|
||||||
shuffleMode = mode;
|
shuffleMode = mode;
|
||||||
|
|
||||||
final index = permutation[playbackIndex];
|
final QueueItem currentQueueItem = playbackContext[playbackIndex];
|
||||||
permutation =
|
final int index = currentQueueItem.originalIndex;
|
||||||
qm.generatePermutation(shuffleMode, originalPlaybackContext, index);
|
|
||||||
playbackContext =
|
playbackContext =
|
||||||
qm.getPermutatedSongs(originalPlaybackContext, permutation);
|
await queueGenerator.generateQueue(shuffleMode, originalPlaybackContext, index);
|
||||||
|
final queuedMediaItems = playbackContext.map((e) => e.mediaItem).toList();
|
||||||
|
|
||||||
AudioServiceBackground.setQueue(playbackContext);
|
// FIXME: this does not react correctly when inserted track is currently played
|
||||||
|
AudioServiceBackground.setQueue(queuedMediaItems);
|
||||||
|
|
||||||
|
final newQueue = queueGenerator.mediaItemsToAudioSource(queuedMediaItems);
|
||||||
|
_updateQueue(newQueue, currentQueueItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateQueue(ConcatenatingAudioSource newQueue, QueueItem currentQueueItem) {
|
||||||
|
final int index = currentQueueItem.originalIndex;
|
||||||
|
|
||||||
final newQueue = qm.mediaItemsToAudioSource(playbackContext);
|
|
||||||
queue.removeRange(0, playbackIndex);
|
queue.removeRange(0, playbackIndex);
|
||||||
queue.removeRange(1, queue.length);
|
queue.removeRange(1, queue.length);
|
||||||
|
|
||||||
if (shuffleMode == ShuffleMode.none) {
|
if (shuffleMode == ShuffleMode.none) {
|
||||||
queue.insertAll(0, newQueue.children.sublist(0, index));
|
switch (currentQueueItem.type) {
|
||||||
queue.addAll(newQueue.children.sublist(index + 1));
|
case QueueItemType.standard:
|
||||||
playbackIndex = index;
|
queue.insertAll(0, newQueue.children.sublist(0, index));
|
||||||
|
queue.addAll(newQueue.children.sublist(index + 1));
|
||||||
|
playbackIndex = index;
|
||||||
|
break;
|
||||||
|
case QueueItemType.predecessor:
|
||||||
|
queue.insertAll(0, newQueue.children.sublist(0, index));
|
||||||
|
queue.addAll(newQueue.children.sublist(index));
|
||||||
|
playbackIndex = index;
|
||||||
|
break;
|
||||||
|
case QueueItemType.successor:
|
||||||
|
queue.insertAll(0, newQueue.children.sublist(0, index + 1));
|
||||||
|
queue.addAll(newQueue.children.sublist(index + 1));
|
||||||
|
playbackIndex = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
queue.addAll(newQueue.children.sublist(1));
|
queue.addAll(newQueue.children.sublist(1));
|
||||||
}
|
}
|
||||||
|
@ -171,18 +203,20 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> playPlaylist(List<MediaItem> mediaItems, int index) async {
|
Future<void> playPlaylist(List<MediaItem> mediaItems, int index) async {
|
||||||
permutation = qm.generatePermutation(shuffleMode, mediaItems, index);
|
|
||||||
playbackContext = qm.getPermutatedSongs(mediaItems, permutation);
|
|
||||||
originalPlaybackContext = mediaItems;
|
originalPlaybackContext = mediaItems;
|
||||||
|
|
||||||
AudioServiceBackground.setQueue(playbackContext);
|
playbackContext = await queueGenerator.generateQueue(shuffleMode, mediaItems, index);
|
||||||
queue = qm.mediaItemsToAudioSource(playbackContext);
|
final queuedMediaItems = playbackContext.map((e) => e.mediaItem).toList();
|
||||||
|
|
||||||
|
AudioServiceBackground.setQueue(queuedMediaItems);
|
||||||
|
queue = queueGenerator.mediaItemsToAudioSource(queuedMediaItems);
|
||||||
audioPlayer.play();
|
audioPlayer.play();
|
||||||
final int startIndex = shuffleMode == ShuffleMode.none ? index : 0;
|
final int startIndex = shuffleMode == ShuffleMode.none ? index : 0;
|
||||||
await audioPlayer.load(queue, initialIndex: startIndex);
|
await audioPlayer.load(queue, initialIndex: startIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlePlayerState(PlayerState ps) {
|
void handlePlayerState(PlayerState ps) {
|
||||||
|
_log.info('handlePlayerState called');
|
||||||
if (ps.processingState == ProcessingState.ready && ps.playing) {
|
if (ps.processingState == ProcessingState.ready && ps.playing) {
|
||||||
AudioServiceBackground.setState(
|
AudioServiceBackground.setState(
|
||||||
controls: [
|
controls: [
|
||||||
|
@ -211,4 +245,14 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this can only be a temporary solution! gets called too often.
|
||||||
|
void handleSequenceState(SequenceState st) {
|
||||||
|
_log.info('handleSequenceState called');
|
||||||
|
if (0 <= playbackIndex && playbackIndex < playbackContext.length) {
|
||||||
|
_log.info('handleSequenceState: setting MediaItem');
|
||||||
|
AudioServiceBackground.setMediaItem(
|
||||||
|
playbackContext[playbackIndex].mediaItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
176
lib/system/audio/queue_generator.dart
Normal file
176
lib/system/audio/queue_generator.dart
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:just_audio/just_audio.dart';
|
||||||
|
|
||||||
|
import '../../domain/entities/shuffle_mode.dart';
|
||||||
|
import '../datasources/music_data_source_contract.dart';
|
||||||
|
import '../models/queue_item.dart';
|
||||||
|
import '../models/song_model.dart';
|
||||||
|
|
||||||
|
class QueueGenerator {
|
||||||
|
QueueGenerator(this._musicDataSource);
|
||||||
|
|
||||||
|
final MusicDataSource _musicDataSource;
|
||||||
|
|
||||||
|
// TODO: test
|
||||||
|
// TODO: optimize -> too slow for whole library
|
||||||
|
// fetching all songs together and preparing playback takes ~500ms compared to ~10.000ms individually
|
||||||
|
Future<List<MediaItem>> getMediaItemsFromPaths(List<String> paths) async {
|
||||||
|
final mediaItems = <MediaItem>[];
|
||||||
|
for (final path in paths) {
|
||||||
|
final song = await _musicDataSource.getSongByPath(path);
|
||||||
|
mediaItems.add(song.toMediaItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<QueueItem>> generateQueue(
|
||||||
|
ShuffleMode shuffleMode,
|
||||||
|
List<MediaItem> mediaItems,
|
||||||
|
int startIndex,
|
||||||
|
) async {
|
||||||
|
List<QueueItem> queue;
|
||||||
|
|
||||||
|
switch (shuffleMode) {
|
||||||
|
case ShuffleMode.none:
|
||||||
|
queue = _generateNormalQueue(mediaItems);
|
||||||
|
break;
|
||||||
|
case ShuffleMode.standard:
|
||||||
|
queue = _generateShuffleQueue(mediaItems, startIndex);
|
||||||
|
break;
|
||||||
|
case ShuffleMode.plus:
|
||||||
|
queue = await _generateShufflePlusQueue(mediaItems, startIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConcatenatingAudioSource mediaItemsToAudioSource(List<MediaItem> mediaItems) {
|
||||||
|
return ConcatenatingAudioSource(
|
||||||
|
children: mediaItems
|
||||||
|
.map((MediaItem m) => AudioSource.uri(Uri.file(m.id)))
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<QueueItem> _generateNormalQueue(List<MediaItem> mediaItems) {
|
||||||
|
return List<QueueItem>.generate(
|
||||||
|
mediaItems.length,
|
||||||
|
(i) => QueueItem(
|
||||||
|
mediaItems[i],
|
||||||
|
originalIndex: i,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<QueueItem> _generateShuffleQueue(
|
||||||
|
List<MediaItem> mediaItems,
|
||||||
|
int startIndex,
|
||||||
|
) {
|
||||||
|
final List<QueueItem> queue = List<QueueItem>.generate(
|
||||||
|
mediaItems.length,
|
||||||
|
(i) => QueueItem(
|
||||||
|
mediaItems[i],
|
||||||
|
originalIndex: i,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
queue.removeAt(startIndex);
|
||||||
|
queue.shuffle();
|
||||||
|
final first = QueueItem(
|
||||||
|
mediaItems[startIndex],
|
||||||
|
originalIndex: startIndex,
|
||||||
|
);
|
||||||
|
return [first] + queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<QueueItem>> _generateShufflePlusQueue(
|
||||||
|
List<MediaItem> mediaItems,
|
||||||
|
int startIndex,
|
||||||
|
) async {
|
||||||
|
final List<QueueItem> queue = await _getQueueItemWithLinks(
|
||||||
|
mediaItems[startIndex],
|
||||||
|
startIndex,
|
||||||
|
);
|
||||||
|
final List<int> indices = [];
|
||||||
|
|
||||||
|
// filter mediaitem list
|
||||||
|
// TODO: multiply higher rated songs
|
||||||
|
for (var i = 0; i < mediaItems.length; i++) {
|
||||||
|
if (i != startIndex && !(mediaItems[i].extras['blocked'] as bool)) {
|
||||||
|
indices.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indices.shuffle();
|
||||||
|
|
||||||
|
for (var i = 0; i < indices.length; i++) {
|
||||||
|
final int index = indices[i];
|
||||||
|
final MediaItem mediaItem = mediaItems[index];
|
||||||
|
|
||||||
|
queue.addAll(await _getQueueItemWithLinks(mediaItem, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: naming things is hard
|
||||||
|
Future<List<QueueItem>> _getQueueItemWithLinks(
|
||||||
|
MediaItem mediaItem,
|
||||||
|
int index,
|
||||||
|
) async {
|
||||||
|
final List<QueueItem> queueItems = [];
|
||||||
|
|
||||||
|
final predecessors = await _getPredecessors(mediaItem);
|
||||||
|
final successors = await _getSuccessors(mediaItem);
|
||||||
|
|
||||||
|
for (final p in predecessors) {
|
||||||
|
queueItems.add(QueueItem(
|
||||||
|
p,
|
||||||
|
originalIndex: index,
|
||||||
|
type: QueueItemType.predecessor,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
queueItems.add(QueueItem(
|
||||||
|
mediaItem,
|
||||||
|
originalIndex: index,
|
||||||
|
));
|
||||||
|
|
||||||
|
for (final p in successors) {
|
||||||
|
queueItems.add(QueueItem(
|
||||||
|
p,
|
||||||
|
originalIndex: index,
|
||||||
|
type: QueueItemType.successor,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return queueItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<MediaItem>> _getPredecessors(MediaItem mediaItem) async {
|
||||||
|
final List<MediaItem> mediaItems = [];
|
||||||
|
MediaItem currentMediaItem = mediaItem;
|
||||||
|
|
||||||
|
while (currentMediaItem.previous != null) {
|
||||||
|
currentMediaItem =
|
||||||
|
(await _musicDataSource.getSongByPath(currentMediaItem.previous))
|
||||||
|
.toMediaItem();
|
||||||
|
mediaItems.add(currentMediaItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaItems.reversed.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<MediaItem>> _getSuccessors(MediaItem mediaItem) async {
|
||||||
|
final List<MediaItem> mediaItems = [];
|
||||||
|
MediaItem currentMediaItem = mediaItem;
|
||||||
|
|
||||||
|
while (currentMediaItem.next != null) {
|
||||||
|
currentMediaItem =
|
||||||
|
(await _musicDataSource.getSongByPath(currentMediaItem.next))
|
||||||
|
.toMediaItem();
|
||||||
|
mediaItems.add(currentMediaItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaItems.toList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,73 +0,0 @@
|
||||||
import 'package:audio_service/audio_service.dart';
|
|
||||||
import 'package:just_audio/just_audio.dart';
|
|
||||||
|
|
||||||
import '../../domain/entities/shuffle_mode.dart';
|
|
||||||
import 'music_data_source_contract.dart';
|
|
||||||
|
|
||||||
class QueueManager {
|
|
||||||
QueueManager(this._musicDataSource);
|
|
||||||
|
|
||||||
final MusicDataSource _musicDataSource;
|
|
||||||
|
|
||||||
// TODO: test
|
|
||||||
// TODO: optimize -> too slow for whole library
|
|
||||||
// fetching all songs together and preparing playback takes ~500ms compared to ~10.000ms individually
|
|
||||||
Future<List<MediaItem>> getMediaItemsFromPaths(List<String> paths) async {
|
|
||||||
final mediaItems = <MediaItem>[];
|
|
||||||
for (final path in paths) {
|
|
||||||
final song = await _musicDataSource.getSongByPath(path);
|
|
||||||
mediaItems.add(song.toMediaItem());
|
|
||||||
}
|
|
||||||
|
|
||||||
return mediaItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: test
|
|
||||||
List<int> generatePermutation(
|
|
||||||
ShuffleMode shuffleMode, List<MediaItem> mediaItems, int startIndex) {
|
|
||||||
// permutation[i] = j; => song j is on the i-th position in the permutated list
|
|
||||||
List<int> permutation;
|
|
||||||
final int length = mediaItems.length;
|
|
||||||
|
|
||||||
switch (shuffleMode) {
|
|
||||||
case ShuffleMode.none:
|
|
||||||
permutation = List<int>.generate(length, (i) => i);
|
|
||||||
break;
|
|
||||||
case ShuffleMode.standard:
|
|
||||||
final tmp = List<int>.generate(length, (i) => i)
|
|
||||||
..removeAt(startIndex)
|
|
||||||
..shuffle();
|
|
||||||
permutation = [startIndex] + tmp;
|
|
||||||
break;
|
|
||||||
case ShuffleMode.plus:
|
|
||||||
permutation = generatePlusPermutation(mediaItems, startIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return permutation;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MediaItem> getPermutatedSongs(
|
|
||||||
List<MediaItem> songs, List<int> permutation) {
|
|
||||||
return List.generate(
|
|
||||||
permutation.length, (index) => songs[permutation[index]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConcatenatingAudioSource mediaItemsToAudioSource(List<MediaItem> mediaItems) {
|
|
||||||
return ConcatenatingAudioSource(
|
|
||||||
children: mediaItems
|
|
||||||
.map((MediaItem m) => AudioSource.uri(Uri.file(m.id)))
|
|
||||||
.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<int> generatePlusPermutation(
|
|
||||||
List<MediaItem> mediaItems, int startIndex) {
|
|
||||||
final List<int> indices = [];
|
|
||||||
for (var i = 0; i < mediaItems.length; i++) {
|
|
||||||
if (i != startIndex && mediaItems[i].extras['blocked'] == 'false') {
|
|
||||||
indices.add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
indices.shuffle();
|
|
||||||
return [startIndex] + indices;
|
|
||||||
}
|
|
||||||
}
|
|
11
lib/system/models/queue_item.dart
Normal file
11
lib/system/models/queue_item.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
|
||||||
|
class QueueItem {
|
||||||
|
QueueItem(this.mediaItem, {this.originalIndex, this.type = QueueItemType.standard});
|
||||||
|
|
||||||
|
final MediaItem mediaItem;
|
||||||
|
final int originalIndex;
|
||||||
|
final QueueItemType type;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum QueueItemType { standard, predecessor, successor }
|
|
@ -209,3 +209,9 @@ class SongModel extends Song {
|
||||||
return [discNumber, trackNumber];
|
return [discNumber, trackNumber];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: maybe move to another file
|
||||||
|
extension SongModelExtension on MediaItem {
|
||||||
|
String get previous => extras['previous'] as String;
|
||||||
|
String get next => extras['next'] as String;
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import '../../domain/entities/playback_state.dart';
|
||||||
import '../../domain/entities/shuffle_mode.dart';
|
import '../../domain/entities/shuffle_mode.dart';
|
||||||
import '../../domain/entities/song.dart';
|
import '../../domain/entities/song.dart';
|
||||||
import '../../domain/repositories/audio_repository.dart';
|
import '../../domain/repositories/audio_repository.dart';
|
||||||
import '../datasources/audio_manager_contract.dart';
|
import '../audio/audio_manager_contract.dart';
|
||||||
import '../models/song_model.dart';
|
import '../models/song_model.dart';
|
||||||
|
|
||||||
class AudioRepositoryImpl implements AudioRepository {
|
class AudioRepositoryImpl implements AudioRepository {
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
import 'package:mucke/injection_container.dart';
|
import 'package:mucke/injection_container.dart';
|
||||||
import 'package:mucke/system/repositories/audio_repository_impl.dart';
|
import 'package:mucke/system/repositories/audio_repository_impl.dart';
|
||||||
import 'package:mucke/system/repositories/music_data_repository_impl.dart';
|
import 'package:mucke/system/repositories/music_data_repository_impl.dart';
|
||||||
import 'package:mucke/system/datasources/audio_manager_contract.dart';
|
import 'package:mucke/system/audio/audio_manager_contract.dart';
|
||||||
import 'package:mucke/system/datasources/local_music_fetcher.dart';
|
import 'package:mucke/system/datasources/local_music_fetcher.dart';
|
||||||
import 'package:mucke/system/datasources/audio_player_task.dart';
|
import 'package:mucke/system/audio/audio_player_task.dart';
|
||||||
import 'package:mucke/system/datasources/local_music_fetcher_contract.dart';
|
import 'package:mucke/system/datasources/local_music_fetcher_contract.dart';
|
||||||
import 'package:mucke/system/datasources/audio_manager.dart';
|
import 'package:mucke/system/audio/audio_manager.dart';
|
||||||
import 'package:mucke/system/datasources/moor_music_data_source.dart';
|
import 'package:mucke/system/datasources/moor_music_data_source.dart';
|
||||||
import 'package:mucke/system/datasources/queue_manager.dart';
|
import 'package:mucke/system/audio/queue_generator.dart';
|
||||||
import 'package:mucke/system/datasources/music_data_source_contract.dart';
|
import 'package:mucke/system/datasources/music_data_source_contract.dart';
|
||||||
import 'package:mucke/system/models/artist_model.dart';
|
import 'package:mucke/system/models/artist_model.dart';
|
||||||
import 'package:mucke/system/models/album_model.dart';
|
import 'package:mucke/system/models/album_model.dart';
|
||||||
|
|
37
test/system/datasources/queue_manager_test.dart
Normal file
37
test/system/datasources/queue_manager_test.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:mucke/system/datasources/music_data_source_contract.dart';
|
||||||
|
import 'package:mucke/system/audio/queue_generator.dart';
|
||||||
|
import 'package:mucke/system/models/album_model.dart';
|
||||||
|
import 'package:mucke/system/models/song_model.dart';
|
||||||
|
|
||||||
|
import '../../test_constants.dart';
|
||||||
|
|
||||||
|
class MockMusicDataSource extends Mock implements MusicDataSource {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
QueueGenerator queueManager;
|
||||||
|
MusicDataSource musicDataSource;
|
||||||
|
List<MediaItem> mediaItems;
|
||||||
|
|
||||||
|
group('generatePlusPermutation', () {
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
musicDataSource = MockMusicDataSource();
|
||||||
|
queueManager = QueueGenerator(musicDataSource);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should exclude blocked songs',
|
||||||
|
() async {
|
||||||
|
// arrange
|
||||||
|
|
||||||
|
// act
|
||||||
|
|
||||||
|
// assert
|
||||||
|
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
import 'package:mucke/system/datasources/audio_manager_contract.dart';
|
import 'package:mucke/system/audio/audio_manager_contract.dart';
|
||||||
import 'package:mucke/system/models/song_model.dart';
|
import 'package:mucke/system/models/song_model.dart';
|
||||||
import 'package:mucke/system/repositories/audio_repository_impl.dart';
|
import 'package:mucke/system/repositories/audio_repository_impl.dart';
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue