refactored audio_handler

This commit is contained in:
Moritz Weber 2020-12-13 12:37:08 +01:00
parent 4f8cd4b9ee
commit 9c58244bb4
11 changed files with 450 additions and 256 deletions

View file

@ -0,0 +1,22 @@
class PlayerState {
PlayerState(this.playing, this.processingState);
final bool playing;
final ProcessingState processingState;
}
/// Enumerates the different processing states of a player.
enum ProcessingState {
/// The player has not loaded an [AudioSource].
none,
/// The player is loading an [AudioSource].
loading,
/// The player is buffering audio and unable to play.
buffering,
/// The player is has enough audio buffered and is able to play.
ready,
/// The player has reached the end of the audio.
completed,
}

View file

@ -2,6 +2,7 @@ import 'package:audio_service/audio_service.dart';
import 'package:device_info/device_info.dart';
import 'package:flutter_audio_query/flutter_audio_query.dart';
import 'package:get_it/get_it.dart';
import 'package:just_audio/just_audio.dart' as ja;
import 'domain/repositories/audio_repository.dart';
import 'domain/repositories/music_data_repository.dart';
@ -11,6 +12,9 @@ import 'presentation/state/navigation_store.dart';
import 'system/audio/audio_handler.dart';
import 'system/audio/audio_manager.dart';
import 'system/audio/audio_manager_contract.dart';
import 'system/audio/audio_player_contract.dart';
import 'system/audio/audio_player_impl.dart';
import 'system/audio/queue_generator.dart';
import 'system/datasources/local_music_fetcher.dart';
import 'system/datasources/local_music_fetcher_contract.dart';
import 'system/datasources/moor_music_data_source.dart';
@ -73,16 +77,26 @@ Future<void> setupGetIt() async {
);
getIt.registerLazySingleton<AudioManager>(() => AudioManagerImpl(getIt()));
final AudioPlayer audioPlayer = AudioPlayerImpl(
ja.AudioPlayer(),
QueueGenerator(getIt()),
);
getIt.registerLazySingleton<AudioPlayer>(() => audioPlayer);
final _audioHandler = await AudioService.init(
builder: () => MyAudioHandler(getIt()),
config: AudioServiceConfig(
androidNotificationChannelName: 'mucke',
androidEnableQueue: true,
),
);
builder: () => MyAudioHandler(getIt(), getIt()),
config: AudioServiceConfig(
androidNotificationChannelName: 'mucke',
androidEnableQueue: true,
),
);
getIt.registerLazySingleton<AudioHandler>(() => _audioHandler);
getIt.registerLazySingleton<QueueGenerator>(() => QueueGenerator(getIt()));
// external
getIt.registerFactory<ja.AudioPlayer>(() => ja.AudioPlayer());
getIt.registerLazySingleton<FlutterAudioQuery>(() => FlutterAudioQuery());
getIt.registerLazySingleton<DeviceInfoPlugin>(() => DeviceInfoPlugin());

View file

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
@ -34,8 +36,8 @@ class TimeProgressIndicator extends StatelessWidget {
),
alignment: Alignment.centerLeft,
child: FractionallySizedBox(
widthFactor:
audioStore.currentPositionStream.value / duration,
widthFactor: min(
audioStore.currentPositionStream.value / duration, 1.0),
heightFactor: 1.0,
child: Container(
height: double.infinity,

View file

@ -1,79 +1,45 @@
import 'dart:math';
import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';
import 'package:logging/logging.dart';
import '../../domain/entities/player_state.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../datasources/music_data_source_contract.dart';
import '../models/queue_item.dart';
import '../models/song_model.dart';
import 'queue_generator.dart';
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';
import 'audio_player_contract.dart';
import 'stream_constants.dart';
class MyAudioHandler extends BaseAudioHandler {
MyAudioHandler(this._musicDataSource);
MyAudioHandler(this._musicDataSource, this._audioPlayer) {
_audioPlayer.queueStream.listen((event) {
_handleSetQueue(event);
});
_audioPlayer.currentIndexStream.listen((event) => _handleIndexChange(event));
final _audioPlayer = AudioPlayer();
_audioPlayer.currentSongStream.listen((songModel) {
mediaItemSubject.add(songModel.toMediaItem());
});
_audioPlayer.playerStateStream.listen((event) {
_handlePlayerState(event);
});
_audioPlayer.shuffleModeStream.listen((shuffleMode) {
customEventSubject.add({SHUFFLE_MODE: shuffleMode});
});
}
final AudioPlayer _audioPlayer;
final MusicDataSource _musicDataSource;
QueueGenerator queueGenerator;
// TODO: confusing naming
List<MediaItem> originalPlaybackContext = <MediaItem>[];
List<QueueItem> playbackContext = <QueueItem>[];
// TODO: this is not trivial: queue is loaded by audioplayer
// this reference enables direct manipulation of the loaded queue
ConcatenatingAudioSource _queue;
List<MediaItem> mediaItemQueue;
ShuffleMode _shuffleMode = ShuffleMode.none;
ShuffleMode get shuffleMode => _shuffleMode;
set shuffleMode(ShuffleMode s) {
_shuffleMode = s;
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;
mediaItemSubject.add(mediaItemQueue[i]);
customEventSubject.add({KEY_INDEX: i});
playbackStateSubject.add(playbackState.value.copyWith(
controls: [
MediaControl.skipToPrevious,
MediaControl.pause,
MediaControl.skipToNext
],
playing: _audioPlayer.playing,
processingState: AudioProcessingState.ready,
updatePosition: _audioPlayer.position,
));
}
}
static final _log = Logger('AudioHandler');
@override
Future<void> stop() async {
await _audioPlayer.stop();
await _audioPlayer.dispose();
// await _audioPlayer.dispose();
await super.stop();
}
@ -99,16 +65,12 @@ class MyAudioHandler extends BaseAudioHandler {
@override
Future<void> addQueueItem(MediaItem mediaItem) async {
await _queue.add(AudioSource.uri(Uri.file(mediaItem.id)));
mediaItemQueue.add(mediaItem);
handleSetQueue(mediaItemQueue);
_audioPlayer.addToQueue(SongModel.fromMediaItem(mediaItem));
}
@override
Future<void> customAction(String name, Map<String, dynamic> arguments) async {
switch (name) {
case INIT:
return init();
case PLAY_WITH_CONTEXT:
final context = arguments['CONTEXT'] as List<String>;
final index = arguments['INDEX'] as int;
@ -127,161 +89,84 @@ class MyAudioHandler extends BaseAudioHandler {
}
}
Future<void> handleSetQueue(List<MediaItem> mediaItemQueue) async {
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
.listen((event) => handleSequenceState(event));
queueGenerator = QueueGenerator(_musicDataSource);
}
Future<void> playWithContext(List<String> context, int index) async {
final mediaItems = await queueGenerator.getMediaItemsFromPaths(context);
playPlaylist(mediaItems, index);
final songs = <SongModel>[];
for (final path in context) {
final song = await _musicDataSource.getSongByPath(path);
songs.add(song);
}
_audioPlayer.playSongList(songs, index);
}
Future<void> onAppLifecycleResumed() async {
customEventSubject.add({SHUFFLE_MODE: shuffleMode});
customEventSubject.add({KEY_INDEX: playbackIndex});
// customEventSubject.add({SHUFFLE_MODE: shuffleMode});
// customEventSubject.add({KEY_INDEX: playbackIndex});
}
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);
mediaItemQueue = playbackContext.map((e) => e.mediaItem).toList();
// FIXME: this does not react correctly when inserted track is currently played
handleSetQueue(mediaItemQueue);
final newQueue = queueGenerator.mediaItemsToAudioSource(mediaItemQueue);
_updateQueue(newQueue, currentQueueItem);
}
void _updateQueue(
ConcatenatingAudioSource newQueue, QueueItem currentQueueItem) {
final int index = currentQueueItem.originalIndex;
_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));
playbackIndex = index;
break;
case QueueItemType.predecessor:
_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));
playbackIndex = index;
break;
}
} else {
_queue.addAll(newQueue.children.sublist(1));
}
_audioPlayer.setShuffleMode(mode, true);
}
Future<void> shuffleAll() async {
shuffleMode = ShuffleMode.standard;
_audioPlayer.setShuffleMode(ShuffleMode.plus, false);
final List<SongModel> songs = await _musicDataSource.getSongs();
final List<MediaItem> mediaItems =
songs.map((song) => song.toMediaItem()).toList();
final rng = Random();
final index = rng.nextInt(mediaItems.length);
final index = rng.nextInt(songs.length);
playPlaylist(mediaItems, index);
}
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);
mediaItemQueue = playbackContext.map((e) => e.mediaItem).toList();
handleSetQueue(mediaItemQueue);
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));
_audioPlayer.playSongList(songs, index);
}
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
_log.info('move: $oldIndex -> $newIndex');
final MediaItem mediaItem = mediaItemQueue.removeAt(oldIndex);
final index = newIndex < oldIndex ? newIndex : newIndex - 1;
mediaItemQueue.insert(index, mediaItem);
handleSetQueue(mediaItemQueue);
_queue.move(oldIndex, index);
_audioPlayer.moveQueueItem(oldIndex, newIndex);
}
Future<void> removeQueueIndex(int index) async {
mediaItemQueue.removeAt(index);
handleSetQueue(mediaItemQueue);
_queue.removeAt(index);
_audioPlayer.removeQueueIndex(index);
}
void handlePlayerState(PlayerState ps) {
_log.info('handlePlayerState called');
if (ps.processingState == ProcessingState.ready && ps.playing) {
void _handleSetQueue(List<SongModel> queue) {
_musicDataSource.setQueue(queue);
final mediaItems = queue.map((e) => e.toMediaItem()).toList();
queueSubject.add(mediaItems);
}
void _handleIndexChange(int index) {
_log.info('index: $index');
if (index != null) {
customEventSubject.add({KEY_INDEX: index});
playbackStateSubject.add(playbackState.value.copyWith(
controls: [
MediaControl.skipToPrevious,
MediaControl.pause,
MediaControl.skipToNext
MediaControl.skipToNext,
],
playing: true,
playing: _audioPlayer.playerStateStream.value.playing,
processingState: AudioProcessingState.ready,
updatePosition: _audioPlayer.position,
));
} else if (ps.processingState == ProcessingState.ready && !ps.playing) {
playbackStateSubject.add(playbackState.value.copyWith(
controls: [
MediaControl.skipToPrevious,
MediaControl.play,
MediaControl.skipToNext
],
processingState: AudioProcessingState.ready,
updatePosition: _audioPlayer.position,
playing: false,
updatePosition: const Duration(milliseconds: 0), // _audioPlayer.positionStream.value,
));
}
}
// TODO: this can only be a temporary solution! gets called too often.
void handleSequenceState(SequenceState st) {
_log.info('handleSequenceState called');
if (0 <= playbackIndex && playbackIndex < playbackContext.length) {
_log.info('handleSequenceState: setting MediaItem');
mediaItemSubject.add(mediaItemQueue[playbackIndex]);
void _handlePlayerState(PlayerState ps) {
_log.info('handlePlayerState called');
if (ps.processingState == ProcessingState.ready && ps.playing) {
playbackStateSubject.add(playbackState.value.copyWith(
controls: [MediaControl.skipToPrevious, MediaControl.pause, MediaControl.skipToNext],
playing: true,
processingState: AudioProcessingState.ready,
updatePosition: _audioPlayer.positionStream.value,
));
} else if (ps.processingState == ProcessingState.ready && !ps.playing) {
playbackStateSubject.add(playbackState.value.copyWith(
controls: [MediaControl.skipToPrevious, MediaControl.play, MediaControl.skipToNext],
processingState: AudioProcessingState.ready,
updatePosition: _audioPlayer.positionStream.value,
playing: false,
));
}
}
}

View file

@ -6,15 +6,13 @@ 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 'stream_constants.dart';
typedef Conversion<S, T> = T Function(S);
class AudioManagerImpl implements AudioManager {
AudioManagerImpl(this._audioHandler) {
_audioHandler.customAction(INIT, null);
_audioHandler.customEventStream.listen((event) {
final data = event as Map<String, dynamic>;
if (data.containsKey(KEY_INDEX)) {

View file

@ -0,0 +1,33 @@
import 'package:rxdart/rxdart.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../models/player_state_model.dart';
import '../models/queue_item.dart';
import '../models/song_model.dart';
abstract class AudioPlayer {
ValueStream<int> get currentIndexStream;
ValueStream<SongModel> get currentSongStream;
ValueStream<PlayerStateModel> get playerStateStream;
ValueStream<Duration> get positionStream;
ValueStream<List<SongModel>> get queueStream;
ValueStream<ShuffleMode> get shuffleModeStream;
Future<void> play();
Future<void> pause();
Future<void> stop();
Future<void> seekToNext();
Future<void> seekToPrevious();
Future<void> dispose();
Future<void> loadQueue(List<QueueItem> queue);
Future<void> addToQueue(SongModel song);
Future<void> moveQueueItem(int oldIndex, int newIndex);
Future<void> removeQueueIndex(int index);
Future<void> setIndex(int index);
Future<void> setShuffleMode(ShuffleMode shuffleMode, bool updateQueue);
Future<void> playSongList(List<SongModel> songs, int startIndex);
}

View file

@ -0,0 +1,191 @@
import 'package:just_audio/just_audio.dart' as ja;
import 'package:rxdart/rxdart.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../models/player_state_model.dart';
import '../models/queue_item.dart';
import '../models/song_model.dart';
import 'audio_player_contract.dart';
import 'queue_generator.dart';
class AudioPlayerImpl implements AudioPlayer {
AudioPlayerImpl(this._audioPlayer, this._queueGenerator) {
_audioPlayer.currentIndexStream.listen((event) {
_currentIndexSubject.add(event);
_currentSongSubject.add(_queueSubject.value[event]);
});
_audioPlayer.positionStream.listen((event) {
_positionSubject.add(event);
});
_audioPlayer.playerStateStream.listen((event) {
_playerStateSubject.add(PlayerStateModel.fromJAPlayerState(event));
});
_queueSubject.listen((event) {
_currentSongSubject.add(event[_currentIndexSubject.value]);
});
}
final ja.AudioPlayer _audioPlayer;
ja.ConcatenatingAudioSource _audioSource;
final QueueGenerator _queueGenerator;
List<SongModel> _inputQueue;
List<QueueItem> _queue;
final BehaviorSubject<int> _currentIndexSubject = BehaviorSubject();
final BehaviorSubject<SongModel> _currentSongSubject = BehaviorSubject();
final BehaviorSubject<PlayerStateModel> _playerStateSubject = BehaviorSubject();
final BehaviorSubject<Duration> _positionSubject = BehaviorSubject();
final BehaviorSubject<List<SongModel>> _queueSubject = BehaviorSubject.seeded([]);
final BehaviorSubject<ShuffleMode> _shuffleModeSubject = BehaviorSubject.seeded(ShuffleMode.none);
@override
ValueStream<int> get currentIndexStream => _currentIndexSubject.stream;
@override
ValueStream<SongModel> get currentSongStream => _currentSongSubject.stream;
@override
ValueStream<PlayerStateModel> get playerStateStream => _playerStateSubject.stream;
@override
ValueStream<Duration> get positionStream => _positionSubject.stream;
@override
ValueStream<List<SongModel>> get queueStream => _queueSubject.stream;
@override
ValueStream<ShuffleMode> get shuffleModeStream => _shuffleModeSubject.stream;
@override
Future<void> dispose() async {
await _audioPlayer.dispose();
}
@override
Future<void> loadQueue(List<QueueItem> queue) {
// TODO: implement loadQueue
throw UnimplementedError();
}
@override
Future<void> pause() async {
await _audioPlayer.pause();
}
@override
Future<void> play() async {
await _audioPlayer.play();
}
@override
Future<void> playSongList(List<SongModel> songs, int startIndex) async {
_inputQueue = songs;
final firstSong = songs.sublist(startIndex, startIndex + 1);
_queueSubject.add(firstSong);
_audioSource = _queueGenerator.songModelsToAudioSource(firstSong);
_audioPlayer.play();
await _audioPlayer.load(_audioSource, initialIndex: 0);
_queue = await _queueGenerator.generateQueue(_shuffleModeSubject.value, songs, startIndex);
final songModelQueue = _queue.map((e) => e.song).toList();
_queueSubject.add(songModelQueue);
final int splitIndex = _shuffleModeSubject.value == ShuffleMode.none ? startIndex : 0;
final newQueue = _queueGenerator.songModelsToAudioSource(songModelQueue);
_audioSource.insertAll(0, newQueue.children.sublist(0, splitIndex));
_audioSource.addAll(newQueue.children.sublist(splitIndex + 1, newQueue.length));
}
@override
Future<void> seekToNext() async {
await _audioPlayer.seekToNext();
}
@override
Future<void> seekToPrevious() async {
await _audioPlayer.seekToPrevious();
}
@override
Future<void> setIndex(int index) async {
await _audioPlayer.seek(const Duration(seconds: 0), index: index);
}
@override
Future<void> stop() async {
_audioPlayer.stop();
}
@override
Future<void> addToQueue(SongModel song) async {
await _audioSource.add(ja.AudioSource.uri(Uri.file(song.path)));
_queue.add(QueueItem(song, originalIndex: -1, type: QueueItemType.added));
_queueSubject.add(_queue.map((e) => e.song).toList());
}
@override
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
final QueueItem queueItem = _queue.removeAt(oldIndex);
final index = newIndex < oldIndex ? newIndex : newIndex - 1;
_queue.insert(index, queueItem);
_queueSubject.add(_queue.map((e) => e.song).toList());
await _audioSource.move(oldIndex, index);
}
@override
Future<void> removeQueueIndex(int index) async {
_queue.removeAt(index);
_queueSubject.add(_queue.map((e) => e.song).toList());
await _audioSource.removeAt(index);
}
@override
Future<void> setShuffleMode(ShuffleMode shuffleMode, bool updateQueue) async {
_shuffleModeSubject.add(shuffleMode);
if (updateQueue) {
final QueueItem currentQueueItem = _queue[_currentIndexSubject.value];
final int index = currentQueueItem.originalIndex;
_queue = await _queueGenerator.generateQueue(shuffleMode, _inputQueue, index);
// TODO: maybe refactor _queue to a subject and listen for changes
final songModelQueue = _queue.map((e) => e.song).toList();
_queueSubject.add(songModelQueue);
final newQueue = _queueGenerator.songModelsToAudioSource(songModelQueue);
_updateQueue(newQueue, currentQueueItem);
}
}
void _updateQueue(ja.ConcatenatingAudioSource newQueue, QueueItem currentQueueItem) {
final int index = currentQueueItem.originalIndex;
_audioSource.removeRange(0, _currentIndexSubject.value);
_audioSource.removeRange(1, _audioSource.length);
if (_shuffleModeSubject.value == ShuffleMode.none) {
switch (currentQueueItem.type) {
case QueueItemType.added:
case QueueItemType.standard:
_audioSource.insertAll(0, newQueue.children.sublist(0, index));
_audioSource.addAll(newQueue.children.sublist(index + 1));
break;
case QueueItemType.predecessor:
_audioSource.insertAll(0, newQueue.children.sublist(0, index));
_audioSource.addAll(newQueue.children.sublist(index));
break;
case QueueItemType.successor:
_audioSource.insertAll(0, newQueue.children.sublist(0, index + 1));
_audioSource.addAll(newQueue.children.sublist(index + 1));
break;
}
_currentIndexSubject.add(index);
} else {
_audioSource.addAll(newQueue.children.sublist(1));
}
}
}

View file

@ -11,35 +11,22 @@ class QueueGenerator {
final MusicDataSource _musicDataSource;
// TODO: test
// TODO: optimize -> too slow for whole library
// fetching all songs together and preparing playback takes ~500ms compared to ~10.000ms individually
Future<List<MediaItem>> getMediaItemsFromPaths(List<String> paths) async {
final mediaItems = <MediaItem>[];
for (final path in paths) {
final song = await _musicDataSource.getSongByPath(path);
mediaItems.add(song.toMediaItem());
}
return mediaItems;
}
Future<List<QueueItem>> generateQueue(
ShuffleMode shuffleMode,
List<MediaItem> mediaItems,
List<SongModel> songModels,
int startIndex,
) async {
List<QueueItem> queue;
switch (shuffleMode) {
case ShuffleMode.none:
queue = _generateNormalQueue(mediaItems);
queue = _generateNormalQueue(songModels);
break;
case ShuffleMode.standard:
queue = _generateShuffleQueue(mediaItems, startIndex);
queue = _generateShuffleQueue(songModels, startIndex);
break;
case ShuffleMode.plus:
queue = await _generateShufflePlusQueue(mediaItems, startIndex);
queue = await _generateShufflePlusQueue(songModels, startIndex);
}
return queue;
@ -47,55 +34,60 @@ class QueueGenerator {
ConcatenatingAudioSource mediaItemsToAudioSource(List<MediaItem> mediaItems) {
return ConcatenatingAudioSource(
children: mediaItems
.map((MediaItem m) => AudioSource.uri(Uri.file(m.id)))
.toList());
children: mediaItems.map((MediaItem m) => AudioSource.uri(Uri.file(m.id))).toList(),
);
}
List<QueueItem> _generateNormalQueue(List<MediaItem> mediaItems) {
ConcatenatingAudioSource songModelsToAudioSource(List<SongModel> songModels) {
return ConcatenatingAudioSource(
children: songModels.map((SongModel m) => AudioSource.uri(Uri.file(m.path))).toList(),
);
}
List<QueueItem> _generateNormalQueue(List<SongModel> songs) {
return List<QueueItem>.generate(
mediaItems.length,
songs.length,
(i) => QueueItem(
mediaItems[i],
songs[i],
originalIndex: i,
),
);
}
List<QueueItem> _generateShuffleQueue(
List<MediaItem> mediaItems,
List<SongModel> songs,
int startIndex,
) {
final List<QueueItem> queue = List<QueueItem>.generate(
mediaItems.length,
songs.length,
(i) => QueueItem(
mediaItems[i],
songs[i],
originalIndex: i,
),
);
queue.removeAt(startIndex);
queue.shuffle();
final first = QueueItem(
mediaItems[startIndex],
songs[startIndex],
originalIndex: startIndex,
);
return [first] + queue;
}
Future<List<QueueItem>> _generateShufflePlusQueue(
List<MediaItem> mediaItems,
List<SongModel> songs,
int startIndex,
) async {
final List<QueueItem> queue = await _getQueueItemWithLinks(
mediaItems[startIndex],
songs[startIndex],
startIndex,
);
final List<int> indices = [];
// filter mediaitem list
// TODO: multiply higher rated songs
for (var i = 0; i < mediaItems.length; i++) {
if (i != startIndex && !(mediaItems[i].extras['blocked'] as bool)) {
for (var i = 0; i < songs.length; i++) {
if (i != startIndex && !songs[i].blocked) {
indices.add(i);
}
}
@ -104,9 +96,9 @@ class QueueGenerator {
for (var i = 0; i < indices.length; i++) {
final int index = indices[i];
final MediaItem mediaItem = mediaItems[index];
final SongModel song = songs[index];
queue.addAll(await _getQueueItemWithLinks(mediaItem, index));
queue.addAll(await _getQueueItemWithLinks(song, index));
}
return queue;
@ -114,13 +106,13 @@ class QueueGenerator {
// TODO: naming things is hard
Future<List<QueueItem>> _getQueueItemWithLinks(
MediaItem mediaItem,
SongModel song,
int index,
) async {
final List<QueueItem> queueItems = [];
final predecessors = await _getPredecessors(mediaItem);
final successors = await _getSuccessors(mediaItem);
final predecessors = await _getPredecessors(song);
final successors = await _getSuccessors(song);
for (final p in predecessors) {
queueItems.add(QueueItem(
@ -131,7 +123,7 @@ class QueueGenerator {
}
queueItems.add(QueueItem(
mediaItem,
song,
originalIndex: index,
));
@ -146,31 +138,27 @@ class QueueGenerator {
return queueItems;
}
Future<List<MediaItem>> _getPredecessors(MediaItem mediaItem) async {
final List<MediaItem> mediaItems = [];
MediaItem currentMediaItem = mediaItem;
Future<List<SongModel>> _getPredecessors(SongModel song) async {
final List<SongModel> songs = [];
SongModel currentSong = song;
while (currentMediaItem.previous != null) {
currentMediaItem =
(await _musicDataSource.getSongByPath(currentMediaItem.previous))
.toMediaItem();
mediaItems.add(currentMediaItem);
while (currentSong.previous != null) {
currentSong = await _musicDataSource.getSongByPath(currentSong.previous);
songs.add(currentSong);
}
return mediaItems.reversed.toList();
return songs.reversed.toList();
}
Future<List<MediaItem>> _getSuccessors(MediaItem mediaItem) async {
final List<MediaItem> mediaItems = [];
MediaItem currentMediaItem = mediaItem;
Future<List<SongModel>> _getSuccessors(SongModel song) async {
final List<SongModel> songs = [];
SongModel currentSong = song;
while (currentMediaItem.next != null) {
currentMediaItem =
(await _musicDataSource.getSongByPath(currentMediaItem.next))
.toMediaItem();
mediaItems.add(currentMediaItem);
while (currentSong.next != null) {
currentSong = await _musicDataSource.getSongByPath(currentSong.next);
songs.add(currentSong);
}
return mediaItems.toList();
return songs.toList();
}
}

View file

@ -0,0 +1,9 @@
const String KEY_INDEX = 'INDEX';
const String SHUFFLE_MODE = 'SHUFFLE_MODE';
const String PLAY_WITH_CONTEXT = 'PLAY_WITH_CONTEXT';
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';

View file

@ -0,0 +1,48 @@
import 'package:just_audio/just_audio.dart' as ja;
import '../../domain/entities/player_state.dart';
class PlayerStateModel extends PlayerState {
PlayerStateModel(bool playing, ProcessingState processingState)
: super(playing, processingState);
factory PlayerStateModel.fromJAPlayerState(ja.PlayerState playerState) =>
PlayerStateModel(
playerState.playing,
playerState.processingState.toProcessingState(),
);
}
// extension JAProcessingStateExt on ProcessingState {
// ProcessingState fromJAProcessingState(ja.ProcessingState processingState) {
// switch (processingState) {
// case ja.ProcessingState.loading:
// return ProcessingState.loading;
// case ja.ProcessingState.buffering:
// return ProcessingState.buffering;
// case ja.ProcessingState.ready:
// return ProcessingState.ready;
// case ja.ProcessingState.completed:
// return ProcessingState.completed;
// default:
// return ProcessingState.none;
// }
// }
// }
extension ProcessingStateExt on ja.ProcessingState {
ProcessingState toProcessingState() {
switch (this) {
case ja.ProcessingState.loading:
return ProcessingState.loading;
case ja.ProcessingState.buffering:
return ProcessingState.buffering;
case ja.ProcessingState.ready:
return ProcessingState.ready;
case ja.ProcessingState.completed:
return ProcessingState.completed;
default:
return ProcessingState.none;
}
}
}

View file

@ -1,11 +1,15 @@
import 'package:audio_service/audio_service.dart';
import 'song_model.dart';
class QueueItem {
QueueItem(this.mediaItem, {this.originalIndex, this.type = QueueItemType.standard});
QueueItem(
this.song, {
this.originalIndex,
this.type = QueueItemType.standard,
});
final MediaItem mediaItem;
final SongModel song;
final int originalIndex;
final QueueItemType type;
}
enum QueueItemType { standard, predecessor, successor }
enum QueueItemType { standard, predecessor, successor, added }