move to single-isolate audio_service
This commit is contained in:
parent
3570f6cdbd
commit
5f22ed01af
17 changed files with 200 additions and 310 deletions
|
@ -51,6 +51,7 @@ android {
|
|||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
shrinkResources false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<application android:name="io.flutter.app.FlutterApplication" android:label="mucke" android:icon="@mipmap/ic_launcher">
|
||||
<activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
||||
<activity android:name="com.ryanheise.audioservice.AudioServiceActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
|
|
@ -22,5 +22,5 @@ abstract class AudioRepository {
|
|||
Future<Either<Failure, void>> shuffleAll();
|
||||
Future<Either<Failure, void>> addToQueue(Song song);
|
||||
Future<Either<Failure, void>> moveQueueItem(int oldIndex, int newIndex);
|
||||
Future<Either<Failure, void>> removeQueueItem(int index);
|
||||
Future<Either<Failure, void>> removeQueueIndex(int index);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter_audio_query/flutter_audio_query.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:moor/isolate.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:mucke/system/audio/audio_handler.dart';
|
||||
|
||||
import 'domain/repositories/audio_repository.dart';
|
||||
import 'domain/repositories/music_data_repository.dart';
|
||||
|
@ -64,18 +66,23 @@ Future<void> setupGetIt() async {
|
|||
);
|
||||
|
||||
// data sources
|
||||
final MoorIsolate moorIsolate = await createMoorIsolate();
|
||||
IsolateNameServer.registerPortWithName(moorIsolate.connectPort, MOOR_ISOLATE);
|
||||
|
||||
final DatabaseConnection databaseConnection = await moorIsolate.connect();
|
||||
final MoorMusicDataSource moorMusicDataSource = MoorMusicDataSource.connect(databaseConnection);
|
||||
final MoorMusicDataSource moorMusicDataSource = MoorMusicDataSource();
|
||||
getIt.registerLazySingleton<MusicDataSource>(() => moorMusicDataSource);
|
||||
getIt.registerLazySingleton<LocalMusicFetcher>(
|
||||
() => LocalMusicFetcherImpl(
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<AudioManager>(() => AudioManagerImpl());
|
||||
getIt.registerLazySingleton<AudioManager>(() => AudioManagerImpl(getIt()));
|
||||
|
||||
final _audioHandler = await AudioService.init(
|
||||
builder: () => MyAudioHandler(getIt()),
|
||||
config: AudioServiceConfig(
|
||||
androidNotificationChannelName: 'mucke',
|
||||
androidEnableQueue: true,
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<AudioHandler>(() => _audioHandler);
|
||||
|
||||
// external
|
||||
getIt.registerLazySingleton<FlutterAudioQuery>(() => FlutterAudioQuery());
|
||||
|
|
|
@ -12,7 +12,7 @@ import 'presentation/pages/library_page.dart';
|
|||
import 'presentation/pages/settings_page.dart';
|
||||
import 'presentation/state/navigation_store.dart';
|
||||
import 'presentation/theming.dart';
|
||||
import 'presentation/widgets/audio_service_widget.dart';
|
||||
// import 'presentation/widgets/audio_service_widget.dart';
|
||||
import 'presentation/widgets/injection_widget.dart';
|
||||
import 'presentation/widgets/navbar.dart';
|
||||
|
||||
|
@ -24,7 +24,8 @@ Future<void> main() async {
|
|||
await session.configure(const AudioSessionConfiguration.music());
|
||||
|
||||
Logger.root.onRecord.listen((record) {
|
||||
print('${record.time} [${record.level.name}] ${record.loggerName}: ${record.message}');
|
||||
print(
|
||||
'${record.time} [${record.level.name}] ${record.loggerName}: ${record.message}');
|
||||
});
|
||||
|
||||
runApp(MyApp());
|
||||
|
@ -39,16 +40,14 @@ class MyApp extends StatelessWidget {
|
|||
]);
|
||||
|
||||
return InjectionWidget(
|
||||
child: AudioServiceWidget(
|
||||
child: MaterialApp(
|
||||
title: 'mucke',
|
||||
theme: theme(),
|
||||
initialRoute: '/',
|
||||
routes: {
|
||||
'/': (context) => const RootPage(),
|
||||
'/playing': (context) => const CurrentlyPlayingPage(),
|
||||
},
|
||||
),
|
||||
child: MaterialApp(
|
||||
title: 'mucke',
|
||||
theme: theme(),
|
||||
initialRoute: '/',
|
||||
routes: {
|
||||
'/': (context) => const RootPage(),
|
||||
'/playing': (context) => const CurrentlyPlayingPage(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class QueuePage extends StatelessWidget {
|
|||
highlight: index == queueIndexStream.value,
|
||||
),
|
||||
onDismissed: (direction) {
|
||||
audioStore.removeQueueItem(index);
|
||||
audioStore.removeQueueIndex(index);
|
||||
Scaffold.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('${song.title} dismissed'),
|
||||
|
|
|
@ -108,7 +108,7 @@ abstract class _AudioStore with Store {
|
|||
_audioRepository.moveQueueItem(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
Future<void> removeQueueItem(int index) async {
|
||||
_audioRepository.removeQueueItem(index);
|
||||
Future<void> removeQueueIndex(int index) async {
|
||||
_audioRepository.removeQueueIndex(index);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../system/audio/audio_player_task.dart';
|
||||
|
||||
class AudioServiceWidget extends StatefulWidget {
|
||||
const AudioServiceWidget({@required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
_AudioServiceWidgetState createState() => _AudioServiceWidgetState();
|
||||
}
|
||||
|
||||
class _AudioServiceWidgetState extends State<AudioServiceWidget>
|
||||
with WidgetsBindingObserver {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
AudioService.connect();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
AudioService.stop();
|
||||
AudioService.disconnect();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
switch (state) {
|
||||
case AppLifecycleState.resumed:
|
||||
print('AppLifecycleState.resumed');
|
||||
AudioService.connect();
|
||||
if (AudioService.running)
|
||||
AudioService.customAction(APP_LIFECYCLE_RESUMED);
|
||||
break;
|
||||
case AppLifecycleState.paused:
|
||||
print('AppLifecycleState.paused');
|
||||
AudioService.disconnect();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
AudioService.disconnect();
|
||||
return true;
|
||||
},
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,36 +1,32 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:moor/isolate.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../datasources/moor_music_data_source.dart';
|
||||
import '../datasources/music_data_source_contract.dart';
|
||||
import '../models/queue_item.dart';
|
||||
import '../models/song_model.dart';
|
||||
import 'queue_generator.dart';
|
||||
|
||||
part 'audio_player_task.g.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';
|
||||
const String SHUFFLE_MODE = 'SHUFFLE_MODE';
|
||||
|
||||
const String PLAY_WITH_CONTEXT = 'PLAY_WITH_CONTEXT';
|
||||
const String INIT = 'INIT';
|
||||
const String APP_LIFECYCLE_RESUMED = 'APP_LIFECYCLE_RESUMED';
|
||||
const String SHUFFLE_ALL = 'SHUFFLE_ALL';
|
||||
const String SET_SHUFFLE_MODE = 'SET_SHUFFLE_MODE';
|
||||
const String MOVE_QUEUE_ITEM = 'MOVE_QUEUE_ITEM';
|
||||
const String REMOVE_QUEUE_ITEM = 'REMOVE_QUEUE_ITEM';
|
||||
|
||||
class AudioPlayerTask = AudioPlayerTaskBase with _$AudioPlayerTask;
|
||||
class MyAudioHandler extends BaseAudioHandler {
|
||||
MyAudioHandler(this._musicDataSource);
|
||||
|
||||
abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store {
|
||||
final audioPlayer = AudioPlayer();
|
||||
MoorMusicDataSource moorMusicDataSource;
|
||||
|
||||
final _audioPlayer = AudioPlayer();
|
||||
final MusicDataSource _musicDataSource;
|
||||
QueueGenerator queueGenerator;
|
||||
|
||||
// TODO: confusing naming
|
||||
|
@ -39,124 +35,113 @@ abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store {
|
|||
|
||||
// TODO: this is not trivial: queue is loaded by audioplayer
|
||||
// this reference enables direct manipulation of the loaded queue
|
||||
ConcatenatingAudioSource queue;
|
||||
ConcatenatingAudioSource _queue;
|
||||
List<MediaItem> mediaItemQueue;
|
||||
|
||||
ShuffleMode _shuffleMode = ShuffleMode.none;
|
||||
|
||||
ShuffleMode get shuffleMode => _shuffleMode;
|
||||
set shuffleMode(ShuffleMode s) {
|
||||
_shuffleMode = s;
|
||||
AudioServiceBackground.sendCustomEvent({SET_SHUFFLE_MODE: s.toString()});
|
||||
customEventSubject.add({SHUFFLE_MODE: s});
|
||||
}
|
||||
|
||||
int _playbackIndex = -1;
|
||||
int get playbackIndex => _playbackIndex;
|
||||
set playbackIndex(int i) {
|
||||
_log.info('index: $i');
|
||||
if (i != null) {
|
||||
_playbackIndex = i;
|
||||
AudioServiceBackground.setMediaItem(mediaItemQueue[i]);
|
||||
AudioServiceBackground.sendCustomEvent({KEY_INDEX: i});
|
||||
mediaItemSubject.add(mediaItemQueue[i]);
|
||||
customEventSubject.add({KEY_INDEX: i});
|
||||
|
||||
AudioServiceBackground.setState(
|
||||
playbackStateSubject.add(playbackState.value.copyWith(
|
||||
controls: [
|
||||
MediaControl.skipToPrevious,
|
||||
MediaControl.pause,
|
||||
MediaControl.skipToNext
|
||||
],
|
||||
playing: audioPlayer.playing,
|
||||
playing: _audioPlayer.playing,
|
||||
processingState: AudioProcessingState.ready,
|
||||
updateTime:
|
||||
Duration(milliseconds: DateTime.now().millisecondsSinceEpoch),
|
||||
position: audioPlayer.position,
|
||||
);
|
||||
updatePosition: _audioPlayer.position,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
static final _log = Logger('AudioPlayerTask')
|
||||
..onRecord.listen((record) {
|
||||
print(
|
||||
'${record.time} [${record.level.name}] ${record.loggerName}: ${record.message}');
|
||||
});
|
||||
static final _log = Logger('AudioHandler');
|
||||
|
||||
@override
|
||||
Future<void> onStop() async {
|
||||
await audioPlayer.stop();
|
||||
await audioPlayer.dispose();
|
||||
await super.onStop();
|
||||
Future<void> stop() async {
|
||||
await _audioPlayer.stop();
|
||||
await _audioPlayer.dispose();
|
||||
await super.stop();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPlay() async {
|
||||
audioPlayer.play();
|
||||
Future<void> play() async {
|
||||
_audioPlayer.play();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPause() async {
|
||||
await audioPlayer.pause();
|
||||
Future<void> pause() async {
|
||||
await _audioPlayer.pause();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onSkipToNext() async {
|
||||
audioPlayer.seekToNext();
|
||||
Future<void> skipToNext() async {
|
||||
_audioPlayer.seekToNext();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onSkipToPrevious() async {
|
||||
audioPlayer.seekToPrevious();
|
||||
Future<void> skipToPrevious() async {
|
||||
_audioPlayer.seekToPrevious();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onAddQueueItem(MediaItem mediaItem) async {
|
||||
await queue.add(AudioSource.uri(Uri.file(mediaItem.id)));
|
||||
Future<void> addQueueItem(MediaItem mediaItem) async {
|
||||
await _queue.add(AudioSource.uri(Uri.file(mediaItem.id)));
|
||||
mediaItemQueue.add(mediaItem);
|
||||
handleSetQueue(mediaItemQueue);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onCustomAction(String name, arguments) async {
|
||||
Future<void> customAction(String name, Map<String, dynamic> arguments) async {
|
||||
switch (name) {
|
||||
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;
|
||||
final context = arguments['CONTEXT'] as List<String>;
|
||||
final index = arguments['INDEX'] as int;
|
||||
return playWithContext(context, index);
|
||||
case APP_LIFECYCLE_RESUMED:
|
||||
return onAppLifecycleResumed();
|
||||
case SET_SHUFFLE_MODE:
|
||||
return setShuffleMode((arguments as String).toShuffleMode());
|
||||
return setCustomShuffleMode(arguments['SHUFFLE_MODE'] as ShuffleMode);
|
||||
case SHUFFLE_ALL:
|
||||
return shuffleAll();
|
||||
case MOVE_QUEUE_ITEM:
|
||||
final args = arguments as List<dynamic>;
|
||||
return moveQueueItem(args[0] as int, args[1] as int);
|
||||
return moveQueueItem(arguments['OLD_INDEX'] as int, arguments['NEW_INDEX'] as int);
|
||||
case REMOVE_QUEUE_ITEM:
|
||||
return removeQueueItem(arguments as int);
|
||||
return removeQueueIndex(arguments as int);
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handleSetQueue(List<MediaItem> mediaItemQueue) async {
|
||||
final songModels = mediaItemQueue.map((e) => SongModel.fromMediaItem(e)).toList();
|
||||
AudioServiceBackground.setQueue(mediaItemQueue);
|
||||
moorMusicDataSource.setQueue(songModels);
|
||||
queueSubject.add(mediaItemQueue);
|
||||
final songModels =
|
||||
mediaItemQueue.map((e) => SongModel.fromMediaItem(e)).toList();
|
||||
_musicDataSource.setQueue(songModels);
|
||||
}
|
||||
|
||||
Future<void> init() async {
|
||||
print('AudioPlayerTask.init');
|
||||
audioPlayer.playerStateStream.listen((event) => handlePlayerState(event));
|
||||
audioPlayer.currentIndexStream.listen((event) => playbackIndex = event);
|
||||
audioPlayer.sequenceStateStream
|
||||
_audioPlayer.playerStateStream.listen((event) => handlePlayerState(event));
|
||||
_audioPlayer.currentIndexStream.listen((event) => playbackIndex = event);
|
||||
_audioPlayer.sequenceStateStream
|
||||
.listen((event) => handleSequenceState(event));
|
||||
|
||||
final connectPort = IsolateNameServer.lookupPortByName(MOOR_ISOLATE);
|
||||
final MoorIsolate moorIsolate = MoorIsolate.fromConnectPort(connectPort);
|
||||
final DatabaseConnection databaseConnection = await moorIsolate.connect();
|
||||
moorMusicDataSource = MoorMusicDataSource.connect(databaseConnection);
|
||||
|
||||
queueGenerator = QueueGenerator(moorMusicDataSource);
|
||||
queueGenerator = QueueGenerator(_musicDataSource);
|
||||
}
|
||||
|
||||
Future<void> playWithContext(List<String> context, int index) async {
|
||||
|
@ -165,18 +150,17 @@ abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store {
|
|||
}
|
||||
|
||||
Future<void> onAppLifecycleResumed() async {
|
||||
AudioServiceBackground.sendCustomEvent({KEY_INDEX: playbackIndex});
|
||||
AudioServiceBackground.sendCustomEvent(
|
||||
{SET_SHUFFLE_MODE: shuffleMode.toString()});
|
||||
customEventSubject.add({SHUFFLE_MODE: shuffleMode});
|
||||
customEventSubject.add({KEY_INDEX: playbackIndex});
|
||||
}
|
||||
|
||||
Future<void> setShuffleMode(ShuffleMode mode) async {
|
||||
Future<void> setCustomShuffleMode(ShuffleMode mode) async {
|
||||
shuffleMode = mode;
|
||||
|
||||
final QueueItem currentQueueItem = playbackContext[playbackIndex];
|
||||
final int index = currentQueueItem.originalIndex;
|
||||
playbackContext =
|
||||
await queueGenerator.generateQueue(shuffleMode, originalPlaybackContext, index);
|
||||
playbackContext = await queueGenerator.generateQueue(
|
||||
shuffleMode, originalPlaybackContext, index);
|
||||
mediaItemQueue = playbackContext.map((e) => e.mediaItem).toList();
|
||||
|
||||
// FIXME: this does not react correctly when inserted track is currently played
|
||||
|
@ -186,39 +170,39 @@ abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store {
|
|||
_updateQueue(newQueue, currentQueueItem);
|
||||
}
|
||||
|
||||
void _updateQueue(ConcatenatingAudioSource newQueue, QueueItem currentQueueItem) {
|
||||
void _updateQueue(
|
||||
ConcatenatingAudioSource newQueue, QueueItem currentQueueItem) {
|
||||
final int index = currentQueueItem.originalIndex;
|
||||
|
||||
queue.removeRange(0, playbackIndex);
|
||||
queue.removeRange(1, queue.length);
|
||||
_queue.removeRange(0, playbackIndex);
|
||||
_queue.removeRange(1, _queue.length);
|
||||
|
||||
if (shuffleMode == ShuffleMode.none) {
|
||||
switch (currentQueueItem.type) {
|
||||
case QueueItemType.standard:
|
||||
queue.insertAll(0, newQueue.children.sublist(0, index));
|
||||
queue.addAll(newQueue.children.sublist(index + 1));
|
||||
_queue.insertAll(0, newQueue.children.sublist(0, index));
|
||||
_queue.addAll(newQueue.children.sublist(index + 1));
|
||||
playbackIndex = index;
|
||||
break;
|
||||
case QueueItemType.predecessor:
|
||||
queue.insertAll(0, newQueue.children.sublist(0, index));
|
||||
queue.addAll(newQueue.children.sublist(index));
|
||||
_queue.insertAll(0, newQueue.children.sublist(0, index));
|
||||
_queue.addAll(newQueue.children.sublist(index));
|
||||
playbackIndex = index;
|
||||
break;
|
||||
case QueueItemType.successor:
|
||||
queue.insertAll(0, newQueue.children.sublist(0, index + 1));
|
||||
queue.addAll(newQueue.children.sublist(index + 1));
|
||||
_queue.insertAll(0, newQueue.children.sublist(0, index + 1));
|
||||
_queue.addAll(newQueue.children.sublist(index + 1));
|
||||
playbackIndex = index;
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
queue.addAll(newQueue.children.sublist(1));
|
||||
_queue.addAll(newQueue.children.sublist(1));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> shuffleAll() async {
|
||||
shuffleMode = ShuffleMode.standard;
|
||||
final List<SongModel> songs = await moorMusicDataSource.getSongs();
|
||||
final List<SongModel> songs = await _musicDataSource.getSongs();
|
||||
final List<MediaItem> mediaItems =
|
||||
songs.map((song) => song.toMediaItem()).toList();
|
||||
|
||||
|
@ -229,16 +213,25 @@ abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store {
|
|||
}
|
||||
|
||||
Future<void> playPlaylist(List<MediaItem> mediaItems, int index) async {
|
||||
final firstMediaItem = mediaItems.sublist(index, index + 1);
|
||||
mediaItemQueue = firstMediaItem;
|
||||
handleSetQueue(firstMediaItem);
|
||||
_queue = queueGenerator.mediaItemsToAudioSource(firstMediaItem);
|
||||
_audioPlayer.play();
|
||||
await _audioPlayer.load(_queue, initialIndex: 0);
|
||||
|
||||
|
||||
originalPlaybackContext = mediaItems;
|
||||
|
||||
playbackContext = await queueGenerator.generateQueue(shuffleMode, mediaItems, index);
|
||||
playbackContext =
|
||||
await queueGenerator.generateQueue(shuffleMode, mediaItems, index);
|
||||
mediaItemQueue = playbackContext.map((e) => e.mediaItem).toList();
|
||||
|
||||
handleSetQueue(mediaItemQueue);
|
||||
queue = queueGenerator.mediaItemsToAudioSource(mediaItemQueue);
|
||||
audioPlayer.play();
|
||||
final int startIndex = shuffleMode == ShuffleMode.none ? index : 0;
|
||||
await audioPlayer.load(queue, initialIndex: startIndex);
|
||||
final int splitIndex = shuffleMode == ShuffleMode.none ? index : 0;
|
||||
final newQueue = queueGenerator.mediaItemsToAudioSource(mediaItemQueue);
|
||||
_queue.insertAll(0, newQueue.children.sublist(0, splitIndex));
|
||||
_queue.addAll(newQueue.children.sublist(splitIndex + 1, newQueue.length));
|
||||
}
|
||||
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
|
||||
|
@ -247,19 +240,19 @@ abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store {
|
|||
final index = newIndex < oldIndex ? newIndex : newIndex - 1;
|
||||
mediaItemQueue.insert(index, mediaItem);
|
||||
handleSetQueue(mediaItemQueue);
|
||||
queue.move(oldIndex, index);
|
||||
_queue.move(oldIndex, index);
|
||||
}
|
||||
|
||||
Future<void> removeQueueItem(int index) async {
|
||||
Future<void> removeQueueIndex(int index) async {
|
||||
mediaItemQueue.removeAt(index);
|
||||
handleSetQueue(mediaItemQueue);
|
||||
queue.removeAt(index);
|
||||
_queue.removeAt(index);
|
||||
}
|
||||
|
||||
void handlePlayerState(PlayerState ps) {
|
||||
_log.info('handlePlayerState called');
|
||||
if (ps.processingState == ProcessingState.ready && ps.playing) {
|
||||
AudioServiceBackground.setState(
|
||||
playbackStateSubject.add(playbackState.value.copyWith(
|
||||
controls: [
|
||||
MediaControl.skipToPrevious,
|
||||
MediaControl.pause,
|
||||
|
@ -267,23 +260,19 @@ abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store {
|
|||
],
|
||||
playing: true,
|
||||
processingState: AudioProcessingState.ready,
|
||||
updateTime:
|
||||
Duration(milliseconds: DateTime.now().millisecondsSinceEpoch),
|
||||
position: audioPlayer.position,
|
||||
);
|
||||
updatePosition: _audioPlayer.position,
|
||||
));
|
||||
} else if (ps.processingState == ProcessingState.ready && !ps.playing) {
|
||||
AudioServiceBackground.setState(
|
||||
playbackStateSubject.add(playbackState.value.copyWith(
|
||||
controls: [
|
||||
MediaControl.skipToPrevious,
|
||||
MediaControl.play,
|
||||
MediaControl.skipToNext
|
||||
],
|
||||
processingState: AudioProcessingState.ready,
|
||||
updateTime:
|
||||
Duration(milliseconds: DateTime.now().millisecondsSinceEpoch),
|
||||
position: audioPlayer.position,
|
||||
updatePosition: _audioPlayer.position,
|
||||
playing: false,
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,8 +281,7 @@ abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store {
|
|||
_log.info('handleSequenceState called');
|
||||
if (0 <= playbackIndex && playbackIndex < playbackContext.length) {
|
||||
_log.info('handleSequenceState: setting MediaItem');
|
||||
AudioServiceBackground.setMediaItem(
|
||||
mediaItemQueue[playbackIndex]);
|
||||
mediaItemSubject.add(mediaItemQueue[playbackIndex]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,63 +6,54 @@ import '../../domain/entities/playback_state.dart' as entity;
|
|||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../models/playback_state_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
import 'audio_handler.dart';
|
||||
import 'audio_manager_contract.dart';
|
||||
import 'audio_player_task.dart';
|
||||
|
||||
typedef Conversion<S, T> = T Function(S);
|
||||
|
||||
class AudioManagerImpl implements AudioManager {
|
||||
AudioManagerImpl() {
|
||||
// this listener prevents the loss of data, when custom events are sent but not used yet
|
||||
// the customEventStream only works, when there is a listener
|
||||
AudioService.customEventStream.listen((event) {
|
||||
AudioManagerImpl(this._audioHandler) {
|
||||
_audioHandler.customAction(INIT, null);
|
||||
|
||||
_audioHandler.customEventStream.listen((event) {
|
||||
final data = event as Map<String, dynamic>;
|
||||
if (data.containsKey(KEY_INDEX)) {
|
||||
_queueIndex = data[KEY_INDEX] as int;
|
||||
}
|
||||
if (data.containsKey(SET_SHUFFLE_MODE)) {
|
||||
final modeString = data[SET_SHUFFLE_MODE] as String;
|
||||
_shuffleMode = modeString.toShuffleMode();
|
||||
if (data.containsKey(SHUFFLE_MODE)) {
|
||||
_shuffleMode = data[SHUFFLE_MODE] as ShuffleMode;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
final AudioHandler _audioHandler;
|
||||
int _queueIndex;
|
||||
ShuffleMode _shuffleMode;
|
||||
|
||||
@override
|
||||
Stream<SongModel> get currentSongStream =>
|
||||
_filterStream<MediaItem, SongModel>(
|
||||
_currentMediaItemStream,
|
||||
_audioHandler.mediaItem.stream,
|
||||
(MediaItem mi) => SongModel.fromMediaItem(mi),
|
||||
);
|
||||
|
||||
@override
|
||||
Stream<entity.PlaybackState> get playbackStateStream => _filterStream(
|
||||
_sourcePlaybackStateStream,
|
||||
_audioHandler.playbackState.stream,
|
||||
(PlaybackState ps) => PlaybackStateModel.fromASPlaybackState(ps),
|
||||
);
|
||||
|
||||
// TODO: test
|
||||
@override
|
||||
Stream<List<SongModel>> get queueStream {
|
||||
return _queue.map((mediaItems) =>
|
||||
return _audioHandler.queue.stream.map((mediaItems) =>
|
||||
mediaItems.map((m) => SongModel.fromMediaItem(m)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<int> get queueIndexStream =>
|
||||
_queueIndexStream(AudioService.customEventStream.cast());
|
||||
_queueIndexStream(_audioHandler.customEventStream.cast());
|
||||
|
||||
// TODO: test
|
||||
Stream<int> _queueIndexStream(Stream<Map<String, dynamic>> source) async* {
|
||||
if (_queueIndex != null) {
|
||||
yield _queueIndex;
|
||||
|
@ -77,7 +68,7 @@ class AudioManagerImpl implements AudioManager {
|
|||
|
||||
@override
|
||||
Stream<ShuffleMode> get shuffleModeStream =>
|
||||
_shuffleModeStream(AudioService.customEventStream.cast());
|
||||
_shuffleModeStream(_audioHandler.customEventStream.cast());
|
||||
|
||||
Stream<ShuffleMode> _shuffleModeStream(
|
||||
Stream<Map<String, dynamic>> source) async* {
|
||||
|
@ -86,9 +77,8 @@ class AudioManagerImpl implements AudioManager {
|
|||
}
|
||||
|
||||
await for (final data in source) {
|
||||
if (data.containsKey(SET_SHUFFLE_MODE)) {
|
||||
final modeString = data[SET_SHUFFLE_MODE] as String;
|
||||
yield modeString.toShuffleMode();
|
||||
if (data.containsKey(SHUFFLE_MODE)) {
|
||||
yield data[SHUFFLE_MODE] as ShuffleMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,46 +88,33 @@ class AudioManagerImpl implements AudioManager {
|
|||
|
||||
@override
|
||||
Future<void> playSong(int index, List<SongModel> songList) async {
|
||||
await _startAudioService();
|
||||
final List<String> queue = songList.map((s) => s.path).toList();
|
||||
await AudioService.customAction(PLAY_WITH_CONTEXT, [queue, index]);
|
||||
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 {
|
||||
await _startAudioService();
|
||||
await AudioService.play();
|
||||
_audioHandler.play();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pause() async {
|
||||
await AudioService.pause();
|
||||
}
|
||||
|
||||
Future<void> _startAudioService() async {
|
||||
if (!AudioService.running) {
|
||||
await AudioService.start(
|
||||
backgroundTaskEntrypoint: _backgroundTaskEntrypoint,
|
||||
androidEnableQueue: true,
|
||||
androidStopForegroundOnPause: true,
|
||||
);
|
||||
await AudioService.customAction(INIT);
|
||||
}
|
||||
await _audioHandler.pause();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> skipToNext() async {
|
||||
await AudioService.skipToNext();
|
||||
await _audioHandler.skipToNext();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> skipToPrevious() async {
|
||||
await AudioService.skipToPrevious();
|
||||
await _audioHandler.skipToPrevious();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setShuffleMode(ShuffleMode shuffleMode) async {
|
||||
await AudioService.customAction(SET_SHUFFLE_MODE, shuffleMode.toString());
|
||||
await _audioHandler.customAction(SET_SHUFFLE_MODE, {'SHUFFLE_MODE': shuffleMode});
|
||||
}
|
||||
|
||||
Stream<T> _filterStream<S, T>(Stream<S> stream, Conversion<S, T> fn) async* {
|
||||
|
@ -154,11 +131,11 @@ class AudioManagerImpl implements AudioManager {
|
|||
|
||||
Stream<int> _position() async* {
|
||||
PlaybackState state;
|
||||
Duration updateTime;
|
||||
DateTime updateTime;
|
||||
Duration statePosition;
|
||||
|
||||
// TODO: should this class get an init method for this?
|
||||
_sourcePlaybackStateStream.listen((currentState) {
|
||||
_audioHandler.playbackState.stream.listen((currentState) {
|
||||
state = currentState;
|
||||
updateTime = currentState?.updateTime;
|
||||
statePosition = currentState?.position;
|
||||
|
@ -169,7 +146,7 @@ class AudioManagerImpl implements AudioManager {
|
|||
if (state.playing) {
|
||||
yield statePosition.inMilliseconds +
|
||||
(DateTime.now().millisecondsSinceEpoch -
|
||||
updateTime.inMilliseconds);
|
||||
updateTime.millisecondsSinceEpoch);
|
||||
} else {
|
||||
yield statePosition.inMilliseconds;
|
||||
}
|
||||
|
@ -182,26 +159,21 @@ class AudioManagerImpl implements AudioManager {
|
|||
|
||||
@override
|
||||
Future<void> shuffleAll() async {
|
||||
await _startAudioService();
|
||||
await AudioService.customAction(SHUFFLE_ALL);
|
||||
await _audioHandler.customAction(SHUFFLE_ALL, null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> addToQueue(SongModel songModel) async {
|
||||
await AudioService.addQueueItem(songModel.toMediaItem());
|
||||
await _audioHandler.addQueueItem(songModel.toMediaItem());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
|
||||
await AudioService.customAction(MOVE_QUEUE_ITEM, [oldIndex, newIndex]);
|
||||
await _audioHandler.customAction(MOVE_QUEUE_ITEM, {'OLD_INDEX': oldIndex, 'NEW_INDEX': newIndex});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> removeQueueItem(int index) async {
|
||||
await AudioService.customAction(REMOVE_QUEUE_ITEM, index);
|
||||
}
|
||||
}
|
||||
|
||||
void _backgroundTaskEntrypoint() {
|
||||
AudioServiceBackground.run(() => AudioPlayerTask());
|
||||
Future<void> removeQueueIndex(int index) async {
|
||||
await _audioHandler.customAction(REMOVE_QUEUE_ITEM, {'INDEX': index});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ 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;
|
||||
|
@ -21,5 +20,5 @@ abstract class AudioManager {
|
|||
Future<void> shuffleAll();
|
||||
Future<void> addToQueue(SongModel songModel);
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex);
|
||||
Future<void> removeQueueItem(int index);
|
||||
Future<void> removeQueueIndex(int index);
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'audio_player_task.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// StoreGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
|
||||
|
||||
mixin _$AudioPlayerTask on AudioPlayerTaskBase, Store {
|
||||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
|
||||
''';
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ class PlaybackStateModel {
|
|||
}
|
||||
|
||||
switch (playbackState.processingState) {
|
||||
case AudioProcessingState.stopped:
|
||||
case AudioProcessingState.completed:
|
||||
return entity.PlaybackState.stopped;
|
||||
default:
|
||||
return entity.PlaybackState.none;
|
||||
|
|
|
@ -93,8 +93,8 @@ class AudioRepositoryImpl implements AudioRepository {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> removeQueueItem(int index) async {
|
||||
await _audioManager.removeQueueItem(index);
|
||||
Future<Either<Failure, void>> removeQueueIndex(int index) async {
|
||||
await _audioManager.removeQueueIndex(index);
|
||||
return const Right(null);
|
||||
}
|
||||
}
|
||||
|
|
72
pubspec.lock
72
pubspec.lock
|
@ -39,10 +39,12 @@ packages:
|
|||
audio_service:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: audio_service
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.15.2"
|
||||
path: "."
|
||||
ref: one-isolate
|
||||
resolved-ref: "88559958955f17c54097f34b9a6365a23eac4899"
|
||||
url: "https://github.com/ryanheise/audio_service.git"
|
||||
source: git
|
||||
version: "0.15.3"
|
||||
audio_session:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -358,28 +360,28 @@ packages:
|
|||
name: json_annotation
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "3.1.1"
|
||||
just_audio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../just_audio/just_audio"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.5.4+1"
|
||||
name: just_audio
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.7"
|
||||
just_audio_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "../just_audio/just_audio_platform_interface"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.2.0"
|
||||
name: just_audio_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
just_audio_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "../just_audio/just_audio_web"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.1.0"
|
||||
name: just_audio_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
logging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -414,28 +416,28 @@ packages:
|
|||
name: mobx
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1+3"
|
||||
version: "1.2.1+4"
|
||||
mobx_codegen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: mobx_codegen
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1+1"
|
||||
version: "1.1.2"
|
||||
mockito:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mockito
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
version: "4.1.3"
|
||||
moor:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: moor
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
version: "3.4.0"
|
||||
moor_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -456,14 +458,14 @@ packages:
|
|||
name: node_interop
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.2.1"
|
||||
node_io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.2.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -484,7 +486,7 @@ packages:
|
|||
name: path_provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.6.21"
|
||||
version: "1.6.24"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -498,21 +500,21 @@ packages:
|
|||
name: path_provider_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.4+4"
|
||||
version: "0.0.4+6"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "1.0.4"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.4+1"
|
||||
version: "0.0.4+3"
|
||||
pedantic:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -554,7 +556,7 @@ packages:
|
|||
name: provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.3.2+2"
|
||||
version: "4.3.2+3"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -575,14 +577,14 @@ packages:
|
|||
name: quiver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.5"
|
||||
recase:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: recase
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
version: "3.0.1"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -629,7 +631,7 @@ packages:
|
|||
name: sqflite
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1+1"
|
||||
version: "1.3.2+1"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -643,7 +645,7 @@ packages:
|
|||
name: sqlite3
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.7"
|
||||
version: "0.1.8"
|
||||
sqlite3_flutter_libs:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -755,7 +757,7 @@ packages:
|
|||
name: win32
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.3"
|
||||
version: "1.7.4"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -771,5 +773,5 @@ packages:
|
|||
source: hosted
|
||||
version: "2.2.1"
|
||||
sdks:
|
||||
dart: ">=2.10.0-110 <2.11.0"
|
||||
flutter: ">=1.20.0 <2.0.0"
|
||||
dart: ">=2.10.2 <2.11.0"
|
||||
flutter: ">=1.22.2 <2.0.0"
|
||||
|
|
|
@ -8,7 +8,10 @@ environment:
|
|||
flutter: ">=1.20.0"
|
||||
|
||||
dependencies:
|
||||
audio_service: ^0.15.0
|
||||
audio_service: # ^0.15.0
|
||||
git:
|
||||
url: https://github.com/ryanheise/audio_service.git
|
||||
ref: one-isolate
|
||||
audio_session: ^0.0.3
|
||||
dartz: ^0.9.1
|
||||
equatable: ^1.1.0
|
||||
|
@ -17,8 +20,7 @@ dependencies:
|
|||
flutter_audio_query: ^0.3.5
|
||||
flutter_mobx: ^1.1.0
|
||||
get_it: ^4.0.2
|
||||
just_audio: # ^0.4.0
|
||||
path: ../just_audio/just_audio
|
||||
just_audio: ^0.5.0
|
||||
logging: ^0.11.4
|
||||
mobx: ^1.1.1
|
||||
mockito: ^4.1.1
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:mucke/system/repositories/audio_repository_impl.dart';
|
|||
import 'package:mucke/system/repositories/music_data_repository_impl.dart';
|
||||
import 'package:mucke/system/audio/audio_manager_contract.dart';
|
||||
import 'package:mucke/system/datasources/local_music_fetcher.dart';
|
||||
import 'package:mucke/system/audio/audio_player_task.dart';
|
||||
import 'package:mucke/system/datasources/local_music_fetcher_contract.dart';
|
||||
import 'package:mucke/system/audio/audio_manager.dart';
|
||||
import 'package:mucke/system/datasources/moor_music_data_source.dart';
|
||||
|
@ -20,7 +19,6 @@ 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';
|
||||
|
|
Loading…
Add table
Reference in a new issue