mucke/lib/system/datasources/audio_player_task.dart
2020-09-20 12:33:11 +02:00

233 lines
7.1 KiB
Dart

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:moor/isolate.dart';
import 'package:moor/moor.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../models/song_model.dart';
import 'moor_music_data_source.dart';
import 'queue_manager.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';
class AudioPlayerTask extends BackgroundAudioTask {
final audioPlayer = AudioPlayer();
MoorMusicDataSource moorMusicDataSource;
QueueManager qm;
List<MediaItem> originalPlaybackContext = <MediaItem>[];
List<MediaItem> playbackContext = <MediaItem>[];
ConcatenatingAudioSource queue;
List<int> permutation;
ShuffleMode _shuffleMode = ShuffleMode.none;
ShuffleMode get shuffleMode => _shuffleMode;
set shuffleMode(ShuffleMode s) {
_shuffleMode = s;
AudioServiceBackground.sendCustomEvent({SET_SHUFFLE_MODE: s.toString()});
}
int _playbackIndex = -1;
int get playbackIndex => _playbackIndex;
set playbackIndex(int i) {
if (i != null) {
_playbackIndex = i;
AudioServiceBackground.setMediaItem(playbackContext[i]);
AudioServiceBackground.sendCustomEvent({KEY_INDEX: i});
AudioServiceBackground.setState(
controls: [
MediaControl.skipToPrevious,
MediaControl.pause,
MediaControl.skipToNext
],
playing: true, // FIXME: not necessarily true
processingState: AudioProcessingState.ready,
updateTime:
Duration(milliseconds: DateTime.now().millisecondsSinceEpoch),
position: const Duration(
milliseconds: 0), // FIXME: not true for shuffle mode changes
);
}
}
Duration position;
@override
Future<void> onStop() async {
await audioPlayer.stop();
await audioPlayer.dispose();
await super.onStop();
}
// @override
// Future<void> onClose() async {
// audioPlayer.stop();
// AudioServiceBackground.setState(controls: null, processingState: null, playing: false);
// AudioServiceBackground.setMediaItem(null);
// }
@override
Future<void> onPlay() async {
audioPlayer.play();
}
@override
Future<void> onPause() async {
await audioPlayer.pause();
}
@override
Future<void> onSkipToNext() async {
audioPlayer.seekToNext();
}
@override
Future<void> onSkipToPrevious() async {
audioPlayer.seekToPrevious();
}
@override
Future<void> onCustomAction(String name, 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;
return playWithContext(context, index);
case APP_LIFECYCLE_RESUMED:
return onAppLifecycleResumed();
case SET_SHUFFLE_MODE:
return setShuffleMode((arguments as String).toShuffleMode());
case SHUFFLE_ALL:
return shuffleAll();
default:
}
}
Future<void> init() async {
print('AudioPlayerTask.init');
audioPlayer.positionStream.listen((position) => handlePosition(position));
audioPlayer.playerStateStream.listen((event) => handlePlayerState(event));
audioPlayer.sequenceStateStream
.listen((event) => playbackIndex = event?.currentIndex);
final connectPort = IsolateNameServer.lookupPortByName(MOOR_ISOLATE);
final MoorIsolate moorIsolate = MoorIsolate.fromConnectPort(connectPort);
final DatabaseConnection databaseConnection = await moorIsolate.connect();
moorMusicDataSource = MoorMusicDataSource.connect(databaseConnection);
qm = QueueManager(moorMusicDataSource);
}
Future<void> playWithContext(List<String> context, int index) async {
final mediaItems = await qm.getMediaItemsFromPaths(context);
playPlaylist(mediaItems, index);
}
Future<void> onAppLifecycleResumed() async {
AudioServiceBackground.sendCustomEvent({KEY_INDEX: playbackIndex});
AudioServiceBackground.sendCustomEvent(
{SET_SHUFFLE_MODE: shuffleMode.toString()});
}
// FIXME: position (duration) reset when changing mode
Future<void> setShuffleMode(ShuffleMode mode) async {
shuffleMode = mode;
final index = permutation[playbackIndex];
permutation =
qm.generatePermutation(shuffleMode, originalPlaybackContext, index);
playbackContext =
qm.getPermutatedSongs(originalPlaybackContext, permutation);
AudioServiceBackground.setQueue(playbackContext);
final newQueue = qm.mediaItemsToAudioSource(playbackContext);
queue.removeRange(0, playbackIndex);
queue.removeRange(1, queue.length);
if (shuffleMode == ShuffleMode.none) {
queue.insertAll(0, newQueue.children.sublist(0, index));
queue.addAll(newQueue.children.sublist(index + 1));
playbackIndex = index;
} else {
queue.addAll(newQueue.children.sublist(1));
}
}
Future<void> shuffleAll() async {
shuffleMode = ShuffleMode.standard;
final List<SongModel> songs = await moorMusicDataSource.getSongs();
final List<MediaItem> mediaItems =
songs.map((song) => song.toMediaItem()).toList();
final rng = Random();
final index = rng.nextInt(mediaItems.length);
playPlaylist(mediaItems, index);
}
Future<void> playPlaylist(List<MediaItem> mediaItems, int index) async {
permutation = qm.generatePermutation(shuffleMode, mediaItems, index);
playbackContext = qm.getPermutatedSongs(mediaItems, permutation);
originalPlaybackContext = mediaItems;
AudioServiceBackground.setQueue(playbackContext);
queue = qm.mediaItemsToAudioSource(playbackContext);
await audioPlayer.load(queue);
if (shuffleMode == ShuffleMode.none) {
await audioPlayer.seek(const Duration(milliseconds: 0), index: index);
}
audioPlayer.play();
}
void handlePosition(Duration position) {
this.position = position;
}
void handlePlayerState(PlayerState ps) {
if (ps.processingState == ProcessingState.ready && ps.playing) {
AudioServiceBackground.setState(
controls: [
MediaControl.skipToPrevious,
MediaControl.pause,
MediaControl.skipToNext
],
playing: true,
processingState: AudioProcessingState.ready,
updateTime:
Duration(milliseconds: DateTime.now().millisecondsSinceEpoch),
position: position,
);
} else if (ps.processingState == ProcessingState.ready && !ps.playing) {
AudioServiceBackground.setState(
controls: [
MediaControl.skipToPrevious,
MediaControl.play,
MediaControl.skipToNext
],
processingState: AudioProcessingState.ready,
updateTime:
Duration(milliseconds: DateTime.now().millisecondsSinceEpoch),
position: position,
playing: false,
);
}
}
}