queue and audiosource stuff

This commit is contained in:
Moritz Weber 2021-03-06 19:09:19 +01:00
parent 39e4111037
commit 5c6a8a0a6d
19 changed files with 629 additions and 221 deletions

View file

@ -3,30 +3,104 @@ import '../entities/shuffle_mode.dart';
import '../entities/song.dart';
import '../repositories/music_data_repository.dart';
class QueueGenerationModule {
QueueGenerationModule(this._musicDataRepository);
class QueueManagerModule {
QueueManagerModule(this._musicDataRepository);
List<QueueItem> get queue => _queue;
final MusicDataInfoRepository _musicDataRepository;
Future<List<QueueItem>> generateQueue(
List<Song> _originalSongList = [];
List<Song> _addedSongs = [];
// this resembles the queue in AudioPlayer
List<QueueItem> _queue;
void addToQueue(Song song) {
_addedSongs.add(song);
final queueItem = QueueItem(
song,
originalIndex: _addedSongs.length - 1,
type: QueueItemType.added,
);
_queue.add(queueItem);
}
void insertIntoQueue(Song song, int index) {
_addedSongs.add(song);
final queueItem = QueueItem(
song,
originalIndex: _addedSongs.length - 1,
type: QueueItemType.added,
);
_queue.insert(index, queueItem);
}
void moveQueueItem(int oldIndex, int newIndex) {
final queueItem = _queue.removeAt(oldIndex);
_queue.insert(newIndex, queueItem);
}
void removeQueueIndex(int index) {
final queueItem = _queue[index];
if (queueItem.type == QueueItemType.added) {
_addedSongs.removeAt(queueItem.originalIndex);
} else if (queueItem.type == QueueItemType.standard) {
_originalSongList.removeAt(queueItem.originalIndex);
}
for (int i = 0; i < queue.length; i++) {
if (queue[i].type == queueItem.type && queue[i].originalIndex > queueItem.originalIndex) {
queue[i] = QueueItem(
queue[i].song,
originalIndex: queue[i].originalIndex - 1,
type: queue[i].type,
);
}
}
_queue.removeAt(index);
}
Future<void> reshuffleQueue(
ShuffleMode shuffleMode,
int currentIndex,
) async {
final songs = _originalSongList.cast<Song>() + _addedSongs;
final currentQueueItem = _queue[currentIndex];
int originalIndex = currentQueueItem.originalIndex;
if (currentQueueItem.type == QueueItemType.added) {
originalIndex += _originalSongList.length;
}
_queue = await _generateQueue(shuffleMode, songs, originalIndex);
}
Future<void> setQueue(
ShuffleMode shuffleMode,
List<Song> songs,
int startIndex,
) async {
List<QueueItem> queue;
_originalSongList = songs;
_addedSongs = [];
switch (shuffleMode) {
case ShuffleMode.none:
queue = _generateNormalQueue(songs);
break;
case ShuffleMode.standard:
queue = _generateShuffleQueue(songs, startIndex);
break;
case ShuffleMode.plus:
queue = await _generateShufflePlusQueue(songs, startIndex);
_queue = await _generateQueue(shuffleMode, songs, startIndex);
}
return queue;
// ignore: missing_return
Future<List<QueueItem>> _generateQueue(
ShuffleMode shuffleMode,
List<Song> songs,
int startIndex,
) async {
switch (shuffleMode) {
case ShuffleMode.none:
return _generateNormalQueue(songs);
case ShuffleMode.standard:
return _generateShuffleQueue(songs, startIndex);
case ShuffleMode.plus:
return await _generateShufflePlusQueue(songs, startIndex);
}
}
List<QueueItem> _generateNormalQueue(List<Song> songs) {

View file

@ -3,7 +3,6 @@ import 'package:rxdart/rxdart.dart';
import '../entities/event.dart';
import '../entities/loop_mode.dart';
import '../entities/playback_event.dart';
import '../entities/queue_item.dart';
import '../entities/shuffle_mode.dart';
import '../entities/song.dart';
@ -12,8 +11,7 @@ abstract class AudioPlayerInfoRepository {
ValueStream<ShuffleMode> get shuffleModeStream;
ValueStream<LoopMode> get loopModeStream;
ValueStream<List<Song>> get songListStream;
ValueStream<List<QueueItem>> get queueStream;
ValueStream<List<Song>> get queueStream;
ValueStream<int> get currentIndexStream;
Stream<Song> get currentSongStream;
@ -28,14 +26,16 @@ abstract class AudioPlayerRepository extends AudioPlayerInfoRepository {
Future<void> stop();
Future<bool> seekToNext();
Future<void> seekToPrevious();
Future<void> seekToIndex(int index);
Future<void> dispose();
Future<void> playSong(Song song);
Future<void> loadQueue({List<QueueItem> queue, int initialIndex});
Future<void> loadQueue({List<Song> queue, int initialIndex});
Future<void> addToQueue(Song song);
Future<void> playNext(Song song);
Future<void> moveQueueItem(int oldIndex, int newIndex);
Future<void> removeQueueIndex(int index);
Future<void> setIndex(int index);
Future<void> replaceQueueAroundIndex({List<Song> before, List<Song> after, int index});
/// Set the ShuffleMode. Does not affect playback/queue.
Future<void> setShuffleMode(ShuffleMode shuffleMode);

View file

@ -0,0 +1,30 @@
import '../entities/song.dart';
import '../modules/queue_manager.dart';
import '../repositories/audio_player_repository.dart';
import '../repositories/persistent_player_state_repository.dart';
import '../repositories/platform_integration_repository.dart';
class AddToQueue {
AddToQueue(
this._audioPlayerRepository,
this._platformIntegrationRepository,
this._playerStateRepository,
this._queueManagerModule,
);
final AudioPlayerRepository _audioPlayerRepository;
final PlatformIntegrationRepository _platformIntegrationRepository;
final PlayerStateRepository _playerStateRepository;
final QueueManagerModule _queueManagerModule;
Future<void> call(Song song) async {
_queueManagerModule.addToQueue(song);
await _audioPlayerRepository.addToQueue(song);
final songList = _audioPlayerRepository.queueStream.value;
print(songList.length);
_platformIntegrationRepository.setQueue(songList);
}
}

View file

@ -0,0 +1,28 @@
import '../modules/queue_manager.dart';
import '../repositories/audio_player_repository.dart';
import '../repositories/persistent_player_state_repository.dart';
import '../repositories/platform_integration_repository.dart';
class MoveQueueItem {
MoveQueueItem(
this._audioPlayerRepository,
this._platformIntegrationRepository,
this._playerStateRepository,
this._queueManagerModule,
);
final AudioPlayerRepository _audioPlayerRepository;
final PlatformIntegrationRepository _platformIntegrationRepository;
final PlayerStateRepository _playerStateRepository;
final QueueManagerModule _queueManagerModule;
Future<void> call(int oldIndex, int newIndex) async {
_queueManagerModule.moveQueueItem(oldIndex, newIndex);
await _audioPlayerRepository.moveQueueItem(oldIndex, newIndex);
final songList = _audioPlayerRepository.queueStream.value;
_platformIntegrationRepository.setQueue(songList);
}
}

View file

@ -0,0 +1,31 @@
import '../entities/song.dart';
import '../modules/queue_manager.dart';
import '../repositories/audio_player_repository.dart';
import '../repositories/persistent_player_state_repository.dart';
import '../repositories/platform_integration_repository.dart';
class PlayNext {
PlayNext(
this._audioPlayerRepository,
this._platformIntegrationRepository,
this._playerStateRepository,
this._queueManagerModule,
);
final AudioPlayerRepository _audioPlayerRepository;
final PlatformIntegrationRepository _platformIntegrationRepository;
final PlayerStateRepository _playerStateRepository;
final QueueManagerModule _queueManagerModule;
Future<void> call(Song song) async {
final currentIndex = _audioPlayerRepository.currentIndexStream.value;
_queueManagerModule.insertIntoQueue(song, currentIndex + 1);
await _audioPlayerRepository.playNext(song);
final songList = _audioPlayerRepository.queueStream.value;
print(songList.length);
_platformIntegrationRepository.setQueue(songList);
}
}

View file

@ -1,5 +1,6 @@
import '../entities/shuffle_mode.dart';
import '../entities/song.dart';
import '../modules/queue_generator.dart';
import '../modules/queue_manager.dart';
import '../repositories/audio_player_repository.dart';
import '../repositories/persistent_player_state_repository.dart';
import '../repositories/platform_integration_repository.dart';
@ -16,27 +17,32 @@ class PlaySongs {
final PlatformIntegrationRepository _platformIntegrationRepository;
final PlayerStateRepository _playerStateRepository;
final QueueGenerationModule _queueGenerationModule;
final QueueManagerModule _queueGenerationModule;
Future<void> call({List<Song> songs, int initialIndex}) async {
if (0 <= initialIndex && initialIndex < songs.length) {
// _audioPlayerRepository.playSong(songs[initialIndex]);
final queueItems = await _queueGenerationModule.generateQueue(
_audioPlayerRepository.shuffleModeStream.value,
final shuffleMode = _audioPlayerRepository.shuffleModeStream.value;
await _queueGenerationModule.setQueue(
shuffleMode,
songs,
initialIndex,
);
final queueItems = _queueGenerationModule.queue;
final songList = queueItems.map((e) => e.song).toList();
await _audioPlayerRepository.loadQueue(
initialIndex: initialIndex,
queue: queueItems,
initialIndex: shuffleMode == ShuffleMode.none ? initialIndex : 0,
queue: songList,
);
_audioPlayerRepository.play();
_platformIntegrationRepository.setCurrentSong(songs[initialIndex]);
// _platformIntegrationRepository.play();
_platformIntegrationRepository.setQueue(queueItems.map((e) => e.song).toList());
_platformIntegrationRepository.setQueue(songList);
}
}
}

View file

@ -0,0 +1,29 @@
import '../modules/queue_manager.dart';
import '../repositories/audio_player_repository.dart';
import '../repositories/persistent_player_state_repository.dart';
import '../repositories/platform_integration_repository.dart';
class RemoveQueueIndex {
RemoveQueueIndex(
this._audioPlayerRepository,
this._platformIntegrationRepository,
this._playerStateRepository,
this._queueManagerModule,
);
final AudioPlayerRepository _audioPlayerRepository;
final PlatformIntegrationRepository _platformIntegrationRepository;
final PlayerStateRepository _playerStateRepository;
final QueueManagerModule _queueManagerModule;
Future<void> call(int index) async {
_queueManagerModule.removeQueueIndex(index);
await _audioPlayerRepository.removeQueueIndex(index);
final songList = _audioPlayerRepository.queueStream.value;
print(songList.length);
_platformIntegrationRepository.setQueue(songList);
}
}

View file

@ -0,0 +1,11 @@
import '../repositories/audio_player_repository.dart';
class SeekToIndex {
SeekToIndex(this._audioPlayerRepository);
final AudioPlayerRepository _audioPlayerRepository;
Future<void> call(int index) async {
await _audioPlayerRepository.seekToIndex(index);
}
}

View file

@ -1,8 +1,5 @@
import 'package:mucke/domain/entities/queue_item.dart';
import '../entities/shuffle_mode.dart';
import '../entities/song.dart';
import '../modules/queue_generator.dart';
import '../modules/queue_manager.dart';
import '../repositories/audio_player_repository.dart';
import '../repositories/persistent_player_state_repository.dart';
import '../repositories/platform_integration_repository.dart';
@ -12,53 +9,32 @@ class SetShuffleMode {
this._audioPlayerRepository,
this._platformIntegrationRepository,
this._playerStateRepository,
this._queueGenerationModule,
this._queueManagerModule,
);
final AudioPlayerRepository _audioPlayerRepository;
final PlatformIntegrationRepository _platformIntegrationRepository;
final PlayerStateRepository _playerStateRepository;
final QueueGenerationModule _queueGenerationModule;
final QueueManagerModule _queueManagerModule;
Future<void> call(ShuffleMode shuffleMode) async {
// _audioPlayerRepository.playSong(songs[initialIndex]);
_audioPlayerRepository.setShuffleMode(shuffleMode);
final queue = _audioPlayerRepository.queueStream.value;
final currentIndex = _audioPlayerRepository.currentIndexStream.value;
final originalIndex = _queueManagerModule.queue[currentIndex].originalIndex;
final QueueItem currentQueueItem = queue[currentIndex];
final int index = currentQueueItem.originalIndex;
await _queueManagerModule.reshuffleQueue(shuffleMode, currentIndex);
final queue = _queueManagerModule.queue;
// WAS LETZTE GEDANKE?
// _inputQueue ist die originale Song Liste aus playSongs o.ä. BEVOR der QueueGenerator drüber läuft
final songList = queue.map((e) => e.song).toList();
final splitIndex = shuffleMode == ShuffleMode.none ? originalIndex : 0;
_audioPlayerRepository.replaceQueueAroundIndex(
index: currentIndex,
before: songList.sublist(0, splitIndex),
after: songList.sublist(splitIndex + 1),
);
// _queue = await _queueGenerator.generateQueue(shuffleMode, _inputQueue, index);
// // TODO: maybe refactor _queue to a subject and listen for changes
// final songModelQueue = _queue.map((e) => e.song).toList();
// _queueSubject.add(_queue);
// final newQueue = _songModelsToAudioSource(songModelQueue);
// _updateQueue(newQueue, currentQueueItem);
// final queueItems = await _queueGenerationModule.generateQueue(
// shuffleMode,
// songs,
// initialIndex,
// );
// final songList = queueItems.map((e) => e.song).toList();
// await _audioPlayerRepository.loadQueue(
// initialIndex: initialIndex,
// queue: songList,
// );
// _audioPlayerRepository.play();
// _platformIntegrationRepository.setCurrentSong(songs[initialIndex]);
// // _platformIntegrationRepository.play();
// _platformIntegrationRepository.setQueue(songList);
_platformIntegrationRepository.setQueue(songList);
}
}

View file

@ -2,7 +2,7 @@ import 'dart:math';
import '../entities/shuffle_mode.dart';
import '../entities/song.dart';
import '../modules/queue_generator.dart';
import '../modules/queue_manager.dart';
import '../repositories/audio_player_repository.dart';
import '../repositories/music_data_repository.dart';
import '../repositories/persistent_player_state_repository.dart';
@ -24,7 +24,7 @@ class ShuffleAll {
final PlatformIntegrationRepository _platformIntegrationRepository;
final PlayerStateRepository _playerStateRepository;
final QueueGenerationModule _queueGenerationModule;
final QueueManagerModule _queueGenerationModule;
Future<void> call() async {
final List<Song> songs = await _musicDataRepository.songStream.first;
@ -33,20 +33,23 @@ class ShuffleAll {
_audioPlayerRepository.setShuffleMode(SHUFFLE_MODE);
final queueItems = await _queueGenerationModule.generateQueue(
await _queueGenerationModule.setQueue(
SHUFFLE_MODE,
songs,
index,
);
final queueItems = _queueGenerationModule.queue;
final songList = queueItems.map((e) => e.song).toList();
await _audioPlayerRepository.loadQueue(
initialIndex: 0,
queue: queueItems,
queue: songList,
);
_audioPlayerRepository.play();
_platformIntegrationRepository.setCurrentSong(songs[index]);
// _platformIntegrationRepository.play();
_platformIntegrationRepository.setQueue(queueItems.map((e) => e.song).toList());
_platformIntegrationRepository.setQueue(songList);
}
}

View file

@ -6,21 +6,27 @@ import 'package:just_audio/just_audio.dart';
import 'domain/actors/audio_player_actor.dart';
import 'domain/actors/platform_integration_actor.dart';
import 'domain/modules/queue_generator.dart';
import 'domain/modules/queue_manager.dart';
import 'domain/repositories/audio_player_repository.dart';
import 'domain/repositories/music_data_modifier_repository.dart';
import 'domain/repositories/music_data_repository.dart';
import 'domain/repositories/persistent_player_state_repository.dart';
import 'domain/repositories/platform_integration_repository.dart';
import 'domain/repositories/settings_repository.dart';
import 'domain/usecases/add_to_queue.dart';
import 'domain/usecases/handle_playback_state.dart';
import 'domain/usecases/move_queue_item.dart';
import 'domain/usecases/pause.dart';
import 'domain/usecases/play.dart';
import 'domain/usecases/play_next.dart';
import 'domain/usecases/play_songs.dart';
import 'domain/usecases/remove_queue_index.dart';
import 'domain/usecases/seek_to_index.dart';
import 'domain/usecases/seek_to_next.dart';
import 'domain/usecases/seek_to_previous.dart';
import 'domain/usecases/set_current_song.dart';
import 'domain/usecases/set_loop_mode.dart';
import 'domain/usecases/set_shuffle_mode.dart';
import 'domain/usecases/shuffle_all.dart';
import 'domain/usecases/update_database.dart';
import 'presentation/state/audio_store.dart';
@ -64,12 +70,18 @@ Future<void> setupGetIt() async {
() {
final audioStore = AudioStore(
audioPlayerInfoRepository: getIt(),
addToQueue: getIt(),
moveQueueItem: getIt(),
pause: getIt(),
play: getIt(),
playNext: getIt(),
playSongs: getIt(),
removeQueueIndex: getIt(),
seekToIndex: getIt(),
seekToNext: getIt(),
seekToPrevious: getIt(),
setLoopMode: getIt(),
setShuffleMode: getIt(),
shuffleAll: getIt(),
);
return audioStore;
@ -83,11 +95,27 @@ Future<void> setupGetIt() async {
);
// use cases
getIt.registerLazySingleton<AddToQueue>(
() => AddToQueue(
getIt(),
getIt(),
getIt(),
getIt(),
),
);
getIt.registerLazySingleton<HandlePlaybackEvent>(
() => HandlePlaybackEvent(
getIt(),
),
);
getIt.registerLazySingleton<MoveQueueItem>(
() => MoveQueueItem(
getIt(),
getIt(),
getIt(),
getIt(),
),
);
getIt.registerLazySingleton<Pause>(
() => Pause(
getIt(),
@ -98,6 +126,14 @@ Future<void> setupGetIt() async {
getIt(),
),
);
getIt.registerLazySingleton<PlayNext>(
() => PlayNext(
getIt(),
getIt(),
getIt(),
getIt(),
),
);
getIt.registerLazySingleton<PlaySongs>(
() => PlaySongs(
getIt(),
@ -106,6 +142,19 @@ Future<void> setupGetIt() async {
getIt(),
),
);
getIt.registerLazySingleton<RemoveQueueIndex>(
() => RemoveQueueIndex(
getIt(),
getIt(),
getIt(),
getIt(),
),
);
getIt.registerLazySingleton<SeekToIndex>(
() => SeekToIndex(
getIt(),
),
);
getIt.registerLazySingleton<SeekToNext>(
() => SeekToNext(
getIt(),
@ -126,6 +175,14 @@ Future<void> setupGetIt() async {
getIt(),
),
);
getIt.registerLazySingleton<SetShuffleMode>(
() => SetShuffleMode(
getIt(),
getIt(),
getIt(),
getIt(),
),
);
getIt.registerLazySingleton<ShuffleAll>(
() => ShuffleAll(
getIt(),
@ -142,8 +199,8 @@ Future<void> setupGetIt() async {
);
// modules
getIt.registerLazySingleton<QueueGenerationModule>(
() => QueueGenerationModule(
getIt.registerLazySingleton<QueueManagerModule>(
() => QueueManagerModule(
getIt(),
),
);

View file

@ -108,6 +108,13 @@ class AlbumDetailsPage extends StatelessWidget {
return Container(
child: Column(
children: [
ListTile(
title: const Text('Play next'),
onTap: () {
audioStore.playNext(song);
Navigator.pop(context);
},
),
ListTile(
title: const Text('Add to queue'),
onTap: () {

View file

@ -47,7 +47,7 @@ class QueuePage extends StatelessWidget {
subtitle: '${song.artist}',
albumArtPath: song.albumArtPath,
highlight: index == activeIndex,
onTap: () => audioStore.setIndex(index),
onTap: () => audioStore.seekToIndex(index),
),
onDismissed: (direction) {
audioStore.removeQueueIndex(index);

View file

@ -77,6 +77,13 @@ class _SongsPageState extends State<SongsPage>
return Container(
child: Column(
children: [
ListTile(
title: const Text('Play next'),
onTap: () {
audioStore.playNext(song);
Navigator.pop(context);
},
),
ListTile(
title: const Text('Add to queue'),
onTap: () {

View file

@ -7,45 +7,77 @@ import '../../domain/entities/loop_mode.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../../domain/entities/song.dart';
import '../../domain/repositories/audio_player_repository.dart';
import '../../domain/usecases/add_to_queue.dart';
import '../../domain/usecases/move_queue_item.dart';
import '../../domain/usecases/pause.dart';
import '../../domain/usecases/play.dart';
import '../../domain/usecases/play_next.dart';
import '../../domain/usecases/play_songs.dart';
import '../../domain/usecases/remove_queue_index.dart';
import '../../domain/usecases/seek_to_index.dart';
import '../../domain/usecases/seek_to_next.dart';
import '../../domain/usecases/seek_to_previous.dart';
import '../../domain/usecases/set_loop_mode.dart';
import '../../domain/usecases/set_shuffle_mode.dart';
import '../../domain/usecases/shuffle_all.dart';
part 'audio_store.g.dart';
class AudioStore extends _AudioStore with _$AudioStore {
AudioStore({
@required AddToQueue addToQueue,
@required MoveQueueItem moveQueueItem,
@required Pause pause,
@required Play play,
@required PlayNext playNext,
@required PlaySongs playSongs,
@required RemoveQueueIndex removeQueueIndex,
@required SeekToIndex seekToIndex,
@required SeekToNext seekToNext,
@required SeekToPrevious seekToPrevious,
@required SetLoopMode setLoopMode,
@required SetShuffleMode setShuffleMode,
@required ShuffleAll shuffleAll,
@required AudioPlayerInfoRepository audioPlayerInfoRepository,
}) : super(playSongs, audioPlayerInfoRepository, pause, play, seekToNext, seekToPrevious,
setLoopMode, shuffleAll);
}) : super(
addToQueue,
moveQueueItem,
playSongs,
audioPlayerInfoRepository,
pause,
play,
playNext,
removeQueueIndex,
seekToIndex,
seekToNext,
seekToPrevious,
setLoopMode,
setShuffleMode,
shuffleAll,
);
}
abstract class _AudioStore with Store {
_AudioStore(
this._addToQueue,
this._moveQueueItem,
this._playSongs,
this._audioPlayerInfoRepository,
this._pause,
this._play,
this._playNext,
this._removeQueueIndex,
this._seekToIndex,
this._seekToNext,
this._seekToPrevious,
this._setLoopMode,
this._setShuffleMode,
this._shuffleAll,
) {
currentPositionStream = _audioPlayerInfoRepository.positionStream
.asObservable(initialValue: const Duration(seconds: 0));
queueStream = _audioPlayerInfoRepository.songListStream.asObservable();
queueStream = _audioPlayerInfoRepository.queueStream.asObservable();
queueIndexStream = _audioPlayerInfoRepository.currentIndexStream.asObservable();
@ -60,12 +92,18 @@ abstract class _AudioStore with Store {
final AudioPlayerInfoRepository _audioPlayerInfoRepository;
final AddToQueue _addToQueue;
final MoveQueueItem _moveQueueItem;
final Pause _pause;
final Play _play;
final PlayNext _playNext;
final PlaySongs _playSongs;
final RemoveQueueIndex _removeQueueIndex;
final SeekToIndex _seekToIndex;
final SeekToNext _seekToNext;
final SeekToPrevious _seekToPrevious;
final SetLoopMode _setLoopMode;
final SetShuffleMode _setShuffleMode;
final ShuffleAll _shuffleAll;
@observable
@ -77,6 +115,8 @@ abstract class _AudioStore with Store {
@observable
ObservableStream<Duration> currentPositionStream;
// beware that this only triggers reactions when a new list (new reference) is set
// doesn't work if the same reference is added to BehaviorSubject
@observable
ObservableStream<List<Song>> queueStream;
@ -109,12 +149,12 @@ abstract class _AudioStore with Store {
_seekToPrevious();
}
Future<void> setIndex(int index) async {
// _audioInterface.setIndex(index);
Future<void> seekToIndex(int index) async {
_seekToIndex(index);
}
Future<void> setShuffleMode(ShuffleMode shuffleMode) async {
// _audioInterface.setShuffleMode(shuffleMode);
_setShuffleMode(shuffleMode);
}
Future<void> setLoopMode(LoopMode loopMode) async {
@ -126,15 +166,19 @@ abstract class _AudioStore with Store {
}
Future<void> addToQueue(Song song) async {
// _audioInterface.addToQueue(song);
_addToQueue(song);
}
Future<void> playNext(Song song) async {
_playNext(song);
}
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
// _audioInterface.moveQueueItem(oldIndex, newIndex);
_moveQueueItem(oldIndex, newIndex);
}
Future<void> removeQueueIndex(int index) async {
// _audioInterface.removeQueueIndex(index);
_removeQueueIndex(index);
}
Future<void> playAlbum(Album album) async {

View file

@ -18,8 +18,6 @@ class AlbumArt extends StatelessWidget {
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2.0),
boxShadow: const [
BoxShadow(color: Colors.black45, blurRadius: 1, offset: Offset(0, 1)),
BoxShadow(color: Colors.black38, blurRadius: 5, offset: Offset(0, 1)),
BoxShadow(color: Colors.black26, blurRadius: 8, offset: Offset(0, 1)),
],
),
@ -33,22 +31,6 @@ class AlbumArt extends StatelessWidget {
fit: BoxFit.cover,
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
height: 150,
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0x00000000), Color(0xBB000000)],
stops: [0.0, 1.0],
),
),
),
),
Positioned(
bottom: 0,
left: 0,

View file

@ -6,7 +6,7 @@ import '../models/song_model.dart';
abstract class AudioPlayerDataSource {
ValueStream<int> get currentIndexStream;
ValueStream<SongModel> get currentSongStream;
// ValueStream<SongModel> get currentSongStream;
Stream<PlaybackEventModel> get playbackEventStream;
ValueStream<bool> get playingStream;
ValueStream<Duration> get positionStream;
@ -21,10 +21,10 @@ abstract class AudioPlayerDataSource {
Future<void> loadQueue({List<SongModel> queue, int initialIndex});
Future<void> addToQueue(SongModel song);
Future<void> moveQueueItem(int oldIndex, int newIndex);
Future<void> playNext(SongModel song);
Future<void> removeQueueIndex(int index);
Future<void> setIndex(int index);
Future<void> replaceQueueAroundIndex({List<SongModel> before, List<SongModel> after, int index});
Future<void> seekToIndex(int index);
Future<void> setLoopMode(LoopMode loopMode);
Future<void> playSongList(List<SongModel> songs, int startIndex);
}

View file

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:just_audio/just_audio.dart' as ja;
import 'package:logging/logging.dart';
import 'package:rxdart/rxdart.dart';
@ -8,24 +10,25 @@ import '../models/playback_event_model.dart';
import '../models/song_model.dart';
import 'audio_player_data_source.dart';
/// beide fälle (start > ende und ende <= start) beim initialen laden schon behandeln
/// offset berechnung passt -> damit sollte die index ausgabe auch stimmen
/// vielleicht lässt sich allgemeiner fall mit modulo rechnung finden: loadIndex = (li+-1) % length
/// die beiden richtungen (skip next/prev) verhalten sich in den beiden fällen genau gegensätzlich
const int LOAD_INTERVAL = 2;
class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
AudioPlayerDataSourceImpl(this._audioPlayer) {
_audioPlayer.currentIndexStream.listen((event) {
_log.info('currentIndex: $event');
_currentIndexSubject.add(event);
if (_queue != null && event != null && event < _queue.length) {
_currentSongSubject.add(_queue[event]);
_audioPlayer.currentIndexStream.listen((index) async {
_log.info('currentIndexSteam.listen: $index');
if (!await _updateLoadedQueue(index)) {
_updateCurrentIndex(index);
}
});
_audioPlayer.playingStream.listen((event) {
_log.info('playing: $event');
_playingSubject.add(event);
});
_audioPlayer.playingStream.listen((event) => _playingSubject.add(event));
_audioPlayer.positionStream.listen((event) {
_positionSubject.add(event);
});
_audioPlayer.positionStream.listen((event) => _positionSubject.add(event));
_playbackEventModelStream = Rx.combineLatest2<ja.PlaybackEvent, bool, PlaybackEventModel>(
_audioPlayer.playbackEventStream,
@ -40,29 +43,43 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
static final _log = Logger('AudioPlayer');
final BehaviorSubject<int> _currentIndexSubject = BehaviorSubject();
final BehaviorSubject<SongModel> _currentSongSubject = BehaviorSubject();
final BehaviorSubject<PlaybackEventModel> _playbackEventSubject = BehaviorSubject();
final BehaviorSubject<bool> _playingSubject = BehaviorSubject();
final BehaviorSubject<Duration> _positionSubject = BehaviorSubject();
Stream<PlaybackEventModel> _playbackEventModelStream;
List<SongModel> _queue;
int _loadStartIndex;
int _loadEndIndex;
void _setQueue(List<SongModel> queue, {int index}) {
_queue = queue;
if (index != null) {
_currentSongSubject.add(_queue[index]);
} else if (_currentIndexSubject.value != null) {
_currentSongSubject.add(_queue[_currentIndexSubject.value]);
set loadStartIndex(int i) {
_loadStartIndex = i;
_log.info('loadStartIndex <- $i');
// _updateCurrentIndex(_audioPlayer.currentIndex);
}
int get loadStartIndex => _loadStartIndex;
set loadEndIndex(int i) {
_loadEndIndex = i;
_log.info('loadEndIndex <- $i');
// _updateCurrentIndex(_audioPlayer.currentIndex);
}
int get loadEndIndex => _loadEndIndex;
int get loadOffset {
if (loadStartIndex != null && loadEndIndex != null) {
final offset = loadStartIndex < loadEndIndex ? loadStartIndex : loadStartIndex - loadEndIndex;
print('offset: $offset');
return offset;
}
return null;
}
@override
ValueStream<int> get currentIndexStream => _currentIndexSubject.stream;
@override
ValueStream<SongModel> get currentSongStream => _currentSongSubject.stream;
@override
Stream<PlaybackEventModel> get playbackEventStream => _playbackEventModelStream;
@ -75,7 +92,6 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
@override
Future<void> dispose() async {
await _currentIndexSubject.close();
await _currentSongSubject.close();
await _playbackEventSubject.close();
await _positionSubject.close();
await _audioPlayer.dispose();
@ -86,12 +102,18 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
if (queue == null || initialIndex == null || initialIndex >= queue.length) {
return;
}
_setQueue(queue, index: initialIndex);
_queue = queue;
// final smallQueue = queue.sublist(max(initialIndex - 10, 0), min(initialIndex + 140, queue.length));
loadStartIndex = max(initialIndex - LOAD_INTERVAL, 0);
loadEndIndex = min(initialIndex + LOAD_INTERVAL + 1, queue.length);
_audioSource = _songModelsToAudioSource(queue);
_audioPlayer.setAudioSource(_audioSource, initialIndex: initialIndex);
final smallQueue = queue.sublist(loadStartIndex, loadEndIndex);
_audioSource = _songModelsToAudioSource(smallQueue);
// _loadStartIndex.add(loadStartIndex);
// _loadEndIndex.add(loadEndIndex);
_audioPlayer.setAudioSource(_audioSource, initialIndex: initialIndex - loadStartIndex);
}
@override
@ -104,26 +126,6 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
_audioPlayer.play();
}
@override
Future<void> playSongList(List<SongModel> songs, int startIndex) async {
// _inputQueue = songs;
// final firstSong = songs[startIndex];
// _queueSubject.add([QueueItemModel(firstSong, originalIndex: startIndex)]);
// _audioSource = _songModelsToAudioSource([firstSong]);
// await _audioPlayer.setAudioSource(_audioSource);
// _audioPlayer.play();
// _queue = await _queueGenerator.generateQueue(_shuffleModeSubject.value, songs, startIndex);
// final songModelQueue = _queue.map((e) => e.song).toList();
// _queueSubject.add(_queue);
// final int splitIndex = _shuffleModeSubject.value == ShuffleMode.none ? startIndex : 0;
// final newQueue = _songModelsToAudioSource(songModelQueue);
// await _audioSource.insertAll(0, newQueue.children.sublist(0, splitIndex));
// _audioSource.addAll(newQueue.children.sublist(splitIndex + 1, newQueue.length));
}
@override
Future<bool> seekToNext() async {
final result = _audioPlayer.hasNext;
@ -141,7 +143,7 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
}
@override
Future<void> setIndex(int index) async {
Future<void> seekToIndex(int index) async {
await _audioPlayer.seek(const Duration(seconds: 0), index: index);
}
@ -153,43 +155,43 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
@override
Future<void> addToQueue(SongModel song) async {
await _audioSource.add(ja.AudioSource.uri(Uri.file(song.path)));
// _queue.add(QueueItemModel(song, originalIndex: -1, type: QueueItemType.added));
// _queueSubject.add(_queue);
_queue.add(song);
}
@override
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
// final QueueItemModel queueItem = _queue.removeAt(oldIndex);
// _queue.insert(newIndex, queueItem);
// _queueSubject.add(_queue);
await _audioSource.move(oldIndex, newIndex);
final song = _queue.removeAt(oldIndex);
_queue.insert(newIndex, song);
}
@override
Future<void> playNext(SongModel song) async {
final index = currentIndexStream.value + 1;
await _audioSource.insert(index, ja.AudioSource.uri(Uri.file(song.path)));
_queue.insert(index, song);
}
@override
Future<void> removeQueueIndex(int index) async {
// _queue.removeAt(index);
// _queueSubject.add(_queue);
await _audioSource.removeAt(index);
_queue.removeAt(index);
}
// @override
// Future<void> setShuffleMode(ShuffleMode shuffleMode, bool updateQueue) async {
// _log.info('setShuffleMode: $shuffleMode');
// if (shuffleMode == null) return;
// _shuffleModeSubject.add(shuffleMode);
@override
Future<void> replaceQueueAroundIndex(
{List<SongModel> before, List<SongModel> after, int index}) async {
_queue = before + [_queue[index]] + after;
// if (updateQueue) {
// final QueueItem currentQueueItem = _queue[_currentIndexSubject.value];
// final int index = currentQueueItem.originalIndex;
// _queue = await _queueGenerator.generateQueue(shuffleMode, _inputQueue, index);
// // TODO: maybe refactor _queue to a subject and listen for changes
// final songModelQueue = _queue.map((e) => e.song).toList();
// _queueSubject.add(_queue);
final _before = _songModelsToAudioSource(before);
final _after = _songModelsToAudioSource(after);
// final newQueue = _songModelsToAudioSource(songModelQueue);
// _updateQueue(newQueue, currentQueueItem);
// }
// }
await _audioSource.removeRange(0, index);
await _audioSource.removeRange(1, _audioSource.length);
await _audioSource.insertAll(0, _before.children);
await _audioSource.addAll(_after.children);
}
@override
Future<void> setLoopMode(LoopMode loopMode) async {
@ -203,32 +205,117 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
);
}
// Future<void> _updateQueue(
// ja.ConcatenatingAudioSource newQueue, QueueItem currentQueueItem) async {
// final int index = currentQueueItem.originalIndex;
/// extend the loaded audiosource, when seeking to previous/next
Future<bool> _updateLoadedQueue(int newIndex) async {
_log.info('updateLoadedQueue: $newIndex');
_log.info('[$loadStartIndex, $loadEndIndex]');
// _audioSource.removeRange(0, _currentIndexSubject.value);
// _audioSource.removeRange(1, _audioSource.length);
if (loadStartIndex == null || loadEndIndex == null || newIndex == null) return false;
// if (_shuffleModeSubject.value == ShuffleMode.none) {
// switch (currentQueueItem.type) {
// case QueueItemType.added:
// case QueueItemType.standard:
// await _audioSource.insertAll(0, newQueue.children.sublist(0, index));
// _audioSource.addAll(newQueue.children.sublist(index + 1));
// break;
// case QueueItemType.predecessor:
// await _audioSource.insertAll(0, newQueue.children.sublist(0, index));
// _audioSource.addAll(newQueue.children.sublist(index));
// break;
// case QueueItemType.successor:
// await _audioSource.insertAll(0, newQueue.children.sublist(0, index + 1));
// _audioSource.addAll(newQueue.children.sublist(index + 1));
// break;
// }
// _currentIndexSubject.add(index);
// } else {
// _audioSource.addAll(newQueue.children.sublist(1));
// }
// }
if (loadStartIndex == loadEndIndex || (loadStartIndex == 0 && loadEndIndex == _queue.length))
return false;
if (loadStartIndex < loadEndIndex) {
_log.info('base case');
return await _updateLoadedQueueBaseCase(newIndex);
} else {
_log.info('inverted case');
return await _updateLoadedQueueInverted(newIndex);
}
}
Future<bool> _updateLoadedQueueBaseCase(int newIndex) async {
if (newIndex < LOAD_INTERVAL) {
// nearing the start of the loaded songs
if (loadStartIndex > 0) {
// load the song previous to the already loaded songs
_log.info('loadStartIndex--');
loadStartIndex--;
await _audioSource.insert(0, ja.AudioSource.uri(Uri.file(_queue[loadStartIndex].path)));
return true;
} else if (loadEndIndex < _queue.length) {
// load the last song, if it isn't already loaded
_log.info('loadStartIndex = ${_queue.length - 1}');
loadStartIndex = _queue.length - 1;
await _audioSource.add(ja.AudioSource.uri(Uri.file(_queue.last.path)));
return false;
}
} else if (newIndex > _audioSource.length - LOAD_INTERVAL - 1) {
// need to load next song
if (loadEndIndex < _queue.length) {
// we ARE NOT at the end of the queue -> load next song
_log.info('loadEndIndex++');
loadEndIndex++;
await _audioSource.add(ja.AudioSource.uri(Uri.file(_queue[loadEndIndex - 1].path)));
return false;
} else if (loadStartIndex > 0) {
// we ARE at the end of the queue AND the first song has not been loaded yet
// -> load first song
_log.info('loadEndIndex = 1');
loadEndIndex = 1;
await _audioSource.insert(0, ja.AudioSource.uri(Uri.file(_queue[0].path)));
return true;
}
}
return false;
}
Future<bool> _updateLoadedQueueInverted(int newIndex) async {
final rightOfLoadEnd = newIndex >= loadEndIndex;
int leftBorder = newIndex - LOAD_INTERVAL;
if (newIndex < loadEndIndex) {
leftBorder += _audioSource.length;
}
int rightBorder = newIndex + LOAD_INTERVAL;
if (newIndex > loadEndIndex) {
rightBorder -= _audioSource.length;
}
if (leftBorder < loadEndIndex) {
// nearing the start of the loaded songs
// load the song previous to the already loaded songs
_log.info('inv: loadStartIndex--');
loadStartIndex--;
await _audioSource.insert(
loadEndIndex, ja.AudioSource.uri(Uri.file(_queue[loadStartIndex].path)));
return rightOfLoadEnd;
} else if (rightBorder >= loadEndIndex) {
// need to load next song
// we ARE NOT at the end of the queue -> load next song
_log.info('inv: loadEndIndex++');
loadEndIndex++;
await _audioSource.insert(
loadEndIndex - 1, ja.AudioSource.uri(Uri.file(_queue[loadEndIndex - 1].path)));
return rightOfLoadEnd;
}
return false;
}
void _updateCurrentIndex(int apIndex) {
if (loadStartIndex == null || loadEndIndex == null) {
_currentIndexSubject.add(apIndex);
return;
}
int result;
if (_audioSource != null && _audioSource.length == _queue.length) {
_log.info('EVERYTHING LOADED');
result = apIndex;
} else if (loadStartIndex < loadEndIndex) {
// base case
result = apIndex != null ? (apIndex + (loadStartIndex ?? 0)) : null;
} else {
// inverted case
if (apIndex < loadEndIndex) {
result = apIndex;
} else {
result = apIndex + (loadStartIndex - loadEndIndex);
}
}
_currentIndexSubject.add(result);
_log.info('updateCurrentIndex: $result');
}
}

View file

@ -2,7 +2,6 @@ import 'package:rxdart/rxdart.dart';
import '../../domain/entities/loop_mode.dart';
import '../../domain/entities/playback_event.dart';
import '../../domain/entities/queue_item.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../../domain/entities/song.dart';
import '../../domain/repositories/audio_player_repository.dart';
@ -13,15 +12,22 @@ class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
AudioPlayerRepositoryImpl(this._audioPlayerDataSource) {
_shuffleModeSubject.add(ShuffleMode.none);
_loopModeSubject.add(LoopMode.off);
_audioPlayerDataSource.currentIndexStream.listen(
(index) {
print('CURRENT INDEX: $index');
_updateCurrentSong(queueStream.value, index);
},
);
_queueSubject.listen((queue) => _updateCurrentSong(queue, currentIndexStream.value));
}
final AudioPlayerDataSource _audioPlayerDataSource;
// final BehaviorSubject<int> _currentIndexSubject = BehaviorSubject();
// final BehaviorSubject<Song> _currentSongSubject = BehaviorSubject<Song>();
final BehaviorSubject<Song> _currentSongSubject = BehaviorSubject<Song>();
final BehaviorSubject<LoopMode> _loopModeSubject = BehaviorSubject();
final BehaviorSubject<List<Song>> _songListSubject = BehaviorSubject();
final BehaviorSubject<List<QueueItem>> _queueSubject = BehaviorSubject();
final BehaviorSubject<List<Song>> _queueSubject = BehaviorSubject();
final BehaviorSubject<ShuffleMode> _shuffleModeSubject = BehaviorSubject();
@override
@ -34,16 +40,13 @@ class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
ValueStream<LoopMode> get loopModeStream => _loopModeSubject.stream;
@override
ValueStream<List<Song>> get songListStream => _songListSubject.stream;
@override
ValueStream<List<QueueItem>> get queueStream => _queueSubject.stream;
ValueStream<List<Song>> get queueStream => _queueSubject.stream;
@override
ValueStream<int> get currentIndexStream => _audioPlayerDataSource.currentIndexStream;
@override
Stream<Song> get currentSongStream => _audioPlayerDataSource.currentSongStream;
Stream<Song> get currentSongStream => _currentSongSubject.stream;
@override
Stream<PlaybackEvent> get playbackEventStream => _audioPlayerDataSource.playbackEventStream;
@ -55,9 +58,9 @@ class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
Stream<Duration> get positionStream => _audioPlayerDataSource.positionStream;
@override
Future<void> addToQueue(Song song) {
// TODO: implement addToQueue
throw UnimplementedError();
Future<void> addToQueue(Song song) async {
_audioPlayerDataSource.addToQueue(song as SongModel);
_queueSubject.add(_queueSubject.value + [song]);
}
@override
@ -66,10 +69,9 @@ class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
}
@override
Future<void> loadQueue({List<QueueItem> queue, int initialIndex}) async {
Future<void> loadQueue({List<Song> queue, int initialIndex}) async {
// _currentSongSubject.add(queue[initialIndex]);
_queueSubject.add(queue);
_songListSubject.add(queue.map((e) => e.song).toList());
// _currentIndexSubject.add(initialIndex);
await _audioPlayerDataSource.loadQueue(
@ -79,9 +81,15 @@ class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
}
@override
Future<void> moveQueueItem(int oldIndex, int newIndex) {
// TODO: implement moveQueueItem
throw UnimplementedError();
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
final _songList = _queueSubject.value.toList();
_audioPlayerDataSource.moveQueueItem(oldIndex, newIndex);
final song = _songList.removeAt(oldIndex);
_songList.insert(newIndex, song);
_queueSubject.add(_songList);
}
@override
@ -104,9 +112,32 @@ class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
}
@override
Future<void> removeQueueIndex(int index) {
// TODO: implement removeQueueIndex
throw UnimplementedError();
Future<void> playNext(Song song) async {
final index = currentIndexStream.value + 1;
final _songList = _queueSubject.value;
_audioPlayerDataSource.playNext(song as SongModel);
_queueSubject.add(_songList.sublist(0, index) + [song] + _songList.sublist(index));
}
@override
Future<void> removeQueueIndex(int index) async {
final _songList = _queueSubject.value;
_audioPlayerDataSource.removeQueueIndex(index);
_queueSubject.add(_songList.sublist(0, index) + _songList.sublist(index + 1));
}
@override
Future<void> replaceQueueAroundIndex({List<Song> before, List<Song> after, int index}) async {
_queueSubject.add(before + [_queueSubject.value[index]] + after);
await _audioPlayerDataSource.replaceQueueAroundIndex(
before: before.map((e) => e as SongModel).toList(),
after: after.map((e) => e as SongModel).toList(),
index: index,
);
}
@override
@ -120,9 +151,8 @@ class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
}
@override
Future<void> setIndex(int index) {
// TODO: implement setIndex
throw UnimplementedError();
Future<void> seekToIndex(int index) async {
await _audioPlayerDataSource.seekToIndex(index);
}
@override
@ -141,4 +171,10 @@ class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
// TODO: implement stop
throw UnimplementedError();
}
void _updateCurrentSong(List<Song> queue, int index) {
if (queue != null && index != null && index < queue.length) {
_currentSongSubject.add(queue[index]);
}
}
}