From 39e411103760a3a354120e0d7a1f2160e7690677 Mon Sep 17 00:00:00 2001 From: Moritz Weber Date: Fri, 26 Feb 2021 22:07:18 +0100 Subject: [PATCH] big changes --- lib/domain/actors/audio_player_actor.dart | 18 ++ .../actors/platform_integration_actor.dart | 27 ++ lib/domain/entities/event.dart | 3 + lib/domain/modules/queue_generator.dart | 149 ++++++++++ .../repositories/audio_player_repository.dart | 49 ++++ lib/domain/repositories/audio_repository.dart | 30 --- .../repositories/music_data_repository.dart | 9 +- .../persistent_player_state_repository.dart | 6 + .../platform_integration_repository.dart | 38 +++ .../usecases/handle_playback_state.dart | 12 + lib/domain/usecases/pause.dart | 11 + lib/domain/usecases/play.dart | 11 + lib/domain/usecases/play_songs.dart | 42 +++ lib/domain/usecases/seek_to_next.dart | 11 + lib/domain/usecases/seek_to_previous.dart | 11 + lib/domain/usecases/set_current_song.dart | 12 + lib/domain/usecases/set_loop_mode.dart | 12 + lib/domain/usecases/set_shuffle_mode.dart | 64 +++++ lib/domain/usecases/shuffle_all.dart | 52 ++++ lib/domain/usecases/update_database.dart | 11 + lib/injection_container.dart | 174 ++++++++++-- lib/presentation/state/audio_store.dart | 93 ++++--- lib/presentation/state/audio_store.g.dart | 21 +- lib/presentation/state/music_data_store.dart | 47 ++-- lib/presentation/utils.dart | 4 +- .../widgets/currently_playing_bar.dart | 120 +++++---- lib/presentation/widgets/next_button.dart | 4 +- .../widgets/play_pause_button.dart | 46 ++-- .../widgets/time_progress_indicator.dart | 7 +- lib/system/audio/audio_handler.dart | 229 +++++----------- lib/system/audio/audio_player_impl.dart | 254 ------------------ lib/system/audio/queue_generator.dart | 165 ------------ lib/system/audio/stream_constants.dart | 8 - .../audio_player_data_source.dart} | 11 +- .../audio_player_data_source_impl.dart | 234 ++++++++++++++++ .../datasources/local_music_fetcher.dart | 109 +------- .../local_music_fetcher_contract.dart | 14 - .../datasources/local_music_fetcher_impl.dart | 109 ++++++++ .../platform_integration_data_source.dart | 12 + ...platform_integration_data_source_impl.dart | 78 ++++++ .../audio_player_repository_impl.dart | 144 ++++++++++ .../repositories/audio_repository_impl.dart | 157 ----------- .../music_data_repository_impl.dart | 7 +- ...rsistent_player_state_repository_impl.dart | 21 ++ .../platform_integration_repository_impl.dart | 45 ++++ 45 files changed, 1608 insertions(+), 1083 deletions(-) create mode 100644 lib/domain/actors/audio_player_actor.dart create mode 100644 lib/domain/actors/platform_integration_actor.dart create mode 100644 lib/domain/entities/event.dart create mode 100644 lib/domain/modules/queue_generator.dart create mode 100644 lib/domain/repositories/audio_player_repository.dart delete mode 100644 lib/domain/repositories/audio_repository.dart create mode 100644 lib/domain/repositories/platform_integration_repository.dart create mode 100644 lib/domain/usecases/handle_playback_state.dart create mode 100644 lib/domain/usecases/pause.dart create mode 100644 lib/domain/usecases/play.dart create mode 100644 lib/domain/usecases/play_songs.dart create mode 100644 lib/domain/usecases/seek_to_next.dart create mode 100644 lib/domain/usecases/seek_to_previous.dart create mode 100644 lib/domain/usecases/set_current_song.dart create mode 100644 lib/domain/usecases/set_loop_mode.dart create mode 100644 lib/domain/usecases/set_shuffle_mode.dart create mode 100644 lib/domain/usecases/shuffle_all.dart create mode 100644 lib/domain/usecases/update_database.dart delete mode 100644 lib/system/audio/audio_player_impl.dart delete mode 100644 lib/system/audio/queue_generator.dart delete mode 100644 lib/system/audio/stream_constants.dart rename lib/system/{audio/audio_player_contract.dart => datasources/audio_player_data_source.dart} (67%) create mode 100644 lib/system/datasources/audio_player_data_source_impl.dart delete mode 100644 lib/system/datasources/local_music_fetcher_contract.dart create mode 100644 lib/system/datasources/local_music_fetcher_impl.dart create mode 100644 lib/system/datasources/platform_integration_data_source.dart create mode 100644 lib/system/datasources/platform_integration_data_source_impl.dart create mode 100644 lib/system/repositories/audio_player_repository_impl.dart delete mode 100644 lib/system/repositories/audio_repository_impl.dart create mode 100644 lib/system/repositories/platform_integration_repository_impl.dart diff --git a/lib/domain/actors/audio_player_actor.dart b/lib/domain/actors/audio_player_actor.dart new file mode 100644 index 0000000..10e009e --- /dev/null +++ b/lib/domain/actors/audio_player_actor.dart @@ -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); +} \ No newline at end of file diff --git a/lib/domain/actors/platform_integration_actor.dart b/lib/domain/actors/platform_integration_actor.dart new file mode 100644 index 0000000..58ddf9b --- /dev/null +++ b/lib/domain/actors/platform_integration_actor.dart @@ -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: + } + } +} diff --git a/lib/domain/entities/event.dart b/lib/domain/entities/event.dart new file mode 100644 index 0000000..c7316f2 --- /dev/null +++ b/lib/domain/entities/event.dart @@ -0,0 +1,3 @@ +abstract class Event { + Map payload; +} \ No newline at end of file diff --git a/lib/domain/modules/queue_generator.dart b/lib/domain/modules/queue_generator.dart new file mode 100644 index 0000000..9c943f2 --- /dev/null +++ b/lib/domain/modules/queue_generator.dart @@ -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> generateQueue( + ShuffleMode shuffleMode, + List songs, + int startIndex, + ) async { + List 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 _generateNormalQueue(List songs) { + return List.generate( + songs.length, + (i) => QueueItem( + songs[i], + originalIndex: i, + ), + ); + } + + List _generateShuffleQueue( + List songs, + int startIndex, + ) { + final List queue = List.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> _generateShufflePlusQueue( + List songs, + int startIndex, + ) async { + final List queue = await _getQueueItemWithLinks( + songs[startIndex], + startIndex, + ); + final List 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> _getQueueItemWithLinks( + Song song, + int index, + ) async { + final List 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> _getPredecessors(Song song) async { + final List songs = []; + Song currentSong = song; + + while (currentSong.previous != null) { + currentSong = await _musicDataRepository.getSongByPath(currentSong.previous); + songs.add(currentSong); + } + + return songs.reversed.toList(); + } + + Future> _getSuccessors(Song song) async { + final List songs = []; + Song currentSong = song; + + while (currentSong.next != null) { + currentSong = await _musicDataRepository.getSongByPath(currentSong.next); + songs.add(currentSong); + } + + return songs.toList(); + } +} diff --git a/lib/domain/repositories/audio_player_repository.dart b/lib/domain/repositories/audio_player_repository.dart new file mode 100644 index 0000000..9f97865 --- /dev/null +++ b/lib/domain/repositories/audio_player_repository.dart @@ -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 eventStream; + + ValueStream get shuffleModeStream; + ValueStream get loopModeStream; + ValueStream> get songListStream; + ValueStream> get queueStream; + + ValueStream get currentIndexStream; + Stream get currentSongStream; + Stream get playbackEventStream; + Stream get playingStream; + Stream get positionStream; +} + +abstract class AudioPlayerRepository extends AudioPlayerInfoRepository { + Future play(); + Future pause(); + Future stop(); + Future seekToNext(); + Future seekToPrevious(); + Future dispose(); + + Future playSong(Song song); + Future loadQueue({List queue, int initialIndex}); + Future addToQueue(Song song); + Future moveQueueItem(int oldIndex, int newIndex); + Future removeQueueIndex(int index); + Future setIndex(int index); + + /// Set the ShuffleMode. Does not affect playback/queue. + Future setShuffleMode(ShuffleMode shuffleMode); + Future setLoopMode(LoopMode loopMode); +} + +class AudioPlayerEvent extends Event { + AudioPlayerEventType type; +} + +enum AudioPlayerEventType { dummy } diff --git a/lib/domain/repositories/audio_repository.dart b/lib/domain/repositories/audio_repository.dart deleted file mode 100644 index c631e45..0000000 --- a/lib/domain/repositories/audio_repository.dart +++ /dev/null @@ -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 get currentSongStream; - Stream get playbackStateStream; - Stream get currentPositionStream; - - Future playSong(int index, List songList); - Future play(); - Future pause(); - Future skipToNext(); - Future skipToPrevious(); - Future setIndex(int index); - - Future playAlbum(Album album); - Future playArtist(Artist artist, {ShuffleMode shuffleMode = ShuffleMode.none}); - - Future setShuffleMode(ShuffleMode shuffleMode); - Future setLoopMode(LoopMode loopMode); - - Future shuffleAll(); - Future addToQueue(Song song); - Future moveQueueItem(int oldIndex, int newIndex); - Future removeQueueIndex(int index); -} diff --git a/lib/domain/repositories/music_data_repository.dart b/lib/domain/repositories/music_data_repository.dart index 800850d..9ec6c9f 100644 --- a/lib/domain/repositories/music_data_repository.dart +++ b/lib/domain/repositories/music_data_repository.dart @@ -2,7 +2,8 @@ import '../entities/album.dart'; import '../entities/artist.dart'; import '../entities/song.dart'; -abstract class MusicDataRepository { +abstract class MusicDataInfoRepository { + Future getSongByPath(String path); Stream> get songStream; Stream> getAlbumSongStream(Album album); @@ -10,6 +11,8 @@ abstract class MusicDataRepository { Stream> getArtistAlbumStream(Artist artist); Stream> get artistStream; - - Future updateDatabase(); } + +abstract class MusicDataRepository extends MusicDataInfoRepository { + Future updateDatabase(); +} \ No newline at end of file diff --git a/lib/domain/repositories/persistent_player_state_repository.dart b/lib/domain/repositories/persistent_player_state_repository.dart index 0df0464..790c81f 100644 --- a/lib/domain/repositories/persistent_player_state_repository.dart +++ b/lib/domain/repositories/persistent_player_state_repository.dart @@ -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 get currentSongStream; Stream get loopModeStream; Stream get shuffleModeStream; + + void setShuffleMode(ShuffleMode shuffleMode); + void setLoopMode(LoopMode loopMode); + void setQueue(List queue); + void setCurrentIndex(int index); } diff --git a/lib/domain/repositories/platform_integration_repository.dart b/lib/domain/repositories/platform_integration_repository.dart new file mode 100644 index 0000000..9bc3313 --- /dev/null +++ b/lib/domain/repositories/platform_integration_repository.dart @@ -0,0 +1,38 @@ +import '../entities/event.dart'; +import '../entities/playback_event.dart'; +import '../entities/song.dart'; + +/* + +- position +- controls (playbackState) + +*/ + +abstract class PlatformIntegrationInfoRepository { + Stream get eventStream; +} + +abstract class PlatformIntegrationRepository extends PlatformIntegrationInfoRepository { + void play(); + void pause(); + void onStop(); + + void handlePlaybackEvent(PlaybackEvent playbackEvent); + void setCurrentSong(Song song); + void setQueue(List queue); +} + +class PlatformIntegrationEvent extends Event { + PlatformIntegrationEvent({this.type}); + + final PlatformIntegrationEventType type; +} + +enum PlatformIntegrationEventType { + play, + pause, + stop, + skipNext, + skipPrevious, +} diff --git a/lib/domain/usecases/handle_playback_state.dart b/lib/domain/usecases/handle_playback_state.dart new file mode 100644 index 0000000..cd05fc9 --- /dev/null +++ b/lib/domain/usecases/handle_playback_state.dart @@ -0,0 +1,12 @@ +import '../entities/playback_event.dart'; +import '../repositories/platform_integration_repository.dart'; + +class HandlePlaybackEvent { + HandlePlaybackEvent(this._platformIntegrationRepository); + + final PlatformIntegrationRepository _platformIntegrationRepository; + + Future call(PlaybackEvent playbackEvent) async { + _platformIntegrationRepository.handlePlaybackEvent(playbackEvent); + } +} \ No newline at end of file diff --git a/lib/domain/usecases/pause.dart b/lib/domain/usecases/pause.dart new file mode 100644 index 0000000..4bb83ae --- /dev/null +++ b/lib/domain/usecases/pause.dart @@ -0,0 +1,11 @@ +import '../repositories/audio_player_repository.dart'; + +class Pause { + Pause(this._audioPlayerRepository); + + final AudioPlayerRepository _audioPlayerRepository; + + Future call() async { + _audioPlayerRepository.pause(); + } +} diff --git a/lib/domain/usecases/play.dart b/lib/domain/usecases/play.dart new file mode 100644 index 0000000..8ed174c --- /dev/null +++ b/lib/domain/usecases/play.dart @@ -0,0 +1,11 @@ +import '../repositories/audio_player_repository.dart'; + +class Play { + Play(this._audioPlayerRepository); + + final AudioPlayerRepository _audioPlayerRepository; + + Future call() async { + _audioPlayerRepository.play(); + } +} diff --git a/lib/domain/usecases/play_songs.dart b/lib/domain/usecases/play_songs.dart new file mode 100644 index 0000000..fe9bbfe --- /dev/null +++ b/lib/domain/usecases/play_songs.dart @@ -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 call({List 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()); + } + } +} diff --git a/lib/domain/usecases/seek_to_next.dart b/lib/domain/usecases/seek_to_next.dart new file mode 100644 index 0000000..f2a54af --- /dev/null +++ b/lib/domain/usecases/seek_to_next.dart @@ -0,0 +1,11 @@ +import '../repositories/audio_player_repository.dart'; + +class SeekToNext { + SeekToNext(this._audioPlayerRepository); + + final AudioPlayerRepository _audioPlayerRepository; + + Future call() async { + return await _audioPlayerRepository.seekToNext(); + } +} diff --git a/lib/domain/usecases/seek_to_previous.dart b/lib/domain/usecases/seek_to_previous.dart new file mode 100644 index 0000000..abf3e69 --- /dev/null +++ b/lib/domain/usecases/seek_to_previous.dart @@ -0,0 +1,11 @@ +import '../repositories/audio_player_repository.dart'; + +class SeekToPrevious { + SeekToPrevious(this._audioPlayerRepository); + + final AudioPlayerRepository _audioPlayerRepository; + + Future call() async { + await _audioPlayerRepository.seekToPrevious(); + } +} diff --git a/lib/domain/usecases/set_current_song.dart b/lib/domain/usecases/set_current_song.dart new file mode 100644 index 0000000..7b71186 --- /dev/null +++ b/lib/domain/usecases/set_current_song.dart @@ -0,0 +1,12 @@ +import '../entities/song.dart'; +import '../repositories/platform_integration_repository.dart'; + +class SetCurrentSong { + SetCurrentSong(this._platformIntegrationRepository); + + final PlatformIntegrationRepository _platformIntegrationRepository; + + Future call(Song song) async { + _platformIntegrationRepository.setCurrentSong(song); + } +} \ No newline at end of file diff --git a/lib/domain/usecases/set_loop_mode.dart b/lib/domain/usecases/set_loop_mode.dart new file mode 100644 index 0000000..d92e8fa --- /dev/null +++ b/lib/domain/usecases/set_loop_mode.dart @@ -0,0 +1,12 @@ +import '../entities/loop_mode.dart'; +import '../repositories/audio_player_repository.dart'; + +class SetLoopMode { + SetLoopMode(this._audioPlayerRepository); + + final AudioPlayerRepository _audioPlayerRepository; + + Future call(LoopMode loopMode) async { + await _audioPlayerRepository.setLoopMode(loopMode); + } +} diff --git a/lib/domain/usecases/set_shuffle_mode.dart b/lib/domain/usecases/set_shuffle_mode.dart new file mode 100644 index 0000000..7edf10c --- /dev/null +++ b/lib/domain/usecases/set_shuffle_mode.dart @@ -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 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); + } +} diff --git a/lib/domain/usecases/shuffle_all.dart b/lib/domain/usecases/shuffle_all.dart new file mode 100644 index 0000000..93902a3 --- /dev/null +++ b/lib/domain/usecases/shuffle_all.dart @@ -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 call() async { + final List 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()); + } +} diff --git a/lib/domain/usecases/update_database.dart b/lib/domain/usecases/update_database.dart new file mode 100644 index 0000000..c44355e --- /dev/null +++ b/lib/domain/usecases/update_database.dart @@ -0,0 +1,11 @@ +import '../repositories/music_data_repository.dart'; + +class UpdateDatabase { + UpdateDatabase(this._musicDataRepository); + + final MusicDataRepository _musicDataRepository; + + Future call() async { + await _musicDataRepository.updateDatabase(); + } +} \ No newline at end of file diff --git a/lib/injection_container.dart b/lib/injection_container.dart index 53b6659..2c48f6e 100644 --- a/lib/injection_container.dart +++ b/lib/injection_container.dart @@ -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 setupGetIt() async { getIt.registerFactory( () { final musicDataStore = MusicDataStore( - musicDataRepository: getIt(), + musicDataInfoRepository: getIt(), settingsRepository: getIt(), musicDataModifierRepository: getIt(), + updateDatabase: getIt(), ); return musicDataStore; }, @@ -47,8 +63,14 @@ Future setupGetIt() async { getIt.registerFactory( () { 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 setupGetIt() async { }, ); + // use cases + getIt.registerLazySingleton( + () => HandlePlaybackEvent( + getIt(), + ), + ); + getIt.registerLazySingleton( + () => Pause( + getIt(), + ), + ); + getIt.registerLazySingleton( + () => Play( + getIt(), + ), + ); + getIt.registerLazySingleton( + () => PlaySongs( + getIt(), + getIt(), + getIt(), + getIt(), + ), + ); + getIt.registerLazySingleton( + () => SeekToNext( + getIt(), + ), + ); + getIt.registerLazySingleton( + () => SeekToPrevious( + getIt(), + ), + ); + getIt.registerLazySingleton( + () => SetCurrentSong( + getIt(), + ), + ); + getIt.registerLazySingleton( + () => SetLoopMode( + getIt(), + ), + ); + getIt.registerLazySingleton( + () => ShuffleAll( + getIt(), + getIt(), + getIt(), + getIt(), + getIt(), + ), + ); + getIt.registerLazySingleton( + () => UpdateDatabase( + getIt(), + ), + ); + + // modules + getIt.registerLazySingleton( + () => QueueGenerationModule( + getIt(), + ), + ); + // repositories + getIt.registerLazySingleton( + () => AudioPlayerRepositoryImpl( + getIt(), + ), + ); + getIt.registerLazySingleton( + () => getIt(), + ); + getIt.registerLazySingleton( () => MusicDataRepositoryImpl( getIt(), getIt(), ), ); + getIt.registerLazySingleton( + () => getIt(), + ); getIt.registerLazySingleton( () => MusicDataModifierRepositoryImpl( getIt(), ), ); - getIt.registerLazySingleton( - () => AudioRepositoryImpl( - getIt(), - ), - ); + getIt.registerLazySingleton( () => PlayerStateRepositoryImpl( getIt(), @@ -87,6 +183,14 @@ Future setupGetIt() async { getIt(), ), ); + getIt.registerLazySingleton( + () => PlatformIntegrationRepositoryImpl( + getIt(), + ), + ); + getIt.registerLazySingleton( + () => getIt(), + ); // data sources final MoorDatabase moorDatabase = MoorDatabase(); @@ -101,14 +205,20 @@ Future setupGetIt() async { ), ); - final AudioPlayer audioPlayer = AudioPlayerImpl( - ja.AudioPlayer(), - QueueGenerator(getIt()), + final AudioPlayerDataSource audioPlayer = AudioPlayerDataSourceImpl( + AudioPlayer(), ); - getIt.registerLazySingleton(() => audioPlayer); + getIt.registerLazySingleton(() => audioPlayer); + final PlatformIntegrationDataSource _platformIntegrationDataSource = + PlatformIntegrationDataSourceImpl(); + getIt.registerLazySingleton( + () => _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 setupGetIt() async { ); getIt.registerLazySingleton(() => _audioHandler); - getIt.registerLazySingleton(() => QueueGenerator(getIt())); - - // external - getIt.registerFactory(() => ja.AudioPlayer()); + getIt.registerFactory(() => AudioPlayer()); getIt.registerLazySingleton(() => FlutterAudioQuery()); getIt.registerLazySingleton(() => DeviceInfoPlugin()); + + // actors + getIt.registerSingleton( + PlatformIntegrationActor( + getIt(), + getIt(), + getIt(), + ), + ); + + getIt.registerSingleton( + AudioPlayerActor( + getIt(), + getIt(), + getIt(), + ), + ); } diff --git a/lib/presentation/state/audio_store.dart b/lib/presentation/state/audio_store.dart index 6a7d323..4d457c3 100644 --- a/lib/presentation/state/audio_store.dart +++ b/lib/presentation/state/audio_store.dart @@ -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 currentSongStream; @observable - ObservableStream playbackStateStream; + ObservableStream playingStream; @observable - ObservableStream currentPositionStream; + ObservableStream currentPositionStream; @observable ObservableStream> queueStream; @@ -61,58 +90,58 @@ abstract class _AudioStore with Store { ObservableStream loopModeStream; Future playSong(int index, List songList) async { - _audioRepository.playSong(index, songList); + _playSongs(songs: songList, initialIndex: index); } Future play() async { - _audioRepository.play(); + _play(); } Future pause() async { - _audioRepository.pause(); + _pause(); } Future skipToNext() async { - _audioRepository.skipToNext(); + _seekToNext(); } Future skipToPrevious() async { - _audioRepository.skipToPrevious(); + _seekToPrevious(); } Future setIndex(int index) async { - _audioRepository.setIndex(index); + // _audioInterface.setIndex(index); } Future setShuffleMode(ShuffleMode shuffleMode) async { - _audioRepository.setShuffleMode(shuffleMode); + // _audioInterface.setShuffleMode(shuffleMode); } Future setLoopMode(LoopMode loopMode) async { - _audioRepository.setLoopMode(loopMode); + _setLoopMode(loopMode); } Future shuffleAll() async { - _audioRepository.shuffleAll(); + _shuffleAll(); } Future addToQueue(Song song) async { - _audioRepository.addToQueue(song); + // _audioInterface.addToQueue(song); } Future moveQueueItem(int oldIndex, int newIndex) async { - _audioRepository.moveQueueItem(oldIndex, newIndex); + // _audioInterface.moveQueueItem(oldIndex, newIndex); } Future removeQueueIndex(int index) async { - _audioRepository.removeQueueIndex(index); + // _audioInterface.removeQueueIndex(index); } Future playAlbum(Album album) async { - _audioRepository.playAlbum(album); + // _audioInterface.playAlbum(album); } Future shuffleArtist(Artist artist) async { - _audioRepository.playArtist(artist, shuffleMode: ShuffleMode.plus); + // _audioInterface.playArtist(artist, shuffleMode: ShuffleMode.plus); } } diff --git a/lib/presentation/state/audio_store.g.dart b/lib/presentation/state/audio_store.g.dart index 755760a..e540fb4 100644 --- a/lib/presentation/state/audio_store.g.dart +++ b/lib/presentation/state/audio_store.g.dart @@ -24,19 +24,18 @@ mixin _$AudioStore on _AudioStore, Store { }); } - final _$playbackStateStreamAtom = - Atom(name: '_AudioStore.playbackStateStream'); + final _$playingStreamAtom = Atom(name: '_AudioStore.playingStream'); @override - ObservableStream get playbackStateStream { - _$playbackStateStreamAtom.reportRead(); - return super.playbackStateStream; + ObservableStream get playingStream { + _$playingStreamAtom.reportRead(); + return super.playingStream; } @override - set playbackStateStream(ObservableStream value) { - _$playbackStateStreamAtom.reportWrite(value, super.playbackStateStream, () { - super.playbackStateStream = value; + set playingStream(ObservableStream value) { + _$playingStreamAtom.reportWrite(value, super.playingStream, () { + super.playingStream = value; }); } @@ -44,13 +43,13 @@ mixin _$AudioStore on _AudioStore, Store { Atom(name: '_AudioStore.currentPositionStream'); @override - ObservableStream get currentPositionStream { + ObservableStream get currentPositionStream { _$currentPositionStreamAtom.reportRead(); return super.currentPositionStream; } @override - set currentPositionStream(ObservableStream value) { + set currentPositionStream(ObservableStream 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}, diff --git a/lib/presentation/state/music_data_store.dart b/lib/presentation/state/music_data_store.dart index 26bd4e8..148304b 100644 --- a/lib/presentation/state/music_data_store.dart +++ b/lib/presentation/state/music_data_store.dart @@ -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 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 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 updateDatabase() async { isUpdatingDatabase = true; - await _musicDataRepository.updateDatabase(); + await _updateDatabase(); isUpdatingDatabase = false; } @action Future fetchSongsFromAlbum(Album album) async { - albumSongStream = _musicDataRepository.getAlbumSongStream(album).asObservable(initialValue: []); + albumSongStream = + _musicDataInfoRepository.getAlbumSongStream(album).asObservable(initialValue: []); } @action Future fetchAlbumsFromArtist(Artist artist) async { artistAlbumStream = - _musicDataRepository.getArtistAlbumStream(artist).asObservable(initialValue: []); + _musicDataInfoRepository.getArtistAlbumStream(artist).asObservable(initialValue: []); } Future setSongBlocked(Song song, bool blocked) async { diff --git a/lib/presentation/utils.dart b/lib/presentation/utils.dart index 6caf6f0..882bf68 100644 --- a/lib/presentation/utils.dart +++ b/lib/presentation/utils.dart @@ -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; diff --git a/lib/presentation/widgets/currently_playing_bar.dart b/lib/presentation/widgets/currently_playing_bar.dart index b9c53f2..25f8a64 100644 --- a/lib/presentation/widgets/currently_playing_bar.dart +++ b/lib/presentation/widgets/currently_playing_bar.dart @@ -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: [ - GestureDetector( - onTap: () => _onTap(context), - child: Container( - color: Colors.transparent, - child: Row( - children: [ - Image( - image: getAlbumImage(song.albumArtPath), - height: 64.0, - ), - Container( - width: 10.0, - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 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: [ + GestureDetector( + onTap: () => _onTap(context), + child: Container( + color: Colors.transparent, + child: Row( + children: [ + 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: [ + 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(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(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(); }, ); } diff --git a/lib/presentation/widgets/next_button.dart b/lib/presentation/widgets/next_button.dart index 3f78bb2..f201a23 100644 --- a/lib/presentation/widgets/next_button.dart +++ b/lib/presentation/widgets/next_button.dart @@ -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, diff --git a/lib/presentation/widgets/play_pause_button.dart b/lib/presentation/widgets/play_pause_button.dart index 273e284..7ca7780 100644 --- a/lib/presentation/widgets/play_pause_button.dart +++ b/lib/presentation/widgets/play_pause_button.dart @@ -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(); + }, + ); } }, ); diff --git a/lib/presentation/widgets/time_progress_indicator.dart b/lib/presentation/widgets/time_progress_indicator.dart index db89781..3f9bd7f 100644 --- a/lib/presentation/widgets/time_progress_indicator.dart +++ b/lib/presentation/widgets/time_progress_indicator.dart @@ -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, diff --git a/lib/system/audio/audio_handler.dart b/lib/system/audio/audio_handler.dart index aab1fdb..75034b8 100644 --- a/lib/system/audio/audio_handler.dart +++ b/lib/system/audio/audio_handler.dart @@ -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 _initAudioPlayer() async { - if (_playerStateDataSource.loopModeStream != null) { - _audioPlayer.setLoopMode(await _playerStateDataSource.loopModeStream.first); - } + // Future _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 stop() async { - await _audioPlayer.stop(); - await _audioPlayer.dispose(); + // await _audioPlayer.stop(); + // await _audioPlayer.dispose(); await super.stop(); } - @override - Future play() async { - _audioPlayer.play(); - } - - @override - Future pause() async { - await _audioPlayer.pause(); - } - @override Future skipToNext() async { - _audioPlayer.seekToNext().then((value) { - if (value) { - _musicDataSource.incrementSkipCount(_audioPlayer.currentSongStream.value); - } - }); - } - - @override - Future skipToPrevious() async { - _audioPlayer.seekToPrevious(); + // _audioPlayer.seekToNext().then((value) { + // if (value) { + // _musicDataSource.incrementSkipCount(_audioPlayer.currentSongStream.value); + // } + // }); } @override Future addQueueItem(MediaItem mediaItem) async { - _audioPlayer.addToQueue(SongModel.fromMediaItem(mediaItem)); + // _audioPlayer.addToQueue(SongModel.fromMediaItem(mediaItem)); } @override Future removeQueueItemAt(int index) async { - _audioPlayer.removeQueueIndex(index); - } - - @override - Future customAction(String name, Map arguments) async { - switch (name) { - case PLAY_WITH_CONTEXT: - final context = arguments['CONTEXT'] as List; - 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 playWithContext(List context, int index) async { - final songs = []; - for (final path in context) { - final song = await _musicDataSource.getSongByPath(path); - songs.add(song); - } - - _audioPlayer.playSongList(songs, index); - } - - Future setCustomShuffleMode(ShuffleMode mode) async { - _audioPlayer.setShuffleMode(mode, true); - } - - Future setCustomLoopMode(LoopMode mode) async { - _audioPlayer.setLoopMode(mode); - } - - Future shuffleAll() async { - _audioPlayer.setShuffleMode(ShuffleMode.plus, false); - - final List songs = await _musicDataSource.getSongs(); - final rng = Random(); - final index = rng.nextInt(songs.length); - - _audioPlayer.playSongList(songs, index); + // _audioPlayer.removeQueueIndex(index); } Future moveQueueItem(int oldIndex, int newIndex) async { - _audioPlayer.moveQueueItem(oldIndex, newIndex); + // _audioPlayer.moveQueueItem(oldIndex, newIndex); } Future setIndex(int index) async { - _audioPlayer.setIndex(index); + // _audioPlayer.setIndex(index); } Future playAlbum(AlbumModel album) async { - _audioPlayer.setShuffleMode(ShuffleMode.none, false); + // _audioPlayer.setShuffleMode(ShuffleMode.none, false); final List songs = await _musicDataSource.getAlbumSongStream(album).first; - _audioPlayer.playSongList(songs, 0); + // _audioPlayer.playSongList(songs, 0); } Future playArtist(ArtistModel artist, ShuffleMode shuffleMode) async { - _audioPlayer.setShuffleMode(shuffleMode, false); + // _audioPlayer.setShuffleMode(shuffleMode, false); final List 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 queueItems) { - _playerStateDataSource.setQueue(queueItems); + // void _handleSetQueue(List 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); + // } + // } } diff --git a/lib/system/audio/audio_player_impl.dart b/lib/system/audio/audio_player_impl.dart deleted file mode 100644 index fcca853..0000000 --- a/lib/system/audio/audio_player_impl.dart +++ /dev/null @@ -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( - _audioPlayer.playbackEventStream, - _audioPlayer.playingStream, - (a, b) => PlaybackEventModel.fromJAPlaybackEvent(a, b), - ).distinct(); - } - - final ja.AudioPlayer _audioPlayer; - ja.ConcatenatingAudioSource _audioSource; - final QueueGenerator _queueGenerator; - - List _inputQueue; - List _queue; - - static final _log = Logger('AudioPlayer'); - - final BehaviorSubject _currentIndexSubject = BehaviorSubject(); - final BehaviorSubject _currentSongSubject = BehaviorSubject(); - final BehaviorSubject _playbackEventSubject = BehaviorSubject(); - final BehaviorSubject _playingSubject = BehaviorSubject(); - final BehaviorSubject _positionSubject = BehaviorSubject(); - final BehaviorSubject> _queueSubject = BehaviorSubject(); - final BehaviorSubject _shuffleModeSubject = BehaviorSubject.seeded(ShuffleMode.none); - final BehaviorSubject _loopModeSubject = BehaviorSubject(); - - Stream _playbackEventModelStream; - - @override - ValueStream get currentIndexStream => _currentIndexSubject.stream; - - @override - ValueStream get currentSongStream => _currentSongSubject.stream; - - @override - Stream get playbackEventStream => _playbackEventModelStream; - - @override - ValueStream get positionStream => _positionSubject.stream; - - @override - ValueStream get playingStream => _playingSubject.stream; - - @override - ValueStream> get queueStream => _queueSubject.stream; - - @override - ValueStream get shuffleModeStream => _shuffleModeSubject.stream; - - @override - ValueStream get loopModeStream => _loopModeSubject.stream; - - @override - Future 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 loadQueue({List 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 pause() async { - await _audioPlayer.pause(); - } - - @override - Future play() async { - _audioPlayer.play(); - } - - @override - Future playSongList(List 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 seekToNext() async { - final result = _audioPlayer.hasNext; - await _audioPlayer.seekToNext(); - return result; - } - - @override - Future seekToPrevious() async { - if (_audioPlayer.position > const Duration(seconds: 3) || !_audioPlayer.hasPrevious) { - await _audioPlayer.seek(const Duration(seconds: 0)); - } else { - await _audioPlayer.seekToPrevious(); - } - } - - @override - Future setIndex(int index) async { - await _audioPlayer.seek(const Duration(seconds: 0), index: index); - } - - @override - Future stop() async { - _audioPlayer.stop(); - } - - @override - Future 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 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 removeQueueIndex(int index) async { - _queue.removeAt(index); - _queueSubject.add(_queue); - await _audioSource.removeAt(index); - } - - @override - Future 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 setLoopMode(LoopMode loopMode) async { - if (loopMode == null) return; - await _audioPlayer.setLoopMode(loopMode.toJA()); - } - - Future _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)); - } - } -} diff --git a/lib/system/audio/queue_generator.dart b/lib/system/audio/queue_generator.dart deleted file mode 100644 index d7778c6..0000000 --- a/lib/system/audio/queue_generator.dart +++ /dev/null @@ -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> generateQueue( - ShuffleMode shuffleMode, - List songModels, - int startIndex, - ) async { - List 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 mediaItems) { - return ConcatenatingAudioSource( - children: mediaItems.map((MediaItem m) => AudioSource.uri(Uri.file(m.id))).toList(), - ); - } - - ConcatenatingAudioSource songModelsToAudioSource(List songModels) { - return ConcatenatingAudioSource( - children: songModels.map((SongModel m) => AudioSource.uri(Uri.file(m.path))).toList(), - ); - } - - List _generateNormalQueue(List songs) { - return List.generate( - songs.length, - (i) => QueueItemModel( - songs[i], - originalIndex: i, - ), - ); - } - - List _generateShuffleQueue( - List songs, - int startIndex, - ) { - final List queue = List.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> _generateShufflePlusQueue( - List songs, - int startIndex, - ) async { - final List queue = await _getQueueItemWithLinks( - songs[startIndex], - startIndex, - ); - final List 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> _getQueueItemWithLinks( - SongModel song, - int index, - ) async { - final List 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> _getPredecessors(SongModel song) async { - final List songs = []; - SongModel currentSong = song; - - while (currentSong.previous != null) { - currentSong = await _musicDataSource.getSongByPath(currentSong.previous); - songs.add(currentSong); - } - - return songs.reversed.toList(); - } - - Future> _getSuccessors(SongModel song) async { - final List songs = []; - SongModel currentSong = song; - - while (currentSong.next != null) { - currentSong = await _musicDataSource.getSongByPath(currentSong.next); - songs.add(currentSong); - } - - return songs.toList(); - } -} diff --git a/lib/system/audio/stream_constants.dart b/lib/system/audio/stream_constants.dart deleted file mode 100644 index 2a6013c..0000000 --- a/lib/system/audio/stream_constants.dart +++ /dev/null @@ -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'; diff --git a/lib/system/audio/audio_player_contract.dart b/lib/system/datasources/audio_player_data_source.dart similarity index 67% rename from lib/system/audio/audio_player_contract.dart rename to lib/system/datasources/audio_player_data_source.dart index 21da394..9c32cea 100644 --- a/lib/system/audio/audio_player_contract.dart +++ b/lib/system/datasources/audio_player_data_source.dart @@ -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 get currentIndexStream; ValueStream get currentSongStream; Stream get playbackEventStream; ValueStream get playingStream; ValueStream get positionStream; - ValueStream> get queueStream; - ValueStream get shuffleModeStream; - ValueStream get loopModeStream; Future play(); Future pause(); @@ -23,14 +18,12 @@ abstract class AudioPlayer { Future seekToPrevious(); Future dispose(); - Future loadQueue({List queue, int initialIndex}); + Future loadQueue({List queue, int initialIndex}); Future addToQueue(SongModel song); Future moveQueueItem(int oldIndex, int newIndex); Future removeQueueIndex(int index); Future setIndex(int index); - - Future setShuffleMode(ShuffleMode shuffleMode, bool updateQueue); Future setLoopMode(LoopMode loopMode); Future playSongList(List songs, int startIndex); diff --git a/lib/system/datasources/audio_player_data_source_impl.dart b/lib/system/datasources/audio_player_data_source_impl.dart new file mode 100644 index 0000000..e496b4f --- /dev/null +++ b/lib/system/datasources/audio_player_data_source_impl.dart @@ -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( + _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 _currentIndexSubject = BehaviorSubject(); + final BehaviorSubject _currentSongSubject = BehaviorSubject(); + final BehaviorSubject _playbackEventSubject = BehaviorSubject(); + final BehaviorSubject _playingSubject = BehaviorSubject(); + final BehaviorSubject _positionSubject = BehaviorSubject(); + + Stream _playbackEventModelStream; + List _queue; + + void _setQueue(List queue, {int index}) { + _queue = queue; + if (index != null) { + _currentSongSubject.add(_queue[index]); + } else if (_currentIndexSubject.value != null) { + _currentSongSubject.add(_queue[_currentIndexSubject.value]); + } + } + + @override + ValueStream get currentIndexStream => _currentIndexSubject.stream; + + @override + ValueStream get currentSongStream => _currentSongSubject.stream; + + @override + Stream get playbackEventStream => _playbackEventModelStream; + + @override + ValueStream get positionStream => _positionSubject.stream; + + @override + ValueStream get playingStream => _playingSubject.stream; + + @override + Future dispose() async { + await _currentIndexSubject.close(); + await _currentSongSubject.close(); + await _playbackEventSubject.close(); + await _positionSubject.close(); + await _audioPlayer.dispose(); + } + + @override + Future loadQueue({List 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 pause() async { + await _audioPlayer.pause(); + } + + @override + Future play() async { + _audioPlayer.play(); + } + + @override + Future playSongList(List 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 seekToNext() async { + final result = _audioPlayer.hasNext; + await _audioPlayer.seekToNext(); + return result; + } + + @override + Future seekToPrevious() async { + if (_audioPlayer.position > const Duration(seconds: 3) || !_audioPlayer.hasPrevious) { + await _audioPlayer.seek(const Duration(seconds: 0)); + } else { + await _audioPlayer.seekToPrevious(); + } + } + + @override + Future setIndex(int index) async { + await _audioPlayer.seek(const Duration(seconds: 0), index: index); + } + + @override + Future stop() async { + _audioPlayer.stop(); + } + + @override + Future 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 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 removeQueueIndex(int index) async { + // _queue.removeAt(index); + // _queueSubject.add(_queue); + await _audioSource.removeAt(index); + } + + // @override + // Future 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 setLoopMode(LoopMode loopMode) async { + if (loopMode == null) return; + await _audioPlayer.setLoopMode(loopMode.toJA()); + } + + ja.ConcatenatingAudioSource _songModelsToAudioSource(List songModels) { + return ja.ConcatenatingAudioSource( + children: songModels.map((SongModel m) => ja.AudioSource.uri(Uri.file(m.path))).toList(), + ); + } + + // Future _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)); + // } + // } +} diff --git a/lib/system/datasources/local_music_fetcher.dart b/lib/system/datasources/local_music_fetcher.dart index fe4ee69..6639fee 100644 --- a/lib/system/datasources/local_music_fetcher.dart +++ b/lib/system/datasources/local_music_fetcher.dart @@ -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> getLocalMusic(); - final FlutterAudioQuery _flutterAudioQuery; - final SettingsDataSource _settingsDataSource; - // CODESMELL: should probably encapsulate the deviceinfoplugin - final DeviceInfoPlugin _deviceInfo; - - AndroidDeviceInfo _androidDeviceInfo; - Future get androidDeviceInfo async { - _androidDeviceInfo ??= await _deviceInfo.androidInfo; - return _androidDeviceInfo; - } - - @override - Future> getArtists() async { - final List artistInfoList = await _flutterAudioQuery.getArtists(); - return artistInfoList - .map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo)) - .toSet() - .toList(); - } - - @override - Future> getAlbums() async { - final List albumInfoList = await _flutterAudioQuery.getAlbums(); - return albumInfoList.map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo)).toList(); - } - - @override - Future> getSongs() async { - final List songInfoList = await _flutterAudioQuery.getSongs(); - return songInfoList - .where((songInfo) => songInfo.isMusic) - .map((SongInfo songInfo) => SongModel.fromSongInfo(songInfo)) - .toList(); - } - - @override - Future 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> getLocalMusic() async { - final musicDirectories = await _settingsDataSource.getLibraryFolders(); - - final songs = await _getFilteredSongs(musicDirectories); - final albumTitles = Set.from(songs.map((song) => song.album)); - final albums = await _getFilteredAlbums(albumTitles); - final artistNames = Set.from(albums.map((album) => album.artist)); - final artists = await _getFilteredArtists(artistNames); - - return { - 'SONGS': songs, - 'ALBUMS': albums, - 'ARTISTS': artists, - }; - } - - Future> _getFilteredSongs(Iterable musicDirectories) async { - final List 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> _getFilteredAlbums(Iterable albumTitles) async { - final List albumInfoList = await _flutterAudioQuery.getAlbums(); - return albumInfoList - .where((albumInfo) => albumTitles.contains(albumInfo.title)) - .map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo)) - .toList(); - } - - Future> _getFilteredArtists(Iterable artistNames) async { - final List artistInfoList = await _flutterAudioQuery.getArtists(); - return artistInfoList - .where((artistInfo) => artistNames.contains(artistInfo.name)) - .map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo)) - .toSet() - .toList(); - } -} + Future> getArtists(); + Future> getAlbums(); + Future> getSongs(); + Future getAlbumArtwork(int id); +} \ No newline at end of file diff --git a/lib/system/datasources/local_music_fetcher_contract.dart b/lib/system/datasources/local_music_fetcher_contract.dart deleted file mode 100644 index 6639fee..0000000 --- a/lib/system/datasources/local_music_fetcher_contract.dart +++ /dev/null @@ -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> getLocalMusic(); - - Future> getArtists(); - Future> getAlbums(); - Future> getSongs(); - Future getAlbumArtwork(int id); -} \ No newline at end of file diff --git a/lib/system/datasources/local_music_fetcher_impl.dart b/lib/system/datasources/local_music_fetcher_impl.dart new file mode 100644 index 0000000..bff1770 --- /dev/null +++ b/lib/system/datasources/local_music_fetcher_impl.dart @@ -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 get androidDeviceInfo async { + _androidDeviceInfo ??= await _deviceInfo.androidInfo; + return _androidDeviceInfo; + } + + @override + Future> getArtists() async { + final List artistInfoList = await _flutterAudioQuery.getArtists(); + return artistInfoList + .map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo)) + .toSet() + .toList(); + } + + @override + Future> getAlbums() async { + final List albumInfoList = await _flutterAudioQuery.getAlbums(); + return albumInfoList.map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo)).toList(); + } + + @override + Future> getSongs() async { + final List songInfoList = await _flutterAudioQuery.getSongs(); + return songInfoList + .where((songInfo) => songInfo.isMusic) + .map((SongInfo songInfo) => SongModel.fromSongInfo(songInfo)) + .toList(); + } + + @override + Future 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> getLocalMusic() async { + final musicDirectories = await _settingsDataSource.getLibraryFolders(); + + final songs = await _getFilteredSongs(musicDirectories); + final albumTitles = Set.from(songs.map((song) => song.album)); + final albums = await _getFilteredAlbums(albumTitles); + final artistNames = Set.from(albums.map((album) => album.artist)); + final artists = await _getFilteredArtists(artistNames); + + return { + 'SONGS': songs, + 'ALBUMS': albums, + 'ARTISTS': artists, + }; + } + + Future> _getFilteredSongs(Iterable musicDirectories) async { + final List 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> _getFilteredAlbums(Iterable albumTitles) async { + final List albumInfoList = await _flutterAudioQuery.getAlbums(); + return albumInfoList + .where((albumInfo) => albumTitles.contains(albumInfo.title)) + .map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo)) + .toList(); + } + + Future> _getFilteredArtists(Iterable artistNames) async { + final List artistInfoList = await _flutterAudioQuery.getArtists(); + return artistInfoList + .where((artistInfo) => artistNames.contains(artistInfo.name)) + .map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo)) + .toSet() + .toList(); + } +} diff --git a/lib/system/datasources/platform_integration_data_source.dart b/lib/system/datasources/platform_integration_data_source.dart new file mode 100644 index 0000000..dc01f4c --- /dev/null +++ b/lib/system/datasources/platform_integration_data_source.dart @@ -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 get eventStream; + + Future handlePlaybackEvent(PlaybackEventModel playbackEventModel); + Future onPause(); + Future onPlay(); + Future setCurrentSong(SongModel songModel); +} \ No newline at end of file diff --git a/lib/system/datasources/platform_integration_data_source_impl.dart b/lib/system/datasources/platform_integration_data_source_impl.dart new file mode 100644 index 0000000..d475532 --- /dev/null +++ b/lib/system/datasources/platform_integration_data_source_impl.dart @@ -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 _eventSubject = BehaviorSubject(); + + // BaseAudioHandler interface + + @override + Future play() async { + _eventSubject.add(PlatformIntegrationEvent(type: PlatformIntegrationEventType.play)); + } + + @override + Future pause() async { + _eventSubject.add(PlatformIntegrationEvent(type: PlatformIntegrationEventType.pause)); + } + + // PlatformIntegrationDataSource interface + + @override + Stream get eventStream => _eventSubject.stream; + + @override + Future 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 onPlay() async { + playbackState.add(playbackState.value.copyWith( + controls: [MediaControl.skipToPrevious, MediaControl.pause, MediaControl.skipToNext], + playing: true, + processingState: AudioProcessingState.ready, + )); + } + + @override + Future onPause() async { + playbackState.add(playbackState.value.copyWith( + controls: [MediaControl.skipToPrevious, MediaControl.play, MediaControl.skipToNext], + processingState: AudioProcessingState.ready, + playing: false, + )); + } + + @override + Future setCurrentSong(SongModel songModel) async { + mediaItem.add(songModel.toMediaItem()); + } +} diff --git a/lib/system/repositories/audio_player_repository_impl.dart b/lib/system/repositories/audio_player_repository_impl.dart new file mode 100644 index 0000000..0783a67 --- /dev/null +++ b/lib/system/repositories/audio_player_repository_impl.dart @@ -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 _currentIndexSubject = BehaviorSubject(); + // final BehaviorSubject _currentSongSubject = BehaviorSubject(); + final BehaviorSubject _loopModeSubject = BehaviorSubject(); + final BehaviorSubject> _songListSubject = BehaviorSubject(); + final BehaviorSubject> _queueSubject = BehaviorSubject(); + final BehaviorSubject _shuffleModeSubject = BehaviorSubject(); + + @override + Stream eventStream; + + @override + ValueStream get shuffleModeStream => _shuffleModeSubject.stream; + + @override + ValueStream get loopModeStream => _loopModeSubject.stream; + + @override + ValueStream> get songListStream => _songListSubject.stream; + + @override + ValueStream> get queueStream => _queueSubject.stream; + + @override + ValueStream get currentIndexStream => _audioPlayerDataSource.currentIndexStream; + + @override + Stream get currentSongStream => _audioPlayerDataSource.currentSongStream; + + @override + Stream get playbackEventStream => _audioPlayerDataSource.playbackEventStream; + + @override + Stream get playingStream => _audioPlayerDataSource.playingStream; + + @override + Stream get positionStream => _audioPlayerDataSource.positionStream; + + @override + Future addToQueue(Song song) { + // TODO: implement addToQueue + throw UnimplementedError(); + } + + @override + Future dispose() async { + _audioPlayerDataSource.dispose(); + } + + @override + Future loadQueue({List 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 moveQueueItem(int oldIndex, int newIndex) { + // TODO: implement moveQueueItem + throw UnimplementedError(); + } + + @override + Future pause() async { + _audioPlayerDataSource.pause(); + } + + @override + Future play() async { + _audioPlayerDataSource.play(); + } + + @override + Future playSong(Song song) async { + await _audioPlayerDataSource.loadQueue( + initialIndex: 0, + queue: [song as SongModel], + ); + _audioPlayerDataSource.play(); + } + + @override + Future removeQueueIndex(int index) { + // TODO: implement removeQueueIndex + throw UnimplementedError(); + } + + @override + Future seekToNext() async { + return await _audioPlayerDataSource.seekToNext(); + } + + @override + Future seekToPrevious() async { + await _audioPlayerDataSource.seekToPrevious(); + } + + @override + Future setIndex(int index) { + // TODO: implement setIndex + throw UnimplementedError(); + } + + @override + Future setLoopMode(LoopMode loopMode) async { + _loopModeSubject.add(loopMode); + await _audioPlayerDataSource.setLoopMode(loopMode); + } + + @override + Future setShuffleMode(ShuffleMode shuffleMode) async { + _shuffleModeSubject.add(shuffleMode); + } + + @override + Future stop() { + // TODO: implement stop + throw UnimplementedError(); + } +} diff --git a/lib/system/repositories/audio_repository_impl.dart b/lib/system/repositories/audio_repository_impl.dart deleted file mode 100644 index fb59c57..0000000 --- a/lib/system/repositories/audio_repository_impl.dart +++ /dev/null @@ -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 = T Function(S); - -class AudioRepositoryImpl implements AudioRepository { - AudioRepositoryImpl(this._audioHandler); - - final AudioHandler _audioHandler; - - @override - Stream get currentSongStream => _filterStream( - _audioHandler.mediaItem.stream, - (MediaItem mi) => SongModel.fromMediaItem(mi), - ); - - @override - Stream get playbackStateStream => _filterStream( - _audioHandler.playbackState.stream, - (PlaybackState ps) => PlaybackStateModel.fromASPlaybackState(ps), - ); - - @override - Stream get currentPositionStream => _position().distinct(); - - @override - Future playSong(int index, List songList) async { - if (0 <= index && index < songList.length) { - final List context = songList.map((s) => s.path).toList(); - await _audioHandler.customAction(PLAY_WITH_CONTEXT, {'CONTEXT': context, 'INDEX': index}); - } - } - - @override - Future play() async { - _audioHandler.play(); - } - - @override - Future pause() async { - await _audioHandler.pause(); - } - - @override - Future skipToNext() async { - await _audioHandler.skipToNext(); - } - - @override - Future skipToPrevious() async { - await _audioHandler.skipToPrevious(); - } - - @override - Future setIndex(int index) async { - await _audioHandler.customAction(SET_INDEX, {'INDEX': index}); - } - - @override - Future setShuffleMode(ShuffleMode shuffleMode) async { - await _audioHandler.customAction(SET_SHUFFLE_MODE, {'SHUFFLE_MODE': shuffleMode}); - } - - @override - Future setLoopMode(LoopMode loopMode) async { - await _audioHandler.customAction(SET_LOOP_MODE, {'LOOP_MODE': loopMode}); - } - - Stream _filterStream(Stream stream, Conversion fn) async* { - T lastItem; - - await for (final S item in stream) { - final T newItem = fn(item); - if (newItem != lastItem) { - lastItem = newItem; - yield newItem; - } - } - } - - Stream _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 shuffleAll() async { - await _audioHandler.customAction(SHUFFLE_ALL, null); - } - - @override - Future addToQueue(Song song) async { - await _audioHandler.addQueueItem((song as SongModel).toMediaItem()); - } - - @override - Future moveQueueItem(int oldIndex, int newIndex) async { - await _audioHandler.customAction(MOVE_QUEUE_ITEM, { - 'OLD_INDEX': oldIndex, - 'NEW_INDEX': newIndex, - }); - } - - @override - Future removeQueueIndex(int index) async { - await _audioHandler.removeQueueItemAt(index); - } - - @override - Future playAlbum(Album album) async { - await _audioHandler.customAction(PLAY_ALBUM, {'ALBUM': album as AlbumModel}); - } - - @override - Future playArtist(Artist artist, {ShuffleMode shuffleMode = ShuffleMode.none}) async { - await _audioHandler.customAction(PLAY_ARTIST, { - 'ARTIST': artist as ArtistModel, - 'SHUFFLE_MODE': shuffleMode, - }); - } -} diff --git a/lib/system/repositories/music_data_repository_impl.dart b/lib/system/repositories/music_data_repository_impl.dart index 2d51f20..9f5f95f 100644 --- a/lib/system/repositories/music_data_repository_impl.dart +++ b/lib/system/repositories/music_data_repository_impl.dart @@ -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 getSongByPath(String path) async { + return await _musicDataSource.getSongByPath(path); + } + @override Stream> get songStream => _musicDataSource.songStream; diff --git a/lib/system/repositories/persistent_player_state_repository_impl.dart b/lib/system/repositories/persistent_player_state_repository_impl.dart index d030f27..126b874 100644 --- a/lib/system/repositories/persistent_player_state_repository_impl.dart +++ b/lib/system/repositories/persistent_player_state_repository_impl.dart @@ -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 get shuffleModeStream => _playerStateDataSource.shuffleModeStream; + + @override + void setCurrentIndex(int index) { + // TODO: implement setCurrentIndex + } + + @override + void setLoopMode(LoopMode loopMode) { + // TODO: implement setLoopMode + } + + @override + void setQueue(List queue) { + // TODO: implement setQueue + } + + @override + void setShuffleMode(ShuffleMode shuffleMode) { + // TODO: implement setShuffleMode + } } diff --git a/lib/system/repositories/platform_integration_repository_impl.dart b/lib/system/repositories/platform_integration_repository_impl.dart new file mode 100644 index 0000000..d262a0a --- /dev/null +++ b/lib/system/repositories/platform_integration_repository_impl.dart @@ -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 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 queue) { + // TODO: implement setQueue + } +}