From f79fedefd48d4ac034960fcdc257e6be0a746022 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 23 Jun 2024 12:23:28 +0600 Subject: [PATCH] chore: create new audio player centric playback notifier with drift persistence --- lib/models/connect/connect.dart | 4 +- lib/models/connect/ws_event.dart | 12 +- lib/models/database/database.dart | 6 + lib/models/database/database.g.dart | 1305 +++++++++++++++++ .../database/tables/audio_player_state.dart | 27 + lib/models/database/typeconverters/map.dart | 15 + lib/modules/player/player_controls.dart | 27 +- lib/modules/player/use_progress.dart | 20 +- lib/pages/connect/control/control.dart | 23 +- lib/provider/audio_player/audio_player.dart | 225 +++ lib/provider/audio_player/state.dart | 42 + lib/provider/connect/connect.dart | 10 +- lib/provider/tray_manager/tray_menu.dart | 8 +- lib/services/audio_player/audio_player.dart | 18 +- .../audio_player/audio_player_impl.dart | 9 +- .../audio_players_streams_mixin.dart | 6 +- lib/services/audio_player/loop_mode.dart | 90 -- .../audio_services/mobile_audio_service.dart | 23 +- 18 files changed, 1694 insertions(+), 176 deletions(-) create mode 100644 lib/models/database/tables/audio_player_state.dart create mode 100644 lib/models/database/typeconverters/map.dart create mode 100644 lib/provider/audio_player/audio_player.dart create mode 100644 lib/provider/audio_player/state.dart delete mode 100644 lib/services/audio_player/loop_mode.dart diff --git a/lib/models/connect/connect.dart b/lib/models/connect/connect.dart index 28386050..0a06be32 100644 --- a/lib/models/connect/connect.dart +++ b/lib/models/connect/connect.dart @@ -4,9 +4,9 @@ import 'dart:async'; import 'dart:convert'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:spotify/spotify.dart'; +import 'package:media_kit/media_kit.dart' hide Track; +import 'package:spotify/spotify.dart' hide Playlist; import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; -import 'package:spotube/services/audio_player/loop_mode.dart'; part 'connect.freezed.dart'; part 'connect.g.dart'; diff --git a/lib/models/connect/ws_event.dart b/lib/models/connect/ws_event.dart index 2d7213b1..c3c29e76 100644 --- a/lib/models/connect/ws_event.dart +++ b/lib/models/connect/ws_event.dart @@ -183,7 +183,7 @@ class WebSocketEvent { if (type == WsEvent.loop) { await callback( WebSocketLoopEvent( - PlaybackLoopMode.fromString(data as String), + PlaylistMode.values.firstWhere((e) => e.name == data as String), ), ); } @@ -224,12 +224,16 @@ class WebSocketEvent { } } -class WebSocketLoopEvent extends WebSocketEvent { - WebSocketLoopEvent(PlaybackLoopMode data) : super(WsEvent.loop, data); +class WebSocketLoopEvent extends WebSocketEvent { + WebSocketLoopEvent(PlaylistMode data) : super(WsEvent.loop, data); WebSocketLoopEvent.fromJson(Map json) : super( - WsEvent.loop, PlaybackLoopMode.fromString(json["data"] as String)); + WsEvent.loop, + PlaylistMode.values.firstWhere( + (e) => e.name == json["data"] as String, + ), + ); @override String toJson() { diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index e387291a..98dc22dc 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:drift/drift.dart'; import 'package:encrypt/encrypt.dart'; +import 'package:media_kit/media_kit.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:spotify/spotify.dart'; @@ -25,11 +26,13 @@ part 'tables/preferences.dart'; part 'tables/scrobbler.dart'; part 'tables/skip_segment.dart'; part 'tables/source_match.dart'; +part 'tables/audio_player_state.dart'; part 'typeconverters/color.dart'; part 'typeconverters/locale.dart'; part 'typeconverters/string_list.dart'; part 'typeconverters/encrypted_text.dart'; +part 'typeconverters/map.dart'; @DriftDatabase( tables: [ @@ -39,6 +42,9 @@ part 'typeconverters/encrypted_text.dart'; ScrobblerTable, SkipSegmentTable, SourceMatchTable, + AudioPlayerStateTable, + PlaylistTable, + PlaylistMediaTable, ], ) class AppDatabase extends _$AppDatabase { diff --git a/lib/models/database/database.g.dart b/lib/models/database/database.g.dart index 6f899066..ca9d6d97 100644 --- a/lib/models/database/database.g.dart +++ b/lib/models/database/database.g.dart @@ -2575,6 +2575,839 @@ class SourceMatchTableCompanion extends UpdateCompanion { } } +class $AudioPlayerStateTableTable extends AudioPlayerStateTable + with TableInfo<$AudioPlayerStateTableTable, AudioPlayerStateTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $AudioPlayerStateTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _playingMeta = + const VerificationMeta('playing'); + @override + late final GeneratedColumn playing = GeneratedColumn( + 'playing', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("playing" IN (0, 1))')); + static const VerificationMeta _volumeMeta = const VerificationMeta('volume'); + @override + late final GeneratedColumn volume = GeneratedColumn( + 'volume', aliasedName, false, + type: DriftSqlType.double, requiredDuringInsert: true); + static const VerificationMeta _loopModeMeta = + const VerificationMeta('loopMode'); + @override + late final GeneratedColumnWithTypeConverter loopMode = + GeneratedColumn('loop_mode', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true) + .withConverter( + $AudioPlayerStateTableTable.$converterloopMode); + static const VerificationMeta _shuffledMeta = + const VerificationMeta('shuffled'); + @override + late final GeneratedColumn shuffled = GeneratedColumn( + 'shuffled', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("shuffled" IN (0, 1))')); + @override + List get $columns => + [id, playing, volume, loopMode, shuffled]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'audio_player_state_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('playing')) { + context.handle(_playingMeta, + playing.isAcceptableOrUnknown(data['playing']!, _playingMeta)); + } else if (isInserting) { + context.missing(_playingMeta); + } + if (data.containsKey('volume')) { + context.handle(_volumeMeta, + volume.isAcceptableOrUnknown(data['volume']!, _volumeMeta)); + } else if (isInserting) { + context.missing(_volumeMeta); + } + context.handle(_loopModeMeta, const VerificationResult.success()); + if (data.containsKey('shuffled')) { + context.handle(_shuffledMeta, + shuffled.isAcceptableOrUnknown(data['shuffled']!, _shuffledMeta)); + } else if (isInserting) { + context.missing(_shuffledMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + AudioPlayerStateTableData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AudioPlayerStateTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + playing: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}playing'])!, + volume: attachedDatabase.typeMapping + .read(DriftSqlType.double, data['${effectivePrefix}volume'])!, + loopMode: $AudioPlayerStateTableTable.$converterloopMode.fromSql( + attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}loop_mode'])!), + shuffled: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}shuffled'])!, + ); + } + + @override + $AudioPlayerStateTableTable createAlias(String alias) { + return $AudioPlayerStateTableTable(attachedDatabase, alias); + } + + static JsonTypeConverter2 $converterloopMode = + const EnumNameConverter(PlaylistMode.values); +} + +class AudioPlayerStateTableData extends DataClass + implements Insertable { + final int id; + final bool playing; + final double volume; + final PlaylistMode loopMode; + final bool shuffled; + const AudioPlayerStateTableData( + {required this.id, + required this.playing, + required this.volume, + required this.loopMode, + required this.shuffled}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['playing'] = Variable(playing); + map['volume'] = Variable(volume); + { + map['loop_mode'] = Variable( + $AudioPlayerStateTableTable.$converterloopMode.toSql(loopMode)); + } + map['shuffled'] = Variable(shuffled); + return map; + } + + AudioPlayerStateTableCompanion toCompanion(bool nullToAbsent) { + return AudioPlayerStateTableCompanion( + id: Value(id), + playing: Value(playing), + volume: Value(volume), + loopMode: Value(loopMode), + shuffled: Value(shuffled), + ); + } + + factory AudioPlayerStateTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AudioPlayerStateTableData( + id: serializer.fromJson(json['id']), + playing: serializer.fromJson(json['playing']), + volume: serializer.fromJson(json['volume']), + loopMode: $AudioPlayerStateTableTable.$converterloopMode + .fromJson(serializer.fromJson(json['loopMode'])), + shuffled: serializer.fromJson(json['shuffled']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'playing': serializer.toJson(playing), + 'volume': serializer.toJson(volume), + 'loopMode': serializer.toJson( + $AudioPlayerStateTableTable.$converterloopMode.toJson(loopMode)), + 'shuffled': serializer.toJson(shuffled), + }; + } + + AudioPlayerStateTableData copyWith( + {int? id, + bool? playing, + double? volume, + PlaylistMode? loopMode, + bool? shuffled}) => + AudioPlayerStateTableData( + id: id ?? this.id, + playing: playing ?? this.playing, + volume: volume ?? this.volume, + loopMode: loopMode ?? this.loopMode, + shuffled: shuffled ?? this.shuffled, + ); + @override + String toString() { + return (StringBuffer('AudioPlayerStateTableData(') + ..write('id: $id, ') + ..write('playing: $playing, ') + ..write('volume: $volume, ') + ..write('loopMode: $loopMode, ') + ..write('shuffled: $shuffled') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, playing, volume, loopMode, shuffled); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AudioPlayerStateTableData && + other.id == this.id && + other.playing == this.playing && + other.volume == this.volume && + other.loopMode == this.loopMode && + other.shuffled == this.shuffled); +} + +class AudioPlayerStateTableCompanion + extends UpdateCompanion { + final Value id; + final Value playing; + final Value volume; + final Value loopMode; + final Value shuffled; + const AudioPlayerStateTableCompanion({ + this.id = const Value.absent(), + this.playing = const Value.absent(), + this.volume = const Value.absent(), + this.loopMode = const Value.absent(), + this.shuffled = const Value.absent(), + }); + AudioPlayerStateTableCompanion.insert({ + this.id = const Value.absent(), + required bool playing, + required double volume, + required PlaylistMode loopMode, + required bool shuffled, + }) : playing = Value(playing), + volume = Value(volume), + loopMode = Value(loopMode), + shuffled = Value(shuffled); + static Insertable custom({ + Expression? id, + Expression? playing, + Expression? volume, + Expression? loopMode, + Expression? shuffled, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (playing != null) 'playing': playing, + if (volume != null) 'volume': volume, + if (loopMode != null) 'loop_mode': loopMode, + if (shuffled != null) 'shuffled': shuffled, + }); + } + + AudioPlayerStateTableCompanion copyWith( + {Value? id, + Value? playing, + Value? volume, + Value? loopMode, + Value? shuffled}) { + return AudioPlayerStateTableCompanion( + id: id ?? this.id, + playing: playing ?? this.playing, + volume: volume ?? this.volume, + loopMode: loopMode ?? this.loopMode, + shuffled: shuffled ?? this.shuffled, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (playing.present) { + map['playing'] = Variable(playing.value); + } + if (volume.present) { + map['volume'] = Variable(volume.value); + } + if (loopMode.present) { + map['loop_mode'] = Variable( + $AudioPlayerStateTableTable.$converterloopMode.toSql(loopMode.value)); + } + if (shuffled.present) { + map['shuffled'] = Variable(shuffled.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AudioPlayerStateTableCompanion(') + ..write('id: $id, ') + ..write('playing: $playing, ') + ..write('volume: $volume, ') + ..write('loopMode: $loopMode, ') + ..write('shuffled: $shuffled') + ..write(')')) + .toString(); + } +} + +class $PlaylistTableTable extends PlaylistTable + with TableInfo<$PlaylistTableTable, PlaylistTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $PlaylistTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _audioPlayerStateIdMeta = + const VerificationMeta('audioPlayerStateId'); + @override + late final GeneratedColumn audioPlayerStateId = GeneratedColumn( + 'audio_player_state_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES audio_player_state_table (id)')); + static const VerificationMeta _indexMeta = const VerificationMeta('index'); + @override + late final GeneratedColumn index = GeneratedColumn( + 'index', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + @override + List get $columns => [id, audioPlayerStateId, index]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'playlist_table'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('audio_player_state_id')) { + context.handle( + _audioPlayerStateIdMeta, + audioPlayerStateId.isAcceptableOrUnknown( + data['audio_player_state_id']!, _audioPlayerStateIdMeta)); + } else if (isInserting) { + context.missing(_audioPlayerStateIdMeta); + } + if (data.containsKey('index')) { + context.handle( + _indexMeta, index.isAcceptableOrUnknown(data['index']!, _indexMeta)); + } else if (isInserting) { + context.missing(_indexMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + PlaylistTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PlaylistTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + audioPlayerStateId: attachedDatabase.typeMapping.read( + DriftSqlType.int, data['${effectivePrefix}audio_player_state_id'])!, + index: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}index'])!, + ); + } + + @override + $PlaylistTableTable createAlias(String alias) { + return $PlaylistTableTable(attachedDatabase, alias); + } +} + +class PlaylistTableData extends DataClass + implements Insertable { + final int id; + final int audioPlayerStateId; + final int index; + const PlaylistTableData( + {required this.id, + required this.audioPlayerStateId, + required this.index}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['audio_player_state_id'] = Variable(audioPlayerStateId); + map['index'] = Variable(index); + return map; + } + + PlaylistTableCompanion toCompanion(bool nullToAbsent) { + return PlaylistTableCompanion( + id: Value(id), + audioPlayerStateId: Value(audioPlayerStateId), + index: Value(index), + ); + } + + factory PlaylistTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PlaylistTableData( + id: serializer.fromJson(json['id']), + audioPlayerStateId: serializer.fromJson(json['audioPlayerStateId']), + index: serializer.fromJson(json['index']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'audioPlayerStateId': serializer.toJson(audioPlayerStateId), + 'index': serializer.toJson(index), + }; + } + + PlaylistTableData copyWith({int? id, int? audioPlayerStateId, int? index}) => + PlaylistTableData( + id: id ?? this.id, + audioPlayerStateId: audioPlayerStateId ?? this.audioPlayerStateId, + index: index ?? this.index, + ); + @override + String toString() { + return (StringBuffer('PlaylistTableData(') + ..write('id: $id, ') + ..write('audioPlayerStateId: $audioPlayerStateId, ') + ..write('index: $index') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, audioPlayerStateId, index); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PlaylistTableData && + other.id == this.id && + other.audioPlayerStateId == this.audioPlayerStateId && + other.index == this.index); +} + +class PlaylistTableCompanion extends UpdateCompanion { + final Value id; + final Value audioPlayerStateId; + final Value index; + const PlaylistTableCompanion({ + this.id = const Value.absent(), + this.audioPlayerStateId = const Value.absent(), + this.index = const Value.absent(), + }); + PlaylistTableCompanion.insert({ + this.id = const Value.absent(), + required int audioPlayerStateId, + required int index, + }) : audioPlayerStateId = Value(audioPlayerStateId), + index = Value(index); + static Insertable custom({ + Expression? id, + Expression? audioPlayerStateId, + Expression? index, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (audioPlayerStateId != null) + 'audio_player_state_id': audioPlayerStateId, + if (index != null) 'index': index, + }); + } + + PlaylistTableCompanion copyWith( + {Value? id, Value? audioPlayerStateId, Value? index}) { + return PlaylistTableCompanion( + id: id ?? this.id, + audioPlayerStateId: audioPlayerStateId ?? this.audioPlayerStateId, + index: index ?? this.index, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (audioPlayerStateId.present) { + map['audio_player_state_id'] = Variable(audioPlayerStateId.value); + } + if (index.present) { + map['index'] = Variable(index.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PlaylistTableCompanion(') + ..write('id: $id, ') + ..write('audioPlayerStateId: $audioPlayerStateId, ') + ..write('index: $index') + ..write(')')) + .toString(); + } +} + +class $PlaylistMediaTableTable extends PlaylistMediaTable + with TableInfo<$PlaylistMediaTableTable, PlaylistMediaTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $PlaylistMediaTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _playlistIdMeta = + const VerificationMeta('playlistId'); + @override + late final GeneratedColumn playlistId = GeneratedColumn( + 'playlist_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('REFERENCES playlist_table (id)')); + static const VerificationMeta _uriMeta = const VerificationMeta('uri'); + @override + late final GeneratedColumn uri = GeneratedColumn( + 'uri', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _extrasMeta = const VerificationMeta('extras'); + @override + late final GeneratedColumnWithTypeConverter?, String> + extras = GeneratedColumn('extras', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter?>( + $PlaylistMediaTableTable.$converterextrasn); + static const VerificationMeta _httpHeadersMeta = + const VerificationMeta('httpHeaders'); + @override + late final GeneratedColumnWithTypeConverter?, String> + httpHeaders = GeneratedColumn('http_headers', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter?>( + $PlaylistMediaTableTable.$converterhttpHeadersn); + @override + List get $columns => + [id, playlistId, uri, extras, httpHeaders]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'playlist_media_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('playlist_id')) { + context.handle( + _playlistIdMeta, + playlistId.isAcceptableOrUnknown( + data['playlist_id']!, _playlistIdMeta)); + } else if (isInserting) { + context.missing(_playlistIdMeta); + } + if (data.containsKey('uri')) { + context.handle( + _uriMeta, uri.isAcceptableOrUnknown(data['uri']!, _uriMeta)); + } else if (isInserting) { + context.missing(_uriMeta); + } + context.handle(_extrasMeta, const VerificationResult.success()); + context.handle(_httpHeadersMeta, const VerificationResult.success()); + return context; + } + + @override + Set get $primaryKey => {id}; + @override + PlaylistMediaTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PlaylistMediaTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + playlistId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}playlist_id'])!, + uri: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}uri'])!, + extras: $PlaylistMediaTableTable.$converterextrasn.fromSql( + attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}extras'])), + httpHeaders: $PlaylistMediaTableTable.$converterhttpHeadersn.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}http_headers'])), + ); + } + + @override + $PlaylistMediaTableTable createAlias(String alias) { + return $PlaylistMediaTableTable(attachedDatabase, alias); + } + + static TypeConverter, String> $converterextras = + const MapTypeConverter(); + static TypeConverter?, String?> $converterextrasn = + NullAwareTypeConverter.wrap($converterextras); + static TypeConverter, String> $converterhttpHeaders = + const MapTypeConverter(); + static TypeConverter?, String?> $converterhttpHeadersn = + NullAwareTypeConverter.wrap($converterhttpHeaders); +} + +class PlaylistMediaTableData extends DataClass + implements Insertable { + final int id; + final int playlistId; + final String uri; + final Map? extras; + final Map? httpHeaders; + const PlaylistMediaTableData( + {required this.id, + required this.playlistId, + required this.uri, + this.extras, + this.httpHeaders}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['playlist_id'] = Variable(playlistId); + map['uri'] = Variable(uri); + if (!nullToAbsent || extras != null) { + map['extras'] = Variable( + $PlaylistMediaTableTable.$converterextrasn.toSql(extras)); + } + if (!nullToAbsent || httpHeaders != null) { + map['http_headers'] = Variable( + $PlaylistMediaTableTable.$converterhttpHeadersn.toSql(httpHeaders)); + } + return map; + } + + PlaylistMediaTableCompanion toCompanion(bool nullToAbsent) { + return PlaylistMediaTableCompanion( + id: Value(id), + playlistId: Value(playlistId), + uri: Value(uri), + extras: + extras == null && nullToAbsent ? const Value.absent() : Value(extras), + httpHeaders: httpHeaders == null && nullToAbsent + ? const Value.absent() + : Value(httpHeaders), + ); + } + + factory PlaylistMediaTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PlaylistMediaTableData( + id: serializer.fromJson(json['id']), + playlistId: serializer.fromJson(json['playlistId']), + uri: serializer.fromJson(json['uri']), + extras: serializer.fromJson?>(json['extras']), + httpHeaders: + serializer.fromJson?>(json['httpHeaders']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'playlistId': serializer.toJson(playlistId), + 'uri': serializer.toJson(uri), + 'extras': serializer.toJson?>(extras), + 'httpHeaders': serializer.toJson?>(httpHeaders), + }; + } + + PlaylistMediaTableData copyWith( + {int? id, + int? playlistId, + String? uri, + Value?> extras = const Value.absent(), + Value?> httpHeaders = const Value.absent()}) => + PlaylistMediaTableData( + id: id ?? this.id, + playlistId: playlistId ?? this.playlistId, + uri: uri ?? this.uri, + extras: extras.present ? extras.value : this.extras, + httpHeaders: httpHeaders.present ? httpHeaders.value : this.httpHeaders, + ); + @override + String toString() { + return (StringBuffer('PlaylistMediaTableData(') + ..write('id: $id, ') + ..write('playlistId: $playlistId, ') + ..write('uri: $uri, ') + ..write('extras: $extras, ') + ..write('httpHeaders: $httpHeaders') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, playlistId, uri, extras, httpHeaders); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PlaylistMediaTableData && + other.id == this.id && + other.playlistId == this.playlistId && + other.uri == this.uri && + other.extras == this.extras && + other.httpHeaders == this.httpHeaders); +} + +class PlaylistMediaTableCompanion + extends UpdateCompanion { + final Value id; + final Value playlistId; + final Value uri; + final Value?> extras; + final Value?> httpHeaders; + const PlaylistMediaTableCompanion({ + this.id = const Value.absent(), + this.playlistId = const Value.absent(), + this.uri = const Value.absent(), + this.extras = const Value.absent(), + this.httpHeaders = const Value.absent(), + }); + PlaylistMediaTableCompanion.insert({ + this.id = const Value.absent(), + required int playlistId, + required String uri, + this.extras = const Value.absent(), + this.httpHeaders = const Value.absent(), + }) : playlistId = Value(playlistId), + uri = Value(uri); + static Insertable custom({ + Expression? id, + Expression? playlistId, + Expression? uri, + Expression? extras, + Expression? httpHeaders, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (playlistId != null) 'playlist_id': playlistId, + if (uri != null) 'uri': uri, + if (extras != null) 'extras': extras, + if (httpHeaders != null) 'http_headers': httpHeaders, + }); + } + + PlaylistMediaTableCompanion copyWith( + {Value? id, + Value? playlistId, + Value? uri, + Value?>? extras, + Value?>? httpHeaders}) { + return PlaylistMediaTableCompanion( + id: id ?? this.id, + playlistId: playlistId ?? this.playlistId, + uri: uri ?? this.uri, + extras: extras ?? this.extras, + httpHeaders: httpHeaders ?? this.httpHeaders, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (playlistId.present) { + map['playlist_id'] = Variable(playlistId.value); + } + if (uri.present) { + map['uri'] = Variable(uri.value); + } + if (extras.present) { + map['extras'] = Variable( + $PlaylistMediaTableTable.$converterextrasn.toSql(extras.value)); + } + if (httpHeaders.present) { + map['http_headers'] = Variable($PlaylistMediaTableTable + .$converterhttpHeadersn + .toSql(httpHeaders.value)); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PlaylistMediaTableCompanion(') + ..write('id: $id, ') + ..write('playlistId: $playlistId, ') + ..write('uri: $uri, ') + ..write('extras: $extras, ') + ..write('httpHeaders: $httpHeaders') + ..write(')')) + .toString(); + } +} + abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(e); _$AppDatabaseManager get managers => _$AppDatabaseManager(this); @@ -2588,6 +3421,11 @@ abstract class _$AppDatabase extends GeneratedDatabase { $SkipSegmentTableTable(this); late final $SourceMatchTableTable sourceMatchTable = $SourceMatchTableTable(this); + late final $AudioPlayerStateTableTable audioPlayerStateTable = + $AudioPlayerStateTableTable(this); + late final $PlaylistTableTable playlistTable = $PlaylistTableTable(this); + late final $PlaylistMediaTableTable playlistMediaTable = + $PlaylistMediaTableTable(this); late final Index uniqueBlacklist = Index('unique_blacklist', 'CREATE UNIQUE INDEX unique_blacklist ON blacklist_table (element_type, element_id)'); late final Index uniqTrackMatch = Index('uniq_track_match', @@ -2603,6 +3441,9 @@ abstract class _$AppDatabase extends GeneratedDatabase { scrobblerTable, skipSegmentTable, sourceMatchTable, + audioPlayerStateTable, + playlistTable, + playlistMediaTable, uniqueBlacklist, uniqTrackMatch ]; @@ -3746,6 +4587,464 @@ class $$SourceMatchTableTableOrderingComposer ColumnOrderings(column, joinBuilders: joinBuilders)); } +typedef $$AudioPlayerStateTableTableInsertCompanionBuilder + = AudioPlayerStateTableCompanion Function({ + Value id, + required bool playing, + required double volume, + required PlaylistMode loopMode, + required bool shuffled, +}); +typedef $$AudioPlayerStateTableTableUpdateCompanionBuilder + = AudioPlayerStateTableCompanion Function({ + Value id, + Value playing, + Value volume, + Value loopMode, + Value shuffled, +}); + +class $$AudioPlayerStateTableTableTableManager extends RootTableManager< + _$AppDatabase, + $AudioPlayerStateTableTable, + AudioPlayerStateTableData, + $$AudioPlayerStateTableTableFilterComposer, + $$AudioPlayerStateTableTableOrderingComposer, + $$AudioPlayerStateTableTableProcessedTableManager, + $$AudioPlayerStateTableTableInsertCompanionBuilder, + $$AudioPlayerStateTableTableUpdateCompanionBuilder> { + $$AudioPlayerStateTableTableTableManager( + _$AppDatabase db, $AudioPlayerStateTableTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: $$AudioPlayerStateTableTableFilterComposer( + ComposerState(db, table)), + orderingComposer: $$AudioPlayerStateTableTableOrderingComposer( + ComposerState(db, table)), + getChildManagerBuilder: (p) => + $$AudioPlayerStateTableTableProcessedTableManager(p), + getUpdateCompanionBuilder: ({ + Value id = const Value.absent(), + Value playing = const Value.absent(), + Value volume = const Value.absent(), + Value loopMode = const Value.absent(), + Value shuffled = const Value.absent(), + }) => + AudioPlayerStateTableCompanion( + id: id, + playing: playing, + volume: volume, + loopMode: loopMode, + shuffled: shuffled, + ), + getInsertCompanionBuilder: ({ + Value id = const Value.absent(), + required bool playing, + required double volume, + required PlaylistMode loopMode, + required bool shuffled, + }) => + AudioPlayerStateTableCompanion.insert( + id: id, + playing: playing, + volume: volume, + loopMode: loopMode, + shuffled: shuffled, + ), + )); +} + +class $$AudioPlayerStateTableTableProcessedTableManager + extends ProcessedTableManager< + _$AppDatabase, + $AudioPlayerStateTableTable, + AudioPlayerStateTableData, + $$AudioPlayerStateTableTableFilterComposer, + $$AudioPlayerStateTableTableOrderingComposer, + $$AudioPlayerStateTableTableProcessedTableManager, + $$AudioPlayerStateTableTableInsertCompanionBuilder, + $$AudioPlayerStateTableTableUpdateCompanionBuilder> { + $$AudioPlayerStateTableTableProcessedTableManager(super.$state); +} + +class $$AudioPlayerStateTableTableFilterComposer + extends FilterComposer<_$AppDatabase, $AudioPlayerStateTableTable> { + $$AudioPlayerStateTableTableFilterComposer(super.$state); + ColumnFilters get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); + + ColumnFilters get playing => $state.composableBuilder( + column: $state.table.playing, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); + + ColumnFilters get volume => $state.composableBuilder( + column: $state.table.volume, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); + + ColumnWithTypeConverterFilters + get loopMode => $state.composableBuilder( + column: $state.table.loopMode, + builder: (column, joinBuilders) => ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + ColumnFilters get shuffled => $state.composableBuilder( + column: $state.table.shuffled, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); + + ComposableFilter playlistTableRefs( + ComposableFilter Function($$PlaylistTableTableFilterComposer f) f) { + final $$PlaylistTableTableFilterComposer composer = $state.composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $state.db.playlistTable, + getReferencedColumn: (t) => t.audioPlayerStateId, + builder: (joinBuilder, parentComposers) => + $$PlaylistTableTableFilterComposer(ComposerState($state.db, + $state.db.playlistTable, joinBuilder, parentComposers))); + return f(composer); + } +} + +class $$AudioPlayerStateTableTableOrderingComposer + extends OrderingComposer<_$AppDatabase, $AudioPlayerStateTableTable> { + $$AudioPlayerStateTableTableOrderingComposer(super.$state); + ColumnOrderings get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get playing => $state.composableBuilder( + column: $state.table.playing, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get volume => $state.composableBuilder( + column: $state.table.volume, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get loopMode => $state.composableBuilder( + column: $state.table.loopMode, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get shuffled => $state.composableBuilder( + column: $state.table.shuffled, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); +} + +typedef $$PlaylistTableTableInsertCompanionBuilder = PlaylistTableCompanion + Function({ + Value id, + required int audioPlayerStateId, + required int index, +}); +typedef $$PlaylistTableTableUpdateCompanionBuilder = PlaylistTableCompanion + Function({ + Value id, + Value audioPlayerStateId, + Value index, +}); + +class $$PlaylistTableTableTableManager extends RootTableManager< + _$AppDatabase, + $PlaylistTableTable, + PlaylistTableData, + $$PlaylistTableTableFilterComposer, + $$PlaylistTableTableOrderingComposer, + $$PlaylistTableTableProcessedTableManager, + $$PlaylistTableTableInsertCompanionBuilder, + $$PlaylistTableTableUpdateCompanionBuilder> { + $$PlaylistTableTableTableManager(_$AppDatabase db, $PlaylistTableTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: + $$PlaylistTableTableFilterComposer(ComposerState(db, table)), + orderingComposer: + $$PlaylistTableTableOrderingComposer(ComposerState(db, table)), + getChildManagerBuilder: (p) => + $$PlaylistTableTableProcessedTableManager(p), + getUpdateCompanionBuilder: ({ + Value id = const Value.absent(), + Value audioPlayerStateId = const Value.absent(), + Value index = const Value.absent(), + }) => + PlaylistTableCompanion( + id: id, + audioPlayerStateId: audioPlayerStateId, + index: index, + ), + getInsertCompanionBuilder: ({ + Value id = const Value.absent(), + required int audioPlayerStateId, + required int index, + }) => + PlaylistTableCompanion.insert( + id: id, + audioPlayerStateId: audioPlayerStateId, + index: index, + ), + )); +} + +class $$PlaylistTableTableProcessedTableManager extends ProcessedTableManager< + _$AppDatabase, + $PlaylistTableTable, + PlaylistTableData, + $$PlaylistTableTableFilterComposer, + $$PlaylistTableTableOrderingComposer, + $$PlaylistTableTableProcessedTableManager, + $$PlaylistTableTableInsertCompanionBuilder, + $$PlaylistTableTableUpdateCompanionBuilder> { + $$PlaylistTableTableProcessedTableManager(super.$state); +} + +class $$PlaylistTableTableFilterComposer + extends FilterComposer<_$AppDatabase, $PlaylistTableTable> { + $$PlaylistTableTableFilterComposer(super.$state); + ColumnFilters get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); + + ColumnFilters get index => $state.composableBuilder( + column: $state.table.index, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); + + $$AudioPlayerStateTableTableFilterComposer get audioPlayerStateId { + final $$AudioPlayerStateTableTableFilterComposer composer = + $state.composerBuilder( + composer: this, + getCurrentColumn: (t) => t.audioPlayerStateId, + referencedTable: $state.db.audioPlayerStateTable, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, parentComposers) => + $$AudioPlayerStateTableTableFilterComposer(ComposerState( + $state.db, + $state.db.audioPlayerStateTable, + joinBuilder, + parentComposers))); + return composer; + } + + ComposableFilter playlistMediaTableRefs( + ComposableFilter Function($$PlaylistMediaTableTableFilterComposer f) f) { + final $$PlaylistMediaTableTableFilterComposer composer = $state + .composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $state.db.playlistMediaTable, + getReferencedColumn: (t) => t.playlistId, + builder: (joinBuilder, parentComposers) => + $$PlaylistMediaTableTableFilterComposer(ComposerState( + $state.db, + $state.db.playlistMediaTable, + joinBuilder, + parentComposers))); + return f(composer); + } +} + +class $$PlaylistTableTableOrderingComposer + extends OrderingComposer<_$AppDatabase, $PlaylistTableTable> { + $$PlaylistTableTableOrderingComposer(super.$state); + ColumnOrderings get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get index => $state.composableBuilder( + column: $state.table.index, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + $$AudioPlayerStateTableTableOrderingComposer get audioPlayerStateId { + final $$AudioPlayerStateTableTableOrderingComposer composer = + $state.composerBuilder( + composer: this, + getCurrentColumn: (t) => t.audioPlayerStateId, + referencedTable: $state.db.audioPlayerStateTable, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, parentComposers) => + $$AudioPlayerStateTableTableOrderingComposer(ComposerState( + $state.db, + $state.db.audioPlayerStateTable, + joinBuilder, + parentComposers))); + return composer; + } +} + +typedef $$PlaylistMediaTableTableInsertCompanionBuilder + = PlaylistMediaTableCompanion Function({ + Value id, + required int playlistId, + required String uri, + Value?> extras, + Value?> httpHeaders, +}); +typedef $$PlaylistMediaTableTableUpdateCompanionBuilder + = PlaylistMediaTableCompanion Function({ + Value id, + Value playlistId, + Value uri, + Value?> extras, + Value?> httpHeaders, +}); + +class $$PlaylistMediaTableTableTableManager extends RootTableManager< + _$AppDatabase, + $PlaylistMediaTableTable, + PlaylistMediaTableData, + $$PlaylistMediaTableTableFilterComposer, + $$PlaylistMediaTableTableOrderingComposer, + $$PlaylistMediaTableTableProcessedTableManager, + $$PlaylistMediaTableTableInsertCompanionBuilder, + $$PlaylistMediaTableTableUpdateCompanionBuilder> { + $$PlaylistMediaTableTableTableManager( + _$AppDatabase db, $PlaylistMediaTableTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: + $$PlaylistMediaTableTableFilterComposer(ComposerState(db, table)), + orderingComposer: $$PlaylistMediaTableTableOrderingComposer( + ComposerState(db, table)), + getChildManagerBuilder: (p) => + $$PlaylistMediaTableTableProcessedTableManager(p), + getUpdateCompanionBuilder: ({ + Value id = const Value.absent(), + Value playlistId = const Value.absent(), + Value uri = const Value.absent(), + Value?> extras = const Value.absent(), + Value?> httpHeaders = const Value.absent(), + }) => + PlaylistMediaTableCompanion( + id: id, + playlistId: playlistId, + uri: uri, + extras: extras, + httpHeaders: httpHeaders, + ), + getInsertCompanionBuilder: ({ + Value id = const Value.absent(), + required int playlistId, + required String uri, + Value?> extras = const Value.absent(), + Value?> httpHeaders = const Value.absent(), + }) => + PlaylistMediaTableCompanion.insert( + id: id, + playlistId: playlistId, + uri: uri, + extras: extras, + httpHeaders: httpHeaders, + ), + )); +} + +class $$PlaylistMediaTableTableProcessedTableManager + extends ProcessedTableManager< + _$AppDatabase, + $PlaylistMediaTableTable, + PlaylistMediaTableData, + $$PlaylistMediaTableTableFilterComposer, + $$PlaylistMediaTableTableOrderingComposer, + $$PlaylistMediaTableTableProcessedTableManager, + $$PlaylistMediaTableTableInsertCompanionBuilder, + $$PlaylistMediaTableTableUpdateCompanionBuilder> { + $$PlaylistMediaTableTableProcessedTableManager(super.$state); +} + +class $$PlaylistMediaTableTableFilterComposer + extends FilterComposer<_$AppDatabase, $PlaylistMediaTableTable> { + $$PlaylistMediaTableTableFilterComposer(super.$state); + ColumnFilters get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); + + ColumnFilters get uri => $state.composableBuilder( + column: $state.table.uri, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); + + ColumnWithTypeConverterFilters?, Map, + String> + get extras => $state.composableBuilder( + column: $state.table.extras, + builder: (column, joinBuilders) => ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + ColumnWithTypeConverterFilters?, Map, + String> + get httpHeaders => $state.composableBuilder( + column: $state.table.httpHeaders, + builder: (column, joinBuilders) => ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + $$PlaylistTableTableFilterComposer get playlistId { + final $$PlaylistTableTableFilterComposer composer = $state.composerBuilder( + composer: this, + getCurrentColumn: (t) => t.playlistId, + referencedTable: $state.db.playlistTable, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, parentComposers) => + $$PlaylistTableTableFilterComposer(ComposerState($state.db, + $state.db.playlistTable, joinBuilder, parentComposers))); + return composer; + } +} + +class $$PlaylistMediaTableTableOrderingComposer + extends OrderingComposer<_$AppDatabase, $PlaylistMediaTableTable> { + $$PlaylistMediaTableTableOrderingComposer(super.$state); + ColumnOrderings get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get uri => $state.composableBuilder( + column: $state.table.uri, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get extras => $state.composableBuilder( + column: $state.table.extras, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get httpHeaders => $state.composableBuilder( + column: $state.table.httpHeaders, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + $$PlaylistTableTableOrderingComposer get playlistId { + final $$PlaylistTableTableOrderingComposer composer = + $state.composerBuilder( + composer: this, + getCurrentColumn: (t) => t.playlistId, + referencedTable: $state.db.playlistTable, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, parentComposers) => + $$PlaylistTableTableOrderingComposer(ComposerState($state.db, + $state.db.playlistTable, joinBuilder, parentComposers))); + return composer; + } +} + class _$AppDatabaseManager { final _$AppDatabase _db; _$AppDatabaseManager(this._db); @@ -3761,4 +5060,10 @@ class _$AppDatabaseManager { $$SkipSegmentTableTableTableManager(_db, _db.skipSegmentTable); $$SourceMatchTableTableTableManager get sourceMatchTable => $$SourceMatchTableTableTableManager(_db, _db.sourceMatchTable); + $$AudioPlayerStateTableTableTableManager get audioPlayerStateTable => + $$AudioPlayerStateTableTableTableManager(_db, _db.audioPlayerStateTable); + $$PlaylistTableTableTableManager get playlistTable => + $$PlaylistTableTableTableManager(_db, _db.playlistTable); + $$PlaylistMediaTableTableTableManager get playlistMediaTable => + $$PlaylistMediaTableTableTableManager(_db, _db.playlistMediaTable); } diff --git a/lib/models/database/tables/audio_player_state.dart b/lib/models/database/tables/audio_player_state.dart new file mode 100644 index 00000000..45f5ffd9 --- /dev/null +++ b/lib/models/database/tables/audio_player_state.dart @@ -0,0 +1,27 @@ +part of '../database.dart'; + +class AudioPlayerStateTable extends Table { + IntColumn get id => integer().autoIncrement()(); + BoolColumn get playing => boolean()(); + RealColumn get volume => real()(); + TextColumn get loopMode => textEnum()(); + BoolColumn get shuffled => boolean()(); +} + +class PlaylistTable extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get audioPlayerStateId => + integer().references(AudioPlayerStateTable, #id)(); + IntColumn get index => integer()(); +} + +class PlaylistMediaTable extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get playlistId => integer().references(PlaylistTable, #id)(); + + TextColumn get uri => text()(); + TextColumn get extras => + text().nullable().map(const MapTypeConverter())(); + TextColumn get httpHeaders => + text().nullable().map(const MapTypeConverter())(); +} diff --git a/lib/models/database/typeconverters/map.dart b/lib/models/database/typeconverters/map.dart new file mode 100644 index 00000000..0b0ff7e0 --- /dev/null +++ b/lib/models/database/typeconverters/map.dart @@ -0,0 +1,15 @@ +part of '../database.dart'; + +class MapTypeConverter extends TypeConverter, String> { + const MapTypeConverter(); + + @override + fromSql(String fromDb) { + return json.decode(fromDb) as Map; + } + + @override + toSql(value) { + return json.encode(value); + } +} diff --git a/lib/modules/player/player_controls.dart b/lib/modules/player/player_controls.dart index ba69560c..a1a3ffcf 100644 --- a/lib/modules/player/player_controls.dart +++ b/lib/modules/player/player_controls.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:media_kit/media_kit.dart'; import 'package:palette_generator/palette_generator.dart'; import 'package:spotube/collections/spotube_icons.dart'; @@ -12,7 +13,6 @@ import 'package:spotube/modules/player/use_progress.dart'; import 'package:spotube/models/logger.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:spotube/services/audio_player/loop_mode.dart'; class PlayerControls extends HookConsumerWidget { final PaletteGenerator? palette; @@ -234,38 +234,29 @@ class PlayerControls extends HookConsumerWidget { ? null : playlistNotifier.next, ), - StreamBuilder( + StreamBuilder( stream: audioPlayer.loopModeStream, builder: (context, snapshot) { - final loopMode = snapshot.data ?? PlaybackLoopMode.none; + final loopMode = snapshot.data ?? PlaylistMode.none; return IconButton( - tooltip: loopMode == PlaybackLoopMode.one + tooltip: loopMode == PlaylistMode.single ? context.l10n.loop_track - : loopMode == PlaybackLoopMode.all + : loopMode == PlaylistMode.loop ? context.l10n.repeat_playlist : null, icon: Icon( - loopMode == PlaybackLoopMode.one + loopMode == PlaylistMode.single ? SpotubeIcons.repeatOne : SpotubeIcons.repeat, ), - style: loopMode == PlaybackLoopMode.one || - loopMode == PlaybackLoopMode.all + style: loopMode == PlaylistMode.single || + loopMode == PlaylistMode.loop ? activeButtonStyle : buttonStyle, onPressed: playlist.isFetching == true ? null : () async { - audioPlayer.setLoopMode( - switch (loopMode) { - PlaybackLoopMode.all => - PlaybackLoopMode.one, - PlaybackLoopMode.one => - PlaybackLoopMode.none, - PlaybackLoopMode.none => - PlaybackLoopMode.all, - }, - ); + await audioPlayer.setLoopMode(loopMode); }, ); }), diff --git a/lib/modules/player/use_progress.dart b/lib/modules/player/use_progress.dart index 15a979af..eaea638e 100644 --- a/lib/modules/player/use_progress.dart +++ b/lib/modules/player/use_progress.dart @@ -1,4 +1,3 @@ -import 'package:async/async.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; @@ -19,26 +18,13 @@ import 'package:spotube/services/audio_player/audio_player.dart'; final sliderValue = position.value.inSeconds; useEffect(() { - final durationOperation = - CancelableOperation.fromFuture(audioPlayer.duration); - durationOperation.then((value) { - if (value != null) { - duration.value = value; - } - }); + duration.value = audioPlayer.duration; final durationSubscription = audioPlayer.durationStream.listen((event) { duration.value = event; }); - final positionOperation = - CancelableOperation.fromFuture(audioPlayer.position); - - positionOperation.then((value) { - if (value != null) { - position.value = value; - } - }); + position.value = audioPlayer.position; var lastPosition = position.value; @@ -54,9 +40,7 @@ import 'package:spotube/services/audio_player/audio_player.dart'; }); return () { - positionOperation.cancel(); positionSubscription.cancel(); - durationOperation.cancel(); durationSubscription.cancel(); }; }, []); diff --git a/lib/pages/connect/control/control.dart b/lib/pages/connect/control/control.dart index eb2c48c5..d27b7867 100644 --- a/lib/pages/connect/control/control.dart +++ b/lib/pages/connect/control/control.dart @@ -16,7 +16,7 @@ import 'package:spotube/extensions/image.dart'; import 'package:spotube/pages/track/track.dart'; import 'package:spotube/provider/connect/clients.dart'; import 'package:spotube/provider/connect/connect.dart'; -import 'package:spotube/services/audio_player/loop_mode.dart'; +import 'package:media_kit/media_kit.dart' hide Track; import 'package:spotube/utils/service_utils.dart'; class RemotePlayerQueue extends ConsumerWidget { @@ -244,18 +244,18 @@ class ConnectControlPage extends HookConsumerWidget { : connectNotifier.next, ), IconButton( - tooltip: loopMode == PlaybackLoopMode.one + tooltip: loopMode == PlaylistMode.single ? context.l10n.loop_track - : loopMode == PlaybackLoopMode.all + : loopMode == PlaylistMode.loop ? context.l10n.repeat_playlist : null, icon: Icon( - loopMode == PlaybackLoopMode.one + loopMode == PlaylistMode.single ? SpotubeIcons.repeatOne : SpotubeIcons.repeat, ), - style: loopMode == PlaybackLoopMode.one || - loopMode == PlaybackLoopMode.all + style: loopMode == PlaylistMode.single || + loopMode == PlaylistMode.loop ? activeButtonStyle : buttonStyle, onPressed: playlist.activeTrack == null @@ -263,12 +263,11 @@ class ConnectControlPage extends HookConsumerWidget { : () async { connectNotifier.setLoopMode( switch (loopMode) { - PlaybackLoopMode.all => - PlaybackLoopMode.one, - PlaybackLoopMode.one => - PlaybackLoopMode.none, - PlaybackLoopMode.none => - PlaybackLoopMode.all, + PlaylistMode.loop => + PlaylistMode.single, + PlaylistMode.single => + PlaylistMode.none, + PlaylistMode.none => PlaylistMode.loop, }, ); }, diff --git a/lib/provider/audio_player/audio_player.dart b/lib/provider/audio_player/audio_player.dart new file mode 100644 index 00000000..747c78e6 --- /dev/null +++ b/lib/provider/audio_player/audio_player.dart @@ -0,0 +1,225 @@ +import 'package:drift/drift.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:media_kit/media_kit.dart' hide Track; +import 'package:spotify/spotify.dart' hide Playlist; +import 'package:spotube/models/database/database.dart'; +import 'package:spotube/provider/audio_player/state.dart'; +import 'package:spotube/provider/database/database.dart'; +import 'package:spotube/services/audio_player/audio_player.dart'; + +class AudioPlayerNotifier extends Notifier { + Future _syncSavedState() async { + final database = ref.read(databaseProvider); + + var playerState = + await database.select(database.audioPlayerStateTable).getSingleOrNull(); + + if (playerState == null) { + await database.into(database.audioPlayerStateTable).insert( + AudioPlayerStateTableCompanion.insert( + playing: audioPlayer.isPlaying, + volume: audioPlayer.volume, + loopMode: audioPlayer.loopMode, + shuffled: audioPlayer.isShuffled, + id: const Value(0), + ), + ); + + playerState = + await database.select(database.audioPlayerStateTable).getSingle(); + } else { + await audioPlayer.setVolume(playerState.volume); + await audioPlayer.setLoopMode(playerState.loopMode); + await audioPlayer.setShuffle(playerState.shuffled); + } + + var playlist = + await database.select(database.playlistTable).getSingleOrNull(); + var medias = await database.select(database.playlistMediaTable).get(); + + if (playlist == null) { + await database.into(database.playlistTable).insert( + PlaylistTableCompanion.insert( + audioPlayerStateId: 0, + index: audioPlayer.playlist.index, + id: const Value(0), + ), + ); + + playlist = await database.select(database.playlistTable).getSingle(); + } + + if (medias.isEmpty && audioPlayer.playlist.medias.isNotEmpty) { + await database.batch((batch) { + batch.insertAll( + database.playlistMediaTable, + [ + for (final media in audioPlayer.playlist.medias) + PlaylistMediaTableCompanion.insert( + playlistId: playlist!.id, + uri: media.uri, + extras: Value(media.extras), + httpHeaders: Value(media.httpHeaders), + ), + ], + ); + }); + } else { + await audioPlayer.openPlaylist( + medias + .map((media) => Media( + media.uri, + extras: media.extras, + httpHeaders: media.httpHeaders, + )) + .toList(), + initialIndex: playlist.index, + ); + } + } + + Future _updatePlayerState( + AudioPlayerStateTableCompanion companion, + ) async { + final database = ref.read(databaseProvider); + + await (database.update(database.audioPlayerStateTable) + ..where((tb) => tb.id.equals(0))) + .write(companion); + } + + Future _updatePlaylist( + Playlist playlist, + ) async { + final database = ref.read(databaseProvider); + + await database.batch((batch) { + batch.update( + database.playlistTable, + PlaylistTableCompanion(index: Value(playlist.index)), + where: (tb) => tb.id.equals(0), + ); + + batch.deleteAll(database.playlistMediaTable); + + if (playlist.medias.isEmpty) return; + batch.insertAll( + database.playlistMediaTable, + [ + for (final media in playlist.medias) + PlaylistMediaTableCompanion.insert( + playlistId: 0, + uri: media.uri, + extras: Value(media.extras), + httpHeaders: Value(media.httpHeaders), + ), + ], + ); + }); + } + + @override + build() { + final subscriptions = [ + audioPlayer.playingStream.listen((playing) async { + state = state.copyWith(playing: playing); + + await _updatePlayerState( + AudioPlayerStateTableCompanion( + playing: Value(playing), + ), + ); + }), + audioPlayer.volumeStream.listen((volume) async { + state = state.copyWith(volume: volume); + + await _updatePlayerState( + AudioPlayerStateTableCompanion( + volume: Value(volume), + ), + ); + }), + audioPlayer.loopModeStream.listen((loopMode) async { + state = state.copyWith(loopMode: loopMode); + + await _updatePlayerState( + AudioPlayerStateTableCompanion( + loopMode: Value(loopMode), + ), + ); + }), + audioPlayer.shuffledStream.listen((shuffled) async { + state = state.copyWith(shuffled: shuffled); + + await _updatePlayerState( + AudioPlayerStateTableCompanion( + shuffled: Value(shuffled), + ), + ); + }), + audioPlayer.playlistStream.listen((playlist) async { + state = state.copyWith(playlist: playlist); + + await _updatePlaylist(playlist); + }), + ]; + + _syncSavedState(); + + ref.onDispose(() { + for (final subscription in subscriptions) { + subscription.cancel(); + } + }); + + return AudioPlayerState( + loopMode: audioPlayer.loopMode, + playing: audioPlayer.isPlaying, + playlist: audioPlayer.playlist, + shuffled: audioPlayer.isShuffled, + volume: audioPlayer.volume, + ); + } + + // Tracks related methods + + Future addTrack(Track track) async { + await audioPlayer.addTrack(SpotubeMedia(track)); + } + + Future addTracks(Iterable tracks) async { + for (final track in tracks) { + await addTrack(track); + } + } + + Future removeTrack(Track track) async { + final index = state.tracks.indexWhere((element) => element == track); + + if (index == -1) return; + + await audioPlayer.removeTrack(index); + } + + Future removeTracks(Iterable tracks) async { + for (final track in tracks) { + await removeTrack(track); + } + } + + Future load( + List track, { + required int initialIndex, + bool autoPlay = false, + }) async { + await audioPlayer.openPlaylist( + track.map((t) => SpotubeMedia(t)).toList(), + initialIndex: initialIndex, + autoPlay: autoPlay, + ); + } +} + +final audioPlayerProvider = NotifierProvider( + () => AudioPlayerNotifier(), +); \ No newline at end of file diff --git a/lib/provider/audio_player/state.dart b/lib/provider/audio_player/state.dart new file mode 100644 index 00000000..3c874011 --- /dev/null +++ b/lib/provider/audio_player/state.dart @@ -0,0 +1,42 @@ +import 'package:media_kit/media_kit.dart' hide Track; +import 'package:spotify/spotify.dart' hide Playlist; +import 'package:spotube/services/audio_player/audio_player.dart'; + +class AudioPlayerState { + final bool playing; + final double volume; + final PlaylistMode loopMode; + final bool shuffled; + final Playlist playlist; + + final List tracks; + + AudioPlayerState({ + required this.playing, + required this.volume, + required this.loopMode, + required this.shuffled, + required this.playlist, + List? tracks, + }) : tracks = tracks ?? + playlist.medias + .map((media) => SpotubeMedia.fromMedia(media).track) + .toList(); + + AudioPlayerState copyWith({ + bool? playing, + double? volume, + PlaylistMode? loopMode, + bool? shuffled, + Playlist? playlist, + }) { + return AudioPlayerState( + playing: playing ?? this.playing, + volume: volume ?? this.volume, + loopMode: loopMode ?? this.loopMode, + shuffled: shuffled ?? this.shuffled, + playlist: playlist ?? this.playlist, + tracks: playlist == null ? tracks : null, + ); + } +} diff --git a/lib/provider/connect/connect.dart b/lib/provider/connect/connect.dart index feb9fbd2..c6014445 100644 --- a/lib/provider/connect/connect.dart +++ b/lib/provider/connect/connect.dart @@ -1,13 +1,13 @@ import 'dart:convert'; +import 'package:media_kit/media_kit.dart' hide Track; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotify/spotify.dart'; +import 'package:spotify/spotify.dart' hide Playlist; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/models/logger.dart'; import 'package:spotube/provider/connect/clients.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; -import 'package:spotube/services/audio_player/loop_mode.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/status.dart' as status; @@ -27,8 +27,8 @@ final shuffleProvider = StateProvider( (ref) => false, ); -final loopModeProvider = StateProvider( - (ref) => PlaybackLoopMode.none, +final loopModeProvider = StateProvider( + (ref) => PlaylistMode.none, ); final queueProvider = StateProvider( @@ -158,7 +158,7 @@ class ConnectNotifier extends AsyncNotifier { emit(WebSocketShuffleEvent(value)); } - Future setLoopMode(PlaybackLoopMode value) async { + Future setLoopMode(PlaylistMode value) async { emit(WebSocketLoopEvent(value)); } diff --git a/lib/provider/tray_manager/tray_menu.dart b/lib/provider/tray_manager/tray_menu.dart index cb793707..35aca4f5 100644 --- a/lib/provider/tray_manager/tray_menu.dart +++ b/lib/provider/tray_manager/tray_menu.dart @@ -3,11 +3,11 @@ import 'dart:io'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:spotube/services/audio_player/loop_mode.dart'; +import 'package:media_kit/media_kit.dart' hide Track; import 'package:tray_manager/tray_manager.dart'; import 'package:window_manager/window_manager.dart'; -final audioPlayerLoopMode = StreamProvider((ref) { +final audioPlayerLoopMode = StreamProvider((ref) { return audioPlayer.loopModeStream; }); @@ -23,7 +23,7 @@ final trayMenuProvider = Provider((ref) { final isPlaybackPlaying = ref.watch(proxyPlaylistProvider.select((s) => s.activeTrack != null)); final isLoopOne = - ref.watch(audioPlayerLoopMode).asData?.value == PlaybackLoopMode.one; + ref.watch(audioPlayerLoopMode).asData?.value == PlaylistMode.single; final isShuffled = ref.watch(audioPlayerShuffleMode).asData?.value ?? false; final isPlaying = ref.watch(audioPlayerPlaying).asData?.value ?? false; @@ -75,7 +75,7 @@ final trayMenuProvider = Provider((ref) { checked: isLoopOne, onClick: (menuItem) { audioPlayer.setLoopMode( - isLoopOne ? PlaybackLoopMode.none : PlaybackLoopMode.one, + isLoopOne ? PlaylistMode.none : PlaylistMode.single, ); }, ), diff --git a/lib/services/audio_player/audio_player.dart b/lib/services/audio_player/audio_player.dart index df23039c..713d518b 100644 --- a/lib/services/audio_player/audio_player.dart +++ b/lib/services/audio_player/audio_player.dart @@ -1,16 +1,16 @@ import 'dart:io'; +import 'package:media_kit/media_kit.dart' hide Track; import 'package:spotube/provider/server/server.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter/foundation.dart'; -import 'package:spotify/spotify.dart'; +import 'package:spotify/spotify.dart' hide Playlist; import 'package:spotube/models/local_track.dart'; import 'package:spotube/services/audio_player/custom_player.dart'; import 'dart:async'; import 'package:media_kit/media_kit.dart' as mk; -import 'package:spotube/services/audio_player/loop_mode.dart'; import 'package:spotube/services/audio_player/playback_state.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; @@ -66,15 +66,19 @@ abstract class AudioPlayerInterface { bool get mkSupportedPlatform => _mkSupportedPlatform; - Future get duration async { + Duration get duration { return _mkPlayer.state.duration; } - Future get position async { + Playlist get playlist { + return _mkPlayer.state.playlist; + } + + Duration get position { return _mkPlayer.state.position; } - Future get bufferedPosition async { + Duration get bufferedPosition { return _mkPlayer.state.buffer; } @@ -111,8 +115,8 @@ abstract class AudioPlayerInterface { return _mkPlayer.shuffled; } - PlaybackLoopMode get loopMode { - return PlaybackLoopMode.fromPlaylistMode(_mkPlayer.state.playlistMode); + PlaylistMode get loopMode { + return _mkPlayer.state.playlistMode; } /// Returns the current volume of the player, between 0 and 1 diff --git a/lib/services/audio_player/audio_player_impl.dart b/lib/services/audio_player/audio_player_impl.dart index 58868aed..82c8c906 100644 --- a/lib/services/audio_player/audio_player_impl.dart +++ b/lib/services/audio_player/audio_player_impl.dart @@ -65,7 +65,7 @@ class SpotubeAudioPlayer extends AudioPlayerInterface } String? get nextSource { - if (loopMode == PlaybackLoopMode.all && + if (loopMode == PlaylistMode.loop && _mkPlayer.state.playlist.index == _mkPlayer.state.playlist.medias.length - 1) { return sources.first; @@ -77,8 +77,7 @@ class SpotubeAudioPlayer extends AudioPlayerInterface } String? get previousSource { - if (loopMode == PlaybackLoopMode.all && - _mkPlayer.state.playlist.index == 0) { + if (loopMode == PlaylistMode.loop && _mkPlayer.state.playlist.index == 0) { return sources.last; } @@ -125,8 +124,8 @@ class SpotubeAudioPlayer extends AudioPlayerInterface await _mkPlayer.setShuffle(shuffle); } - Future setLoopMode(PlaybackLoopMode loop) async { - await _mkPlayer.setPlaylistMode(loop.toPlaylistMode()); + Future setLoopMode(PlaylistMode loop) async { + await _mkPlayer.setPlaylistMode(loop); } Future setAudioNormalization(bool normalize) async { diff --git a/lib/services/audio_player/audio_players_streams_mixin.dart b/lib/services/audio_player/audio_players_streams_mixin.dart index f6fe0630..03ce0d5d 100644 --- a/lib/services/audio_player/audio_players_streams_mixin.dart +++ b/lib/services/audio_player/audio_players_streams_mixin.dart @@ -71,12 +71,12 @@ mixin SpotubeAudioPlayersStreams on AudioPlayerInterface { // } } - Stream get loopModeStream { + Stream get loopModeStream { // if (mkSupportedPlatform) { - return _mkPlayer.stream.playlistMode.map(PlaybackLoopMode.fromPlaylistMode); + return _mkPlayer.stream.playlistMode; // } else { // return _justAudio!.loopModeStream - // .map(PlaybackLoopMode.fromLoopMode) + // .map(PlaylistMode.fromLoopMode) // ; // } } diff --git a/lib/services/audio_player/loop_mode.dart b/lib/services/audio_player/loop_mode.dart deleted file mode 100644 index 78da43ba..00000000 --- a/lib/services/audio_player/loop_mode.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'package:audio_service/audio_service.dart'; -import 'package:media_kit/media_kit.dart'; - -/// An unified loop mode for both [LoopMode] and [PlaylistMode] -enum PlaybackLoopMode { - all, - one, - none; - - // static PlaybackLoopMode fromLoopMode(LoopMode loopMode) { - // switch (loopMode) { - // case LoopMode.all: - // return PlaybackLoopMode.all; - // case LoopMode.one: - // return PlaybackLoopMode.one; - // case LoopMode.off: - // return PlaybackLoopMode.none; - // } - // } - - // LoopMode toLoopMode() { - // switch (this) { - // case PlaybackLoopMode.all: - // return LoopMode.all; - // case PlaybackLoopMode.one: - // return LoopMode.one; - // case PlaybackLoopMode.none: - // return LoopMode.off; - // } - // } - - static PlaybackLoopMode fromPlaylistMode(PlaylistMode mode) { - switch (mode) { - case PlaylistMode.single: - return PlaybackLoopMode.one; - case PlaylistMode.loop: - return PlaybackLoopMode.all; - case PlaylistMode.none: - return PlaybackLoopMode.none; - } - } - - PlaylistMode toPlaylistMode() { - switch (this) { - case PlaybackLoopMode.all: - return PlaylistMode.loop; - case PlaybackLoopMode.one: - return PlaylistMode.single; - case PlaybackLoopMode.none: - return PlaylistMode.none; - } - } - - static PlaybackLoopMode fromAudioServiceRepeatMode( - AudioServiceRepeatMode mode) { - switch (mode) { - case AudioServiceRepeatMode.all: - case AudioServiceRepeatMode.group: - return PlaybackLoopMode.all; - case AudioServiceRepeatMode.one: - return PlaybackLoopMode.one; - case AudioServiceRepeatMode.none: - return PlaybackLoopMode.none; - } - } - - AudioServiceRepeatMode toAudioServiceRepeatMode() { - switch (this) { - case PlaybackLoopMode.all: - return AudioServiceRepeatMode.all; - case PlaybackLoopMode.one: - return AudioServiceRepeatMode.one; - case PlaybackLoopMode.none: - return AudioServiceRepeatMode.none; - } - } - - static PlaybackLoopMode fromString(String? value) { - switch (value) { - case 'all': - return PlaybackLoopMode.all; - case 'one': - return PlaybackLoopMode.one; - case 'none': - return PlaybackLoopMode.none; - default: - return PlaybackLoopMode.none; - } - } -} diff --git a/lib/services/audio_services/mobile_audio_service.dart b/lib/services/audio_services/mobile_audio_service.dart index 62cc8552..3dbae18f 100644 --- a/lib/services/audio_services/mobile_audio_service.dart +++ b/lib/services/audio_services/mobile_audio_service.dart @@ -5,7 +5,7 @@ import 'package:audio_session/audio_session.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:spotube/services/audio_player/loop_mode.dart'; +import 'package:media_kit/media_kit.dart' hide Track; class MobileAudioService extends BaseAudioHandler { AudioSession? session; @@ -91,9 +91,13 @@ class MobileAudioService extends BaseAudioHandler { @override Future setRepeatMode(AudioServiceRepeatMode repeatMode) async { super.setRepeatMode(repeatMode); - audioPlayer.setLoopMode( - PlaybackLoopMode.fromAudioServiceRepeatMode(repeatMode), - ); + audioPlayer.setLoopMode(switch (repeatMode) { + AudioServiceRepeatMode.all || + AudioServiceRepeatMode.group => + PlaylistMode.loop, + AudioServiceRepeatMode.one => PlaylistMode.single, + _ => PlaylistMode.none, + }); } @override @@ -120,7 +124,6 @@ class MobileAudioService extends BaseAudioHandler { } Future _transformEvent() async { - final position = (await audioPlayer.position) ?? Duration.zero; return PlaybackState( controls: [ MediaControl.skipToPrevious, @@ -133,12 +136,16 @@ class MobileAudioService extends BaseAudioHandler { }, androidCompactActionIndices: const [0, 1, 2], playing: audioPlayer.isPlaying, - updatePosition: position, - bufferedPosition: await audioPlayer.bufferedPosition ?? Duration.zero, + updatePosition: audioPlayer.position, + bufferedPosition: audioPlayer.bufferedPosition, shuffleMode: audioPlayer.isShuffled == true ? AudioServiceShuffleMode.all : AudioServiceShuffleMode.none, - repeatMode: (audioPlayer.loopMode).toAudioServiceRepeatMode(), + repeatMode: switch (audioPlayer.loopMode) { + PlaylistMode.loop => AudioServiceRepeatMode.all, + PlaylistMode.single => AudioServiceRepeatMode.one, + _ => AudioServiceRepeatMode.none, + }, processingState: playlist.isFetching == true ? AudioProcessingState.loading : AudioProcessingState.ready,