added ShuffleAllButton

This commit is contained in:
Moritz Weber 2020-08-25 16:23:52 +02:00
parent b7075aad27
commit f866ef51c2
10 changed files with 159 additions and 83 deletions

View file

@ -19,4 +19,5 @@ abstract class AudioRepository {
Future<Either<Failure, void>> skipToNext();
Future<Either<Failure, void>> skipToPrevious();
Future<Either<Failure, void>> setShuffleMode(ShuffleMode shuffleMode);
Future<Either<Failure, void>> shuffleAll();
}

View file

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import '../widgets/shuffle_all_button.dart';
class HomePage extends StatefulWidget {
const HomePage({Key key}) : super(key: key);
@ -13,7 +15,7 @@ class _HomePageState extends State<HomePage> {
print('HomePage.build');
return Container(
child: const Center(
child: Text('Home Page'),
child: ShuffleAllButton(),
),
);
}

View file

@ -1,8 +1,8 @@
import 'package:meta/meta.dart';
import 'package:mobx/mobx.dart';
import 'package:mucke/domain/entities/shuffle_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';
@ -103,6 +103,10 @@ abstract class _AudioStore with Store {
_audioRepository.setShuffleMode(shuffleMode);
}
Future<void> shuffleAll() async {
_audioRepository.shuffleAll();
}
@action
Future<void> updateSong(Song streamValue) async {
print('updateSong');

View file

@ -102,6 +102,21 @@ mixin _$AudioStore on _AudioStore, Store {
});
}
final _$shuffleModeStreamAtom = Atom(name: '_AudioStore.shuffleModeStream');
@override
ObservableStream<ShuffleMode> get shuffleModeStream {
_$shuffleModeStreamAtom.reportRead();
return super.shuffleModeStream;
}
@override
set shuffleModeStream(ObservableStream<ShuffleMode> value) {
_$shuffleModeStreamAtom.reportWrite(value, super.shuffleModeStream, () {
super.shuffleModeStream = value;
});
}
final _$playSongAsyncAction = AsyncAction('_AudioStore.playSong');
@override
@ -165,7 +180,8 @@ song: ${song},
playbackStateStream: ${playbackStateStream},
currentPositionStream: ${currentPositionStream},
queueStream: ${queueStream},
queueIndexStream: ${queueIndexStream}
queueIndexStream: ${queueIndexStream},
shuffleModeStream: ${shuffleModeStream}
''';
}
}

View file

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../state/audio_store.dart';
class ShuffleAllButton extends StatelessWidget {
const ShuffleAllButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
final AudioStore audioStore = Provider.of<AudioStore>(context);
return RaisedButton(
child: const Text('Shuffle All'),
onPressed: () => audioStore.shuffleAll(),
color: Theme.of(context).accentColor,
);
}
}

View file

@ -76,9 +76,11 @@ class AudioManagerImpl implements AudioManager {
}
@override
Stream<ShuffleMode> get shuffleModeStream => _shuffleModeStream(AudioService.customEventStream.cast());
Stream<ShuffleMode> get shuffleModeStream =>
_shuffleModeStream(AudioService.customEventStream.cast());
Stream<ShuffleMode> _shuffleModeStream(Stream<Map<String, dynamic>> source) async* {
Stream<ShuffleMode> _shuffleModeStream(
Stream<Map<String, dynamic>> source) async* {
if (_shuffleMode != null) {
yield _shuffleMode;
}
@ -116,6 +118,7 @@ class AudioManagerImpl implements AudioManager {
await AudioService.start(
backgroundTaskEntrypoint: _backgroundTaskEntrypoint,
androidEnableQueue: true,
androidStopForegroundOnPause: true,
);
await AudioService.customAction(INIT);
}
@ -153,7 +156,7 @@ class AudioManagerImpl implements AudioManager {
Duration updateTime;
Duration statePosition;
// should this class get an init method for this?
// TODO: should this class get an init method for this?
_sourcePlaybackStateStream.listen((currentState) {
state = currentState;
updateTime = currentState?.updateTime;
@ -175,6 +178,12 @@ class AudioManagerImpl implements AudioManager {
await Future.delayed(const Duration(milliseconds: 200));
}
}
@override
Future<void> shuffleAll() async {
await _startAudioService();
await AudioService.customAction(SHUFFLE_ALL);
}
}
void _backgroundTaskEntrypoint() {

View file

@ -1,6 +1,5 @@
import 'package:mucke/domain/entities/shuffle_mode.dart';
import '../../domain/entities/playback_state.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../models/song_model.dart';
abstract class AudioManager {
@ -19,4 +18,5 @@ abstract class AudioManager {
Future<void> skipToNext();
Future<void> skipToPrevious();
Future<void> setShuffleMode(ShuffleMode shuffleMode);
Future<void> shuffleAll();
}

View file

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:math';
import 'dart:ui';
import 'package:audio_service/audio_service.dart';
@ -7,12 +8,14 @@ import 'package:moor/isolate.dart';
import 'package:moor/moor.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../models/song_model.dart';
import 'moor_music_data_source.dart';
const String INIT = 'INIT';
const String PLAY_WITH_CONTEXT = 'PLAY_WITH_CONTEXT';
const String APP_LIFECYCLE_RESUMED = 'APP_LIFECYCLE_RESUMED';
const String SET_SHUFFLE_MODE = 'SET_SHUFFLE_MODE';
const String SHUFFLE_ALL = 'SHUFFLE_ALL';
const String KEY_INDEX = 'INDEX';
@ -25,13 +28,18 @@ class AudioPlayerTask extends BackgroundAudioTask {
List<MediaItem> _playbackContext = <MediaItem>[];
ShuffleMode _shuffleMode = ShuffleMode.none;
ShuffleMode get shuffleMode => _shuffleMode;
set shuffleMode(ShuffleMode s) {
_shuffleMode = s;
AudioServiceBackground.sendCustomEvent({SET_SHUFFLE_MODE: s.toString()});
}
int _index = -1;
int get playbackIndex => _index;
int _playbackIndex = -1;
int get playbackIndex => _playbackIndex;
set playbackIndex(int i) {
print('setting index');
_index = i;
AudioServiceBackground.sendCustomEvent({KEY_INDEX: _index});
_playbackIndex = i;
AudioServiceBackground.sendCustomEvent({KEY_INDEX: _playbackIndex});
}
Duration _position;
@ -50,7 +58,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override
Future<void> onPlayFromMediaId(String mediaId) async {
AudioServiceBackground.setState(
controls: [pauseControl, stopControl],
controls: [MediaControl.pause, MediaControl.skipToNext],
playing: true,
processingState: AudioProcessingState.ready,
);
@ -64,7 +72,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override
Future<void> onPlay() async {
AudioServiceBackground.setState(
controls: [pauseControl, stopControl],
controls: [MediaControl.pause, MediaControl.skipToNext],
processingState: AudioProcessingState.ready,
updateTime: Duration(milliseconds: DateTime.now().millisecondsSinceEpoch),
position: _position,
@ -76,7 +84,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override
Future<void> onPause() async {
AudioServiceBackground.setState(
controls: [playControl, stopControl],
controls: [MediaControl.play, MediaControl.skipToNext],
processingState: AudioProcessingState.ready,
updateTime: Duration(milliseconds: DateTime.now().millisecondsSinceEpoch),
position: _position,
@ -116,6 +124,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
return _onAppLifecycleResumed();
case SET_SHUFFLE_MODE:
return _setShuffleMode((arguments as String).toShuffleMode());
case SHUFFLE_ALL:
return shuffleAll();
default:
}
}
@ -135,7 +145,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
final _mediaItems = await _getMediaItemsFromPaths(context);
final permutation = _generateSongPermutation(_mediaItems.length, index);
_playbackContext = _getPermutatedSongs(_mediaItems, permutation);
if (_shuffleMode == ShuffleMode.none)
if (shuffleMode == ShuffleMode.none)
playbackIndex = index;
else
playbackIndex = 0;
@ -149,21 +159,40 @@ class AudioPlayerTask extends BackgroundAudioTask {
}
Future<void> _setShuffleMode(ShuffleMode mode) async {
_shuffleMode = mode;
AudioServiceBackground.sendCustomEvent({SET_SHUFFLE_MODE: _shuffleMode.toString()});
shuffleMode = mode;
// TODO: adapt queue
}
// TODO: pasted code -> reformat!
Future<void> shuffleAll() async {
final start = DateTime.now();
shuffleMode = ShuffleMode.standard;
final List<SongModel> songs = await _moorMusicDataSource.getSongs();
final mediaItems = <MediaItem>[];
for (final song in songs) {
mediaItems.add(song.toMediaItem());
}
final rng = Random();
final index = rng.nextInt(mediaItems.length);
final permutation = _generateSongPermutation(mediaItems.length, index);
_playbackContext = _getPermutatedSongs(mediaItems, permutation);
playbackIndex = 0;
AudioServiceBackground.setQueue(_playbackContext);
final end = DateTime.now();
print(end.difference(start).inMilliseconds);
_startPlayback(playbackIndex);
}
// TODO: test
// TODO: optimize -> too slow for whole library
// fetching all songs together and preparing playback takes ~500ms compared to ~10.000ms individually
Future<List<MediaItem>> _getMediaItemsFromPaths(List<String> paths) async {
final mediaItems = <MediaItem>[];
for (final path in paths) {
final song = await _moorMusicDataSource.getSongByPath(path);
mediaItems.add(song.toMediaItem());
}
// TODO: not good, side effects...
_originalPlaybackContext = mediaItems;
return mediaItems;
}
@ -173,7 +202,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
// permutation[i] = j; => song j is on the i-th position in the permutated list
List<int> permutation;
switch (_shuffleMode) {
switch (shuffleMode) {
case ShuffleMode.none:
permutation = List<int>.generate(length, (i) => i);
break;
@ -200,7 +229,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
Future<void> _startPlayback(int index) async {
// TODO: DRY
AudioServiceBackground.setState(
controls: [pauseControl, stopControl],
controls: [MediaControl.pause, MediaControl.skipToNext],
playing: true,
processingState: AudioProcessingState.ready,
);
@ -232,19 +261,3 @@ class AudioPlayerTask extends BackgroundAudioTask {
return false;
}
}
MediaControl playControl = const MediaControl(
androidIcon: 'drawable/ic_action_play_arrow',
label: 'Play',
action: MediaAction.play,
);
MediaControl pauseControl = const MediaControl(
androidIcon: 'drawable/ic_action_pause',
label: 'Pause',
action: MediaAction.pause,
);
MediaControl stopControl = const MediaControl(
androidIcon: 'drawable/ic_action_stop',
label: 'Stop',
action: MediaAction.stop,
);

View file

@ -73,4 +73,10 @@ class AudioRepositoryImpl implements AudioRepository {
await _audioManager.setShuffleMode(shuffleMode);
return const Right(null);
}
@override
Future<Either<Failure, void>> shuffleAll() async {
await _audioManager.shuffleAll();
return const Right(null);
}
}

View file

@ -1,52 +1,58 @@
// Helper file to make coverage work for all dart files\n
// ignore_for_file: unused_import
import 'package:mucke/domain/entities/album.dart';
import 'package:mucke/domain/entities/song.dart';
import 'package:mucke/domain/entities/playback_state.dart';
import 'package:mucke/domain/entities/artist.dart';
import 'package:mucke/domain/repositories/music_data_repository.dart';
import 'package:mucke/domain/repositories/audio_repository.dart';
import 'package:mucke/main.dart';
import 'package:mucke/core/usecase.dart';
import 'package:mucke/core/error/failures.dart';
import 'package:mucke/presentation/state/audio_store.dart';
import 'package:mucke/presentation/state/navigation_store.dart';
import 'package:mucke/presentation/state/music_data_store.dart';
import 'package:mucke/presentation/utils.dart';
import 'package:mucke/presentation/theming.dart';
import 'package:mucke/presentation/pages/home_page.dart';
import 'package:mucke/presentation/pages/songs_page.dart';
import 'package:mucke/presentation/pages/currently_playing.dart';
import 'package:mucke/presentation/pages/albums_page.dart';
import 'package:mucke/presentation/pages/library_tab_container.dart';
import 'package:mucke/presentation/pages/settings_page.dart';
import 'package:mucke/presentation/pages/album_details_page.dart';
import 'package:mucke/presentation/pages/library_page.dart';
import 'package:mucke/presentation/widgets/next_song.dart';
import 'package:mucke/presentation/widgets/currently_playing_bar.dart';
import 'package:mucke/presentation/widgets/queue_card.dart';
import 'package:mucke/presentation/widgets/injection_widget.dart';
import 'package:mucke/presentation/widgets/play_pause_button.dart';
import 'package:mucke/presentation/widgets/album_art.dart';
import 'package:mucke/presentation/widgets/next_indicator.dart';
import 'package:mucke/presentation/widgets/next_button.dart';
import 'package:mucke/presentation/widgets/navbar.dart';
import 'package:mucke/presentation/widgets/time_progress_indicator.dart';
import 'package:mucke/presentation/widgets/audio_service_widget.dart';
import 'package:mucke/presentation/widgets/album_art_list_tile.dart';
import 'package:mucke/presentation/widgets/previous_button.dart';
import 'package:mucke/presentation/widgets/playback_control.dart';
import 'package:mucke/system/datasources/music_data_source_contract.dart';
import 'package:mucke/system/datasources/local_music_fetcher.dart';
import 'package:mucke/system/datasources/audio_manager.dart';
import 'package:mucke/system/datasources/audio_player_task.dart';
import 'package:mucke/system/datasources/moor_music_data_source.dart';
import 'package:mucke/system/datasources/local_music_fetcher_contract.dart';
import 'package:mucke/system/datasources/audio_manager_contract.dart';
import 'package:mucke/injection_container.dart';
import 'package:mucke/system/repositories/audio_repository_impl.dart';
import 'package:mucke/system/repositories/music_data_repository_impl.dart';
import 'package:mucke/system/models/playback_state_model.dart';
import 'package:mucke/system/datasources/audio_manager_contract.dart';
import 'package:mucke/system/datasources/local_music_fetcher.dart';
import 'package:mucke/system/datasources/audio_player_task.dart';
import 'package:mucke/system/datasources/local_music_fetcher_contract.dart';
import 'package:mucke/system/datasources/audio_manager.dart';
import 'package:mucke/system/datasources/moor_music_data_source.dart';
import 'package:mucke/system/datasources/music_data_source_contract.dart';
import 'package:mucke/system/models/artist_model.dart';
import 'package:mucke/system/models/album_model.dart';
import 'package:mucke/system/models/song_model.dart';
import 'package:mucke/injection_container.dart';
import 'package:mucke/system/models/playback_state_model.dart';
import 'package:mucke/main.dart';
import 'package:mucke/presentation/state/music_data_store.dart';
import 'package:mucke/presentation/state/navigation_store.dart';
import 'package:mucke/presentation/state/audio_store.dart';
import 'package:mucke/presentation/widgets/next_indicator.dart';
import 'package:mucke/presentation/widgets/audio_service_widget.dart';
import 'package:mucke/presentation/widgets/previous_button.dart';
import 'package:mucke/presentation/widgets/play_pause_button.dart';
import 'package:mucke/presentation/widgets/album_art.dart';
import 'package:mucke/presentation/widgets/shuffle_all_button.dart';
import 'package:mucke/presentation/widgets/shuffle_button.dart';
import 'package:mucke/presentation/widgets/injection_widget.dart';
import 'package:mucke/presentation/widgets/next_song.dart';
import 'package:mucke/presentation/widgets/queue_card.dart';
import 'package:mucke/presentation/widgets/playback_control.dart';
import 'package:mucke/presentation/widgets/album_art_list_tile.dart';
import 'package:mucke/presentation/widgets/navbar.dart';
import 'package:mucke/presentation/widgets/time_progress_indicator.dart';
import 'package:mucke/presentation/widgets/next_button.dart';
import 'package:mucke/presentation/widgets/currently_playing_bar.dart';
import 'package:mucke/presentation/theming.dart';
import 'package:mucke/presentation/utils.dart';
import 'package:mucke/presentation/pages/library_page.dart';
import 'package:mucke/presentation/pages/settings_page.dart';
import 'package:mucke/presentation/pages/library_tab_container.dart';
import 'package:mucke/presentation/pages/songs_page.dart';
import 'package:mucke/presentation/pages/queue_page.dart';
import 'package:mucke/presentation/pages/home_page.dart';
import 'package:mucke/presentation/pages/albums_page.dart';
import 'package:mucke/presentation/pages/album_details_page.dart';
import 'package:mucke/presentation/pages/currently_playing.dart';
import 'package:mucke/presentation/pages/artists_page.dart';
import 'package:mucke/core/usecase.dart';
import 'package:mucke/core/error/failures.dart';
import 'package:mucke/domain/repositories/audio_repository.dart';
import 'package:mucke/domain/repositories/music_data_repository.dart';
import 'package:mucke/domain/entities/shuffle_mode.dart';
import 'package:mucke/domain/entities/album.dart';
import 'package:mucke/domain/entities/artist.dart';
import 'package:mucke/domain/entities/song.dart';
import 'package:mucke/domain/entities/playback_state.dart';
void main(){}