queue persistence implemented

This commit is contained in:
Moritz Weber 2020-12-15 21:09:58 +01:00
parent 9c58244bb4
commit 9e4459bcc3
12 changed files with 489 additions and 124 deletions

View file

@ -1,4 +1,4 @@
import 'song_model.dart';
import 'song.dart';
class QueueItem {
QueueItem(
@ -7,7 +7,7 @@ class QueueItem {
this.type = QueueItemType.standard,
});
final SongModel song;
final Song song;
final int originalIndex;
final QueueItemType type;
}

View file

@ -9,6 +9,7 @@ abstract class MusicDataRepository {
Stream<List<Song>> get songStream;
Stream<List<Song>> getAlbumSongStream(Album album);
Stream<List<Song>> get queueStream;
Stream<int> get currentIndexStream;
Future<Either<Failure, List<Song>>> getSongs();
Future<Either<Failure, List<Song>>> getSongsFromAlbum(Album album);

View file

@ -18,19 +18,17 @@ class AudioStore extends _AudioStore with _$AudioStore {
abstract class _AudioStore with Store {
_AudioStore(this._audioRepository, this._musicDataRepository) {
currentSongStream =
_audioRepository.currentSongStream.distinct().asObservable();
currentSongStream = _audioRepository.currentSongStream.distinct().asObservable();
currentPositionStream =
_audioRepository.currentPositionStream.asObservable(initialValue: 0);
currentPositionStream = _audioRepository.currentPositionStream.asObservable(initialValue: 0);
queueStream =
_musicDataRepository.queueStream.asObservable(initialValue: []);
queueStream = _musicDataRepository.queueStream.asObservable();
queueIndexStream = _audioRepository.queueIndexStream.asObservable();
queueIndexStream = _musicDataRepository.currentIndexStream.asObservable();
// queueIndexStream = _audioRepository.queueIndexStream.asObservable();
shuffleModeStream = _audioRepository.shuffleModeStream
.asObservable(initialValue: ShuffleMode.none);
shuffleModeStream =
_audioRepository.shuffleModeStream.asObservable(initialValue: ShuffleMode.none);
playbackStateStream = _audioRepository.playbackStateStream.asObservable();
}
@ -44,9 +42,14 @@ abstract class _AudioStore with Store {
@computed
Song get currentSong {
print('currentSong!!!');
if (queueStream.value != [] && queueIndexStream.status == StreamStatus.active) {
final song = queueStream.value[queueIndexStream.value];
return song;
print(queueStream.value);
print(queueIndexStream.value);
if (queueStream.value != null && queueIndexStream.value != null) {
if (queueIndexStream.value < queueStream.value.length) {
final song = queueStream.value[queueIndexStream.value];
return song;
}
}
return null;

View file

@ -6,6 +6,7 @@ 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_model.dart';
import '../models/song_model.dart';
import 'audio_player_contract.dart';
import 'stream_constants.dart';
@ -29,6 +30,15 @@ class MyAudioHandler extends BaseAudioHandler {
_audioPlayer.shuffleModeStream.listen((shuffleMode) {
customEventSubject.add({SHUFFLE_MODE: shuffleMode});
});
_initAudioPlayer();
}
Future<void> _initAudioPlayer() async {
_audioPlayer.loadQueue(
queue: await _musicDataSource.queueStream.first,
startIndex: await _musicDataSource.currentIndexStream.first,
);
}
final AudioPlayer _audioPlayer;
@ -126,18 +136,20 @@ class MyAudioHandler extends BaseAudioHandler {
_audioPlayer.removeQueueIndex(index);
}
void _handleSetQueue(List<SongModel> queue) {
void _handleSetQueue(List<QueueItemModel> queue) {
print('handleSetQueue');
_musicDataSource.setQueue(queue);
final mediaItems = queue.map((e) => e.toMediaItem()).toList();
final mediaItems = queue.map((e) => e.song.toMediaItem()).toList();
queueSubject.add(mediaItems);
}
void _handleIndexChange(int index) {
_log.info('index: $index');
if (index != null) {
customEventSubject.add({KEY_INDEX: index});
_musicDataSource.setCurrentIndex(index);
customEventSubject.add({KEY_INDEX: index});
playbackStateSubject.add(playbackState.value.copyWith(
controls: [
MediaControl.skipToPrevious,

View file

@ -2,7 +2,7 @@ import 'package:rxdart/rxdart.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../models/player_state_model.dart';
import '../models/queue_item.dart';
import '../models/queue_item_model.dart';
import '../models/song_model.dart';
abstract class AudioPlayer {
@ -10,7 +10,7 @@ abstract class AudioPlayer {
ValueStream<SongModel> get currentSongStream;
ValueStream<PlayerStateModel> get playerStateStream;
ValueStream<Duration> get positionStream;
ValueStream<List<SongModel>> get queueStream;
ValueStream<List<QueueItemModel>> get queueStream;
ValueStream<ShuffleMode> get shuffleModeStream;
Future<void> play();
@ -20,7 +20,7 @@ abstract class AudioPlayer {
Future<void> seekToPrevious();
Future<void> dispose();
Future<void> loadQueue(List<QueueItem> queue);
Future<void> loadQueue({List<QueueItemModel> queue, int startIndex});
Future<void> addToQueue(SongModel song);
Future<void> moveQueueItem(int oldIndex, int newIndex);
Future<void> removeQueueIndex(int index);

View file

@ -1,9 +1,10 @@
import 'package:just_audio/just_audio.dart' as ja;
import 'package:rxdart/rxdart.dart';
import '../../domain/entities/queue_item.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../models/player_state_model.dart';
import '../models/queue_item.dart';
import '../models/queue_item_model.dart';
import '../models/song_model.dart';
import 'audio_player_contract.dart';
import 'queue_generator.dart';
@ -12,7 +13,7 @@ class AudioPlayerImpl implements AudioPlayer {
AudioPlayerImpl(this._audioPlayer, this._queueGenerator) {
_audioPlayer.currentIndexStream.listen((event) {
_currentIndexSubject.add(event);
_currentSongSubject.add(_queueSubject.value[event]);
_currentSongSubject.add(_queueSubject.value[event].song);
});
_audioPlayer.positionStream.listen((event) {
@ -24,7 +25,7 @@ class AudioPlayerImpl implements AudioPlayer {
});
_queueSubject.listen((event) {
_currentSongSubject.add(event[_currentIndexSubject.value]);
_currentSongSubject.add(event[_currentIndexSubject.value].song);
});
}
@ -33,13 +34,13 @@ class AudioPlayerImpl implements AudioPlayer {
final QueueGenerator _queueGenerator;
List<SongModel> _inputQueue;
List<QueueItem> _queue;
List<QueueItemModel> _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<List<QueueItemModel>> _queueSubject = BehaviorSubject();
final BehaviorSubject<ShuffleMode> _shuffleModeSubject = BehaviorSubject.seeded(ShuffleMode.none);
@override
@ -55,7 +56,7 @@ class AudioPlayerImpl implements AudioPlayer {
ValueStream<Duration> get positionStream => _positionSubject.stream;
@override
ValueStream<List<SongModel>> get queueStream => _queueSubject.stream;
ValueStream<List<QueueItemModel>> get queueStream => _queueSubject.stream;
@override
ValueStream<ShuffleMode> get shuffleModeStream => _shuffleModeSubject.stream;
@ -66,9 +67,21 @@ class AudioPlayerImpl implements AudioPlayer {
}
@override
Future<void> loadQueue(List<QueueItem> queue) {
// TODO: implement loadQueue
throw UnimplementedError();
Future<void> loadQueue({List<QueueItemModel> queue, int startIndex = 0}) async {
if (queue == null || queue.isEmpty) {
return;
}
_audioSource = _queueGenerator.songModelsToAudioSource([queue[startIndex].song]);
await _audioPlayer.load(_audioSource);
final songModelQueue = queue.map((e) => e.song).toList();
_queueSubject.add(queue);
final completeAudioSource = _queueGenerator.songModelsToAudioSource(songModelQueue);
_audioSource.insertAll(0, completeAudioSource.children.sublist(0, startIndex));
_audioSource.addAll(
completeAudioSource.children.sublist(startIndex + 1, completeAudioSource.length),
);
}
@override
@ -85,15 +98,15 @@ class AudioPlayerImpl implements AudioPlayer {
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);
final firstSong = songs[startIndex]; // .sublist(startIndex, startIndex + 1);
_queueSubject.add([QueueItemModel(firstSong, originalIndex: startIndex)]);
_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);
_queueSubject.add(_queue);
final int splitIndex = _shuffleModeSubject.value == ShuffleMode.none ? startIndex : 0;
final newQueue = _queueGenerator.songModelsToAudioSource(songModelQueue);
@ -124,23 +137,23 @@ class AudioPlayerImpl implements AudioPlayer {
@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());
_queue.add(QueueItemModel(song, originalIndex: -1, type: QueueItemType.added));
_queueSubject.add(_queue);
}
@override
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
final QueueItem queueItem = _queue.removeAt(oldIndex);
final QueueItemModel queueItem = _queue.removeAt(oldIndex);
final index = newIndex < oldIndex ? newIndex : newIndex - 1;
_queue.insert(index, queueItem);
_queueSubject.add(_queue.map((e) => e.song).toList());
_queueSubject.add(_queue);
await _audioSource.move(oldIndex, index);
}
@override
Future<void> removeQueueIndex(int index) async {
_queue.removeAt(index);
_queueSubject.add(_queue.map((e) => e.song).toList());
_queueSubject.add(_queue);
await _audioSource.removeAt(index);
}
@ -154,7 +167,7 @@ class AudioPlayerImpl implements AudioPlayer {
_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);
_queueSubject.add(_queue);
final newQueue = _queueGenerator.songModelsToAudioSource(songModelQueue);
_updateQueue(newQueue, currentQueueItem);

View file

@ -1,9 +1,10 @@
import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';
import '../../domain/entities/queue_item.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../datasources/music_data_source_contract.dart';
import '../models/queue_item.dart';
import '../models/queue_item_model.dart';
import '../models/song_model.dart';
class QueueGenerator {
@ -11,12 +12,12 @@ class QueueGenerator {
final MusicDataSource _musicDataSource;
Future<List<QueueItem>> generateQueue(
Future<List<QueueItemModel>> generateQueue(
ShuffleMode shuffleMode,
List<SongModel> songModels,
int startIndex,
) async {
List<QueueItem> queue;
List<QueueItemModel> queue;
switch (shuffleMode) {
case ShuffleMode.none:
@ -44,41 +45,41 @@ class QueueGenerator {
);
}
List<QueueItem> _generateNormalQueue(List<SongModel> songs) {
return List<QueueItem>.generate(
List<QueueItemModel> _generateNormalQueue(List<SongModel> songs) {
return List<QueueItemModel>.generate(
songs.length,
(i) => QueueItem(
(i) => QueueItemModel(
songs[i],
originalIndex: i,
),
);
}
List<QueueItem> _generateShuffleQueue(
List<QueueItemModel> _generateShuffleQueue(
List<SongModel> songs,
int startIndex,
) {
final List<QueueItem> queue = List<QueueItem>.generate(
final List<QueueItemModel> queue = List<QueueItemModel>.generate(
songs.length,
(i) => QueueItem(
(i) => QueueItemModel(
songs[i],
originalIndex: i,
),
);
queue.removeAt(startIndex);
queue.shuffle();
final first = QueueItem(
final first = QueueItemModel(
songs[startIndex],
originalIndex: startIndex,
);
return [first] + queue;
}
Future<List<QueueItem>> _generateShufflePlusQueue(
Future<List<QueueItemModel>> _generateShufflePlusQueue(
List<SongModel> songs,
int startIndex,
) async {
final List<QueueItem> queue = await _getQueueItemWithLinks(
final List<QueueItemModel> queue = await _getQueueItemWithLinks(
songs[startIndex],
startIndex,
);
@ -105,30 +106,30 @@ class QueueGenerator {
}
// TODO: naming things is hard
Future<List<QueueItem>> _getQueueItemWithLinks(
Future<List<QueueItemModel>> _getQueueItemWithLinks(
SongModel song,
int index,
) async {
final List<QueueItem> queueItems = [];
final List<QueueItemModel> queueItems = [];
final predecessors = await _getPredecessors(song);
final successors = await _getSuccessors(song);
for (final p in predecessors) {
queueItems.add(QueueItem(
queueItems.add(QueueItemModel(
p,
originalIndex: index,
type: QueueItemType.predecessor,
));
}
queueItems.add(QueueItem(
queueItems.add(QueueItemModel(
song,
originalIndex: index,
));
for (final p in successors) {
queueItems.add(QueueItem(
queueItems.add(QueueItemModel(
p,
originalIndex: index,
type: QueueItemType.successor,

View file

@ -1,14 +1,15 @@
import 'dart:io';
import 'dart:isolate';
import 'package:moor/ffi.dart';
import 'package:moor/isolate.dart';
import 'package:moor/moor.dart';
import 'package:moor/ffi.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import '../models/album_model.dart';
import '../models/artist_model.dart';
import '../models/queue_item_model.dart';
import '../models/song_model.dart';
import 'music_data_source_contract.dart';
@ -54,18 +55,24 @@ class Songs extends Table {
Set<Column> get primaryKey => {path};
}
@DataClassName('QueueEntry')
@DataClassName('MoorQueueEntry')
class QueueEntries extends Table {
IntColumn get index => integer()();
TextColumn get path => text()();
IntColumn get originalIndex => integer()();
IntColumn get type => integer()();
@override
Set<Column> get primaryKey => {index};
}
@UseMoor(tables: [Artists, Albums, Songs, QueueEntries])
class MoorMusicDataSource extends _$MoorMusicDataSource
implements MusicDataSource {
@DataClassName('PersistentPlayerState')
class PlayerState extends Table {
IntColumn get index => integer()();
}
@UseMoor(tables: [Artists, Albums, Songs, QueueEntries, PlayerState])
class MoorMusicDataSource extends _$MoorMusicDataSource implements MusicDataSource {
/// Use MoorMusicDataSource in main isolate only.
MoorMusicDataSource() : super(_openConnection());
@ -73,17 +80,15 @@ class MoorMusicDataSource extends _$MoorMusicDataSource
MoorMusicDataSource.withQueryExecutor(QueryExecutor e) : super(e);
/// Used to connect to a database on another isolate.
MoorMusicDataSource.connect(DatabaseConnection connection)
: super.connect(connection);
MoorMusicDataSource.connect(DatabaseConnection connection) : super.connect(connection);
@override
int get schemaVersion => 1;
@override
Future<List<AlbumModel>> getAlbums() async {
return select(albums).get().then((moorAlbumList) => moorAlbumList
.map((moorAlbum) => AlbumModel.fromMoorAlbum(moorAlbum))
.toList());
return select(albums).get().then((moorAlbumList) =>
moorAlbumList.map((moorAlbum) => AlbumModel.fromMoorAlbum(moorAlbum)).toList());
}
// TODO: insert can throw exception -> implications?
@ -94,16 +99,14 @@ class MoorMusicDataSource extends _$MoorMusicDataSource
@override
Stream<List<SongModel>> get songStream {
return select(songs).watch().map((moorSongList) => moorSongList
.map((moorSong) => SongModel.fromMoorSong(moorSong))
.toList());
return select(songs).watch().map((moorSongList) =>
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
}
@override
Future<List<SongModel>> getSongs() {
return select(songs).get().then((moorSongList) => moorSongList
.map((moorSong) => SongModel.fromMoorSong(moorSong))
.toList());
return select(songs).get().then((moorSongList) =>
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
}
@override
@ -115,9 +118,8 @@ class MoorMusicDataSource extends _$MoorMusicDataSource
(t) => OrderingTerm(expression: t.trackNumber)
]))
.watch()
.map((moorSongList) => moorSongList
.map((moorSong) => SongModel.fromMoorSong(moorSong))
.toList());
.map((moorSongList) =>
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
}
@override
@ -129,9 +131,8 @@ class MoorMusicDataSource extends _$MoorMusicDataSource
(t) => OrderingTerm(expression: t.trackNumber)
]))
.get()
.then((moorSongList) => moorSongList
.map((moorSong) => SongModel.fromMoorSong(moorSong))
.toList());
.then((moorSongList) =>
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
}
@override
@ -173,9 +174,8 @@ class MoorMusicDataSource extends _$MoorMusicDataSource
@override
Future<List<ArtistModel>> getArtists() {
return select(artists).get().then((moorArtistList) => moorArtistList
.map((moorArtist) => ArtistModel.fromMoorArtist(moorArtist))
.toList());
return select(artists).get().then((moorArtistList) =>
moorArtistList.map((moorArtist) => ArtistModel.fromMoorArtist(moorArtist)).toList());
}
@override
@ -209,9 +209,8 @@ class MoorMusicDataSource extends _$MoorMusicDataSource
(t) => OrderingTerm(expression: t.trackNumber)
]))
.get()
.then((moorSongList) => moorSongList
.map((moorSong) => SongModel.fromMoorSong(moorSong))
.toList());
.then((moorSongList) =>
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
bool current = false;
SongModel nextSong;
@ -233,34 +232,67 @@ class MoorMusicDataSource extends _$MoorMusicDataSource
}
@override
Stream<List<SongModel>> get queueStream {
final query = (select(queueEntries)
..orderBy([(t) => OrderingTerm(expression: t.index)]))
Stream<List<SongModel>> get songQueueStream {
final query = (select(queueEntries)..orderBy([(t) => OrderingTerm(expression: t.index)]))
.join([innerJoin(songs, songs.path.equalsExp(queueEntries.path))]);
return query.watch().map((rows) {
return rows
.map((row) => SongModel.fromMoorSong(row.readTable(songs)))
.toList();
print('rows: ${rows.length}');
return rows.map((row) => SongModel.fromMoorSong(row.readTable(songs))).toList();
});
}
@override
Future<void> setQueue(List<SongModel> queue) async {
final _queueEntries = <Insertable<QueueEntry>>[];
Stream<List<QueueItemModel>> get queueStream {
final query = (select(queueEntries)..orderBy([(t) => OrderingTerm(expression: t.index)]))
.join([innerJoin(songs, songs.path.equalsExp(queueEntries.path))]);
return query.watch().map((rows) {
return rows.map((row) {
return QueueItemModel(
SongModel.fromMoorSong(row.readTable(songs)),
originalIndex: row.readTable(queueEntries).originalIndex,
type: row.readTable(queueEntries).type.toQueueItemType(),
);
}).toList();
});
}
@override
Future<void> setQueue(List<QueueItemModel> queue) async {
print('setQueue');
final _queueEntries = <Insertable<MoorQueueEntry>>[];
for (var i = 0; i < queue.length; i++) {
_queueEntries.add(QueueEntriesCompanion(index: Value(i), path: Value(queue[i].path)));
_queueEntries.add(QueueEntriesCompanion(
index: Value(i),
path: Value(queue[i].song.path),
originalIndex: Value(queue[i].originalIndex),
type: Value(queue[i].type.toInt()),
));
}
await delete(queueEntries).go();
await batch((batch) {
batch.insertAll(
queueEntries,
_queueEntries
);
batch.insertAll(queueEntries, _queueEntries);
});
}
@override
Stream<int> get currentIndexStream {
return select(playerState).watchSingle().map((event) => event.index);
}
@override
Future<void> setCurrentIndex(int index) async {
print('setCurrentIndex: $index');
final currentState = await select(playerState).getSingle();
if (currentState != null) {
update(playerState).write(PlayerStateCompanion(index: Value(index)));
} else {
into(playerState).insert(PlayerStateCompanion(index: Value(index)));
}
}
}
LazyDatabase _openConnection() {

View file

@ -1213,18 +1213,28 @@ class $SongsTable extends Songs with TableInfo<$SongsTable, MoorSong> {
}
}
class QueueEntry extends DataClass implements Insertable<QueueEntry> {
class MoorQueueEntry extends DataClass implements Insertable<MoorQueueEntry> {
final int index;
final String path;
QueueEntry({@required this.index, @required this.path});
factory QueueEntry.fromData(Map<String, dynamic> data, GeneratedDatabase db,
final int originalIndex;
final int type;
MoorQueueEntry(
{@required this.index,
@required this.path,
@required this.originalIndex,
@required this.type});
factory MoorQueueEntry.fromData(
Map<String, dynamic> data, GeneratedDatabase db,
{String prefix}) {
final effectivePrefix = prefix ?? '';
final intType = db.typeSystem.forDartType<int>();
final stringType = db.typeSystem.forDartType<String>();
return QueueEntry(
return MoorQueueEntry(
index: intType.mapFromDatabaseResponse(data['${effectivePrefix}index']),
path: stringType.mapFromDatabaseResponse(data['${effectivePrefix}path']),
originalIndex: intType
.mapFromDatabaseResponse(data['${effectivePrefix}original_index']),
type: intType.mapFromDatabaseResponse(data['${effectivePrefix}type']),
);
}
@override
@ -1236,6 +1246,12 @@ class QueueEntry extends DataClass implements Insertable<QueueEntry> {
if (!nullToAbsent || path != null) {
map['path'] = Variable<String>(path);
}
if (!nullToAbsent || originalIndex != null) {
map['original_index'] = Variable<int>(originalIndex);
}
if (!nullToAbsent || type != null) {
map['type'] = Variable<int>(type);
}
return map;
}
@ -1244,15 +1260,21 @@ class QueueEntry extends DataClass implements Insertable<QueueEntry> {
index:
index == null && nullToAbsent ? const Value.absent() : Value(index),
path: path == null && nullToAbsent ? const Value.absent() : Value(path),
originalIndex: originalIndex == null && nullToAbsent
? const Value.absent()
: Value(originalIndex),
type: type == null && nullToAbsent ? const Value.absent() : Value(type),
);
}
factory QueueEntry.fromJson(Map<String, dynamic> json,
factory MoorQueueEntry.fromJson(Map<String, dynamic> json,
{ValueSerializer serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return QueueEntry(
return MoorQueueEntry(
index: serializer.fromJson<int>(json['index']),
path: serializer.fromJson<String>(json['path']),
originalIndex: serializer.fromJson<int>(json['originalIndex']),
type: serializer.fromJson<int>(json['type']),
);
}
@override
@ -1261,57 +1283,86 @@ class QueueEntry extends DataClass implements Insertable<QueueEntry> {
return <String, dynamic>{
'index': serializer.toJson<int>(index),
'path': serializer.toJson<String>(path),
'originalIndex': serializer.toJson<int>(originalIndex),
'type': serializer.toJson<int>(type),
};
}
QueueEntry copyWith({int index, String path}) => QueueEntry(
MoorQueueEntry copyWith(
{int index, String path, int originalIndex, int type}) =>
MoorQueueEntry(
index: index ?? this.index,
path: path ?? this.path,
originalIndex: originalIndex ?? this.originalIndex,
type: type ?? this.type,
);
@override
String toString() {
return (StringBuffer('QueueEntry(')
return (StringBuffer('MoorQueueEntry(')
..write('index: $index, ')
..write('path: $path')
..write('path: $path, ')
..write('originalIndex: $originalIndex, ')
..write('type: $type')
..write(')'))
.toString();
}
@override
int get hashCode => $mrjf($mrjc(index.hashCode, path.hashCode));
int get hashCode => $mrjf($mrjc(index.hashCode,
$mrjc(path.hashCode, $mrjc(originalIndex.hashCode, type.hashCode))));
@override
bool operator ==(dynamic other) =>
identical(this, other) ||
(other is QueueEntry &&
(other is MoorQueueEntry &&
other.index == this.index &&
other.path == this.path);
other.path == this.path &&
other.originalIndex == this.originalIndex &&
other.type == this.type);
}
class QueueEntriesCompanion extends UpdateCompanion<QueueEntry> {
class QueueEntriesCompanion extends UpdateCompanion<MoorQueueEntry> {
final Value<int> index;
final Value<String> path;
final Value<int> originalIndex;
final Value<int> type;
const QueueEntriesCompanion({
this.index = const Value.absent(),
this.path = const Value.absent(),
this.originalIndex = const Value.absent(),
this.type = const Value.absent(),
});
QueueEntriesCompanion.insert({
this.index = const Value.absent(),
@required String path,
}) : path = Value(path);
static Insertable<QueueEntry> custom({
@required int originalIndex,
@required int type,
}) : path = Value(path),
originalIndex = Value(originalIndex),
type = Value(type);
static Insertable<MoorQueueEntry> custom({
Expression<int> index,
Expression<String> path,
Expression<int> originalIndex,
Expression<int> type,
}) {
return RawValuesInsertable({
if (index != null) 'index': index,
if (path != null) 'path': path,
if (originalIndex != null) 'original_index': originalIndex,
if (type != null) 'type': type,
});
}
QueueEntriesCompanion copyWith({Value<int> index, Value<String> path}) {
QueueEntriesCompanion copyWith(
{Value<int> index,
Value<String> path,
Value<int> originalIndex,
Value<int> type}) {
return QueueEntriesCompanion(
index: index ?? this.index,
path: path ?? this.path,
originalIndex: originalIndex ?? this.originalIndex,
type: type ?? this.type,
);
}
@ -1324,6 +1375,12 @@ class QueueEntriesCompanion extends UpdateCompanion<QueueEntry> {
if (path.present) {
map['path'] = Variable<String>(path.value);
}
if (originalIndex.present) {
map['original_index'] = Variable<int>(originalIndex.value);
}
if (type.present) {
map['type'] = Variable<int>(type.value);
}
return map;
}
@ -1331,14 +1388,16 @@ class QueueEntriesCompanion extends UpdateCompanion<QueueEntry> {
String toString() {
return (StringBuffer('QueueEntriesCompanion(')
..write('index: $index, ')
..write('path: $path')
..write('path: $path, ')
..write('originalIndex: $originalIndex, ')
..write('type: $type')
..write(')'))
.toString();
}
}
class $QueueEntriesTable extends QueueEntries
with TableInfo<$QueueEntriesTable, QueueEntry> {
with TableInfo<$QueueEntriesTable, MoorQueueEntry> {
final GeneratedDatabase _db;
final String _alias;
$QueueEntriesTable(this._db, [this._alias]);
@ -1366,8 +1425,34 @@ class $QueueEntriesTable extends QueueEntries
);
}
final VerificationMeta _originalIndexMeta =
const VerificationMeta('originalIndex');
GeneratedIntColumn _originalIndex;
@override
List<GeneratedColumn> get $columns => [index, path];
GeneratedIntColumn get originalIndex =>
_originalIndex ??= _constructOriginalIndex();
GeneratedIntColumn _constructOriginalIndex() {
return GeneratedIntColumn(
'original_index',
$tableName,
false,
);
}
final VerificationMeta _typeMeta = const VerificationMeta('type');
GeneratedIntColumn _type;
@override
GeneratedIntColumn get type => _type ??= _constructType();
GeneratedIntColumn _constructType() {
return GeneratedIntColumn(
'type',
$tableName,
false,
);
}
@override
List<GeneratedColumn> get $columns => [index, path, originalIndex, type];
@override
$QueueEntriesTable get asDslTable => this;
@override
@ -1375,7 +1460,7 @@ class $QueueEntriesTable extends QueueEntries
@override
final String actualTableName = 'queue_entries';
@override
VerificationContext validateIntegrity(Insertable<QueueEntry> instance,
VerificationContext validateIntegrity(Insertable<MoorQueueEntry> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
@ -1389,15 +1474,29 @@ class $QueueEntriesTable extends QueueEntries
} else if (isInserting) {
context.missing(_pathMeta);
}
if (data.containsKey('original_index')) {
context.handle(
_originalIndexMeta,
originalIndex.isAcceptableOrUnknown(
data['original_index'], _originalIndexMeta));
} else if (isInserting) {
context.missing(_originalIndexMeta);
}
if (data.containsKey('type')) {
context.handle(
_typeMeta, type.isAcceptableOrUnknown(data['type'], _typeMeta));
} else if (isInserting) {
context.missing(_typeMeta);
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {index};
@override
QueueEntry map(Map<String, dynamic> data, {String tablePrefix}) {
MoorQueueEntry map(Map<String, dynamic> data, {String tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null;
return QueueEntry.fromData(data, _db, prefix: effectivePrefix);
return MoorQueueEntry.fromData(data, _db, prefix: effectivePrefix);
}
@override
@ -1406,6 +1505,163 @@ class $QueueEntriesTable extends QueueEntries
}
}
class PersistentPlayerState extends DataClass
implements Insertable<PersistentPlayerState> {
final int index;
PersistentPlayerState({@required this.index});
factory PersistentPlayerState.fromData(
Map<String, dynamic> data, GeneratedDatabase db,
{String prefix}) {
final effectivePrefix = prefix ?? '';
final intType = db.typeSystem.forDartType<int>();
return PersistentPlayerState(
index: intType.mapFromDatabaseResponse(data['${effectivePrefix}index']),
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (!nullToAbsent || index != null) {
map['index'] = Variable<int>(index);
}
return map;
}
PlayerStateCompanion toCompanion(bool nullToAbsent) {
return PlayerStateCompanion(
index:
index == null && nullToAbsent ? const Value.absent() : Value(index),
);
}
factory PersistentPlayerState.fromJson(Map<String, dynamic> json,
{ValueSerializer serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return PersistentPlayerState(
index: serializer.fromJson<int>(json['index']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'index': serializer.toJson<int>(index),
};
}
PersistentPlayerState copyWith({int index}) => PersistentPlayerState(
index: index ?? this.index,
);
@override
String toString() {
return (StringBuffer('PersistentPlayerState(')
..write('index: $index')
..write(')'))
.toString();
}
@override
int get hashCode => $mrjf(index.hashCode);
@override
bool operator ==(dynamic other) =>
identical(this, other) ||
(other is PersistentPlayerState && other.index == this.index);
}
class PlayerStateCompanion extends UpdateCompanion<PersistentPlayerState> {
final Value<int> index;
const PlayerStateCompanion({
this.index = const Value.absent(),
});
PlayerStateCompanion.insert({
@required int index,
}) : index = Value(index);
static Insertable<PersistentPlayerState> custom({
Expression<int> index,
}) {
return RawValuesInsertable({
if (index != null) 'index': index,
});
}
PlayerStateCompanion copyWith({Value<int> index}) {
return PlayerStateCompanion(
index: index ?? this.index,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (index.present) {
map['index'] = Variable<int>(index.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('PlayerStateCompanion(')
..write('index: $index')
..write(')'))
.toString();
}
}
class $PlayerStateTable extends PlayerState
with TableInfo<$PlayerStateTable, PersistentPlayerState> {
final GeneratedDatabase _db;
final String _alias;
$PlayerStateTable(this._db, [this._alias]);
final VerificationMeta _indexMeta = const VerificationMeta('index');
GeneratedIntColumn _index;
@override
GeneratedIntColumn get index => _index ??= _constructIndex();
GeneratedIntColumn _constructIndex() {
return GeneratedIntColumn(
'index',
$tableName,
false,
);
}
@override
List<GeneratedColumn> get $columns => [index];
@override
$PlayerStateTable get asDslTable => this;
@override
String get $tableName => _alias ?? 'player_state';
@override
final String actualTableName = 'player_state';
@override
VerificationContext validateIntegrity(
Insertable<PersistentPlayerState> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('index')) {
context.handle(
_indexMeta, index.isAcceptableOrUnknown(data['index'], _indexMeta));
} else if (isInserting) {
context.missing(_indexMeta);
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => <GeneratedColumn>{};
@override
PersistentPlayerState map(Map<String, dynamic> data, {String tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null;
return PersistentPlayerState.fromData(data, _db, prefix: effectivePrefix);
}
@override
$PlayerStateTable createAlias(String alias) {
return $PlayerStateTable(_db, alias);
}
}
abstract class _$MoorMusicDataSource extends GeneratedDatabase {
_$MoorMusicDataSource(QueryExecutor e)
: super(SqlTypeSystem.defaultInstance, e);
@ -1419,9 +1675,11 @@ abstract class _$MoorMusicDataSource extends GeneratedDatabase {
$QueueEntriesTable _queueEntries;
$QueueEntriesTable get queueEntries =>
_queueEntries ??= $QueueEntriesTable(this);
$PlayerStateTable _playerState;
$PlayerStateTable get playerState => _playerState ??= $PlayerStateTable(this);
@override
Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities =>
[artists, albums, songs, queueEntries];
[artists, albums, songs, queueEntries, playerState];
}

View file

@ -1,5 +1,6 @@
import '../models/album_model.dart';
import '../models/artist_model.dart';
import '../models/queue_item_model.dart';
import '../models/song_model.dart';
abstract class MusicDataSource {
@ -8,8 +9,11 @@ abstract class MusicDataSource {
Stream<List<SongModel>> get songStream;
Stream<List<SongModel>> getAlbumSongStream(AlbumModel album);
Future<void> setQueue(List<SongModel> queue);
Stream<List<SongModel>> get queueStream;
Future<void> setQueue(List<QueueItemModel> queue);
Stream<List<SongModel>> get songQueueStream;
Stream<List<QueueItemModel>> get queueStream;
Future<void> setCurrentIndex(int index);
Stream<int> get currentIndexStream;
/// Insert album into the database. Return the ID of the inserted album.
Future<int> insertAlbum(AlbumModel albumModel);

View file

@ -0,0 +1,38 @@
import '../../domain/entities/queue_item.dart';
import 'song_model.dart';
class QueueItemModel extends QueueItem {
QueueItemModel(
this.song, {
int originalIndex,
QueueItemType type = QueueItemType.standard,
}) : super(song, originalIndex: originalIndex, type: type);
@override
SongModel song;
}
extension IntToQueueItemType on int {
QueueItemType toQueueItemType() {
switch(this) {
case 1: return QueueItemType.predecessor;
case 2: return QueueItemType.successor;
case 3: return QueueItemType.added;
default: return QueueItemType.standard;
}
}
}
extension QueueItemTypeToInt on QueueItemType {
int toInt() {
switch(this) {
case QueueItemType.predecessor:
return 1;
case QueueItemType.successor:
return 2;
case QueueItemType.added:
return 3;
default: return 0;
}
}
}

View file

@ -135,5 +135,8 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
}
@override
Stream<List<Song>> get queueStream => musicDataSource.queueStream;
Stream<List<Song>> get queueStream => musicDataSource.songQueueStream;
@override
Stream<int> get currentIndexStream => musicDataSource.currentIndexStream;
}