a whole lot of changes
This commit is contained in:
parent
1952433aa9
commit
e463ab02c9
17 changed files with 439 additions and 94 deletions
|
@ -7,9 +7,13 @@ import '../entities/song.dart';
|
|||
abstract class AudioRepository {
|
||||
Stream<Song> get currentSongStream;
|
||||
Stream<PlaybackState> get playbackStateStream;
|
||||
Stream<List<Song>> get queueStream;
|
||||
Stream<int> get queueIndexStream;
|
||||
Stream<int> get currentPositionStream;
|
||||
|
||||
Future<Either<Failure, void>> playSong(int index, List<Song> songList);
|
||||
Future<Either<Failure, void>> play();
|
||||
Future<Either<Failure, void>> pause();
|
||||
Future<Either<Failure, void>> skipToNext();
|
||||
Future<Either<Failure, void>> skipToPrevious();
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mucke/presentation/widgets/next_indicator.dart';
|
||||
import 'package:mucke/presentation/widgets/playback_control.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../domain/entities/song.dart';
|
||||
|
@ -26,6 +28,8 @@ class CurrentlyPlayingPage extends StatelessWidget {
|
|||
print('CurrentlyPlayingPage.build -> Observer.build');
|
||||
final Song song = audioStore.song;
|
||||
|
||||
print(audioStore.queueIndexStream.value);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
|
@ -91,56 +95,9 @@ class CurrentlyPlayingPage extends StatelessWidget {
|
|||
const Spacer(
|
||||
flex: 3,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.repeat, size: 20.0),
|
||||
Icon(Icons.skip_previous, size: 32.0),
|
||||
const PlayPauseButton(
|
||||
circle: true,
|
||||
iconSize: 52.0,
|
||||
),
|
||||
Icon(Icons.skip_next, size: 32.0),
|
||||
Icon(Icons.shuffle, size: 20.0),
|
||||
],
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
),
|
||||
),
|
||||
const PlaybackControl(),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.expand_less,
|
||||
color: Colors.white70,
|
||||
),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white70,
|
||||
),
|
||||
children: [
|
||||
const TextSpan(text: 'Fire'),
|
||||
const TextSpan(text: ' • '),
|
||||
TextSpan(
|
||||
text: 'Beartooth',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w300,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const NextIndicator(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -35,6 +35,12 @@ abstract class _AudioStore with Store {
|
|||
@observable
|
||||
ObservableStream<int> currentPositionStream;
|
||||
|
||||
@observable
|
||||
ObservableStream<List<Song>> queueStream;
|
||||
|
||||
@observable
|
||||
ObservableStream<int> queueIndexStream;
|
||||
|
||||
@action
|
||||
void init() {
|
||||
if (!_initialized) {
|
||||
|
@ -44,6 +50,10 @@ abstract class _AudioStore with Store {
|
|||
currentPositionStream =
|
||||
_audioRepository.currentPositionStream.asObservable(initialValue: 0);
|
||||
|
||||
queueStream = _audioRepository.queueStream.asObservable(initialValue: []);
|
||||
|
||||
queueIndexStream = _audioRepository.queueIndexStream.asObservable();
|
||||
|
||||
_disposers.add(autorun((_) {
|
||||
updateSong(currentSong.value);
|
||||
}));
|
||||
|
@ -74,6 +84,16 @@ abstract class _AudioStore with Store {
|
|||
_audioRepository.pause();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> skipToNext() async {
|
||||
_audioRepository.skipToNext();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> skipToPrevious() async {
|
||||
_audioRepository.skipToPrevious();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> updateSong(Song streamValue) async {
|
||||
print('updateSong');
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mucke/presentation/widgets/next_button.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../domain/entities/song.dart';
|
||||
|
@ -53,17 +54,10 @@ class CurrentlyPlayingBar extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
const Spacer(),
|
||||
// IconButton(
|
||||
// icon: Icon(Icons.favorite_border),
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
const PlayPauseButton(
|
||||
circle: false,
|
||||
),
|
||||
// IconButton(
|
||||
// icon: Icon(Icons.skip_next),
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
const NextButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
41
lib/presentation/widgets/next_button.dart
Normal file
41
lib/presentation/widgets/next_button.dart
Normal file
|
@ -0,0 +1,41 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../state/audio_store.dart';
|
||||
|
||||
class NextButton extends StatelessWidget {
|
||||
const NextButton({Key key, this.iconSize = 24.0}) : super(key: key);
|
||||
|
||||
final double iconSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AudioStore audioStore = Provider.of<AudioStore>(context);
|
||||
|
||||
return Observer(
|
||||
builder: (BuildContext context) {
|
||||
final queue = audioStore.queueStream.value; //
|
||||
final int index = audioStore.queueIndexStream.value; //
|
||||
|
||||
if (index != null && index < queue.length - 1) { //
|
||||
return IconButton(
|
||||
icon: Icon(Icons.skip_next), //
|
||||
iconSize: iconSize,
|
||||
onPressed: () {
|
||||
audioStore.skipToNext(); //
|
||||
},
|
||||
|
||||
);
|
||||
}
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
Icons.skip_next,
|
||||
),
|
||||
iconSize: iconSize,
|
||||
onPressed: null,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
38
lib/presentation/widgets/next_indicator.dart
Normal file
38
lib/presentation/widgets/next_indicator.dart
Normal file
|
@ -0,0 +1,38 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mucke/presentation/state/audio_store.dart';
|
||||
import 'package:mucke/presentation/widgets/next_song.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class NextIndicator extends StatelessWidget {
|
||||
const NextIndicator({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AudioStore audioStore = Provider.of<AudioStore>(context);
|
||||
|
||||
return Observer(
|
||||
builder: (BuildContext context) {
|
||||
final queue = audioStore.queueStream.value;
|
||||
final int index = audioStore.queueIndexStream.value;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.expand_less,
|
||||
color: Colors.white70,
|
||||
),
|
||||
if (index < queue.length - 1) NextSong(song: queue[index + 1]),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
31
lib/presentation/widgets/next_song.dart
Normal file
31
lib/presentation/widgets/next_song.dart
Normal file
|
@ -0,0 +1,31 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../domain/entities/song.dart';
|
||||
|
||||
class NextSong extends StatelessWidget {
|
||||
const NextSong({Key key, this.song}) : super(key: key);
|
||||
|
||||
final Song song;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white70,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: '${song.title}'),
|
||||
const TextSpan(text: ' • '),
|
||||
TextSpan(
|
||||
text: '${song.artist}',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w300,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
29
lib/presentation/widgets/playback_control.dart
Normal file
29
lib/presentation/widgets/playback_control.dart
Normal file
|
@ -0,0 +1,29 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'next_button.dart';
|
||||
import 'play_pause_button.dart';
|
||||
import 'previous_button.dart';
|
||||
|
||||
class PlaybackControl extends StatelessWidget {
|
||||
const PlaybackControl({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.repeat, size: 20.0),
|
||||
const PreviousButton(iconSize: 32.0),
|
||||
const PlayPauseButton(
|
||||
circle: true,
|
||||
iconSize: 52.0,
|
||||
),
|
||||
const NextButton(iconSize: 32.0),
|
||||
Icon(Icons.shuffle, size: 20.0),
|
||||
],
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
40
lib/presentation/widgets/previous_button.dart
Normal file
40
lib/presentation/widgets/previous_button.dart
Normal file
|
@ -0,0 +1,40 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../state/audio_store.dart';
|
||||
|
||||
class PreviousButton extends StatelessWidget {
|
||||
const PreviousButton({Key key, this.iconSize = 24.0}) : super(key: key);
|
||||
|
||||
final double iconSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AudioStore audioStore = Provider.of<AudioStore>(context);
|
||||
|
||||
return Observer(
|
||||
builder: (BuildContext context) {
|
||||
final int index = audioStore.queueIndexStream.value; //
|
||||
|
||||
if (index > 0) { //
|
||||
return IconButton(
|
||||
icon: Icon(Icons.skip_previous), //
|
||||
iconSize: iconSize,
|
||||
onPressed: () {
|
||||
audioStore.skipToPrevious(); //
|
||||
},
|
||||
|
||||
);
|
||||
}
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
Icons.skip_previous,
|
||||
),
|
||||
iconSize: iconSize,
|
||||
onPressed: null,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,11 +10,30 @@ import 'audio_player_task.dart';
|
|||
|
||||
typedef Conversion<S, T> = T Function(S);
|
||||
|
||||
// index geht verloren, wenn noch kein Subscriber vorhanden ist, weil die events nicht gebuffert werden (kann ich nicht ändern)
|
||||
// deshalb sollte sofort ein listener erstellt werden, der den jeweils letzten wert speichert
|
||||
// der fertige Stream nimmt dann diesen letzten Wert, wenn er keinen im source stream findet
|
||||
// so sollte kein memory leak entstehen, weil immer nur ein wert gebuffert wird
|
||||
|
||||
class AudioManagerImpl implements AudioManager {
|
||||
AudioManagerImpl() {
|
||||
AudioService.customEventStream.listen((event) {
|
||||
final data = event as Map<String, dynamic>;
|
||||
if (data.containsKey(KEY_INDEX)) {
|
||||
_queueIndex = data[KEY_INDEX] as int;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Stream<MediaItem> _currentMediaItemStream =
|
||||
AudioService.currentMediaItemStream;
|
||||
final Stream<PlaybackState> _sourcePlaybackStateStream =
|
||||
AudioService.playbackStateStream;
|
||||
final Stream<List<MediaItem>> _queue = AudioService.queueStream;
|
||||
@override
|
||||
final Stream customEventStream = AudioService.customEventStream;
|
||||
|
||||
int _queueIndex;
|
||||
|
||||
@override
|
||||
Stream<SongModel> get currentSongStream =>
|
||||
|
@ -29,6 +48,30 @@ class AudioManagerImpl implements AudioManager {
|
|||
(PlaybackState ps) => PlaybackStateModel.fromASPlaybackState(ps),
|
||||
);
|
||||
|
||||
// TODO: test
|
||||
@override
|
||||
Stream<List<SongModel>> get queueStream {
|
||||
return _queue.map((mediaItems) =>
|
||||
mediaItems.map((m) => SongModel.fromMediaItem(m)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<int> get queueIndexStream =>
|
||||
_queueIndexStream(AudioService.customEventStream.cast());
|
||||
|
||||
// TODO: test
|
||||
Stream<int> _queueIndexStream(Stream<Map<String, dynamic>> source) async* {
|
||||
if (_queueIndex != null) {
|
||||
yield _queueIndex;
|
||||
}
|
||||
|
||||
await for (final data in source) {
|
||||
if (data.containsKey(KEY_INDEX)) {
|
||||
yield data[KEY_INDEX] as int;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<int> get currentPositionStream => _position().distinct();
|
||||
|
||||
|
@ -36,10 +79,7 @@ class AudioManagerImpl implements AudioManager {
|
|||
Future<void> playSong(int index, List<SongModel> songList) async {
|
||||
await _startAudioService();
|
||||
final List<String> queue = songList.map((s) => s.path).toList();
|
||||
|
||||
await AudioService.customAction(SET_QUEUE, queue);
|
||||
|
||||
AudioService.playFromMediaId(queue[index]);
|
||||
await AudioService.customAction(PLAY_WITH_CONTEXT, [queue, index]);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -58,9 +98,20 @@ class AudioManagerImpl implements AudioManager {
|
|||
backgroundTaskEntrypoint: _backgroundTaskEntrypoint,
|
||||
androidEnableQueue: true,
|
||||
);
|
||||
await AudioService.customAction(INIT);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> skipToNext() async {
|
||||
await AudioService.skipToNext();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> skipToPrevious() async {
|
||||
await AudioService.skipToPrevious();
|
||||
}
|
||||
|
||||
Stream<T> _filterStream<S, T>(Stream<S> stream, Conversion<S, T> fn) async* {
|
||||
T lastItem;
|
||||
|
||||
|
@ -89,7 +140,8 @@ class AudioManagerImpl implements AudioManager {
|
|||
if (statePosition != null && updateTime != null && state != null) {
|
||||
if (state.playing) {
|
||||
yield statePosition.inMilliseconds +
|
||||
(DateTime.now().millisecondsSinceEpoch - updateTime.inMilliseconds);
|
||||
(DateTime.now().millisecondsSinceEpoch -
|
||||
updateTime.inMilliseconds);
|
||||
} else {
|
||||
yield statePosition.inMilliseconds;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,15 @@ import '../models/song_model.dart';
|
|||
abstract class AudioManager {
|
||||
Stream<SongModel> get currentSongStream;
|
||||
Stream<PlaybackState> get playbackStateStream;
|
||||
Stream<List<SongModel>> get queueStream;
|
||||
Stream get customEventStream;
|
||||
Stream<int> get queueIndexStream;
|
||||
/// Current position in the song in milliseconds.
|
||||
Stream<int> get currentPositionStream;
|
||||
|
||||
Future<void> playSong(int index, List<SongModel> songList);
|
||||
Future<void> play();
|
||||
Future<void> pause();
|
||||
Future<void> skipToNext();
|
||||
Future<void> skipToPrevious();
|
||||
}
|
|
@ -8,7 +8,10 @@ import 'package:moor/moor.dart';
|
|||
|
||||
import 'moor_music_data_source.dart';
|
||||
|
||||
const String SET_QUEUE = 'SET_QUEUE';
|
||||
const String INIT = 'INIT';
|
||||
const String PLAY_WITH_CONTEXT = 'PLAY_WITH_CONTEXT';
|
||||
|
||||
const String KEY_INDEX = 'INDEX';
|
||||
|
||||
class AudioPlayerTask extends BackgroundAudioTask {
|
||||
final _audioPlayer = AudioPlayer();
|
||||
|
@ -16,19 +19,20 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
MoorMusicDataSource _moorMusicDataSource;
|
||||
|
||||
final _mediaItems = <String, MediaItem>{};
|
||||
final _queue = <MediaItem>[];
|
||||
List<MediaItem> _originalPlaybackContext = <MediaItem>[];
|
||||
List<MediaItem> _playbackContext = <MediaItem>[];
|
||||
int _index = -1;
|
||||
int get playbackIndex => _index;
|
||||
set playbackIndex(int i) {
|
||||
print('setting index');
|
||||
_index = i;
|
||||
AudioServiceBackground.sendCustomEvent({KEY_INDEX: _index});
|
||||
}
|
||||
|
||||
Duration _position;
|
||||
|
||||
@override
|
||||
Future<void> onStart(Map<String, dynamic> params) async {
|
||||
_audioPlayer.getPositionStream().listen((duration) => _position = duration);
|
||||
|
||||
final connectPort = IsolateNameServer.lookupPortByName(MOOR_ISOLATE);
|
||||
final MoorIsolate moorIsolate = MoorIsolate.fromConnectPort(connectPort);
|
||||
final DatabaseConnection databaseConnection = await moorIsolate.connect();
|
||||
_moorMusicDataSource = MoorMusicDataSource.connect(databaseConnection);
|
||||
|
||||
await _completer.future;
|
||||
}
|
||||
|
||||
|
@ -81,17 +85,125 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
await _audioPlayer.pause();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onSkipToNext() async {
|
||||
if (_incrementIndex()) {
|
||||
await _audioPlayer.stop();
|
||||
_startPlayback(_index);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onSkipToPrevious() async {
|
||||
if (_decrementIndex()) {
|
||||
await _audioPlayer.stop();
|
||||
_startPlayback(_index);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onCustomAction(String name, arguments) async {
|
||||
switch (name) {
|
||||
case SET_QUEUE:
|
||||
return _setQueue(List<String>.from(arguments as List<dynamic>));
|
||||
case INIT:
|
||||
return _init();
|
||||
case PLAY_WITH_CONTEXT:
|
||||
// arguments: [List<String>, int]
|
||||
final args = arguments as List<dynamic>;
|
||||
final _context = List<String>.from(args[0] as List<dynamic>);
|
||||
final index = args[1] as int;
|
||||
return _playWithContext(_context, index);
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setQueue(List<String> queue) async {
|
||||
Future<void> _init() async {
|
||||
print('AudioPlayerTask._init');
|
||||
_audioPlayer.getPositionStream().listen((duration) => _position = duration);
|
||||
|
||||
final connectPort = IsolateNameServer.lookupPortByName(MOOR_ISOLATE);
|
||||
final MoorIsolate moorIsolate = MoorIsolate.fromConnectPort(connectPort);
|
||||
final DatabaseConnection databaseConnection = await moorIsolate.connect();
|
||||
_moorMusicDataSource = MoorMusicDataSource.connect(databaseConnection);
|
||||
}
|
||||
|
||||
Future<void> _playWithContext(List<String> context, int index) async {
|
||||
print('AudioPlayerTask._playWithContext');
|
||||
final _mediaItems = await _getMediaItemsFromPaths(context);
|
||||
final permutation = _generateSongPermutation(_mediaItems);
|
||||
_playbackContext = _getPermutatedSongs(_mediaItems, permutation);
|
||||
playbackIndex = index;
|
||||
AudioServiceBackground.setQueue(_playbackContext);
|
||||
_startPlayback(index);
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
// TODO: optimize -> too slow for whole library
|
||||
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());
|
||||
}
|
||||
_originalPlaybackContext = mediaItems;
|
||||
|
||||
return mediaItems;
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
// TODO: needs implementation for shuffle mode
|
||||
List<int> _generateSongPermutation(List<MediaItem> songs) {
|
||||
// permutation[i] = j; => song j is on the i-th position in the permutated list
|
||||
final permutation = <int>[];
|
||||
|
||||
for (var i = 0; i < songs.length; i++) {
|
||||
permutation.add(i);
|
||||
}
|
||||
|
||||
return permutation;
|
||||
}
|
||||
|
||||
List<MediaItem> _getPermutatedSongs(
|
||||
List<MediaItem> songs, List<int> permutation) {
|
||||
return List.generate(
|
||||
permutation.length, (index) => songs[permutation[index]]);
|
||||
}
|
||||
|
||||
// TODO: cleanup and test
|
||||
Future<void> _startPlayback(int index) async {
|
||||
// TODO: DRY
|
||||
AudioServiceBackground.setState(
|
||||
controls: [pauseControl, stopControl],
|
||||
playing: true,
|
||||
processingState: AudioProcessingState.ready,
|
||||
);
|
||||
|
||||
// TODO: needs implementation for shuffle mode (play first song)
|
||||
final _mediaItem = _playbackContext[index];
|
||||
await AudioServiceBackground.setMediaItem(_mediaItem);
|
||||
await _audioPlayer.setFilePath(_mediaItem.id);
|
||||
|
||||
// exploration: this works, but has to be used every time play() is called; maybe stateStream is the better option
|
||||
_audioPlayer.play().then((_) {
|
||||
print(_audioPlayer.playbackState);
|
||||
if (_audioPlayer.playbackState == AudioPlaybackState.completed)
|
||||
onSkipToNext();
|
||||
});
|
||||
}
|
||||
|
||||
bool _incrementIndex() {
|
||||
if (playbackIndex < _playbackContext.length - 1) {
|
||||
playbackIndex++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _decrementIndex() {
|
||||
if (playbackIndex > 0) {
|
||||
playbackIndex--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,7 +88,9 @@ class MoorMusicDataSource extends _$MoorMusicDataSource
|
|||
|
||||
@override
|
||||
Future<List<SongModel>> getSongsFromAlbum(AlbumModel album) {
|
||||
return (select(songs)..where((tbl) => tbl.albumTitle.equals(album.title)))
|
||||
return (select(songs)
|
||||
..where((tbl) => tbl.albumTitle.equals(album.title))
|
||||
..orderBy([(t) => OrderingTerm(expression: t.trackNumber)]))
|
||||
.get()
|
||||
.then((moorSongList) => moorSongList
|
||||
.map((moorSong) => SongModel.fromMoorSong(moorSong))
|
||||
|
|
|
@ -82,6 +82,11 @@ class SongModel extends Song {
|
|||
|
||||
final int albumId;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$title';
|
||||
}
|
||||
|
||||
SongModel copyWith({
|
||||
String title,
|
||||
String album,
|
||||
|
@ -115,15 +120,14 @@ class SongModel extends Song {
|
|||
);
|
||||
|
||||
MediaItem toMediaItem() => MediaItem(
|
||||
id: path,
|
||||
title: title,
|
||||
album: album,
|
||||
artist: artist,
|
||||
duration: Duration(milliseconds: duration),
|
||||
artUri: 'file://$albumArtPath',
|
||||
extras: {
|
||||
'albumId': albumId,
|
||||
'trackNumber': trackNumber,
|
||||
}
|
||||
);
|
||||
id: path,
|
||||
title: title,
|
||||
album: album,
|
||||
artist: artist,
|
||||
duration: Duration(milliseconds: duration),
|
||||
artUri: 'file://$albumArtPath',
|
||||
extras: {
|
||||
'albumId': albumId,
|
||||
'trackNumber': trackNumber,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -20,9 +20,13 @@ class AudioRepositoryImpl implements AudioRepository {
|
|||
_audioManager.playbackStateStream;
|
||||
|
||||
@override
|
||||
Stream<int> get currentPositionStream {
|
||||
return _audioManager.currentPositionStream;
|
||||
}
|
||||
Stream<List<Song>> get queueStream => _audioManager.queueStream;
|
||||
|
||||
@override
|
||||
Stream<int> get queueIndexStream => _audioManager.queueIndexStream;
|
||||
|
||||
@override
|
||||
Stream<int> get currentPositionStream => _audioManager.currentPositionStream;
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> playSong(int index, List<Song> songList) async {
|
||||
|
@ -31,7 +35,7 @@ class AudioRepositoryImpl implements AudioRepository {
|
|||
|
||||
if (0 <= index && index < songList.length) {
|
||||
await _audioManager.playSong(index, songModelList);
|
||||
return Right(null);
|
||||
return const Right(null);
|
||||
}
|
||||
return Left(IndexFailure());
|
||||
}
|
||||
|
@ -39,12 +43,24 @@ class AudioRepositoryImpl implements AudioRepository {
|
|||
@override
|
||||
Future<Either<Failure, void>> play() async {
|
||||
await _audioManager.play();
|
||||
return Right(null);
|
||||
return const Right(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> pause() async {
|
||||
await _audioManager.pause();
|
||||
return Right(null);
|
||||
return const Right(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> skipToNext() async {
|
||||
await _audioManager.skipToNext();
|
||||
return const Right(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> skipToPrevious() async {
|
||||
await _audioManager.skipToPrevious();
|
||||
return const Right(null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -414,7 +414,7 @@ packages:
|
|||
name: moor_ffi
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
version: "0.5.0"
|
||||
moor_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
|
|
@ -23,7 +23,7 @@ dependencies:
|
|||
get_it: ^4.0.2
|
||||
provider: ^4.0.4
|
||||
moor: ^3.0.2
|
||||
moor_ffi: ^0.6.0
|
||||
moor_ffi: ^0.5.0
|
||||
path_provider:
|
||||
path:
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue