big changes

This commit is contained in:
Moritz Weber 2021-02-26 22:07:18 +01:00
parent 320028dafa
commit 39e4111037
45 changed files with 1608 additions and 1083 deletions

View file

@ -0,0 +1,18 @@
import '../entities/song.dart';
import '../repositories/audio_player_repository.dart';
import '../usecases/handle_playback_state.dart';
import '../usecases/set_current_song.dart';
class AudioPlayerActor {
AudioPlayerActor(this._audioPlayerInfoRepository, this._handlePlaybackEvent, this._setCurrentSong) {
_audioPlayerInfoRepository.currentSongStream.listen(_handleCurrentSong);
_audioPlayerInfoRepository.playbackEventStream.listen(_handlePlaybackEvent);
}
final AudioPlayerInfoRepository _audioPlayerInfoRepository;
final HandlePlaybackEvent _handlePlaybackEvent;
final SetCurrentSong _setCurrentSong;
void _handleCurrentSong(Song song) => _setCurrentSong(song);
}

View file

@ -0,0 +1,27 @@
import '../repositories/platform_integration_repository.dart';
import '../usecases/pause.dart';
import '../usecases/play.dart';
class PlatformIntegrationActor {
PlatformIntegrationActor(this._platformIntegrationInfoRepository, this._pause, this._play) {
_platformIntegrationInfoRepository.eventStream
.listen((event) => _handlePlatformIntegrationEvent(event));
}
final PlatformIntegrationInfoRepository _platformIntegrationInfoRepository;
final Pause _pause;
final Play _play;
void _handlePlatformIntegrationEvent(PlatformIntegrationEvent event) {
switch (event.type) {
case PlatformIntegrationEventType.play:
_play();
break;
case PlatformIntegrationEventType.pause:
_pause();
break;
default:
}
}
}

View file

@ -0,0 +1,3 @@
abstract class Event {
Map<String, dynamic> payload;
}

View file

@ -0,0 +1,149 @@
import '../entities/queue_item.dart';
import '../entities/shuffle_mode.dart';
import '../entities/song.dart';
import '../repositories/music_data_repository.dart';
class QueueGenerationModule {
QueueGenerationModule(this._musicDataRepository);
final MusicDataInfoRepository _musicDataRepository;
Future<List<QueueItem>> generateQueue(
ShuffleMode shuffleMode,
List<Song> songs,
int startIndex,
) async {
List<QueueItem> queue;
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);
}
return queue;
}
List<QueueItem> _generateNormalQueue(List<Song> songs) {
return List<QueueItem>.generate(
songs.length,
(i) => QueueItem(
songs[i],
originalIndex: i,
),
);
}
List<QueueItem> _generateShuffleQueue(
List<Song> songs,
int startIndex,
) {
final List<QueueItem> queue = List<QueueItem>.generate(
songs.length,
(i) => QueueItem(
songs[i],
originalIndex: i,
),
);
queue.removeAt(startIndex);
queue.shuffle();
final first = QueueItem(
songs[startIndex],
originalIndex: startIndex,
);
return [first] + queue;
}
Future<List<QueueItem>> _generateShufflePlusQueue(
List<Song> songs,
int startIndex,
) async {
final List<QueueItem> queue = await _getQueueItemWithLinks(
songs[startIndex],
startIndex,
);
final List<int> indices = [];
// filter mediaitem list
// TODO: multiply higher rated songs
for (var i = 0; i < songs.length; i++) {
if (i != startIndex && !songs[i].blocked) {
indices.add(i);
}
}
indices.shuffle();
for (var i = 0; i < indices.length; i++) {
final int index = indices[i];
final Song song = songs[index];
queue.addAll(await _getQueueItemWithLinks(song, index));
}
return queue;
}
// TODO: naming things is hard
Future<List<QueueItem>> _getQueueItemWithLinks(
Song song,
int index,
) async {
final List<QueueItem> queueItems = [];
final predecessors = await _getPredecessors(song);
final successors = await _getSuccessors(song);
for (final p in predecessors) {
queueItems.add(QueueItem(
p,
originalIndex: index,
type: QueueItemType.predecessor,
));
}
queueItems.add(QueueItem(
song,
originalIndex: index,
));
for (final p in successors) {
queueItems.add(QueueItem(
p,
originalIndex: index,
type: QueueItemType.successor,
));
}
return queueItems;
}
Future<List<Song>> _getPredecessors(Song song) async {
final List<Song> songs = [];
Song currentSong = song;
while (currentSong.previous != null) {
currentSong = await _musicDataRepository.getSongByPath(currentSong.previous);
songs.add(currentSong);
}
return songs.reversed.toList();
}
Future<List<Song>> _getSuccessors(Song song) async {
final List<Song> songs = [];
Song currentSong = song;
while (currentSong.next != null) {
currentSong = await _musicDataRepository.getSongByPath(currentSong.next);
songs.add(currentSong);
}
return songs.toList();
}
}

View file

@ -0,0 +1,49 @@
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';
abstract class AudioPlayerInfoRepository {
Stream<AudioPlayerEvent> eventStream;
ValueStream<ShuffleMode> get shuffleModeStream;
ValueStream<LoopMode> get loopModeStream;
ValueStream<List<Song>> get songListStream;
ValueStream<List<QueueItem>> get queueStream;
ValueStream<int> get currentIndexStream;
Stream<Song> get currentSongStream;
Stream<PlaybackEvent> get playbackEventStream;
Stream<bool> get playingStream;
Stream<Duration> get positionStream;
}
abstract class AudioPlayerRepository extends AudioPlayerInfoRepository {
Future<void> play();
Future<void> pause();
Future<void> stop();
Future<bool> seekToNext();
Future<void> seekToPrevious();
Future<void> dispose();
Future<void> playSong(Song song);
Future<void> loadQueue({List<QueueItem> queue, int initialIndex});
Future<void> addToQueue(Song song);
Future<void> moveQueueItem(int oldIndex, int newIndex);
Future<void> removeQueueIndex(int index);
Future<void> setIndex(int index);
/// Set the ShuffleMode. Does not affect playback/queue.
Future<void> setShuffleMode(ShuffleMode shuffleMode);
Future<void> setLoopMode(LoopMode loopMode);
}
class AudioPlayerEvent extends Event {
AudioPlayerEventType type;
}
enum AudioPlayerEventType { dummy }

View file

@ -1,30 +0,0 @@
import '../entities/album.dart';
import '../entities/artist.dart';
import '../entities/loop_mode.dart';
import '../entities/playback_state.dart';
import '../entities/shuffle_mode.dart';
import '../entities/song.dart';
abstract class AudioRepository {
Stream<Song> get currentSongStream;
Stream<PlaybackState> get playbackStateStream;
Stream<int> get currentPositionStream;
Future<void> playSong(int index, List<Song> songList);
Future<void> play();
Future<void> pause();
Future<void> skipToNext();
Future<void> skipToPrevious();
Future<void> setIndex(int index);
Future<void> playAlbum(Album album);
Future<void> playArtist(Artist artist, {ShuffleMode shuffleMode = ShuffleMode.none});
Future<void> setShuffleMode(ShuffleMode shuffleMode);
Future<void> setLoopMode(LoopMode loopMode);
Future<void> shuffleAll();
Future<void> addToQueue(Song song);
Future<void> moveQueueItem(int oldIndex, int newIndex);
Future<void> removeQueueIndex(int index);
}

View file

@ -2,7 +2,8 @@ import '../entities/album.dart';
import '../entities/artist.dart';
import '../entities/song.dart';
abstract class MusicDataRepository {
abstract class MusicDataInfoRepository {
Future<Song> getSongByPath(String path);
Stream<List<Song>> get songStream;
Stream<List<Song>> getAlbumSongStream(Album album);
@ -10,6 +11,8 @@ abstract class MusicDataRepository {
Stream<List<Album>> getArtistAlbumStream(Artist artist);
Stream<List<Artist>> get artistStream;
}
abstract class MusicDataRepository extends MusicDataInfoRepository {
Future<void> updateDatabase();
}

View file

@ -1,4 +1,5 @@
import '../entities/loop_mode.dart';
import '../entities/queue_item.dart';
import '../entities/shuffle_mode.dart';
import '../entities/song.dart';
@ -8,4 +9,9 @@ abstract class PlayerStateRepository {
Stream<Song> get currentSongStream;
Stream<LoopMode> get loopModeStream;
Stream<ShuffleMode> get shuffleModeStream;
void setShuffleMode(ShuffleMode shuffleMode);
void setLoopMode(LoopMode loopMode);
void setQueue(List<QueueItem> queue);
void setCurrentIndex(int index);
}

View file

@ -0,0 +1,38 @@
import '../entities/event.dart';
import '../entities/playback_event.dart';
import '../entities/song.dart';
/*
- position
- controls (playbackState)
*/
abstract class PlatformIntegrationInfoRepository {
Stream<PlatformIntegrationEvent> get eventStream;
}
abstract class PlatformIntegrationRepository extends PlatformIntegrationInfoRepository {
void play();
void pause();
void onStop();
void handlePlaybackEvent(PlaybackEvent playbackEvent);
void setCurrentSong(Song song);
void setQueue(List<Song> queue);
}
class PlatformIntegrationEvent extends Event {
PlatformIntegrationEvent({this.type});
final PlatformIntegrationEventType type;
}
enum PlatformIntegrationEventType {
play,
pause,
stop,
skipNext,
skipPrevious,
}

View file

@ -0,0 +1,12 @@
import '../entities/playback_event.dart';
import '../repositories/platform_integration_repository.dart';
class HandlePlaybackEvent {
HandlePlaybackEvent(this._platformIntegrationRepository);
final PlatformIntegrationRepository _platformIntegrationRepository;
Future<void> call(PlaybackEvent playbackEvent) async {
_platformIntegrationRepository.handlePlaybackEvent(playbackEvent);
}
}

View file

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

View file

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

View file

@ -0,0 +1,42 @@
import '../entities/song.dart';
import '../modules/queue_generator.dart';
import '../repositories/audio_player_repository.dart';
import '../repositories/persistent_player_state_repository.dart';
import '../repositories/platform_integration_repository.dart';
class PlaySongs {
PlaySongs(
this._audioPlayerRepository,
this._platformIntegrationRepository,
this._playerStateRepository,
this._queueGenerationModule,
);
final AudioPlayerRepository _audioPlayerRepository;
final PlatformIntegrationRepository _platformIntegrationRepository;
final PlayerStateRepository _playerStateRepository;
final QueueGenerationModule _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,
songs,
initialIndex,
);
await _audioPlayerRepository.loadQueue(
initialIndex: initialIndex,
queue: queueItems,
);
_audioPlayerRepository.play();
_platformIntegrationRepository.setCurrentSong(songs[initialIndex]);
// _platformIntegrationRepository.play();
_platformIntegrationRepository.setQueue(queueItems.map((e) => e.song).toList());
}
}
}

View file

@ -0,0 +1,11 @@
import '../repositories/audio_player_repository.dart';
class SeekToNext {
SeekToNext(this._audioPlayerRepository);
final AudioPlayerRepository _audioPlayerRepository;
Future<bool> call() async {
return await _audioPlayerRepository.seekToNext();
}
}

View file

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

View file

@ -0,0 +1,12 @@
import '../entities/song.dart';
import '../repositories/platform_integration_repository.dart';
class SetCurrentSong {
SetCurrentSong(this._platformIntegrationRepository);
final PlatformIntegrationRepository _platformIntegrationRepository;
Future<void> call(Song song) async {
_platformIntegrationRepository.setCurrentSong(song);
}
}

View file

@ -0,0 +1,12 @@
import '../entities/loop_mode.dart';
import '../repositories/audio_player_repository.dart';
class SetLoopMode {
SetLoopMode(this._audioPlayerRepository);
final AudioPlayerRepository _audioPlayerRepository;
Future<void> call(LoopMode loopMode) async {
await _audioPlayerRepository.setLoopMode(loopMode);
}
}

View file

@ -0,0 +1,64 @@
import 'package:mucke/domain/entities/queue_item.dart';
import '../entities/shuffle_mode.dart';
import '../entities/song.dart';
import '../modules/queue_generator.dart';
import '../repositories/audio_player_repository.dart';
import '../repositories/persistent_player_state_repository.dart';
import '../repositories/platform_integration_repository.dart';
class SetShuffleMode {
SetShuffleMode(
this._audioPlayerRepository,
this._platformIntegrationRepository,
this._playerStateRepository,
this._queueGenerationModule,
);
final AudioPlayerRepository _audioPlayerRepository;
final PlatformIntegrationRepository _platformIntegrationRepository;
final PlayerStateRepository _playerStateRepository;
final QueueGenerationModule _queueGenerationModule;
Future<void> call(ShuffleMode shuffleMode) async {
// _audioPlayerRepository.playSong(songs[initialIndex]);
_audioPlayerRepository.setShuffleMode(shuffleMode);
final queue = _audioPlayerRepository.queueStream.value;
final currentIndex = _audioPlayerRepository.currentIndexStream.value;
final QueueItem currentQueueItem = queue[currentIndex];
final int index = currentQueueItem.originalIndex;
// WAS LETZTE GEDANKE?
// _inputQueue ist die originale Song Liste aus playSongs o.ä. BEVOR der QueueGenerator drüber läuft
// _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);
}
}

View file

@ -0,0 +1,52 @@
import 'dart:math';
import '../entities/shuffle_mode.dart';
import '../entities/song.dart';
import '../modules/queue_generator.dart';
import '../repositories/audio_player_repository.dart';
import '../repositories/music_data_repository.dart';
import '../repositories/persistent_player_state_repository.dart';
import '../repositories/platform_integration_repository.dart';
const SHUFFLE_MODE = ShuffleMode.plus;
class ShuffleAll {
ShuffleAll(
this._audioPlayerRepository,
this._musicDataRepository,
this._platformIntegrationRepository,
this._playerStateRepository,
this._queueGenerationModule,
);
final AudioPlayerRepository _audioPlayerRepository;
final MusicDataRepository _musicDataRepository;
final PlatformIntegrationRepository _platformIntegrationRepository;
final PlayerStateRepository _playerStateRepository;
final QueueGenerationModule _queueGenerationModule;
Future<void> call() async {
final List<Song> songs = await _musicDataRepository.songStream.first;
final rng = Random();
final index = rng.nextInt(songs.length);
_audioPlayerRepository.setShuffleMode(SHUFFLE_MODE);
final queueItems = await _queueGenerationModule.generateQueue(
SHUFFLE_MODE,
songs,
index,
);
await _audioPlayerRepository.loadQueue(
initialIndex: 0,
queue: queueItems,
);
_audioPlayerRepository.play();
_platformIntegrationRepository.setCurrentSong(songs[index]);
// _platformIntegrationRepository.play();
_platformIntegrationRepository.setQueue(queueItems.map((e) => e.song).toList());
}
}

View file

@ -0,0 +1,11 @@
import '../repositories/music_data_repository.dart';
class UpdateDatabase {
UpdateDatabase(this._musicDataRepository);
final MusicDataRepository _musicDataRepository;
Future<void> call() async {
await _musicDataRepository.updateDatabase();
}
}

View file

@ -2,30 +2,45 @@ import 'package:audio_service/audio_service.dart';
import 'package:device_info/device_info.dart';
import 'package:flutter_audio_query/flutter_audio_query.dart';
import 'package:get_it/get_it.dart';
import 'package:just_audio/just_audio.dart' as ja;
import 'package:just_audio/just_audio.dart';
import 'domain/repositories/audio_repository.dart';
import 'domain/actors/audio_player_actor.dart';
import 'domain/actors/platform_integration_actor.dart';
import 'domain/modules/queue_generator.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/handle_playback_state.dart';
import 'domain/usecases/pause.dart';
import 'domain/usecases/play.dart';
import 'domain/usecases/play_songs.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/shuffle_all.dart';
import 'domain/usecases/update_database.dart';
import 'presentation/state/audio_store.dart';
import 'presentation/state/music_data_store.dart';
import 'presentation/state/navigation_store.dart';
import 'system/audio/audio_handler.dart';
import 'system/audio/audio_player_contract.dart';
import 'system/audio/audio_player_impl.dart';
import 'system/audio/queue_generator.dart';
import 'system/datasources/audio_player_data_source.dart';
import 'system/datasources/audio_player_data_source_impl.dart';
import 'system/datasources/local_music_fetcher.dart';
import 'system/datasources/local_music_fetcher_contract.dart';
import 'system/datasources/local_music_fetcher_impl.dart';
import 'system/datasources/moor_database.dart';
import 'system/datasources/music_data_source_contract.dart';
import 'system/datasources/platform_integration_data_source.dart';
import 'system/datasources/platform_integration_data_source_impl.dart';
import 'system/datasources/player_state_data_source.dart';
import 'system/datasources/settings_data_source.dart';
import 'system/repositories/audio_repository_impl.dart';
import 'system/repositories/audio_player_repository_impl.dart';
import 'system/repositories/music_data_modifier_repository_impl.dart';
import 'system/repositories/music_data_repository_impl.dart';
import 'system/repositories/persistent_player_state_repository_impl.dart';
import 'system/repositories/platform_integration_repository_impl.dart';
import 'system/repositories/settings_repository_impl.dart';
final GetIt getIt = GetIt.instance;
@ -37,9 +52,10 @@ Future<void> setupGetIt() async {
getIt.registerFactory<MusicDataStore>(
() {
final musicDataStore = MusicDataStore(
musicDataRepository: getIt(),
musicDataInfoRepository: getIt(),
settingsRepository: getIt(),
musicDataModifierRepository: getIt(),
updateDatabase: getIt(),
);
return musicDataStore;
},
@ -47,8 +63,14 @@ Future<void> setupGetIt() async {
getIt.registerFactory<AudioStore>(
() {
final audioStore = AudioStore(
audioRepository: getIt(),
persistentPlayerStateRepository: getIt(),
audioPlayerInfoRepository: getIt(),
pause: getIt(),
play: getIt(),
playSongs: getIt(),
seekToNext: getIt(),
seekToPrevious: getIt(),
setLoopMode: getIt(),
shuffleAll: getIt(),
);
return audioStore;
},
@ -60,23 +82,97 @@ Future<void> setupGetIt() async {
},
);
// use cases
getIt.registerLazySingleton<HandlePlaybackEvent>(
() => HandlePlaybackEvent(
getIt(),
),
);
getIt.registerLazySingleton<Pause>(
() => Pause(
getIt(),
),
);
getIt.registerLazySingleton<Play>(
() => Play(
getIt(),
),
);
getIt.registerLazySingleton<PlaySongs>(
() => PlaySongs(
getIt(),
getIt(),
getIt(),
getIt(),
),
);
getIt.registerLazySingleton<SeekToNext>(
() => SeekToNext(
getIt(),
),
);
getIt.registerLazySingleton<SeekToPrevious>(
() => SeekToPrevious(
getIt(),
),
);
getIt.registerLazySingleton<SetCurrentSong>(
() => SetCurrentSong(
getIt(),
),
);
getIt.registerLazySingleton<SetLoopMode>(
() => SetLoopMode(
getIt(),
),
);
getIt.registerLazySingleton<ShuffleAll>(
() => ShuffleAll(
getIt(),
getIt(),
getIt(),
getIt(),
getIt(),
),
);
getIt.registerLazySingleton<UpdateDatabase>(
() => UpdateDatabase(
getIt(),
),
);
// modules
getIt.registerLazySingleton<QueueGenerationModule>(
() => QueueGenerationModule(
getIt(),
),
);
// repositories
getIt.registerLazySingleton<AudioPlayerRepository>(
() => AudioPlayerRepositoryImpl(
getIt(),
),
);
getIt.registerLazySingleton<AudioPlayerInfoRepository>(
() => getIt<AudioPlayerRepository>(),
);
getIt.registerLazySingleton<MusicDataRepository>(
() => MusicDataRepositoryImpl(
getIt(),
getIt(),
),
);
getIt.registerLazySingleton<MusicDataInfoRepository>(
() => getIt<MusicDataRepository>(),
);
getIt.registerLazySingleton<MusicDataModifierRepository>(
() => MusicDataModifierRepositoryImpl(
getIt(),
),
);
getIt.registerLazySingleton<AudioRepository>(
() => AudioRepositoryImpl(
getIt(),
),
);
getIt.registerLazySingleton<PlayerStateRepository>(
() => PlayerStateRepositoryImpl(
getIt(),
@ -87,6 +183,14 @@ Future<void> setupGetIt() async {
getIt(),
),
);
getIt.registerLazySingleton<PlatformIntegrationRepository>(
() => PlatformIntegrationRepositoryImpl(
getIt(),
),
);
getIt.registerLazySingleton<PlatformIntegrationInfoRepository>(
() => getIt<PlatformIntegrationRepository>(),
);
// data sources
final MoorDatabase moorDatabase = MoorDatabase();
@ -101,14 +205,20 @@ Future<void> setupGetIt() async {
),
);
final AudioPlayer audioPlayer = AudioPlayerImpl(
ja.AudioPlayer(),
QueueGenerator(getIt()),
final AudioPlayerDataSource audioPlayer = AudioPlayerDataSourceImpl(
AudioPlayer(),
);
getIt.registerLazySingleton<AudioPlayer>(() => audioPlayer);
getIt.registerLazySingleton<AudioPlayerDataSource>(() => audioPlayer);
final PlatformIntegrationDataSource _platformIntegrationDataSource =
PlatformIntegrationDataSourceImpl();
getIt.registerLazySingleton<PlatformIntegrationDataSource>(
() => _platformIntegrationDataSource,
);
// external
final _audioHandler = await AudioService.init(
builder: () => MyAudioHandler(getIt(), getIt(), getIt()),
builder: () => _platformIntegrationDataSource as AudioHandler,
config: AudioServiceConfig(
androidNotificationChannelName: 'mucke',
androidEnableQueue: true,
@ -116,12 +226,26 @@ Future<void> setupGetIt() async {
);
getIt.registerLazySingleton<AudioHandler>(() => _audioHandler);
getIt.registerLazySingleton<QueueGenerator>(() => QueueGenerator(getIt()));
// external
getIt.registerFactory<ja.AudioPlayer>(() => ja.AudioPlayer());
getIt.registerFactory<AudioPlayer>(() => AudioPlayer());
getIt.registerLazySingleton<FlutterAudioQuery>(() => FlutterAudioQuery());
getIt.registerLazySingleton<DeviceInfoPlugin>(() => DeviceInfoPlugin());
// actors
getIt.registerSingleton<PlatformIntegrationActor>(
PlatformIntegrationActor(
getIt(),
getIt(),
getIt(),
),
);
getIt.registerSingleton<AudioPlayerActor>(
AudioPlayerActor(
getIt(),
getIt(),
getIt(),
),
);
}

View file

@ -4,49 +4,78 @@ import 'package:mobx/mobx.dart';
import '../../domain/entities/album.dart';
import '../../domain/entities/artist.dart';
import '../../domain/entities/loop_mode.dart';
import '../../domain/entities/playback_state.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../../domain/entities/song.dart';
import '../../domain/repositories/audio_repository.dart';
import '../../domain/repositories/persistent_player_state_repository.dart';
import '../../domain/repositories/audio_player_repository.dart';
import '../../domain/usecases/pause.dart';
import '../../domain/usecases/play.dart';
import '../../domain/usecases/play_songs.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/shuffle_all.dart';
part 'audio_store.g.dart';
class AudioStore extends _AudioStore with _$AudioStore {
AudioStore({
@required AudioRepository audioRepository,
@required PlayerStateRepository persistentPlayerStateRepository,
}) : super(audioRepository, persistentPlayerStateRepository);
@required Pause pause,
@required Play play,
@required PlaySongs playSongs,
@required SeekToNext seekToNext,
@required SeekToPrevious seekToPrevious,
@required SetLoopMode setLoopMode,
@required ShuffleAll shuffleAll,
@required AudioPlayerInfoRepository audioPlayerInfoRepository,
}) : super(playSongs, audioPlayerInfoRepository, pause, play, seekToNext, seekToPrevious,
setLoopMode, shuffleAll);
}
abstract class _AudioStore with Store {
_AudioStore(this._audioRepository, this._persistentPlayerStateRepository) {
currentPositionStream = _audioRepository.currentPositionStream.asObservable(initialValue: 0);
_AudioStore(
this._playSongs,
this._audioPlayerInfoRepository,
this._pause,
this._play,
this._seekToNext,
this._seekToPrevious,
this._setLoopMode,
this._shuffleAll,
) {
currentPositionStream = _audioPlayerInfoRepository.positionStream
.asObservable(initialValue: const Duration(seconds: 0));
queueStream = _persistentPlayerStateRepository.queueStream.asObservable();
queueStream = _audioPlayerInfoRepository.songListStream.asObservable();
queueIndexStream = _persistentPlayerStateRepository.currentIndexStream.asObservable();
queueIndexStream = _audioPlayerInfoRepository.currentIndexStream.asObservable();
currentSongStream = _persistentPlayerStateRepository.currentSongStream.asObservable();
currentSongStream = _audioPlayerInfoRepository.currentSongStream.asObservable();
shuffleModeStream = _persistentPlayerStateRepository.shuffleModeStream.asObservable();
shuffleModeStream = _audioPlayerInfoRepository.shuffleModeStream.asObservable();
loopModeStream = _persistentPlayerStateRepository.loopModeStream.asObservable();
loopModeStream = _audioPlayerInfoRepository.loopModeStream.asObservable();
playbackStateStream = _audioRepository.playbackStateStream.asObservable();
playingStream = _audioPlayerInfoRepository.playingStream.asObservable();
}
final AudioRepository _audioRepository;
final PlayerStateRepository _persistentPlayerStateRepository;
final AudioPlayerInfoRepository _audioPlayerInfoRepository;
final Pause _pause;
final Play _play;
final PlaySongs _playSongs;
final SeekToNext _seekToNext;
final SeekToPrevious _seekToPrevious;
final SetLoopMode _setLoopMode;
final ShuffleAll _shuffleAll;
@observable
ObservableStream<Song> currentSongStream;
@observable
ObservableStream<PlaybackState> playbackStateStream;
ObservableStream<bool> playingStream;
@observable
ObservableStream<int> currentPositionStream;
ObservableStream<Duration> currentPositionStream;
@observable
ObservableStream<List<Song>> queueStream;
@ -61,58 +90,58 @@ abstract class _AudioStore with Store {
ObservableStream<LoopMode> loopModeStream;
Future<void> playSong(int index, List<Song> songList) async {
_audioRepository.playSong(index, songList);
_playSongs(songs: songList, initialIndex: index);
}
Future<void> play() async {
_audioRepository.play();
_play();
}
Future<void> pause() async {
_audioRepository.pause();
_pause();
}
Future<void> skipToNext() async {
_audioRepository.skipToNext();
_seekToNext();
}
Future<void> skipToPrevious() async {
_audioRepository.skipToPrevious();
_seekToPrevious();
}
Future<void> setIndex(int index) async {
_audioRepository.setIndex(index);
// _audioInterface.setIndex(index);
}
Future<void> setShuffleMode(ShuffleMode shuffleMode) async {
_audioRepository.setShuffleMode(shuffleMode);
// _audioInterface.setShuffleMode(shuffleMode);
}
Future<void> setLoopMode(LoopMode loopMode) async {
_audioRepository.setLoopMode(loopMode);
_setLoopMode(loopMode);
}
Future<void> shuffleAll() async {
_audioRepository.shuffleAll();
_shuffleAll();
}
Future<void> addToQueue(Song song) async {
_audioRepository.addToQueue(song);
// _audioInterface.addToQueue(song);
}
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
_audioRepository.moveQueueItem(oldIndex, newIndex);
// _audioInterface.moveQueueItem(oldIndex, newIndex);
}
Future<void> removeQueueIndex(int index) async {
_audioRepository.removeQueueIndex(index);
// _audioInterface.removeQueueIndex(index);
}
Future<void> playAlbum(Album album) async {
_audioRepository.playAlbum(album);
// _audioInterface.playAlbum(album);
}
Future<void> shuffleArtist(Artist artist) async {
_audioRepository.playArtist(artist, shuffleMode: ShuffleMode.plus);
// _audioInterface.playArtist(artist, shuffleMode: ShuffleMode.plus);
}
}

View file

@ -24,19 +24,18 @@ mixin _$AudioStore on _AudioStore, Store {
});
}
final _$playbackStateStreamAtom =
Atom(name: '_AudioStore.playbackStateStream');
final _$playingStreamAtom = Atom(name: '_AudioStore.playingStream');
@override
ObservableStream<PlaybackState> get playbackStateStream {
_$playbackStateStreamAtom.reportRead();
return super.playbackStateStream;
ObservableStream<bool> get playingStream {
_$playingStreamAtom.reportRead();
return super.playingStream;
}
@override
set playbackStateStream(ObservableStream<PlaybackState> value) {
_$playbackStateStreamAtom.reportWrite(value, super.playbackStateStream, () {
super.playbackStateStream = value;
set playingStream(ObservableStream<bool> value) {
_$playingStreamAtom.reportWrite(value, super.playingStream, () {
super.playingStream = value;
});
}
@ -44,13 +43,13 @@ mixin _$AudioStore on _AudioStore, Store {
Atom(name: '_AudioStore.currentPositionStream');
@override
ObservableStream<int> get currentPositionStream {
ObservableStream<Duration> get currentPositionStream {
_$currentPositionStreamAtom.reportRead();
return super.currentPositionStream;
}
@override
set currentPositionStream(ObservableStream<int> value) {
set currentPositionStream(ObservableStream<Duration> value) {
_$currentPositionStreamAtom.reportWrite(value, super.currentPositionStream,
() {
super.currentPositionStream = value;
@ -121,7 +120,7 @@ mixin _$AudioStore on _AudioStore, Store {
String toString() {
return '''
currentSongStream: ${currentSongStream},
playbackStateStream: ${playbackStateStream},
playingStream: ${playingStream},
currentPositionStream: ${currentPositionStream},
queueStream: ${queueStream},
queueIndexStream: ${queueIndexStream},

View file

@ -7,26 +7,35 @@ import '../../domain/entities/song.dart';
import '../../domain/repositories/music_data_modifier_repository.dart';
import '../../domain/repositories/music_data_repository.dart';
import '../../domain/repositories/settings_repository.dart';
import '../../domain/usecases/update_database.dart';
part 'music_data_store.g.dart';
class MusicDataStore extends _MusicDataStore with _$MusicDataStore {
MusicDataStore({
@required MusicDataRepository musicDataRepository,
@required SettingsRepository settingsRepository,
@required MusicDataInfoRepository musicDataInfoRepository,
@required MusicDataModifierRepository musicDataModifierRepository,
}) : super(musicDataRepository, settingsRepository, musicDataModifierRepository);
@required SettingsRepository settingsRepository,
@required UpdateDatabase updateDatabase,
}) : super(
musicDataInfoRepository,
settingsRepository,
musicDataModifierRepository,
updateDatabase,
);
}
abstract class _MusicDataStore with Store {
_MusicDataStore(
this._musicDataRepository, this._settingsRepository, this._musicDataModifierRepository) {
songStream = _musicDataRepository.songStream.asObservable(initialValue: []);
albumStream = _musicDataRepository.albumStream.asObservable(initialValue: []);
artistStream = _musicDataRepository.artistStream.asObservable(initialValue: []);
_MusicDataStore(this._musicDataInfoRepository, this._settingsRepository,
this._musicDataModifierRepository, this._updateDatabase) {
songStream = _musicDataInfoRepository.songStream.asObservable(initialValue: []);
albumStream = _musicDataInfoRepository.albumStream.asObservable(initialValue: []);
artistStream = _musicDataInfoRepository.artistStream.asObservable(initialValue: []);
}
final MusicDataRepository _musicDataRepository;
final UpdateDatabase _updateDatabase;
final MusicDataInfoRepository _musicDataInfoRepository;
final MusicDataModifierRepository _musicDataModifierRepository;
final SettingsRepository _settingsRepository;
@ -49,30 +58,30 @@ abstract class _MusicDataStore with Store {
bool isUpdatingDatabase = false;
@computed
List<Album> get sortedArtistAlbums => artistAlbumStream.value.toList()..sort((a, b) {
if (b.pubYear == null)
return -1;
if (a.pubYear == null)
return 1;
return -a.pubYear.compareTo(b.pubYear);
});
List<Album> get sortedArtistAlbums => artistAlbumStream.value.toList()
..sort((a, b) {
if (b.pubYear == null) return -1;
if (a.pubYear == null) return 1;
return -a.pubYear.compareTo(b.pubYear);
});
@action
Future<void> updateDatabase() async {
isUpdatingDatabase = true;
await _musicDataRepository.updateDatabase();
await _updateDatabase();
isUpdatingDatabase = false;
}
@action
Future<void> fetchSongsFromAlbum(Album album) async {
albumSongStream = _musicDataRepository.getAlbumSongStream(album).asObservable(initialValue: []);
albumSongStream =
_musicDataInfoRepository.getAlbumSongStream(album).asObservable(initialValue: []);
}
@action
Future<void> fetchAlbumsFromArtist(Artist artist) async {
artistAlbumStream =
_musicDataRepository.getArtistAlbumStream(artist).asObservable(initialValue: []);
_musicDataInfoRepository.getArtistAlbumStream(artist).asObservable(initialValue: []);
}
Future<void> setSongBlocked(Song song, bool blocked) async {

View file

@ -11,7 +11,7 @@ ImageProvider getAlbumImage(String albumArtPath) {
return FileImage(File(albumArtPath));
}
String msToTimeString(int milliseconds) {
String msToTimeString(Duration duration) {
String twoDigits(num n) {
if (n >= 10) {
return '$n';
@ -19,8 +19,6 @@ String msToTimeString(int milliseconds) {
return '0$n';
}
final Duration duration = Duration(seconds: (milliseconds / 1000).round());
final int hours = duration.inHours;
final int minutes = duration.inMinutes.remainder(60) as int;

View file

@ -18,70 +18,74 @@ class CurrentlyPlayingBar extends StatelessWidget {
return Observer(
builder: (BuildContext context) {
final Song song = audioStore.currentSongStream.value;
if (song != null) {
return Column(
verticalDirection: VerticalDirection.up,
children: <Widget>[
GestureDetector(
onTap: () => _onTap(context),
child: Container(
color: Colors.transparent,
child: Row(
children: <Widget>[
Image(
image: getAlbumImage(song.albumArtPath),
height: 64.0,
),
Container(
width: 10.0,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
song.title,
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
Text(
song.artist,
style: const TextStyle(
fontSize: 12.0,
color: Colors.white70,
),
)
],
if (audioStore.currentSongStream != null) {
final Song song = audioStore.currentSongStream.value;
final Duration position = audioStore.currentPositionStream?.value ?? const Duration(seconds: 0);
if (song != null) {
return Column(
verticalDirection: VerticalDirection.up,
children: <Widget>[
GestureDetector(
onTap: () => _onTap(context),
child: Container(
color: Colors.transparent,
child: Row(
children: <Widget>[
Image(
image: getAlbumImage(song.albumArtPath),
height: 64.0,
),
),
const PlayPauseButton(
circle: false,
),
const NextButton(),
Container(
width: 10.0,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
song.title,
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
Text(
song.artist,
style: const TextStyle(
fontSize: 12.0,
color: Colors.white70,
),
)
],
),
),
const PlayPauseButton(
circle: false,
),
const NextButton(),
],
),
),
),
Container(
child: LinearProgressIndicator(
value: position.inMilliseconds / song.duration,
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
backgroundColor: Colors.white10,
),
height: 2,
decoration: BoxDecoration(
boxShadow: [
BoxShadow(color: Theme.of(context).primaryColor, blurRadius: 1),
],
),
),
),
Container(
child: LinearProgressIndicator(
value: audioStore.currentPositionStream.value / song.duration,
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
backgroundColor: Colors.white10,
),
height: 2,
decoration: BoxDecoration(
boxShadow: [
BoxShadow(color: Theme.of(context).primaryColor, blurRadius: 1),
],
),
),
],
],
);
}
return Container(
height: 0,
);
}
return Container(
height: 0,
);
return Container();
},
);
}

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
import '../../domain/entities/loop_mode.dart';
import '../state/audio_store.dart';
class NextButton extends StatelessWidget {
@ -17,8 +18,9 @@ class NextButton extends StatelessWidget {
builder: (BuildContext context) {
final queue = audioStore.queueStream.value;
final int index = audioStore.queueIndexStream.value;
final LoopMode loopMode = audioStore.loopModeStream.value;
if (index != null && index < queue.length - 1) {
if ((index != null && index < queue.length - 1) || loopMode != LoopMode.off) {
return IconButton(
icon: const Icon(Icons.skip_next_rounded),
iconSize: iconSize,

View file

@ -2,12 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
import '../../domain/entities/playback_state.dart';
import '../state/audio_store.dart';
class PlayPauseButton extends StatelessWidget {
const PlayPauseButton({Key key, this.circle = false, this.iconSize = 24.0})
: super(key: key);
const PlayPauseButton({Key key, this.circle = false, this.iconSize = 24.0}) : super(key: key);
final bool circle;
final double iconSize;
@ -18,28 +16,26 @@ class PlayPauseButton extends StatelessWidget {
return Observer(
builder: (BuildContext context) {
switch (audioStore.playbackStateStream.value) {
case PlaybackState.playing:
return IconButton(
icon: circle
? const Icon(Icons.pause_circle_filled_rounded)
: const Icon(Icons.pause_rounded),
iconSize: iconSize,
onPressed: () {
audioStore.pause();
},
);
case PlaybackState.paused:
default:
return IconButton(
icon: circle
? const Icon(Icons.play_circle_filled_rounded)
: const Icon(Icons.play_arrow_rounded),
iconSize: iconSize,
onPressed: () {
audioStore.play();
},
);
if (audioStore.playingStream?.value != null && audioStore.playingStream.value) {
return IconButton(
icon: circle
? const Icon(Icons.pause_circle_filled_rounded)
: const Icon(Icons.pause_rounded),
iconSize: iconSize,
onPressed: () {
audioStore.pause();
},
);
} else {
return IconButton(
icon: circle
? const Icon(Icons.play_circle_filled_rounded)
: const Icon(Icons.play_arrow_rounded),
iconSize: iconSize,
onPressed: () {
audioStore.play();
},
);
}
},
);

View file

@ -16,7 +16,8 @@ class TimeProgressIndicator extends StatelessWidget {
return Observer(
builder: (BuildContext context) {
final int duration = audioStore.currentSongStream.value?.duration ?? 1000;
final duration =
Duration(milliseconds: audioStore.currentSongStream.value?.duration ?? 1000);
return Row(
children: [
@ -37,7 +38,9 @@ class TimeProgressIndicator extends StatelessWidget {
alignment: Alignment.centerLeft,
child: FractionallySizedBox(
widthFactor: min(
audioStore.currentPositionStream.value / duration, 1.0),
audioStore.currentPositionStream.value.inMilliseconds / duration.inMilliseconds,
1.0,
),
heightFactor: 1.0,
child: Container(
height: double.infinity,

View file

@ -3,63 +3,59 @@ import 'dart:math';
import 'package:audio_service/audio_service.dart';
import 'package:logging/logging.dart';
import '../../domain/entities/loop_mode.dart';
import '../../domain/entities/playback_event.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../datasources/music_data_source_contract.dart';
import '../datasources/player_state_data_source.dart';
import '../models/album_model.dart';
import '../models/artist_model.dart';
import '../models/playback_event_model.dart';
import '../models/queue_item_model.dart';
import '../models/song_model.dart';
import 'audio_player_contract.dart';
import 'stream_constants.dart';
const String SET_CURRENT_SONG = 'SET_CURRENT_SONG';
const String PLAY = 'PLAY';
const String PAUSE = 'PAUSE';
class MyAudioHandler extends BaseAudioHandler {
MyAudioHandler(this._musicDataSource, this._audioPlayer, this._playerStateDataSource) {
_audioPlayer.queueStream.skip(1).listen((event) {
_handleSetQueue(event);
});
MyAudioHandler(this._musicDataSource, this._playerStateDataSource) {
// _audioPlayer.queueStream.skip(1).listen((event) {
// _handleSetQueue(event);
// });
_audioPlayer.currentSongStream.listen((songModel) => mediaItem.add(songModel.toMediaItem()));
// _audioPlayer.playbackEventStream.listen((event) => _handlePlaybackEvent(event));
_audioPlayer.playbackEventStream.listen((event) => _handlePlaybackEvent(event));
// _audioPlayer.shuffleModeStream.skip(1).listen((shuffleMode) {
// _playerStateDataSource.setShuffleMode(shuffleMode);
// });
_audioPlayer.shuffleModeStream.skip(1).listen((shuffleMode) {
_playerStateDataSource.setShuffleMode(shuffleMode);
});
// _audioPlayer.loopModeStream.skip(1).listen((event) {
// _playerStateDataSource.setLoopMode(event);
// });
_audioPlayer.loopModeStream.skip(1).listen((event) {
_playerStateDataSource.setLoopMode(event);
});
// _audioPlayer.positionStream.listen((event) {
// _handlePosition(event, _audioPlayer.currentSongStream.value);
// });
_audioPlayer.positionStream.listen((event) {
_handlePosition(event, _audioPlayer.currentSongStream.value);
});
_initAudioPlayer();
// _initAudioPlayer();
}
Future<void> _initAudioPlayer() async {
if (_playerStateDataSource.loopModeStream != null) {
_audioPlayer.setLoopMode(await _playerStateDataSource.loopModeStream.first);
}
// Future<void> _initAudioPlayer() async {
// if (_playerStateDataSource.loopModeStream != null) {
// _audioPlayer.setLoopMode(await _playerStateDataSource.loopModeStream.first);
// }
if (_playerStateDataSource.shuffleModeStream != null) {
_audioPlayer.setShuffleMode(await _playerStateDataSource.shuffleModeStream.first, false);
}
// if (_playerStateDataSource.shuffleModeStream != null) {
// _audioPlayer.setShuffleMode(await _playerStateDataSource.shuffleModeStream.first, false);
// }
if (_playerStateDataSource.queueStream != null &&
_playerStateDataSource.currentIndexStream != null) {
_audioPlayer.loadQueue(
queue: await _playerStateDataSource.queueStream.first,
initialIndex: await _playerStateDataSource.currentIndexStream.first,
);
}
}
// if (_playerStateDataSource.queueStream != null &&
// _playerStateDataSource.currentIndexStream != null) {
// _audioPlayer.loadQueue(
// queue: await _playerStateDataSource.queueStream.first,
// initialIndex: await _playerStateDataSource.currentIndexStream.first,
// );
// }
// }
final AudioPlayer _audioPlayer;
// final AudioPlayerDataSource _audioPlayer;
final MusicDataSource _musicDataSource;
final PlayerStateDataSource _playerStateDataSource;
@ -69,167 +65,72 @@ class MyAudioHandler extends BaseAudioHandler {
@override
Future<void> stop() async {
await _audioPlayer.stop();
await _audioPlayer.dispose();
// await _audioPlayer.stop();
// await _audioPlayer.dispose();
await super.stop();
}
@override
Future<void> play() async {
_audioPlayer.play();
}
@override
Future<void> pause() async {
await _audioPlayer.pause();
}
@override
Future<void> skipToNext() async {
_audioPlayer.seekToNext().then((value) {
if (value) {
_musicDataSource.incrementSkipCount(_audioPlayer.currentSongStream.value);
}
});
}
@override
Future<void> skipToPrevious() async {
_audioPlayer.seekToPrevious();
// _audioPlayer.seekToNext().then((value) {
// if (value) {
// _musicDataSource.incrementSkipCount(_audioPlayer.currentSongStream.value);
// }
// });
}
@override
Future<void> addQueueItem(MediaItem mediaItem) async {
_audioPlayer.addToQueue(SongModel.fromMediaItem(mediaItem));
// _audioPlayer.addToQueue(SongModel.fromMediaItem(mediaItem));
}
@override
Future<void> removeQueueItemAt(int index) async {
_audioPlayer.removeQueueIndex(index);
}
@override
Future<void> customAction(String name, Map<String, dynamic> arguments) async {
switch (name) {
case PLAY_WITH_CONTEXT:
final context = arguments['CONTEXT'] as List<String>;
final index = arguments['INDEX'] as int;
return playWithContext(context, index);
case SET_SHUFFLE_MODE:
return setCustomShuffleMode(arguments['SHUFFLE_MODE'] as ShuffleMode);
case SET_LOOP_MODE:
return setCustomLoopMode(arguments['LOOP_MODE'] as LoopMode);
case SHUFFLE_ALL:
return shuffleAll();
case MOVE_QUEUE_ITEM:
return moveQueueItem(arguments['OLD_INDEX'] as int, arguments['NEW_INDEX'] as int);
case SET_INDEX:
return setIndex(arguments['INDEX'] as int);
case PLAY_ALBUM:
return playAlbum(arguments['ALBUM'] as AlbumModel);
case PLAY_ARTIST:
return playArtist(
arguments['ARTIST'] as ArtistModel,
arguments['SHUFFLE_MODE'] as ShuffleMode,
);
default:
}
}
Future<void> playWithContext(List<String> context, int index) async {
final songs = <SongModel>[];
for (final path in context) {
final song = await _musicDataSource.getSongByPath(path);
songs.add(song);
}
_audioPlayer.playSongList(songs, index);
}
Future<void> setCustomShuffleMode(ShuffleMode mode) async {
_audioPlayer.setShuffleMode(mode, true);
}
Future<void> setCustomLoopMode(LoopMode mode) async {
_audioPlayer.setLoopMode(mode);
}
Future<void> shuffleAll() async {
_audioPlayer.setShuffleMode(ShuffleMode.plus, false);
final List<SongModel> songs = await _musicDataSource.getSongs();
final rng = Random();
final index = rng.nextInt(songs.length);
_audioPlayer.playSongList(songs, index);
// _audioPlayer.removeQueueIndex(index);
}
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
_audioPlayer.moveQueueItem(oldIndex, newIndex);
// _audioPlayer.moveQueueItem(oldIndex, newIndex);
}
Future<void> setIndex(int index) async {
_audioPlayer.setIndex(index);
// _audioPlayer.setIndex(index);
}
Future<void> playAlbum(AlbumModel album) async {
_audioPlayer.setShuffleMode(ShuffleMode.none, false);
// _audioPlayer.setShuffleMode(ShuffleMode.none, false);
final List<SongModel> songs = await _musicDataSource.getAlbumSongStream(album).first;
_audioPlayer.playSongList(songs, 0);
// _audioPlayer.playSongList(songs, 0);
}
Future<void> playArtist(ArtistModel artist, ShuffleMode shuffleMode) async {
_audioPlayer.setShuffleMode(shuffleMode, false);
// _audioPlayer.setShuffleMode(shuffleMode, false);
final List<SongModel> songs = await _musicDataSource.getArtistSongStream(artist).first;
final rng = Random();
final index = rng.nextInt(songs.length);
_audioPlayer.playSongList(songs, index);
// _audioPlayer.playSongList(songs, index);
}
void _handleSetQueue(List<QueueItemModel> queueItems) {
_playerStateDataSource.setQueue(queueItems);
// void _handleSetQueue(List<QueueItemModel> queueItems) {
// _playerStateDataSource.setQueue(queueItems);
final mediaItems = queueItems.map((e) => e.song.toMediaItem()).toList();
queue.add(mediaItems);
}
// final mediaItems = queueItems.map((e) => e.song.toMediaItem()).toList();
// queue.add(mediaItems);
// }
void _handlePlaybackEvent(PlaybackEventModel pe) {
if (pe.index != null) {
_playerStateDataSource.setCurrentIndex(pe.index);
}
// void _handlePosition(Duration position, SongModel song) {
// if (song == null || position == null) return;
if (pe.processingState == ProcessingState.ready) {
if (_audioPlayer.playingStream.value) {
playbackState.add(playbackState.value.copyWith(
controls: [MediaControl.skipToPrevious, MediaControl.pause, MediaControl.skipToNext],
playing: true,
processingState: AudioProcessingState.ready,
updatePosition: pe.updatePosition,
));
} else {
playbackState.add(playbackState.value.copyWith(
controls: [MediaControl.skipToPrevious, MediaControl.play, MediaControl.skipToNext],
processingState: AudioProcessingState.ready,
updatePosition: pe.updatePosition,
playing: false,
));
}
}
}
// final int pos = position.inMilliseconds;
void _handlePosition(Duration position, SongModel song) {
if (song == null || position == null) return;
final int pos = position.inMilliseconds;
if (pos < song.duration * 0.05) {
_countSongPlayback = true;
} else if (pos > song.duration * 0.95 && _countSongPlayback) {
_countSongPlayback = false;
_musicDataSource.incrementPlayCount(song);
}
}
// if (pos < song.duration * 0.05) {
// _countSongPlayback = true;
// } else if (pos > song.duration * 0.95 && _countSongPlayback) {
// _countSongPlayback = false;
// _musicDataSource.incrementPlayCount(song);
// }
// }
}

View file

@ -1,254 +0,0 @@
import 'package:just_audio/just_audio.dart' as ja;
import 'package:logging/logging.dart';
import 'package:rxdart/rxdart.dart';
import '../../domain/entities/loop_mode.dart';
import '../../domain/entities/queue_item.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../models/loop_mode_model.dart';
import '../models/playback_event_model.dart';
import '../models/queue_item_model.dart';
import '../models/song_model.dart';
import 'audio_player_contract.dart';
import 'queue_generator.dart';
class AudioPlayerImpl implements AudioPlayer {
AudioPlayerImpl(this._audioPlayer, this._queueGenerator) {
_audioPlayer.currentIndexStream.listen((event) {
_log.info('currentIndex: $event');
_currentIndexSubject.add(event);
if (_queueSubject.value != null && event != null) {
_currentSongSubject.add(_queueSubject.value[event].song);
}
});
_audioPlayer.playingStream.listen((event) {
_log.info('playing: $event');
_playingSubject.add(event);
});
_audioPlayer.positionStream.listen((event) {
_positionSubject.add(event);
});
_audioPlayer.loopModeStream.listen((event) {
_log.info('loopMode: $event');
_loopModeSubject.add(event.toEntity());
});
_queueSubject.listen((event) {
if (_currentIndexSubject.value != null) {
_currentSongSubject.add(event[_currentIndexSubject.value].song);
}
});
_playbackEventModelStream = Rx.combineLatest2<ja.PlaybackEvent, bool, PlaybackEventModel>(
_audioPlayer.playbackEventStream,
_audioPlayer.playingStream,
(a, b) => PlaybackEventModel.fromJAPlaybackEvent(a, b),
).distinct();
}
final ja.AudioPlayer _audioPlayer;
ja.ConcatenatingAudioSource _audioSource;
final QueueGenerator _queueGenerator;
List<SongModel> _inputQueue;
List<QueueItemModel> _queue;
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();
final BehaviorSubject<List<QueueItemModel>> _queueSubject = BehaviorSubject();
final BehaviorSubject<ShuffleMode> _shuffleModeSubject = BehaviorSubject.seeded(ShuffleMode.none);
final BehaviorSubject<LoopMode> _loopModeSubject = BehaviorSubject();
Stream<PlaybackEventModel> _playbackEventModelStream;
@override
ValueStream<int> get currentIndexStream => _currentIndexSubject.stream;
@override
ValueStream<SongModel> get currentSongStream => _currentSongSubject.stream;
@override
Stream<PlaybackEventModel> get playbackEventStream => _playbackEventModelStream;
@override
ValueStream<Duration> get positionStream => _positionSubject.stream;
@override
ValueStream<bool> get playingStream => _playingSubject.stream;
@override
ValueStream<List<QueueItemModel>> get queueStream => _queueSubject.stream;
@override
ValueStream<ShuffleMode> get shuffleModeStream => _shuffleModeSubject.stream;
@override
ValueStream<LoopMode> get loopModeStream => _loopModeSubject.stream;
@override
Future<void> dispose() async {
await _currentIndexSubject.close();
await _currentSongSubject.close();
await _playbackEventSubject.close();
await _positionSubject.close();
await _queueSubject.close();
await _shuffleModeSubject.close();
await _loopModeSubject.close();
await _audioPlayer.dispose();
}
@override
Future<void> loadQueue({List<QueueItemModel> queue, int initialIndex = 0}) async {
if (queue == null || initialIndex == null || initialIndex >= queue.length) {
return;
}
_queue = queue;
_queueSubject.add(queue);
// final smallQueue = queue.sublist(max(initialIndex - 10, 0), min(initialIndex + 140, queue.length));
final songModelQueue = queue.map((e) => e.song).toList();
_audioSource = _queueGenerator.songModelsToAudioSource(songModelQueue);
_audioPlayer.setAudioSource(_audioSource, initialIndex: initialIndex);
}
@override
Future<void> pause() async {
await _audioPlayer.pause();
}
@override
Future<void> play() async {
_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 = _queueGenerator.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 = _queueGenerator.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;
await _audioPlayer.seekToNext();
return result;
}
@override
Future<void> seekToPrevious() async {
if (_audioPlayer.position > const Duration(seconds: 3) || !_audioPlayer.hasPrevious) {
await _audioPlayer.seek(const Duration(seconds: 0));
} else {
await _audioPlayer.seekToPrevious();
}
}
@override
Future<void> setIndex(int index) async {
await _audioPlayer.seek(const Duration(seconds: 0), index: index);
}
@override
Future<void> stop() async {
_audioPlayer.stop();
}
@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);
}
@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);
}
@override
Future<void> removeQueueIndex(int index) async {
_queue.removeAt(index);
_queueSubject.add(_queue);
await _audioSource.removeAt(index);
}
@override
Future<void> setShuffleMode(ShuffleMode shuffleMode, bool updateQueue) async {
_log.info('setShuffleMode: $shuffleMode');
if (shuffleMode == null) return;
_shuffleModeSubject.add(shuffleMode);
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 newQueue = _queueGenerator.songModelsToAudioSource(songModelQueue);
_updateQueue(newQueue, currentQueueItem);
}
}
@override
Future<void> setLoopMode(LoopMode loopMode) async {
if (loopMode == null) return;
await _audioPlayer.setLoopMode(loopMode.toJA());
}
Future<void> _updateQueue(
ja.ConcatenatingAudioSource newQueue, QueueItem currentQueueItem) async {
final int index = currentQueueItem.originalIndex;
_audioSource.removeRange(0, _currentIndexSubject.value);
_audioSource.removeRange(1, _audioSource.length);
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));
}
}
}

View file

@ -1,165 +0,0 @@
import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';
import '../../domain/entities/queue_item.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../datasources/music_data_source_contract.dart';
import '../models/queue_item_model.dart';
import '../models/song_model.dart';
class QueueGenerator {
QueueGenerator(this._musicDataSource);
final MusicDataSource _musicDataSource;
Future<List<QueueItemModel>> generateQueue(
ShuffleMode shuffleMode,
List<SongModel> songModels,
int startIndex,
) async {
List<QueueItemModel> queue;
switch (shuffleMode) {
case ShuffleMode.none:
queue = _generateNormalQueue(songModels);
break;
case ShuffleMode.standard:
queue = _generateShuffleQueue(songModels, startIndex);
break;
case ShuffleMode.plus:
queue = await _generateShufflePlusQueue(songModels, startIndex);
}
return queue;
}
ConcatenatingAudioSource mediaItemsToAudioSource(List<MediaItem> mediaItems) {
return ConcatenatingAudioSource(
children: mediaItems.map((MediaItem m) => AudioSource.uri(Uri.file(m.id))).toList(),
);
}
ConcatenatingAudioSource songModelsToAudioSource(List<SongModel> songModels) {
return ConcatenatingAudioSource(
children: songModels.map((SongModel m) => AudioSource.uri(Uri.file(m.path))).toList(),
);
}
List<QueueItemModel> _generateNormalQueue(List<SongModel> songs) {
return List<QueueItemModel>.generate(
songs.length,
(i) => QueueItemModel(
songs[i],
originalIndex: i,
),
);
}
List<QueueItemModel> _generateShuffleQueue(
List<SongModel> songs,
int startIndex,
) {
final List<QueueItemModel> queue = List<QueueItemModel>.generate(
songs.length,
(i) => QueueItemModel(
songs[i],
originalIndex: i,
),
);
queue.removeAt(startIndex);
queue.shuffle();
final first = QueueItemModel(
songs[startIndex],
originalIndex: startIndex,
);
return [first] + queue;
}
Future<List<QueueItemModel>> _generateShufflePlusQueue(
List<SongModel> songs,
int startIndex,
) async {
final List<QueueItemModel> queue = await _getQueueItemWithLinks(
songs[startIndex],
startIndex,
);
final List<int> indices = [];
// filter mediaitem list
// TODO: multiply higher rated songs
for (var i = 0; i < songs.length; i++) {
if (i != startIndex && !songs[i].blocked) {
indices.add(i);
}
}
indices.shuffle();
for (var i = 0; i < indices.length; i++) {
final int index = indices[i];
final SongModel song = songs[index];
queue.addAll(await _getQueueItemWithLinks(song, index));
}
return queue;
}
// TODO: naming things is hard
Future<List<QueueItemModel>> _getQueueItemWithLinks(
SongModel song,
int index,
) async {
final List<QueueItemModel> queueItems = [];
final predecessors = await _getPredecessors(song);
final successors = await _getSuccessors(song);
for (final p in predecessors) {
queueItems.add(QueueItemModel(
p,
originalIndex: index,
type: QueueItemType.predecessor,
));
}
queueItems.add(QueueItemModel(
song,
originalIndex: index,
));
for (final p in successors) {
queueItems.add(QueueItemModel(
p,
originalIndex: index,
type: QueueItemType.successor,
));
}
return queueItems;
}
Future<List<SongModel>> _getPredecessors(SongModel song) async {
final List<SongModel> songs = [];
SongModel currentSong = song;
while (currentSong.previous != null) {
currentSong = await _musicDataSource.getSongByPath(currentSong.previous);
songs.add(currentSong);
}
return songs.reversed.toList();
}
Future<List<SongModel>> _getSuccessors(SongModel song) async {
final List<SongModel> songs = [];
SongModel currentSong = song;
while (currentSong.next != null) {
currentSong = await _musicDataSource.getSongByPath(currentSong.next);
songs.add(currentSong);
}
return songs.toList();
}
}

View file

@ -1,8 +0,0 @@
const String PLAY_WITH_CONTEXT = 'PLAY_WITH_CONTEXT';
const String SHUFFLE_ALL = 'SHUFFLE_ALL';
const String SET_SHUFFLE_MODE = 'SET_SHUFFLE_MODE';
const String SET_LOOP_MODE = 'SET_LOOP_MODE';
const String SET_INDEX = 'SET_INDEX';
const String MOVE_QUEUE_ITEM = 'MOVE_QUEUE_ITEM';
const String PLAY_ALBUM = 'PLAY_ALBUM';
const String PLAY_ARTIST = 'PLAY_ARTIST';

View file

@ -1,20 +1,15 @@
import 'package:rxdart/rxdart.dart';
import '../../domain/entities/loop_mode.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../models/playback_event_model.dart';
import '../models/queue_item_model.dart';
import '../models/song_model.dart';
abstract class AudioPlayer {
abstract class AudioPlayerDataSource {
ValueStream<int> get currentIndexStream;
ValueStream<SongModel> get currentSongStream;
Stream<PlaybackEventModel> get playbackEventStream;
ValueStream<bool> get playingStream;
ValueStream<Duration> get positionStream;
ValueStream<List<QueueItemModel>> get queueStream;
ValueStream<ShuffleMode> get shuffleModeStream;
ValueStream<LoopMode> get loopModeStream;
Future<void> play();
Future<void> pause();
@ -23,14 +18,12 @@ abstract class AudioPlayer {
Future<void> seekToPrevious();
Future<void> dispose();
Future<void> loadQueue({List<QueueItemModel> queue, int initialIndex});
Future<void> loadQueue({List<SongModel> queue, int initialIndex});
Future<void> addToQueue(SongModel song);
Future<void> moveQueueItem(int oldIndex, int newIndex);
Future<void> removeQueueIndex(int index);
Future<void> setIndex(int index);
Future<void> setShuffleMode(ShuffleMode shuffleMode, bool updateQueue);
Future<void> setLoopMode(LoopMode loopMode);
Future<void> playSongList(List<SongModel> songs, int startIndex);

View file

@ -0,0 +1,234 @@
import 'package:just_audio/just_audio.dart' as ja;
import 'package:logging/logging.dart';
import 'package:rxdart/rxdart.dart';
import '../../domain/entities/loop_mode.dart';
import '../models/loop_mode_model.dart';
import '../models/playback_event_model.dart';
import '../models/song_model.dart';
import 'audio_player_data_source.dart';
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.playingStream.listen((event) {
_log.info('playing: $event');
_playingSubject.add(event);
});
_audioPlayer.positionStream.listen((event) {
_positionSubject.add(event);
});
_playbackEventModelStream = Rx.combineLatest2<ja.PlaybackEvent, bool, PlaybackEventModel>(
_audioPlayer.playbackEventStream,
_audioPlayer.playingStream,
(a, b) => PlaybackEventModel.fromJAPlaybackEvent(a, b),
).distinct();
}
final ja.AudioPlayer _audioPlayer;
ja.ConcatenatingAudioSource _audioSource;
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;
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]);
}
}
@override
ValueStream<int> get currentIndexStream => _currentIndexSubject.stream;
@override
ValueStream<SongModel> get currentSongStream => _currentSongSubject.stream;
@override
Stream<PlaybackEventModel> get playbackEventStream => _playbackEventModelStream;
@override
ValueStream<Duration> get positionStream => _positionSubject.stream;
@override
ValueStream<bool> get playingStream => _playingSubject.stream;
@override
Future<void> dispose() async {
await _currentIndexSubject.close();
await _currentSongSubject.close();
await _playbackEventSubject.close();
await _positionSubject.close();
await _audioPlayer.dispose();
}
@override
Future<void> loadQueue({List<SongModel> queue, int initialIndex = 0}) async {
if (queue == null || initialIndex == null || initialIndex >= queue.length) {
return;
}
_setQueue(queue, index: initialIndex);
// final smallQueue = queue.sublist(max(initialIndex - 10, 0), min(initialIndex + 140, queue.length));
_audioSource = _songModelsToAudioSource(queue);
_audioPlayer.setAudioSource(_audioSource, initialIndex: initialIndex);
}
@override
Future<void> pause() async {
await _audioPlayer.pause();
}
@override
Future<void> play() async {
_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;
await _audioPlayer.seekToNext();
return result;
}
@override
Future<void> seekToPrevious() async {
if (_audioPlayer.position > const Duration(seconds: 3) || !_audioPlayer.hasPrevious) {
await _audioPlayer.seek(const Duration(seconds: 0));
} else {
await _audioPlayer.seekToPrevious();
}
}
@override
Future<void> setIndex(int index) async {
await _audioPlayer.seek(const Duration(seconds: 0), index: index);
}
@override
Future<void> stop() async {
_audioPlayer.stop();
}
@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);
}
@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);
}
@override
Future<void> removeQueueIndex(int index) async {
// _queue.removeAt(index);
// _queueSubject.add(_queue);
await _audioSource.removeAt(index);
}
// @override
// Future<void> setShuffleMode(ShuffleMode shuffleMode, bool updateQueue) async {
// _log.info('setShuffleMode: $shuffleMode');
// if (shuffleMode == null) return;
// _shuffleModeSubject.add(shuffleMode);
// 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 newQueue = _songModelsToAudioSource(songModelQueue);
// _updateQueue(newQueue, currentQueueItem);
// }
// }
@override
Future<void> setLoopMode(LoopMode loopMode) async {
if (loopMode == null) return;
await _audioPlayer.setLoopMode(loopMode.toJA());
}
ja.ConcatenatingAudioSource _songModelsToAudioSource(List<SongModel> songModels) {
return ja.ConcatenatingAudioSource(
children: songModels.map((SongModel m) => ja.AudioSource.uri(Uri.file(m.path))).toList(),
);
}
// Future<void> _updateQueue(
// ja.ConcatenatingAudioSource newQueue, QueueItem currentQueueItem) async {
// final int index = currentQueueItem.originalIndex;
// _audioSource.removeRange(0, _currentIndexSubject.value);
// _audioSource.removeRange(1, _audioSource.length);
// 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));
// }
// }
}

View file

@ -1,109 +1,14 @@
import 'dart:typed_data';
import 'dart:ui';
import 'package:device_info/device_info.dart';
import 'package:flutter_audio_query/flutter_audio_query.dart';
import '../models/album_model.dart';
import '../models/artist_model.dart';
import '../models/song_model.dart';
import 'local_music_fetcher_contract.dart';
import 'settings_data_source.dart';
class LocalMusicFetcherImpl implements LocalMusicFetcher {
LocalMusicFetcherImpl(this._flutterAudioQuery, this._settingsDataSource, this._deviceInfo);
abstract class LocalMusicFetcher {
Future<Map<String, List>> getLocalMusic();
final FlutterAudioQuery _flutterAudioQuery;
final SettingsDataSource _settingsDataSource;
// CODESMELL: should probably encapsulate the deviceinfoplugin
final DeviceInfoPlugin _deviceInfo;
AndroidDeviceInfo _androidDeviceInfo;
Future<AndroidDeviceInfo> get androidDeviceInfo async {
_androidDeviceInfo ??= await _deviceInfo.androidInfo;
return _androidDeviceInfo;
}
@override
Future<List<ArtistModel>> getArtists() async {
final List<ArtistInfo> artistInfoList = await _flutterAudioQuery.getArtists();
return artistInfoList
.map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo))
.toSet()
.toList();
}
@override
Future<List<AlbumModel>> getAlbums() async {
final List<AlbumInfo> albumInfoList = await _flutterAudioQuery.getAlbums();
return albumInfoList.map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo)).toList();
}
@override
Future<List<SongModel>> getSongs() async {
final List<SongInfo> songInfoList = await _flutterAudioQuery.getSongs();
return songInfoList
.where((songInfo) => songInfo.isMusic)
.map((SongInfo songInfo) => SongModel.fromSongInfo(songInfo))
.toList();
}
@override
Future<Uint8List> getAlbumArtwork(int id) async {
final info = await androidDeviceInfo;
if (info.version.sdkInt >= 29) {
return _flutterAudioQuery.getArtwork(
type: ResourceType.ALBUM,
id: id.toString(),
size: const Size(480.0, 480.0),
);
}
return Uint8List(0);
}
@override
Future<Map<String, List>> getLocalMusic() async {
final musicDirectories = await _settingsDataSource.getLibraryFolders();
final songs = await _getFilteredSongs(musicDirectories);
final albumTitles = Set<String>.from(songs.map((song) => song.album));
final albums = await _getFilteredAlbums(albumTitles);
final artistNames = Set<String>.from(albums.map((album) => album.artist));
final artists = await _getFilteredArtists(artistNames);
return {
'SONGS': songs,
'ALBUMS': albums,
'ARTISTS': artists,
};
}
Future<List<SongModel>> _getFilteredSongs(Iterable<String> musicDirectories) async {
final List<SongInfo> songInfoList = await _flutterAudioQuery.getSongs();
return songInfoList
.where(
(songInfo) =>
songInfo.isMusic &&
musicDirectories.any((element) => songInfo.filePath.startsWith(element)),
)
.map((SongInfo songInfo) => SongModel.fromSongInfo(songInfo))
.toList();
}
Future<List<AlbumModel>> _getFilteredAlbums(Iterable<String> albumTitles) async {
final List<AlbumInfo> albumInfoList = await _flutterAudioQuery.getAlbums();
return albumInfoList
.where((albumInfo) => albumTitles.contains(albumInfo.title))
.map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo))
.toList();
}
Future<List<ArtistModel>> _getFilteredArtists(Iterable<String> artistNames) async {
final List<ArtistInfo> artistInfoList = await _flutterAudioQuery.getArtists();
return artistInfoList
.where((artistInfo) => artistNames.contains(artistInfo.name))
.map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo))
.toSet()
.toList();
}
Future<List<ArtistModel>> getArtists();
Future<List<AlbumModel>> getAlbums();
Future<List<SongModel>> getSongs();
Future<Uint8List> getAlbumArtwork(int id);
}

View file

@ -1,14 +0,0 @@
import 'dart:typed_data';
import '../models/album_model.dart';
import '../models/artist_model.dart';
import '../models/song_model.dart';
abstract class LocalMusicFetcher {
Future<Map<String, List>> getLocalMusic();
Future<List<ArtistModel>> getArtists();
Future<List<AlbumModel>> getAlbums();
Future<List<SongModel>> getSongs();
Future<Uint8List> getAlbumArtwork(int id);
}

View file

@ -0,0 +1,109 @@
import 'dart:typed_data';
import 'dart:ui';
import 'package:device_info/device_info.dart';
import 'package:flutter_audio_query/flutter_audio_query.dart';
import '../models/album_model.dart';
import '../models/artist_model.dart';
import '../models/song_model.dart';
import 'local_music_fetcher.dart';
import 'settings_data_source.dart';
class LocalMusicFetcherImpl implements LocalMusicFetcher {
LocalMusicFetcherImpl(this._flutterAudioQuery, this._settingsDataSource, this._deviceInfo);
final FlutterAudioQuery _flutterAudioQuery;
final SettingsDataSource _settingsDataSource;
// CODESMELL: should probably encapsulate the deviceinfoplugin
final DeviceInfoPlugin _deviceInfo;
AndroidDeviceInfo _androidDeviceInfo;
Future<AndroidDeviceInfo> get androidDeviceInfo async {
_androidDeviceInfo ??= await _deviceInfo.androidInfo;
return _androidDeviceInfo;
}
@override
Future<List<ArtistModel>> getArtists() async {
final List<ArtistInfo> artistInfoList = await _flutterAudioQuery.getArtists();
return artistInfoList
.map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo))
.toSet()
.toList();
}
@override
Future<List<AlbumModel>> getAlbums() async {
final List<AlbumInfo> albumInfoList = await _flutterAudioQuery.getAlbums();
return albumInfoList.map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo)).toList();
}
@override
Future<List<SongModel>> getSongs() async {
final List<SongInfo> songInfoList = await _flutterAudioQuery.getSongs();
return songInfoList
.where((songInfo) => songInfo.isMusic)
.map((SongInfo songInfo) => SongModel.fromSongInfo(songInfo))
.toList();
}
@override
Future<Uint8List> getAlbumArtwork(int id) async {
final info = await androidDeviceInfo;
if (info.version.sdkInt >= 29) {
return _flutterAudioQuery.getArtwork(
type: ResourceType.ALBUM,
id: id.toString(),
size: const Size(480.0, 480.0),
);
}
return Uint8List(0);
}
@override
Future<Map<String, List>> getLocalMusic() async {
final musicDirectories = await _settingsDataSource.getLibraryFolders();
final songs = await _getFilteredSongs(musicDirectories);
final albumTitles = Set<String>.from(songs.map((song) => song.album));
final albums = await _getFilteredAlbums(albumTitles);
final artistNames = Set<String>.from(albums.map((album) => album.artist));
final artists = await _getFilteredArtists(artistNames);
return {
'SONGS': songs,
'ALBUMS': albums,
'ARTISTS': artists,
};
}
Future<List<SongModel>> _getFilteredSongs(Iterable<String> musicDirectories) async {
final List<SongInfo> songInfoList = await _flutterAudioQuery.getSongs();
return songInfoList
.where(
(songInfo) =>
songInfo.isMusic &&
musicDirectories.any((element) => songInfo.filePath.startsWith(element)),
)
.map((SongInfo songInfo) => SongModel.fromSongInfo(songInfo))
.toList();
}
Future<List<AlbumModel>> _getFilteredAlbums(Iterable<String> albumTitles) async {
final List<AlbumInfo> albumInfoList = await _flutterAudioQuery.getAlbums();
return albumInfoList
.where((albumInfo) => albumTitles.contains(albumInfo.title))
.map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo))
.toList();
}
Future<List<ArtistModel>> _getFilteredArtists(Iterable<String> artistNames) async {
final List<ArtistInfo> artistInfoList = await _flutterAudioQuery.getArtists();
return artistInfoList
.where((artistInfo) => artistNames.contains(artistInfo.name))
.map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo))
.toSet()
.toList();
}
}

View file

@ -0,0 +1,12 @@
import '../../domain/repositories/platform_integration_repository.dart';
import '../models/playback_event_model.dart';
import '../models/song_model.dart';
abstract class PlatformIntegrationDataSource {
Stream<PlatformIntegrationEvent> get eventStream;
Future<void> handlePlaybackEvent(PlaybackEventModel playbackEventModel);
Future<void> onPause();
Future<void> onPlay();
Future<void> setCurrentSong(SongModel songModel);
}

View file

@ -0,0 +1,78 @@
import 'package:audio_service/audio_service.dart';
import 'package:rxdart/rxdart.dart';
import '../../domain/entities/playback_event.dart';
import '../../domain/repositories/platform_integration_repository.dart';
import '../models/playback_event_model.dart';
import '../models/song_model.dart';
import 'platform_integration_data_source.dart';
class PlatformIntegrationDataSourceImpl extends BaseAudioHandler
implements PlatformIntegrationDataSource {
PlatformIntegrationDataSourceImpl();
final BehaviorSubject<PlatformIntegrationEvent> _eventSubject = BehaviorSubject();
// BaseAudioHandler interface
@override
Future<void> play() async {
_eventSubject.add(PlatformIntegrationEvent(type: PlatformIntegrationEventType.play));
}
@override
Future<void> pause() async {
_eventSubject.add(PlatformIntegrationEvent(type: PlatformIntegrationEventType.pause));
}
// PlatformIntegrationDataSource interface
@override
Stream<PlatformIntegrationEvent> get eventStream => _eventSubject.stream;
@override
Future<void> handlePlaybackEvent(PlaybackEventModel pe) async {
if (pe.processingState == ProcessingState.ready) {
final timeDelta = DateTime.now().difference(pe.updateTime);
print(timeDelta);
if (pe.playing) {
playbackState.add(playbackState.value.copyWith(
controls: [MediaControl.skipToPrevious, MediaControl.pause, MediaControl.skipToNext],
playing: true,
processingState: AudioProcessingState.ready,
updatePosition: pe.updatePosition + timeDelta,
));
} else {
playbackState.add(playbackState.value.copyWith(
controls: [MediaControl.skipToPrevious, MediaControl.play, MediaControl.skipToNext],
processingState: AudioProcessingState.ready,
updatePosition: pe.updatePosition + timeDelta,
playing: false,
));
}
}
}
@override
Future<void> onPlay() async {
playbackState.add(playbackState.value.copyWith(
controls: [MediaControl.skipToPrevious, MediaControl.pause, MediaControl.skipToNext],
playing: true,
processingState: AudioProcessingState.ready,
));
}
@override
Future<void> onPause() async {
playbackState.add(playbackState.value.copyWith(
controls: [MediaControl.skipToPrevious, MediaControl.play, MediaControl.skipToNext],
processingState: AudioProcessingState.ready,
playing: false,
));
}
@override
Future<void> setCurrentSong(SongModel songModel) async {
mediaItem.add(songModel.toMediaItem());
}
}

View file

@ -0,0 +1,144 @@
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';
import '../datasources/audio_player_data_source.dart';
import '../models/song_model.dart';
class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
AudioPlayerRepositoryImpl(this._audioPlayerDataSource) {
_shuffleModeSubject.add(ShuffleMode.none);
_loopModeSubject.add(LoopMode.off);
}
final AudioPlayerDataSource _audioPlayerDataSource;
// final BehaviorSubject<int> _currentIndexSubject = BehaviorSubject();
// 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<ShuffleMode> _shuffleModeSubject = BehaviorSubject();
@override
Stream<AudioPlayerEvent> eventStream;
@override
ValueStream<ShuffleMode> get shuffleModeStream => _shuffleModeSubject.stream;
@override
ValueStream<LoopMode> get loopModeStream => _loopModeSubject.stream;
@override
ValueStream<List<Song>> get songListStream => _songListSubject.stream;
@override
ValueStream<List<QueueItem>> get queueStream => _queueSubject.stream;
@override
ValueStream<int> get currentIndexStream => _audioPlayerDataSource.currentIndexStream;
@override
Stream<Song> get currentSongStream => _audioPlayerDataSource.currentSongStream;
@override
Stream<PlaybackEvent> get playbackEventStream => _audioPlayerDataSource.playbackEventStream;
@override
Stream<bool> get playingStream => _audioPlayerDataSource.playingStream;
@override
Stream<Duration> get positionStream => _audioPlayerDataSource.positionStream;
@override
Future<void> addToQueue(Song song) {
// TODO: implement addToQueue
throw UnimplementedError();
}
@override
Future<void> dispose() async {
_audioPlayerDataSource.dispose();
}
@override
Future<void> loadQueue({List<QueueItem> 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(
initialIndex: initialIndex,
queue: queue.map((e) => e as SongModel).toList(),
);
}
@override
Future<void> moveQueueItem(int oldIndex, int newIndex) {
// TODO: implement moveQueueItem
throw UnimplementedError();
}
@override
Future<void> pause() async {
_audioPlayerDataSource.pause();
}
@override
Future<void> play() async {
_audioPlayerDataSource.play();
}
@override
Future<void> playSong(Song song) async {
await _audioPlayerDataSource.loadQueue(
initialIndex: 0,
queue: [song as SongModel],
);
_audioPlayerDataSource.play();
}
@override
Future<void> removeQueueIndex(int index) {
// TODO: implement removeQueueIndex
throw UnimplementedError();
}
@override
Future<bool> seekToNext() async {
return await _audioPlayerDataSource.seekToNext();
}
@override
Future<void> seekToPrevious() async {
await _audioPlayerDataSource.seekToPrevious();
}
@override
Future<void> setIndex(int index) {
// TODO: implement setIndex
throw UnimplementedError();
}
@override
Future<void> setLoopMode(LoopMode loopMode) async {
_loopModeSubject.add(loopMode);
await _audioPlayerDataSource.setLoopMode(loopMode);
}
@override
Future<void> setShuffleMode(ShuffleMode shuffleMode) async {
_shuffleModeSubject.add(shuffleMode);
}
@override
Future<void> stop() {
// TODO: implement stop
throw UnimplementedError();
}
}

View file

@ -1,157 +0,0 @@
import 'dart:async';
import 'package:audio_service/audio_service.dart';
import '../../domain/entities/album.dart';
import '../../domain/entities/artist.dart';
import '../../domain/entities/loop_mode.dart';
import '../../domain/entities/playback_state.dart' as entity;
import '../../domain/entities/shuffle_mode.dart';
import '../../domain/entities/song.dart';
import '../../domain/repositories/audio_repository.dart';
import '../audio/stream_constants.dart';
import '../models/album_model.dart';
import '../models/artist_model.dart';
import '../models/playback_state_model.dart';
import '../models/song_model.dart';
typedef Conversion<S, T> = T Function(S);
class AudioRepositoryImpl implements AudioRepository {
AudioRepositoryImpl(this._audioHandler);
final AudioHandler _audioHandler;
@override
Stream<SongModel> get currentSongStream => _filterStream<MediaItem, SongModel>(
_audioHandler.mediaItem.stream,
(MediaItem mi) => SongModel.fromMediaItem(mi),
);
@override
Stream<entity.PlaybackState> get playbackStateStream => _filterStream(
_audioHandler.playbackState.stream,
(PlaybackState ps) => PlaybackStateModel.fromASPlaybackState(ps),
);
@override
Stream<int> get currentPositionStream => _position().distinct();
@override
Future<void> playSong(int index, List<Song> songList) async {
if (0 <= index && index < songList.length) {
final List<String> context = songList.map((s) => s.path).toList();
await _audioHandler.customAction(PLAY_WITH_CONTEXT, {'CONTEXT': context, 'INDEX': index});
}
}
@override
Future<void> play() async {
_audioHandler.play();
}
@override
Future<void> pause() async {
await _audioHandler.pause();
}
@override
Future<void> skipToNext() async {
await _audioHandler.skipToNext();
}
@override
Future<void> skipToPrevious() async {
await _audioHandler.skipToPrevious();
}
@override
Future<void> setIndex(int index) async {
await _audioHandler.customAction(SET_INDEX, {'INDEX': index});
}
@override
Future<void> setShuffleMode(ShuffleMode shuffleMode) async {
await _audioHandler.customAction(SET_SHUFFLE_MODE, {'SHUFFLE_MODE': shuffleMode});
}
@override
Future<void> setLoopMode(LoopMode loopMode) async {
await _audioHandler.customAction(SET_LOOP_MODE, {'LOOP_MODE': loopMode});
}
Stream<T> _filterStream<S, T>(Stream<S> stream, Conversion<S, T> fn) async* {
T lastItem;
await for (final S item in stream) {
final T newItem = fn(item);
if (newItem != lastItem) {
lastItem = newItem;
yield newItem;
}
}
}
Stream<int> _position() async* {
PlaybackState state;
DateTime updateTime;
Duration statePosition;
// TODO: should this class get an init method for this?
_audioHandler.playbackState.stream.listen((currentState) {
state = currentState;
updateTime = currentState?.updateTime;
statePosition = currentState?.position;
});
while (true) {
if (statePosition != null && updateTime != null && state != null) {
if (state.playing) {
yield statePosition.inMilliseconds +
(DateTime.now().millisecondsSinceEpoch - updateTime.millisecondsSinceEpoch);
} else {
yield statePosition.inMilliseconds;
}
} else {
yield 0;
}
await Future.delayed(const Duration(milliseconds: 200));
}
}
@override
Future<void> shuffleAll() async {
await _audioHandler.customAction(SHUFFLE_ALL, null);
}
@override
Future<void> addToQueue(Song song) async {
await _audioHandler.addQueueItem((song as SongModel).toMediaItem());
}
@override
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
await _audioHandler.customAction(MOVE_QUEUE_ITEM, {
'OLD_INDEX': oldIndex,
'NEW_INDEX': newIndex,
});
}
@override
Future<void> removeQueueIndex(int index) async {
await _audioHandler.removeQueueItemAt(index);
}
@override
Future<void> playAlbum(Album album) async {
await _audioHandler.customAction(PLAY_ALBUM, {'ALBUM': album as AlbumModel});
}
@override
Future<void> playArtist(Artist artist, {ShuffleMode shuffleMode = ShuffleMode.none}) async {
await _audioHandler.customAction(PLAY_ARTIST, {
'ARTIST': artist as ArtistModel,
'SHUFFLE_MODE': shuffleMode,
});
}
}

View file

@ -7,7 +7,7 @@ import '../../domain/entities/album.dart';
import '../../domain/entities/artist.dart';
import '../../domain/entities/song.dart';
import '../../domain/repositories/music_data_repository.dart';
import '../datasources/local_music_fetcher_contract.dart';
import '../datasources/local_music_fetcher.dart';
import '../datasources/music_data_source_contract.dart';
import '../models/album_model.dart';
import '../models/artist_model.dart';
@ -24,6 +24,11 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
static final _log = Logger('MusicDataRepository');
@override
Future<Song> getSongByPath(String path) async {
return await _musicDataSource.getSongByPath(path);
}
@override
Stream<List<Song>> get songStream => _musicDataSource.songStream;

View file

@ -1,4 +1,5 @@
import '../../domain/entities/loop_mode.dart';
import '../../domain/entities/queue_item.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../../domain/entities/song.dart';
import '../../domain/repositories/persistent_player_state_repository.dart';
@ -23,4 +24,24 @@ class PlayerStateRepositoryImpl implements PlayerStateRepository {
@override
Stream<ShuffleMode> get shuffleModeStream => _playerStateDataSource.shuffleModeStream;
@override
void setCurrentIndex(int index) {
// TODO: implement setCurrentIndex
}
@override
void setLoopMode(LoopMode loopMode) {
// TODO: implement setLoopMode
}
@override
void setQueue(List<QueueItem> queue) {
// TODO: implement setQueue
}
@override
void setShuffleMode(ShuffleMode shuffleMode) {
// TODO: implement setShuffleMode
}
}

View file

@ -0,0 +1,45 @@
import '../../domain/entities/playback_event.dart';
import '../../domain/entities/song.dart';
import '../../domain/repositories/platform_integration_repository.dart';
import '../datasources/platform_integration_data_source.dart';
import '../models/playback_event_model.dart';
import '../models/song_model.dart';
class PlatformIntegrationRepositoryImpl implements PlatformIntegrationRepository {
PlatformIntegrationRepositoryImpl(this._platformIntegrationDataSource);
final PlatformIntegrationDataSource _platformIntegrationDataSource;
@override
Stream<PlatformIntegrationEvent> get eventStream => _platformIntegrationDataSource.eventStream;
@override
void handlePlaybackEvent(PlaybackEvent playbackEvent) {
_platformIntegrationDataSource.handlePlaybackEvent(playbackEvent as PlaybackEventModel);
}
@override
void pause() {
_platformIntegrationDataSource.onPause();
}
@override
void play() {
_platformIntegrationDataSource.onPlay();
}
@override
void onStop() {
// TODO: implement onStop
}
@override
void setCurrentSong(Song song) {
_platformIntegrationDataSource.setCurrentSong(song as SongModel);
}
@override
void setQueue(List<Song> queue) {
// TODO: implement setQueue
}
}