big changes
This commit is contained in:
parent
320028dafa
commit
39e4111037
45 changed files with 1608 additions and 1083 deletions
18
lib/domain/actors/audio_player_actor.dart
Normal file
18
lib/domain/actors/audio_player_actor.dart
Normal file
|
@ -0,0 +1,18 @@
|
|||
import '../entities/song.dart';
|
||||
import '../repositories/audio_player_repository.dart';
|
||||
import '../usecases/handle_playback_state.dart';
|
||||
import '../usecases/set_current_song.dart';
|
||||
|
||||
class AudioPlayerActor {
|
||||
AudioPlayerActor(this._audioPlayerInfoRepository, this._handlePlaybackEvent, this._setCurrentSong) {
|
||||
_audioPlayerInfoRepository.currentSongStream.listen(_handleCurrentSong);
|
||||
_audioPlayerInfoRepository.playbackEventStream.listen(_handlePlaybackEvent);
|
||||
}
|
||||
|
||||
final AudioPlayerInfoRepository _audioPlayerInfoRepository;
|
||||
|
||||
final HandlePlaybackEvent _handlePlaybackEvent;
|
||||
final SetCurrentSong _setCurrentSong;
|
||||
|
||||
void _handleCurrentSong(Song song) => _setCurrentSong(song);
|
||||
}
|
27
lib/domain/actors/platform_integration_actor.dart
Normal file
27
lib/domain/actors/platform_integration_actor.dart
Normal file
|
@ -0,0 +1,27 @@
|
|||
import '../repositories/platform_integration_repository.dart';
|
||||
import '../usecases/pause.dart';
|
||||
import '../usecases/play.dart';
|
||||
|
||||
class PlatformIntegrationActor {
|
||||
PlatformIntegrationActor(this._platformIntegrationInfoRepository, this._pause, this._play) {
|
||||
_platformIntegrationInfoRepository.eventStream
|
||||
.listen((event) => _handlePlatformIntegrationEvent(event));
|
||||
}
|
||||
|
||||
final PlatformIntegrationInfoRepository _platformIntegrationInfoRepository;
|
||||
|
||||
final Pause _pause;
|
||||
final Play _play;
|
||||
|
||||
void _handlePlatformIntegrationEvent(PlatformIntegrationEvent event) {
|
||||
switch (event.type) {
|
||||
case PlatformIntegrationEventType.play:
|
||||
_play();
|
||||
break;
|
||||
case PlatformIntegrationEventType.pause:
|
||||
_pause();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
3
lib/domain/entities/event.dart
Normal file
3
lib/domain/entities/event.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
abstract class Event {
|
||||
Map<String, dynamic> payload;
|
||||
}
|
149
lib/domain/modules/queue_generator.dart
Normal file
149
lib/domain/modules/queue_generator.dart
Normal file
|
@ -0,0 +1,149 @@
|
|||
import '../entities/queue_item.dart';
|
||||
import '../entities/shuffle_mode.dart';
|
||||
import '../entities/song.dart';
|
||||
import '../repositories/music_data_repository.dart';
|
||||
|
||||
class QueueGenerationModule {
|
||||
QueueGenerationModule(this._musicDataRepository);
|
||||
|
||||
final MusicDataInfoRepository _musicDataRepository;
|
||||
|
||||
Future<List<QueueItem>> generateQueue(
|
||||
ShuffleMode shuffleMode,
|
||||
List<Song> songs,
|
||||
int startIndex,
|
||||
) async {
|
||||
List<QueueItem> queue;
|
||||
|
||||
switch (shuffleMode) {
|
||||
case ShuffleMode.none:
|
||||
queue = _generateNormalQueue(songs);
|
||||
break;
|
||||
case ShuffleMode.standard:
|
||||
queue = _generateShuffleQueue(songs, startIndex);
|
||||
break;
|
||||
case ShuffleMode.plus:
|
||||
queue = await _generateShufflePlusQueue(songs, startIndex);
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
List<QueueItem> _generateNormalQueue(List<Song> songs) {
|
||||
return List<QueueItem>.generate(
|
||||
songs.length,
|
||||
(i) => QueueItem(
|
||||
songs[i],
|
||||
originalIndex: i,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<QueueItem> _generateShuffleQueue(
|
||||
List<Song> songs,
|
||||
int startIndex,
|
||||
) {
|
||||
final List<QueueItem> queue = List<QueueItem>.generate(
|
||||
songs.length,
|
||||
(i) => QueueItem(
|
||||
songs[i],
|
||||
originalIndex: i,
|
||||
),
|
||||
);
|
||||
queue.removeAt(startIndex);
|
||||
queue.shuffle();
|
||||
final first = QueueItem(
|
||||
songs[startIndex],
|
||||
originalIndex: startIndex,
|
||||
);
|
||||
return [first] + queue;
|
||||
}
|
||||
|
||||
Future<List<QueueItem>> _generateShufflePlusQueue(
|
||||
List<Song> songs,
|
||||
int startIndex,
|
||||
) async {
|
||||
final List<QueueItem> queue = await _getQueueItemWithLinks(
|
||||
songs[startIndex],
|
||||
startIndex,
|
||||
);
|
||||
final List<int> indices = [];
|
||||
|
||||
// filter mediaitem list
|
||||
// TODO: multiply higher rated songs
|
||||
for (var i = 0; i < songs.length; i++) {
|
||||
if (i != startIndex && !songs[i].blocked) {
|
||||
indices.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
indices.shuffle();
|
||||
|
||||
for (var i = 0; i < indices.length; i++) {
|
||||
final int index = indices[i];
|
||||
final Song song = songs[index];
|
||||
|
||||
queue.addAll(await _getQueueItemWithLinks(song, index));
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
// TODO: naming things is hard
|
||||
Future<List<QueueItem>> _getQueueItemWithLinks(
|
||||
Song song,
|
||||
int index,
|
||||
) async {
|
||||
final List<QueueItem> queueItems = [];
|
||||
|
||||
final predecessors = await _getPredecessors(song);
|
||||
final successors = await _getSuccessors(song);
|
||||
|
||||
for (final p in predecessors) {
|
||||
queueItems.add(QueueItem(
|
||||
p,
|
||||
originalIndex: index,
|
||||
type: QueueItemType.predecessor,
|
||||
));
|
||||
}
|
||||
|
||||
queueItems.add(QueueItem(
|
||||
song,
|
||||
originalIndex: index,
|
||||
));
|
||||
|
||||
for (final p in successors) {
|
||||
queueItems.add(QueueItem(
|
||||
p,
|
||||
originalIndex: index,
|
||||
type: QueueItemType.successor,
|
||||
));
|
||||
}
|
||||
|
||||
return queueItems;
|
||||
}
|
||||
|
||||
Future<List<Song>> _getPredecessors(Song song) async {
|
||||
final List<Song> songs = [];
|
||||
Song currentSong = song;
|
||||
|
||||
while (currentSong.previous != null) {
|
||||
currentSong = await _musicDataRepository.getSongByPath(currentSong.previous);
|
||||
songs.add(currentSong);
|
||||
}
|
||||
|
||||
return songs.reversed.toList();
|
||||
}
|
||||
|
||||
Future<List<Song>> _getSuccessors(Song song) async {
|
||||
final List<Song> songs = [];
|
||||
Song currentSong = song;
|
||||
|
||||
while (currentSong.next != null) {
|
||||
currentSong = await _musicDataRepository.getSongByPath(currentSong.next);
|
||||
songs.add(currentSong);
|
||||
}
|
||||
|
||||
return songs.toList();
|
||||
}
|
||||
}
|
49
lib/domain/repositories/audio_player_repository.dart
Normal file
49
lib/domain/repositories/audio_player_repository.dart
Normal file
|
@ -0,0 +1,49 @@
|
|||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
import '../entities/event.dart';
|
||||
import '../entities/loop_mode.dart';
|
||||
import '../entities/playback_event.dart';
|
||||
import '../entities/queue_item.dart';
|
||||
import '../entities/shuffle_mode.dart';
|
||||
import '../entities/song.dart';
|
||||
|
||||
abstract class AudioPlayerInfoRepository {
|
||||
Stream<AudioPlayerEvent> eventStream;
|
||||
|
||||
ValueStream<ShuffleMode> get shuffleModeStream;
|
||||
ValueStream<LoopMode> get loopModeStream;
|
||||
ValueStream<List<Song>> get songListStream;
|
||||
ValueStream<List<QueueItem>> get queueStream;
|
||||
|
||||
ValueStream<int> get currentIndexStream;
|
||||
Stream<Song> get currentSongStream;
|
||||
Stream<PlaybackEvent> get playbackEventStream;
|
||||
Stream<bool> get playingStream;
|
||||
Stream<Duration> get positionStream;
|
||||
}
|
||||
|
||||
abstract class AudioPlayerRepository extends AudioPlayerInfoRepository {
|
||||
Future<void> play();
|
||||
Future<void> pause();
|
||||
Future<void> stop();
|
||||
Future<bool> seekToNext();
|
||||
Future<void> seekToPrevious();
|
||||
Future<void> dispose();
|
||||
|
||||
Future<void> playSong(Song song);
|
||||
Future<void> loadQueue({List<QueueItem> queue, int initialIndex});
|
||||
Future<void> addToQueue(Song song);
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex);
|
||||
Future<void> removeQueueIndex(int index);
|
||||
Future<void> setIndex(int index);
|
||||
|
||||
/// Set the ShuffleMode. Does not affect playback/queue.
|
||||
Future<void> setShuffleMode(ShuffleMode shuffleMode);
|
||||
Future<void> setLoopMode(LoopMode loopMode);
|
||||
}
|
||||
|
||||
class AudioPlayerEvent extends Event {
|
||||
AudioPlayerEventType type;
|
||||
}
|
||||
|
||||
enum AudioPlayerEventType { dummy }
|
|
@ -1,30 +0,0 @@
|
|||
import '../entities/album.dart';
|
||||
import '../entities/artist.dart';
|
||||
import '../entities/loop_mode.dart';
|
||||
import '../entities/playback_state.dart';
|
||||
import '../entities/shuffle_mode.dart';
|
||||
import '../entities/song.dart';
|
||||
|
||||
abstract class AudioRepository {
|
||||
Stream<Song> get currentSongStream;
|
||||
Stream<PlaybackState> get playbackStateStream;
|
||||
Stream<int> get currentPositionStream;
|
||||
|
||||
Future<void> playSong(int index, List<Song> songList);
|
||||
Future<void> play();
|
||||
Future<void> pause();
|
||||
Future<void> skipToNext();
|
||||
Future<void> skipToPrevious();
|
||||
Future<void> setIndex(int index);
|
||||
|
||||
Future<void> playAlbum(Album album);
|
||||
Future<void> playArtist(Artist artist, {ShuffleMode shuffleMode = ShuffleMode.none});
|
||||
|
||||
Future<void> setShuffleMode(ShuffleMode shuffleMode);
|
||||
Future<void> setLoopMode(LoopMode loopMode);
|
||||
|
||||
Future<void> shuffleAll();
|
||||
Future<void> addToQueue(Song song);
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex);
|
||||
Future<void> removeQueueIndex(int index);
|
||||
}
|
|
@ -2,7 +2,8 @@ import '../entities/album.dart';
|
|||
import '../entities/artist.dart';
|
||||
import '../entities/song.dart';
|
||||
|
||||
abstract class MusicDataRepository {
|
||||
abstract class MusicDataInfoRepository {
|
||||
Future<Song> getSongByPath(String path);
|
||||
Stream<List<Song>> get songStream;
|
||||
Stream<List<Song>> getAlbumSongStream(Album album);
|
||||
|
||||
|
@ -10,6 +11,8 @@ abstract class MusicDataRepository {
|
|||
Stream<List<Album>> getArtistAlbumStream(Artist artist);
|
||||
|
||||
Stream<List<Artist>> get artistStream;
|
||||
}
|
||||
|
||||
abstract class MusicDataRepository extends MusicDataInfoRepository {
|
||||
Future<void> updateDatabase();
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import '../entities/loop_mode.dart';
|
||||
import '../entities/queue_item.dart';
|
||||
import '../entities/shuffle_mode.dart';
|
||||
import '../entities/song.dart';
|
||||
|
||||
|
@ -8,4 +9,9 @@ abstract class PlayerStateRepository {
|
|||
Stream<Song> get currentSongStream;
|
||||
Stream<LoopMode> get loopModeStream;
|
||||
Stream<ShuffleMode> get shuffleModeStream;
|
||||
|
||||
void setShuffleMode(ShuffleMode shuffleMode);
|
||||
void setLoopMode(LoopMode loopMode);
|
||||
void setQueue(List<QueueItem> queue);
|
||||
void setCurrentIndex(int index);
|
||||
}
|
||||
|
|
38
lib/domain/repositories/platform_integration_repository.dart
Normal file
38
lib/domain/repositories/platform_integration_repository.dart
Normal file
|
@ -0,0 +1,38 @@
|
|||
import '../entities/event.dart';
|
||||
import '../entities/playback_event.dart';
|
||||
import '../entities/song.dart';
|
||||
|
||||
/*
|
||||
|
||||
- position
|
||||
- controls (playbackState)
|
||||
|
||||
*/
|
||||
|
||||
abstract class PlatformIntegrationInfoRepository {
|
||||
Stream<PlatformIntegrationEvent> get eventStream;
|
||||
}
|
||||
|
||||
abstract class PlatformIntegrationRepository extends PlatformIntegrationInfoRepository {
|
||||
void play();
|
||||
void pause();
|
||||
void onStop();
|
||||
|
||||
void handlePlaybackEvent(PlaybackEvent playbackEvent);
|
||||
void setCurrentSong(Song song);
|
||||
void setQueue(List<Song> queue);
|
||||
}
|
||||
|
||||
class PlatformIntegrationEvent extends Event {
|
||||
PlatformIntegrationEvent({this.type});
|
||||
|
||||
final PlatformIntegrationEventType type;
|
||||
}
|
||||
|
||||
enum PlatformIntegrationEventType {
|
||||
play,
|
||||
pause,
|
||||
stop,
|
||||
skipNext,
|
||||
skipPrevious,
|
||||
}
|
12
lib/domain/usecases/handle_playback_state.dart
Normal file
12
lib/domain/usecases/handle_playback_state.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import '../entities/playback_event.dart';
|
||||
import '../repositories/platform_integration_repository.dart';
|
||||
|
||||
class HandlePlaybackEvent {
|
||||
HandlePlaybackEvent(this._platformIntegrationRepository);
|
||||
|
||||
final PlatformIntegrationRepository _platformIntegrationRepository;
|
||||
|
||||
Future<void> call(PlaybackEvent playbackEvent) async {
|
||||
_platformIntegrationRepository.handlePlaybackEvent(playbackEvent);
|
||||
}
|
||||
}
|
11
lib/domain/usecases/pause.dart
Normal file
11
lib/domain/usecases/pause.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
import '../repositories/audio_player_repository.dart';
|
||||
|
||||
class Pause {
|
||||
Pause(this._audioPlayerRepository);
|
||||
|
||||
final AudioPlayerRepository _audioPlayerRepository;
|
||||
|
||||
Future<void> call() async {
|
||||
_audioPlayerRepository.pause();
|
||||
}
|
||||
}
|
11
lib/domain/usecases/play.dart
Normal file
11
lib/domain/usecases/play.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
import '../repositories/audio_player_repository.dart';
|
||||
|
||||
class Play {
|
||||
Play(this._audioPlayerRepository);
|
||||
|
||||
final AudioPlayerRepository _audioPlayerRepository;
|
||||
|
||||
Future<void> call() async {
|
||||
_audioPlayerRepository.play();
|
||||
}
|
||||
}
|
42
lib/domain/usecases/play_songs.dart
Normal file
42
lib/domain/usecases/play_songs.dart
Normal file
|
@ -0,0 +1,42 @@
|
|||
import '../entities/song.dart';
|
||||
import '../modules/queue_generator.dart';
|
||||
import '../repositories/audio_player_repository.dart';
|
||||
import '../repositories/persistent_player_state_repository.dart';
|
||||
import '../repositories/platform_integration_repository.dart';
|
||||
|
||||
class PlaySongs {
|
||||
PlaySongs(
|
||||
this._audioPlayerRepository,
|
||||
this._platformIntegrationRepository,
|
||||
this._playerStateRepository,
|
||||
this._queueGenerationModule,
|
||||
);
|
||||
|
||||
final AudioPlayerRepository _audioPlayerRepository;
|
||||
final PlatformIntegrationRepository _platformIntegrationRepository;
|
||||
final PlayerStateRepository _playerStateRepository;
|
||||
|
||||
final QueueGenerationModule _queueGenerationModule;
|
||||
|
||||
Future<void> call({List<Song> songs, int initialIndex}) async {
|
||||
if (0 <= initialIndex && initialIndex < songs.length) {
|
||||
// _audioPlayerRepository.playSong(songs[initialIndex]);
|
||||
|
||||
final queueItems = await _queueGenerationModule.generateQueue(
|
||||
_audioPlayerRepository.shuffleModeStream.value,
|
||||
songs,
|
||||
initialIndex,
|
||||
);
|
||||
|
||||
await _audioPlayerRepository.loadQueue(
|
||||
initialIndex: initialIndex,
|
||||
queue: queueItems,
|
||||
);
|
||||
_audioPlayerRepository.play();
|
||||
|
||||
_platformIntegrationRepository.setCurrentSong(songs[initialIndex]);
|
||||
// _platformIntegrationRepository.play();
|
||||
_platformIntegrationRepository.setQueue(queueItems.map((e) => e.song).toList());
|
||||
}
|
||||
}
|
||||
}
|
11
lib/domain/usecases/seek_to_next.dart
Normal file
11
lib/domain/usecases/seek_to_next.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
import '../repositories/audio_player_repository.dart';
|
||||
|
||||
class SeekToNext {
|
||||
SeekToNext(this._audioPlayerRepository);
|
||||
|
||||
final AudioPlayerRepository _audioPlayerRepository;
|
||||
|
||||
Future<bool> call() async {
|
||||
return await _audioPlayerRepository.seekToNext();
|
||||
}
|
||||
}
|
11
lib/domain/usecases/seek_to_previous.dart
Normal file
11
lib/domain/usecases/seek_to_previous.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
import '../repositories/audio_player_repository.dart';
|
||||
|
||||
class SeekToPrevious {
|
||||
SeekToPrevious(this._audioPlayerRepository);
|
||||
|
||||
final AudioPlayerRepository _audioPlayerRepository;
|
||||
|
||||
Future<void> call() async {
|
||||
await _audioPlayerRepository.seekToPrevious();
|
||||
}
|
||||
}
|
12
lib/domain/usecases/set_current_song.dart
Normal file
12
lib/domain/usecases/set_current_song.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import '../entities/song.dart';
|
||||
import '../repositories/platform_integration_repository.dart';
|
||||
|
||||
class SetCurrentSong {
|
||||
SetCurrentSong(this._platformIntegrationRepository);
|
||||
|
||||
final PlatformIntegrationRepository _platformIntegrationRepository;
|
||||
|
||||
Future<void> call(Song song) async {
|
||||
_platformIntegrationRepository.setCurrentSong(song);
|
||||
}
|
||||
}
|
12
lib/domain/usecases/set_loop_mode.dart
Normal file
12
lib/domain/usecases/set_loop_mode.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import '../entities/loop_mode.dart';
|
||||
import '../repositories/audio_player_repository.dart';
|
||||
|
||||
class SetLoopMode {
|
||||
SetLoopMode(this._audioPlayerRepository);
|
||||
|
||||
final AudioPlayerRepository _audioPlayerRepository;
|
||||
|
||||
Future<void> call(LoopMode loopMode) async {
|
||||
await _audioPlayerRepository.setLoopMode(loopMode);
|
||||
}
|
||||
}
|
64
lib/domain/usecases/set_shuffle_mode.dart
Normal file
64
lib/domain/usecases/set_shuffle_mode.dart
Normal file
|
@ -0,0 +1,64 @@
|
|||
import 'package:mucke/domain/entities/queue_item.dart';
|
||||
|
||||
import '../entities/shuffle_mode.dart';
|
||||
import '../entities/song.dart';
|
||||
import '../modules/queue_generator.dart';
|
||||
import '../repositories/audio_player_repository.dart';
|
||||
import '../repositories/persistent_player_state_repository.dart';
|
||||
import '../repositories/platform_integration_repository.dart';
|
||||
|
||||
class SetShuffleMode {
|
||||
SetShuffleMode(
|
||||
this._audioPlayerRepository,
|
||||
this._platformIntegrationRepository,
|
||||
this._playerStateRepository,
|
||||
this._queueGenerationModule,
|
||||
);
|
||||
|
||||
final AudioPlayerRepository _audioPlayerRepository;
|
||||
final PlatformIntegrationRepository _platformIntegrationRepository;
|
||||
final PlayerStateRepository _playerStateRepository;
|
||||
|
||||
final QueueGenerationModule _queueGenerationModule;
|
||||
|
||||
Future<void> call(ShuffleMode shuffleMode) async {
|
||||
// _audioPlayerRepository.playSong(songs[initialIndex]);
|
||||
|
||||
_audioPlayerRepository.setShuffleMode(shuffleMode);
|
||||
|
||||
final queue = _audioPlayerRepository.queueStream.value;
|
||||
final currentIndex = _audioPlayerRepository.currentIndexStream.value;
|
||||
|
||||
final QueueItem currentQueueItem = queue[currentIndex];
|
||||
final int index = currentQueueItem.originalIndex;
|
||||
|
||||
// WAS LETZTE GEDANKE?
|
||||
// _inputQueue ist die originale Song Liste aus playSongs o.ä. BEVOR der QueueGenerator drüber läuft
|
||||
|
||||
// _queue = await _queueGenerator.generateQueue(shuffleMode, _inputQueue, index);
|
||||
// // TODO: maybe refactor _queue to a subject and listen for changes
|
||||
// final songModelQueue = _queue.map((e) => e.song).toList();
|
||||
// _queueSubject.add(_queue);
|
||||
|
||||
// final newQueue = _songModelsToAudioSource(songModelQueue);
|
||||
// _updateQueue(newQueue, currentQueueItem);
|
||||
|
||||
// final queueItems = await _queueGenerationModule.generateQueue(
|
||||
// shuffleMode,
|
||||
// songs,
|
||||
// initialIndex,
|
||||
// );
|
||||
|
||||
// final songList = queueItems.map((e) => e.song).toList();
|
||||
|
||||
// await _audioPlayerRepository.loadQueue(
|
||||
// initialIndex: initialIndex,
|
||||
// queue: songList,
|
||||
// );
|
||||
// _audioPlayerRepository.play();
|
||||
|
||||
// _platformIntegrationRepository.setCurrentSong(songs[initialIndex]);
|
||||
// // _platformIntegrationRepository.play();
|
||||
// _platformIntegrationRepository.setQueue(songList);
|
||||
}
|
||||
}
|
52
lib/domain/usecases/shuffle_all.dart
Normal file
52
lib/domain/usecases/shuffle_all.dart
Normal file
|
@ -0,0 +1,52 @@
|
|||
import 'dart:math';
|
||||
|
||||
import '../entities/shuffle_mode.dart';
|
||||
import '../entities/song.dart';
|
||||
import '../modules/queue_generator.dart';
|
||||
import '../repositories/audio_player_repository.dart';
|
||||
import '../repositories/music_data_repository.dart';
|
||||
import '../repositories/persistent_player_state_repository.dart';
|
||||
import '../repositories/platform_integration_repository.dart';
|
||||
|
||||
const SHUFFLE_MODE = ShuffleMode.plus;
|
||||
|
||||
class ShuffleAll {
|
||||
ShuffleAll(
|
||||
this._audioPlayerRepository,
|
||||
this._musicDataRepository,
|
||||
this._platformIntegrationRepository,
|
||||
this._playerStateRepository,
|
||||
this._queueGenerationModule,
|
||||
);
|
||||
|
||||
final AudioPlayerRepository _audioPlayerRepository;
|
||||
final MusicDataRepository _musicDataRepository;
|
||||
final PlatformIntegrationRepository _platformIntegrationRepository;
|
||||
final PlayerStateRepository _playerStateRepository;
|
||||
|
||||
final QueueGenerationModule _queueGenerationModule;
|
||||
|
||||
Future<void> call() async {
|
||||
final List<Song> songs = await _musicDataRepository.songStream.first;
|
||||
final rng = Random();
|
||||
final index = rng.nextInt(songs.length);
|
||||
|
||||
_audioPlayerRepository.setShuffleMode(SHUFFLE_MODE);
|
||||
|
||||
final queueItems = await _queueGenerationModule.generateQueue(
|
||||
SHUFFLE_MODE,
|
||||
songs,
|
||||
index,
|
||||
);
|
||||
|
||||
await _audioPlayerRepository.loadQueue(
|
||||
initialIndex: 0,
|
||||
queue: queueItems,
|
||||
);
|
||||
_audioPlayerRepository.play();
|
||||
|
||||
_platformIntegrationRepository.setCurrentSong(songs[index]);
|
||||
// _platformIntegrationRepository.play();
|
||||
_platformIntegrationRepository.setQueue(queueItems.map((e) => e.song).toList());
|
||||
}
|
||||
}
|
11
lib/domain/usecases/update_database.dart
Normal file
11
lib/domain/usecases/update_database.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
import '../repositories/music_data_repository.dart';
|
||||
|
||||
class UpdateDatabase {
|
||||
UpdateDatabase(this._musicDataRepository);
|
||||
|
||||
final MusicDataRepository _musicDataRepository;
|
||||
|
||||
Future<void> call() async {
|
||||
await _musicDataRepository.updateDatabase();
|
||||
}
|
||||
}
|
|
@ -2,30 +2,45 @@ import 'package:audio_service/audio_service.dart';
|
|||
import 'package:device_info/device_info.dart';
|
||||
import 'package:flutter_audio_query/flutter_audio_query.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:just_audio/just_audio.dart' as ja;
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
|
||||
import 'domain/repositories/audio_repository.dart';
|
||||
import 'domain/actors/audio_player_actor.dart';
|
||||
import 'domain/actors/platform_integration_actor.dart';
|
||||
import 'domain/modules/queue_generator.dart';
|
||||
import 'domain/repositories/audio_player_repository.dart';
|
||||
import 'domain/repositories/music_data_modifier_repository.dart';
|
||||
import 'domain/repositories/music_data_repository.dart';
|
||||
import 'domain/repositories/persistent_player_state_repository.dart';
|
||||
import 'domain/repositories/platform_integration_repository.dart';
|
||||
import 'domain/repositories/settings_repository.dart';
|
||||
import 'domain/usecases/handle_playback_state.dart';
|
||||
import 'domain/usecases/pause.dart';
|
||||
import 'domain/usecases/play.dart';
|
||||
import 'domain/usecases/play_songs.dart';
|
||||
import 'domain/usecases/seek_to_next.dart';
|
||||
import 'domain/usecases/seek_to_previous.dart';
|
||||
import 'domain/usecases/set_current_song.dart';
|
||||
import 'domain/usecases/set_loop_mode.dart';
|
||||
import 'domain/usecases/shuffle_all.dart';
|
||||
import 'domain/usecases/update_database.dart';
|
||||
import 'presentation/state/audio_store.dart';
|
||||
import 'presentation/state/music_data_store.dart';
|
||||
import 'presentation/state/navigation_store.dart';
|
||||
import 'system/audio/audio_handler.dart';
|
||||
import 'system/audio/audio_player_contract.dart';
|
||||
import 'system/audio/audio_player_impl.dart';
|
||||
import 'system/audio/queue_generator.dart';
|
||||
import 'system/datasources/audio_player_data_source.dart';
|
||||
import 'system/datasources/audio_player_data_source_impl.dart';
|
||||
import 'system/datasources/local_music_fetcher.dart';
|
||||
import 'system/datasources/local_music_fetcher_contract.dart';
|
||||
import 'system/datasources/local_music_fetcher_impl.dart';
|
||||
import 'system/datasources/moor_database.dart';
|
||||
import 'system/datasources/music_data_source_contract.dart';
|
||||
import 'system/datasources/platform_integration_data_source.dart';
|
||||
import 'system/datasources/platform_integration_data_source_impl.dart';
|
||||
import 'system/datasources/player_state_data_source.dart';
|
||||
import 'system/datasources/settings_data_source.dart';
|
||||
import 'system/repositories/audio_repository_impl.dart';
|
||||
import 'system/repositories/audio_player_repository_impl.dart';
|
||||
import 'system/repositories/music_data_modifier_repository_impl.dart';
|
||||
import 'system/repositories/music_data_repository_impl.dart';
|
||||
import 'system/repositories/persistent_player_state_repository_impl.dart';
|
||||
import 'system/repositories/platform_integration_repository_impl.dart';
|
||||
import 'system/repositories/settings_repository_impl.dart';
|
||||
|
||||
final GetIt getIt = GetIt.instance;
|
||||
|
@ -37,9 +52,10 @@ Future<void> setupGetIt() async {
|
|||
getIt.registerFactory<MusicDataStore>(
|
||||
() {
|
||||
final musicDataStore = MusicDataStore(
|
||||
musicDataRepository: getIt(),
|
||||
musicDataInfoRepository: getIt(),
|
||||
settingsRepository: getIt(),
|
||||
musicDataModifierRepository: getIt(),
|
||||
updateDatabase: getIt(),
|
||||
);
|
||||
return musicDataStore;
|
||||
},
|
||||
|
@ -47,8 +63,14 @@ Future<void> setupGetIt() async {
|
|||
getIt.registerFactory<AudioStore>(
|
||||
() {
|
||||
final audioStore = AudioStore(
|
||||
audioRepository: getIt(),
|
||||
persistentPlayerStateRepository: getIt(),
|
||||
audioPlayerInfoRepository: getIt(),
|
||||
pause: getIt(),
|
||||
play: getIt(),
|
||||
playSongs: getIt(),
|
||||
seekToNext: getIt(),
|
||||
seekToPrevious: getIt(),
|
||||
setLoopMode: getIt(),
|
||||
shuffleAll: getIt(),
|
||||
);
|
||||
return audioStore;
|
||||
},
|
||||
|
@ -60,23 +82,97 @@ Future<void> setupGetIt() async {
|
|||
},
|
||||
);
|
||||
|
||||
// use cases
|
||||
getIt.registerLazySingleton<HandlePlaybackEvent>(
|
||||
() => HandlePlaybackEvent(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<Pause>(
|
||||
() => Pause(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<Play>(
|
||||
() => Play(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<PlaySongs>(
|
||||
() => PlaySongs(
|
||||
getIt(),
|
||||
getIt(),
|
||||
getIt(),
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<SeekToNext>(
|
||||
() => SeekToNext(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<SeekToPrevious>(
|
||||
() => SeekToPrevious(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<SetCurrentSong>(
|
||||
() => SetCurrentSong(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<SetLoopMode>(
|
||||
() => SetLoopMode(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<ShuffleAll>(
|
||||
() => ShuffleAll(
|
||||
getIt(),
|
||||
getIt(),
|
||||
getIt(),
|
||||
getIt(),
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<UpdateDatabase>(
|
||||
() => UpdateDatabase(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
|
||||
// modules
|
||||
getIt.registerLazySingleton<QueueGenerationModule>(
|
||||
() => QueueGenerationModule(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
|
||||
// repositories
|
||||
getIt.registerLazySingleton<AudioPlayerRepository>(
|
||||
() => AudioPlayerRepositoryImpl(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<AudioPlayerInfoRepository>(
|
||||
() => getIt<AudioPlayerRepository>(),
|
||||
);
|
||||
|
||||
getIt.registerLazySingleton<MusicDataRepository>(
|
||||
() => MusicDataRepositoryImpl(
|
||||
getIt(),
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<MusicDataInfoRepository>(
|
||||
() => getIt<MusicDataRepository>(),
|
||||
);
|
||||
getIt.registerLazySingleton<MusicDataModifierRepository>(
|
||||
() => MusicDataModifierRepositoryImpl(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<AudioRepository>(
|
||||
() => AudioRepositoryImpl(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerLazySingleton<PlayerStateRepository>(
|
||||
() => PlayerStateRepositoryImpl(
|
||||
getIt(),
|
||||
|
@ -87,6 +183,14 @@ Future<void> setupGetIt() async {
|
|||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<PlatformIntegrationRepository>(
|
||||
() => PlatformIntegrationRepositoryImpl(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<PlatformIntegrationInfoRepository>(
|
||||
() => getIt<PlatformIntegrationRepository>(),
|
||||
);
|
||||
|
||||
// data sources
|
||||
final MoorDatabase moorDatabase = MoorDatabase();
|
||||
|
@ -101,14 +205,20 @@ Future<void> setupGetIt() async {
|
|||
),
|
||||
);
|
||||
|
||||
final AudioPlayer audioPlayer = AudioPlayerImpl(
|
||||
ja.AudioPlayer(),
|
||||
QueueGenerator(getIt()),
|
||||
final AudioPlayerDataSource audioPlayer = AudioPlayerDataSourceImpl(
|
||||
AudioPlayer(),
|
||||
);
|
||||
getIt.registerLazySingleton<AudioPlayer>(() => audioPlayer);
|
||||
getIt.registerLazySingleton<AudioPlayerDataSource>(() => audioPlayer);
|
||||
|
||||
final PlatformIntegrationDataSource _platformIntegrationDataSource =
|
||||
PlatformIntegrationDataSourceImpl();
|
||||
getIt.registerLazySingleton<PlatformIntegrationDataSource>(
|
||||
() => _platformIntegrationDataSource,
|
||||
);
|
||||
|
||||
// external
|
||||
final _audioHandler = await AudioService.init(
|
||||
builder: () => MyAudioHandler(getIt(), getIt(), getIt()),
|
||||
builder: () => _platformIntegrationDataSource as AudioHandler,
|
||||
config: AudioServiceConfig(
|
||||
androidNotificationChannelName: 'mucke',
|
||||
androidEnableQueue: true,
|
||||
|
@ -116,12 +226,26 @@ Future<void> setupGetIt() async {
|
|||
);
|
||||
getIt.registerLazySingleton<AudioHandler>(() => _audioHandler);
|
||||
|
||||
getIt.registerLazySingleton<QueueGenerator>(() => QueueGenerator(getIt()));
|
||||
|
||||
// external
|
||||
getIt.registerFactory<ja.AudioPlayer>(() => ja.AudioPlayer());
|
||||
getIt.registerFactory<AudioPlayer>(() => AudioPlayer());
|
||||
|
||||
getIt.registerLazySingleton<FlutterAudioQuery>(() => FlutterAudioQuery());
|
||||
|
||||
getIt.registerLazySingleton<DeviceInfoPlugin>(() => DeviceInfoPlugin());
|
||||
|
||||
// actors
|
||||
getIt.registerSingleton<PlatformIntegrationActor>(
|
||||
PlatformIntegrationActor(
|
||||
getIt(),
|
||||
getIt(),
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerSingleton<AudioPlayerActor>(
|
||||
AudioPlayerActor(
|
||||
getIt(),
|
||||
getIt(),
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,49 +4,78 @@ import 'package:mobx/mobx.dart';
|
|||
import '../../domain/entities/album.dart';
|
||||
import '../../domain/entities/artist.dart';
|
||||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../../domain/entities/playback_state.dart';
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../../domain/entities/song.dart';
|
||||
import '../../domain/repositories/audio_repository.dart';
|
||||
import '../../domain/repositories/persistent_player_state_repository.dart';
|
||||
import '../../domain/repositories/audio_player_repository.dart';
|
||||
import '../../domain/usecases/pause.dart';
|
||||
import '../../domain/usecases/play.dart';
|
||||
import '../../domain/usecases/play_songs.dart';
|
||||
import '../../domain/usecases/seek_to_next.dart';
|
||||
import '../../domain/usecases/seek_to_previous.dart';
|
||||
import '../../domain/usecases/set_loop_mode.dart';
|
||||
import '../../domain/usecases/shuffle_all.dart';
|
||||
|
||||
part 'audio_store.g.dart';
|
||||
|
||||
class AudioStore extends _AudioStore with _$AudioStore {
|
||||
AudioStore({
|
||||
@required AudioRepository audioRepository,
|
||||
@required PlayerStateRepository persistentPlayerStateRepository,
|
||||
}) : super(audioRepository, persistentPlayerStateRepository);
|
||||
@required Pause pause,
|
||||
@required Play play,
|
||||
@required PlaySongs playSongs,
|
||||
@required SeekToNext seekToNext,
|
||||
@required SeekToPrevious seekToPrevious,
|
||||
@required SetLoopMode setLoopMode,
|
||||
@required ShuffleAll shuffleAll,
|
||||
@required AudioPlayerInfoRepository audioPlayerInfoRepository,
|
||||
}) : super(playSongs, audioPlayerInfoRepository, pause, play, seekToNext, seekToPrevious,
|
||||
setLoopMode, shuffleAll);
|
||||
}
|
||||
|
||||
abstract class _AudioStore with Store {
|
||||
_AudioStore(this._audioRepository, this._persistentPlayerStateRepository) {
|
||||
currentPositionStream = _audioRepository.currentPositionStream.asObservable(initialValue: 0);
|
||||
_AudioStore(
|
||||
this._playSongs,
|
||||
this._audioPlayerInfoRepository,
|
||||
this._pause,
|
||||
this._play,
|
||||
this._seekToNext,
|
||||
this._seekToPrevious,
|
||||
this._setLoopMode,
|
||||
this._shuffleAll,
|
||||
) {
|
||||
currentPositionStream = _audioPlayerInfoRepository.positionStream
|
||||
.asObservable(initialValue: const Duration(seconds: 0));
|
||||
|
||||
queueStream = _persistentPlayerStateRepository.queueStream.asObservable();
|
||||
queueStream = _audioPlayerInfoRepository.songListStream.asObservable();
|
||||
|
||||
queueIndexStream = _persistentPlayerStateRepository.currentIndexStream.asObservable();
|
||||
queueIndexStream = _audioPlayerInfoRepository.currentIndexStream.asObservable();
|
||||
|
||||
currentSongStream = _persistentPlayerStateRepository.currentSongStream.asObservable();
|
||||
currentSongStream = _audioPlayerInfoRepository.currentSongStream.asObservable();
|
||||
|
||||
shuffleModeStream = _persistentPlayerStateRepository.shuffleModeStream.asObservable();
|
||||
shuffleModeStream = _audioPlayerInfoRepository.shuffleModeStream.asObservable();
|
||||
|
||||
loopModeStream = _persistentPlayerStateRepository.loopModeStream.asObservable();
|
||||
loopModeStream = _audioPlayerInfoRepository.loopModeStream.asObservable();
|
||||
|
||||
playbackStateStream = _audioRepository.playbackStateStream.asObservable();
|
||||
playingStream = _audioPlayerInfoRepository.playingStream.asObservable();
|
||||
}
|
||||
|
||||
final AudioRepository _audioRepository;
|
||||
final PlayerStateRepository _persistentPlayerStateRepository;
|
||||
final AudioPlayerInfoRepository _audioPlayerInfoRepository;
|
||||
|
||||
final Pause _pause;
|
||||
final Play _play;
|
||||
final PlaySongs _playSongs;
|
||||
final SeekToNext _seekToNext;
|
||||
final SeekToPrevious _seekToPrevious;
|
||||
final SetLoopMode _setLoopMode;
|
||||
final ShuffleAll _shuffleAll;
|
||||
|
||||
@observable
|
||||
ObservableStream<Song> currentSongStream;
|
||||
|
||||
@observable
|
||||
ObservableStream<PlaybackState> playbackStateStream;
|
||||
ObservableStream<bool> playingStream;
|
||||
|
||||
@observable
|
||||
ObservableStream<int> currentPositionStream;
|
||||
ObservableStream<Duration> currentPositionStream;
|
||||
|
||||
@observable
|
||||
ObservableStream<List<Song>> queueStream;
|
||||
|
@ -61,58 +90,58 @@ abstract class _AudioStore with Store {
|
|||
ObservableStream<LoopMode> loopModeStream;
|
||||
|
||||
Future<void> playSong(int index, List<Song> songList) async {
|
||||
_audioRepository.playSong(index, songList);
|
||||
_playSongs(songs: songList, initialIndex: index);
|
||||
}
|
||||
|
||||
Future<void> play() async {
|
||||
_audioRepository.play();
|
||||
_play();
|
||||
}
|
||||
|
||||
Future<void> pause() async {
|
||||
_audioRepository.pause();
|
||||
_pause();
|
||||
}
|
||||
|
||||
Future<void> skipToNext() async {
|
||||
_audioRepository.skipToNext();
|
||||
_seekToNext();
|
||||
}
|
||||
|
||||
Future<void> skipToPrevious() async {
|
||||
_audioRepository.skipToPrevious();
|
||||
_seekToPrevious();
|
||||
}
|
||||
|
||||
Future<void> setIndex(int index) async {
|
||||
_audioRepository.setIndex(index);
|
||||
// _audioInterface.setIndex(index);
|
||||
}
|
||||
|
||||
Future<void> setShuffleMode(ShuffleMode shuffleMode) async {
|
||||
_audioRepository.setShuffleMode(shuffleMode);
|
||||
// _audioInterface.setShuffleMode(shuffleMode);
|
||||
}
|
||||
|
||||
Future<void> setLoopMode(LoopMode loopMode) async {
|
||||
_audioRepository.setLoopMode(loopMode);
|
||||
_setLoopMode(loopMode);
|
||||
}
|
||||
|
||||
Future<void> shuffleAll() async {
|
||||
_audioRepository.shuffleAll();
|
||||
_shuffleAll();
|
||||
}
|
||||
|
||||
Future<void> addToQueue(Song song) async {
|
||||
_audioRepository.addToQueue(song);
|
||||
// _audioInterface.addToQueue(song);
|
||||
}
|
||||
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
|
||||
_audioRepository.moveQueueItem(oldIndex, newIndex);
|
||||
// _audioInterface.moveQueueItem(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
Future<void> removeQueueIndex(int index) async {
|
||||
_audioRepository.removeQueueIndex(index);
|
||||
// _audioInterface.removeQueueIndex(index);
|
||||
}
|
||||
|
||||
Future<void> playAlbum(Album album) async {
|
||||
_audioRepository.playAlbum(album);
|
||||
// _audioInterface.playAlbum(album);
|
||||
}
|
||||
|
||||
Future<void> shuffleArtist(Artist artist) async {
|
||||
_audioRepository.playArtist(artist, shuffleMode: ShuffleMode.plus);
|
||||
// _audioInterface.playArtist(artist, shuffleMode: ShuffleMode.plus);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,19 +24,18 @@ mixin _$AudioStore on _AudioStore, Store {
|
|||
});
|
||||
}
|
||||
|
||||
final _$playbackStateStreamAtom =
|
||||
Atom(name: '_AudioStore.playbackStateStream');
|
||||
final _$playingStreamAtom = Atom(name: '_AudioStore.playingStream');
|
||||
|
||||
@override
|
||||
ObservableStream<PlaybackState> get playbackStateStream {
|
||||
_$playbackStateStreamAtom.reportRead();
|
||||
return super.playbackStateStream;
|
||||
ObservableStream<bool> get playingStream {
|
||||
_$playingStreamAtom.reportRead();
|
||||
return super.playingStream;
|
||||
}
|
||||
|
||||
@override
|
||||
set playbackStateStream(ObservableStream<PlaybackState> value) {
|
||||
_$playbackStateStreamAtom.reportWrite(value, super.playbackStateStream, () {
|
||||
super.playbackStateStream = value;
|
||||
set playingStream(ObservableStream<bool> value) {
|
||||
_$playingStreamAtom.reportWrite(value, super.playingStream, () {
|
||||
super.playingStream = value;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -44,13 +43,13 @@ mixin _$AudioStore on _AudioStore, Store {
|
|||
Atom(name: '_AudioStore.currentPositionStream');
|
||||
|
||||
@override
|
||||
ObservableStream<int> get currentPositionStream {
|
||||
ObservableStream<Duration> get currentPositionStream {
|
||||
_$currentPositionStreamAtom.reportRead();
|
||||
return super.currentPositionStream;
|
||||
}
|
||||
|
||||
@override
|
||||
set currentPositionStream(ObservableStream<int> value) {
|
||||
set currentPositionStream(ObservableStream<Duration> value) {
|
||||
_$currentPositionStreamAtom.reportWrite(value, super.currentPositionStream,
|
||||
() {
|
||||
super.currentPositionStream = value;
|
||||
|
@ -121,7 +120,7 @@ mixin _$AudioStore on _AudioStore, Store {
|
|||
String toString() {
|
||||
return '''
|
||||
currentSongStream: ${currentSongStream},
|
||||
playbackStateStream: ${playbackStateStream},
|
||||
playingStream: ${playingStream},
|
||||
currentPositionStream: ${currentPositionStream},
|
||||
queueStream: ${queueStream},
|
||||
queueIndexStream: ${queueIndexStream},
|
||||
|
|
|
@ -7,26 +7,35 @@ import '../../domain/entities/song.dart';
|
|||
import '../../domain/repositories/music_data_modifier_repository.dart';
|
||||
import '../../domain/repositories/music_data_repository.dart';
|
||||
import '../../domain/repositories/settings_repository.dart';
|
||||
import '../../domain/usecases/update_database.dart';
|
||||
|
||||
part 'music_data_store.g.dart';
|
||||
|
||||
class MusicDataStore extends _MusicDataStore with _$MusicDataStore {
|
||||
MusicDataStore({
|
||||
@required MusicDataRepository musicDataRepository,
|
||||
@required SettingsRepository settingsRepository,
|
||||
@required MusicDataInfoRepository musicDataInfoRepository,
|
||||
@required MusicDataModifierRepository musicDataModifierRepository,
|
||||
}) : super(musicDataRepository, settingsRepository, musicDataModifierRepository);
|
||||
@required SettingsRepository settingsRepository,
|
||||
@required UpdateDatabase updateDatabase,
|
||||
}) : super(
|
||||
musicDataInfoRepository,
|
||||
settingsRepository,
|
||||
musicDataModifierRepository,
|
||||
updateDatabase,
|
||||
);
|
||||
}
|
||||
|
||||
abstract class _MusicDataStore with Store {
|
||||
_MusicDataStore(
|
||||
this._musicDataRepository, this._settingsRepository, this._musicDataModifierRepository) {
|
||||
songStream = _musicDataRepository.songStream.asObservable(initialValue: []);
|
||||
albumStream = _musicDataRepository.albumStream.asObservable(initialValue: []);
|
||||
artistStream = _musicDataRepository.artistStream.asObservable(initialValue: []);
|
||||
_MusicDataStore(this._musicDataInfoRepository, this._settingsRepository,
|
||||
this._musicDataModifierRepository, this._updateDatabase) {
|
||||
songStream = _musicDataInfoRepository.songStream.asObservable(initialValue: []);
|
||||
albumStream = _musicDataInfoRepository.albumStream.asObservable(initialValue: []);
|
||||
artistStream = _musicDataInfoRepository.artistStream.asObservable(initialValue: []);
|
||||
}
|
||||
|
||||
final MusicDataRepository _musicDataRepository;
|
||||
final UpdateDatabase _updateDatabase;
|
||||
|
||||
final MusicDataInfoRepository _musicDataInfoRepository;
|
||||
final MusicDataModifierRepository _musicDataModifierRepository;
|
||||
final SettingsRepository _settingsRepository;
|
||||
|
||||
|
@ -49,30 +58,30 @@ abstract class _MusicDataStore with Store {
|
|||
bool isUpdatingDatabase = false;
|
||||
|
||||
@computed
|
||||
List<Album> get sortedArtistAlbums => artistAlbumStream.value.toList()..sort((a, b) {
|
||||
if (b.pubYear == null)
|
||||
return -1;
|
||||
if (a.pubYear == null)
|
||||
return 1;
|
||||
return -a.pubYear.compareTo(b.pubYear);
|
||||
});
|
||||
List<Album> get sortedArtistAlbums => artistAlbumStream.value.toList()
|
||||
..sort((a, b) {
|
||||
if (b.pubYear == null) return -1;
|
||||
if (a.pubYear == null) return 1;
|
||||
return -a.pubYear.compareTo(b.pubYear);
|
||||
});
|
||||
|
||||
@action
|
||||
Future<void> updateDatabase() async {
|
||||
isUpdatingDatabase = true;
|
||||
await _musicDataRepository.updateDatabase();
|
||||
await _updateDatabase();
|
||||
isUpdatingDatabase = false;
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> fetchSongsFromAlbum(Album album) async {
|
||||
albumSongStream = _musicDataRepository.getAlbumSongStream(album).asObservable(initialValue: []);
|
||||
albumSongStream =
|
||||
_musicDataInfoRepository.getAlbumSongStream(album).asObservable(initialValue: []);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> fetchAlbumsFromArtist(Artist artist) async {
|
||||
artistAlbumStream =
|
||||
_musicDataRepository.getArtistAlbumStream(artist).asObservable(initialValue: []);
|
||||
_musicDataInfoRepository.getArtistAlbumStream(artist).asObservable(initialValue: []);
|
||||
}
|
||||
|
||||
Future<void> setSongBlocked(Song song, bool blocked) async {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -18,70 +18,74 @@ class CurrentlyPlayingBar extends StatelessWidget {
|
|||
|
||||
return Observer(
|
||||
builder: (BuildContext context) {
|
||||
final Song song = audioStore.currentSongStream.value;
|
||||
if (song != null) {
|
||||
return Column(
|
||||
verticalDirection: VerticalDirection.up,
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => _onTap(context),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Image(
|
||||
image: getAlbumImage(song.albumArtPath),
|
||||
height: 64.0,
|
||||
),
|
||||
Container(
|
||||
width: 10.0,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
song.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
Text(
|
||||
song.artist,
|
||||
style: const TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: Colors.white70,
|
||||
),
|
||||
)
|
||||
],
|
||||
if (audioStore.currentSongStream != null) {
|
||||
final Song song = audioStore.currentSongStream.value;
|
||||
final Duration position = audioStore.currentPositionStream?.value ?? const Duration(seconds: 0);
|
||||
if (song != null) {
|
||||
return Column(
|
||||
verticalDirection: VerticalDirection.up,
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => _onTap(context),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Image(
|
||||
image: getAlbumImage(song.albumArtPath),
|
||||
height: 64.0,
|
||||
),
|
||||
),
|
||||
const PlayPauseButton(
|
||||
circle: false,
|
||||
),
|
||||
const NextButton(),
|
||||
Container(
|
||||
width: 10.0,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
song.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
Text(
|
||||
song.artist,
|
||||
style: const TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: Colors.white70,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const PlayPauseButton(
|
||||
circle: false,
|
||||
),
|
||||
const NextButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: LinearProgressIndicator(
|
||||
value: position.inMilliseconds / song.duration,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
backgroundColor: Colors.white10,
|
||||
),
|
||||
height: 2,
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(color: Theme.of(context).primaryColor, blurRadius: 1),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: LinearProgressIndicator(
|
||||
value: audioStore.currentPositionStream.value / song.duration,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
backgroundColor: Colors.white10,
|
||||
),
|
||||
height: 2,
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(color: Theme.of(context).primaryColor, blurRadius: 1),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
return Container(
|
||||
height: 0,
|
||||
);
|
||||
}
|
||||
return Container(
|
||||
height: 0,
|
||||
);
|
||||
return Container();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -3,63 +3,59 @@ import 'dart:math';
|
|||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../../domain/entities/playback_event.dart';
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../datasources/music_data_source_contract.dart';
|
||||
import '../datasources/player_state_data_source.dart';
|
||||
import '../models/album_model.dart';
|
||||
import '../models/artist_model.dart';
|
||||
import '../models/playback_event_model.dart';
|
||||
import '../models/queue_item_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
import 'audio_player_contract.dart';
|
||||
import 'stream_constants.dart';
|
||||
|
||||
const String SET_CURRENT_SONG = 'SET_CURRENT_SONG';
|
||||
const String PLAY = 'PLAY';
|
||||
const String PAUSE = 'PAUSE';
|
||||
|
||||
class MyAudioHandler extends BaseAudioHandler {
|
||||
MyAudioHandler(this._musicDataSource, this._audioPlayer, this._playerStateDataSource) {
|
||||
_audioPlayer.queueStream.skip(1).listen((event) {
|
||||
_handleSetQueue(event);
|
||||
});
|
||||
MyAudioHandler(this._musicDataSource, this._playerStateDataSource) {
|
||||
// _audioPlayer.queueStream.skip(1).listen((event) {
|
||||
// _handleSetQueue(event);
|
||||
// });
|
||||
|
||||
_audioPlayer.currentSongStream.listen((songModel) => mediaItem.add(songModel.toMediaItem()));
|
||||
// _audioPlayer.playbackEventStream.listen((event) => _handlePlaybackEvent(event));
|
||||
|
||||
_audioPlayer.playbackEventStream.listen((event) => _handlePlaybackEvent(event));
|
||||
// _audioPlayer.shuffleModeStream.skip(1).listen((shuffleMode) {
|
||||
// _playerStateDataSource.setShuffleMode(shuffleMode);
|
||||
// });
|
||||
|
||||
_audioPlayer.shuffleModeStream.skip(1).listen((shuffleMode) {
|
||||
_playerStateDataSource.setShuffleMode(shuffleMode);
|
||||
});
|
||||
// _audioPlayer.loopModeStream.skip(1).listen((event) {
|
||||
// _playerStateDataSource.setLoopMode(event);
|
||||
// });
|
||||
|
||||
_audioPlayer.loopModeStream.skip(1).listen((event) {
|
||||
_playerStateDataSource.setLoopMode(event);
|
||||
});
|
||||
// _audioPlayer.positionStream.listen((event) {
|
||||
// _handlePosition(event, _audioPlayer.currentSongStream.value);
|
||||
// });
|
||||
|
||||
_audioPlayer.positionStream.listen((event) {
|
||||
_handlePosition(event, _audioPlayer.currentSongStream.value);
|
||||
});
|
||||
|
||||
_initAudioPlayer();
|
||||
// _initAudioPlayer();
|
||||
}
|
||||
|
||||
Future<void> _initAudioPlayer() async {
|
||||
if (_playerStateDataSource.loopModeStream != null) {
|
||||
_audioPlayer.setLoopMode(await _playerStateDataSource.loopModeStream.first);
|
||||
}
|
||||
// Future<void> _initAudioPlayer() async {
|
||||
// if (_playerStateDataSource.loopModeStream != null) {
|
||||
// _audioPlayer.setLoopMode(await _playerStateDataSource.loopModeStream.first);
|
||||
// }
|
||||
|
||||
if (_playerStateDataSource.shuffleModeStream != null) {
|
||||
_audioPlayer.setShuffleMode(await _playerStateDataSource.shuffleModeStream.first, false);
|
||||
}
|
||||
// if (_playerStateDataSource.shuffleModeStream != null) {
|
||||
// _audioPlayer.setShuffleMode(await _playerStateDataSource.shuffleModeStream.first, false);
|
||||
// }
|
||||
|
||||
if (_playerStateDataSource.queueStream != null &&
|
||||
_playerStateDataSource.currentIndexStream != null) {
|
||||
_audioPlayer.loadQueue(
|
||||
queue: await _playerStateDataSource.queueStream.first,
|
||||
initialIndex: await _playerStateDataSource.currentIndexStream.first,
|
||||
);
|
||||
}
|
||||
}
|
||||
// if (_playerStateDataSource.queueStream != null &&
|
||||
// _playerStateDataSource.currentIndexStream != null) {
|
||||
// _audioPlayer.loadQueue(
|
||||
// queue: await _playerStateDataSource.queueStream.first,
|
||||
// initialIndex: await _playerStateDataSource.currentIndexStream.first,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
final AudioPlayer _audioPlayer;
|
||||
// final AudioPlayerDataSource _audioPlayer;
|
||||
final MusicDataSource _musicDataSource;
|
||||
final PlayerStateDataSource _playerStateDataSource;
|
||||
|
||||
|
@ -69,167 +65,72 @@ class MyAudioHandler extends BaseAudioHandler {
|
|||
|
||||
@override
|
||||
Future<void> stop() async {
|
||||
await _audioPlayer.stop();
|
||||
await _audioPlayer.dispose();
|
||||
// await _audioPlayer.stop();
|
||||
// await _audioPlayer.dispose();
|
||||
await super.stop();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> play() async {
|
||||
_audioPlayer.play();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pause() async {
|
||||
await _audioPlayer.pause();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> skipToNext() async {
|
||||
_audioPlayer.seekToNext().then((value) {
|
||||
if (value) {
|
||||
_musicDataSource.incrementSkipCount(_audioPlayer.currentSongStream.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> skipToPrevious() async {
|
||||
_audioPlayer.seekToPrevious();
|
||||
// _audioPlayer.seekToNext().then((value) {
|
||||
// if (value) {
|
||||
// _musicDataSource.incrementSkipCount(_audioPlayer.currentSongStream.value);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> addQueueItem(MediaItem mediaItem) async {
|
||||
_audioPlayer.addToQueue(SongModel.fromMediaItem(mediaItem));
|
||||
// _audioPlayer.addToQueue(SongModel.fromMediaItem(mediaItem));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> removeQueueItemAt(int index) async {
|
||||
_audioPlayer.removeQueueIndex(index);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> customAction(String name, Map<String, dynamic> arguments) async {
|
||||
switch (name) {
|
||||
case PLAY_WITH_CONTEXT:
|
||||
final context = arguments['CONTEXT'] as List<String>;
|
||||
final index = arguments['INDEX'] as int;
|
||||
return playWithContext(context, index);
|
||||
case SET_SHUFFLE_MODE:
|
||||
return setCustomShuffleMode(arguments['SHUFFLE_MODE'] as ShuffleMode);
|
||||
case SET_LOOP_MODE:
|
||||
return setCustomLoopMode(arguments['LOOP_MODE'] as LoopMode);
|
||||
case SHUFFLE_ALL:
|
||||
return shuffleAll();
|
||||
case MOVE_QUEUE_ITEM:
|
||||
return moveQueueItem(arguments['OLD_INDEX'] as int, arguments['NEW_INDEX'] as int);
|
||||
case SET_INDEX:
|
||||
return setIndex(arguments['INDEX'] as int);
|
||||
case PLAY_ALBUM:
|
||||
return playAlbum(arguments['ALBUM'] as AlbumModel);
|
||||
case PLAY_ARTIST:
|
||||
return playArtist(
|
||||
arguments['ARTIST'] as ArtistModel,
|
||||
arguments['SHUFFLE_MODE'] as ShuffleMode,
|
||||
);
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> playWithContext(List<String> context, int index) async {
|
||||
final songs = <SongModel>[];
|
||||
for (final path in context) {
|
||||
final song = await _musicDataSource.getSongByPath(path);
|
||||
songs.add(song);
|
||||
}
|
||||
|
||||
_audioPlayer.playSongList(songs, index);
|
||||
}
|
||||
|
||||
Future<void> setCustomShuffleMode(ShuffleMode mode) async {
|
||||
_audioPlayer.setShuffleMode(mode, true);
|
||||
}
|
||||
|
||||
Future<void> setCustomLoopMode(LoopMode mode) async {
|
||||
_audioPlayer.setLoopMode(mode);
|
||||
}
|
||||
|
||||
Future<void> shuffleAll() async {
|
||||
_audioPlayer.setShuffleMode(ShuffleMode.plus, false);
|
||||
|
||||
final List<SongModel> songs = await _musicDataSource.getSongs();
|
||||
final rng = Random();
|
||||
final index = rng.nextInt(songs.length);
|
||||
|
||||
_audioPlayer.playSongList(songs, index);
|
||||
// _audioPlayer.removeQueueIndex(index);
|
||||
}
|
||||
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
|
||||
_audioPlayer.moveQueueItem(oldIndex, newIndex);
|
||||
// _audioPlayer.moveQueueItem(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
Future<void> setIndex(int index) async {
|
||||
_audioPlayer.setIndex(index);
|
||||
// _audioPlayer.setIndex(index);
|
||||
}
|
||||
|
||||
Future<void> playAlbum(AlbumModel album) async {
|
||||
_audioPlayer.setShuffleMode(ShuffleMode.none, false);
|
||||
// _audioPlayer.setShuffleMode(ShuffleMode.none, false);
|
||||
final List<SongModel> songs = await _musicDataSource.getAlbumSongStream(album).first;
|
||||
|
||||
_audioPlayer.playSongList(songs, 0);
|
||||
// _audioPlayer.playSongList(songs, 0);
|
||||
}
|
||||
|
||||
Future<void> playArtist(ArtistModel artist, ShuffleMode shuffleMode) async {
|
||||
_audioPlayer.setShuffleMode(shuffleMode, false);
|
||||
// _audioPlayer.setShuffleMode(shuffleMode, false);
|
||||
final List<SongModel> songs = await _musicDataSource.getArtistSongStream(artist).first;
|
||||
|
||||
final rng = Random();
|
||||
final index = rng.nextInt(songs.length);
|
||||
|
||||
_audioPlayer.playSongList(songs, index);
|
||||
// _audioPlayer.playSongList(songs, index);
|
||||
}
|
||||
|
||||
void _handleSetQueue(List<QueueItemModel> queueItems) {
|
||||
_playerStateDataSource.setQueue(queueItems);
|
||||
// void _handleSetQueue(List<QueueItemModel> queueItems) {
|
||||
// _playerStateDataSource.setQueue(queueItems);
|
||||
|
||||
final mediaItems = queueItems.map((e) => e.song.toMediaItem()).toList();
|
||||
queue.add(mediaItems);
|
||||
}
|
||||
// final mediaItems = queueItems.map((e) => e.song.toMediaItem()).toList();
|
||||
// queue.add(mediaItems);
|
||||
// }
|
||||
|
||||
void _handlePlaybackEvent(PlaybackEventModel pe) {
|
||||
if (pe.index != null) {
|
||||
_playerStateDataSource.setCurrentIndex(pe.index);
|
||||
}
|
||||
// void _handlePosition(Duration position, SongModel song) {
|
||||
// if (song == null || position == null) return;
|
||||
|
||||
if (pe.processingState == ProcessingState.ready) {
|
||||
if (_audioPlayer.playingStream.value) {
|
||||
playbackState.add(playbackState.value.copyWith(
|
||||
controls: [MediaControl.skipToPrevious, MediaControl.pause, MediaControl.skipToNext],
|
||||
playing: true,
|
||||
processingState: AudioProcessingState.ready,
|
||||
updatePosition: pe.updatePosition,
|
||||
));
|
||||
} else {
|
||||
playbackState.add(playbackState.value.copyWith(
|
||||
controls: [MediaControl.skipToPrevious, MediaControl.play, MediaControl.skipToNext],
|
||||
processingState: AudioProcessingState.ready,
|
||||
updatePosition: pe.updatePosition,
|
||||
playing: false,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
// final int pos = position.inMilliseconds;
|
||||
|
||||
void _handlePosition(Duration position, SongModel song) {
|
||||
if (song == null || position == null) return;
|
||||
|
||||
final int pos = position.inMilliseconds;
|
||||
|
||||
if (pos < song.duration * 0.05) {
|
||||
_countSongPlayback = true;
|
||||
} else if (pos > song.duration * 0.95 && _countSongPlayback) {
|
||||
_countSongPlayback = false;
|
||||
_musicDataSource.incrementPlayCount(song);
|
||||
}
|
||||
}
|
||||
// if (pos < song.duration * 0.05) {
|
||||
// _countSongPlayback = true;
|
||||
// } else if (pos > song.duration * 0.95 && _countSongPlayback) {
|
||||
// _countSongPlayback = false;
|
||||
// _musicDataSource.incrementPlayCount(song);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -1,254 +0,0 @@
|
|||
import 'package:just_audio/just_audio.dart' as ja;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../../domain/entities/queue_item.dart';
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../models/loop_mode_model.dart';
|
||||
import '../models/playback_event_model.dart';
|
||||
import '../models/queue_item_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
import 'audio_player_contract.dart';
|
||||
import 'queue_generator.dart';
|
||||
|
||||
class AudioPlayerImpl implements AudioPlayer {
|
||||
AudioPlayerImpl(this._audioPlayer, this._queueGenerator) {
|
||||
_audioPlayer.currentIndexStream.listen((event) {
|
||||
_log.info('currentIndex: $event');
|
||||
_currentIndexSubject.add(event);
|
||||
if (_queueSubject.value != null && event != null) {
|
||||
_currentSongSubject.add(_queueSubject.value[event].song);
|
||||
}
|
||||
});
|
||||
|
||||
_audioPlayer.playingStream.listen((event) {
|
||||
_log.info('playing: $event');
|
||||
_playingSubject.add(event);
|
||||
});
|
||||
|
||||
_audioPlayer.positionStream.listen((event) {
|
||||
_positionSubject.add(event);
|
||||
});
|
||||
|
||||
_audioPlayer.loopModeStream.listen((event) {
|
||||
_log.info('loopMode: $event');
|
||||
_loopModeSubject.add(event.toEntity());
|
||||
});
|
||||
|
||||
_queueSubject.listen((event) {
|
||||
if (_currentIndexSubject.value != null) {
|
||||
_currentSongSubject.add(event[_currentIndexSubject.value].song);
|
||||
}
|
||||
});
|
||||
|
||||
_playbackEventModelStream = Rx.combineLatest2<ja.PlaybackEvent, bool, PlaybackEventModel>(
|
||||
_audioPlayer.playbackEventStream,
|
||||
_audioPlayer.playingStream,
|
||||
(a, b) => PlaybackEventModel.fromJAPlaybackEvent(a, b),
|
||||
).distinct();
|
||||
}
|
||||
|
||||
final ja.AudioPlayer _audioPlayer;
|
||||
ja.ConcatenatingAudioSource _audioSource;
|
||||
final QueueGenerator _queueGenerator;
|
||||
|
||||
List<SongModel> _inputQueue;
|
||||
List<QueueItemModel> _queue;
|
||||
|
||||
static final _log = Logger('AudioPlayer');
|
||||
|
||||
final BehaviorSubject<int> _currentIndexSubject = BehaviorSubject();
|
||||
final BehaviorSubject<SongModel> _currentSongSubject = BehaviorSubject();
|
||||
final BehaviorSubject<PlaybackEventModel> _playbackEventSubject = BehaviorSubject();
|
||||
final BehaviorSubject<bool> _playingSubject = BehaviorSubject();
|
||||
final BehaviorSubject<Duration> _positionSubject = BehaviorSubject();
|
||||
final BehaviorSubject<List<QueueItemModel>> _queueSubject = BehaviorSubject();
|
||||
final BehaviorSubject<ShuffleMode> _shuffleModeSubject = BehaviorSubject.seeded(ShuffleMode.none);
|
||||
final BehaviorSubject<LoopMode> _loopModeSubject = BehaviorSubject();
|
||||
|
||||
Stream<PlaybackEventModel> _playbackEventModelStream;
|
||||
|
||||
@override
|
||||
ValueStream<int> get currentIndexStream => _currentIndexSubject.stream;
|
||||
|
||||
@override
|
||||
ValueStream<SongModel> get currentSongStream => _currentSongSubject.stream;
|
||||
|
||||
@override
|
||||
Stream<PlaybackEventModel> get playbackEventStream => _playbackEventModelStream;
|
||||
|
||||
@override
|
||||
ValueStream<Duration> get positionStream => _positionSubject.stream;
|
||||
|
||||
@override
|
||||
ValueStream<bool> get playingStream => _playingSubject.stream;
|
||||
|
||||
@override
|
||||
ValueStream<List<QueueItemModel>> get queueStream => _queueSubject.stream;
|
||||
|
||||
@override
|
||||
ValueStream<ShuffleMode> get shuffleModeStream => _shuffleModeSubject.stream;
|
||||
|
||||
@override
|
||||
ValueStream<LoopMode> get loopModeStream => _loopModeSubject.stream;
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
await _currentIndexSubject.close();
|
||||
await _currentSongSubject.close();
|
||||
await _playbackEventSubject.close();
|
||||
await _positionSubject.close();
|
||||
await _queueSubject.close();
|
||||
await _shuffleModeSubject.close();
|
||||
await _loopModeSubject.close();
|
||||
await _audioPlayer.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> loadQueue({List<QueueItemModel> queue, int initialIndex = 0}) async {
|
||||
if (queue == null || initialIndex == null || initialIndex >= queue.length) {
|
||||
return;
|
||||
}
|
||||
_queue = queue;
|
||||
_queueSubject.add(queue);
|
||||
|
||||
// final smallQueue = queue.sublist(max(initialIndex - 10, 0), min(initialIndex + 140, queue.length));
|
||||
|
||||
final songModelQueue = queue.map((e) => e.song).toList();
|
||||
_audioSource = _queueGenerator.songModelsToAudioSource(songModelQueue);
|
||||
_audioPlayer.setAudioSource(_audioSource, initialIndex: initialIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pause() async {
|
||||
await _audioPlayer.pause();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> play() async {
|
||||
_audioPlayer.play();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> playSongList(List<SongModel> songs, int startIndex) async {
|
||||
_inputQueue = songs;
|
||||
|
||||
final firstSong = songs[startIndex];
|
||||
_queueSubject.add([QueueItemModel(firstSong, originalIndex: startIndex)]);
|
||||
_audioSource = _queueGenerator.songModelsToAudioSource([firstSong]);
|
||||
await _audioPlayer.setAudioSource(_audioSource);
|
||||
_audioPlayer.play();
|
||||
|
||||
_queue = await _queueGenerator.generateQueue(_shuffleModeSubject.value, songs, startIndex);
|
||||
final songModelQueue = _queue.map((e) => e.song).toList();
|
||||
_queueSubject.add(_queue);
|
||||
|
||||
final int splitIndex = _shuffleModeSubject.value == ShuffleMode.none ? startIndex : 0;
|
||||
final newQueue = _queueGenerator.songModelsToAudioSource(songModelQueue);
|
||||
await _audioSource.insertAll(0, newQueue.children.sublist(0, splitIndex));
|
||||
_audioSource.addAll(newQueue.children.sublist(splitIndex + 1, newQueue.length));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> seekToNext() async {
|
||||
final result = _audioPlayer.hasNext;
|
||||
await _audioPlayer.seekToNext();
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> seekToPrevious() async {
|
||||
if (_audioPlayer.position > const Duration(seconds: 3) || !_audioPlayer.hasPrevious) {
|
||||
await _audioPlayer.seek(const Duration(seconds: 0));
|
||||
} else {
|
||||
await _audioPlayer.seekToPrevious();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setIndex(int index) async {
|
||||
await _audioPlayer.seek(const Duration(seconds: 0), index: index);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() async {
|
||||
_audioPlayer.stop();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> addToQueue(SongModel song) async {
|
||||
await _audioSource.add(ja.AudioSource.uri(Uri.file(song.path)));
|
||||
_queue.add(QueueItemModel(song, originalIndex: -1, type: QueueItemType.added));
|
||||
_queueSubject.add(_queue);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
|
||||
final QueueItemModel queueItem = _queue.removeAt(oldIndex);
|
||||
_queue.insert(newIndex, queueItem);
|
||||
_queueSubject.add(_queue);
|
||||
await _audioSource.move(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> removeQueueIndex(int index) async {
|
||||
_queue.removeAt(index);
|
||||
_queueSubject.add(_queue);
|
||||
await _audioSource.removeAt(index);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setShuffleMode(ShuffleMode shuffleMode, bool updateQueue) async {
|
||||
_log.info('setShuffleMode: $shuffleMode');
|
||||
if (shuffleMode == null) return;
|
||||
_shuffleModeSubject.add(shuffleMode);
|
||||
|
||||
if (updateQueue) {
|
||||
final QueueItem currentQueueItem = _queue[_currentIndexSubject.value];
|
||||
final int index = currentQueueItem.originalIndex;
|
||||
_queue = await _queueGenerator.generateQueue(shuffleMode, _inputQueue, index);
|
||||
// TODO: maybe refactor _queue to a subject and listen for changes
|
||||
final songModelQueue = _queue.map((e) => e.song).toList();
|
||||
_queueSubject.add(_queue);
|
||||
|
||||
final newQueue = _queueGenerator.songModelsToAudioSource(songModelQueue);
|
||||
_updateQueue(newQueue, currentQueueItem);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setLoopMode(LoopMode loopMode) async {
|
||||
if (loopMode == null) return;
|
||||
await _audioPlayer.setLoopMode(loopMode.toJA());
|
||||
}
|
||||
|
||||
Future<void> _updateQueue(
|
||||
ja.ConcatenatingAudioSource newQueue, QueueItem currentQueueItem) async {
|
||||
final int index = currentQueueItem.originalIndex;
|
||||
|
||||
_audioSource.removeRange(0, _currentIndexSubject.value);
|
||||
_audioSource.removeRange(1, _audioSource.length);
|
||||
|
||||
if (_shuffleModeSubject.value == ShuffleMode.none) {
|
||||
switch (currentQueueItem.type) {
|
||||
case QueueItemType.added:
|
||||
case QueueItemType.standard:
|
||||
await _audioSource.insertAll(0, newQueue.children.sublist(0, index));
|
||||
_audioSource.addAll(newQueue.children.sublist(index + 1));
|
||||
break;
|
||||
case QueueItemType.predecessor:
|
||||
await _audioSource.insertAll(0, newQueue.children.sublist(0, index));
|
||||
_audioSource.addAll(newQueue.children.sublist(index));
|
||||
break;
|
||||
case QueueItemType.successor:
|
||||
await _audioSource.insertAll(0, newQueue.children.sublist(0, index + 1));
|
||||
_audioSource.addAll(newQueue.children.sublist(index + 1));
|
||||
break;
|
||||
}
|
||||
_currentIndexSubject.add(index);
|
||||
} else {
|
||||
_audioSource.addAll(newQueue.children.sublist(1));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
|
||||
import '../../domain/entities/queue_item.dart';
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../datasources/music_data_source_contract.dart';
|
||||
import '../models/queue_item_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
|
||||
class QueueGenerator {
|
||||
QueueGenerator(this._musicDataSource);
|
||||
|
||||
final MusicDataSource _musicDataSource;
|
||||
|
||||
Future<List<QueueItemModel>> generateQueue(
|
||||
ShuffleMode shuffleMode,
|
||||
List<SongModel> songModels,
|
||||
int startIndex,
|
||||
) async {
|
||||
List<QueueItemModel> queue;
|
||||
|
||||
switch (shuffleMode) {
|
||||
case ShuffleMode.none:
|
||||
queue = _generateNormalQueue(songModels);
|
||||
break;
|
||||
case ShuffleMode.standard:
|
||||
queue = _generateShuffleQueue(songModels, startIndex);
|
||||
break;
|
||||
case ShuffleMode.plus:
|
||||
queue = await _generateShufflePlusQueue(songModels, startIndex);
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
ConcatenatingAudioSource mediaItemsToAudioSource(List<MediaItem> mediaItems) {
|
||||
return ConcatenatingAudioSource(
|
||||
children: mediaItems.map((MediaItem m) => AudioSource.uri(Uri.file(m.id))).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
ConcatenatingAudioSource songModelsToAudioSource(List<SongModel> songModels) {
|
||||
return ConcatenatingAudioSource(
|
||||
children: songModels.map((SongModel m) => AudioSource.uri(Uri.file(m.path))).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
List<QueueItemModel> _generateNormalQueue(List<SongModel> songs) {
|
||||
return List<QueueItemModel>.generate(
|
||||
songs.length,
|
||||
(i) => QueueItemModel(
|
||||
songs[i],
|
||||
originalIndex: i,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<QueueItemModel> _generateShuffleQueue(
|
||||
List<SongModel> songs,
|
||||
int startIndex,
|
||||
) {
|
||||
final List<QueueItemModel> queue = List<QueueItemModel>.generate(
|
||||
songs.length,
|
||||
(i) => QueueItemModel(
|
||||
songs[i],
|
||||
originalIndex: i,
|
||||
),
|
||||
);
|
||||
queue.removeAt(startIndex);
|
||||
queue.shuffle();
|
||||
final first = QueueItemModel(
|
||||
songs[startIndex],
|
||||
originalIndex: startIndex,
|
||||
);
|
||||
return [first] + queue;
|
||||
}
|
||||
|
||||
Future<List<QueueItemModel>> _generateShufflePlusQueue(
|
||||
List<SongModel> songs,
|
||||
int startIndex,
|
||||
) async {
|
||||
final List<QueueItemModel> queue = await _getQueueItemWithLinks(
|
||||
songs[startIndex],
|
||||
startIndex,
|
||||
);
|
||||
final List<int> indices = [];
|
||||
|
||||
// filter mediaitem list
|
||||
// TODO: multiply higher rated songs
|
||||
for (var i = 0; i < songs.length; i++) {
|
||||
if (i != startIndex && !songs[i].blocked) {
|
||||
indices.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
indices.shuffle();
|
||||
|
||||
for (var i = 0; i < indices.length; i++) {
|
||||
final int index = indices[i];
|
||||
final SongModel song = songs[index];
|
||||
|
||||
queue.addAll(await _getQueueItemWithLinks(song, index));
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
// TODO: naming things is hard
|
||||
Future<List<QueueItemModel>> _getQueueItemWithLinks(
|
||||
SongModel song,
|
||||
int index,
|
||||
) async {
|
||||
final List<QueueItemModel> queueItems = [];
|
||||
|
||||
final predecessors = await _getPredecessors(song);
|
||||
final successors = await _getSuccessors(song);
|
||||
|
||||
for (final p in predecessors) {
|
||||
queueItems.add(QueueItemModel(
|
||||
p,
|
||||
originalIndex: index,
|
||||
type: QueueItemType.predecessor,
|
||||
));
|
||||
}
|
||||
|
||||
queueItems.add(QueueItemModel(
|
||||
song,
|
||||
originalIndex: index,
|
||||
));
|
||||
|
||||
for (final p in successors) {
|
||||
queueItems.add(QueueItemModel(
|
||||
p,
|
||||
originalIndex: index,
|
||||
type: QueueItemType.successor,
|
||||
));
|
||||
}
|
||||
|
||||
return queueItems;
|
||||
}
|
||||
|
||||
Future<List<SongModel>> _getPredecessors(SongModel song) async {
|
||||
final List<SongModel> songs = [];
|
||||
SongModel currentSong = song;
|
||||
|
||||
while (currentSong.previous != null) {
|
||||
currentSong = await _musicDataSource.getSongByPath(currentSong.previous);
|
||||
songs.add(currentSong);
|
||||
}
|
||||
|
||||
return songs.reversed.toList();
|
||||
}
|
||||
|
||||
Future<List<SongModel>> _getSuccessors(SongModel song) async {
|
||||
final List<SongModel> songs = [];
|
||||
SongModel currentSong = song;
|
||||
|
||||
while (currentSong.next != null) {
|
||||
currentSong = await _musicDataSource.getSongByPath(currentSong.next);
|
||||
songs.add(currentSong);
|
||||
}
|
||||
|
||||
return songs.toList();
|
||||
}
|
||||
}
|
|
@ -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';
|
|
@ -1,20 +1,15 @@
|
|||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../models/playback_event_model.dart';
|
||||
import '../models/queue_item_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
|
||||
abstract class AudioPlayer {
|
||||
abstract class AudioPlayerDataSource {
|
||||
ValueStream<int> get currentIndexStream;
|
||||
ValueStream<SongModel> get currentSongStream;
|
||||
Stream<PlaybackEventModel> get playbackEventStream;
|
||||
ValueStream<bool> get playingStream;
|
||||
ValueStream<Duration> get positionStream;
|
||||
ValueStream<List<QueueItemModel>> get queueStream;
|
||||
ValueStream<ShuffleMode> get shuffleModeStream;
|
||||
ValueStream<LoopMode> get loopModeStream;
|
||||
|
||||
Future<void> play();
|
||||
Future<void> pause();
|
||||
|
@ -23,14 +18,12 @@ abstract class AudioPlayer {
|
|||
Future<void> seekToPrevious();
|
||||
Future<void> dispose();
|
||||
|
||||
Future<void> loadQueue({List<QueueItemModel> queue, int initialIndex});
|
||||
Future<void> loadQueue({List<SongModel> queue, int initialIndex});
|
||||
Future<void> addToQueue(SongModel song);
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex);
|
||||
Future<void> removeQueueIndex(int index);
|
||||
Future<void> setIndex(int index);
|
||||
|
||||
|
||||
Future<void> setShuffleMode(ShuffleMode shuffleMode, bool updateQueue);
|
||||
Future<void> setLoopMode(LoopMode loopMode);
|
||||
|
||||
Future<void> playSongList(List<SongModel> songs, int startIndex);
|
234
lib/system/datasources/audio_player_data_source_impl.dart
Normal file
234
lib/system/datasources/audio_player_data_source_impl.dart
Normal file
|
@ -0,0 +1,234 @@
|
|||
import 'package:just_audio/just_audio.dart' as ja;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../models/loop_mode_model.dart';
|
||||
import '../models/playback_event_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
import 'audio_player_data_source.dart';
|
||||
|
||||
class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
|
||||
AudioPlayerDataSourceImpl(this._audioPlayer) {
|
||||
_audioPlayer.currentIndexStream.listen((event) {
|
||||
_log.info('currentIndex: $event');
|
||||
_currentIndexSubject.add(event);
|
||||
if (_queue != null && event != null && event < _queue.length) {
|
||||
_currentSongSubject.add(_queue[event]);
|
||||
}
|
||||
});
|
||||
|
||||
_audioPlayer.playingStream.listen((event) {
|
||||
_log.info('playing: $event');
|
||||
_playingSubject.add(event);
|
||||
});
|
||||
|
||||
_audioPlayer.positionStream.listen((event) {
|
||||
_positionSubject.add(event);
|
||||
});
|
||||
|
||||
_playbackEventModelStream = Rx.combineLatest2<ja.PlaybackEvent, bool, PlaybackEventModel>(
|
||||
_audioPlayer.playbackEventStream,
|
||||
_audioPlayer.playingStream,
|
||||
(a, b) => PlaybackEventModel.fromJAPlaybackEvent(a, b),
|
||||
).distinct();
|
||||
}
|
||||
|
||||
final ja.AudioPlayer _audioPlayer;
|
||||
ja.ConcatenatingAudioSource _audioSource;
|
||||
|
||||
static final _log = Logger('AudioPlayer');
|
||||
|
||||
final BehaviorSubject<int> _currentIndexSubject = BehaviorSubject();
|
||||
final BehaviorSubject<SongModel> _currentSongSubject = BehaviorSubject();
|
||||
final BehaviorSubject<PlaybackEventModel> _playbackEventSubject = BehaviorSubject();
|
||||
final BehaviorSubject<bool> _playingSubject = BehaviorSubject();
|
||||
final BehaviorSubject<Duration> _positionSubject = BehaviorSubject();
|
||||
|
||||
Stream<PlaybackEventModel> _playbackEventModelStream;
|
||||
List<SongModel> _queue;
|
||||
|
||||
void _setQueue(List<SongModel> queue, {int index}) {
|
||||
_queue = queue;
|
||||
if (index != null) {
|
||||
_currentSongSubject.add(_queue[index]);
|
||||
} else if (_currentIndexSubject.value != null) {
|
||||
_currentSongSubject.add(_queue[_currentIndexSubject.value]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
ValueStream<int> get currentIndexStream => _currentIndexSubject.stream;
|
||||
|
||||
@override
|
||||
ValueStream<SongModel> get currentSongStream => _currentSongSubject.stream;
|
||||
|
||||
@override
|
||||
Stream<PlaybackEventModel> get playbackEventStream => _playbackEventModelStream;
|
||||
|
||||
@override
|
||||
ValueStream<Duration> get positionStream => _positionSubject.stream;
|
||||
|
||||
@override
|
||||
ValueStream<bool> get playingStream => _playingSubject.stream;
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
await _currentIndexSubject.close();
|
||||
await _currentSongSubject.close();
|
||||
await _playbackEventSubject.close();
|
||||
await _positionSubject.close();
|
||||
await _audioPlayer.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> loadQueue({List<SongModel> queue, int initialIndex = 0}) async {
|
||||
if (queue == null || initialIndex == null || initialIndex >= queue.length) {
|
||||
return;
|
||||
}
|
||||
_setQueue(queue, index: initialIndex);
|
||||
|
||||
// final smallQueue = queue.sublist(max(initialIndex - 10, 0), min(initialIndex + 140, queue.length));
|
||||
|
||||
_audioSource = _songModelsToAudioSource(queue);
|
||||
_audioPlayer.setAudioSource(_audioSource, initialIndex: initialIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pause() async {
|
||||
await _audioPlayer.pause();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> play() async {
|
||||
_audioPlayer.play();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> playSongList(List<SongModel> songs, int startIndex) async {
|
||||
// _inputQueue = songs;
|
||||
|
||||
// final firstSong = songs[startIndex];
|
||||
// _queueSubject.add([QueueItemModel(firstSong, originalIndex: startIndex)]);
|
||||
// _audioSource = _songModelsToAudioSource([firstSong]);
|
||||
// await _audioPlayer.setAudioSource(_audioSource);
|
||||
// _audioPlayer.play();
|
||||
|
||||
// _queue = await _queueGenerator.generateQueue(_shuffleModeSubject.value, songs, startIndex);
|
||||
// final songModelQueue = _queue.map((e) => e.song).toList();
|
||||
// _queueSubject.add(_queue);
|
||||
|
||||
// final int splitIndex = _shuffleModeSubject.value == ShuffleMode.none ? startIndex : 0;
|
||||
// final newQueue = _songModelsToAudioSource(songModelQueue);
|
||||
// await _audioSource.insertAll(0, newQueue.children.sublist(0, splitIndex));
|
||||
// _audioSource.addAll(newQueue.children.sublist(splitIndex + 1, newQueue.length));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> seekToNext() async {
|
||||
final result = _audioPlayer.hasNext;
|
||||
await _audioPlayer.seekToNext();
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> seekToPrevious() async {
|
||||
if (_audioPlayer.position > const Duration(seconds: 3) || !_audioPlayer.hasPrevious) {
|
||||
await _audioPlayer.seek(const Duration(seconds: 0));
|
||||
} else {
|
||||
await _audioPlayer.seekToPrevious();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setIndex(int index) async {
|
||||
await _audioPlayer.seek(const Duration(seconds: 0), index: index);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() async {
|
||||
_audioPlayer.stop();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> addToQueue(SongModel song) async {
|
||||
await _audioSource.add(ja.AudioSource.uri(Uri.file(song.path)));
|
||||
// _queue.add(QueueItemModel(song, originalIndex: -1, type: QueueItemType.added));
|
||||
// _queueSubject.add(_queue);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
|
||||
// final QueueItemModel queueItem = _queue.removeAt(oldIndex);
|
||||
// _queue.insert(newIndex, queueItem);
|
||||
// _queueSubject.add(_queue);
|
||||
await _audioSource.move(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> removeQueueIndex(int index) async {
|
||||
// _queue.removeAt(index);
|
||||
// _queueSubject.add(_queue);
|
||||
await _audioSource.removeAt(index);
|
||||
}
|
||||
|
||||
// @override
|
||||
// Future<void> setShuffleMode(ShuffleMode shuffleMode, bool updateQueue) async {
|
||||
// _log.info('setShuffleMode: $shuffleMode');
|
||||
// if (shuffleMode == null) return;
|
||||
// _shuffleModeSubject.add(shuffleMode);
|
||||
|
||||
// if (updateQueue) {
|
||||
// final QueueItem currentQueueItem = _queue[_currentIndexSubject.value];
|
||||
// final int index = currentQueueItem.originalIndex;
|
||||
// _queue = await _queueGenerator.generateQueue(shuffleMode, _inputQueue, index);
|
||||
// // TODO: maybe refactor _queue to a subject and listen for changes
|
||||
// final songModelQueue = _queue.map((e) => e.song).toList();
|
||||
// _queueSubject.add(_queue);
|
||||
|
||||
// final newQueue = _songModelsToAudioSource(songModelQueue);
|
||||
// _updateQueue(newQueue, currentQueueItem);
|
||||
// }
|
||||
// }
|
||||
|
||||
@override
|
||||
Future<void> setLoopMode(LoopMode loopMode) async {
|
||||
if (loopMode == null) return;
|
||||
await _audioPlayer.setLoopMode(loopMode.toJA());
|
||||
}
|
||||
|
||||
ja.ConcatenatingAudioSource _songModelsToAudioSource(List<SongModel> songModels) {
|
||||
return ja.ConcatenatingAudioSource(
|
||||
children: songModels.map((SongModel m) => ja.AudioSource.uri(Uri.file(m.path))).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
// Future<void> _updateQueue(
|
||||
// ja.ConcatenatingAudioSource newQueue, QueueItem currentQueueItem) async {
|
||||
// final int index = currentQueueItem.originalIndex;
|
||||
|
||||
// _audioSource.removeRange(0, _currentIndexSubject.value);
|
||||
// _audioSource.removeRange(1, _audioSource.length);
|
||||
|
||||
// if (_shuffleModeSubject.value == ShuffleMode.none) {
|
||||
// switch (currentQueueItem.type) {
|
||||
// case QueueItemType.added:
|
||||
// case QueueItemType.standard:
|
||||
// await _audioSource.insertAll(0, newQueue.children.sublist(0, index));
|
||||
// _audioSource.addAll(newQueue.children.sublist(index + 1));
|
||||
// break;
|
||||
// case QueueItemType.predecessor:
|
||||
// await _audioSource.insertAll(0, newQueue.children.sublist(0, index));
|
||||
// _audioSource.addAll(newQueue.children.sublist(index));
|
||||
// break;
|
||||
// case QueueItemType.successor:
|
||||
// await _audioSource.insertAll(0, newQueue.children.sublist(0, index + 1));
|
||||
// _audioSource.addAll(newQueue.children.sublist(index + 1));
|
||||
// break;
|
||||
// }
|
||||
// _currentIndexSubject.add(index);
|
||||
// } else {
|
||||
// _audioSource.addAll(newQueue.children.sublist(1));
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -1,109 +1,14 @@
|
|||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:device_info/device_info.dart';
|
||||
import 'package:flutter_audio_query/flutter_audio_query.dart';
|
||||
|
||||
import '../models/album_model.dart';
|
||||
import '../models/artist_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
import 'local_music_fetcher_contract.dart';
|
||||
import 'settings_data_source.dart';
|
||||
|
||||
class LocalMusicFetcherImpl implements LocalMusicFetcher {
|
||||
LocalMusicFetcherImpl(this._flutterAudioQuery, this._settingsDataSource, this._deviceInfo);
|
||||
abstract class LocalMusicFetcher {
|
||||
Future<Map<String, List>> getLocalMusic();
|
||||
|
||||
final FlutterAudioQuery _flutterAudioQuery;
|
||||
final SettingsDataSource _settingsDataSource;
|
||||
// CODESMELL: should probably encapsulate the deviceinfoplugin
|
||||
final DeviceInfoPlugin _deviceInfo;
|
||||
|
||||
AndroidDeviceInfo _androidDeviceInfo;
|
||||
Future<AndroidDeviceInfo> get androidDeviceInfo async {
|
||||
_androidDeviceInfo ??= await _deviceInfo.androidInfo;
|
||||
return _androidDeviceInfo;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ArtistModel>> getArtists() async {
|
||||
final List<ArtistInfo> artistInfoList = await _flutterAudioQuery.getArtists();
|
||||
return artistInfoList
|
||||
.map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo))
|
||||
.toSet()
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<AlbumModel>> getAlbums() async {
|
||||
final List<AlbumInfo> albumInfoList = await _flutterAudioQuery.getAlbums();
|
||||
return albumInfoList.map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<SongModel>> getSongs() async {
|
||||
final List<SongInfo> songInfoList = await _flutterAudioQuery.getSongs();
|
||||
return songInfoList
|
||||
.where((songInfo) => songInfo.isMusic)
|
||||
.map((SongInfo songInfo) => SongModel.fromSongInfo(songInfo))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> getAlbumArtwork(int id) async {
|
||||
final info = await androidDeviceInfo;
|
||||
if (info.version.sdkInt >= 29) {
|
||||
return _flutterAudioQuery.getArtwork(
|
||||
type: ResourceType.ALBUM,
|
||||
id: id.toString(),
|
||||
size: const Size(480.0, 480.0),
|
||||
);
|
||||
}
|
||||
return Uint8List(0);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, List>> getLocalMusic() async {
|
||||
final musicDirectories = await _settingsDataSource.getLibraryFolders();
|
||||
|
||||
final songs = await _getFilteredSongs(musicDirectories);
|
||||
final albumTitles = Set<String>.from(songs.map((song) => song.album));
|
||||
final albums = await _getFilteredAlbums(albumTitles);
|
||||
final artistNames = Set<String>.from(albums.map((album) => album.artist));
|
||||
final artists = await _getFilteredArtists(artistNames);
|
||||
|
||||
return {
|
||||
'SONGS': songs,
|
||||
'ALBUMS': albums,
|
||||
'ARTISTS': artists,
|
||||
};
|
||||
}
|
||||
|
||||
Future<List<SongModel>> _getFilteredSongs(Iterable<String> musicDirectories) async {
|
||||
final List<SongInfo> songInfoList = await _flutterAudioQuery.getSongs();
|
||||
return songInfoList
|
||||
.where(
|
||||
(songInfo) =>
|
||||
songInfo.isMusic &&
|
||||
musicDirectories.any((element) => songInfo.filePath.startsWith(element)),
|
||||
)
|
||||
.map((SongInfo songInfo) => SongModel.fromSongInfo(songInfo))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<List<AlbumModel>> _getFilteredAlbums(Iterable<String> albumTitles) async {
|
||||
final List<AlbumInfo> albumInfoList = await _flutterAudioQuery.getAlbums();
|
||||
return albumInfoList
|
||||
.where((albumInfo) => albumTitles.contains(albumInfo.title))
|
||||
.map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<List<ArtistModel>> _getFilteredArtists(Iterable<String> artistNames) async {
|
||||
final List<ArtistInfo> artistInfoList = await _flutterAudioQuery.getArtists();
|
||||
return artistInfoList
|
||||
.where((artistInfo) => artistNames.contains(artistInfo.name))
|
||||
.map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo))
|
||||
.toSet()
|
||||
.toList();
|
||||
}
|
||||
Future<List<ArtistModel>> getArtists();
|
||||
Future<List<AlbumModel>> getAlbums();
|
||||
Future<List<SongModel>> getSongs();
|
||||
Future<Uint8List> getAlbumArtwork(int id);
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import '../models/album_model.dart';
|
||||
import '../models/artist_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
|
||||
abstract class LocalMusicFetcher {
|
||||
Future<Map<String, List>> getLocalMusic();
|
||||
|
||||
Future<List<ArtistModel>> getArtists();
|
||||
Future<List<AlbumModel>> getAlbums();
|
||||
Future<List<SongModel>> getSongs();
|
||||
Future<Uint8List> getAlbumArtwork(int id);
|
||||
}
|
109
lib/system/datasources/local_music_fetcher_impl.dart
Normal file
109
lib/system/datasources/local_music_fetcher_impl.dart
Normal file
|
@ -0,0 +1,109 @@
|
|||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:device_info/device_info.dart';
|
||||
import 'package:flutter_audio_query/flutter_audio_query.dart';
|
||||
|
||||
import '../models/album_model.dart';
|
||||
import '../models/artist_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
import 'local_music_fetcher.dart';
|
||||
import 'settings_data_source.dart';
|
||||
|
||||
class LocalMusicFetcherImpl implements LocalMusicFetcher {
|
||||
LocalMusicFetcherImpl(this._flutterAudioQuery, this._settingsDataSource, this._deviceInfo);
|
||||
|
||||
final FlutterAudioQuery _flutterAudioQuery;
|
||||
final SettingsDataSource _settingsDataSource;
|
||||
// CODESMELL: should probably encapsulate the deviceinfoplugin
|
||||
final DeviceInfoPlugin _deviceInfo;
|
||||
|
||||
AndroidDeviceInfo _androidDeviceInfo;
|
||||
Future<AndroidDeviceInfo> get androidDeviceInfo async {
|
||||
_androidDeviceInfo ??= await _deviceInfo.androidInfo;
|
||||
return _androidDeviceInfo;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ArtistModel>> getArtists() async {
|
||||
final List<ArtistInfo> artistInfoList = await _flutterAudioQuery.getArtists();
|
||||
return artistInfoList
|
||||
.map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo))
|
||||
.toSet()
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<AlbumModel>> getAlbums() async {
|
||||
final List<AlbumInfo> albumInfoList = await _flutterAudioQuery.getAlbums();
|
||||
return albumInfoList.map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<SongModel>> getSongs() async {
|
||||
final List<SongInfo> songInfoList = await _flutterAudioQuery.getSongs();
|
||||
return songInfoList
|
||||
.where((songInfo) => songInfo.isMusic)
|
||||
.map((SongInfo songInfo) => SongModel.fromSongInfo(songInfo))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> getAlbumArtwork(int id) async {
|
||||
final info = await androidDeviceInfo;
|
||||
if (info.version.sdkInt >= 29) {
|
||||
return _flutterAudioQuery.getArtwork(
|
||||
type: ResourceType.ALBUM,
|
||||
id: id.toString(),
|
||||
size: const Size(480.0, 480.0),
|
||||
);
|
||||
}
|
||||
return Uint8List(0);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, List>> getLocalMusic() async {
|
||||
final musicDirectories = await _settingsDataSource.getLibraryFolders();
|
||||
|
||||
final songs = await _getFilteredSongs(musicDirectories);
|
||||
final albumTitles = Set<String>.from(songs.map((song) => song.album));
|
||||
final albums = await _getFilteredAlbums(albumTitles);
|
||||
final artistNames = Set<String>.from(albums.map((album) => album.artist));
|
||||
final artists = await _getFilteredArtists(artistNames);
|
||||
|
||||
return {
|
||||
'SONGS': songs,
|
||||
'ALBUMS': albums,
|
||||
'ARTISTS': artists,
|
||||
};
|
||||
}
|
||||
|
||||
Future<List<SongModel>> _getFilteredSongs(Iterable<String> musicDirectories) async {
|
||||
final List<SongInfo> songInfoList = await _flutterAudioQuery.getSongs();
|
||||
return songInfoList
|
||||
.where(
|
||||
(songInfo) =>
|
||||
songInfo.isMusic &&
|
||||
musicDirectories.any((element) => songInfo.filePath.startsWith(element)),
|
||||
)
|
||||
.map((SongInfo songInfo) => SongModel.fromSongInfo(songInfo))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<List<AlbumModel>> _getFilteredAlbums(Iterable<String> albumTitles) async {
|
||||
final List<AlbumInfo> albumInfoList = await _flutterAudioQuery.getAlbums();
|
||||
return albumInfoList
|
||||
.where((albumInfo) => albumTitles.contains(albumInfo.title))
|
||||
.map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<List<ArtistModel>> _getFilteredArtists(Iterable<String> artistNames) async {
|
||||
final List<ArtistInfo> artistInfoList = await _flutterAudioQuery.getArtists();
|
||||
return artistInfoList
|
||||
.where((artistInfo) => artistNames.contains(artistInfo.name))
|
||||
.map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo))
|
||||
.toSet()
|
||||
.toList();
|
||||
}
|
||||
}
|
12
lib/system/datasources/platform_integration_data_source.dart
Normal file
12
lib/system/datasources/platform_integration_data_source.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import '../../domain/repositories/platform_integration_repository.dart';
|
||||
import '../models/playback_event_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
|
||||
abstract class PlatformIntegrationDataSource {
|
||||
Stream<PlatformIntegrationEvent> get eventStream;
|
||||
|
||||
Future<void> handlePlaybackEvent(PlaybackEventModel playbackEventModel);
|
||||
Future<void> onPause();
|
||||
Future<void> onPlay();
|
||||
Future<void> setCurrentSong(SongModel songModel);
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
import '../../domain/entities/playback_event.dart';
|
||||
import '../../domain/repositories/platform_integration_repository.dart';
|
||||
import '../models/playback_event_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
import 'platform_integration_data_source.dart';
|
||||
|
||||
class PlatformIntegrationDataSourceImpl extends BaseAudioHandler
|
||||
implements PlatformIntegrationDataSource {
|
||||
PlatformIntegrationDataSourceImpl();
|
||||
|
||||
final BehaviorSubject<PlatformIntegrationEvent> _eventSubject = BehaviorSubject();
|
||||
|
||||
// BaseAudioHandler interface
|
||||
|
||||
@override
|
||||
Future<void> play() async {
|
||||
_eventSubject.add(PlatformIntegrationEvent(type: PlatformIntegrationEventType.play));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pause() async {
|
||||
_eventSubject.add(PlatformIntegrationEvent(type: PlatformIntegrationEventType.pause));
|
||||
}
|
||||
|
||||
// PlatformIntegrationDataSource interface
|
||||
|
||||
@override
|
||||
Stream<PlatformIntegrationEvent> get eventStream => _eventSubject.stream;
|
||||
|
||||
@override
|
||||
Future<void> handlePlaybackEvent(PlaybackEventModel pe) async {
|
||||
if (pe.processingState == ProcessingState.ready) {
|
||||
final timeDelta = DateTime.now().difference(pe.updateTime);
|
||||
print(timeDelta);
|
||||
if (pe.playing) {
|
||||
playbackState.add(playbackState.value.copyWith(
|
||||
controls: [MediaControl.skipToPrevious, MediaControl.pause, MediaControl.skipToNext],
|
||||
playing: true,
|
||||
processingState: AudioProcessingState.ready,
|
||||
updatePosition: pe.updatePosition + timeDelta,
|
||||
));
|
||||
} else {
|
||||
playbackState.add(playbackState.value.copyWith(
|
||||
controls: [MediaControl.skipToPrevious, MediaControl.play, MediaControl.skipToNext],
|
||||
processingState: AudioProcessingState.ready,
|
||||
updatePosition: pe.updatePosition + timeDelta,
|
||||
playing: false,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPlay() async {
|
||||
playbackState.add(playbackState.value.copyWith(
|
||||
controls: [MediaControl.skipToPrevious, MediaControl.pause, MediaControl.skipToNext],
|
||||
playing: true,
|
||||
processingState: AudioProcessingState.ready,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPause() async {
|
||||
playbackState.add(playbackState.value.copyWith(
|
||||
controls: [MediaControl.skipToPrevious, MediaControl.play, MediaControl.skipToNext],
|
||||
processingState: AudioProcessingState.ready,
|
||||
playing: false,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setCurrentSong(SongModel songModel) async {
|
||||
mediaItem.add(songModel.toMediaItem());
|
||||
}
|
||||
}
|
144
lib/system/repositories/audio_player_repository_impl.dart
Normal file
144
lib/system/repositories/audio_player_repository_impl.dart
Normal file
|
@ -0,0 +1,144 @@
|
|||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../../domain/entities/playback_event.dart';
|
||||
import '../../domain/entities/queue_item.dart';
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../../domain/entities/song.dart';
|
||||
import '../../domain/repositories/audio_player_repository.dart';
|
||||
import '../datasources/audio_player_data_source.dart';
|
||||
import '../models/song_model.dart';
|
||||
|
||||
class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
|
||||
AudioPlayerRepositoryImpl(this._audioPlayerDataSource) {
|
||||
_shuffleModeSubject.add(ShuffleMode.none);
|
||||
_loopModeSubject.add(LoopMode.off);
|
||||
}
|
||||
|
||||
final AudioPlayerDataSource _audioPlayerDataSource;
|
||||
|
||||
// final BehaviorSubject<int> _currentIndexSubject = BehaviorSubject();
|
||||
// final BehaviorSubject<Song> _currentSongSubject = BehaviorSubject<Song>();
|
||||
final BehaviorSubject<LoopMode> _loopModeSubject = BehaviorSubject();
|
||||
final BehaviorSubject<List<Song>> _songListSubject = BehaviorSubject();
|
||||
final BehaviorSubject<List<QueueItem>> _queueSubject = BehaviorSubject();
|
||||
final BehaviorSubject<ShuffleMode> _shuffleModeSubject = BehaviorSubject();
|
||||
|
||||
@override
|
||||
Stream<AudioPlayerEvent> eventStream;
|
||||
|
||||
@override
|
||||
ValueStream<ShuffleMode> get shuffleModeStream => _shuffleModeSubject.stream;
|
||||
|
||||
@override
|
||||
ValueStream<LoopMode> get loopModeStream => _loopModeSubject.stream;
|
||||
|
||||
@override
|
||||
ValueStream<List<Song>> get songListStream => _songListSubject.stream;
|
||||
|
||||
@override
|
||||
ValueStream<List<QueueItem>> get queueStream => _queueSubject.stream;
|
||||
|
||||
@override
|
||||
ValueStream<int> get currentIndexStream => _audioPlayerDataSource.currentIndexStream;
|
||||
|
||||
@override
|
||||
Stream<Song> get currentSongStream => _audioPlayerDataSource.currentSongStream;
|
||||
|
||||
@override
|
||||
Stream<PlaybackEvent> get playbackEventStream => _audioPlayerDataSource.playbackEventStream;
|
||||
|
||||
@override
|
||||
Stream<bool> get playingStream => _audioPlayerDataSource.playingStream;
|
||||
|
||||
@override
|
||||
Stream<Duration> get positionStream => _audioPlayerDataSource.positionStream;
|
||||
|
||||
@override
|
||||
Future<void> addToQueue(Song song) {
|
||||
// TODO: implement addToQueue
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_audioPlayerDataSource.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> loadQueue({List<QueueItem> queue, int initialIndex}) async {
|
||||
// _currentSongSubject.add(queue[initialIndex]);
|
||||
_queueSubject.add(queue);
|
||||
_songListSubject.add(queue.map((e) => e.song).toList());
|
||||
// _currentIndexSubject.add(initialIndex);
|
||||
|
||||
await _audioPlayerDataSource.loadQueue(
|
||||
initialIndex: initialIndex,
|
||||
queue: queue.map((e) => e as SongModel).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex) {
|
||||
// TODO: implement moveQueueItem
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pause() async {
|
||||
_audioPlayerDataSource.pause();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> play() async {
|
||||
_audioPlayerDataSource.play();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> playSong(Song song) async {
|
||||
await _audioPlayerDataSource.loadQueue(
|
||||
initialIndex: 0,
|
||||
queue: [song as SongModel],
|
||||
);
|
||||
_audioPlayerDataSource.play();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> removeQueueIndex(int index) {
|
||||
// TODO: implement removeQueueIndex
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> seekToNext() async {
|
||||
return await _audioPlayerDataSource.seekToNext();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> seekToPrevious() async {
|
||||
await _audioPlayerDataSource.seekToPrevious();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setIndex(int index) {
|
||||
// TODO: implement setIndex
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setLoopMode(LoopMode loopMode) async {
|
||||
_loopModeSubject.add(loopMode);
|
||||
await _audioPlayerDataSource.setLoopMode(loopMode);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setShuffleMode(ShuffleMode shuffleMode) async {
|
||||
_shuffleModeSubject.add(shuffleMode);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() {
|
||||
// TODO: implement stop
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
|
||||
import '../../domain/entities/album.dart';
|
||||
import '../../domain/entities/artist.dart';
|
||||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../../domain/entities/playback_state.dart' as entity;
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../../domain/entities/song.dart';
|
||||
import '../../domain/repositories/audio_repository.dart';
|
||||
import '../audio/stream_constants.dart';
|
||||
import '../models/album_model.dart';
|
||||
import '../models/artist_model.dart';
|
||||
import '../models/playback_state_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
|
||||
typedef Conversion<S, T> = T Function(S);
|
||||
|
||||
class AudioRepositoryImpl implements AudioRepository {
|
||||
AudioRepositoryImpl(this._audioHandler);
|
||||
|
||||
final AudioHandler _audioHandler;
|
||||
|
||||
@override
|
||||
Stream<SongModel> get currentSongStream => _filterStream<MediaItem, SongModel>(
|
||||
_audioHandler.mediaItem.stream,
|
||||
(MediaItem mi) => SongModel.fromMediaItem(mi),
|
||||
);
|
||||
|
||||
@override
|
||||
Stream<entity.PlaybackState> get playbackStateStream => _filterStream(
|
||||
_audioHandler.playbackState.stream,
|
||||
(PlaybackState ps) => PlaybackStateModel.fromASPlaybackState(ps),
|
||||
);
|
||||
|
||||
@override
|
||||
Stream<int> get currentPositionStream => _position().distinct();
|
||||
|
||||
@override
|
||||
Future<void> playSong(int index, List<Song> songList) async {
|
||||
if (0 <= index && index < songList.length) {
|
||||
final List<String> context = songList.map((s) => s.path).toList();
|
||||
await _audioHandler.customAction(PLAY_WITH_CONTEXT, {'CONTEXT': context, 'INDEX': index});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> play() async {
|
||||
_audioHandler.play();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pause() async {
|
||||
await _audioHandler.pause();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> skipToNext() async {
|
||||
await _audioHandler.skipToNext();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> skipToPrevious() async {
|
||||
await _audioHandler.skipToPrevious();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setIndex(int index) async {
|
||||
await _audioHandler.customAction(SET_INDEX, {'INDEX': index});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setShuffleMode(ShuffleMode shuffleMode) async {
|
||||
await _audioHandler.customAction(SET_SHUFFLE_MODE, {'SHUFFLE_MODE': shuffleMode});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setLoopMode(LoopMode loopMode) async {
|
||||
await _audioHandler.customAction(SET_LOOP_MODE, {'LOOP_MODE': loopMode});
|
||||
}
|
||||
|
||||
Stream<T> _filterStream<S, T>(Stream<S> stream, Conversion<S, T> fn) async* {
|
||||
T lastItem;
|
||||
|
||||
await for (final S item in stream) {
|
||||
final T newItem = fn(item);
|
||||
if (newItem != lastItem) {
|
||||
lastItem = newItem;
|
||||
yield newItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Stream<int> _position() async* {
|
||||
PlaybackState state;
|
||||
DateTime updateTime;
|
||||
Duration statePosition;
|
||||
|
||||
// TODO: should this class get an init method for this?
|
||||
_audioHandler.playbackState.stream.listen((currentState) {
|
||||
state = currentState;
|
||||
updateTime = currentState?.updateTime;
|
||||
statePosition = currentState?.position;
|
||||
});
|
||||
|
||||
while (true) {
|
||||
if (statePosition != null && updateTime != null && state != null) {
|
||||
if (state.playing) {
|
||||
yield statePosition.inMilliseconds +
|
||||
(DateTime.now().millisecondsSinceEpoch - updateTime.millisecondsSinceEpoch);
|
||||
} else {
|
||||
yield statePosition.inMilliseconds;
|
||||
}
|
||||
} else {
|
||||
yield 0;
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> shuffleAll() async {
|
||||
await _audioHandler.customAction(SHUFFLE_ALL, null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> addToQueue(Song song) async {
|
||||
await _audioHandler.addQueueItem((song as SongModel).toMediaItem());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
|
||||
await _audioHandler.customAction(MOVE_QUEUE_ITEM, {
|
||||
'OLD_INDEX': oldIndex,
|
||||
'NEW_INDEX': newIndex,
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> removeQueueIndex(int index) async {
|
||||
await _audioHandler.removeQueueItemAt(index);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> playAlbum(Album album) async {
|
||||
await _audioHandler.customAction(PLAY_ALBUM, {'ALBUM': album as AlbumModel});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> playArtist(Artist artist, {ShuffleMode shuffleMode = ShuffleMode.none}) async {
|
||||
await _audioHandler.customAction(PLAY_ARTIST, {
|
||||
'ARTIST': artist as ArtistModel,
|
||||
'SHUFFLE_MODE': shuffleMode,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import '../../domain/entities/album.dart';
|
|||
import '../../domain/entities/artist.dart';
|
||||
import '../../domain/entities/song.dart';
|
||||
import '../../domain/repositories/music_data_repository.dart';
|
||||
import '../datasources/local_music_fetcher_contract.dart';
|
||||
import '../datasources/local_music_fetcher.dart';
|
||||
import '../datasources/music_data_source_contract.dart';
|
||||
import '../models/album_model.dart';
|
||||
import '../models/artist_model.dart';
|
||||
|
@ -24,6 +24,11 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
|
|||
|
||||
static final _log = Logger('MusicDataRepository');
|
||||
|
||||
@override
|
||||
Future<Song> getSongByPath(String path) async {
|
||||
return await _musicDataSource.getSongByPath(path);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<Song>> get songStream => _musicDataSource.songStream;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../../domain/entities/queue_item.dart';
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../../domain/entities/song.dart';
|
||||
import '../../domain/repositories/persistent_player_state_repository.dart';
|
||||
|
@ -23,4 +24,24 @@ class PlayerStateRepositoryImpl implements PlayerStateRepository {
|
|||
|
||||
@override
|
||||
Stream<ShuffleMode> get shuffleModeStream => _playerStateDataSource.shuffleModeStream;
|
||||
|
||||
@override
|
||||
void setCurrentIndex(int index) {
|
||||
// TODO: implement setCurrentIndex
|
||||
}
|
||||
|
||||
@override
|
||||
void setLoopMode(LoopMode loopMode) {
|
||||
// TODO: implement setLoopMode
|
||||
}
|
||||
|
||||
@override
|
||||
void setQueue(List<QueueItem> queue) {
|
||||
// TODO: implement setQueue
|
||||
}
|
||||
|
||||
@override
|
||||
void setShuffleMode(ShuffleMode shuffleMode) {
|
||||
// TODO: implement setShuffleMode
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import '../../domain/entities/playback_event.dart';
|
||||
import '../../domain/entities/song.dart';
|
||||
import '../../domain/repositories/platform_integration_repository.dart';
|
||||
import '../datasources/platform_integration_data_source.dart';
|
||||
import '../models/playback_event_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
|
||||
class PlatformIntegrationRepositoryImpl implements PlatformIntegrationRepository {
|
||||
PlatformIntegrationRepositoryImpl(this._platformIntegrationDataSource);
|
||||
|
||||
final PlatformIntegrationDataSource _platformIntegrationDataSource;
|
||||
|
||||
@override
|
||||
Stream<PlatformIntegrationEvent> get eventStream => _platformIntegrationDataSource.eventStream;
|
||||
|
||||
@override
|
||||
void handlePlaybackEvent(PlaybackEvent playbackEvent) {
|
||||
_platformIntegrationDataSource.handlePlaybackEvent(playbackEvent as PlaybackEventModel);
|
||||
}
|
||||
|
||||
@override
|
||||
void pause() {
|
||||
_platformIntegrationDataSource.onPause();
|
||||
}
|
||||
|
||||
@override
|
||||
void play() {
|
||||
_platformIntegrationDataSource.onPlay();
|
||||
}
|
||||
|
||||
@override
|
||||
void onStop() {
|
||||
// TODO: implement onStop
|
||||
}
|
||||
|
||||
@override
|
||||
void setCurrentSong(Song song) {
|
||||
_platformIntegrationDataSource.setCurrentSong(song as SongModel);
|
||||
}
|
||||
|
||||
@override
|
||||
void setQueue(List<Song> queue) {
|
||||
// TODO: implement setQueue
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue