move to single-isolate audio_service

This commit is contained in:
Moritz Weber 2020-12-08 20:54:41 +01:00
parent 3570f6cdbd
commit 5f22ed01af
17 changed files with 200 additions and 310 deletions

View file

@ -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
}
}
}

View file

@ -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"/>

View file

@ -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);
}

View file

@ -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());

View file

@ -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(),
},
),
);
}

View file

@ -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'),

View file

@ -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);
}
}

View file

@ -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,
);
}
}

View file

@ -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]);
}
}
}

View file

@ -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});
}
}

View file

@ -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);
}

View file

@ -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 '''
''';
}
}

View file

@ -16,7 +16,7 @@ class PlaybackStateModel {
}
switch (playbackState.processingState) {
case AudioProcessingState.stopped:
case AudioProcessingState.completed:
return entity.PlaybackState.stopped;
default:
return entity.PlaybackState.none;

View file

@ -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);
}
}

View file

@ -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"

View file

@ -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

View file

@ -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';